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()
}
})