Add indicators to show selected state in data section
This commit is contained in:
parent
4725faf8b5
commit
7be2d6896e
|
@ -118,3 +118,16 @@ export const selectedAutomation = derived(automationStore, $automationStore => {
|
||||||
x => x._id === $automationStore.selectedAutomationId
|
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
|
||||||
|
})
|
||||||
|
|
|
@ -38,6 +38,7 @@ import {
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
import { getComponentFieldOptions } from "helpers/formFields"
|
import { getComponentFieldOptions } from "helpers/formFields"
|
||||||
import { createBuilderWebsocket } from "builderStore/websocket"
|
import { createBuilderWebsocket } from "builderStore/websocket"
|
||||||
|
import { BuilderSocketEvent } from "@budibase/shared-core"
|
||||||
|
|
||||||
const INITIAL_FRONTEND_STATE = {
|
const INITIAL_FRONTEND_STATE = {
|
||||||
initialised: false,
|
initialised: false,
|
||||||
|
@ -1394,6 +1395,13 @@ export const getFrontendStore = () => {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
websocket: {
|
||||||
|
selectResource: id => {
|
||||||
|
websocket.emit(BuilderSocketEvent.SelectResource, {
|
||||||
|
resourceId: id,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return store
|
return store
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
} from "helpers/data/utils"
|
} from "helpers/data/utils"
|
||||||
import IntegrationIcon from "./IntegrationIcon.svelte"
|
import IntegrationIcon from "./IntegrationIcon.svelte"
|
||||||
import { TableNames } from "constants"
|
import { TableNames } from "constants"
|
||||||
|
import { userSelectedResourceMap } from "builderStore"
|
||||||
|
|
||||||
let openDataSources = []
|
let openDataSources = []
|
||||||
|
|
||||||
|
@ -166,6 +167,7 @@
|
||||||
selected={$isActive("./table/:tableId") &&
|
selected={$isActive("./table/:tableId") &&
|
||||||
$tables.selected?._id === TableNames.USERS}
|
$tables.selected?._id === TableNames.USERS}
|
||||||
on:click={() => selectTable(TableNames.USERS)}
|
on:click={() => selectTable(TableNames.USERS)}
|
||||||
|
selectedBy={$userSelectedResourceMap[TableNames.USERS]}
|
||||||
/>
|
/>
|
||||||
{#each enrichedDataSources as datasource, idx}
|
{#each enrichedDataSources as datasource, idx}
|
||||||
<NavItem
|
<NavItem
|
||||||
|
@ -176,6 +178,7 @@
|
||||||
withArrow={true}
|
withArrow={true}
|
||||||
on:click={() => selectDatasource(datasource)}
|
on:click={() => selectDatasource(datasource)}
|
||||||
on:iconClick={() => toggleNode(datasource)}
|
on:iconClick={() => toggleNode(datasource)}
|
||||||
|
selectedBy={$userSelectedResourceMap[datasource._id]}
|
||||||
>
|
>
|
||||||
<div class="datasource-icon" slot="icon">
|
<div class="datasource-icon" slot="icon">
|
||||||
<IntegrationIcon
|
<IntegrationIcon
|
||||||
|
@ -201,6 +204,7 @@
|
||||||
selected={$isActive("./query/:queryId") &&
|
selected={$isActive("./query/:queryId") &&
|
||||||
$queries.selectedQueryId === query._id}
|
$queries.selectedQueryId === query._id}
|
||||||
on:click={() => $goto(`./query/${query._id}`)}
|
on:click={() => $goto(`./query/${query._id}`)}
|
||||||
|
selectedBy={$userSelectedResourceMap[query._id]}
|
||||||
>
|
>
|
||||||
<EditQueryPopover {query} />
|
<EditQueryPopover {query} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
import { goto, isActive } from "@roxi/routify"
|
import { goto, isActive } from "@roxi/routify"
|
||||||
|
import { userSelectedResourceMap } from "builderStore"
|
||||||
|
|
||||||
const alphabetical = (a, b) =>
|
const alphabetical = (a, b) =>
|
||||||
a.name?.toLowerCase() > b.name?.toLowerCase() ? 1 : -1
|
a.name?.toLowerCase() > b.name?.toLowerCase() ? 1 : -1
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
selected={$isActive("./table/:tableId") &&
|
selected={$isActive("./table/:tableId") &&
|
||||||
$tables.selected?._id === table._id}
|
$tables.selected?._id === table._id}
|
||||||
on:click={() => selectTable(table._id)}
|
on:click={() => selectTable(table._id)}
|
||||||
|
selectedBy={$userSelectedResourceMap[table._id]}
|
||||||
>
|
>
|
||||||
{#if table._id !== TableNames.USERS}
|
{#if table._id !== TableNames.USERS}
|
||||||
<EditTablePopover {table} />
|
<EditTablePopover {table} />
|
||||||
|
@ -42,6 +44,7 @@
|
||||||
text={viewName}
|
text={viewName}
|
||||||
selected={$isActive("./view") && $views.selected?.name === viewName}
|
selected={$isActive("./view") && $views.selected?.name === viewName}
|
||||||
on:click={() => $goto(`./view/${encodeURIComponent(viewName)}`)}
|
on:click={() => $goto(`./view/${encodeURIComponent(viewName)}`)}
|
||||||
|
selectedBy={$userSelectedResourceMap[viewName]}
|
||||||
>
|
>
|
||||||
<EditViewPopover
|
<EditViewPopover
|
||||||
view={{ name: viewName, ...table.views[viewName] }}
|
view={{ name: viewName, ...table.views[viewName] }}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
import { createEventDispatcher, getContext } from "svelte"
|
import { createEventDispatcher, getContext } from "svelte"
|
||||||
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
import { UserAvatar } from "@budibase/frontend-core"
|
||||||
|
|
||||||
export let icon
|
export let icon
|
||||||
export let withArrow = false
|
export let withArrow = false
|
||||||
|
@ -18,12 +20,15 @@
|
||||||
export let rightAlignIcon = false
|
export let rightAlignIcon = false
|
||||||
export let id
|
export let id
|
||||||
export let showTooltip = false
|
export let showTooltip = false
|
||||||
|
export let selectedBy = null
|
||||||
|
|
||||||
const scrollApi = getContext("scroll")
|
const scrollApi = getContext("scroll")
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let contentRef
|
let contentRef
|
||||||
|
|
||||||
$: selected && contentRef && scrollToView()
|
$: selected && contentRef && scrollToView()
|
||||||
|
$: style = getStyle(indentLevel, selectedBy)
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
scrollToView()
|
scrollToView()
|
||||||
|
@ -42,6 +47,14 @@
|
||||||
const bounds = contentRef.getBoundingClientRect()
|
const bounds = contentRef.getBoundingClientRect()
|
||||||
scrollApi.scrollTo(bounds)
|
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>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -51,8 +64,7 @@
|
||||||
class:withActions
|
class:withActions
|
||||||
class:scrollable
|
class:scrollable
|
||||||
class:highlighted
|
class:highlighted
|
||||||
style={`padding-left: calc(${indentLevel * 14}px)`}
|
class:selectedBy
|
||||||
{draggable}
|
|
||||||
on:dragend
|
on:dragend
|
||||||
on:dragstart
|
on:dragstart
|
||||||
on:dragover
|
on:dragover
|
||||||
|
@ -61,6 +73,8 @@
|
||||||
ondragover="return false"
|
ondragover="return false"
|
||||||
ondragenter="return false"
|
ondragenter="return false"
|
||||||
{id}
|
{id}
|
||||||
|
{style}
|
||||||
|
{draggable}
|
||||||
>
|
>
|
||||||
<div class="nav-item-content" bind:this={contentRef}>
|
<div class="nav-item-content" bind:this={contentRef}>
|
||||||
{#if withArrow}
|
{#if withArrow}
|
||||||
|
@ -97,6 +111,14 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -142,6 +164,37 @@
|
||||||
padding-left: var(--spacing-l);
|
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 */
|
/* Needed to fully display the actions icon */
|
||||||
.nav-item.scrollable .nav-item-content {
|
.nav-item.scrollable .nav-item-content {
|
||||||
padding-right: 1px;
|
padding-right: 1px;
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
import { isActive, goto, redirect } from "@roxi/routify"
|
import { isActive, goto, redirect } from "@roxi/routify"
|
||||||
import BetaButton from "./_components/BetaButton.svelte"
|
import BetaButton from "./_components/BetaButton.svelte"
|
||||||
import { datasources } from "stores/backend"
|
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
|
// If we ever don't have any data other than the users table, prompt the
|
||||||
|
@ -13,6 +15,10 @@
|
||||||
$redirect("./new")
|
$redirect("./new")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
store.actions.websocket.selectResource(null)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- routify:options index=1 -->
|
<!-- routify:options index=1 -->
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
import { syncURLToState } from "helpers/urlStateSync"
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
import * as routify from "@roxi/routify"
|
import * as routify from "@roxi/routify"
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
|
$: datasourceId = $datasources.selectedDatasourceId
|
||||||
|
$: store.actions.websocket.selectResource(datasourceId)
|
||||||
|
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
urlParam: "datasourceId",
|
urlParam: "datasourceId",
|
||||||
|
|
|
@ -7,9 +7,11 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
|
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
|
||||||
import { TableNames } from "constants"
|
import { TableNames } from "constants"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
|
$: store.actions.websocket.selectResource(BUDIBASE_INTERNAL_DB_ID)
|
||||||
$: internalTablesBySourceId = $tables.list.filter(
|
$: internalTablesBySourceId = $tables.list.filter(
|
||||||
table =>
|
table =>
|
||||||
table.type !== "external" &&
|
table.type !== "external" &&
|
||||||
|
|
|
@ -6,8 +6,11 @@
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { DEFAULT_BB_DATASOURCE_ID } from "constants/backend"
|
import { DEFAULT_BB_DATASOURCE_ID } from "constants/backend"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
|
$: store.actions.websocket.selectResource(DEFAULT_BB_DATASOURCE_ID)
|
||||||
$: internalTablesBySourceId = $tables.list.filter(
|
$: internalTablesBySourceId = $tables.list.filter(
|
||||||
table =>
|
table =>
|
||||||
table.type !== "external" && table.sourceId === DEFAULT_BB_DATASOURCE_ID
|
table.type !== "external" && table.sourceId === DEFAULT_BB_DATASOURCE_ID
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
import { syncURLToState } from "helpers/urlStateSync"
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
import * as routify from "@roxi/routify"
|
import * as routify from "@roxi/routify"
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
|
$: queryId = $queries.selectedQueryId
|
||||||
|
$: store.actions.websocket.selectResource(queryId)
|
||||||
|
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
urlParam: "queryId",
|
urlParam: "queryId",
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import * as routify from "@roxi/routify"
|
import * as routify from "@roxi/routify"
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
|
$: tableId = $tables.selectedTableId
|
||||||
|
$: store.actions.websocket.selectResource(tableId)
|
||||||
|
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
urlParam: "tableId",
|
urlParam: "tableId",
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
import { syncURLToState } from "helpers/urlStateSync"
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
import * as routify from "@roxi/routify"
|
import * as routify from "@roxi/routify"
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
|
$: viewName = $views.selectedViewName
|
||||||
|
$: store.actions.websocket.selectResource(viewName)
|
||||||
|
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
urlParam: "viewName",
|
urlParam: "viewName",
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
import { gridSocket } from "./index"
|
import { gridSocket } from "./index"
|
||||||
import { clearLock, updateLock } from "../utilities/redis"
|
import { clearLock, updateLock } from "../utilities/redis"
|
||||||
import { Socket } from "socket.io"
|
import { Socket } from "socket.io"
|
||||||
import { BuilderSocketEvent } from "@budibase/shared-core"
|
import { BuilderSocketEvent, GridSocketEvent } from "@budibase/shared-core"
|
||||||
|
|
||||||
export default class BuilderSocket extends BaseSocket {
|
export default class BuilderSocket extends BaseSocket {
|
||||||
constructor(app: Koa, server: http.Server) {
|
constructor(app: Koa, server: http.Server) {
|
||||||
|
@ -38,6 +38,11 @@ export default class BuilderSocket extends BaseSocket {
|
||||||
// Reply with all current sessions
|
// Reply with all current sessions
|
||||||
callback({ users: sessions })
|
callback({ users: sessions })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Handle users selecting a new cell
|
||||||
|
socket?.on(BuilderSocketEvent.SelectResource, ({ resourceId }) => {
|
||||||
|
this.updateUser(socket, { selectedResourceId: resourceId })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async onDisconnect(socket: Socket) {
|
async onDisconnect(socket: Socket) {
|
||||||
|
|
|
@ -88,6 +88,7 @@ export enum BuilderSocketEvent {
|
||||||
LockTransfer = "LockTransfer",
|
LockTransfer = "LockTransfer",
|
||||||
ScreenChange = "ScreenChange",
|
ScreenChange = "ScreenChange",
|
||||||
AppMetadataChange = "AppMetadataChange",
|
AppMetadataChange = "AppMetadataChange",
|
||||||
|
SelectResource = "SelectResource",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SocketSessionTTL = 60
|
export const SocketSessionTTL = 60
|
||||||
|
|
Loading…
Reference in New Issue