From 636ecf9adf5612cca64b94f248e8f27ac6676f42 Mon Sep 17 00:00:00 2001 From: Damir Date: Sun, 18 Oct 2026 11:19:58 +0800 Subject: [PATCH] Revert "feat(collaboration): live presence + session chat (cursors, mentions, emoji, pins)" --- src/components/blocks/blocks.css | 1 - src/components/dash-chat/dash-chat.css | 299 ----------- src/components/dash-chat/dash-chat.jsx | 501 ------------------ .../dash-collaboration-status.jsx | 2 - .../dash-presence/presence-avatars.css | 41 -- .../dash-presence/presence-avatars.jsx | 90 ---- .../dash-presence/presence-cursors.css | 49 -- .../dash-presence/presence-cursors.jsx | 130 ----- src/components/menu-bar/menu-bar.jsx | 4 - .../sprite-selector-item.css | 30 -- .../sprite-selector-item.jsx | 19 +- .../sprite-selector/sprite-list.jsx | 6 - .../sprite-selector/sprite-selector.jsx | 6 - src/components/stage/stage.jsx | 2 - src/components/target-pane/target-pane.jsx | 6 - src/containers/blocks.jsx | 96 +--- src/containers/stage.jsx | 36 -- src/containers/target-pane.jsx | 33 -- src/lib/dash-chat.js | 117 ---- src/lib/dash-collaboration-session.js | 13 - src/lib/dash-collaboration.js | 62 +-- src/lib/dash-presence.js | 187 ------- 22 files changed, 5 insertions(+), 1725 deletions(-) delete mode 100644 src/components/dash-chat/dash-chat.css delete mode 100644 src/components/dash-chat/dash-chat.jsx delete mode 100644 src/components/dash-presence/presence-avatars.css delete mode 100644 src/components/dash-presence/presence-avatars.jsx delete mode 100644 src/components/dash-presence/presence-cursors.css delete mode 100644 src/components/dash-presence/presence-cursors.jsx delete mode 100644 src/lib/dash-chat.js delete mode 100644 src/lib/dash-presence.js diff --git a/src/components/blocks/blocks.css b/src/components/blocks/blocks.css index 98c523bd9..89de82012 100644 --- a/src/components/blocks/blocks.css +++ b/src/components/blocks/blocks.css @@ -4,7 +4,6 @@ .blocks { height: 100%; - position: relative; } .drag-over:after { diff --git a/src/components/dash-chat/dash-chat.css b/src/components/dash-chat/dash-chat.css deleted file mode 100644 index 4895ee156..000000000 --- a/src/components/dash-chat/dash-chat.css +++ /dev/null @@ -1,299 +0,0 @@ -@import "../../css/colors.css"; -@import "../../css/units.css"; -@import "../../css/z-index.css"; - -.wrapper { - position: relative; - display: flex; - align-items: center; -} - -.toggle { - position: relative; - padding: 0.15rem 0.45rem; - border: 1px solid $ui-black-transparent; - border-radius: 0.25rem; - background: $ui-primary; - font-size: 0.95rem; - line-height: 1.25; - cursor: pointer; -} - -.badge { - position: absolute; - top: -0.35rem; - right: -0.35rem; - min-width: 1rem; - height: 1rem; - padding: 0 0.2rem; - border-radius: 0.5rem; - background: $error-light; - color: #fff; - font-size: 0.625rem; - font-weight: bold; - line-height: 1rem; - text-align: center; -} - -.panel { - position: absolute; - top: calc(100% + 0.4rem); - right: 0; - z-index: $z-index-drag-layer; - display: flex; - flex-direction: column; - width: 20rem; - height: 26rem; - border: 1px solid $ui-black-transparent; - border-radius: $space; - background: $ui-white; - color: $text-primary; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 0.8125rem; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.22); - overflow: hidden; -} - -.header { - display: flex; - flex-direction: row; - align-items: center; - gap: 0.35rem; - padding: 0.4rem 0.6rem; - border-bottom: 1px solid $ui-black-transparent; - background: $ui-tertiary; - font-weight: bold; -} - -.header-button, -.header-button-active { - margin-left: 0.15rem; - padding: 0.1rem 0.35rem; - border: 1px solid transparent; - border-radius: 0.25rem; - background: transparent; - font-size: 0.85rem; - cursor: pointer; -} - -.header-button:hover { - background: rgba(0, 0, 0, 0.08); -} - -.header-button-active { - background: $looks-transparent; - border-color: $looks-secondary; -} - -.header-button:first-of-type { - margin-left: auto; -} - -.list { - flex: 1; - overflow-y: auto; - padding: 0.5rem; - display: flex; - flex-direction: column; - gap: 0.55rem; -} - -.empty { - margin: auto; - color: $text-primary-transparent; - text-align: center; -} - -.message, -.message-self { - display: flex; - flex-direction: column; - gap: 0.15rem; - max-width: 100%; -} - -.message-self .message-text { - background: $looks-transparent; - border-radius: 0.45rem; - padding: 0.25rem 0.45rem; -} - -.message-meta { - display: flex; - flex-direction: row; - align-items: baseline; - gap: 0.4rem; -} - -.message-author { - font-weight: bold; - font-size: 0.75rem; -} - -.message-time { - color: $text-primary-transparent; - font-size: 0.65rem; -} - -.message-text { - word-break: break-word; - white-space: pre-wrap; - line-height: 1.35; -} - -.mention { - color: $looks-secondary; - font-weight: bold; -} - -.mention-me { - color: #fff; - background: $error-light; - border-radius: 0.3rem; - padding: 0 0.25rem; - font-weight: bold; -} - -.pin-chip { - display: inline-flex; - flex-direction: row; - align-items: center; - gap: 0.25rem; - align-self: flex-start; - margin-top: 0.15rem; - padding: 0.1rem 0.4rem; - border: 1px solid $ui-black-transparent; - border-radius: 0.5rem; - background: $ui-tertiary; - font-size: 0.65rem; - cursor: pointer; -} - -.pin-chip:hover { - background: $looks-transparent; -} - -.pin-name { - margin-left: 0.2rem; - font-style: italic; - opacity: 0.85; -} - -.pending-pin { - padding: 0.25rem 0.6rem; - border-top: 1px solid $ui-black-transparent; - background: $looks-transparent; - font-size: 0.7rem; -} - -.composer { - display: flex; - flex-direction: row; - align-items: flex-end; - gap: 0.3rem; - padding: 0.45rem; - border-top: 1px solid $ui-black-transparent; - background: $ui-tertiary; -} - -.composer-button, -.composer-button-active { - flex: none; - padding: 0.25rem 0.4rem; - border: 1px solid $ui-black-transparent; - border-radius: 0.35rem; - background: $ui-white; - font-size: 0.95rem; - line-height: 1.25; - cursor: pointer; -} - -.composer-button-active { - background: $looks-transparent; - border-color: $looks-secondary; -} - -.input { - flex: 1; - resize: none; - padding: 0.35rem 0.5rem; - border: 1px solid $ui-black-transparent; - border-radius: 0.45rem; - font: inherit; - line-height: 1.35; - max-height: 5.5rem; -} - -.send-button { - flex: none; - padding: 0.3rem 0.55rem; - border: 1px solid $ui-black-transparent; - border-radius: 0.35rem; - background: $looks-secondary; - color: #fff; - font-size: 0.95rem; - cursor: pointer; -} - -.send-button:disabled { - opacity: 0.45; - cursor: default; -} - -.emoji-picker { - position: absolute; - right: 0.45rem; - bottom: 3.6rem; - z-index: $z-index-drag-layer; - width: 17.5rem; - border: 1px solid $ui-black-transparent; - border-radius: $space; - background: $ui-white; - box-shadow: 0 4px 14px rgba(0, 0, 0, 0.2); - overflow: hidden; -} - -.emoji-categories { - display: flex; - flex-direction: row; - border-bottom: 1px solid $ui-black-transparent; - background: $ui-tertiary; -} - -.emoji-category, -.emoji-category-active { - flex: 1; - padding: 0.3rem 0; - border: none; - background: transparent; - font-size: 1rem; - cursor: pointer; -} - -.emoji-category-active { - background: $looks-transparent; - box-shadow: inset 0 -2px 0 $looks-secondary; -} - -.emoji-grid { - display: grid; - grid-template-columns: repeat(8, 1fr); - gap: 0.1rem; - padding: 0.35rem; - max-height: 11.5rem; - overflow-y: auto; -} - -.emoji-cell { - padding: 0.15rem 0; - border: none; - border-radius: 0.25rem; - background: transparent; - font-size: 1.15rem; - line-height: 1.35; - cursor: pointer; -} - -.emoji-cell:hover { - background: $looks-transparent; -} diff --git a/src/components/dash-chat/dash-chat.jsx b/src/components/dash-chat/dash-chat.jsx deleted file mode 100644 index 3000f170c..000000000 --- a/src/components/dash-chat/dash-chat.jsx +++ /dev/null @@ -1,501 +0,0 @@ -import bindAll from 'lodash.bindall'; -import PropTypes from 'prop-types'; -import React from 'react'; -import {connect} from 'react-redux'; -import {FormattedMessage, injectIntl, intlShape} from 'react-intl'; - -import AddonHooks from '../../addons/hooks'; -import {listenDashChat, MAX_TEXT} from '../../lib/dash-chat.js'; -import {presenceColor} from '../../lib/dash-presence.js'; -import {activateTab, COSTUMES_TAB_INDEX} from '../../reducers/editor-tab'; -import VM from 'scratch-vm'; -import * as styles from './dash-chat.css'; - -const EMOJI_CATEGORIES = [ - { - id: 'smileys', - icon: '\u{1F600}', - emojis: ['\u{1F600}', '\u{1F601}', '\u{1F602}', '\u{1F923}', '\u{1F603}', '\u{1F604}', '\u{1F605}', - '\u{1F606}', '\u{1F609}', '\u{1F60A}', '\u{1F60B}', '\u{1F60D}', '\u{1F970}', '\u{1F618}', '\u{1F617}', - '\u{1F619}', '\u{1F61A}', '\u{1F914}', '\u{1F928}', '\u{1F610}', '\u{1F611}', '\u{1F634}', '\u{1F637}', - '\u{1F60E}', '\u{1F913}', '\u{1F929}', '\u{1F973}', '\u{1F97A}', '\u{1F62D}', '\u{1F622}', '\u{1F621}', - '\u{1F92C}', '\u{1F92F}', '\u{1F631}', '\u{1F633}', '\u{1F92A}', '\u{1F92B}', '\u{1F917}', '\u{1F60F}'] - }, - { - id: 'gestures', - icon: '\u{1F44D}', - emojis: ['\u{1F44D}', '\u{1F44E}', '\u{1F44F}', '\u{1F64C}', '\u{1F44C}', '\u{270C}\u{FE0F}', '\u{1F91E}', - '\u{1F91F}', '\u{1F44B}', '\u{1F91A}', '\u{270B}', '\u{1F590}\u{FE0F}', '\u{1F448}', '\u{1F449}', - '\u{1F446}', '\u{1F447}', '\u{1F44A}', '\u{1F91B}', '\u{1F91C}', '\u{1F450}', '\u{1F64F}', '\u{1F91D}', - '\u{1F9E1}', '\u{1F4AA}', '\u{1F9B6}', '\u{1F485}', '\u{1F933}', '\u{1F930}', '\u{1F60C}', '\u{1F971}'] - }, - { - id: 'hearts', - icon: '\u{2764}\u{FE0F}', - emojis: ['\u{2764}\u{FE0F}', '\u{1F9E1}', '\u{1F49B}', '\u{1F49A}', '\u{1F499}', '\u{1F49C}', '\u{1F90E}', - '\u{1F5A4}', '\u{1F90D}', '\u{1F494}', '\u{2763}\u{FE0F}', '\u{1F495}', '\u{1F496}', '\u{1F497}', - '\u{1F498}', '\u{1F49D}', '\u{1F49E}', '\u{1F493}', '\u{2764}\u{FE0F}\u{200D}\u{1F525}', '\u{1F4AF}'] - }, - { - id: 'nature', - icon: '\u{1F431}', - emojis: ['\u{1F431}', '\u{1F436}', '\u{1F43A}', '\u{1F98A}', '\u{1F43B}', '\u{1F428}', '\u{1F42F}', - '\u{1F42E}', '\u{1F437}', '\u{1F438}', '\u{1F435}', '\u{1F414}', '\u{1F427}', '\u{1F426}', '\u{1F424}', - '\u{1F989}', '\u{1F987}', '\u{1F43C}', '\u{1F43D}', '\u{1F430}', '\u{1F439}', '\u{1F433}', '\u{1F40B}', - '\u{1F42C}', '\u{1F41F}', '\u{1F420}', '\u{1F421}', '\u{1F33B}', '\u{1F337}', '\u{1F341}', '\u{1F340}'] - }, - { - id: 'objects', - icon: '\u{2B50}', - emojis: ['\u{2B50}', '\u{1F31F}', '\u{2728}', '\u{1F4AB}', '\u{1F525}', '\u{1F389}', '\u{1F388}', - '\u{1F381}', '\u{1F3A8}', '\u{1F3AC}', '\u{1F3B5}', '\u{1F3B8}', '\u{1F3B2}', '\u{1F3AE}', '\u{1F579}', - '\u{1F4BB}', '\u{1F4FA}', '\u{1F47E}', '\u{1F916}', '\u{1F680}', '\u{1F6F8}', '\u{2600}\u{FE0F}', - '\u{1F308}', '\u{1F32A}\u{FE0F}', '\u{26C8}\u{FE0F}', '\u{26A1}', '\u{1F4A1}', '\u{1F4BF}', '\u{1F3B9}', - '\u{1F3AF}'] - }, - { - id: 'food', - icon: '\u{1F354}', - emojis: ['\u{1F354}', '\u{1F35F}', '\u{1F355}', '\u{1F363}', '\u{1F361}', '\u{1F373}', '\u{1F35D}', - '\u{1F368}', '\u{1F366}', '\u{1F367}', '\u{1F382}', '\u{1F370}', '\u{1F9C1}', '\u{1F36A}', '\u{1F36B}', - '\u{1F36C}', '\u{1F36D}', '\u{1F95E}', '\u{1F9C0}', '\u{1F35E}', '\u{1F950}', '\u{1F955}', '\u{1F34E}', - '\u{1F34A}', '\u{1F349}', '\u{1F347}', '\u{1F353}', '\u{1F352}', '\u{1F34B}', '\u{1F34C}'] - } -]; - -const MENTION_PATTERN = /(@[A-Za-z0-9_-]+)/g; - -const targetName = target => { - if (!target) return ''; - if (typeof target.getName === 'function') return target.getName(); - if (target.sprite && target.sprite.name) return target.sprite.name; - return ''; -}; - -class DashChat extends React.Component { - constructor (props) { - super(props); - this.state = { - open: false, - version: 0, - draft: '', - pinEnabled: false, - showEmoji: false, - emojiCategory: EMOJI_CATEGORIES[0].id, - pinnedOnly: false - }; - bindAll(this, [ - 'handleChatChange', - 'handleToggleOpen', - 'handleClose', - 'handleChangeDraft', - 'handleKeyDown', - 'handleSend', - 'handleToggleEmoji', - 'handleSelectEmoji', - 'handleSelectEmojiCategory', - 'handleTogglePin', - 'handleTogglePinnedOnly', - 'handleClickPin', - 'handleDocumentClick', - 'setListRef', - 'setInputRef' - ]); - this.detachChat = null; - this.pendingPin = null; - } - componentDidMount () { - this.detachChat = listenDashChat(this.props.vm, this.handleChatChange); - document.addEventListener('mousedown', this.handleDocumentClick, true); - } - componentWillUnmount () { - if (this.detachChat) this.detachChat(); - document.removeEventListener('mousedown', this.handleDocumentClick, true); - } - componentDidUpdate (prevProps, prevState) { - if (this.state.open && (prevState.version !== this.state.version || !prevState.open)) { - this.scrollToBottom(); - } - } - handleDocumentClick (event) { - if (!this.state.showEmoji || !this.wrapperRef) return; - if (!this.wrapperRef.contains(event.target)) this.setState({showEmoji: false}); - } - handleChatChange () { - this.setState({version: this.state.version + 1}); - } - handleToggleOpen () { - const open = !this.state.open; - this.setState({open, showEmoji: false}); - if (open) this.props.vm.dashCollaboration && this.props.vm.dashCollaboration.chat.markRead(); - } - handleClose () { - this.setState({open: false, showEmoji: false}); - } - handleChangeDraft (event) { - this.setState({draft: event.target.value.slice(0, MAX_TEXT)}); - } - handleKeyDown (event) { - if (event.key === 'Enter' && !event.shiftKey) { - event.preventDefault(); - this.handleSend(); - } - } - handleSend () { - const chat = this.props.vm.dashCollaboration && this.props.vm.dashCollaboration.chat; - if (!chat) return; - const pin = this.state.pinEnabled ? this.capturePin() : null; - if (chat.sendText(this.state.draft, pin)) { - this.setState({draft: '', pinEnabled: false}); - } - } - capturePin () { - const vm = this.props.vm; - const target = vm.editingTarget; - if (!target) return null; - const targetId = target.id; - const name = targetName(target); - const workspace = AddonHooks.blocklyWorkspace; - const selected = workspace && workspace.selected; - if (selected && selected.id && this.props.activeTabIndex === 0) { - return {type: 'block', targetId, blockId: selected.id, name}; - } - if (this.props.activeTabIndex === COSTUMES_TAB_INDEX) { - return {type: 'costume', targetId, name}; - } - return {type: 'sprite', targetId, name}; - } - handleToggleEmoji () { - this.setState({showEmoji: !this.state.showEmoji}); - } - handleSelectEmojiCategory (id) { - this.setState({emojiCategory: id}); - } - handleSelectEmoji (emoji) { - const input = this.inputRef; - const draft = this.state.draft; - if (!input) { - this.setState({draft: (draft + emoji).slice(0, MAX_TEXT)}); - return; - } - const start = typeof input.selectionStart === 'number' ? input.selectionStart : draft.length; - const end = typeof input.selectionEnd === 'number' ? input.selectionEnd : draft.length; - const next = (draft.slice(0, start) + emoji + draft.slice(end)).slice(0, MAX_TEXT); - this.setState({draft: next}, () => { - input.focus(); - const caret = Math.min(start + emoji.length, MAX_TEXT); - input.setSelectionRange(caret, caret); - }); - } - handleTogglePin () { - this.setState({pinEnabled: !this.state.pinEnabled}); - } - handleTogglePinnedOnly () { - this.setState({pinnedOnly: !this.state.pinnedOnly}); - } - handleClickPin (pin) { - if (!pin) return; - const vm = this.props.vm; - vm.setEditingTarget(pin.targetId); - if (pin.type === 'costume') this.props.onActivateTab(COSTUMES_TAB_INDEX); - if (pin.type === 'block' && pin.blockId) { - const workspace = AddonHooks.blocklyWorkspace; - const root = workspace && workspace.getInjectionDiv && workspace.getInjectionDiv(); - const element = root && root.querySelector(`[data-id="${pin.blockId}"]`); - if (element) { - const restore = element.style.filter; - element.style.filter = - 'drop-shadow(0 0 3px rgba(76, 151, 255, 1)) drop-shadow(0 0 3px rgba(76, 151, 255, 1))'; - setTimeout(() => { - element.style.filter = restore; - }, 2500); - } - } - } - setListRef (ref) { - this.listRef = ref; - } - setInputRef (ref) { - this.inputRef = ref; - } - scrollToBottom () { - if (this.listRef) this.listRef.scrollTop = this.listRef.scrollHeight; - } - renderMentions (text) { - const self = (this.props.vm.dashCollaboration && this.props.vm.dashCollaboration.chat.selfUsername) || ''; - return String(text).split(MENTION_PATTERN).map((part, index) => { - if (part.startsWith('@')) { - const isMe = self && part.toLowerCase() === `@${self.toLowerCase()}`; - return ( - {part} - ); - } - return part; - }); - } - renderMessage (message) { - return ( -
-
- - {message.username} - - - {new Date(message.ts).toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'})} - -
-
{this.renderMentions(message.text)}
- {message.pin ? ( - - ) : null} -
- ); - } - render () { - const chat = this.props.vm.dashCollaboration && this.props.vm.dashCollaboration.chat; - if (!chat) return null; - const messages = this.state.pinnedOnly ? - chat.messages.filter(message => message.pin) : chat.messages; - const category = EMOJI_CATEGORIES.find(entry => entry.id === this.state.emojiCategory) || - EMOJI_CATEGORIES[0]; - const pendingPin = this.state.pinEnabled ? this.capturePin() : null; - return ( -
{ - this.wrapperRef = ref; - }} - > - - {this.state.open ? ( -
-
- - - -
-
- {messages.length ? messages.map(message => this.renderMessage(message)) : ( -
- -
- )} -
- {pendingPin ? ( -
- {'\u{1F4CC} '} - {pendingPin.type === 'block' ? ( - - ) : pendingPin.type === 'costume' ? ( - - ) : ( - - )} - {pendingPin.name ? {pendingPin.name} : null} -
- ) : null} -
- - -