diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 3cd4fe1..cd869ee 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -86,6 +86,19 @@ "permissions": [ "currentuser" ], + "menu": [ + { + "name": "Open Chat", + "command": "open" + }, + { + "separator": true + }, + { + "name": "Reset Chat", + "command": "reset" + } + ], "relaunchButtons": [ { "command": "open", diff --git a/packages/plugin/src/Ui.tsx b/packages/plugin/src/Ui.tsx index 668f9e5..a54cbdb 100644 --- a/packages/plugin/src/Ui.tsx +++ b/packages/plugin/src/Ui.tsx @@ -108,7 +108,9 @@ const App = observer(() => { store.setStatus(ConnectionEnum.ERROR) ); - socket.on('chat message', (data) => store.addMessage(data)); + socket.on('chat message', (data) => + store.addMessage(data, socket, false) + ); socket.on('join leave message', (data) => { const username = data.user.name || 'Anon'; @@ -124,6 +126,10 @@ const App = observer(() => { store.setOnline(data); }); + socket.on('remove message', (messageId) => + store.removeMessage(messageId) + ); + store.setStatus(ConnectionEnum.CONNECTED); socket.emit('set user', toJS(store.currentUser)); @@ -143,6 +149,7 @@ const App = observer(() => { socket.io.off('error'); socket.io.off('reconnect_error'); socket.off('chat message'); + socket.off('remove message'); socket.off('join leave message'); socket.off('online'); socket.disconnect(); diff --git a/packages/plugin/src/main/index.ts b/packages/plugin/src/main/index.ts index 30ade4f..87dba22 100644 --- a/packages/plugin/src/main/index.ts +++ b/packages/plugin/src/main/index.ts @@ -9,6 +9,7 @@ let sendNotifications = false; let triggerSelectionEvent = true; const isRelaunch = figma.command === 'relaunch'; +const isReset = figma.command === 'reset'; const currentUser = { ...figma.currentUser, @@ -16,221 +17,235 @@ const currentUser = { delete currentUser.sessionId; -figma.showUI(__html__, { - width: 333, - height: 490, - // visible: !isRelaunch -}); - -figma.root.setRelaunchData({ - open: '', -}); - -const main = async () => { - const timestamp = +new Date(); - - // random user id for current user - let history = figma.root.getPluginData('history'); - let roomName = figma.root.getPluginData('roomName'); - let secret = figma.root.getPluginData('secret'); - // const ownerId = figma.root.getPluginData('ownerId'); - - try { - JSON.parse(history); - } catch { - history = ''; - } - - if (!roomName) { - const randomRoomName = timestamp + '-' + generateString(15); - figma.root.setPluginData('roomName', randomRoomName); - roomName = randomRoomName; - } - - if (!secret) { - secret = generateString(20); - figma.root.setPluginData('secret', secret); - } - - if (!history) { - history = '[]'; - figma.root.setPluginData('history', history); - } - - // Parse History - try { - history = typeof history === 'string' ? JSON.parse(history) : []; - } catch {} - - return { - roomName, - secret, - history, - }; -}; - -const getSelectionIds = () => figma.currentPage.selection.map((n) => n.id); +if (isReset) { +} else { + figma.showUI(__html__, { + width: 333, + height: 490, + // visible: !isRelaunch + }); -const sendSelection = () => { - EventEmitter.emit('selection', { - page: { - id: figma.currentPage.id, - name: figma.currentPage.name, - }, - nodes: getSelectionIds(), + figma.root.setRelaunchData({ + open: '', }); -}; -let alreadyAskedForRelaunchMessage = false; - -const isValidShape = (node) => - node.type === 'RECTANGLE' || - node.type === 'ELLIPSE' || - node.type === 'GROUP' || - node.type === 'TEXT' || - node.type === 'VECTOR' || - node.type === 'FRAME' || - node.type === 'COMPONENT' || - node.type === 'INSTANCE' || - node.type === 'POLYGON'; - -const goToPage = (id) => { - if (figma.getNodeById(id)) { - figma.currentPage = figma.getNodeById(id) as PageNode; - } -}; + const main = async () => { + const timestamp = +new Date(); + + // random user id for current user + let history = figma.root.getPluginData('history'); + let roomName = figma.root.getPluginData('roomName'); + let secret = figma.root.getPluginData('secret'); + // const ownerId = figma.root.getPluginData('ownerId'); -let previousSelection = figma.currentPage.selection || []; + if (!history) { + history = '[]'; + figma.root.setPluginData('history', history); + } -EventEmitter.on('clear-chat-history', (_, send) => { - figma.root.setPluginData('history', '[]'); + // Parse History + try { + history = typeof history === 'string' ? JSON.parse(history) : []; + } catch { + history = JSON.parse('[]'); + } - send('history', JSON.parse('[]')); -}); + if (!roomName) { + const randomRoomName = timestamp + '-' + generateString(15); + figma.root.setPluginData('roomName', randomRoomName); + roomName = randomRoomName; + } -EventEmitter.on('minimize', (flag) => { - isMinimized = flag; - sendNotifications = isMinimized; + if (!secret) { + secret = generateString(20); + figma.root.setPluginData('secret', secret); + } - // resize window - figma.ui.resize(flag ? 180 : 333, flag ? 108 : 490); -}); + return { + roomName, + secret, + history, + }; + }; -EventEmitter.on('add-message-to-history', (payload) => { - const messageHistory = JSON.parse( - figma.root.getPluginData('history') || '[]' - ); + const getSelectionIds = () => figma.currentPage.selection.map((n) => n.id); - figma.root.setPluginData( - 'history', - JSON.stringify(messageHistory.concat(payload)) + const sendSelection = () => { + EventEmitter.emit('selection', { + page: { + id: figma.currentPage.id, + name: figma.currentPage.name, + }, + nodes: getSelectionIds(), + }); + }; + + let alreadyAskedForRelaunchMessage = false; + + const isValidShape = (node) => + node.type === 'RECTANGLE' || + node.type === 'ELLIPSE' || + node.type === 'GROUP' || + node.type === 'TEXT' || + node.type === 'VECTOR' || + node.type === 'FRAME' || + node.type === 'COMPONENT' || + node.type === 'INSTANCE' || + node.type === 'POLYGON'; + + const goToPage = (id) => { + if (figma.getNodeById(id)) { + figma.currentPage = figma.getNodeById(id) as PageNode; + } + }; + + let previousSelection = figma.currentPage.selection || []; + + EventEmitter.on('remove message', (messageId: string) => { + const messageHistory = JSON.parse( + figma.root.getPluginData('history') || '[]' + ); + + figma.root.setPluginData( + 'history', + JSON.stringify( + messageHistory.filter((message) => + message?.id ? message?.id !== messageId : true + ) + ) + ); + }); + + EventEmitter.on('clear-chat-history', (_, send) => { + figma.root.setPluginData('history', '[]'); + + send('history', JSON.parse('[]')); + }); + + EventEmitter.on('add-message-to-history', (payload) => { + const messageHistory = JSON.parse( + figma.root.getPluginData('history') || '[]' + ); + + figma.root.setPluginData( + 'history', + JSON.stringify(messageHistory.concat(payload)) + ); + }); + + EventEmitter.answer( + 'get-history', + JSON.parse(figma.root.getPluginData('history') || '[]') ); -}); -EventEmitter.answer( - 'get-history', - JSON.parse(figma.root.getPluginData('history') || '[]') -); + EventEmitter.on('minimize', (flag) => { + isMinimized = flag; + sendNotifications = isMinimized; -EventEmitter.on('notify', (payload) => { - figma.notify(payload); -}); + // resize window + figma.ui.resize(flag ? 180 : 333, flag ? 108 : 490); + }); -EventEmitter.on('notification', (payload) => { - if (sendNotifications) { + EventEmitter.on('notify', (payload) => { figma.notify(payload); - } -}); + }); -EventEmitter.answer('current-user', async () => currentUser); + EventEmitter.on('notification', (payload) => { + if (sendNotifications) { + figma.notify(payload); + } + }); -EventEmitter.answer('root-data', async () => { - const { roomName, secret, history } = await main(); + EventEmitter.answer('current-user', async () => currentUser); - return { - roomName, - secret, - history, - currentUser, - selection: getSelectionIds(), - }; -}); + EventEmitter.answer('root-data', async () => { + const { roomName, secret, history } = await main(); -EventEmitter.on('focus', (payload) => { - if (!isMinimized) { - isFocused = payload; + return { + roomName, + secret, + history, + currentUser, + selection: getSelectionIds(), + }; + }); - if (!isFocused) { - sendNotifications = true; + EventEmitter.on('focus', (payload) => { + if (!isMinimized) { + isFocused = payload; + + if (!isFocused) { + sendNotifications = true; + } } - } -}); - -EventEmitter.on('focus-nodes', (payload) => { - let selectedNodes = []; - triggerSelectionEvent = false; - - // fallback for ids - if (payload.ids) { - selectedNodes = payload.ids; - } else { - goToPage(payload?.page?.id); - selectedNodes = payload.nodes; - } - - const nodes = figma.currentPage.findAll( - (n) => selectedNodes.indexOf(n.id) !== -1 - ); + }); + + EventEmitter.on('focus-nodes', (payload) => { + let selectedNodes = []; + triggerSelectionEvent = false; + + // fallback for ids + if (payload.ids) { + selectedNodes = payload.ids; + } else { + goToPage(payload?.page?.id); + selectedNodes = payload.nodes; + } + + const nodes = figma.currentPage.findAll( + (n) => selectedNodes.indexOf(n.id) !== -1 + ); - figma.currentPage.selection = nodes; - figma.viewport.scrollAndZoomIntoView(nodes); + figma.currentPage.selection = nodes; + figma.viewport.scrollAndZoomIntoView(nodes); - setTimeout(() => (triggerSelectionEvent = true)); -}); + setTimeout(() => (triggerSelectionEvent = true)); + }); -EventEmitter.on('ask-for-relaunch-message', (_, emit) => { - if (isRelaunch && !alreadyAskedForRelaunchMessage) { - alreadyAskedForRelaunchMessage = true; - emit('relaunch-message', { - selection: { - page: { - id: figma.currentPage.id, - name: figma.currentPage.name, + EventEmitter.on('ask-for-relaunch-message', (_, emit) => { + if (isRelaunch && !alreadyAskedForRelaunchMessage) { + alreadyAskedForRelaunchMessage = true; + emit('relaunch-message', { + selection: { + page: { + id: figma.currentPage.id, + name: figma.currentPage.name, + }, + nodes: getSelectionIds(), }, - nodes: getSelectionIds(), - }, - }); - } -}); - -EventEmitter.on('cancel', () => {}); - -// events -figma.on('selectionchange', () => { - if (figma.currentPage.selection.length > 0) { - for (const node of figma.currentPage.selection) { - if (node.setRelaunchData && isValidShape(node)) { - node.setRelaunchData({ - relaunch: '', - }); - } + }); } - previousSelection = figma.currentPage.selection; - } else { - if (previousSelection.length > 0) { - // tidy up 🧹 - for (const node of previousSelection) { - if ( - node.setRelaunchData && - isValidShape(node) && - figma.getNodeById(node.id) - ) { - node.setRelaunchData({}); + }); + + EventEmitter.on('cancel', () => {}); + + // events + figma.on('selectionchange', () => { + if (figma.currentPage.selection.length > 0) { + for (const node of figma.currentPage.selection) { + if (node.setRelaunchData && isValidShape(node)) { + node.setRelaunchData({ + relaunch: '', + }); + } + } + previousSelection = figma.currentPage.selection; + } else { + if (previousSelection.length > 0) { + // tidy up 🧹 + for (const node of previousSelection) { + if ( + node.setRelaunchData && + isValidShape(node) && + figma.getNodeById(node.id) + ) { + node.setRelaunchData({}); + } } } } - } - if (triggerSelectionEvent) { - sendSelection(); - } -}); + if (triggerSelectionEvent) { + sendSelection(); + } + }); +} diff --git a/packages/plugin/src/store/index.tsx b/packages/plugin/src/store/index.tsx index 52112e6..db365ce 100644 --- a/packages/plugin/src/store/index.tsx +++ b/packages/plugin/src/store/index.tsx @@ -9,6 +9,7 @@ import { DEFAULT_SERVER_URL } from '@fc/shared/utils/constants'; import { ConnectionEnum, CurrentUser, + MessageData, StoreSettings, } from '@fc/shared/utils/interfaces'; import { darkTheme, lightTheme } from '@fc/shared/utils/theme'; @@ -31,8 +32,7 @@ export class RootStore { @ignore online = []; - @ignore - messages = []; + messages: MessageData[] = []; @ignore messagesRef = createRef(); @@ -64,7 +64,10 @@ export class RootStore { } setCurrentUser(currentUser) { - this.currentUser = currentUser; + this.currentUser = { + ...this.currentUser, + ...currentUser, + }; } setSecret(secret) { @@ -181,9 +184,8 @@ export class RootStore { 'Do you really want to delete the complete chat history? (This cannot be undone)' ) ) { - EventEmitter.emit('clear-chat-history'); - this.messages = []; + EventEmitter.emit('clear-chat-history'); cb(); this.addNotification('Chat history successfully deleted'); } @@ -212,43 +214,48 @@ export class RootStore { } } - addMessage(messageData) { - const isLocal = !messageData.user; - - const decryptedMessage = this.encryptor.decrypt( - isLocal ? messageData : messageData.message + removeMessage(messageId) { + this.messages = this.messages.filter((message) => + message?.id ? message?.id !== messageId : true ); + EventEmitter.emit('remove message', messageId); + } + addMessage(messageData: Partial, socket, isLocal = true) { // silent on error try { - const data = JSON.parse(decryptedMessage); - - let newMessage: Record = { - message: { - ...data, - }, - }; + let newMessage: Partial; // is local sender if (isLocal) { + // generate messageId + const messageId = ( + new Date().getTime() * + Math.random() * + 10000 + ).toString(32); + + messageData.message.date = new Date().toString(); + newMessage = { + ...messageData, + id: messageId, user: toJS(this.currentUser), - message: { - ...data, - }, }; - EventEmitter.emit('add-message-to-history', newMessage); + socket.emit('chat message', { + roomName: this.roomName, + message: this.encryptor.encrypt(JSON.stringify(newMessage)), + }); + + EventEmitter.emit('add-message-to-history', newMessage as any); } else { - newMessage = { - user: messageData.user, - message: { - ...data, - }, - }; + const decryptedMessage = this.encryptor.decrypt(messageData as string); + + newMessage = JSON.parse(decryptedMessage); - if (data.external) { - EventEmitter.emit('add-message-to-history', newMessage); + if (newMessage.message?.external) { + EventEmitter.emit('add-message-to-history', newMessage as any); } if (this.settings.enableNotificationSound) { @@ -258,11 +265,11 @@ export class RootStore { if (this.settings.enableNotificationTooltip) { let text = ''; - if (data.text) { + if (newMessage.message.text) { text = - data.text.length > 25 - ? data.text.substr(0, 25 - 1) + '...' - : data.text; + newMessage.message.text.length > 25 + ? newMessage.message.text.substr(0, 25 - 1) + '...' + : newMessage.message.text; } EventEmitter.emit( @@ -274,7 +281,7 @@ export class RootStore { } } - this.messages.push(newMessage); + this.messages.push(newMessage as MessageData); setTimeout(() => this.scrollToBottom(), 0); } catch (e) { diff --git a/packages/plugin/src/views/Chat/components/Chatbar.tsx b/packages/plugin/src/views/Chat/components/Chatbar.tsx index ea5332a..ca1acd6 100644 --- a/packages/plugin/src/views/Chat/components/Chatbar.tsx +++ b/packages/plugin/src/views/Chat/components/Chatbar.tsx @@ -91,9 +91,9 @@ const ChatBar: FunctionComponent = (props) => { {store.online .filter((_, i) => i < 2) - .map((user) => ( + .map((user, i) => ( = (props) => { selectionTooltipRef.current.show()} @@ -169,7 +169,7 @@ const ChatBar: FunctionComponent = (props) => {
{store.selectionCount < 10 && store.selectionCount}
- + diff --git a/packages/plugin/src/views/Chat/index.tsx b/packages/plugin/src/views/Chat/index.tsx index 3b16d67..1568b7f 100644 --- a/packages/plugin/src/views/Chat/index.tsx +++ b/packages/plugin/src/views/Chat/index.tsx @@ -40,16 +40,15 @@ const Chat: FunctionComponent = observer(() => { } if (store.roomName) { - let data = { + let message = { text: chatState.textMessage, - date: new Date(), }; if (store.selectionCount > 0 && chatState.selectionIsChecked) { - data = { - ...data, + message = { + ...message, ...{ - selection: store.selection, + selection: toJS(store.selection), }, }; } @@ -60,14 +59,12 @@ const Chat: FunctionComponent = observer(() => { 'error' ); } else { - const message = store.encryptor.encrypt(JSON.stringify(data)); - - socket.emit('chat message', { - roomName: store.roomName, - message, - }); - - store.addMessage(message); + store.addMessage( + { + message, + }, + socket + ); chatState.setTextMessage(''); chatState.setSelectionIsChecked(false); @@ -126,9 +123,20 @@ const Chat: FunctionComponent = observer(() => { EventEmitter.emit('focus-nodes', selectionData); }; + const removeMessage = (messageId) => { + if (socket && messageId) { + store.removeMessage(messageId); + socket.emit('remove message', { + roomName: store.roomName, + messageId, + }); + } + }; + return ( 0}> { {