InboxZone: a PixiJS Container positioned at (-500,-500) with a

dashed border and "Inbox" label. Auto-synced media slides in with
spring animations and random offset/rotation for a natural look.

MattermostImport: modal dialog for pulling images from a MM thread
URL and managing linked channels for auto-sync. Uses the existing
dark modal styling pattern from ShortcutsHelp.

Editor wiring: toolbar button for MM import, InboxZone added to
viewport on init, socket listener for board:media-arrived events
that routes assets into the InboxZone with a toast notification.
This commit is contained in:
Hiren Kangad
2026-03-09 23:36:04 +05:30
parent 51afce9674
commit 21b2a433c7
4 changed files with 566 additions and 0 deletions
+37
View File
@@ -21,6 +21,8 @@ import UserCursors from '../components/UserCursors';
import ContextMenu from '../components/ContextMenu';
import LayerPanel from '../components/LayerPanel';
import ShortcutsHelp from '../components/ShortcutsHelp';
import MattermostImport from '../components/MattermostImport';
import { InboxZone } from '../canvas/InboxZone';
// Pre-sort shortcuts by specificity (most modifiers first) for correct matching
const sortedShortcuts = sortBySpecificity(shortcutDefs);
@@ -82,9 +84,11 @@ export default function Editor({ isPublicView }: EditorProps) {
const [showLayers, setShowLayers] = useState(false);
const [showGrid, setShowGrid] = useState(false);
const [showHelp, setShowHelp] = useState(false);
const [showMmImport, setShowMmImport] = useState(false);
const [layerList, setLayerList] = useState<any[]>([]);
const [selectedLayerIds, setSelectedLayerIds] = useState<string[]>([]);
const clipboardRef = useRef<SceneItem[]>([]);
const inboxZoneRef = useRef<InboxZone | null>(null);
const resolvedBoardId = boardId || boardData?.board?.id;
@@ -238,6 +242,11 @@ export default function Editor({ isPublicView }: EditorProps) {
// Create UndoManager
undoRef.current = new UndoManager(scene);
// Create InboxZone and add to viewport
const inboxZone = new InboxZone(scene.textures, scene.springs);
viewport.addChild(inboxZone);
inboxZoneRef.current = inboxZone;
if (user) {
const socket = connectSocket();
@@ -270,6 +279,15 @@ export default function Editor({ isPublicView }: EditorProps) {
}
});
// Listen for auto-synced media arriving via socket
socket.on('board:media-arrived', (data: any) => {
const assets = data?.assets;
if (Array.isArray(assets) && assets.length > 0 && inboxZoneRef.current) {
inboxZoneRef.current.addMedia(assets);
showToast(`${assets.length} image${assets.length !== 1 ? 's' : ''} arrived in Inbox`);
}
});
// Cursor tracking: listen on viewport for pointermove
const onPointerMove = (e: any) => {
const world = viewport.toWorld(e.global.x, e.global.y);
@@ -317,6 +335,11 @@ export default function Editor({ isPublicView }: EditorProps) {
clearTimeout(timer);
selectionRef.current?.destroy();
selectionRef.current = null;
if (inboxZoneRef.current) {
inboxZoneRef.current.clear();
inboxZoneRef.current.destroy({ children: true });
inboxZoneRef.current = null;
}
syncCleanupRef.current?.();
dropCleanupRef.current?.();
pasteCleanupRef.current?.();
@@ -896,6 +919,7 @@ export default function Editor({ isPublicView }: EditorProps) {
onToggleLayers={() => setShowLayers((v) => !v)}
showLayers={showLayers}
onToggleHelp={() => setShowHelp((v) => !v)}
onMmImport={() => setShowMmImport(true)}
boardName={board?.name}
/>
@@ -1063,6 +1087,19 @@ export default function Editor({ isPublicView }: EditorProps) {
{showHelp && (
<ShortcutsHelp shortcuts={shortcutDefs} onClose={() => setShowHelp(false)} />
)}
{/* Mattermost import modal */}
{showMmImport && resolvedBoardId && (
<MattermostImport
boardId={resolvedBoardId}
onClose={() => setShowMmImport(false)}
onMediaArrived={(assets) => {
if (inboxZoneRef.current) {
inboxZoneRef.current.addMedia(assets);
}
}}
/>
)}
</div>
);
}