diff --git a/packages/frontend-core/src/components/sheet/Sheet.svelte b/packages/frontend-core/src/components/sheet/Sheet.svelte index ee8ed020b1..bacdd27f69 100644 --- a/packages/frontend-core/src/components/sheet/Sheet.svelte +++ b/packages/frontend-core/src/components/sheet/Sheet.svelte @@ -8,6 +8,7 @@ import { createScrollStores } from "./stores/scroll" import { createBoundsStores } from "./stores/bounds" import { createInterfaceStores } from "./stores/interface" + export { createUserStores } from "./stores/users" import DeleteButton from "./DeleteButton.svelte" import SheetBody from "./SheetBody.svelte" import ResizeOverlay from "./ResizeOverlay.svelte" @@ -16,6 +17,7 @@ import ScrollOverlay from "./ScrollOverlay.svelte" import StickyColumn from "./StickyColumn.svelte" import { createWebsocket } from "./websocket" + import { createUserStores } from "./stores/users" export let API export let tableId @@ -52,6 +54,7 @@ context = { ...context, ...createViewportStores(context) } context = { ...context, ...createReorderStores(context) } context = { ...context, ...createInterfaceStores(context) } + context = { ...context, ...createUserStores(context) } // Keep config store up to date $: config.set({ diff --git a/packages/frontend-core/src/components/sheet/SheetCell.svelte b/packages/frontend-core/src/components/sheet/SheetCell.svelte index 4ea67d36cd..821ef30b65 100644 --- a/packages/frontend-core/src/components/sheet/SheetCell.svelte +++ b/packages/frontend-core/src/components/sheet/SheetCell.svelte @@ -9,6 +9,7 @@ export let reorderTarget = false export let width = "" export let center = false + export let selectedUser = null
+ {#if selectedUser} +
+ {selectedUser.email} +
+ {/if}
@@ -51,6 +58,9 @@ box-shadow: inset 0 0 0 2px var(--spectrum-global-color-blue-400); z-index: 1; } + .cell.selected-other { + box-shadow: inset 0 0 0 2px var(--spectrum-global-color-purple-400); + } .cell:not(.selected) { user-select: none; } @@ -110,4 +120,14 @@ justify-content: center; align-items: center; } + + .name { + position: absolute; + bottom: 100%; + background: var(--spectrum-global-color-purple-400); + padding: 1px 4px 0 4px; + border-radius: 2px; + font-size: 12px; + font-weight: 600; + } diff --git a/packages/frontend-core/src/components/sheet/SheetRow.svelte b/packages/frontend-core/src/components/sheet/SheetRow.svelte index 632047467f..920cd48c0c 100644 --- a/packages/frontend-core/src/components/sheet/SheetRow.svelte +++ b/packages/frontend-core/src/components/sheet/SheetRow.svelte @@ -12,6 +12,7 @@ rows, visibleColumns, hoveredRowId, + selectedCellMap, } = getContext("spreadsheet") $: rowSelected = !!$selectedRows[row._id] @@ -30,6 +31,7 @@ {rowSelected} {rowHovered} selected={$selectedCellId === cellIdx} + selectedUser={$selectedCellMap[cellIdx]} reorderSource={$reorder.sourceColumn === column.name} reorderTarget={$reorder.targetColumn === column.name} on:click={() => ($selectedCellId = cellIdx)} diff --git a/packages/frontend-core/src/components/sheet/stores/users.js b/packages/frontend-core/src/components/sheet/stores/users.js new file mode 100644 index 0000000000..d79b74899d --- /dev/null +++ b/packages/frontend-core/src/components/sheet/stores/users.js @@ -0,0 +1,41 @@ +import { writable, get, derived } from "svelte/store" + +export const createUserStores = () => { + const users = writable([]) + const userId = writable(null) + + const updateUser = user => { + const $users = get(users) + const index = $users.findIndex(x => x.id === user.id) + if (index === -1) { + users.set([...$users, user]) + } else { + users.update(state => { + state[index] = user + return state.slice() + }) + } + } + + const selectedCellMap = derived([users, userId], ([$users, $userId]) => { + let map = {} + $users.forEach(user => { + if (user.selectedCellId && user.id !== $userId) { + map[user.selectedCellId] = user + } + }) + console.log(map) + return map + }) + + return { + users: { + ...users, + actions: { + updateUser, + }, + }, + selectedCellMap, + userId, + } +} diff --git a/packages/frontend-core/src/components/sheet/websocket.js b/packages/frontend-core/src/components/sheet/websocket.js index 254619b36d..7df00bd6d9 100644 --- a/packages/frontend-core/src/components/sheet/websocket.js +++ b/packages/frontend-core/src/components/sheet/websocket.js @@ -2,7 +2,7 @@ import { derived, get } from "svelte/store" import { io } from "socket.io-client" export const createWebsocket = context => { - const { rows, config } = context + const { rows, config, users, userId, selectedCellId } = context const tableId = derived(config, $config => $config.tableId) // Determine connection info @@ -28,9 +28,11 @@ export const createWebsocket = context => { console.log("Idenifying dataspace", tableId) // Identify which dataspace we are editing - socket.emit("identify", tableId, response => { + socket.emit("select-dataspace", tableId, response => { // handle initial connection info console.log("response", response) + users.set(response.users) + userId.set(response.id) }) } @@ -40,10 +42,15 @@ export const createWebsocket = context => { }) socket.on("row-update", data => { + console.log("row-update:", data.id) if (data.id) { rows.actions.refreshRow(data.id) } - console.log(data) + }) + + socket.on("user-update", user => { + console.log("user-update", user) + users.actions.updateUser(user) }) socket.on("connect_error", err => { @@ -53,5 +60,10 @@ export const createWebsocket = context => { // Change websocket connection when dataspace changes tableId.subscribe(connectToDataspace) + // Notify selected cell changes + selectedCellId.subscribe($selectedCellId => { + socket.emit("select-cell", $selectedCellId) + }) + return () => socket?.disconnect() } diff --git a/packages/server/src/websockets/dataspace.ts b/packages/server/src/websockets/dataspace.ts index e5cd73b8f4..7c4ff38c90 100644 --- a/packages/server/src/websockets/dataspace.ts +++ b/packages/server/src/websockets/dataspace.ts @@ -10,14 +10,31 @@ export default class DataspaceSocket extends Socket { this.io.on("connection", socket => { const user = socket.data.user - console.log(`Dataspace user connected: ${user?._id}`) + console.log(`Dataspace user connected: ${user?.id}`) + + // Socket state + let currentRoom: string // Initial identification of conneted dataspace - socket.on("identify", async (tableId, callback) => { + socket.on("select-dataspace", async (tableId, callback) => { + if (currentRoom) { + socket.leave(currentRoom) + } socket.join(tableId) - + currentRoom = tableId const sockets = await this.io.in(tableId).fetchSockets() - callback(sockets.map(socket => socket.data.user)) + socket.broadcast.emit("user-update", socket.data.user) + + callback({ + users: sockets.map(socket => socket.data.user), + id: user.id, + }) + }) + + socket.on("select-cell", cellId => { + console.log("cell update for " + user.id + " to " + cellId) + socket.data.user.selectedCellId = cellId + socket.broadcast.emit("user-update", socket.data.user) }) // Disconnection cleanup diff --git a/packages/server/src/websockets/websocket.ts b/packages/server/src/websockets/websocket.ts index 085d583f48..c58419748c 100644 --- a/packages/server/src/websockets/websocket.ts +++ b/packages/server/src/websockets/websocket.ts @@ -55,7 +55,11 @@ export default class Socket { // Middlewares are finished. // Extract some data from our enriched koa context to persist // as metadata for the socket - socket.data.user = ctx.user + socket.data.user = { + id: ctx.user._id, + email: ctx.user.email, + selectedCellId: null, + } next() } })