diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js
index 192ade9e5d..7d0b9d0547 100644
--- a/packages/builder/src/builderStore/store/frontend.js
+++ b/packages/builder/src/builderStore/store/frontend.js
@@ -41,6 +41,8 @@ const INITIAL_FRONTEND_STATE = {
spectrumThemes: false,
intelligentLoading: false,
deviceAwareness: false,
+ state: false,
+ customThemes: false,
},
currentFrontEndType: "none",
selectedScreenId: "",
@@ -53,6 +55,7 @@ const INITIAL_FRONTEND_STATE = {
routes: {},
clientLibPath: "",
theme: "",
+ customTheme: {},
}
export const getFrontendStore = () => {
@@ -77,6 +80,7 @@ export const getFrontendStore = () => {
layouts,
screens,
theme: application.theme || "spectrum--light",
+ customTheme: application.customTheme,
hasAppPackage: true,
appInstance: application.instance,
clientLibPath,
@@ -110,6 +114,22 @@ export const getFrontendStore = () => {
}
},
},
+ customTheme: {
+ save: async customTheme => {
+ const appId = get(store).appId
+ const response = await api.put(`/api/applications/${appId}`, {
+ customTheme,
+ })
+ if (response.status === 200) {
+ store.update(state => {
+ state.customTheme = customTheme
+ return state
+ })
+ } else {
+ throw new Error("Error updating theme")
+ }
+ },
+ },
routing: {
fetch: async () => {
const response = await api.get("/api/routing")
diff --git a/packages/builder/src/components/design/AppPreview/AppThemeSelect.svelte b/packages/builder/src/components/design/AppPreview/AppThemeSelect.svelte
index 21dae25708..09041dbf5a 100644
--- a/packages/builder/src/components/design/AppPreview/AppThemeSelect.svelte
+++ b/packages/builder/src/components/design/AppPreview/AppThemeSelect.svelte
@@ -33,6 +33,7 @@
diff --git a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte
index 38441d4323..85fb086b59 100644
--- a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte
+++ b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte
@@ -49,6 +49,7 @@
selectedComponentId,
previewType: $store.currentFrontEndType,
theme: $store.theme,
+ customTheme: $store.customTheme,
}
// Saving pages and screens to the DB causes them to have _revs.
diff --git a/packages/builder/src/components/design/AppPreview/ThemeEditor.svelte b/packages/builder/src/components/design/AppPreview/ThemeEditor.svelte
new file mode 100644
index 0000000000..6ea82e2713
--- /dev/null
+++ b/packages/builder/src/components/design/AppPreview/ThemeEditor.svelte
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/builder/src/components/design/AppPreview/iframeTemplate.js b/packages/builder/src/components/design/AppPreview/iframeTemplate.js
index 1e3ff7c856..85f008df08 100644
--- a/packages/builder/src/components/design/AppPreview/iframeTemplate.js
+++ b/packages/builder/src/components/design/AppPreview/iframeTemplate.js
@@ -66,7 +66,8 @@ export default `
screen,
previewType,
appId,
- theme
+ theme,
+ customTheme
} = parsed
// Set some flags so the app knows we're in the builder
@@ -78,6 +79,7 @@ export default `
window["##BUDIBASE_PREVIEW_ID##"] = Math.random()
window["##BUDIBASE_PREVIEW_TYPE##"] = previewType
window["##BUDIBASE_PREVIEW_THEME##"] = theme
+ window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"] = customTheme
// Initialise app
try {
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/EventEditor.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/EventEditor.svelte
index 4ff746a034..d0c561546f 100644
--- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/EventEditor.svelte
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/EventEditor.svelte
@@ -9,11 +9,12 @@
ActionMenu,
MenuItem,
} from "@budibase/bbui"
- import actionTypes from "./actions"
+ import { getAvailableActions } from "./actions"
import { generate } from "shortid"
const flipDurationMs = 150
const EVENT_TYPE_KEY = "##eventHandlerType"
+ const actionTypes = getAvailableActions()
export let actions
export let bindings = []
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js
index 6c285939ac..eaab22d89d 100644
--- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js
@@ -1,3 +1,6 @@
+import { store } from "builderStore"
+import { get } from "svelte/store"
+
import NavigateTo from "./NavigateTo.svelte"
import SaveRow from "./SaveRow.svelte"
import DeleteRow from "./DeleteRow.svelte"
@@ -17,49 +20,56 @@ import UpdateStateStep from "./UpdateState.svelte"
// be considered as camel case too.
// There is technical debt here to sanitize all these and standardise them
// across the packages but it's a breaking change to existing apps.
-export default [
- {
- name: "Save Row",
- component: SaveRow,
- },
- {
- name: "Delete Row",
- component: DeleteRow,
- },
- {
- name: "Navigate To",
- component: NavigateTo,
- },
- {
- name: "Execute Query",
- component: ExecuteQuery,
- },
- {
- name: "Trigger Automation",
- component: TriggerAutomation,
- },
- {
- name: "Validate Form",
- component: ValidateForm,
- },
- {
- name: "Log Out",
- component: LogOut,
- },
- {
- name: "Clear Form",
- component: ClearForm,
- },
- {
- name: "Close Screen Modal",
- component: CloseScreenModal,
- },
- {
- name: "Change Form Step",
- component: ChangeFormStep,
- },
- {
- name: "Update State",
- component: UpdateStateStep,
- },
-]
+export const getAvailableActions = () => {
+ let actions = [
+ {
+ name: "Save Row",
+ component: SaveRow,
+ },
+ {
+ name: "Delete Row",
+ component: DeleteRow,
+ },
+ {
+ name: "Navigate To",
+ component: NavigateTo,
+ },
+ {
+ name: "Execute Query",
+ component: ExecuteQuery,
+ },
+ {
+ name: "Trigger Automation",
+ component: TriggerAutomation,
+ },
+ {
+ name: "Validate Form",
+ component: ValidateForm,
+ },
+ {
+ name: "Log Out",
+ component: LogOut,
+ },
+ {
+ name: "Clear Form",
+ component: ClearForm,
+ },
+ {
+ name: "Close Screen Modal",
+ component: CloseScreenModal,
+ },
+ {
+ name: "Change Form Step",
+ component: ChangeFormStep,
+ },
+ ]
+
+ if (get(store).clientFeatures?.state) {
+ actions.push({
+ name: "Update State",
+ component: UpdateStateStep,
+ })
+ }
+
+ return actions
+}
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte
index 207daae84a..b5d1a8df11 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte
@@ -14,6 +14,7 @@
import { findComponent, findComponentPath } from "builderStore/storeUtils"
import { get } from "svelte/store"
import AppThemeSelect from "components/design/AppPreview/AppThemeSelect.svelte"
+ import ThemeEditor from "components/design/AppPreview/ThemeEditor.svelte"
// Cache previous values so we don't update the URL more than necessary
let previousType
@@ -153,6 +154,9 @@
{#if $store.clientFeatures.spectrumThemes}
{/if}
+ {#if $store.clientFeatures.customThemes}
+
+ {/if}
{#key $store.version}
@@ -202,9 +206,18 @@
padding: var(--spacing-xl) 40px;
}
.preview-header {
- display: grid;
- grid-template-columns: 1fr 100px;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: center;
}
+ .preview-header > :global(*) {
+ flex: 0 0 auto;
+ }
+ .preview-header > :global(*:first-child) {
+ flex: 1 1 auto;
+ }
+
.preview-content {
flex: 1 1 auto;
}
diff --git a/packages/client/manifest.json b/packages/client/manifest.json
index 194706744d..51ae1cf30d 100644
--- a/packages/client/manifest.json
+++ b/packages/client/manifest.json
@@ -2,7 +2,9 @@
"features": {
"spectrumThemes": true,
"intelligentLoading": true,
- "deviceAwareness": true
+ "deviceAwareness": true,
+ "state": true,
+ "customThemes": true
},
"layout": {
"name": "Layout",
diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte
index cdc39ac225..4c494f9104 100644
--- a/packages/client/src/components/ClientApp.svelte
+++ b/packages/client/src/components/ClientApp.svelte
@@ -11,7 +11,7 @@
authStore,
routeStore,
builderStore,
- appStore,
+ themeStore,
} from "stores"
import NotificationDisplay from "components/overlay/NotificationDisplay.svelte"
import ConfirmationDisplay from "components/overlay/ConfirmationDisplay.svelte"
@@ -63,9 +63,6 @@
}
}
}
-
- $: themeClass =
- $builderStore.theme || $appStore.application?.theme || "spectrum--light"
{#if dataLoaded}
@@ -73,7 +70,7 @@
id="spectrum-root"
lang="en"
dir="ltr"
- class="spectrum spectrum--medium {themeClass}"
+ class="spectrum spectrum--medium {$themeStore.theme}"
>
{#if permissionError}
@@ -87,7 +84,11 @@
-
+
{#key $screenStore.activeLayout._id}
{/key}
@@ -132,6 +133,20 @@
border: 1px solid var(--spectrum-global-color-gray-300);
}
+ #app-root {
+ /* Primary */
+ --spectrum-global-color-blue-600: var(--primaryColor);
+ --spectrum-global-color-blue-700: var(--primaryColor);
+ --spectrum-global-color-static-blue-600: var(--primaryColor);
+ --spectrum-global-color-static-blue-700: var(--primaryColor);
+
+ /* Primary hover */
+ --spectrum-global-color-blue-400: var(--primaryColorHover);
+ --spectrum-global-color-blue-500: var(--primaryColorHover);
+ --spectrum-global-color-static-blue-400: var(--primaryColorHover);
+ --spectrum-global-color-static-blue-500: var(--primaryColorHover);
+ }
+
/* Custom scrollbars */
:global(::-webkit-scrollbar) {
width: 8px;
diff --git a/packages/client/src/index.js b/packages/client/src/index.js
index ceae36c9f3..2d1c9fd1c0 100644
--- a/packages/client/src/index.js
+++ b/packages/client/src/index.js
@@ -17,6 +17,7 @@ const loadBudibase = () => {
previewId: window["##BUDIBASE_PREVIEW_ID##"],
previewType: window["##BUDIBASE_PREVIEW_TYPE##"],
theme: window["##BUDIBASE_PREVIEW_THEME##"],
+ customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"],
})
// Set app ID - this window flag is set by both the preview and the real
diff --git a/packages/client/src/stores/index.js b/packages/client/src/stores/index.js
index 6c99e3f453..02c848120c 100644
--- a/packages/client/src/stores/index.js
+++ b/packages/client/src/stores/index.js
@@ -8,6 +8,7 @@ export { dataSourceStore } from "./dataSource"
export { confirmationStore } from "./confirmation"
export { peekStore } from "./peek"
export { stateStore } from "./state"
+export { themeStore } from "./theme"
// Context stores are layered and duplicated, so it is not a singleton
export { createContextStore } from "./context"
diff --git a/packages/client/src/stores/theme.js b/packages/client/src/stores/theme.js
new file mode 100644
index 0000000000..821c41ccc7
--- /dev/null
+++ b/packages/client/src/stores/theme.js
@@ -0,0 +1,30 @@
+import { derived } from "svelte/store"
+import { appStore } from "./app"
+import { builderStore } from "./builder"
+
+const createThemeStore = () => {
+ const store = derived(
+ [builderStore, appStore],
+ ([$builderStore, $appStore]) => {
+ const theme =
+ $builderStore.theme || $appStore.application?.theme || "spectrum--light"
+ const customTheme =
+ $builderStore.customTheme || $appStore.application?.customTheme || {}
+ let customThemeCss = ""
+ Object.entries(customTheme).forEach(([key, value]) => {
+ customThemeCss += `--${key}:${value};`
+ })
+ return {
+ theme,
+ customTheme,
+ customThemeCss,
+ }
+ }
+ )
+
+ return {
+ subscribe: store.subscribe,
+ }
+}
+
+export const themeStore = createThemeStore()