Merge branch 'master' of github.com:Budibase/budibase into contributor-enhancements
This commit is contained in:
commit
c47b7d5adf
|
@ -22,7 +22,7 @@
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
$: blockRefs = $selectedAutomation?.blockRefs || {}
|
$: blockRefs = $selectedAutomation?.blockRefs || {}
|
||||||
$: stepNames = automation?.definition.stepNames
|
$: stepNames = automation?.definition.stepNames || {}
|
||||||
$: allSteps = automation?.definition.steps || []
|
$: allSteps = automation?.definition.steps || []
|
||||||
$: automationName = itemName || stepNames?.[block.id] || block?.name || ""
|
$: automationName = itemName || stepNames?.[block.id] || block?.name || ""
|
||||||
$: automationNameError = getAutomationNameError(automationName)
|
$: automationNameError = getAutomationNameError(automationName)
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
const getAutomationNameError = name => {
|
const getAutomationNameError = name => {
|
||||||
const duplicateError =
|
const duplicateError =
|
||||||
"This name already exists, please enter a unique name"
|
"This name already exists, please enter a unique name"
|
||||||
if (stepNames && editing) {
|
if (editing) {
|
||||||
for (const [key, value] of Object.entries(stepNames)) {
|
for (const [key, value] of Object.entries(stepNames)) {
|
||||||
if (name !== block.name && name === value && key !== block.id) {
|
if (name !== block.name && name === value && key !== block.id) {
|
||||||
return duplicateError
|
return duplicateError
|
||||||
|
|
|
@ -1,7 +1,54 @@
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { BudiStore } from "../BudiStore"
|
import { BudiStore } from "../BudiStore"
|
||||||
|
import {
|
||||||
|
App,
|
||||||
|
AppFeatures,
|
||||||
|
AppIcon,
|
||||||
|
AutomationSettings,
|
||||||
|
Plugin,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
export const INITIAL_APP_META_STATE = {
|
interface ClientFeatures {
|
||||||
|
spectrumThemes: boolean
|
||||||
|
intelligentLoading: boolean
|
||||||
|
deviceAwareness: boolean
|
||||||
|
state: boolean
|
||||||
|
rowSelection: boolean
|
||||||
|
customThemes: boolean
|
||||||
|
devicePreview: boolean
|
||||||
|
messagePassing: boolean
|
||||||
|
continueIfAction: boolean
|
||||||
|
showNotificationAction: boolean
|
||||||
|
sidePanel: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TypeSupportPresets {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AppMetaState {
|
||||||
|
appId: string
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
libraries: string[]
|
||||||
|
clientFeatures: ClientFeatures
|
||||||
|
typeSupportPresets: TypeSupportPresets
|
||||||
|
features: AppFeatures
|
||||||
|
clientLibPath: string
|
||||||
|
hasLock: boolean
|
||||||
|
appInstance: { _id: string } | null
|
||||||
|
initialised: boolean
|
||||||
|
hasAppPackage: boolean
|
||||||
|
usedPlugins: Plugin[] | null
|
||||||
|
automations: AutomationSettings
|
||||||
|
routes: { [key: string]: any }
|
||||||
|
version?: string
|
||||||
|
revertableVersion?: string
|
||||||
|
upgradableVersion?: string
|
||||||
|
icon?: AppIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INITIAL_APP_META_STATE: AppMetaState = {
|
||||||
appId: "",
|
appId: "",
|
||||||
name: "",
|
name: "",
|
||||||
url: "",
|
url: "",
|
||||||
|
@ -34,23 +81,27 @@ export const INITIAL_APP_META_STATE = {
|
||||||
routes: {},
|
routes: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AppMetaStore extends BudiStore {
|
export class AppMetaStore extends BudiStore<AppMetaState> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(INITIAL_APP_META_STATE)
|
super(INITIAL_APP_META_STATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset(): void {
|
||||||
this.store.set({ ...INITIAL_APP_META_STATE })
|
this.store.set({ ...INITIAL_APP_META_STATE })
|
||||||
}
|
}
|
||||||
|
|
||||||
syncAppPackage(pkg) {
|
syncAppPackage(pkg: {
|
||||||
|
application: App
|
||||||
|
clientLibPath: string
|
||||||
|
hasLock: boolean
|
||||||
|
}): void {
|
||||||
const { application: app, clientLibPath, hasLock } = pkg
|
const { application: app, clientLibPath, hasLock } = pkg
|
||||||
|
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
name: app.name,
|
name: app.name,
|
||||||
appId: app.appId,
|
appId: app.appId,
|
||||||
url: app.url,
|
url: app.url || "",
|
||||||
hasLock,
|
hasLock,
|
||||||
clientLibPath,
|
clientLibPath,
|
||||||
libraries: app.componentLibraries,
|
libraries: app.componentLibraries,
|
||||||
|
@ -58,8 +109,8 @@ export class AppMetaStore extends BudiStore {
|
||||||
appInstance: app.instance,
|
appInstance: app.instance,
|
||||||
revertableVersion: app.revertableVersion,
|
revertableVersion: app.revertableVersion,
|
||||||
upgradableVersion: app.upgradableVersion,
|
upgradableVersion: app.upgradableVersion,
|
||||||
usedPlugins: app.usedPlugins,
|
usedPlugins: app.usedPlugins || null,
|
||||||
icon: app.icon || {},
|
icon: app.icon,
|
||||||
features: {
|
features: {
|
||||||
...INITIAL_APP_META_STATE.features,
|
...INITIAL_APP_META_STATE.features,
|
||||||
...app.features,
|
...app.features,
|
||||||
|
@ -70,7 +121,7 @@ export class AppMetaStore extends BudiStore {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
syncClientFeatures(features) {
|
syncClientFeatures(features: Partial<ClientFeatures>): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
clientFeatures: {
|
clientFeatures: {
|
||||||
|
@ -80,14 +131,14 @@ export class AppMetaStore extends BudiStore {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
syncClientTypeSupportPresets(typeSupportPresets) {
|
syncClientTypeSupportPresets(typeSupportPresets: TypeSupportPresets): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
typeSupportPresets,
|
typeSupportPresets,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncAppRoutes() {
|
async syncAppRoutes(): Promise<void> {
|
||||||
const resp = await API.fetchAppRoutes()
|
const resp = await API.fetchAppRoutes()
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
|
@ -96,7 +147,7 @@ export class AppMetaStore extends BudiStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returned from socket
|
// Returned from socket
|
||||||
syncMetadata(metadata) {
|
syncMetadata(metadata: { name: string; url: string; icon?: AppIcon }): void {
|
||||||
const { name, url, icon } = metadata
|
const { name, url, icon } = metadata
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
|
@ -1,10 +1,28 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { createBuilderWebsocket } from "./websocket.js"
|
import { createBuilderWebsocket } from "./websocket.js"
|
||||||
|
import { Socket } from "socket.io-client"
|
||||||
import { BuilderSocketEvent } from "@budibase/shared-core"
|
import { BuilderSocketEvent } from "@budibase/shared-core"
|
||||||
import { BudiStore } from "../BudiStore.js"
|
import { BudiStore } from "../BudiStore.js"
|
||||||
import { TOUR_KEYS } from "components/portal/onboarding/tours.js"
|
import { TOUR_KEYS } from "components/portal/onboarding/tours.js"
|
||||||
|
import { App } from "@budibase/types"
|
||||||
|
|
||||||
export const INITIAL_BUILDER_STATE = {
|
interface BuilderState {
|
||||||
|
previousTopNavPath: Record<string, string>
|
||||||
|
highlightedSetting: {
|
||||||
|
key: string
|
||||||
|
type: "info" | string
|
||||||
|
} | null
|
||||||
|
propertyFocus: string | null
|
||||||
|
builderSidePanel: boolean
|
||||||
|
onboarding: boolean
|
||||||
|
tourNodes: Record<string, HTMLElement> | null
|
||||||
|
tourKey: string | null
|
||||||
|
tourStepKey: string | null
|
||||||
|
hoveredComponentId: string | null
|
||||||
|
websocket?: Socket
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INITIAL_BUILDER_STATE: BuilderState = {
|
||||||
previousTopNavPath: {},
|
previousTopNavPath: {},
|
||||||
highlightedSetting: null,
|
highlightedSetting: null,
|
||||||
propertyFocus: null,
|
propertyFocus: null,
|
||||||
|
@ -16,7 +34,9 @@ export const INITIAL_BUILDER_STATE = {
|
||||||
hoveredComponentId: null,
|
hoveredComponentId: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BuilderStore extends BudiStore {
|
export class BuilderStore extends BudiStore<BuilderState> {
|
||||||
|
websocket?: Socket
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({ ...INITIAL_BUILDER_STATE })
|
super({ ...INITIAL_BUILDER_STATE })
|
||||||
|
|
||||||
|
@ -32,11 +52,9 @@ export class BuilderStore extends BudiStore {
|
||||||
this.registerTourNode = this.registerTourNode.bind(this)
|
this.registerTourNode = this.registerTourNode.bind(this)
|
||||||
this.destroyTourNode = this.destroyTourNode.bind(this)
|
this.destroyTourNode = this.destroyTourNode.bind(this)
|
||||||
this.startBuilderOnboarding = this.startBuilderOnboarding.bind(this)
|
this.startBuilderOnboarding = this.startBuilderOnboarding.bind(this)
|
||||||
|
|
||||||
this.websocket
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init(app) {
|
init(app: App): void {
|
||||||
if (!app?.appId) {
|
if (!app?.appId) {
|
||||||
console.error("BuilderStore: No appId supplied for websocket")
|
console.error("BuilderStore: No appId supplied for websocket")
|
||||||
return
|
return
|
||||||
|
@ -46,45 +64,46 @@ export class BuilderStore extends BudiStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh(): void {
|
||||||
this.store.set(this.store.get())
|
const currentState = get(this.store)
|
||||||
|
this.store.set(currentState)
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset(): void {
|
||||||
this.store.set({ ...INITIAL_BUILDER_STATE })
|
this.store.set({ ...INITIAL_BUILDER_STATE })
|
||||||
this.websocket?.disconnect()
|
this.websocket?.disconnect()
|
||||||
this.websocket = null
|
this.websocket = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
highlightSetting(key, type) {
|
highlightSetting(key?: string, type?: string): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
highlightedSetting: key ? { key, type: type || "info" } : null,
|
highlightedSetting: key ? { key, type: type || "info" } : null,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
propertyFocus(key) {
|
propertyFocus(key: string | null): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
propertyFocus: key,
|
propertyFocus: key,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
showBuilderSidePanel() {
|
showBuilderSidePanel(): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
builderSidePanel: true,
|
builderSidePanel: true,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
hideBuilderSidePanel() {
|
hideBuilderSidePanel(): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
builderSidePanel: false,
|
builderSidePanel: false,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
setPreviousTopNavPath(route, url) {
|
setPreviousTopNavPath(route: string, url: string): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
previousTopNavPath: {
|
previousTopNavPath: {
|
||||||
|
@ -94,13 +113,13 @@ export class BuilderStore extends BudiStore {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
selectResource(id) {
|
selectResource(id: string): void {
|
||||||
this.websocket.emit(BuilderSocketEvent.SelectResource, {
|
this.websocket?.emit(BuilderSocketEvent.SelectResource, {
|
||||||
resourceId: id,
|
resourceId: id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
registerTourNode(tourStepKey, node) {
|
registerTourNode(tourStepKey: string, node: HTMLElement): void {
|
||||||
this.update(state => {
|
this.update(state => {
|
||||||
const update = {
|
const update = {
|
||||||
...state,
|
...state,
|
||||||
|
@ -113,7 +132,7 @@ export class BuilderStore extends BudiStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyTourNode(tourStepKey) {
|
destroyTourNode(tourStepKey: string): void {
|
||||||
const store = get(this.store)
|
const store = get(this.store)
|
||||||
if (store.tourNodes?.[tourStepKey]) {
|
if (store.tourNodes?.[tourStepKey]) {
|
||||||
const nodes = { ...store.tourNodes }
|
const nodes = { ...store.tourNodes }
|
||||||
|
@ -125,7 +144,7 @@ export class BuilderStore extends BudiStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startBuilderOnboarding() {
|
startBuilderOnboarding(): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
onboarding: true,
|
onboarding: true,
|
||||||
|
@ -133,19 +152,19 @@ export class BuilderStore extends BudiStore {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
endBuilderOnboarding() {
|
endBuilderOnboarding(): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
onboarding: false,
|
onboarding: false,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
setTour(tourKey) {
|
setTour(tourKey?: string | null): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
tourStepKey: null,
|
tourStepKey: null,
|
||||||
tourNodes: null,
|
tourNodes: null,
|
||||||
tourKey: tourKey,
|
tourKey: tourKey || null,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,28 +0,0 @@
|
||||||
import { writable } from "svelte/store"
|
|
||||||
|
|
||||||
export const INITIAL_CONTEXT_MENU_STATE = {
|
|
||||||
id: null,
|
|
||||||
items: [],
|
|
||||||
position: { x: 0, y: 0 },
|
|
||||||
visible: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createViewsStore() {
|
|
||||||
const store = writable({ ...INITIAL_CONTEXT_MENU_STATE })
|
|
||||||
|
|
||||||
const open = (id, items, position) => {
|
|
||||||
store.set({ id, items, position, visible: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
store.set({ ...INITIAL_CONTEXT_MENU_STATE })
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe: store.subscribe,
|
|
||||||
open,
|
|
||||||
close,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const contextMenuStore = createViewsStore()
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
|
interface Position {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MenuItem {
|
||||||
|
label: string
|
||||||
|
icon?: string
|
||||||
|
action: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContextMenuState {
|
||||||
|
id: string | null
|
||||||
|
items: MenuItem[]
|
||||||
|
position: Position
|
||||||
|
visible: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INITIAL_CONTEXT_MENU_STATE: ContextMenuState = {
|
||||||
|
id: null,
|
||||||
|
items: [],
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
visible: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createViewsStore() {
|
||||||
|
const store = writable<ContextMenuState>({ ...INITIAL_CONTEXT_MENU_STATE })
|
||||||
|
|
||||||
|
const open = (id: string, items: MenuItem[], position: Position): void => {
|
||||||
|
store.set({ id, items, position, visible: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = (): void => {
|
||||||
|
store.set({ ...INITIAL_CONTEXT_MENU_STATE })
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const contextMenuStore = createViewsStore()
|
|
@ -1,11 +1,12 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable, type Writable } from "svelte/store"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
|
import { DeploymentProgressResponse } from "@budibase/types"
|
||||||
|
|
||||||
export const createDeploymentStore = () => {
|
export const createDeploymentStore = () => {
|
||||||
let store = writable([])
|
let store: Writable<DeploymentProgressResponse[]> = writable([])
|
||||||
|
|
||||||
const load = async () => {
|
const load = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
store.set(await API.getAppDeployments())
|
store.set(await API.getAppDeployments())
|
||||||
} catch (err) {
|
} catch (err) {
|
|
@ -65,7 +65,7 @@ describe("Builder store", () => {
|
||||||
ctx.test.builderStore.reset()
|
ctx.test.builderStore.reset()
|
||||||
expect(disconnected).toBe(true)
|
expect(disconnected).toBe(true)
|
||||||
expect(ctx.test.store).toStrictEqual(INITIAL_BUILDER_STATE)
|
expect(ctx.test.store).toStrictEqual(INITIAL_BUILDER_STATE)
|
||||||
expect(ctx.test.builderStore.websocket).toBeNull()
|
expect(ctx.test.builderStore.websocket).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Attempt to emit a resource select event to the websocket on select", ctx => {
|
it("Attempt to emit a resource select event to the websocket on select", ctx => {
|
||||||
|
|
|
@ -60,7 +60,10 @@ export type Store = BaseStore &
|
||||||
Table.Store &
|
Table.Store &
|
||||||
ViewV2.Store &
|
ViewV2.Store &
|
||||||
NonPlus.Store &
|
NonPlus.Store &
|
||||||
Datasource.Store & {
|
Datasource.Store &
|
||||||
|
Validation.Store &
|
||||||
|
Users.Store &
|
||||||
|
Menu.Store & {
|
||||||
// TODO while typing the rest of stores
|
// TODO while typing the rest of stores
|
||||||
fetch: Writable<any>
|
fetch: Writable<any>
|
||||||
filter: Writable<any>
|
filter: Writable<any>
|
||||||
|
@ -76,6 +79,13 @@ export type Store = BaseStore &
|
||||||
dispatch: (event: string, data: any) => any
|
dispatch: (event: string, data: any) => any
|
||||||
notifications: Writable<any>
|
notifications: Writable<any>
|
||||||
schemaOverrides: Writable<any>
|
schemaOverrides: Writable<any>
|
||||||
|
focusedCellId: Writable<any>
|
||||||
|
previousFocusedRowId: Writable<string>
|
||||||
|
gridID: string
|
||||||
|
selectedRows: Writable<any>
|
||||||
|
selectedRowCount: Writable<any>
|
||||||
|
selectedCellMap: Writable<any>
|
||||||
|
selectedCellCount: Writable<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const attachStores = (context: Store): Store => {
|
export const attachStores = (context: Store): Store => {
|
||||||
|
|
|
@ -1,8 +1,24 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get, Writable } from "svelte/store"
|
||||||
|
|
||||||
|
import { Store as StoreContext } from "."
|
||||||
import { parseCellID } from "../lib/utils"
|
import { parseCellID } from "../lib/utils"
|
||||||
|
|
||||||
|
interface MenuStoreData {
|
||||||
|
left: number
|
||||||
|
top: number
|
||||||
|
visible: boolean
|
||||||
|
multiRowMode: boolean
|
||||||
|
multiCellMode: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MenuStore {
|
||||||
|
menu: Writable<MenuStoreData>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Store = MenuStore
|
||||||
|
|
||||||
export const createStores = () => {
|
export const createStores = () => {
|
||||||
const menu = writable({
|
const menu = writable<MenuStoreData>({
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
visible: false,
|
visible: false,
|
||||||
|
@ -14,7 +30,7 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = (context: StoreContext) => {
|
||||||
const {
|
const {
|
||||||
menu,
|
menu,
|
||||||
focusedCellId,
|
focusedCellId,
|
||||||
|
@ -25,7 +41,7 @@ export const createActions = context => {
|
||||||
selectedCellCount,
|
selectedCellCount,
|
||||||
} = context
|
} = context
|
||||||
|
|
||||||
const open = (cellId, e) => {
|
const open = (cellId: string, e: MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
|
@ -37,7 +53,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute bounds of cell relative to outer data node
|
// Compute bounds of cell relative to outer data node
|
||||||
const targetBounds = e.target.getBoundingClientRect()
|
const targetBounds = (e.target as HTMLElement).getBoundingClientRect()
|
||||||
const dataBounds = dataNode.getBoundingClientRect()
|
const dataBounds = dataNode.getBoundingClientRect()
|
||||||
|
|
||||||
// Check if there are multiple rows selected, and if this is one of them
|
// Check if there are multiple rows selected, and if this is one of them
|
|
@ -1,11 +1,38 @@
|
||||||
import { writable, get, derived } from "svelte/store"
|
import { writable, get, derived, Writable, Readable } from "svelte/store"
|
||||||
import { helpers } from "@budibase/shared-core"
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
import { Store as StoreContext } from "."
|
||||||
|
import { UIUser } from "@budibase/types"
|
||||||
|
|
||||||
export const createStores = () => {
|
interface UIEnrichedUser extends UIUser {
|
||||||
const users = writable([])
|
color: string
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UsersStore {
|
||||||
|
users: Writable<UIUser[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DerivedUsersStore {
|
||||||
|
userCellMap: Readable<Record<string, UIUser>>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionUserStore {
|
||||||
|
users: UsersStore["users"] &
|
||||||
|
Readable<UIEnrichedUser[]> & {
|
||||||
|
actions: {
|
||||||
|
updateUser: (user: UIUser) => void
|
||||||
|
removeUser: (sessionId: string) => void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Store = DerivedUsersStore & ActionUserStore
|
||||||
|
|
||||||
|
export const createStores = (): UsersStore => {
|
||||||
|
const users = writable<UIUser[]>([])
|
||||||
|
|
||||||
const enrichedUsers = derived(users, $users => {
|
const enrichedUsers = derived(users, $users => {
|
||||||
return $users.map(user => ({
|
return $users.map<UIEnrichedUser>(user => ({
|
||||||
...user,
|
...user,
|
||||||
color: helpers.getUserColor(user),
|
color: helpers.getUserColor(user),
|
||||||
label: helpers.getUserLabel(user),
|
label: helpers.getUserLabel(user),
|
||||||
|
@ -20,7 +47,7 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = (context: StoreContext): DerivedUsersStore => {
|
||||||
const { users, focusedCellId } = context
|
const { users, focusedCellId } = context
|
||||||
|
|
||||||
// Generate a lookup map of cell ID to the user that has it selected, to make
|
// Generate a lookup map of cell ID to the user that has it selected, to make
|
||||||
|
@ -28,7 +55,7 @@ export const deriveStores = context => {
|
||||||
const userCellMap = derived(
|
const userCellMap = derived(
|
||||||
[users, focusedCellId],
|
[users, focusedCellId],
|
||||||
([$users, $focusedCellId]) => {
|
([$users, $focusedCellId]) => {
|
||||||
let map = {}
|
let map: Record<string, UIUser> = {}
|
||||||
$users.forEach(user => {
|
$users.forEach(user => {
|
||||||
const cellId = user.gridMetadata?.focusedCellId
|
const cellId = user.gridMetadata?.focusedCellId
|
||||||
if (cellId && cellId !== $focusedCellId) {
|
if (cellId && cellId !== $focusedCellId) {
|
||||||
|
@ -44,10 +71,10 @@ export const deriveStores = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = (context: StoreContext): ActionUserStore => {
|
||||||
const { users } = context
|
const { users } = context
|
||||||
|
|
||||||
const updateUser = user => {
|
const updateUser = (user: UIUser) => {
|
||||||
const $users = get(users)
|
const $users = get(users)
|
||||||
if (!$users.some(x => x.sessionId === user.sessionId)) {
|
if (!$users.some(x => x.sessionId === user.sessionId)) {
|
||||||
users.set([...$users, user])
|
users.set([...$users, user])
|
||||||
|
@ -60,7 +87,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeUser = sessionId => {
|
const removeUser = (sessionId: string) => {
|
||||||
users.update(state => {
|
users.update(state => {
|
||||||
return state.filter(x => x.sessionId !== sessionId)
|
return state.filter(x => x.sessionId !== sessionId)
|
||||||
})
|
})
|
|
@ -1,10 +1,21 @@
|
||||||
import { writable, get, derived } from "svelte/store"
|
import { writable, get, derived, Writable, Readable } from "svelte/store"
|
||||||
|
import { Store as StoreContext } from "."
|
||||||
import { parseCellID } from "../lib/utils"
|
import { parseCellID } from "../lib/utils"
|
||||||
|
|
||||||
|
interface ValidationStore {
|
||||||
|
validation: Writable<Record<string, string>>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DerivedValidationStore {
|
||||||
|
validationRowLookupMap: Readable<Record<string, string[]>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Store = ValidationStore & DerivedValidationStore
|
||||||
|
|
||||||
// Normally we would break out actions into the explicit "createActions"
|
// Normally we would break out actions into the explicit "createActions"
|
||||||
// function, but for validation all these actions are pure so can go into
|
// function, but for validation all these actions are pure so can go into
|
||||||
// "createStores" instead to make dependency ordering simpler
|
// "createStores" instead to make dependency ordering simpler
|
||||||
export const createStores = () => {
|
export const createStores = (): ValidationStore => {
|
||||||
const validation = writable({})
|
const validation = writable({})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -12,12 +23,12 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = (context: StoreContext): DerivedValidationStore => {
|
||||||
const { validation } = context
|
const { validation } = context
|
||||||
|
|
||||||
// Derive which rows have errors so that we can use that info later
|
// Derive which rows have errors so that we can use that info later
|
||||||
const validationRowLookupMap = derived(validation, $validation => {
|
const validationRowLookupMap = derived(validation, $validation => {
|
||||||
let map = {}
|
const map: Record<string, string[]> = {}
|
||||||
Object.entries($validation).forEach(([key, error]) => {
|
Object.entries($validation).forEach(([key, error]) => {
|
||||||
// Extract row ID from all errored cell IDs
|
// Extract row ID from all errored cell IDs
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -36,10 +47,10 @@ export const deriveStores = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = (context: StoreContext) => {
|
||||||
const { validation, focusedCellId, validationRowLookupMap } = context
|
const { validation, focusedCellId, validationRowLookupMap } = context
|
||||||
|
|
||||||
const setError = (cellId, error) => {
|
const setError = (cellId: string | undefined, error: string) => {
|
||||||
if (!cellId) {
|
if (!cellId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -49,11 +60,11 @@ export const createActions = context => {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const rowHasErrors = rowId => {
|
const rowHasErrors = (rowId: string) => {
|
||||||
return get(validationRowLookupMap)[rowId]?.length > 0
|
return get(validationRowLookupMap)[rowId]?.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const focusFirstRowError = rowId => {
|
const focusFirstRowError = (rowId: string) => {
|
||||||
const errorCells = get(validationRowLookupMap)[rowId]
|
const errorCells = get(validationRowLookupMap)[rowId]
|
||||||
const cellId = errorCells?.[0]
|
const cellId = errorCells?.[0]
|
||||||
if (cellId) {
|
if (cellId) {
|
||||||
|
@ -73,7 +84,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialise = context => {
|
export const initialise = (context: StoreContext) => {
|
||||||
const { validation, previousFocusedRowId, validationRowLookupMap } = context
|
const { validation, previousFocusedRowId, validationRowLookupMap } = context
|
||||||
|
|
||||||
// Remove validation errors when changing rows
|
// Remove validation errors when changing rows
|
|
@ -2,3 +2,4 @@ export * from "./columns"
|
||||||
export * from "./datasource"
|
export * from "./datasource"
|
||||||
export * from "./table"
|
export * from "./table"
|
||||||
export * from "./view"
|
export * from "./view"
|
||||||
|
export * from "./user"
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { User } from "@budibase/types"
|
||||||
|
|
||||||
|
export interface UIUser extends User {
|
||||||
|
sessionId: string
|
||||||
|
gridMetadata?: { focusedCellId?: string }
|
||||||
|
}
|
Loading…
Reference in New Issue