Add WIP multi-user UI for sheet interface

This commit is contained in:
Andrew Kingston 2023-03-06 07:43:45 +00:00
parent 36c953443f
commit 3e907af8b5
7 changed files with 107 additions and 8 deletions

View File

@ -8,6 +8,7 @@
import { createScrollStores } from "./stores/scroll" import { createScrollStores } from "./stores/scroll"
import { createBoundsStores } from "./stores/bounds" import { createBoundsStores } from "./stores/bounds"
import { createInterfaceStores } from "./stores/interface" import { createInterfaceStores } from "./stores/interface"
export { createUserStores } from "./stores/users"
import DeleteButton from "./DeleteButton.svelte" import DeleteButton from "./DeleteButton.svelte"
import SheetBody from "./SheetBody.svelte" import SheetBody from "./SheetBody.svelte"
import ResizeOverlay from "./ResizeOverlay.svelte" import ResizeOverlay from "./ResizeOverlay.svelte"
@ -16,6 +17,7 @@
import ScrollOverlay from "./ScrollOverlay.svelte" import ScrollOverlay from "./ScrollOverlay.svelte"
import StickyColumn from "./StickyColumn.svelte" import StickyColumn from "./StickyColumn.svelte"
import { createWebsocket } from "./websocket" import { createWebsocket } from "./websocket"
import { createUserStores } from "./stores/users"
export let API export let API
export let tableId export let tableId
@ -52,6 +54,7 @@
context = { ...context, ...createViewportStores(context) } context = { ...context, ...createViewportStores(context) }
context = { ...context, ...createReorderStores(context) } context = { ...context, ...createReorderStores(context) }
context = { ...context, ...createInterfaceStores(context) } context = { ...context, ...createInterfaceStores(context) }
context = { ...context, ...createUserStores(context) }
// Keep config store up to date // Keep config store up to date
$: config.set({ $: config.set({

View File

@ -9,6 +9,7 @@
export let reorderTarget = false export let reorderTarget = false
export let width = "" export let width = ""
export let center = false export let center = false
export let selectedUser = null
</script> </script>
<div <div
@ -19,6 +20,7 @@
class:row-hovered={rowHovered} class:row-hovered={rowHovered}
class:sticky class:sticky
class:selected class:selected
class:selected-other={selectedUser != null}
class:reorder-source={reorderSource} class:reorder-source={reorderSource}
class:reorder-target={reorderTarget} class:reorder-target={reorderTarget}
class:center class:center
@ -27,6 +29,11 @@
on:mousedown on:mousedown
style="flex: 0 0 {width}px;" style="flex: 0 0 {width}px;"
> >
{#if selectedUser}
<div class="name">
{selectedUser.email}
</div>
{/if}
<slot /> <slot />
</div> </div>
@ -51,6 +58,9 @@
box-shadow: inset 0 0 0 2px var(--spectrum-global-color-blue-400); box-shadow: inset 0 0 0 2px var(--spectrum-global-color-blue-400);
z-index: 1; z-index: 1;
} }
.cell.selected-other {
box-shadow: inset 0 0 0 2px var(--spectrum-global-color-purple-400);
}
.cell:not(.selected) { .cell:not(.selected) {
user-select: none; user-select: none;
} }
@ -110,4 +120,14 @@
justify-content: center; justify-content: center;
align-items: 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;
}
</style> </style>

View File

@ -12,6 +12,7 @@
rows, rows,
visibleColumns, visibleColumns,
hoveredRowId, hoveredRowId,
selectedCellMap,
} = getContext("spreadsheet") } = getContext("spreadsheet")
$: rowSelected = !!$selectedRows[row._id] $: rowSelected = !!$selectedRows[row._id]
@ -30,6 +31,7 @@
{rowSelected} {rowSelected}
{rowHovered} {rowHovered}
selected={$selectedCellId === cellIdx} selected={$selectedCellId === cellIdx}
selectedUser={$selectedCellMap[cellIdx]}
reorderSource={$reorder.sourceColumn === column.name} reorderSource={$reorder.sourceColumn === column.name}
reorderTarget={$reorder.targetColumn === column.name} reorderTarget={$reorder.targetColumn === column.name}
on:click={() => ($selectedCellId = cellIdx)} on:click={() => ($selectedCellId = cellIdx)}

View File

@ -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,
}
}

View File

@ -2,7 +2,7 @@ import { derived, get } from "svelte/store"
import { io } from "socket.io-client" import { io } from "socket.io-client"
export const createWebsocket = context => { export const createWebsocket = context => {
const { rows, config } = context const { rows, config, users, userId, selectedCellId } = context
const tableId = derived(config, $config => $config.tableId) const tableId = derived(config, $config => $config.tableId)
// Determine connection info // Determine connection info
@ -28,9 +28,11 @@ export const createWebsocket = context => {
console.log("Idenifying dataspace", tableId) console.log("Idenifying dataspace", tableId)
// Identify which dataspace we are editing // Identify which dataspace we are editing
socket.emit("identify", tableId, response => { socket.emit("select-dataspace", tableId, response => {
// handle initial connection info // handle initial connection info
console.log("response", response) 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 => { socket.on("row-update", data => {
console.log("row-update:", data.id)
if (data.id) { if (data.id) {
rows.actions.refreshRow(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 => { socket.on("connect_error", err => {
@ -53,5 +60,10 @@ export const createWebsocket = context => {
// Change websocket connection when dataspace changes // Change websocket connection when dataspace changes
tableId.subscribe(connectToDataspace) tableId.subscribe(connectToDataspace)
// Notify selected cell changes
selectedCellId.subscribe($selectedCellId => {
socket.emit("select-cell", $selectedCellId)
})
return () => socket?.disconnect() return () => socket?.disconnect()
} }

View File

@ -10,14 +10,31 @@ export default class DataspaceSocket extends Socket {
this.io.on("connection", socket => { this.io.on("connection", socket => {
const user = socket.data.user 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 // 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) socket.join(tableId)
currentRoom = tableId
const sockets = await this.io.in(tableId).fetchSockets() 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 // Disconnection cleanup

View File

@ -55,7 +55,11 @@ export default class Socket {
// Middlewares are finished. // Middlewares are finished.
// Extract some data from our enriched koa context to persist // Extract some data from our enriched koa context to persist
// as metadata for the socket // as metadata for the socket
socket.data.user = ctx.user socket.data.user = {
id: ctx.user._id,
email: ctx.user.email,
selectedCellId: null,
}
next() next()
} }
}) })