1
0
Fork 0
mirror of synced 2024-10-02 10:08:09 +13:00

Add indicators to show selected state in data section

This commit is contained in:
Andrew Kingston 2023-07-04 08:58:14 +01:00
parent 4725faf8b5
commit 7be2d6896e
14 changed files with 117 additions and 3 deletions

View file

@ -118,3 +118,16 @@ export const selectedAutomation = derived(automationStore, $automationStore => {
x => x._id === $automationStore.selectedAutomationId
)
})
// Derive map of resource IDs to other users.
// We only ever care about a single user in each resource, so if multiple users
// share the same datasource we can just overwrite them.
export const userSelectedResourceMap = derived(userStore, $userStore => {
let map = {}
$userStore.forEach(user => {
if (user.selectedResourceId) {
map[user.selectedResourceId] = user
}
})
return map
})

View file

@ -38,6 +38,7 @@ import {
import { makePropSafe as safe } from "@budibase/string-templates"
import { getComponentFieldOptions } from "helpers/formFields"
import { createBuilderWebsocket } from "builderStore/websocket"
import { BuilderSocketEvent } from "@budibase/shared-core"
const INITIAL_FRONTEND_STATE = {
initialised: false,
@ -1394,6 +1395,13 @@ export const getFrontendStore = () => {
})
},
},
websocket: {
selectResource: id => {
websocket.emit(BuilderSocketEvent.SelectResource, {
resourceId: id,
})
},
},
}
return store

View file

