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 { 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({

View File

@ -9,6 +9,7 @@
export let reorderTarget = false
export let width = ""
export let center = false
export let selectedUser = null
</script>
<div
@ -19,6 +20,7 @@
class:row-hovered={rowHovered}
class:sticky
class:selected
class:selected-other={selectedUser != null}
class:reorder-source={reorderSource}
class:reorder-target={reorderTarget}
class:center
@ -27,6 +29,11 @@
on:mousedown
style="flex: 0 0 {width}px;"
>
{#if selectedUser}
<div class="name">
{selectedUser.email}
</div>
{/if}
<slot />
</div>
@ -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;
}
</style>

View File

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

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

View File

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

View File

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