Add WIP multi-user UI for sheet interface
This commit is contained in:
parent
36c953443f
commit
3e907af8b5
|
@ -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({
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue