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 { 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({
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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"
|
||||
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue