Merge branch 'master' into type-portal-flags-store
This commit is contained in:
commit
997a4ef624
|
@ -2,19 +2,24 @@ import { get } from "svelte/store"
|
||||||
import { BudiStore } from "../BudiStore"
|
import { BudiStore } from "../BudiStore"
|
||||||
import { previewStore } from "@/stores/builder"
|
import { previewStore } from "@/stores/builder"
|
||||||
|
|
||||||
|
interface BuilderHoverStore {
|
||||||
|
hoverTimeout?: NodeJS.Timeout
|
||||||
|
componentId: string | null
|
||||||
|
}
|
||||||
|
|
||||||
export const INITIAL_HOVER_STATE = {
|
export const INITIAL_HOVER_STATE = {
|
||||||
componentId: null,
|
componentId: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HoverStore extends BudiStore {
|
export class HoverStore extends BudiStore<BuilderHoverStore> {
|
||||||
hoverTimeout
|
hoverTimeout?: NodeJS.Timeout
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({ ...INITIAL_HOVER_STATE })
|
super({ ...INITIAL_HOVER_STATE })
|
||||||
this.hover = this.hover.bind(this)
|
this.hover = this.hover.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
hover(componentId, notifyClient = true) {
|
hover(componentId: string, notifyClient = true) {
|
||||||
clearTimeout(this.hoverTimeout)
|
clearTimeout(this.hoverTimeout)
|
||||||
if (componentId) {
|
if (componentId) {
|
||||||
this.processHover(componentId, notifyClient)
|
this.processHover(componentId, notifyClient)
|
||||||
|
@ -25,7 +30,7 @@ export class HoverStore extends BudiStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processHover(componentId, notifyClient) {
|
processHover(componentId: string, notifyClient?: boolean) {
|
||||||
if (componentId === get(this.store).componentId) {
|
if (componentId === get(this.store).componentId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -2,27 +2,22 @@ import { get } from "svelte/store"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { appStore } from "@/stores/builder"
|
import { appStore } from "@/stores/builder"
|
||||||
import { BudiStore } from "../BudiStore"
|
import { BudiStore } from "../BudiStore"
|
||||||
|
import { AppNavigation, AppNavigationLink, UIObject } from "@budibase/types"
|
||||||
|
|
||||||
|
interface BuilderNavigationStore extends AppNavigation {}
|
||||||
|
|
||||||
export const INITIAL_NAVIGATION_STATE = {
|
export const INITIAL_NAVIGATION_STATE = {
|
||||||
navigation: "Top",
|
navigation: "Top",
|
||||||
links: [],
|
links: [],
|
||||||
title: null,
|
|
||||||
sticky: null,
|
|
||||||
hideLogo: null,
|
|
||||||
logoUrl: null,
|
|
||||||
hideTitle: null,
|
|
||||||
textAlign: "Left",
|
textAlign: "Left",
|
||||||
navBackground: null,
|
|
||||||
navWidth: null,
|
|
||||||
navTextColor: null,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NavigationStore extends BudiStore {
|
export class NavigationStore extends BudiStore<BuilderNavigationStore> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(INITIAL_NAVIGATION_STATE)
|
super(INITIAL_NAVIGATION_STATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
syncAppNavigation(nav) {
|
syncAppNavigation(nav: AppNavigation) {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
...nav,
|
...nav,
|
||||||
|
@ -33,15 +28,17 @@ export class NavigationStore extends BudiStore {
|
||||||
this.store.set({ ...INITIAL_NAVIGATION_STATE })
|
this.store.set({ ...INITIAL_NAVIGATION_STATE })
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(navigation) {
|
async save(navigation: AppNavigation) {
|
||||||
const appId = get(appStore).appId
|
const appId = get(appStore).appId
|
||||||
const app = await API.saveAppMetadata(appId, { navigation })
|
const app = await API.saveAppMetadata(appId, { navigation })
|
||||||
this.syncAppNavigation(app.navigation)
|
if (app.navigation) {
|
||||||
|
this.syncAppNavigation(app.navigation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveLink(url, title, roleId) {
|
async saveLink(url: string, title: string, roleId: string) {
|
||||||
const navigation = get(this.store)
|
const navigation = get(this.store)
|
||||||
let links = [...(navigation?.links ?? [])]
|
let links: AppNavigationLink[] = [...(navigation?.links ?? [])]
|
||||||
|
|
||||||
// Skip if we have an identical link
|
// Skip if we have an identical link
|
||||||
if (links.find(link => link.url === url && link.text === title)) {
|
if (links.find(link => link.url === url && link.text === title)) {
|
||||||
|
@ -60,7 +57,7 @@ export class NavigationStore extends BudiStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteLink(urls) {
|
async deleteLink(urls: string[] | string) {
|
||||||
const navigation = get(this.store)
|
const navigation = get(this.store)
|
||||||
let links = navigation?.links
|
let links = navigation?.links
|
||||||
if (!links?.length) {
|
if (!links?.length) {
|
||||||
|
@ -86,7 +83,7 @@ export class NavigationStore extends BudiStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
syncMetadata(metadata) {
|
syncMetadata(metadata: UIObject) {
|
||||||
const { navigation } = metadata
|
const { navigation } = metadata
|
||||||
this.syncAppNavigation(navigation)
|
this.syncAppNavigation(navigation)
|
||||||
}
|
}
|
|
@ -6,18 +6,29 @@ import { automationStore } from "./automations"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { getSequentialName } from "@/helpers/duplicate"
|
import { getSequentialName } from "@/helpers/duplicate"
|
||||||
|
|
||||||
const initialState = {}
|
interface RowAction {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
tableId: string
|
||||||
|
allowedSources?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
export class RowActionStore extends BudiStore {
|
interface RowActionState {
|
||||||
|
[tableId: string]: RowAction[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: RowActionState = {}
|
||||||
|
|
||||||
|
export class RowActionStore extends BudiStore<RowActionState> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(initialState)
|
super(initialState)
|
||||||
}
|
}
|
||||||
|
|
||||||
reset = () => {
|
reset = () => {
|
||||||
this.store.set(initialState)
|
this.set(initialState)
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshRowActions = async sourceId => {
|
refreshRowActions = async (sourceId: string) => {
|
||||||
if (!sourceId) {
|
if (!sourceId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -34,26 +45,30 @@ export class RowActionStore extends BudiStore {
|
||||||
|
|
||||||
// Fetch row actions for this table
|
// Fetch row actions for this table
|
||||||
const res = await API.rowActions.fetch(tableId)
|
const res = await API.rowActions.fetch(tableId)
|
||||||
const actions = Object.values(res || {})
|
const actions = Object.values(res || {}) as RowAction[]
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
[tableId]: actions,
|
[tableId]: actions,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
createRowAction = async (tableId, viewId, name) => {
|
createRowAction = async (tableId: string, viewId?: string, name?: string) => {
|
||||||
if (!tableId) {
|
if (!tableId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a unique name for this action
|
// Get a unique name for this action
|
||||||
if (!name) {
|
if (!name) {
|
||||||
const existingRowActions = get(this.store)[tableId] || []
|
const existingRowActions = get(this)[tableId] || []
|
||||||
name = getSequentialName(existingRowActions, "New row action ", {
|
name = getSequentialName(existingRowActions, "New row action ", {
|
||||||
getName: x => x.name,
|
getName: x => x.name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
throw new Error("Failed to generate a unique name for the row action")
|
||||||
|
}
|
||||||
|
|
||||||
// Create the action
|
// Create the action
|
||||||
const res = await API.rowActions.create(tableId, name)
|
const res = await API.rowActions.create(tableId, name)
|
||||||
|
|
||||||
|
@ -73,41 +88,35 @@ export class RowActionStore extends BudiStore {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
enableView = async (tableId, rowActionId, viewId) => {
|
enableView = async (tableId: string, rowActionId: string, viewId: string) => {
|
||||||
await API.rowActions.enableView(tableId, rowActionId, viewId)
|
await API.rowActions.enableView(tableId, rowActionId, viewId)
|
||||||
await this.refreshRowActions(tableId)
|
await this.refreshRowActions(tableId)
|
||||||
}
|
}
|
||||||
|
|
||||||
disableView = async (tableId, rowActionId, viewId) => {
|
disableView = async (
|
||||||
|
tableId: string,
|
||||||
|
rowActionId: string,
|
||||||
|
viewId: string
|
||||||
|
) => {
|
||||||
await API.rowActions.disableView(tableId, rowActionId, viewId)
|
await API.rowActions.disableView(tableId, rowActionId, viewId)
|
||||||
await this.refreshRowActions(tableId)
|
await this.refreshRowActions(tableId)
|
||||||
}
|
}
|
||||||
|
|
||||||
rename = async (tableId, rowActionId, name) => {
|
delete = async (tableId: string, rowActionId: string) => {
|
||||||
await API.rowActions.update({
|
|
||||||
tableId,
|
|
||||||
rowActionId,
|
|
||||||
name,
|
|
||||||
})
|
|
||||||
await this.refreshRowActions(tableId)
|
|
||||||
automationStore.actions.fetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
delete = async (tableId, rowActionId) => {
|
|
||||||
await API.rowActions.delete(tableId, rowActionId)
|
await API.rowActions.delete(tableId, rowActionId)
|
||||||
await this.refreshRowActions(tableId)
|
await this.refreshRowActions(tableId)
|
||||||
// We don't need to refresh automations as we can only delete row actions
|
// We don't need to refresh automations as we can only delete row actions
|
||||||
// from the automations store, so we already handle the state update there
|
// from the automations store, so we already handle the state update there
|
||||||
}
|
}
|
||||||
|
|
||||||
trigger = async (sourceId, rowActionId, rowId) => {
|
trigger = async (sourceId: string, rowActionId: string, rowId: string) => {
|
||||||
await API.rowActions.trigger(sourceId, rowActionId, rowId)
|
await API.rowActions.trigger(sourceId, rowActionId, rowId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = new RowActionStore()
|
const store = new RowActionStore()
|
||||||
const derivedStore = derived(store, $store => {
|
const derivedStore = derived<RowActionStore, RowActionState>(store, $store => {
|
||||||
let map = {}
|
const map: RowActionState = {}
|
||||||
|
|
||||||
// Generate an entry for every view as well
|
// Generate an entry for every view as well
|
||||||
Object.keys($store || {}).forEach(tableId => {
|
Object.keys($store || {}).forEach(tableId => {
|
||||||
|
@ -115,7 +124,7 @@ const derivedStore = derived(store, $store => {
|
||||||
map[tableId] = $store[tableId]
|
map[tableId] = $store[tableId]
|
||||||
for (let action of $store[tableId]) {
|
for (let action of $store[tableId]) {
|
||||||
const otherSources = (action.allowedSources || []).filter(
|
const otherSources = (action.allowedSources || []).filter(
|
||||||
sourceId => sourceId !== tableId
|
(sourceId: string) => sourceId !== tableId
|
||||||
)
|
)
|
||||||
for (let source of otherSources) {
|
for (let source of otherSources) {
|
||||||
map[source] ??= []
|
map[source] ??= []
|
|
@ -1,35 +0,0 @@
|
||||||
import { writable, get } from "svelte/store"
|
|
||||||
import { API } from "@/api"
|
|
||||||
import { appStore } from "./app"
|
|
||||||
|
|
||||||
const createsnippets = () => {
|
|
||||||
const store = writable([])
|
|
||||||
|
|
||||||
const syncMetadata = metadata => {
|
|
||||||
store.set(metadata?.snippets || [])
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveSnippet = async updatedSnippet => {
|
|
||||||
const snippets = [
|
|
||||||
...get(store).filter(snippet => snippet.name !== updatedSnippet.name),
|
|
||||||
updatedSnippet,
|
|
||||||
]
|
|
||||||
const app = await API.saveAppMetadata(get(appStore).appId, { snippets })
|
|
||||||
syncMetadata(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteSnippet = async snippetName => {
|
|
||||||
const snippets = get(store).filter(snippet => snippet.name !== snippetName)
|
|
||||||
const app = await API.saveAppMetadata(get(appStore).appId, { snippets })
|
|
||||||
syncMetadata(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...store,
|
|
||||||
syncMetadata,
|
|
||||||
saveSnippet,
|
|
||||||
deleteSnippet,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const snippets = createsnippets()
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
import { API } from "@/api"
|
||||||
|
import { appStore } from "./app"
|
||||||
|
import { BudiStore } from "../BudiStore"
|
||||||
|
import { Snippet, UpdateAppResponse } from "@budibase/types"
|
||||||
|
|
||||||
|
export class SnippetStore extends BudiStore<Snippet[]> {
|
||||||
|
constructor() {
|
||||||
|
super([])
|
||||||
|
}
|
||||||
|
|
||||||
|
syncMetadata = (metadata: UpdateAppResponse) => {
|
||||||
|
this.set(metadata?.snippets || [])
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSnippet = async (updatedSnippet: Snippet) => {
|
||||||
|
const snippets = [
|
||||||
|
...get(this).filter(snippet => snippet.name !== updatedSnippet.name),
|
||||||
|
updatedSnippet,
|
||||||
|
]
|
||||||
|
const app = await API.saveAppMetadata(get(appStore).appId, { snippets })
|
||||||
|
this.syncMetadata(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSnippet = async (snippetName: string) => {
|
||||||
|
const snippets = get(this).filter(snippet => snippet.name !== snippetName)
|
||||||
|
const app = await API.saveAppMetadata(get(appStore).appId, { snippets })
|
||||||
|
this.syncMetadata(app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const snippets = new SnippetStore()
|
|
@ -1,58 +0,0 @@
|
||||||
import { writable, get } from "svelte/store"
|
|
||||||
import { API } from "@/api"
|
|
||||||
import { ensureValidTheme, DefaultAppTheme } from "@budibase/shared-core"
|
|
||||||
|
|
||||||
export const createThemeStore = () => {
|
|
||||||
const store = writable({
|
|
||||||
theme: DefaultAppTheme,
|
|
||||||
customTheme: {},
|
|
||||||
})
|
|
||||||
|
|
||||||
const syncAppTheme = app => {
|
|
||||||
store.update(state => {
|
|
||||||
const theme = ensureValidTheme(app.theme, DefaultAppTheme)
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
theme,
|
|
||||||
customTheme: app.customTheme,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = async (theme, appId) => {
|
|
||||||
const app = await API.saveAppMetadata(appId, { theme })
|
|
||||||
store.update(state => {
|
|
||||||
state.theme = app.theme
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveCustom = async (theme, appId) => {
|
|
||||||
const updated = { ...get(store).customTheme, ...theme }
|
|
||||||
const app = await API.saveAppMetadata(appId, { customTheme: updated })
|
|
||||||
store.update(state => {
|
|
||||||
state.customTheme = app.customTheme
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const syncMetadata = metadata => {
|
|
||||||
const { theme, customTheme } = metadata
|
|
||||||
store.update(state => ({
|
|
||||||
...state,
|
|
||||||
theme: ensureValidTheme(theme, DefaultAppTheme),
|
|
||||||
customTheme,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe: store.subscribe,
|
|
||||||
update: store.update,
|
|
||||||
syncMetadata,
|
|
||||||
syncAppTheme,
|
|
||||||
save,
|
|
||||||
saveCustom,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const themeStore = createThemeStore()
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
import { API } from "@/api"
|
||||||
|
import { BudiStore } from "../BudiStore"
|
||||||
|
import { ensureValidTheme, DefaultAppTheme } from "@budibase/shared-core"
|
||||||
|
import { App, UpdateAppResponse, Theme, AppCustomTheme } from "@budibase/types"
|
||||||
|
|
||||||
|
interface ThemeState {
|
||||||
|
theme: Theme
|
||||||
|
customTheme: AppCustomTheme
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ThemeStore extends BudiStore<ThemeState> {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
theme: DefaultAppTheme,
|
||||||
|
customTheme: {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
syncAppTheme = (app: App) => {
|
||||||
|
this.update(state => {
|
||||||
|
const theme = ensureValidTheme(app.theme, DefaultAppTheme)
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
theme,
|
||||||
|
customTheme: app.customTheme || {},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
save = async (theme: Theme, appId: string) => {
|
||||||
|
const app = await API.saveAppMetadata(appId, { theme })
|
||||||
|
this.update(state => ({
|
||||||
|
...state,
|
||||||
|
theme: ensureValidTheme(app.theme, DefaultAppTheme),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
saveCustom = async (theme: Partial<AppCustomTheme>, appId: string) => {
|
||||||
|
const updated = { ...get(this).customTheme, ...theme }
|
||||||
|
const app = await API.saveAppMetadata(appId, { customTheme: updated })
|
||||||
|
this.update(state => ({
|
||||||
|
...state,
|
||||||
|
customTheme: app.customTheme || {},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
syncMetadata = (metadata: UpdateAppResponse) => {
|
||||||
|
const { theme, customTheme } = metadata
|
||||||
|
this.update(state => ({
|
||||||
|
...state,
|
||||||
|
theme: ensureValidTheme(theme, DefaultAppTheme),
|
||||||
|
customTheme: customTheme || {},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const themeStore = new ThemeStore()
|
|
@ -1,4 +1,4 @@
|
||||||
import { User, Document, Plugin, Snippet } from "../"
|
import { User, Document, Plugin, Snippet, Theme } from "../"
|
||||||
import { SocketSession } from "../../sdk"
|
import { SocketSession } from "../../sdk"
|
||||||
|
|
||||||
export type AppMetadataErrors = { [key: string]: string[] }
|
export type AppMetadataErrors = { [key: string]: string[] }
|
||||||
|
@ -14,7 +14,7 @@ export interface App extends Document {
|
||||||
instance: AppInstance
|
instance: AppInstance
|
||||||
tenantId: string
|
tenantId: string
|
||||||
status: string
|
status: string
|
||||||
theme?: string
|
theme?: Theme
|
||||||
customTheme?: AppCustomTheme
|
customTheme?: AppCustomTheme
|
||||||
revertableVersion?: string
|
revertableVersion?: string
|
||||||
lockedBy?: User
|
lockedBy?: User
|
||||||
|
@ -37,8 +37,8 @@ export interface AppInstance {
|
||||||
|
|
||||||
export interface AppNavigation {
|
export interface AppNavigation {
|
||||||
navigation: string
|
navigation: string
|
||||||
title: string
|
title?: string
|
||||||
navWidth: string
|
navWidth?: string
|
||||||
sticky?: boolean
|
sticky?: boolean
|
||||||
hideLogo?: boolean
|
hideLogo?: boolean
|
||||||
logoUrl?: string
|
logoUrl?: string
|
||||||
|
@ -46,6 +46,7 @@ export interface AppNavigation {
|
||||||
navBackground?: string
|
navBackground?: string
|
||||||
navTextColor?: string
|
navTextColor?: string
|
||||||
links?: AppNavigationLink[]
|
links?: AppNavigationLink[]
|
||||||
|
textAlign?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppNavigationLink {
|
export interface AppNavigationLink {
|
||||||
|
@ -53,6 +54,8 @@ export interface AppNavigationLink {
|
||||||
url: string
|
url: string
|
||||||
id?: string
|
id?: string
|
||||||
roleId?: string
|
roleId?: string
|
||||||
|
type?: string
|
||||||
|
subLinks?: AppNavigationLink[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppCustomTheme {
|
export interface AppCustomTheme {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export * from "./integration"
|
export * from "./integration"
|
||||||
|
export * from "./misc"
|
||||||
export * from "./automations"
|
export * from "./automations"
|
||||||
export * from "./grid"
|
export * from "./grid"
|
||||||
export * from "./preview"
|
export * from "./preview"
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
// type purely to capture structures that the type is unknown, but maybe known later
|
||||||
|
export type UIObject = Record<string, any>
|
Loading…
Reference in New Issue