Fix issues with not disconnecting users when swapping datasource and improve multi-user UI
This commit is contained in:
parent
3e907af8b5
commit
4647e1bc07
|
@ -106,8 +106,13 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="buttons">
|
<Sheet
|
||||||
<div class="left-buttons">
|
tableId={$tables.selected?._id}
|
||||||
|
{API}
|
||||||
|
filter={filters}
|
||||||
|
on:add-column={createColumnModal.show}
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="controls">
|
||||||
<CreateColumnButton
|
<CreateColumnButton
|
||||||
highlighted={!hasCols || !hasRows}
|
highlighted={!hasCols || !hasRows}
|
||||||
on:updatecolumns={null}
|
on:updatecolumns={null}
|
||||||
|
@ -124,8 +129,6 @@
|
||||||
{#if isInternal}
|
{#if isInternal}
|
||||||
<CreateViewButton disabled={!hasCols || !hasRows} />
|
<CreateViewButton disabled={!hasCols || !hasRows} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
|
||||||
<div class="right-buttons">
|
|
||||||
<ManageAccessButton resourceId={$tables.selected?._id} />
|
<ManageAccessButton resourceId={$tables.selected?._id} />
|
||||||
{#if isUsersTable}
|
{#if isUsersTable}
|
||||||
<EditRolesButton />
|
<EditRolesButton />
|
||||||
|
@ -157,16 +160,8 @@
|
||||||
tableId={id}
|
tableId={id}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</svelte:fragment>
|
||||||
</div>
|
</Sheet>
|
||||||
<div class="sheet">
|
|
||||||
<Sheet
|
|
||||||
tableId={$tables.selected?._id}
|
|
||||||
{API}
|
|
||||||
filter={filters}
|
|
||||||
on:add-column={createColumnModal.show}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--<div>-->
|
<!--<div>-->
|
||||||
|
@ -234,34 +229,4 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
}
|
}
|
||||||
.sheet {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.pagination {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
.buttons {
|
|
||||||
flex: 0 0 48px;
|
|
||||||
border-bottom: 2px solid var(--spectrum-global-color-gray-200);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 0 8px;
|
|
||||||
}
|
|
||||||
.left-buttons,
|
|
||||||
.right-buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-m);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script>
|
||||||
|
export let user
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="user" style="background:{user.color};" title={user.email}>
|
||||||
|
{user.email[0]}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
div:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,7 +2,7 @@
|
||||||
import { Modal, ModalContent, Button } from "@budibase/bbui"
|
import { Modal, ModalContent, Button } from "@budibase/bbui"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { selectedRows, rows } = getContext("spreadsheet")
|
const { selectedRows, rows } = getContext("sheet")
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import { getIconForField } from "./utils"
|
import { getIconForField } from "./utils"
|
||||||
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
|
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
|
||||||
|
|
||||||
const { visibleColumns, reorder } = getContext("spreadsheet")
|
const { visibleColumns, reorder } = getContext("sheet")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { visibleColumns, hoveredRowId, rows, selectedCellId, reorder } =
|
const { visibleColumns, hoveredRowId, rows, selectedCellId, reorder } =
|
||||||
getContext("spreadsheet")
|
getContext("sheet")
|
||||||
|
|
||||||
$: rowHovered = $hoveredRowId === "new"
|
$: rowHovered = $hoveredRowId === "new"
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
visibleColumns,
|
visibleColumns,
|
||||||
cellHeight,
|
cellHeight,
|
||||||
stickyColumn,
|
stickyColumn,
|
||||||
} = getContext("spreadsheet")
|
} = getContext("sheet")
|
||||||
const MinColumnWidth = 100
|
const MinColumnWidth = 100
|
||||||
|
|
||||||
let initialMouseX = null
|
let initialMouseX = null
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
maxScrollTop,
|
maxScrollTop,
|
||||||
contentWidth,
|
contentWidth,
|
||||||
maxScrollLeft,
|
maxScrollLeft,
|
||||||
} = getContext("spreadsheet")
|
} = getContext("sheet")
|
||||||
|
|
||||||
// Bar config
|
// Bar config
|
||||||
const barOffset = 4
|
const barOffset = 4
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { setContext, createEventDispatcher, onMount } from "svelte"
|
import { setContext, createEventDispatcher, onMount } from "svelte"
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
import { createAPIClient } from "../../api"
|
||||||
import { createReorderStores } from "./stores/reorder"
|
import { createReorderStores } from "./stores/reorder"
|
||||||
import { createViewportStores } from "./stores/viewport"
|
import { createViewportStores } from "./stores/viewport"
|
||||||
import { createRowsStore } from "./stores/rows"
|
import { createRowsStore } from "./stores/rows"
|
||||||
|
@ -9,15 +10,15 @@
|
||||||
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"
|
export { createUserStores } from "./stores/users"
|
||||||
|
import { createWebsocket } from "./websocket"
|
||||||
|
import { 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"
|
||||||
import HeaderRow from "./HeaderRow.svelte"
|
import HeaderRow from "./HeaderRow.svelte"
|
||||||
import { createAPIClient } from "../../api"
|
|
||||||
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 UserAvatars from "./UserAvatars.svelte"
|
||||||
import { createUserStores } from "./stores/users"
|
|
||||||
|
|
||||||
export let API
|
export let API
|
||||||
export let tableId
|
export let tableId
|
||||||
|
@ -65,16 +66,21 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set context for children to consume
|
// Set context for children to consume
|
||||||
setContext("spreadsheet", context)
|
setContext("sheet", context)
|
||||||
|
|
||||||
// Initialise websocket for multi-user
|
// Initialise websocket for multi-user
|
||||||
onMount(() => {
|
onMount(() => createWebsocket(context))
|
||||||
return createWebsocket(context)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="sheet" style="--cell-height:{cellHeight}px;" id="sheet-{rand}">
|
<div class="sheet" style="--cell-height:{cellHeight}px;" id="sheet-{rand}">
|
||||||
<!--<SheetControls />-->
|
<div class="controls">
|
||||||
|
<div class="controls-left">
|
||||||
|
<slot name="controls" />
|
||||||
|
</div>
|
||||||
|
<div class="controls-right">
|
||||||
|
<UserAvatars />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="sheet-data">
|
<div class="sheet-data">
|
||||||
<StickyColumn />
|
<StickyColumn />
|
||||||
<div class="sheet-main">
|
<div class="sheet-main">
|
||||||
|
@ -128,4 +134,24 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Controls */
|
||||||
|
.controls {
|
||||||
|
height: var(--controls-height);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 2px solid var(--spectrum-global-color-gray-200);
|
||||||
|
padding: var(--cell-padding);
|
||||||
|
gap: var(--cell-spacing);
|
||||||
|
}
|
||||||
|
.controls-left,
|
||||||
|
.controls-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--cell-spacing);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
import NewRow from "./NewRow.svelte"
|
import NewRow from "./NewRow.svelte"
|
||||||
import SheetRow from "./SheetRow.svelte"
|
import SheetRow from "./SheetRow.svelte"
|
||||||
|
|
||||||
const { selectedCellId, bounds, visibleRows, config } =
|
const { selectedCellId, bounds, visibleRows, config } = getContext("sheet")
|
||||||
getContext("spreadsheet")
|
|
||||||
|
|
||||||
let ref
|
let ref
|
||||||
|
|
||||||
|
@ -27,8 +26,8 @@
|
||||||
on:click|self={() => ($selectedCellId = null)}
|
on:click|self={() => ($selectedCellId = null)}
|
||||||
>
|
>
|
||||||
<SheetScrollWrapper>
|
<SheetScrollWrapper>
|
||||||
{#each $visibleRows as row}
|
{#each $visibleRows as row, idx}
|
||||||
<SheetRow {row} />
|
<SheetRow {row} {idx} />
|
||||||
{/each}
|
{/each}
|
||||||
{#if $config.allowAddRows}
|
{#if $config.allowAddRows}
|
||||||
<NewRow />
|
<NewRow />
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
|
import Avatar from "./Avatar.svelte"
|
||||||
|
|
||||||
export let header = false
|
export let header = false
|
||||||
export let label = false
|
export let label = false
|
||||||
export let rowSelected = false
|
export let rowSelected = false
|
||||||
|
@ -10,6 +12,18 @@
|
||||||
export let width = ""
|
export let width = ""
|
||||||
export let center = false
|
export let center = false
|
||||||
export let selectedUser = null
|
export let selectedUser = null
|
||||||
|
export let rowIdx
|
||||||
|
|
||||||
|
$: style = getStyle(width, selectedUser)
|
||||||
|
|
||||||
|
const getStyle = (width, selectedUser) => {
|
||||||
|
let style = `flex: 0 0 ${width}px;`
|
||||||
|
if (selectedUser) {
|
||||||
|
console.log(selectedUser)
|
||||||
|
style += `--user-color:${selectedUser.color};`
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -27,14 +41,15 @@
|
||||||
on:focus
|
on:focus
|
||||||
on:click
|
on:click
|
||||||
on:mousedown
|
on:mousedown
|
||||||
style="flex: 0 0 {width}px;"
|
{style}
|
||||||
|
data-row={rowIdx}
|
||||||
>
|
>
|
||||||
{#if selectedUser}
|
<slot />
|
||||||
<div class="name">
|
{#if selectedUser && !selected}
|
||||||
{selectedUser.email}
|
<div class="user">
|
||||||
|
{selectedUser.label} wwwwwwwwwwwwwwwwwwwwaaaaaa
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<slot />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -56,10 +71,11 @@
|
||||||
}
|
}
|
||||||
.cell.selected {
|
.cell.selected {
|
||||||
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: 2;
|
||||||
}
|
}
|
||||||
.cell.selected-other {
|
.cell.selected-other:not(.selected) {
|
||||||
box-shadow: inset 0 0 0 2px var(--spectrum-global-color-purple-400);
|
z-index: 1;
|
||||||
|
box-shadow: inset 0 0 0 2px var(--user-color);
|
||||||
}
|
}
|
||||||
.cell:not(.selected) {
|
.cell:not(.selected) {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
@ -121,13 +137,29 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
/* Other user email */
|
||||||
|
.user {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 100%;
|
bottom: 100%;
|
||||||
background: var(--spectrum-global-color-purple-400);
|
padding: 1px 4px;
|
||||||
padding: 1px 4px 0 4px;
|
background: var(--user-color);
|
||||||
border-radius: 2px;
|
border-radius: 2px 2px 0 0;
|
||||||
|
display: none;
|
||||||
|
color: white;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
max-width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.cell[data-row="0"] .user {
|
||||||
|
bottom: auto;
|
||||||
|
top: 100%;
|
||||||
|
border-radius: 0 0 2px 2px;
|
||||||
|
padding: 0 4px 2px 4px;
|
||||||
|
}
|
||||||
|
.cell:hover .user {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { ActionButton } from "@budibase/bbui"
|
import { ActionButton } from "@budibase/bbui"
|
||||||
|
|
||||||
const { rows } = getContext("spreadsheet")
|
const { rows } = getContext("sheet")
|
||||||
|
|
||||||
$: rowCount = $rows.length
|
$: rowCount = $rows.length
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import { getCellRenderer } from "./renderers"
|
import { getCellRenderer } from "./renderers"
|
||||||
|
|
||||||
export let row
|
export let row
|
||||||
|
export let idx
|
||||||
|
|
||||||
const {
|
const {
|
||||||
selectedCellId,
|
selectedCellId,
|
||||||
|
@ -13,7 +14,7 @@
|
||||||
visibleColumns,
|
visibleColumns,
|
||||||
hoveredRowId,
|
hoveredRowId,
|
||||||
selectedCellMap,
|
selectedCellMap,
|
||||||
} = getContext("spreadsheet")
|
} = getContext("sheet")
|
||||||
|
|
||||||
$: rowSelected = !!$selectedRows[row._id]
|
$: rowSelected = !!$selectedRows[row._id]
|
||||||
$: rowHovered = $hoveredRowId === row._id
|
$: rowHovered = $hoveredRowId === row._id
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
<SheetCell
|
<SheetCell
|
||||||
{rowSelected}
|
{rowSelected}
|
||||||
{rowHovered}
|
{rowHovered}
|
||||||
|
rowIdx={idx}
|
||||||
selected={$selectedCellId === cellIdx}
|
selected={$selectedCellId === cellIdx}
|
||||||
selectedUser={$selectedCellMap[cellIdx]}
|
selectedUser={$selectedCellMap[cellIdx]}
|
||||||
reorderSource={$reorder.sourceColumn === column.name}
|
reorderSource={$reorder.sourceColumn === column.name}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
hoveredRowId,
|
hoveredRowId,
|
||||||
maxScrollTop,
|
maxScrollTop,
|
||||||
maxScrollLeft,
|
maxScrollLeft,
|
||||||
} = getContext("spreadsheet")
|
} = getContext("sheet")
|
||||||
|
|
||||||
export let scrollVertically = true
|
export let scrollVertically = true
|
||||||
export let scrollHorizontally = true
|
export let scrollHorizontally = true
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
scroll,
|
scroll,
|
||||||
reorder,
|
reorder,
|
||||||
config,
|
config,
|
||||||
} = getContext("spreadsheet")
|
selectedCellMap,
|
||||||
|
} = getContext("sheet")
|
||||||
|
|
||||||
$: scrollLeft = $scroll.left
|
$: scrollLeft = $scroll.left
|
||||||
$: rowCount = $rows.length
|
$: rowCount = $rows.length
|
||||||
|
@ -96,7 +97,7 @@
|
||||||
|
|
||||||
<div class="content" on:mouseleave={() => ($hoveredRowId = null)}>
|
<div class="content" on:mouseleave={() => ($hoveredRowId = null)}>
|
||||||
<SheetScrollWrapper scrollHorizontally={false}>
|
<SheetScrollWrapper scrollHorizontally={false}>
|
||||||
{#each $visibleRows as row}
|
{#each $visibleRows as row, idx}
|
||||||
{@const rowSelected = !!$selectedRows[row._id]}
|
{@const rowSelected = !!$selectedRows[row._id]}
|
||||||
{@const rowHovered = $hoveredRowId === row._id}
|
{@const rowHovered = $hoveredRowId === row._id}
|
||||||
<div class="row" on:mouseenter={() => ($hoveredRowId = row._id)}>
|
<div class="row" on:mouseenter={() => ($hoveredRowId = row._id)}>
|
||||||
|
@ -123,8 +124,10 @@
|
||||||
<SheetCell
|
<SheetCell
|
||||||
{rowSelected}
|
{rowSelected}
|
||||||
{rowHovered}
|
{rowHovered}
|
||||||
|
rowIdx={idx}
|
||||||
sticky
|
sticky
|
||||||
selected={$selectedCellId === cellIdx}
|
selected={$selectedCellId === cellIdx}
|
||||||
|
selectedUser={$selectedCellMap[cellIdx]}
|
||||||
on:click={() => ($selectedCellId = cellIdx)}
|
on:click={() => ($selectedCellId = cellIdx)}
|
||||||
width={$stickyColumn.width}
|
width={$stickyColumn.width}
|
||||||
reorderTarget={$reorder.targetColumn === $stickyColumn.name}
|
reorderTarget={$reorder.targetColumn === $stickyColumn.name}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import Avatar from "./Avatar.svelte"
|
||||||
|
|
||||||
|
const { users } = getContext("sheet")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="users">
|
||||||
|
{#each $users as user}
|
||||||
|
<Avatar {user} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.users {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0 10px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -138,8 +138,9 @@
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
.arrow {
|
.arrow {
|
||||||
|
border-right: 2px solid var(--spectrum-global-color-blue-400);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 2px;
|
right: 0;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
bottom: 2px;
|
bottom: 2px;
|
||||||
padding: 0 6px 0 16px;
|
padding: 0 6px 0 16px;
|
||||||
|
|
|
@ -4,6 +4,52 @@ export const createUserStores = () => {
|
||||||
const users = writable([])
|
const users = writable([])
|
||||||
const userId = writable(null)
|
const userId = writable(null)
|
||||||
|
|
||||||
|
// Enrich users with unique colours
|
||||||
|
const enrichedUsers = derived([users, userId], ([$users, $userId]) => {
|
||||||
|
return (
|
||||||
|
$users
|
||||||
|
.slice()
|
||||||
|
// Place current user first
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.id === $userId) {
|
||||||
|
return -1
|
||||||
|
} else if (b.id === $userId) {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Enrich users with colors
|
||||||
|
.map((user, idx) => {
|
||||||
|
// Generate random colour hue
|
||||||
|
let hue = 1
|
||||||
|
for (let i = 0; i < user.email.length && i < 5; i++) {
|
||||||
|
hue *= user.email.charCodeAt(i)
|
||||||
|
}
|
||||||
|
hue = hue % 360
|
||||||
|
const color =
|
||||||
|
idx === 0
|
||||||
|
? "var(--spectrum-global-color-blue-400)"
|
||||||
|
: `hsl(${hue}, 50%, 40%)`
|
||||||
|
|
||||||
|
// Generate friendly label
|
||||||
|
let label = user.email
|
||||||
|
if (user.firstName) {
|
||||||
|
label = user.firstName
|
||||||
|
if (user.lastName) {
|
||||||
|
label += ` ${user.lastName}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
color,
|
||||||
|
label,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const updateUser = user => {
|
const updateUser = user => {
|
||||||
const $users = get(users)
|
const $users = get(users)
|
||||||
const index = $users.findIndex(x => x.id === user.id)
|
const index = $users.findIndex(x => x.id === user.id)
|
||||||
|
@ -17,22 +63,35 @@ export const createUserStores = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedCellMap = derived([users, userId], ([$users, $userId]) => {
|
const removeUser = user => {
|
||||||
let map = {}
|
users.update(state => {
|
||||||
$users.forEach(user => {
|
return state.filter(x => x.id !== user.id)
|
||||||
if (user.selectedCellId && user.id !== $userId) {
|
|
||||||
map[user.selectedCellId] = user
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
console.log(map)
|
}
|
||||||
return map
|
|
||||||
})
|
// Generate a lookup map of cell ID to the user that has it selected, to make
|
||||||
|
// lookups inside sheet cells extremely fast
|
||||||
|
const selectedCellMap = derived(
|
||||||
|
[enrichedUsers, userId],
|
||||||
|
([$enrichedUsers, $userId]) => {
|
||||||
|
let map = {}
|
||||||
|
$enrichedUsers.forEach(user => {
|
||||||
|
if (user.selectedCellId && user.id !== $userId) {
|
||||||
|
map[user.selectedCellId] = user
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
users: {
|
users: {
|
||||||
...users,
|
...enrichedUsers,
|
||||||
|
set: users.set,
|
||||||
|
update: users.update,
|
||||||
actions: {
|
actions: {
|
||||||
updateUser,
|
updateUser,
|
||||||
|
removeUser,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
selectedCellMap,
|
selectedCellMap,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
export const getColor = idx => {
|
export const getColor = (idx, opacity = 0.3) => {
|
||||||
if (idx == null || idx === -1) {
|
if (idx == null || idx === -1) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, 0.3)`
|
return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, ${opacity})`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getIconForField = field => {
|
export const getIconForField = field => {
|
||||||
|
|
|
@ -25,12 +25,11 @@ export const createWebsocket = context => {
|
||||||
if (!socket.connected) {
|
if (!socket.connected) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log("Idenifying dataspace", tableId)
|
console.log("Identifying dataspace", tableId)
|
||||||
|
|
||||||
// Identify which dataspace we are editing
|
// Identify which dataspace we are editing
|
||||||
socket.emit("select-dataspace", tableId, response => {
|
socket.emit("select-dataspace", tableId, response => {
|
||||||
// handle initial connection info
|
// handle initial connection info
|
||||||
console.log("response", response)
|
|
||||||
users.set(response.users)
|
users.set(response.users)
|
||||||
userId.set(response.id)
|
userId.set(response.id)
|
||||||
})
|
})
|
||||||
|
@ -42,17 +41,22 @@ export const createWebsocket = context => {
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on("row-update", data => {
|
socket.on("row-update", data => {
|
||||||
console.log("row-update:", data.id)
|
console.log("row-update", data.id)
|
||||||
if (data.id) {
|
if (data.id) {
|
||||||
rows.actions.refreshRow(data.id)
|
rows.actions.refreshRow(data.id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on("user-update", user => {
|
socket.on("user-update", user => {
|
||||||
console.log("user-update", user)
|
console.log("user-update", user.id)
|
||||||
users.actions.updateUser(user)
|
users.actions.updateUser(user)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
socket.on("user-disconnect", user => {
|
||||||
|
console.log("user-disconnect", user.id)
|
||||||
|
users.actions.removeUser(user)
|
||||||
|
})
|
||||||
|
|
||||||
socket.on("connect_error", err => {
|
socket.on("connect_error", err => {
|
||||||
console.log("Failed to connect to websocket:", err.message)
|
console.log("Failed to connect to websocket:", err.message)
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,31 +15,40 @@ export default class DataspaceSocket extends Socket {
|
||||||
// Socket state
|
// Socket state
|
||||||
let currentRoom: string
|
let currentRoom: string
|
||||||
|
|
||||||
// Initial identification of conneted dataspace
|
// Initial identification of connected dataspace
|
||||||
socket.on("select-dataspace", async (tableId, callback) => {
|
socket.on("select-dataspace", async (tableId, callback) => {
|
||||||
|
// Leave current room
|
||||||
if (currentRoom) {
|
if (currentRoom) {
|
||||||
|
socket.to(currentRoom).emit("user-disconnect", socket.data.user)
|
||||||
socket.leave(currentRoom)
|
socket.leave(currentRoom)
|
||||||
}
|
}
|
||||||
socket.join(tableId)
|
|
||||||
currentRoom = tableId
|
|
||||||
const sockets = await this.io.in(tableId).fetchSockets()
|
|
||||||
socket.broadcast.emit("user-update", socket.data.user)
|
|
||||||
|
|
||||||
|
// Join new room
|
||||||
|
currentRoom = tableId
|
||||||
|
socket.join(currentRoom)
|
||||||
|
socket.to(currentRoom).emit("user-update", socket.data.user)
|
||||||
|
|
||||||
|
// Reply with all users in current room
|
||||||
|
const sockets = await this.io.in(currentRoom).fetchSockets()
|
||||||
callback({
|
callback({
|
||||||
users: sockets.map(socket => socket.data.user),
|
users: sockets.map(socket => socket.data.user),
|
||||||
id: user.id,
|
id: user.id,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Handle users selecting a new cell
|
||||||
socket.on("select-cell", cellId => {
|
socket.on("select-cell", cellId => {
|
||||||
console.log("cell update for " + user.id + " to " + cellId)
|
|
||||||
socket.data.user.selectedCellId = cellId
|
socket.data.user.selectedCellId = cellId
|
||||||
socket.broadcast.emit("user-update", socket.data.user)
|
if (currentRoom) {
|
||||||
|
socket.to(currentRoom).emit("user-update", socket.data.user)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Disconnection cleanup
|
// Disconnection cleanup
|
||||||
socket.on("disconnect", reason => {
|
socket.on("disconnect", () => {
|
||||||
console.log(`Disconnecting ${user.email} because of ${reason}`)
|
if (currentRoom) {
|
||||||
|
socket.to(currentRoom).emit("user-disconnect", socket.data.user)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue