Skip to content

Commit

Permalink
Merge pull request markboard-io#31 from CulturalProfessor/feature-fav…
Browse files Browse the repository at this point in the history
…ourites

Feat :Favourite Boards
  • Loading branch information
shiqimei authored Apr 25, 2023
2 parents f23f54e + f63f390 commit c6e42a2
Show file tree
Hide file tree
Showing 16 changed files with 287 additions and 75 deletions.
14 changes: 14 additions & 0 deletions imports/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1528,6 +1528,20 @@ export const StarIcon = (
</svg>
)

export const FilledStarIcon = (
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 16 16'
width='16'
height='16'
preserveAspectRatio='xMidYMid meet'
>
<g fill='#f0c000'>
<path d='M8 0.350006C8.30049 0.350006 8.57286 0.526798 8.6952 0.801258L10.6192 5.11752L15.3187 5.61353C15.6176 5.64507 15.8699 5.84948 15.9627 6.13526C16.0556 6.42104 15.9716 6.73471 15.7484 6.93588L12.2379 10.0995L13.2184 14.7223C13.2808 15.0163 13.1643 15.3194 12.9212 15.496C12.6781 15.6726 12.3539 15.6897 12.0936 15.5396L8 13.1785L3.90643 15.5396C3.64614 15.6897 3.32187 15.6726 3.07877 15.496C2.83567 15.3194 2.71923 15.0163 2.78158 14.7223L3.76209 10.0995L0.251619 6.93588C0.0283977 6.73471 -0.0555759 6.42104 0.0372811 6.13526C0.130138 5.84948 0.382443 5.64507 0.681275 5.61353L5.38083 5.11752L7.3048 0.801258C7.42714 0.526798 7.69951 0.350006 8 0.350006Z'></path>
</g>
</svg>
)

export const AddPeopleIcon = (
<svg
xmlns='http://www.w3.org/2000/svg'
Expand Down
24 changes: 22 additions & 2 deletions imports/excalidraw/components/TopRightControls.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
import React from 'react'
import styles from './TopRightControls.module.scss'
import cx from 'clsx'
import { MoreIcon, StarIcon } from '/imports/components/icons'
import { MoreIcon, StarIcon, FilledStarIcon } from '/imports/components/icons'
import { Services } from '/imports/services/client'
import { throttle } from 'lodash'
import { getCurrentBoardId } from '/imports/utils/board'
import { useFavoriteBoards, usePrivateBoards } from '/imports/subscriptions'

export const TopRightControls = () => {
const favoriteBoards = useFavoriteBoards()
const privateBoards = usePrivateBoards()
const boardId = getCurrentBoardId()
const currentBoard = privateBoards.find(board => board.id === boardId)
const currentBoardFavorite=favoriteBoards.find(board => board.id === boardId)
const isfavorite = currentBoardFavorite?.id===currentBoard?.id
const toggleFavorite = throttle(async () => {
if (!isfavorite) {
await Services.get('board_favorite').starBoard(boardId)
} else {
await Services.get('board_favorite').cancelStarBoard(boardId)
}
}, 500)

return (
<div className={styles.TopRightControls}>
<div className={cx(styles.share, styles.button)}>Share</div>
<div className={cx(styles.collect, styles.button)}>{StarIcon}</div>
<div className={cx(styles.collect, styles.button)} onClick={toggleFavorite}>
{isfavorite ? FilledStarIcon : StarIcon}
</div>
<div className={cx(styles.more, styles.button)}>{MoreIcon}</div>
</div>
)
Expand Down
4 changes: 4 additions & 0 deletions imports/models/BaseCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ export abstract class BaseCollection<T extends Mongo.OptionalId<{}>> {
protected insert(doc: T) {
return this.collection.insertAsync(doc)
}

protected remove(doc: T){
return this.collection.removeAsync(doc)
}
}
13 changes: 2 additions & 11 deletions imports/models/BoardCollection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { IBoardFilterOptions } from '../services/BoardService'
import { AppStateCollection } from './AppStateCollection'
import { BaseCollection } from './BaseCollection'
import type { IBoard } from '/imports/excalidraw/types'
Expand All @@ -8,7 +7,6 @@ export type BoardRecord = IBoard & {
userid: string
created_at: number
last_updated: number
favorite: boolean
}

export class BoardCollectionClass extends BaseCollection<BoardRecord> {
Expand Down Expand Up @@ -59,14 +57,8 @@ export class BoardCollectionClass extends BaseCollection<BoardRecord> {
return this.collection.insertAsync(this.generateEmptyBoard(userid))
}

public async getMyBoards(
userid: string,
options?: IBoardFilterOptions
): Promise<BoardRecord[] | null> {
public async getMyBoards(userid: string): Promise<BoardRecord[] | null> {
const query = { userid }
if (options != null && options.favorite === true) {
Object.assign(query, { favorite: true })
}
const lastCreatedSortTop = { created_at: -1 }
return this.collection.find(query, { sort: lastCreatedSortTop }).fetchAsync() as Promise<
BoardRecord[]
Expand All @@ -80,8 +72,7 @@ export class BoardCollectionClass extends BaseCollection<BoardRecord> {
created_at: Date.now(),
last_updated: Date.now(),
elements: [],
files: {},
favorite: false
files: {}
}
}
}
Expand Down
57 changes: 57 additions & 0 deletions imports/models/BoardFavoritesCollection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { BaseCollection } from './BaseCollection'
import { Meteor } from 'meteor/meteor'
import { BoardRecord } from './BoardCollection'

export interface IBoardFavoriteRecord {
_id: string
userid: string
boardId: string
last_updated: number
}

export class BoardFavoritesCollectionClass extends BaseCollection<IBoardFavoriteRecord> {
constructor() {
super('board_favorites')
}

public async addFavoriteBoard(board: BoardRecord | null, userid: string): Promise<boolean> {
const { id, title, files, elements } = board ?? {}
const values = { userid: userid, files, title, elements, last_updated: Date.now() }
const result = await this.collection.upsertAsync({ _id: id }, { $set: values })
const numberAffected = result?.numberAffected ?? 0
return numberAffected > 0
}

public async removeFavoriteBoard(userid: string, _id: string): Promise<boolean> {
const query = { userid, _id }
const result = await this.collection.removeAsync(query)
return result > 0
}

public async isFavoriteBoard(userid: string, _id: string): Promise<boolean> {
const query = { userid, _id }
const result = await this.collection.findOneAsync(query)
return result != null
}
public async getBoardById(boardId: string): Promise<BoardRecord | null> {
const document = await this.collection.findOneAsync({ _id: boardId })
if (document != null) Object.assign(document, { id: document._id })
return document as BoardRecord
}

public async getFavoriteBoards(userid: string): Promise<IBoardFavoriteRecord[]> {
try {
const query = { userid }
const lastCreatedSortTop = { created_at: -1 }
const favoriteBoards = this.collection
.find(query, { sort: lastCreatedSortTop })
.fetchAsync() as Promise<IBoardFavoriteRecord[]>
return favoriteBoards
} catch (err) {
console.error(`Error getting favorite boards for user ${userid}:`, err)
throw new Meteor.Error(`Failed to get favorite boards for user ${userid}`)
}
}
}

export const BoardFavoritesCollection = new BoardFavoritesCollectionClass()
1 change: 1 addition & 0 deletions imports/models/Collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class CollectionsClass {
boards: new Mongo.Collection('boards'),
logs: new Mongo.Collection('logs'),
appState: new Mongo.Collection('appState'),
board_favorites: new Mongo.Collection('board_favorites'),
files: new FilesCollection({
storagePath: process.env.UPLOAD_ABSOLUTE_PATH,
collectionName: 'files',
Expand Down
1 change: 1 addition & 0 deletions imports/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './Accounts'
export * from './Logs'
export * from './BoardCollection'
export * from './AppStateCollection'
export * from './BoardFavoritesCollection'
79 changes: 79 additions & 0 deletions imports/services/BoardFavoriteService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { IBoard } from '../excalidraw/types'
import { BoardFavoritesCollection,BoardCollection } from '../models'
import { Collections } from '../models/Collections'
import { BaseService } from './BaseService'
import { Meteor } from 'meteor/meteor'
import { _makeBoard, _makeBoards } from '../utils/boards'

export interface IBoardFilterOptions {
keys?: (keyof IBoard)[]
favorite?: boolean
}

export class BoardFavoriteService extends BaseService {
constructor() {
super('board_favorite')
}

public startup(): void {
Meteor.publish(Collections.names.board_favorites, () => {
const userid = Meteor.userId()
if (userid != null) {
return BoardFavoritesCollection.raw.find({ userid })
}
})
}
public async getBoardById(boardId: string) {
const userid = Meteor.userId()
if (userid != null) {
const record = await BoardFavoritesCollection.getBoardById(boardId)
return record != null ? _makeBoard(record) : null
}
throw new Meteor.Error('Unauthorized')
}

public async starBoard(boardId: string): Promise<boolean> {
const userId = Meteor.userId()
if (userId != null) {
const board= await BoardCollection.getBoardById(boardId)
const result=BoardFavoritesCollection.addFavoriteBoard(board,userId)
return result
} else {
throw new Meteor.Error('Unauthorized')
}
}

public cancelStarBoard(boardId: string): Promise<boolean> {
const userId = Meteor.userId()
if (userId != null) {
return BoardFavoritesCollection.removeFavoriteBoard(userId, boardId)
} else {
throw new Meteor.Error('Unauthorized')
}
}

public async isBoardFavorite(boardId: string): Promise<boolean> {
const userId = Meteor.userId()
if (userId != null) {
return BoardFavoritesCollection.isFavoriteBoard(userId, boardId)
} else {
throw new Meteor.Error('Unauthorized')
}
}

public async getMyFavoriteBoard(): Promise<IBoard[]> {
const userid = Meteor.userId()
if (userid != null) {
const favoriteBoardRecords = (await BoardFavoritesCollection.getFavoriteBoards(userid)) ?? []
const favoriteBoards = await Promise.all(
favoriteBoardRecords.map(async record => {
const board = await this.getBoardById(record._id)
return board
})
)
return favoriteBoards.filter(board => board != null) as IBoard[]
} else {
throw new Meteor.Error('Unauthorized')
}
}
}
31 changes: 13 additions & 18 deletions imports/services/BoardService.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { IBoard } from '../excalidraw/types'
import { BoardCollection, BoardRecord } from '../models'
import { BoardCollection} from '../models'
import { Collections } from '../models/Collections'
import { BaseService } from './BaseService'
import { Meteor } from 'meteor/meteor'
import { _makeBoard, _makeBoards } from '../utils/boards'

export interface IBoardFilterOptions {
keys?: (keyof IBoard)[]
favorite?: boolean
}

export class BoardService extends BaseService {
Expand Down Expand Up @@ -37,14 +37,24 @@ export class BoardService extends BaseService {
const count = await BoardCollection.updateBoard(board)
return count == 1
}
console.error('Unauthorized')
throw new Meteor.Error('Unauthorized')
}
public async getMyBoards(options?: IBoardFilterOptions): Promise<IBoard[]> {
const userId = Meteor.userId()
if (!userId) {
return []
}
const boardRecords = (await BoardCollection.getMyBoards(userId)) ?? []
const boards = boardRecords.map(record => _makeBoard(record, options))
return boards
}

public async getBoardById(boardId: string) {
const userid = Meteor.userId()
if (userid != null) {
const record = await BoardCollection.getBoardById(boardId)
return record != null ? this._makeBoard(record) : null
return record != null ? _makeBoard(record) : null
}
throw new Meteor.Error('Unauthorized')
}
Expand All @@ -64,19 +74,4 @@ export class BoardService extends BaseService {
}
throw new Meteor.Error('Unauthorized')
}

private _makeBoard(record: BoardRecord, options?: IBoardFilterOptions): IBoard {
const { _id: id, title, elements, files } = record
const board = { id, title, elements, files }

if (options != null && Array.isArray(options.keys)) {
return Object.entries(board).reduce((o, [key, value]) => {
if (options.keys!.includes(key as keyof IBoard)) {
o[key] = value
}
return o
}, {} as Record<string, any>) as IBoard
}
return board
}
}
2 changes: 2 additions & 0 deletions imports/services/ServiceManagerServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AppService } from './AppService'
import { ServerServices } from './types'
import { FilesService } from './FilesService'
import { RedirectService } from './RedirectService'
import { BoardFavoriteService } from './BoardFavoriteService'

class ServiceManagerServerClass {
private _services = {} as ServerServices
Expand All @@ -17,6 +18,7 @@ class ServiceManagerServerClass {
this._services['excalidrawSync'] = new ExcalidrawSyncService()
this._services['staticAssets'] = new StaticAssetsService()
this._services['board'] = new BoardService()
this._services['board_favorite'] = new BoardFavoriteService()
this._services['files'] = new FilesService()
this._services['redirect'] = new RedirectService()
}
Expand Down
1 change: 1 addition & 0 deletions imports/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './ServiceManagerServer'
export * from './AccountService'
export * from './ExcalidrawSyncService'
export * from './BoardService'
2 changes: 2 additions & 0 deletions imports/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { StaticAssetsService } from './StaticAssetsService'
import type { AppService } from './AppService'
import type { FilesService } from './FilesService'
import type { RedirectService } from './RedirectService'
import type { BoardFavoriteService } from './BoardFavoriteService'

type OmitPrivateProperties<T> = Omit<T, '_startup' | 'startup' | 'serviceName'>

Expand All @@ -22,6 +23,7 @@ export interface IServices {
excalidrawSync: ExcalidrawSyncService
staticAssets: StaticAssetsService
board: BoardService
board_favorite: BoardFavoriteService
files: FilesService
redirect: RedirectService
}
Expand Down
Loading

0 comments on commit c6e42a2

Please sign in to comment.