@ -13,6 +13,7 @@
} from "helpers/data/utils"
import IntegrationIcon from "./IntegrationIcon.svelte"
import { TableNames } from "constants"
import { userSelectedResourceMap } from "builderStore"
let openDataSources = []
@ -166,6 +167,7 @@
selected={$isActive("./table/:tableId") &&
$tables.selected?._id === TableNames.USERS}
on:click={() => selectTable(TableNames.USERS)}
selectedBy={$userSelectedResourceMap[TableNames.USERS]}
/>
{#each enrichedDataSources as datasource, idx}
<NavItem
@ -176,6 +178,7 @@
withArrow={true}
on:click={() => selectDatasource(datasource)}
on:iconClick={() => toggleNode(datasource)}
selectedBy={$userSelectedResourceMap[datasource._id]}
>
<div class="datasource-icon" slot="icon">
<IntegrationIcon
@ -201,6 +204,7 @@
selected={$isActive("./query/:queryId") &&
$queries.selectedQueryId === query._id}
on:click={() => $goto(`./query/${query._id}`)}
selectedBy={$userSelectedResourceMap[query._id]}
>
<EditQueryPopover {query} />
</NavItem>

View file

@ -5,6 +5,7 @@
import EditViewPopover from "./popovers/EditViewPopover.svelte"
import NavItem from "components/common/NavItem.svelte"
import { goto, isActive } from "@roxi/routify"
import { userSelectedResourceMap } from "builderStore"
const alphabetical = (a, b) =>
a.name?.toLowerCase() > b.name?.toLowerCase() ? 1 : -1
@ -30,6 +31,7 @@
selected={$isActive("./table/:tableId") &&
$tables.selected?._id === table._id}
on:click={() => selectTable(table._id)}
selectedBy={$userSelectedResourceMap[table._id]}
>
{#if table._id !== TableNames.USERS}
<EditTablePopover {table} />
@ -42,6 +44,7 @@
text={viewName}
selected={$isActive("./view") && $views.selected?.name === viewName}
on:click={() => $goto(`./view/${encodeURIComponent(viewName)}`)}
selectedBy={$userSelectedResourceMap[viewName]}
>
<EditViewPopover
view={{ name: viewName, ...table.views[viewName] }}

View file

@ -1,6 +1,8 @@
<script>
import { Icon } from "@budibase/bbui"
import { createEventDispatcher, getContext } from "svelte"
import { helpers } from "@budibase/shared-core"
import { UserAvatar } from "@budibase/frontend-core"
export let icon
export let withArrow = false
@ -18,12 +20,15 @@
export let rightAlignIcon = false
export let id
export let showTooltip = false
export let selectedBy = null
const scrollApi = getContext("scroll")
const dispatch = createEventDispatcher()
let contentRef
$: selected && contentRef && scrollToView()
$: style = getStyle(indentLevel, selectedBy)
const onClick = () => {
scrollToView()
@ -42,6 +47,14 @@
const bounds = contentRef.getBoundingClientRect()
scrollApi.scrollTo(bounds)
}
const getStyle = (indentLevel, selectedBy) => {
let style = `padding-left:calc(${indentLevel * 14}px);`
if (selectedBy) {
style += `--selected-by-color:${helpers.getUserColor(selectedBy)};`
}
return style
}
</script>
<div
@ -51,8 +64,7 @@
class:withActions
class:scrollable
class:highlighted
style={`padding-left: calc(${indentLevel * 14}px)`}
{draggable}
class:selectedBy
on:dragend
on:dragstart
on:dragover
@ -61,6 +73,8 @@
ondragover="return false"
ondragenter="return false"
{id}
{style}
{draggable}
>
<div class="nav-item-content" bind:this={contentRef}>
{#if withArrow}
@ -97,6 +111,14 @@
</div>
{/if}
</div>
{#if selectedBy}
<div class="selected-by-label">{helpers.getUserLabel(selectedBy)}</div>
{/if}
<!--{#if selectedBy}-->
<!-- <div class="avatar">-->
<!-- <UserAvatar size="S" user={selectedBy} tooltipDirection="left" />-->
<!-- </div>-->
<!--{/if}-->
</div>
<style>
@ -142,6 +164,37 @@
padding-left: var(--spacing-l);
}
/* Selected user styles */
.nav-item.selectedBy:after {
content: "";
position: absolute;
width: calc(100% - 4px);
height: 28px;
border: 2px solid var(--selected-by-color);
left: 0;
border-radius: 2px;
}
.selected-by-label {
position: absolute;
right: 0;
background: var(--selected-by-color);
padding: 2px 4px;
font-size: 12px;
color: white;
transform: translateY(calc(1px - 100%));
border-top-right-radius: 2px;
border-top-left-radius: 2px;
pointer-events: none;
opacity: 0;
transition: opacity 130ms ease-out;
}
.nav-item.selectedBy:hover .selected-by-label {
opacity: 1;
}
.avatar {
align-self: center;
}
/* Needed to fully display the actions icon */
.nav-item.scrollable .nav-item-content {
padding-right: 1px;

View file

@ -5,6 +5,8 @@
import { isActive, goto, redirect } from "@roxi/routify"
import BetaButton from "./_components/BetaButton.svelte"
import { datasources } from "stores/backend"
import { onDestroy } from "svelte"
import { store } from "builderStore"
$: {
// If we ever don't have any data other than the users table, prompt the
@ -13,6 +15,10 @@
$redirect("./new")
}
}
onDestroy(() => {
store.actions.websocket.selectResource(null)
})
</script>
<!-- routify:options index=1 -->

View file

@ -4,6 +4,10 @@
import { syncURLToState } from "helpers/urlStateSync"
import * as routify from "@roxi/routify"
import { onDestroy } from "svelte"
import { store } from "builderStore"
$: datasourceId = $datasources.selectedDatasourceId
$: store.actions.websocket.selectResource(datasourceId)
const stopSyncing = syncURLToState({
urlParam: "datasourceId",

View file

@ -7,9 +7,11 @@
import { onMount } from "svelte"
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
import { TableNames } from "constants"
import { store } from "builderStore"
let modal
$: store.actions.websocket.selectResource(BUDIBASE_INTERNAL_DB_ID)
$: internalTablesBySourceId = $tables.list.filter(
table =>
table.type !== "external" &&

View file

@ -6,8 +6,11 @@
import { goto } from "@roxi/routify"
import { DEFAULT_BB_DATASOURCE_ID } from "constants/backend"
import { onMount } from "svelte"
import { store } from "builderStore"
let modal
$: store.actions.websocket.selectResource(DEFAULT_BB_DATASOURCE_ID)
$: internalTablesBySourceId = $tables.list.filter(
table =>
table.type !== "external" && table.sourceId === DEFAULT_BB_DATASOURCE_ID

View file

@ -3,6 +3,10 @@
import { syncURLToState } from "helpers/urlStateSync"
import * as routify from "@roxi/routify"
import { onDestroy } from "svelte"
import { store } from "builderStore"
$: queryId = $queries.selectedQueryId
$: store.actions.websocket.selectResource(queryId)
const stopSyncing = syncURLToState({
urlParam: "queryId",

View file

@ -3,6 +3,10 @@
import { tables } from "stores/backend"
import * as routify from "@roxi/routify"
import { onDestroy } from "svelte"
import { store } from "builderStore"
$: tableId = $tables.selectedTableId
$: store.actions.websocket.selectResource(tableId)
const stopSyncing = syncURLToState({
urlParam: "tableId",

View file

@ -3,6 +3,10 @@
import { syncURLToState } from "helpers/urlStateSync"
import * as routify from "@roxi/routify"
import { onDestroy } from "svelte"
import { store } from "builderStore"
$: viewName = $views.selectedViewName
$: store.actions.websocket.selectResource(viewName)
const stopSyncing = syncURLToState({
urlParam: "viewName",

View file

@ -13,7 +13,7 @@ import {
import { gridSocket } from "./index"
import { clearLock, updateLock } from "../utilities/redis"
import { Socket } from "socket.io"
import { BuilderSocketEvent } from "@budibase/shared-core"
import { BuilderSocketEvent, GridSocketEvent } from "@budibase/shared-core"
export default class BuilderSocket extends BaseSocket {
constructor(app: Koa, server: http.Server) {
@ -38,6 +38,11 @@ export default class BuilderSocket extends BaseSocket {
// Reply with all current sessions
callback({ users: sessions })
})
// Handle users selecting a new cell
socket?.on(BuilderSocketEvent.SelectResource, ({ resourceId }) => {
this.updateUser(socket, { selectedResourceId: resourceId })
})
}
async onDisconnect(socket: Socket) {

View file

@ -88,6 +88,7 @@ export enum BuilderSocketEvent {
LockTransfer = "LockTransfer",
ScreenChange = "ScreenChange",
AppMetadataChange = "AppMetadataChange",
SelectResource = "SelectResource",
}
export const SocketSessionTTL = 60