Merge branch 'master' of github.com:Budibase/budibase into feature/settings-modal
This commit is contained in:
commit
bacc525596
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
context('Screen Tests', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('localhost:4001/_builder')
|
||||||
|
cy.createApp('Conor Cy App', 'Model App Description')
|
||||||
|
cy.navigateToFrontend()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should successful create a screen', () => {
|
||||||
|
cy.createScreen("test Screen")
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should rename a screen', () => {
|
||||||
|
cy.get(".components-pane").within(() => {
|
||||||
|
cy.contains("Settings").click()
|
||||||
|
cy.get("input[name=_instanceName]").clear().type("About Us").blur()
|
||||||
|
})
|
||||||
|
cy.get('.nav-items-container').within(() => {
|
||||||
|
cy.contains("About Us").should('exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -103,3 +103,23 @@ Cypress.Commands.add("addButtonComponent", () => {
|
||||||
|
|
||||||
cy.get("[data-cy=Button]").click()
|
cy.get("[data-cy=Button]").click()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add("navigateToFrontend", () => {
|
||||||
|
cy.get(".close", { timeout: 10000 }).click()
|
||||||
|
cy.contains("frontend").click()
|
||||||
|
cy.get(".close", { timeout: 10000 }).click()
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add("createScreen", (screenName, route) => {
|
||||||
|
cy.get(".newscreen").click()
|
||||||
|
cy.get(".uk-input:first").type(screenName)
|
||||||
|
if (route) {
|
||||||
|
cy.get(".uk-input:last").type(route)
|
||||||
|
}
|
||||||
|
cy.get(".uk-modal-footer").within(() => {
|
||||||
|
cy.contains("Create Screen").click()
|
||||||
|
})
|
||||||
|
cy.get(".nav-items-container").within(() => {
|
||||||
|
cy.contains(screenName).should("exist")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -50,8 +50,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@beyonk/svelte-notifications": "^2.0.3",
|
"@budibase/bbui": "^1.13.0",
|
||||||
"@budibase/bbui": "^1.12.0",
|
|
||||||
"@budibase/client": "^0.0.32",
|
"@budibase/client": "^0.0.32",
|
||||||
"@nx-js/compiler-util": "^2.0.0",
|
"@nx-js/compiler-util": "^2.0.0",
|
||||||
"codemirror": "^5.51.0",
|
"codemirror": "^5.51.0",
|
||||||
|
|
|
@ -153,6 +153,10 @@ export default {
|
||||||
find: "builderStore",
|
find: "builderStore",
|
||||||
replacement: path.resolve(projectRootDir, "src/builderStore"),
|
replacement: path.resolve(projectRootDir, "src/builderStore"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
find: "constants",
|
||||||
|
replacement: path.resolve(projectRootDir, "src/constants"),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
customResolver,
|
customResolver,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -4,30 +4,10 @@
|
||||||
import { Router, basepath } from "@sveltech/routify"
|
import { Router, basepath } from "@sveltech/routify"
|
||||||
import { routes } from "../routify/routes"
|
import { routes } from "../routify/routes"
|
||||||
import { store, initialise } from "builderStore"
|
import { store, initialise } from "builderStore"
|
||||||
import AppNotification, {
|
import NotificationDisplay from "components/common/Notification/NotificationDisplay.svelte"
|
||||||
showAppNotification,
|
|
||||||
} from "components/common/AppNotification.svelte"
|
|
||||||
import { NotificationDisplay } from "@beyonk/svelte-notifications"
|
|
||||||
|
|
||||||
function showErrorBanner() {
|
|
||||||
showAppNotification({
|
|
||||||
status: "danger",
|
|
||||||
message:
|
|
||||||
"Whoops! Looks like we're having trouble. Please refresh the page.",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
window.addEventListener("error", showErrorBanner)
|
|
||||||
window.addEventListener("unhandledrejection", showErrorBanner)
|
|
||||||
})
|
|
||||||
|
|
||||||
$basepath = "/_builder"
|
$basepath = "/_builder"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AppNotification />
|
|
||||||
|
|
||||||
<!-- svelte-notifications -->
|
|
||||||
<NotificationDisplay />
|
<NotificationDisplay />
|
||||||
|
|
||||||
<Router {routes} />
|
<Router {routes} />
|
||||||
|
|
|
@ -57,13 +57,14 @@
|
||||||
.budibase__nav-item {
|
.budibase__nav-item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0 4px 0 2px;
|
padding: 0 4px 0 2px;
|
||||||
height: 35px;
|
height: 36px;
|
||||||
margin: 5px 0px 4px 0px;
|
margin: 0px 0px 0px 0px;
|
||||||
border-radius: 0 5px 5px 0;
|
border-radius: 5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
|
border-top: var(--grey-1) .5px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.budibase__nav-item.selected {
|
.budibase__nav-item.selected {
|
||||||
|
@ -72,18 +73,22 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.budibase__nav-item:hover {
|
.budibase__nav-item:hover {
|
||||||
background: var(--grey-light);
|
background: var(--grey-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.budibase__input {
|
.budibase__input {
|
||||||
height: 35px;
|
height: 36px;
|
||||||
width: 220px;
|
background-color: var(--grey-2);
|
||||||
border-radius: 3px;
|
border: none;
|
||||||
border: 1px solid var(--grey-dark);
|
border-radius: 5px;
|
||||||
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding-left: 12px;
|
padding-left: 8px;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uk-text-right {
|
.uk-text-right {
|
||||||
|
@ -100,7 +105,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.budibase__table {
|
.budibase__table {
|
||||||
border: 1px solid var(--grey-dark);
|
border: 1px solid var(--grey-4);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
@ -116,12 +121,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.budibase__table tr {
|
.budibase__table tr {
|
||||||
border-bottom: 1px solid var(--grey-light);
|
border-bottom: 1px solid var(--grey-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--toggled {
|
.button--toggled {
|
||||||
background: var(--blue-light);
|
background: var(--blue-light);
|
||||||
color: var(--ink-light);
|
color: var(--grey-7);
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
import { uuid } from "builderStore/uuid"
|
||||||
import api from "../api"
|
import api from "../api"
|
||||||
import { getContext } from "svelte"
|
|
||||||
|
|
||||||
/** TODO: DEMO SOLUTION
|
|
||||||
* this section should not be here, it is a quick fix for a demo
|
|
||||||
* when we reorg the backend UI, this should disappear
|
|
||||||
* **/
|
|
||||||
import { CreateEditModelModal } from "components/database/ModelDataTable/modals"
|
|
||||||
/** DEMO SOLUTION END **/
|
|
||||||
|
|
||||||
export const getBackendUiStore = () => {
|
export const getBackendUiStore = () => {
|
||||||
const INITIAL_BACKEND_UI_STATE = {
|
const INITIAL_BACKEND_UI_STATE = {
|
||||||
breadcrumbs: [],
|
|
||||||
models: [],
|
models: [],
|
||||||
views: [],
|
views: [],
|
||||||
users: [],
|
users: [],
|
||||||
selectedDatabase: {},
|
selectedDatabase: {},
|
||||||
selectedModel: {},
|
selectedModel: {},
|
||||||
|
draftModel: {},
|
||||||
|
tabs: {
|
||||||
|
SETUP_PANEL: "SETUP",
|
||||||
|
NAVIGATION_PANEL: "NAVIGATE",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = writable(INITIAL_BACKEND_UI_STATE)
|
const store = writable(INITIAL_BACKEND_UI_STATE)
|
||||||
|
@ -31,26 +29,12 @@ export const getBackendUiStore = () => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedDatabase = db
|
state.selectedDatabase = db
|
||||||
if (models && models.length > 0) {
|
if (models && models.length > 0) {
|
||||||
state.selectedModel = models[0]
|
store.actions.models.select(models[0])
|
||||||
state.selectedView = `all_${models[0]._id}`
|
|
||||||
}
|
}
|
||||||
state.breadcrumbs = [db.name]
|
|
||||||
state.models = models
|
state.models = models
|
||||||
state.views = views
|
state.views = views
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
/** TODO: DEMO SOLUTION**/
|
|
||||||
if (!models || models.length === 0) {
|
|
||||||
const { open, close } = getContext("simple-modal")
|
|
||||||
open(
|
|
||||||
CreateEditModelModal,
|
|
||||||
{
|
|
||||||
onClosed: close,
|
|
||||||
},
|
|
||||||
{ styleContent: { padding: "0" } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/** DEMO SOLUTION END **/
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
records: {
|
records: {
|
||||||
|
@ -59,11 +43,6 @@ export const getBackendUiStore = () => {
|
||||||
state.selectedView = state.selectedView
|
state.selectedView = state.selectedView
|
||||||
return state
|
return state
|
||||||
}),
|
}),
|
||||||
view: record =>
|
|
||||||
store.update(state => {
|
|
||||||
state.breadcrumbs = [state.selectedDatabase.name, record._id]
|
|
||||||
return state
|
|
||||||
}),
|
|
||||||
select: record =>
|
select: record =>
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedRecord = record
|
state.selectedRecord = record
|
||||||
|
@ -71,14 +50,48 @@ export const getBackendUiStore = () => {
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
models: {
|
models: {
|
||||||
create: model =>
|
fetch: async () => {
|
||||||
|
const modelsResponse = await api.get(`/api/models`)
|
||||||
|
const models = await modelsResponse.json()
|
||||||
|
store.update(state => {
|
||||||
|
state.models = models
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
|
select: model =>
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.models.push(model)
|
|
||||||
state.models = state.models
|
|
||||||
state.selectedModel = model
|
state.selectedModel = model
|
||||||
|
state.draftModel = cloneDeep(model)
|
||||||
|
state.selectedField = ""
|
||||||
state.selectedView = `all_${model._id}`
|
state.selectedView = `all_${model._id}`
|
||||||
|
state.tabs.SETUP_PANEL = "SETUP"
|
||||||
return state
|
return state
|
||||||
}),
|
}),
|
||||||
|
save: async ({ model }) => {
|
||||||
|
const updatedModel = cloneDeep(model)
|
||||||
|
|
||||||
|
const SAVE_MODEL_URL = `/api/models`
|
||||||
|
await api.post(SAVE_MODEL_URL, updatedModel)
|
||||||
|
await store.actions.models.fetch()
|
||||||
|
},
|
||||||
|
addField: field => {
|
||||||
|
store.update(state => {
|
||||||
|
if (!state.draftModel.schema) {
|
||||||
|
state.draftModel.schema = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = uuid()
|
||||||
|
|
||||||
|
state.draftModel.schema = {
|
||||||
|
...state.draftModel.schema,
|
||||||
|
[id]: field,
|
||||||
|
}
|
||||||
|
state.selectedField = id
|
||||||
|
state.tabs.NAVIGATION_PANEL = "NAVIGATE"
|
||||||
|
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
views: {
|
views: {
|
||||||
select: view =>
|
select: view =>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { values } from "lodash/fp"
|
import { values } from "lodash/fp"
|
||||||
|
import { get_capitalised_name } from "../../helpers"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
import * as backendStoreActions from "./backend"
|
import * as backendStoreActions from "./backend"
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import api from "../api"
|
import api from "../api"
|
||||||
|
@ -22,7 +24,6 @@ import {
|
||||||
saveCurrentPreviewItem as _saveCurrentPreviewItem,
|
saveCurrentPreviewItem as _saveCurrentPreviewItem,
|
||||||
saveScreenApi as _saveScreenApi,
|
saveScreenApi as _saveScreenApi,
|
||||||
regenerateCssForCurrentScreen,
|
regenerateCssForCurrentScreen,
|
||||||
renameCurrentScreen,
|
|
||||||
} from "../storeUtils"
|
} from "../storeUtils"
|
||||||
|
|
||||||
export const getStore = () => {
|
export const getStore = () => {
|
||||||
|
@ -68,6 +69,7 @@ export const getStore = () => {
|
||||||
store.getPathToComponent = getPathToComponent(store)
|
store.getPathToComponent = getPathToComponent(store)
|
||||||
store.addTemplatedComponent = addTemplatedComponent(store)
|
store.addTemplatedComponent = addTemplatedComponent(store)
|
||||||
store.setMetadataProp = setMetadataProp(store)
|
store.setMetadataProp = setMetadataProp(store)
|
||||||
|
store.editPageOrScreen = editPageOrScreen(store)
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,14 +154,13 @@ const createScreen = store => (screenName, route, layoutComponentName) => {
|
||||||
const rootComponent = state.components[layoutComponentName]
|
const rootComponent = state.components[layoutComponentName]
|
||||||
|
|
||||||
const newScreen = {
|
const newScreen = {
|
||||||
name: screenName || "",
|
|
||||||
description: "",
|
description: "",
|
||||||
url: "",
|
url: "",
|
||||||
_css: "",
|
_css: "",
|
||||||
props: createProps(rootComponent).props,
|
props: createProps(rootComponent).props,
|
||||||
}
|
}
|
||||||
|
|
||||||
newScreen.route = route
|
newScreen.route = route
|
||||||
|
newScreen.props._instanceName = screenName || ""
|
||||||
state.currentPreviewItem = newScreen
|
state.currentPreviewItem = newScreen
|
||||||
state.currentComponentInfo = newScreen.props
|
state.currentComponentInfo = newScreen.props
|
||||||
state.currentFrontEndType = "screen"
|
state.currentFrontEndType = "screen"
|
||||||
|
@ -172,7 +173,7 @@ const createScreen = store => (screenName, route, layoutComponentName) => {
|
||||||
|
|
||||||
const setCurrentScreen = store => screenName => {
|
const setCurrentScreen = store => screenName => {
|
||||||
store.update(s => {
|
store.update(s => {
|
||||||
const screen = getExactComponent(s.screens, screenName)
|
const screen = getExactComponent(s.screens, screenName, true)
|
||||||
s.currentPreviewItem = screen
|
s.currentPreviewItem = screen
|
||||||
s.currentFrontEndType = "screen"
|
s.currentFrontEndType = "screen"
|
||||||
s.currentView = "detail"
|
s.currentView = "detail"
|
||||||
|
@ -243,6 +244,7 @@ const setCurrentPage = store => pageName => {
|
||||||
const currentPage = state.pages[pageName]
|
const currentPage = state.pages[pageName]
|
||||||
|
|
||||||
state.currentFrontEndType = "page"
|
state.currentFrontEndType = "page"
|
||||||
|
state.currentView = "detail"
|
||||||
state.currentPageName = pageName
|
state.currentPageName = pageName
|
||||||
state.screens = Array.isArray(current_screens)
|
state.screens = Array.isArray(current_screens)
|
||||||
? current_screens
|
? current_screens
|
||||||
|
@ -294,10 +296,15 @@ const addChildComponent = store => (componentToAdd, presetName) => {
|
||||||
|
|
||||||
const presetProps = presetName ? component.presets[presetName] : {}
|
const presetProps = presetName ? component.presets[presetName] : {}
|
||||||
|
|
||||||
|
const instanceId = get(backendUiStore).selectedDatabase._id
|
||||||
|
const instanceName = get_capitalised_name(componentToAdd)
|
||||||
|
|
||||||
const newComponent = createProps(
|
const newComponent = createProps(
|
||||||
component,
|
component,
|
||||||
{
|
{
|
||||||
...presetProps,
|
...presetProps,
|
||||||
|
_instanceId: instanceId,
|
||||||
|
_instanceName: instanceName,
|
||||||
},
|
},
|
||||||
state
|
state
|
||||||
)
|
)
|
||||||
|
@ -346,24 +353,23 @@ const selectComponent = store => component => {
|
||||||
|
|
||||||
const setComponentProp = store => (name, value) => {
|
const setComponentProp = store => (name, value) => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const current_component = state.currentComponentInfo
|
let current_component = state.currentComponentInfo
|
||||||
state.currentComponentInfo[name] = value
|
current_component[name] = value
|
||||||
|
|
||||||
_saveCurrentPreviewItem(state)
|
|
||||||
|
|
||||||
state.currentComponentInfo = current_component
|
state.currentComponentInfo = current_component
|
||||||
|
_saveCurrentPreviewItem(state)
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const setPageOrScreenProp = store => (name, value) => {
|
const setPageOrScreenProp = store => (name, value) => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
if (name === "name" && state.currentFrontEndType === "screen") {
|
if (name === "_instanceName" && state.currentFrontEndType === "screen") {
|
||||||
state = renameCurrentScreen(value, state)
|
state.currentPreviewItem.props[name] = value
|
||||||
} else {
|
} else {
|
||||||
state.currentPreviewItem[name] = value
|
state.currentPreviewItem[name] = value
|
||||||
_saveCurrentPreviewItem(state)
|
|
||||||
}
|
}
|
||||||
|
_saveCurrentPreviewItem(state)
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -413,6 +419,18 @@ const setScreenType = store => type => {
|
||||||
|
|
||||||
state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
|
state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
|
||||||
state.currentPreviewItem = pageOrScreen
|
state.currentPreviewItem = pageOrScreen
|
||||||
|
state.currentView = "detail"
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const editPageOrScreen = store => (key, value, setOnComponent = false) => {
|
||||||
|
store.update(state => {
|
||||||
|
setOnComponent
|
||||||
|
? (state.currentPreviewItem.props[key] = value)
|
||||||
|
: (state.currentPreviewItem[key] = value)
|
||||||
|
_saveCurrentPreviewItem(state)
|
||||||
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
import { generate } from "shortid"
|
||||||
|
|
||||||
|
export const notificationStore = writable({
|
||||||
|
notifications: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
export function send(message, type = "default") {
|
||||||
|
notificationStore.update(state => {
|
||||||
|
state.notifications = [
|
||||||
|
...state.notifications,
|
||||||
|
{ id: generate(), type, message },
|
||||||
|
]
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const notifier = {
|
||||||
|
danger: msg => send(msg, "danger"),
|
||||||
|
warning: msg => send(msg, "warning"),
|
||||||
|
info: msg => send(msg, "info"),
|
||||||
|
success: msg => send(msg, "success"),
|
||||||
|
}
|
|
@ -46,8 +46,9 @@ export const saveScreenApi = (screen, s) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const renameCurrentScreen = (newname, state) => {
|
export const renameCurrentScreen = (newname, state) => {
|
||||||
const oldname = state.currentPreviewItem.name
|
const oldname = state.currentPreviewItem.props._instanceName
|
||||||
state.currentPreviewItem.name = newname
|
state.currentPreviewItem.props._instanceName = newname
|
||||||
|
|
||||||
api.patch(
|
api.patch(
|
||||||
`/_builder/api/${state.appId}/pages/${state.currentPageName}/screen`,
|
`/_builder/api/${state.appId}/pages/${state.currentPageName}/screen`,
|
||||||
{
|
{
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
|
|
||||||
.secondary {
|
.secondary {
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
border: solid 1px var(--grey-dark);
|
border: solid 1px var(--grey-4);
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
<script context="module">
|
|
||||||
import UIKit from "uikit"
|
|
||||||
|
|
||||||
export function showAppNotification({ message, status }) {
|
|
||||||
UIKit.notification({
|
|
||||||
message: `
|
|
||||||
<div class="message-container">
|
|
||||||
<div class="information-icon">🤯</div>
|
|
||||||
<span class="notification-message">
|
|
||||||
${message}
|
|
||||||
</span>
|
|
||||||
<button class="hoverable refresh-page-button" onclick="window.location.reload()">Refresh Page</button>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
status,
|
|
||||||
timeout: 100000,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
:global(.information-icon) {
|
|
||||||
font-size: 24px;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.uk-nofi) {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 40px 1fr auto;
|
|
||||||
grid-gap: 5px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.message-container) {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.uk-notification) {
|
|
||||||
width: 50% !important;
|
|
||||||
left: 0 !important;
|
|
||||||
right: 0 !important;
|
|
||||||
margin-right: auto !important;
|
|
||||||
margin-left: auto !important;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.uk-notification-message) {
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.uk-notification-message:hover .uk-notification-close) {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.uk-notification-message-danger) {
|
|
||||||
background: var(--ink-light) !important;
|
|
||||||
color: #fff !important;
|
|
||||||
font-family: Roboto;
|
|
||||||
font-size: 16px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.refresh-page-button) {
|
|
||||||
font-size: 14px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: none;
|
|
||||||
padding: 8px 16px;
|
|
||||||
color: var(--ink);
|
|
||||||
background: #ffffff;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.refresh-page-button):hover {
|
|
||||||
background: var(--grey-light);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
<script>
|
||||||
|
export let title
|
||||||
|
export let icon
|
||||||
|
|
||||||
|
export let primary
|
||||||
|
export let secondary
|
||||||
|
export let tertiary
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div on:click class:primary class:secondary class:tertiary>
|
||||||
|
<i class={icon} />
|
||||||
|
<span>{title}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: var(--ink);
|
||||||
|
font-weight: 400;
|
||||||
|
padding: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: 0.3s transform;
|
||||||
|
background: var(--grey-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--grey-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary {
|
||||||
|
background: var(--ink);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
background: var(--blue-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tertiary {
|
||||||
|
background: var(--white);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -42,14 +42,14 @@
|
||||||
|
|
||||||
/* ---- PRIMARY ----*/
|
/* ---- PRIMARY ----*/
|
||||||
.primary {
|
.primary {
|
||||||
background-color: var(--primary100);
|
background-color: var(--blue);
|
||||||
border-color: var(--primary100);
|
border-color: var(--blue);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.primary:hover {
|
.primary:hover {
|
||||||
background-color: var(--primary75);
|
background-color: var(--blue);
|
||||||
border-color: var(--primary75);
|
border-color: var(--blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
.primary:active {
|
.primary:active {
|
||||||
|
@ -59,8 +59,8 @@
|
||||||
|
|
||||||
.primary-outline {
|
.primary-outline {
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
border-color: var(--primary100);
|
border-color: var(--blue);
|
||||||
color: var(--primary100);
|
color: var(--blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
.primary-outline:hover {
|
.primary-outline:hover {
|
||||||
|
@ -74,8 +74,8 @@
|
||||||
/* ---- secondary ----*/
|
/* ---- secondary ----*/
|
||||||
|
|
||||||
.secondary {
|
.secondary {
|
||||||
background-color: var(--secondary100);
|
background-color: var(--ink);
|
||||||
border-color: var(--secondary100);
|
border-color: var(--ink);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,8 +91,8 @@
|
||||||
|
|
||||||
.secondary-outline {
|
.secondary-outline {
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
border-color: var(--secondary100);
|
border-color: var(--ink);
|
||||||
color: var(--secondary100);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
.secondary-outline:hover {
|
.secondary-outline:hover {
|
||||||
|
@ -136,32 +136,36 @@
|
||||||
|
|
||||||
/* ---- deletion ----*/
|
/* ---- deletion ----*/
|
||||||
.deletion {
|
.deletion {
|
||||||
background-color: var(--deletion100);
|
background-color: var(--red);
|
||||||
border-color: var(--deletion100);
|
border-color: var(--red);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.deletion:hover {
|
.deletion:hover {
|
||||||
background-color: var(--deletion75);
|
background-color: var(--red-light);
|
||||||
border-color: var(--deletion75);
|
border-color: var(--red);
|
||||||
|
color: var(--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
.deletion:pressed {
|
.deletion:pressed {
|
||||||
background-color: var(--deletiondark);
|
background-color: var(--red-dark);
|
||||||
border-color: var(--deletiondark);
|
border-color: var(--red-dark);
|
||||||
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.deletion-outline {
|
.deletion-outline {
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
border-color: var(--deletion100);
|
border-color: var(--red);
|
||||||
color: var(--deletion100);
|
color: var(--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
.deletion-outline:hover {
|
.deletion-outline:hover {
|
||||||
background-color: var(--deletion10);
|
background-color: var(--red-light);
|
||||||
|
color: var(--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
.deletion-outline:pressed {
|
.deletion-outline:pressed {
|
||||||
background-color: var(--deletion25);
|
background-color: var(--red-dark);
|
||||||
|
color: var(--white);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
export let label = ""
|
export let label = ""
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<input class="uk-checkbox" type="checkbox" bind:checked on:change />
|
|
||||||
{label}
|
{label}
|
||||||
|
<input class="uk-checkbox" type="checkbox" bind:checked on:change />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
input {
|
input {
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
background: var(--secondary80);
|
background: var(--grey-7);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
font-family: "Courier New", Courier, monospace;
|
font-family: "Courier New", Courier, monospace;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.uk-modal-footer {
|
.uk-modal-footer {
|
||||||
background: var(--lightslate);
|
background: var(--grey-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.uk-modal-dialog {
|
.uk-modal-dialog {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
export let size = 18
|
export let size = 18
|
||||||
export let icon = ""
|
export let icon = ""
|
||||||
export let style = ""
|
export let style = ""
|
||||||
export let color = "var(--secondary100)"
|
export let color = "var(--ink)"
|
||||||
export let hoverColor = "var(--secondary75)"
|
export let hoverColor = "var(--secondary75)"
|
||||||
export let attributes = {}
|
export let attributes = {}
|
||||||
|
|
||||||
|
|
|
@ -37,19 +37,18 @@
|
||||||
<style>
|
<style>
|
||||||
input {
|
input {
|
||||||
/* width: 32px; */
|
/* width: 32px; */
|
||||||
height: 32px;
|
height: 36px;
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
font-weight: 700;
|
font-weight: 400;
|
||||||
margin: 0px 0px 0px 1px;
|
margin: 0px 0px 0px 2px;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
opacity: 0.7;
|
padding: 0px 8px;
|
||||||
padding: 0px 4px;
|
font-family: inter;
|
||||||
line-height: 1.3;
|
|
||||||
/* padding: 12px; */
|
|
||||||
width: 164px;
|
width: 164px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1px solid var(--grey);
|
background-color: var(--grey-2);
|
||||||
border-radius: 2px;
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--grey-2);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import api from "builderStore/api"
|
||||||
|
|
||||||
|
export let modelId
|
||||||
|
export let linkName
|
||||||
|
export let linked = []
|
||||||
|
|
||||||
|
let records = []
|
||||||
|
let model = {}
|
||||||
|
|
||||||
|
let linkedRecords = new Set(linked)
|
||||||
|
|
||||||
|
$: linked = [...linkedRecords]
|
||||||
|
$: FIELDS_TO_HIDE = [$backendUiStore.selectedModel._id]
|
||||||
|
$: schema = $backendUiStore.selectedModel.schema
|
||||||
|
|
||||||
|
async function fetchRecords() {
|
||||||
|
const FETCH_RECORDS_URL = `/api/${modelId}/records`
|
||||||
|
const response = await api.get(FETCH_RECORDS_URL)
|
||||||
|
const modelResponse = await api.get(`/api/models/${modelId}`)
|
||||||
|
|
||||||
|
model = await modelResponse.json()
|
||||||
|
records = await response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkRecord(id) {
|
||||||
|
if (linkedRecords.has(id)) {
|
||||||
|
linkedRecords.delete(id)
|
||||||
|
} else {
|
||||||
|
linkedRecords.add(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
linkedRecords = linkedRecords
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
fetchRecords()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<header>
|
||||||
|
<h3>{linkName}</h3>
|
||||||
|
</header>
|
||||||
|
{#each records as record}
|
||||||
|
<div class="linked-record" on:click={() => linkRecord(record._id)}>
|
||||||
|
<div class="fields" class:selected={linkedRecords.has(record._id)}>
|
||||||
|
{#each Object.keys(model.schema).filter(key => !FIELDS_TO_HIDE.includes(key)) as key}
|
||||||
|
<div class="field">
|
||||||
|
<span>{model.schema[key].name}</span>
|
||||||
|
<p>{record[key]}</p>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fields.selected {
|
||||||
|
background: var(--light-grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fields {
|
||||||
|
padding: 15px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
grid-gap: 20px;
|
||||||
|
background: var(--white);
|
||||||
|
border: 1px solid var(--grey);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: 0.5s all;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fields:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field span {
|
||||||
|
color: var(--ink-lighter);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field p {
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: 14px;
|
||||||
|
word-break: break-word;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,65 @@
|
||||||
|
<script>
|
||||||
|
import { notificationStore } from "builderStore/store/notifications"
|
||||||
|
import { onMount, onDestroy } from "svelte"
|
||||||
|
import { fade } from "svelte/transition"
|
||||||
|
|
||||||
|
export let themes = {
|
||||||
|
danger: "#E26D69",
|
||||||
|
success: "#84C991",
|
||||||
|
warning: "#f0ad4e",
|
||||||
|
info: "#5bc0de",
|
||||||
|
default: "#aaaaaa",
|
||||||
|
}
|
||||||
|
|
||||||
|
export let timeout = 3000
|
||||||
|
|
||||||
|
$: if ($notificationStore.notifications.length) {
|
||||||
|
setTimeout(() => {
|
||||||
|
notificationStore.update(state => {
|
||||||
|
state.notifications.shift()
|
||||||
|
state.notifications = state.notifications
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}, timeout)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ul class="notifications">
|
||||||
|
{#each $notificationStore.notifications as notification (notification.id)}
|
||||||
|
<li
|
||||||
|
class="toast"
|
||||||
|
style="background: {themes[notification.type]};"
|
||||||
|
transition:fade>
|
||||||
|
<div class="content">{notification.message}</div>
|
||||||
|
{#if notification.icon}
|
||||||
|
<i class={notification.icon} />
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.notifications {
|
||||||
|
width: 40vw;
|
||||||
|
list-style: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 10px;
|
||||||
|
display: block;
|
||||||
|
color: var(--white);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -13,13 +13,25 @@
|
||||||
let numberText = value === null || value === undefined ? "" : value.toString()
|
let numberText = value === null || value === undefined ? "" : value.toString()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="uk-margin">
|
<div class="numberbox">
|
||||||
<label class="uk-form-label">{label}</label>
|
<label>{label}</label>
|
||||||
<div class="uk-form-controls">
|
<input
|
||||||
<input
|
class="budibase__input"
|
||||||
class="budibase__input"
|
type="number"
|
||||||
type="number"
|
{value}
|
||||||
{value}
|
on:change={inputChanged} />
|
||||||
on:change={inputChanged} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.numberbox {
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
color: var(--secondary100);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
.select-container {
|
.select-container {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
position: relative;
|
position: relative;
|
||||||
border: var(--grey-dark) 1px solid;
|
border: var(--grey-4) 1px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.adjusted {
|
.adjusted {
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 20px 20px;
|
padding: 20px 20px;
|
||||||
border-left: solid 1px var(--grey);
|
border-left: solid 1px var(--grey-2);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,10 +60,11 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
color: var(--ink-lighter);
|
color: var(--grey-5);
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
background: none;
|
background: none;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switcher > .selected {
|
.switcher > .selected {
|
||||||
|
|
|
@ -15,16 +15,34 @@
|
||||||
$: valuesText = join("\n")(values)
|
$: valuesText = join("\n")(values)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="uk-margin">
|
<div class="margin">
|
||||||
<label class="uk-form-label">{label}</label>
|
<label class="label">{label}</label>
|
||||||
<div class="uk-form-controls">
|
<div class="uk-form-controls">
|
||||||
<textarea value={valuesText} on:change={inputChanged} />
|
<textarea value={valuesText} on:change={inputChanged} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.margin {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
textarea {
|
textarea {
|
||||||
width: 300px;
|
font-size: 14px;
|
||||||
height: 100px;
|
height: 200px;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: none;
|
||||||
|
cursor: text;
|
||||||
|
background: var(--grey-2);
|
||||||
|
padding: 12px;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { fade } from "svelte/transition"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import api from "builderStore/api"
|
||||||
|
|
||||||
|
export let ids = []
|
||||||
|
export let field
|
||||||
|
|
||||||
|
let records = []
|
||||||
|
let open = false
|
||||||
|
let model
|
||||||
|
|
||||||
|
$: FIELDS_TO_HIDE = [$backendUiStore.selectedModel._id, field.modelId]
|
||||||
|
|
||||||
|
async function fetchRecords() {
|
||||||
|
const response = await api.post("/api/records/search", {
|
||||||
|
keys: ids,
|
||||||
|
})
|
||||||
|
const modelResponse = await api.get(`/api/models/${field.modelId}`)
|
||||||
|
records = await response.json()
|
||||||
|
model = await modelResponse.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
$: ids && fetchRecords()
|
||||||
|
|
||||||
|
function toggleOpen() {
|
||||||
|
open = !open
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
fetchRecords()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<a on:click={toggleOpen}>{records.length}</a>
|
||||||
|
{#if open}
|
||||||
|
<div class="popover" transition:fade>
|
||||||
|
<header>
|
||||||
|
<h3>{field.name}</h3>
|
||||||
|
<i class="ri-close-circle-fill" on:click={toggleOpen} />
|
||||||
|
</header>
|
||||||
|
{#each records as record}
|
||||||
|
<div class="linked-record">
|
||||||
|
<div class="fields">
|
||||||
|
{#each Object.keys(model.schema).filter(key => !FIELDS_TO_HIDE.includes(key)) as key}
|
||||||
|
<div class="field">
|
||||||
|
<span>{model.schema[key].name}</span>
|
||||||
|
<p>{record[key]}</p>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section {
|
||||||
|
display: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 24px;
|
||||||
|
color: var(--ink-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
i:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popover {
|
||||||
|
width: 500px;
|
||||||
|
position: absolute;
|
||||||
|
right: 15%;
|
||||||
|
padding: 20px;
|
||||||
|
background: var(--light-grey);
|
||||||
|
border: 1px solid var(--grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fields {
|
||||||
|
padding: 15px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
grid-gap: 20px;
|
||||||
|
background: var(--white);
|
||||||
|
border: 1px solid var(--grey);
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field span {
|
||||||
|
color: var(--ink-lighter);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field p {
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: 14px;
|
||||||
|
word-break: break-word;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,20 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, getContext } from "svelte"
|
import { onMount, getContext } from "svelte"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
import {
|
import { Button } from "@budibase/bbui"
|
||||||
tap,
|
|
||||||
get,
|
|
||||||
find,
|
|
||||||
last,
|
|
||||||
compose,
|
|
||||||
flatten,
|
|
||||||
map,
|
|
||||||
remove,
|
|
||||||
keys,
|
|
||||||
takeRight,
|
|
||||||
} from "lodash/fp"
|
|
||||||
import Select from "components/common/Select.svelte"
|
import Select from "components/common/Select.svelte"
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
|
import LinkedRecord from "./LinkedRecord.svelte"
|
||||||
import TablePagination from "./TablePagination.svelte"
|
import TablePagination from "./TablePagination.svelte"
|
||||||
import { DeleteRecordModal, CreateEditRecordModal } from "./modals"
|
import { DeleteRecordModal, CreateEditRecordModal } from "./modals"
|
||||||
import * as api from "./api"
|
import * as api from "./api"
|
||||||
|
@ -52,25 +42,39 @@
|
||||||
let headers = []
|
let headers = []
|
||||||
let views = []
|
let views = []
|
||||||
let currentPage = 0
|
let currentPage = 0
|
||||||
|
let search
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if ($backendUiStore.selectedView) {
|
if ($backendUiStore.selectedView) {
|
||||||
api
|
api.fetchDataForView($backendUiStore.selectedView).then(records => {
|
||||||
.fetchDataForView($backendUiStore.selectedView)
|
data = records || []
|
||||||
.then(records => {
|
})
|
||||||
data = records || []
|
|
||||||
headers = Object.keys($backendUiStore.selectedModel.schema).filter(
|
|
||||||
key => !INTERNAL_HEADERS.includes(key)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: paginatedData = data.slice(
|
$: paginatedData = data
|
||||||
currentPage * ITEMS_PER_PAGE,
|
? data.slice(
|
||||||
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
|
currentPage * ITEMS_PER_PAGE,
|
||||||
|
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
|
||||||
|
$: headers = Object.keys($backendUiStore.selectedModel.schema).filter(
|
||||||
|
id => !INTERNAL_HEADERS.includes(id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
$: schema = $backendUiStore.selectedModel.schema
|
||||||
|
|
||||||
|
const createNewRecord = () => {
|
||||||
|
open(
|
||||||
|
CreateEditRecordModal,
|
||||||
|
{
|
||||||
|
onClosed: close,
|
||||||
|
},
|
||||||
|
{ styleContent: { padding: "0" } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (views.length) {
|
if (views.length) {
|
||||||
backendUiStore.actions.views.select(views[0])
|
backendUiStore.actions.views.select(views[0])
|
||||||
|
@ -81,13 +85,16 @@
|
||||||
<section>
|
<section>
|
||||||
<div class="table-controls">
|
<div class="table-controls">
|
||||||
<h2 class="title">{$backendUiStore.selectedModel.name}</h2>
|
<h2 class="title">{$backendUiStore.selectedModel.name}</h2>
|
||||||
|
<Button primary on:click={createNewRecord}>
|
||||||
|
<span class="button-inner">Create New Record</span>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<table class="uk-table">
|
<table class="uk-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Edit</th>
|
<th>Edit</th>
|
||||||
{#each headers as header}
|
{#each headers as header}
|
||||||
<th>{header}</th>
|
<th>{$backendUiStore.selectedModel.schema[header].name}</th>
|
||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -121,7 +128,11 @@
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
{#each headers as header}
|
{#each headers as header}
|
||||||
<td>{row[header]}</td>
|
<td>
|
||||||
|
{#if schema[header].type === 'link'}
|
||||||
|
<LinkedRecord field={schema[header]} ids={row[header]} />
|
||||||
|
{:else}{row[header]}{/if}
|
||||||
|
</td>
|
||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -143,7 +154,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
border: 1px solid var(--grey-dark);
|
border: 1px solid var(--grey-4);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
@ -151,7 +162,7 @@
|
||||||
|
|
||||||
thead {
|
thead {
|
||||||
background: var(--blue-light);
|
background: var(--blue-light);
|
||||||
border: 1px solid var(--grey-dark);
|
border: 1px solid var(--grey-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
thead th {
|
thead th {
|
||||||
|
@ -160,18 +171,17 @@
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody tr {
|
tbody tr {
|
||||||
border-bottom: 1px solid var(--grey-dark);
|
border-bottom: 1px solid var(--grey-4);
|
||||||
transition: 0.3s background-color;
|
transition: 0.3s background-color;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody tr:hover {
|
tbody tr:hover {
|
||||||
background: var(--grey-light);
|
background: var(--grey-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-controls {
|
.table-controls {
|
||||||
|
@ -189,4 +199,9 @@
|
||||||
.no-data {
|
.no-data {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -58,20 +58,19 @@
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid var(--grey-4);
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-family: Roboto;
|
|
||||||
min-width: 20px;
|
min-width: 20px;
|
||||||
transition: 0.3s background-color;
|
transition: 0.3s background-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination__buttons button:hover {
|
.pagination__buttons button:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #fafafa;
|
background-color: var(--grey-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
color: var(--button-text);
|
color: var(--blue);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -15,7 +15,7 @@ export async function createDatabase(appname, instanceName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteRecord(record) {
|
export async function deleteRecord(record) {
|
||||||
const DELETE_RECORDS_URL = `/api/${record._modelId}/records/${record._id}/${record._rev}`
|
const DELETE_RECORDS_URL = `/api/${record.modelId}/records/${record._id}/${record._rev}`
|
||||||
const response = await api.delete(DELETE_RECORDS_URL)
|
const response = await api.delete(DELETE_RECORDS_URL)
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
}
|
}
|
||||||
footer {
|
footer {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background: #fafafa;
|
background: var(--grey-1);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -130,7 +130,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody > tr:hover {
|
tbody > tr:hover {
|
||||||
background-color: var(--grey-light);
|
background-color: var(--grey-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-controls {
|
.table-controls {
|
||||||
|
@ -156,7 +156,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
background-color: var(--grey-light);
|
background-color: var(--grey-1);
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
padding: 20px 40px 20px 40px;
|
padding: 20px 40px 20px 40px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
<script>
|
|
||||||
import Dropdown from "components/common/Dropdown.svelte"
|
|
||||||
import Textbox from "components/common/Textbox.svelte"
|
|
||||||
import Button from "components/common/Button.svelte"
|
|
||||||
import ButtonGroup from "components/common/ButtonGroup.svelte"
|
|
||||||
import NumberBox from "components/common/NumberBox.svelte"
|
|
||||||
import ValuesList from "components/common/ValuesList.svelte"
|
|
||||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
|
||||||
import Checkbox from "components/common/Checkbox.svelte"
|
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
|
||||||
import DatePicker from "components/common/DatePicker.svelte"
|
|
||||||
import { keys, cloneDeep } from "lodash/fp"
|
|
||||||
|
|
||||||
const FIELD_TYPES = ["string", "number", "boolean"]
|
|
||||||
|
|
||||||
export let field = {
|
|
||||||
type: "string",
|
|
||||||
constraints: { type: "string", presence: false },
|
|
||||||
}
|
|
||||||
export let schema
|
|
||||||
export let goBack
|
|
||||||
|
|
||||||
let errors = []
|
|
||||||
let draftField = cloneDeep(field)
|
|
||||||
|
|
||||||
let type = field.type
|
|
||||||
let constraints = field.constraints
|
|
||||||
let required =
|
|
||||||
field.constraints.presence && !field.constraints.presence.allowEmpty
|
|
||||||
|
|
||||||
const save = () => {
|
|
||||||
constraints.presence = required ? { allowEmpty: false } : false
|
|
||||||
draftField.constraints = constraints
|
|
||||||
draftField.type = type
|
|
||||||
schema[field.name] = draftField
|
|
||||||
goBack()
|
|
||||||
}
|
|
||||||
|
|
||||||
$: constraints =
|
|
||||||
type === "string"
|
|
||||||
? { type: "string", length: {}, presence: false }
|
|
||||||
: type === "number"
|
|
||||||
? { type: "number", presence: false, numericality: {} }
|
|
||||||
: type === "boolean"
|
|
||||||
? { type: "boolean", presence: false }
|
|
||||||
: type === "datetime"
|
|
||||||
? { type: "date", datetime: {}, presence: false }
|
|
||||||
: type.startsWith("array")
|
|
||||||
? { type: "array", presence: false }
|
|
||||||
: { type: "string", presence: false }
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
|
|
||||||
<ErrorsBox {errors} />
|
|
||||||
|
|
||||||
<form on:submit|preventDefault class="uk-form-stacked">
|
|
||||||
<Textbox label="Name" bind:text={field.name} />
|
|
||||||
<Dropdown label="Type" bind:selected={type} options={FIELD_TYPES} />
|
|
||||||
|
|
||||||
<Checkbox label="Required" bind:checked={required} />
|
|
||||||
|
|
||||||
{#if type === 'string'}
|
|
||||||
<NumberBox label="Max Length" bind:value={constraints.length.maximum} />
|
|
||||||
<ValuesList label="Categories" bind:values={constraints.inclusion} />
|
|
||||||
{:else if type === 'datetime'}
|
|
||||||
<DatePicker
|
|
||||||
label="Min Value"
|
|
||||||
bind:value={constraints.datetime.earliest} />
|
|
||||||
<DatePicker label="Max Value" bind:value={constraints.datetime.latest} />
|
|
||||||
{:else if type === 'number'}
|
|
||||||
<NumberBox
|
|
||||||
label="Min Value"
|
|
||||||
bind:value={constraints.numericality.greaterThanOrEqualTo} />
|
|
||||||
<NumberBox
|
|
||||||
label="Max Value"
|
|
||||||
bind:value={constraints.numericality.lessThanOrEqualTo} />
|
|
||||||
{/if}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<div class="button">
|
|
||||||
<ActionButton secondary on:click={goBack}>Cancel</ActionButton>
|
|
||||||
</div>
|
|
||||||
<ActionButton primary on:click={save}>Save</ActionButton>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
margin: 40px;
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
padding: 20px 40px;
|
|
||||||
border-radius: 0 0 5px 5px;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
background: var(--grey-light);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,8 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import { compose, map, get, flatten } from "lodash/fp"
|
import { compose, map, get, flatten } from "lodash/fp"
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import { Button } from "@budibase/bbui"
|
||||||
|
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
||||||
import Select from "components/common/Select.svelte"
|
import Select from "components/common/Select.svelte"
|
||||||
import RecordFieldControl from "./RecordFieldControl.svelte"
|
import RecordFieldControl from "./RecordFieldControl.svelte"
|
||||||
import * as api from "../api"
|
import * as api from "../api"
|
||||||
|
@ -59,38 +61,96 @@
|
||||||
backendUiStore.update(state => {
|
backendUiStore.update(state => {
|
||||||
state.selectedView = state.selectedView
|
state.selectedView = state.selectedView
|
||||||
onClosed()
|
onClosed()
|
||||||
|
notifier.success("Record created successfully.")
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<h4 class="budibase__title--4">Create / Edit Record</h4>
|
<header>
|
||||||
|
<i class="ri-file-user-fill" />
|
||||||
|
<h4 class="budibase__title--4">Create / Edit Record</h4>
|
||||||
|
</header>
|
||||||
<ErrorsBox {errors} />
|
<ErrorsBox {errors} />
|
||||||
<form on:submit|preventDefault class="uk-form-stacked">
|
<form on:submit|preventDefault class="uk-form-stacked">
|
||||||
{#each modelSchema as [key, meta]}
|
{#each modelSchema as [key, meta]}
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<RecordFieldControl
|
{#if meta.type === 'link'}
|
||||||
type={determineInputType(meta)}
|
<LinkedRecordSelector
|
||||||
options={determineOptions(meta)}
|
bind:linked={record[key]}
|
||||||
label={key}
|
linkName={meta.name}
|
||||||
bind:value={record[key]} />
|
modelId={meta.modelId} />
|
||||||
|
{:else}
|
||||||
|
<RecordFieldControl
|
||||||
|
type={determineInputType(meta)}
|
||||||
|
options={determineOptions(meta)}
|
||||||
|
label={meta.name}
|
||||||
|
bind:value={record[key]} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
<ActionButton alert on:click={onClosed}>Cancel</ActionButton>
|
<div class="button-margin-3">
|
||||||
<ActionButton on:click={saveRecord}>Save</ActionButton>
|
<Button secondary on:click={onClosed}>Cancel</Button>
|
||||||
|
</div>
|
||||||
|
<div class="button-margin-4">
|
||||||
|
<Button blue on:click={saveRecord}>Save</Button>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
header {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 20px;
|
||||||
|
grid-template-columns: 40px 1fr;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--blue-light);
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: 20px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--ink);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
padding: 20px;
|
padding: 20px 30px;
|
||||||
background: #fafafa;
|
display: grid;
|
||||||
border-radius: 0.5rem;
|
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
background: var(--grey-1);
|
||||||
|
border-bottom-left-radius: 0.5rem;
|
||||||
|
border-bottom-left-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-margin-3 {
|
||||||
|
grid-column-start: 3;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-margin-4 {
|
||||||
|
grid-column-start: 4;
|
||||||
|
display: grid;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -96,7 +96,7 @@
|
||||||
.snippet-selector__heading {
|
.snippet-selector__heading {
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--ink-lighter);
|
color: var(--grey-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
background-color: var(--grey-light);
|
background-color: var(--grey-1);
|
||||||
margin: 0 40px;
|
margin: 0 40px;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background: var(--grey-light);
|
background: var(--grey-1);
|
||||||
border-radius: 0 0 5px 5px;
|
border-radius: 0 0 5px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
import * as api from "../api"
|
import * as api from "../api"
|
||||||
|
|
||||||
export let record
|
export let record
|
||||||
export let onClosed
|
export let onClosed
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
@ -25,6 +25,7 @@
|
||||||
alert
|
alert
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
await api.deleteRecord(record)
|
await api.deleteRecord(record)
|
||||||
|
notifier.danger('Record deleted')
|
||||||
backendUiStore.actions.records.delete(record)
|
backendUiStore.actions.records.delete(record)
|
||||||
onClosed()
|
onClosed()
|
||||||
}}>
|
}}>
|
||||||
|
@ -36,13 +37,13 @@
|
||||||
<style>
|
<style>
|
||||||
.alert {
|
.alert {
|
||||||
color: rgba(255, 0, 31, 1);
|
color: rgba(255, 0, 31, 1);
|
||||||
background: #fafafa;
|
background: var(--grey-1);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-actions {
|
.modal-actions {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: #fafafa;
|
background: var(--grey-1);
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,3 +51,16 @@
|
||||||
on:input={handleInput}
|
on:input={handleInput}
|
||||||
on:change={handleInput} />
|
on:change={handleInput} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
color: var(--dark-grey);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
export { default as DeleteRecordModal } from "./DeleteRecord.svelte"
|
export { default as DeleteRecordModal } from "./DeleteRecord.svelte"
|
||||||
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte"
|
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte"
|
||||||
export { default as CreateEditModelModal } from "./CreateEditModel/CreateEditModel.svelte"
|
|
||||||
export { default as CreateEditViewModal } from "./CreateEditView.svelte"
|
export { default as CreateEditViewModal } from "./CreateEditView.svelte"
|
||||||
export { default as CreateDatabaseModal } from "./CreateDatabase.svelte"
|
export { default as CreateDatabaseModal } from "./CreateDatabase.svelte"
|
||||||
export { default as CreateUserModal } from "./CreateUser.svelte"
|
export { default as CreateUserModal } from "./CreateUser.svelte"
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
<script>
|
|
||||||
import { getContext } from "svelte"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import HierarchyRow from "./HierarchyRow.svelte"
|
|
||||||
import DatabasesList from "./DatabasesList.svelte"
|
|
||||||
import UsersList from "./UsersList.svelte"
|
|
||||||
import NavItem from "./NavItem.svelte"
|
|
||||||
import getIcon from "components/common/icon"
|
|
||||||
import {
|
|
||||||
CreateDatabaseModal,
|
|
||||||
CreateUserModal,
|
|
||||||
} from "components/database/ModelDataTable/modals"
|
|
||||||
const { open, close } = getContext("simple-modal")
|
|
||||||
|
|
||||||
const openDatabaseCreator = () => {
|
|
||||||
open(
|
|
||||||
CreateDatabaseModal,
|
|
||||||
{
|
|
||||||
onClosed: close,
|
|
||||||
},
|
|
||||||
{ styleContent: { padding: "0" } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const openUserCreator = () => {
|
|
||||||
open(
|
|
||||||
CreateUserModal,
|
|
||||||
{
|
|
||||||
onClosed: close,
|
|
||||||
},
|
|
||||||
{ styleContent: { padding: "0" } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="items-root">
|
|
||||||
<div class="hierarchy" />
|
|
||||||
{#if $backendUiStore.selectedDatabase._id}
|
|
||||||
<div class="hierarchy">
|
|
||||||
<div class="components-list-container">
|
|
||||||
<div class="nav-group-header">
|
|
||||||
<div class="hierarchy-title">Users</div>
|
|
||||||
<i class="ri-add-line hoverable" on:click={openUserCreator} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="hierarchy-items-container">
|
|
||||||
<UsersList />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.items-root {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-height: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: var(--white);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-group-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20px 20px 10px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hierarchy-title {
|
|
||||||
align-items: center;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hierarchy {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hierarchy-items-container {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -50,7 +50,7 @@
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--secondary100);
|
color: var(--ink);
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,6 @@
|
||||||
margin: 0 0 0 6px;
|
margin: 0 0 0 6px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
font-family: Roboto;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
outline: none;
|
outline: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -3,10 +3,8 @@
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import getIcon from "../common/icon"
|
import getIcon from "../common/icon"
|
||||||
import {
|
import { CreateEditViewModal } from "components/database/ModelDataTable/modals"
|
||||||
CreateEditModelModal,
|
import api from "builderStore/api"
|
||||||
CreateEditViewModal,
|
|
||||||
} from "components/database/ModelDataTable/modals"
|
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
|
@ -30,14 +28,6 @@
|
||||||
class:selected={$backendUiStore.selectedView === `all_${node._id}`}>
|
class:selected={$backendUiStore.selectedView === `all_${node._id}`}>
|
||||||
<i class={ICON_MAP[type]} />
|
<i class={ICON_MAP[type]} />
|
||||||
<span style="margin-left: 1rem">{node.name}</span>
|
<span style="margin-left: 1rem">{node.name}</span>
|
||||||
<!-- <i
|
|
||||||
class="ri-edit-line hoverable"
|
|
||||||
on:click={editModel}
|
|
||||||
/>
|
|
||||||
<i
|
|
||||||
class="ri-delete-bin-7-line hoverable"
|
|
||||||
on:click={deleteModel}
|
|
||||||
/> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
<script>
|
||||||
|
import * as blockDefinitions from "constants/backend"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import Block from "components/common/Block.svelte"
|
||||||
|
|
||||||
|
const HEADINGS = [
|
||||||
|
{
|
||||||
|
title: "Fields",
|
||||||
|
key: "FIELDS",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Blocks",
|
||||||
|
key: "BLOCKS",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
let selectedTab = "FIELDS"
|
||||||
|
|
||||||
|
function addField(blockDefinition) {
|
||||||
|
backendUiStore.actions.models.addField(blockDefinition)
|
||||||
|
backendUiStore.actions.models.fetch()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<header>
|
||||||
|
{#each HEADINGS as tab}
|
||||||
|
<span
|
||||||
|
class:selected={selectedTab === tab.key}
|
||||||
|
on:click={() => (selectedTab = tab.key)}>
|
||||||
|
{tab.title}
|
||||||
|
</span>
|
||||||
|
{/each}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="block-grid">
|
||||||
|
{#each Object.values(blockDefinitions[selectedTab]) as blockDefinition}
|
||||||
|
<Block
|
||||||
|
on:click={() => addField(blockDefinition)}
|
||||||
|
title={blockDefinition.name}
|
||||||
|
icon={blockDefinition.icon} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
header {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: var(--ink-lighter);
|
||||||
|
font-size: 14px;
|
||||||
|
background: var(--light-grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
span:hover {
|
||||||
|
background: var(--blue-light);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
background: var(--grey-3);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-grid {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-gap: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,112 @@
|
||||||
|
<script>
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { uuid } from "builderStore/uuid"
|
||||||
|
import { fade } from "svelte/transition"
|
||||||
|
import { FIELDS, BLOCKS, MODELS } from "constants/backend"
|
||||||
|
import Block from "components/common/Block.svelte"
|
||||||
|
|
||||||
|
function addNewField(field) {
|
||||||
|
backendUiStore.actions.models.addField(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createModel(model) {
|
||||||
|
const { schema, ...rest } = $backendUiStore.selectedModel
|
||||||
|
|
||||||
|
const newModel = { ...model, schema: {} }
|
||||||
|
|
||||||
|
// TODO: could be better
|
||||||
|
for (let key in model.schema) {
|
||||||
|
newModel.schema[uuid()] = model.schema[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
backendUiStore.actions.models.save({
|
||||||
|
model: {
|
||||||
|
...newModel,
|
||||||
|
...rest,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section transition:fade>
|
||||||
|
<header>
|
||||||
|
<h2>Create New Model</h2>
|
||||||
|
<p>Before you can view your model, you need to set it up.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="block-row">
|
||||||
|
<span class="block-row-title">Fields</span>
|
||||||
|
<p>Blocks are pre-made fields and help you build your model quicker.</p>
|
||||||
|
<div class="blocks">
|
||||||
|
{#each Object.values(FIELDS) as field}
|
||||||
|
<Block
|
||||||
|
primary
|
||||||
|
title={field.name}
|
||||||
|
icon={field.icon}
|
||||||
|
on:click={() => addNewField(field)} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block-row">
|
||||||
|
<span class="block-row-title">Blocks</span>
|
||||||
|
<p>Blocks are pre-made fields and help you build your model quicker.</p>
|
||||||
|
<div class="blocks">
|
||||||
|
{#each Object.values(BLOCKS) as field}
|
||||||
|
<Block
|
||||||
|
secondary
|
||||||
|
title={field.name}
|
||||||
|
icon={field.icon}
|
||||||
|
on:click={() => addNewField(field)} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block-row">
|
||||||
|
<span class="block-row-title">Models</span>
|
||||||
|
<p>Blocks are pre-made fields and help you build your model quicker.</p>
|
||||||
|
<div class="blocks">
|
||||||
|
{#each Object.values(MODELS) as model}
|
||||||
|
<Block
|
||||||
|
tertiary
|
||||||
|
title={model.name}
|
||||||
|
icon={model.icon}
|
||||||
|
on:click={() => createModel(model)} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-row-title {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-row {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-row .blocks {
|
||||||
|
display: grid;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
grid-auto-columns: 110px;
|
||||||
|
grid-gap: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,47 @@
|
||||||
|
<script>
|
||||||
|
export let icon
|
||||||
|
export let className
|
||||||
|
export let title
|
||||||
|
export let selected
|
||||||
|
export let indented
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class:selected on:click class={className}>
|
||||||
|
<i class:indented class={icon} />
|
||||||
|
<span>{title}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.indented {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
padding: 0 10px 0 10px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
transition: 0.3s background-color;
|
||||||
|
color: var(--ink);
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
background-color: var(--blue-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
div:hover {
|
||||||
|
background-color: var(--blue-light);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: var(--grey-7);
|
||||||
|
font-size: 20px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,107 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { slide } from "svelte/transition"
|
||||||
|
import { Switcher } from "@budibase/bbui"
|
||||||
|
import { goto } from "@sveltech/routify"
|
||||||
|
import { store, backendUiStore } from "builderStore"
|
||||||
|
import BlockNavigator from "./BlockNavigator.svelte"
|
||||||
|
import ListItem from "./ListItem.svelte"
|
||||||
|
import { Button } from "@budibase/bbui"
|
||||||
|
|
||||||
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
|
let HEADINGS = [
|
||||||
|
{
|
||||||
|
title: "Navigate",
|
||||||
|
key: "NAVIGATE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Add",
|
||||||
|
key: "ADD",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
$: selectedTab = $backendUiStore.tabs.NAVIGATION_PANEL
|
||||||
|
|
||||||
|
function selectModel(model, fieldId) {
|
||||||
|
backendUiStore.actions.models.select(model)
|
||||||
|
|
||||||
|
if (fieldId) {
|
||||||
|
backendUiStore.update(state => {
|
||||||
|
state.selectedField = fieldId
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupForNewModel() {
|
||||||
|
backendUiStore.update(state => {
|
||||||
|
state.selectedModel = {}
|
||||||
|
state.draftModel = { schema: {} }
|
||||||
|
state.tabs.SETUP_PANEL = "SETUP"
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="items-root">
|
||||||
|
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
|
||||||
|
<div class="hierarchy">
|
||||||
|
<div class="components-list-container">
|
||||||
|
<Switcher
|
||||||
|
headings={HEADINGS}
|
||||||
|
bind:value={$backendUiStore.tabs.NAVIGATION_PANEL}>
|
||||||
|
{#if selectedTab === 'NAVIGATE'}
|
||||||
|
<Button purple wide on:click={setupForNewModel}>
|
||||||
|
Create New Model
|
||||||
|
</Button>
|
||||||
|
<div class="hierarchy-items-container">
|
||||||
|
{#each $backendUiStore.models as model}
|
||||||
|
<ListItem
|
||||||
|
selected={!$backendUiStore.selectedField && model._id === $backendUiStore.selectedModel._id}
|
||||||
|
title={model.name}
|
||||||
|
icon="ri-table-fill"
|
||||||
|
on:click={() => selectModel(model)} />
|
||||||
|
{#if model._id === $backendUiStore.selectedModel._id}
|
||||||
|
<div in:slide>
|
||||||
|
{#each Object.keys(model.schema) as fieldId}
|
||||||
|
<ListItem
|
||||||
|
selected={model._id === $backendUiStore.selectedModel._id && fieldId === $backendUiStore.selectedField}
|
||||||
|
indented
|
||||||
|
icon="ri-layout-column-fill"
|
||||||
|
title={model.schema[fieldId].name}
|
||||||
|
on:click={() => selectModel(model, fieldId)} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else if selectedTab === 'ADD'}
|
||||||
|
<BlockNavigator />
|
||||||
|
{/if}
|
||||||
|
</Switcher>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.items-root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--white);
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hierarchy {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hierarchy-items-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,132 @@
|
||||||
|
<script>
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { Button } from "@budibase/bbui"
|
||||||
|
import Dropdown from "components/common/Dropdown.svelte"
|
||||||
|
import Textbox from "components/common/Textbox.svelte"
|
||||||
|
import ButtonGroup from "components/common/ButtonGroup.svelte"
|
||||||
|
import NumberBox from "components/common/NumberBox.svelte"
|
||||||
|
import ValuesList from "components/common/ValuesList.svelte"
|
||||||
|
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||||
|
import Checkbox from "components/common/Checkbox.svelte"
|
||||||
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
|
import DatePicker from "components/common/DatePicker.svelte"
|
||||||
|
import { keys, cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
|
const FIELD_TYPES = ["string", "number", "boolean", "link"]
|
||||||
|
|
||||||
|
let field = {}
|
||||||
|
|
||||||
|
$: field =
|
||||||
|
$backendUiStore.draftModel.schema[$backendUiStore.selectedField] || {}
|
||||||
|
$: required =
|
||||||
|
field.constraints &&
|
||||||
|
field.constraints.presence &&
|
||||||
|
!constraints.presence.allowEmpty
|
||||||
|
|
||||||
|
function attachModelIdToSchema(evt) {
|
||||||
|
const { draftModel } = $backendUiStore
|
||||||
|
if ($backendUiStore.selectedField !== evt.target.value) {
|
||||||
|
delete draftModel.schema[$backendUiStore.selectedField]
|
||||||
|
draftModel.schema[evt.target.value] = field
|
||||||
|
backendUiStore.update(state => {
|
||||||
|
state.selectedField = evt.target.value
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<div class="field-box">
|
||||||
|
<header>Name</header>
|
||||||
|
<input class="budibase__input" type="text" bind:value={field.name} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<div class="field-box">
|
||||||
|
<header>Type</header>
|
||||||
|
<span>{field.type}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<div class="field">
|
||||||
|
<label>Required</label>
|
||||||
|
<input type="checkbox" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if field.type === 'string'}
|
||||||
|
<NumberBox
|
||||||
|
label="Max Length"
|
||||||
|
bind:value={field.constraints.length.maximum} />
|
||||||
|
<ValuesList label="Categories" bind:values={field.constraints.inclusion} />
|
||||||
|
{:else if field.type === 'datetime'}
|
||||||
|
<DatePicker
|
||||||
|
label="Min Value"
|
||||||
|
bind:value={field.constraints.datetime.earliest} />
|
||||||
|
<DatePicker
|
||||||
|
label="Max Value"
|
||||||
|
bind:value={field.constraints.datetime.latest} />
|
||||||
|
{:else if field.type === 'number'}
|
||||||
|
<NumberBox
|
||||||
|
label="Min Value"
|
||||||
|
bind:value={field.constraints.numericality.greaterThanOrEqualTo} />
|
||||||
|
<NumberBox
|
||||||
|
label="Max Value"
|
||||||
|
bind:value={field.constraints.numericality.lessThanOrEqualTo} />
|
||||||
|
{:else if field.type === 'link'}
|
||||||
|
<div class="field">
|
||||||
|
<label>Link</label>
|
||||||
|
<select
|
||||||
|
class="budibase__input"
|
||||||
|
bind:value={field.modelId}
|
||||||
|
on:change={attachModelIdToSchema}>
|
||||||
|
<option value={''} />
|
||||||
|
{#each $backendUiStore.models as model}
|
||||||
|
{#if model._id !== $backendUiStore.draftModel._id}
|
||||||
|
<option value={model._id}>{model.name}</option>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.info {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-box header {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-box span {
|
||||||
|
background: var(--grey-2);
|
||||||
|
color: var(--grey-6);
|
||||||
|
font-weight: 400;
|
||||||
|
height: 36px;
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 12px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,127 @@
|
||||||
|
<script>
|
||||||
|
import { getContext, onMount } from "svelte"
|
||||||
|
import { Button, Switcher } from "@budibase/bbui"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
|
import { store, backendUiStore } from "builderStore"
|
||||||
|
import api from "builderStore/api"
|
||||||
|
import ModelFieldEditor from "./ModelFieldEditor.svelte"
|
||||||
|
|
||||||
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
|
const ITEMS = [
|
||||||
|
{
|
||||||
|
title: "Setup",
|
||||||
|
key: "SETUP",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Delete",
|
||||||
|
key: "DELETE",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
let edited = false
|
||||||
|
|
||||||
|
$: selectedTab = $backendUiStore.tabs.SETUP_PANEL
|
||||||
|
|
||||||
|
$: edited =
|
||||||
|
$backendUiStore.selectedField ||
|
||||||
|
($backendUiStore.draftModel &&
|
||||||
|
$backendUiStore.draftModel.name !== $backendUiStore.selectedModel.name)
|
||||||
|
|
||||||
|
async function deleteModel() {
|
||||||
|
const model = $backendUiStore.selectedModel
|
||||||
|
const field = $backendUiStore.selectedField
|
||||||
|
|
||||||
|
if (field) {
|
||||||
|
const name = model.schema[field].name
|
||||||
|
delete model.schema[field]
|
||||||
|
backendUiStore.actions.models.save({ model })
|
||||||
|
notifier.danger(`Field ${name} deleted.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const DELETE_MODEL_URL = `/api/models/${model._id}/${model._rev}`
|
||||||
|
const response = await api.delete(DELETE_MODEL_URL)
|
||||||
|
backendUiStore.update(state => {
|
||||||
|
state.selectedView = null
|
||||||
|
state.selectedModel = {}
|
||||||
|
state.draftModel = {}
|
||||||
|
state.models = state.models.filter(({ _id }) => _id !== model._id)
|
||||||
|
notifier.danger(`${model.name} deleted successfully.`)
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveModel() {
|
||||||
|
await backendUiStore.actions.models.save({
|
||||||
|
model: $backendUiStore.draftModel,
|
||||||
|
})
|
||||||
|
notifier.success(
|
||||||
|
"Success! Your changes have been saved. Please continue on with your greatness."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="items-root">
|
||||||
|
<Switcher headings={ITEMS} bind:value={$backendUiStore.tabs.SETUP_PANEL}>
|
||||||
|
{#if selectedTab === 'SETUP'}
|
||||||
|
{#if $backendUiStore.selectedField}
|
||||||
|
<ModelFieldEditor />
|
||||||
|
{:else if $backendUiStore.draftModel.schema}
|
||||||
|
<div class="titled-input">
|
||||||
|
<header>Name</header>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="budibase__input"
|
||||||
|
bind:value={$backendUiStore.draftModel.name} />
|
||||||
|
</div>
|
||||||
|
<div class="titled-input">
|
||||||
|
<header>Import Data</header>
|
||||||
|
<Button wide secondary>Import CSV</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<footer>
|
||||||
|
<Button disabled={!edited} green={edited} wide on:click={saveModel}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</footer>
|
||||||
|
{:else if selectedTab === 'DELETE'}
|
||||||
|
<div class="titled-input">
|
||||||
|
<header>Danger Zone</header>
|
||||||
|
<Button red wide on:click={deleteModel}>Delete</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Switcher>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
header {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-root {
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.titled-input {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titled-input header {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as ModelSetupNav } from "./ModelSetupNav.svelte"
|
|
@ -1,143 +0,0 @@
|
||||||
<script>
|
|
||||||
import { getContext, onMount } from "svelte"
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import HierarchyRow from "./HierarchyRow.svelte"
|
|
||||||
import NavItem from "./NavItem.svelte"
|
|
||||||
import getIcon from "components/common/icon"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
import {
|
|
||||||
CreateEditModelModal,
|
|
||||||
CreateEditViewModal,
|
|
||||||
} from "components/database/ModelDataTable/modals"
|
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
|
||||||
|
|
||||||
function editModel() {
|
|
||||||
open(
|
|
||||||
CreateEditModelModal,
|
|
||||||
{
|
|
||||||
model: node,
|
|
||||||
onClosed: close,
|
|
||||||
},
|
|
||||||
{ styleContent: { padding: "0" } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function newModel() {
|
|
||||||
open(
|
|
||||||
CreateEditModelModal,
|
|
||||||
{
|
|
||||||
onClosed: close,
|
|
||||||
},
|
|
||||||
{ styleContent: { padding: "0" } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function newView() {
|
|
||||||
open(
|
|
||||||
CreateEditViewModal,
|
|
||||||
{
|
|
||||||
onClosed: close,
|
|
||||||
},
|
|
||||||
{ styleContent: { padding: "0" } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectModel(model) {
|
|
||||||
backendUiStore.update(state => {
|
|
||||||
state.selectedModel = model
|
|
||||||
state.selectedView = `all_${model._id}`
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteModel(modelToDelete) {
|
|
||||||
const DELETE_MODEL_URL = `/api/models/${node._id}/${node._rev}`
|
|
||||||
const response = await api.delete(DELETE_MODEL_URL)
|
|
||||||
backendUiStore.update(state => {
|
|
||||||
state.models = state.models.filter(
|
|
||||||
model => model._id !== modelToDelete._id
|
|
||||||
)
|
|
||||||
state.selectedView = {}
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectView(view) {
|
|
||||||
backendUiStore.update(state => {
|
|
||||||
state.selectedView = view.name
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="items-root">
|
|
||||||
<div class="hierarchy">
|
|
||||||
<div class="components-list-container">
|
|
||||||
<div class="nav-group-header">
|
|
||||||
<div class="hierarchy-title">Models</div>
|
|
||||||
<div class="uk-inline">
|
|
||||||
<i class="ri-add-line hoverable" on:click={newModel} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="hierarchy-items-container">
|
|
||||||
{#each $backendUiStore.models as model}
|
|
||||||
<HierarchyRow onSelect={selectModel} node={model} type="model" />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="hierarchy">
|
|
||||||
<div class="components-list-container">
|
|
||||||
<div class="nav-group-header">
|
|
||||||
<div class="hierarchy-title">Views</div>
|
|
||||||
<div class="uk-inline">
|
|
||||||
<i class="ri-add-line hoverable" on:click={newView} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="hierarchy-items-container">
|
|
||||||
{#each $backendUiStore.views as view}
|
|
||||||
<HierarchyRow onSelect={selectView} node={view} type="view" />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.items-root {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-height: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: var(--white);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-group-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20px 20px 10px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hierarchy-title {
|
|
||||||
align-items: center;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hierarchy {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hierarchy-items-container {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -64,7 +64,6 @@
|
||||||
margin: 0 0 0 6px;
|
margin: 0 0 0 6px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
font-family: Roboto;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
outline: none;
|
outline: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -21,23 +21,25 @@
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 1px solid var(--grey-medium);
|
border: 1px solid var(--grey-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-button:hover {
|
.app-button:hover {
|
||||||
background-color: var(--grey-light);
|
background-color: var(--grey-1);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-title {
|
.app-title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
|
font-family: Inter;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-desc {
|
.app-desc {
|
||||||
color: var(--ink-light);
|
color: var(--grey-7);
|
||||||
|
font-family: Inter;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-footer {
|
.card-footer {
|
||||||
|
@ -56,7 +58,7 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 12px 20px;
|
padding: 12px 20px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 1px var(--grey) solid;
|
border: 1px var(--grey-2) solid;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -142,11 +142,11 @@
|
||||||
background-color: var(--blue-light);
|
background-color: var(--blue-light);
|
||||||
}
|
}
|
||||||
.info {
|
.info {
|
||||||
color: var(--primary100);
|
color: var(--blue);
|
||||||
text-decoration-color: var(--primary100);
|
text-decoration-color: var(--blue);
|
||||||
}
|
}
|
||||||
.info :global(svg) {
|
.info :global(svg) {
|
||||||
fill: var(--primary100);
|
fill: var(--blue);
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
@ -164,7 +164,7 @@
|
||||||
padding: 30px 40px;
|
padding: 30px 40px;
|
||||||
border-bottom-left-radius: 5px;
|
border-bottom-left-radius: 5px;
|
||||||
border-bottom-right-radius: 50px;
|
border-bottom-right-radius: 50px;
|
||||||
background-color: var(--grey-light);
|
background-color: var(--grey-1);
|
||||||
}
|
}
|
||||||
.spinner-container {
|
.spinner-container {
|
||||||
background: white;
|
background: white;
|
||||||
|
@ -183,7 +183,7 @@
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
}
|
}
|
||||||
.error {
|
.error {
|
||||||
color: var(--deletion100);
|
color: var(--red);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,11 +21,11 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
color: var(--ink-lighter);
|
color: var(--grey-5);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
<script>
|
<script>
|
||||||
import FlatButton from "./FlatButton.svelte";
|
import FlatButton from "./FlatButton.svelte"
|
||||||
|
|
||||||
export let format = "hex";
|
export let format = "hex"
|
||||||
export let onclick = format => {};
|
export let onclick = format => {}
|
||||||
|
|
||||||
let colorFormats = ["hex", "rgb", "hsl"];
|
let colorFormats = ["hex", "rgb", "hsl"]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="flatbutton-group">
|
||||||
|
{#each colorFormats as text}
|
||||||
|
<FlatButton
|
||||||
|
selected={format === text}
|
||||||
|
{text}
|
||||||
|
on:click={() => onclick(text)} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.flatbutton-group {
|
.flatbutton-group {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
@ -18,12 +27,3 @@
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="flatbutton-group">
|
|
||||||
{#each colorFormats as text}
|
|
||||||
<FlatButton
|
|
||||||
selected={format === text}
|
|
||||||
{text}
|
|
||||||
on:click={() => onclick(text)} />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
<script>
|
<script>
|
||||||
import {buildStyle} from "./helpers.js"
|
import { buildStyle } from "./helpers.js"
|
||||||
import {fade} from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
|
|
||||||
export let backgroundSize = "10px"
|
export let backgroundSize = "10px"
|
||||||
export let borderRadius = ""
|
export let borderRadius = ""
|
||||||
export let height = ""
|
export let height = ""
|
||||||
export let width = ""
|
export let width = ""
|
||||||
export let margin = ""
|
export let margin = ""
|
||||||
|
|
||||||
$: style = buildStyle({backgroundSize, borderRadius, height, width, margin})
|
|
||||||
|
|
||||||
|
$: style = buildStyle({ backgroundSize, borderRadius, height, width, margin })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div in:fade {style}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
div {
|
||||||
background-image: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
|
background-image: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
|
||||||
|
@ -19,7 +22,3 @@
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div in:fade {style}>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
|
@ -1,39 +1,39 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, createEventDispatcher } from "svelte";
|
import { onMount, createEventDispatcher } from "svelte"
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from "svelte/transition"
|
||||||
import Swatch from "./Swatch.svelte";
|
import Swatch from "./Swatch.svelte"
|
||||||
import CheckedBackground from "./CheckedBackground.svelte"
|
import CheckedBackground from "./CheckedBackground.svelte"
|
||||||
import {buildStyle} from "./helpers.js"
|
import { buildStyle } from "./helpers.js"
|
||||||
import {
|
import {
|
||||||
getColorFormat,
|
getColorFormat,
|
||||||
convertToHSVA,
|
convertToHSVA,
|
||||||
convertHsvaToFormat
|
convertHsvaToFormat,
|
||||||
} from "./utils.js";
|
} from "./utils.js"
|
||||||
import Slider from "./Slider.svelte";
|
import Slider from "./Slider.svelte"
|
||||||
import Palette from "./Palette.svelte";
|
import Palette from "./Palette.svelte"
|
||||||
import ButtonGroup from "./ButtonGroup.svelte";
|
import ButtonGroup from "./ButtonGroup.svelte"
|
||||||
import Input from "./Input.svelte";
|
import Input from "./Input.svelte"
|
||||||
|
|
||||||
export let value = "#3ec1d3ff";
|
export let value = "#3ec1d3ff"
|
||||||
export let swatches = [] //TODO: Safe swatches - limit to 12. warn in console
|
export let swatches = [] //TODO: Safe swatches - limit to 12. warn in console
|
||||||
export let disableSwatches = false
|
export let disableSwatches = false
|
||||||
export let format = "hexa";
|
export let format = "hexa"
|
||||||
export let open = false;
|
export let open = false
|
||||||
|
|
||||||
export let pickerHeight = 0;
|
export let pickerHeight = 0
|
||||||
export let pickerWidth = 0;
|
export let pickerWidth = 0
|
||||||
|
|
||||||
let adder = null;
|
let adder = null
|
||||||
|
|
||||||
let h = null;
|
let h = null
|
||||||
let s = null;
|
let s = null
|
||||||
let v = null;
|
let v = null
|
||||||
let a = null;
|
let a = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if(!swatches.length > 0) {
|
if (!swatches.length > 0) {
|
||||||
//Don't use locally stored recent colors if swatches have been passed as props
|
//Don't use locally stored recent colors if swatches have been passed as props
|
||||||
getRecentColors()
|
getRecentColors()
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
if (format) {
|
if (format) {
|
||||||
convertAndSetHSVA()
|
convertAndSetHSVA()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
function getRecentColors() {
|
function getRecentColors() {
|
||||||
let colorStore = localStorage.getItem("cp:recent-colors")
|
let colorStore = localStorage.getItem("cp:recent-colors")
|
||||||
|
@ -61,39 +61,39 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertAndSetHSVA() {
|
function convertAndSetHSVA() {
|
||||||
let hsva = convertToHSVA(value, format);
|
let hsva = convertToHSVA(value, format)
|
||||||
setHSVA(hsva);
|
setHSVA(hsva)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHSVA([hue, sat, val, alpha]) {
|
function setHSVA([hue, sat, val, alpha]) {
|
||||||
h = hue;
|
h = hue
|
||||||
s = sat;
|
s = sat
|
||||||
v = val;
|
v = val
|
||||||
a = alpha;
|
a = alpha
|
||||||
}
|
}
|
||||||
|
|
||||||
//fired by choosing a color from the palette
|
//fired by choosing a color from the palette
|
||||||
function setSaturationAndValue({ detail }) {
|
function setSaturationAndValue({ detail }) {
|
||||||
s = detail.s;
|
s = detail.s
|
||||||
v = detail.v;
|
v = detail.v
|
||||||
value = convertHsvaToFormat([h, s, v, a], format);
|
value = convertHsvaToFormat([h, s, v, a], format)
|
||||||
dispatchValue()
|
dispatchValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHue({color, isDrag}) {
|
function setHue({ color, isDrag }) {
|
||||||
h = color;
|
h = color
|
||||||
value = convertHsvaToFormat([h, s, v, a], format);
|
value = convertHsvaToFormat([h, s, v, a], format)
|
||||||
if(!isDrag) {
|
if (!isDrag) {
|
||||||
dispatchValue()
|
dispatchValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setAlpha({color, isDrag}) {
|
function setAlpha({ color, isDrag }) {
|
||||||
a = color === "1.00" ? "1" : color;
|
a = color === "1.00" ? "1" : color
|
||||||
value = convertHsvaToFormat([h, s, v, a], format);
|
value = convertHsvaToFormat([h, s, v, a], format)
|
||||||
if(!isDrag) {
|
if (!isDrag) {
|
||||||
dispatchValue()
|
dispatchValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dispatchValue() {
|
function dispatchValue() {
|
||||||
|
@ -101,43 +101,42 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeFormatAndConvert(f) {
|
function changeFormatAndConvert(f) {
|
||||||
format = f;
|
format = f
|
||||||
value = convertHsvaToFormat([h, s, v, a], format);
|
value = convertHsvaToFormat([h, s, v, a], format)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleColorInput(text) {
|
function handleColorInput(text) {
|
||||||
let format = getColorFormat(text)
|
let format = getColorFormat(text)
|
||||||
if(format) {
|
if (format) {
|
||||||
value = text
|
value = text
|
||||||
convertAndSetHSVA()
|
convertAndSetHSVA()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dispatchInputChange() {
|
function dispatchInputChange() {
|
||||||
if(format) {
|
if (format) {
|
||||||
dispatchValue()
|
dispatchValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSwatch() {
|
function addSwatch() {
|
||||||
if(format) {
|
if (format) {
|
||||||
dispatch("addswatch", value)
|
dispatch("addswatch", value)
|
||||||
setRecentColor(value)
|
setRecentColor(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeSwatch(idx) {
|
function removeSwatch(idx) {
|
||||||
let removedSwatch = swatches.splice(idx, 1);
|
let removedSwatch = swatches.splice(idx, 1)
|
||||||
swatches = swatches
|
swatches = swatches
|
||||||
dispatch("removeswatch", removedSwatch)
|
dispatch("removeswatch", removedSwatch)
|
||||||
localStorage.setItem("cp:recent-colors", JSON.stringify(swatches))
|
localStorage.setItem("cp:recent-colors", JSON.stringify(swatches))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function applySwatch(color) {
|
function applySwatch(color) {
|
||||||
if(value !== color) {
|
if (value !== color) {
|
||||||
format = getColorFormat(color)
|
format = getColorFormat(color)
|
||||||
if(format) {
|
if (format) {
|
||||||
value = color
|
value = color
|
||||||
convertAndSetHSVA()
|
convertAndSetHSVA()
|
||||||
dispatchValue()
|
dispatchValue()
|
||||||
|
@ -145,11 +144,112 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: border = (v > 90 && s < 5) ? "1px dashed #dedada" : ""
|
$: border = v > 90 && s < 5 ? "1px dashed #dedada" : ""
|
||||||
$: style = buildStyle({background: value, border})
|
$: style = buildStyle({ background: value, border })
|
||||||
$: shrink = swatches.length > 0
|
$: shrink = swatches.length > 0
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="colorpicker-container">
|
||||||
|
|
||||||
|
<div class="palette-panel">
|
||||||
|
<Palette on:change={setSaturationAndValue} {h} {s} {v} {a} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-panel">
|
||||||
|
<div class="alpha-hue-panel">
|
||||||
|
<div>
|
||||||
|
<CheckedBackground borderRadius="50%" backgroundSize="8px">
|
||||||
|
<div class="selected-color" {style} />
|
||||||
|
</CheckedBackground>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Slider type="hue" value={h} on:change={hue => setHue(hue.detail)} />
|
||||||
|
|
||||||
|
<CheckedBackground borderRadius="10px" backgroundSize="7px">
|
||||||
|
<Slider
|
||||||
|
type="alpha"
|
||||||
|
value={a}
|
||||||
|
on:change={alpha => setAlpha(alpha.detail)} />
|
||||||
|
</CheckedBackground>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="format-input-panel">
|
||||||
|
<ButtonGroup {format} onclick={changeFormatAndConvert} />
|
||||||
|
<Input {value} on:input={event => handleColorInput(event.target.value)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="colorpicker-container"
|
||||||
|
bind:clientHeight={pickerHeight}
|
||||||
|
bind:clientWidth={pickerWidth}>
|
||||||
|
|
||||||
|
<div class="palette-panel">
|
||||||
|
<Palette on:change={setSaturationAndValue} {h} {s} {v} {a} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-panel">
|
||||||
|
<div class="alpha-hue-panel">
|
||||||
|
<div>
|
||||||
|
<CheckedBackground borderRadius="50%" backgroundSize="8px">
|
||||||
|
<div class="selected-color" {style} />
|
||||||
|
</CheckedBackground>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Slider
|
||||||
|
type="hue"
|
||||||
|
value={h}
|
||||||
|
on:change={hue => setHue(hue.detail)}
|
||||||
|
on:dragend={dispatchValue} />
|
||||||
|
|
||||||
|
<CheckedBackground borderRadius="10px" backgroundSize="7px">
|
||||||
|
<Slider
|
||||||
|
type="alpha"
|
||||||
|
value={a}
|
||||||
|
on:change={(alpha, isDrag) => setAlpha(alpha.detail, isDrag)}
|
||||||
|
on:dragend={dispatchValue} />
|
||||||
|
</CheckedBackground>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if !disableSwatches}
|
||||||
|
<div transition:fade class="swatch-panel">
|
||||||
|
{#if swatches.length > 0}
|
||||||
|
{#each swatches as color, idx}
|
||||||
|
<Swatch
|
||||||
|
{color}
|
||||||
|
on:click={() => applySwatch(color)}
|
||||||
|
on:removeswatch={() => removeSwatch(idx)} />
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{#if swatches.length !== 12}
|
||||||
|
<div
|
||||||
|
bind:this={adder}
|
||||||
|
transition:fade
|
||||||
|
class="adder"
|
||||||
|
on:click={addSwatch}
|
||||||
|
class:shrink>
|
||||||
|
<span>+</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="format-input-panel">
|
||||||
|
<ButtonGroup {format} onclick={changeFormatAndConvert} />
|
||||||
|
<Input
|
||||||
|
{value}
|
||||||
|
on:input={event => handleColorInput(event.target.value)}
|
||||||
|
on:change={dispatchInputChange} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.colorpicker-container {
|
.colorpicker-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -161,7 +261,8 @@
|
||||||
width: 220px;
|
width: 220px;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: 0 0.15em 1.5em 0 rgba(0,0,0,.1), 0 0 1em 0 rgba(0,0,0,.03);
|
box-shadow: 0 0.15em 1.5em 0 rgba(0, 0, 0, 0.1),
|
||||||
|
0 0 1em 0 rgba(0, 0, 0, 0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
.palette-panel {
|
.palette-panel {
|
||||||
|
@ -192,7 +293,7 @@
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.swatch-panel {
|
.swatch-panel {
|
||||||
flex: 0 0 15px;
|
flex: 0 0 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
|
@ -228,54 +329,3 @@
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="colorpicker-container" bind:clientHeight={pickerHeight} bind:clientWidth={pickerWidth}>
|
|
||||||
|
|
||||||
<div class="palette-panel">
|
|
||||||
<Palette on:change={setSaturationAndValue} {h} {s} {v} {a} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control-panel">
|
|
||||||
<div class="alpha-hue-panel">
|
|
||||||
<div>
|
|
||||||
<CheckedBackground borderRadius="50%" backgroundSize="8px">
|
|
||||||
<div class="selected-color" {style} />
|
|
||||||
</CheckedBackground>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Slider type="hue" value={h} on:change={(hue) => setHue(hue.detail)} on:dragend={dispatchValue} />
|
|
||||||
|
|
||||||
<CheckedBackground borderRadius="10px" backgroundSize="7px">
|
|
||||||
<Slider
|
|
||||||
type="alpha"
|
|
||||||
value={a}
|
|
||||||
on:change={(alpha, isDrag) => setAlpha(alpha.detail, isDrag)}
|
|
||||||
on:dragend={dispatchValue}
|
|
||||||
/>
|
|
||||||
</CheckedBackground>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if !disableSwatches}
|
|
||||||
<div transition:fade class="swatch-panel">
|
|
||||||
{#if swatches.length > 0}
|
|
||||||
{#each swatches as color, idx}
|
|
||||||
<Swatch {color} on:click={() => applySwatch(color)} on:removeswatch={() => removeSwatch(idx)} />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
{#if swatches.length !== 12}
|
|
||||||
<div bind:this={adder} transition:fade class="adder" on:click={addSwatch} class:shrink>
|
|
||||||
<span>+</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="format-input-panel">
|
|
||||||
<ButtonGroup {format} onclick={changeFormatAndConvert} />
|
|
||||||
<Input {value} on:input={event => handleColorInput(event.target.value)} on:change={dispatchInputChange} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,152 +1,184 @@
|
||||||
<script>
|
<script>
|
||||||
import Colorpicker from "./Colorpicker.svelte"
|
import Colorpicker from "./Colorpicker.svelte"
|
||||||
import CheckedBackground from "./CheckedBackground.svelte"
|
import CheckedBackground from "./CheckedBackground.svelte"
|
||||||
import {createEventDispatcher, afterUpdate, beforeUpdate} from "svelte"
|
import { createEventDispatcher, afterUpdate, beforeUpdate } from "svelte"
|
||||||
|
|
||||||
import {buildStyle} from "./helpers.js"
|
import { buildStyle } from "./helpers.js"
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from "svelte/transition"
|
||||||
import {getColorFormat} from "./utils.js"
|
import { getColorFormat } from "./utils.js"
|
||||||
|
|
||||||
export let value = "#3ec1d3ff"
|
export let value = "#3ec1d3ff"
|
||||||
export let swatches = []
|
export let swatches = []
|
||||||
export let disableSwatches = false
|
export let disableSwatches = false
|
||||||
export let open = false;
|
export let open = false
|
||||||
export let width = "25px"
|
export let width = "25px"
|
||||||
export let height = "25px"
|
export let height = "25px"
|
||||||
|
|
||||||
let format = "hexa";
|
let format = "hexa"
|
||||||
let dimensions = {top: 0, left: 0}
|
let dimensions = { top: 0, left: 0 }
|
||||||
let colorPreview = null
|
let colorPreview = null
|
||||||
|
|
||||||
let previewHeight = null
|
let previewHeight = null
|
||||||
let previewWidth = null
|
let previewWidth = null
|
||||||
let pickerWidth = 0
|
let pickerWidth = 0
|
||||||
let pickerHeight = 0
|
let pickerHeight = 0
|
||||||
|
|
||||||
let anchorEl = null
|
let anchorEl = null
|
||||||
let parentNodes = [];
|
let parentNodes = []
|
||||||
let errorMsg = null
|
let errorMsg = null
|
||||||
|
|
||||||
$: previewStyle = buildStyle({width, height, background: value})
|
$: previewStyle = buildStyle({ width, height, background: value })
|
||||||
$: errorPreviewStyle = buildStyle({width, height})
|
$: errorPreviewStyle = buildStyle({ width, height })
|
||||||
$: pickerStyle = buildStyle({top: `${dimensions.top}px`, left: `${dimensions.left}px`})
|
$: pickerStyle = buildStyle({
|
||||||
|
top: `${dimensions.top}px`,
|
||||||
|
left: `${dimensions.left}px`,
|
||||||
|
})
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
beforeUpdate(() => {
|
beforeUpdate(() => {
|
||||||
format = getColorFormat(value)
|
format = getColorFormat(value)
|
||||||
if(!format) {
|
if (!format) {
|
||||||
errorMsg = `Colorpicker - ${value} is an unknown color format. Please use a hex, rgb or hsl value`
|
errorMsg = `Colorpicker - ${value} is an unknown color format. Please use a hex, rgb or hsl value`
|
||||||
console.error(errorMsg)
|
console.error(errorMsg)
|
||||||
}else{
|
} else {
|
||||||
errorMsg = null
|
errorMsg = null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
afterUpdate(() => {
|
afterUpdate(() => {
|
||||||
if(colorPreview && colorPreview.offsetParent && !anchorEl) {
|
if (colorPreview && colorPreview.offsetParent && !anchorEl) {
|
||||||
//Anchor relative to closest positioned ancestor element. If none, then anchor to body
|
//Anchor relative to closest positioned ancestor element. If none, then anchor to body
|
||||||
anchorEl = colorPreview.offsetParent
|
anchorEl = colorPreview.offsetParent
|
||||||
let curEl = colorPreview
|
let curEl = colorPreview
|
||||||
let els = []
|
let els = []
|
||||||
//Travel up dom tree from preview element to find parent elements that scroll
|
//Travel up dom tree from preview element to find parent elements that scroll
|
||||||
while(!anchorEl.isSameNode(curEl)) {
|
while (!anchorEl.isSameNode(curEl)) {
|
||||||
curEl = curEl.parentNode
|
curEl = curEl.parentNode
|
||||||
let elOverflow = window.getComputedStyle(curEl).getPropertyValue("overflow")
|
let elOverflow = window
|
||||||
if(/scroll|auto/.test(elOverflow)) {
|
.getComputedStyle(curEl)
|
||||||
els.push(curEl)
|
.getPropertyValue("overflow")
|
||||||
}
|
if (/scroll|auto/.test(elOverflow)) {
|
||||||
}
|
els.push(curEl)
|
||||||
parentNodes = els
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
function openColorpicker(event) {
|
|
||||||
if(colorPreview) {
|
|
||||||
open = true;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
parentNodes = els
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
$: if(open && colorPreview) {
|
function openColorpicker(event) {
|
||||||
const {top: spaceAbove, width, bottom, right, left: spaceLeft} = colorPreview.getBoundingClientRect()
|
if (colorPreview) {
|
||||||
const {innerHeight, innerWidth} = window
|
open = true
|
||||||
|
|
||||||
const {offsetLeft, offsetTop} = colorPreview
|
|
||||||
//get the scrollTop value for all scrollable parent elements
|
|
||||||
let scrollTop = parentNodes.reduce((scrollAcc, el) => scrollAcc += el.scrollTop, 0);
|
|
||||||
|
|
||||||
const spaceBelow = (innerHeight - spaceAbove) - previewHeight
|
|
||||||
const top = spaceAbove > spaceBelow ? (offsetTop - pickerHeight) - scrollTop : (offsetTop + previewHeight) - scrollTop
|
|
||||||
|
|
||||||
//TOO: Testing and Scroll Awareness for x Scroll
|
|
||||||
const spaceRight = (innerWidth - spaceLeft) + previewWidth
|
|
||||||
const left = spaceRight > spaceLeft ? (offsetLeft + previewWidth) : offsetLeft - pickerWidth
|
|
||||||
|
|
||||||
dimensions = {top, left}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onColorChange(color) {
|
$: if (open && colorPreview) {
|
||||||
value = color.detail;
|
const {
|
||||||
dispatch("change", color.detail)
|
top: spaceAbove,
|
||||||
}
|
width,
|
||||||
|
bottom,
|
||||||
|
right,
|
||||||
|
left: spaceLeft,
|
||||||
|
} = colorPreview.getBoundingClientRect()
|
||||||
|
const { innerHeight, innerWidth } = window
|
||||||
|
|
||||||
|
const { offsetLeft, offsetTop } = colorPreview
|
||||||
|
//get the scrollTop value for all scrollable parent elements
|
||||||
|
let scrollTop = parentNodes.reduce(
|
||||||
|
(scrollAcc, el) => (scrollAcc += el.scrollTop),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
const spaceBelow = innerHeight - spaceAbove - previewHeight
|
||||||
|
const top =
|
||||||
|
spaceAbove > spaceBelow
|
||||||
|
? offsetTop - pickerHeight - scrollTop
|
||||||
|
: offsetTop + previewHeight - scrollTop
|
||||||
|
|
||||||
|
//TOO: Testing and Scroll Awareness for x Scroll
|
||||||
|
const spaceRight = innerWidth - spaceLeft + previewWidth
|
||||||
|
const left =
|
||||||
|
spaceRight > spaceLeft
|
||||||
|
? offsetLeft + previewWidth
|
||||||
|
: offsetLeft - pickerWidth
|
||||||
|
|
||||||
|
dimensions = { top, left }
|
||||||
|
}
|
||||||
|
|
||||||
|
function onColorChange(color) {
|
||||||
|
value = color.detail
|
||||||
|
dispatch("change", color.detail)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="color-preview-container">
|
<div class="color-preview-container">
|
||||||
{#if !errorMsg}
|
{#if !errorMsg}
|
||||||
<CheckedBackground borderRadius="3px" backgroundSize="8px">
|
<CheckedBackground borderRadius="3px" backgroundSize="8px">
|
||||||
<div bind:this={colorPreview} bind:clientHeight={previewHeight} bind:clientWidth={previewWidth} class="color-preview" style={previewStyle} on:click={openColorpicker} />
|
<div
|
||||||
</CheckedBackground>
|
bind:this={colorPreview}
|
||||||
|
bind:clientHeight={previewHeight}
|
||||||
|
bind:clientWidth={previewWidth}
|
||||||
|
class="color-preview"
|
||||||
|
style={previewStyle}
|
||||||
|
on:click={openColorpicker} />
|
||||||
|
</CheckedBackground>
|
||||||
|
|
||||||
{#if open}
|
{#if open}
|
||||||
<div transition:fade class="picker-container" style={pickerStyle}>
|
<div transition:fade class="picker-container" style={pickerStyle}>
|
||||||
<Colorpicker on:change={onColorChange} on:addswatch on:removeswatch bind:format bind:value bind:pickerHeight bind:pickerWidth {swatches} {disableSwatches} {open} />
|
<Colorpicker
|
||||||
</div>
|
on:change={onColorChange}
|
||||||
<div on:click|self={() => open = false} class="overlay"></div>
|
on:addswatch
|
||||||
{/if}
|
on:removeswatch
|
||||||
{:else}
|
bind:format
|
||||||
<div class="color-preview preview-error" style={errorPreviewStyle}>
|
bind:value
|
||||||
<span>×</span>
|
bind:pickerHeight
|
||||||
</div>
|
bind:pickerWidth
|
||||||
|
{swatches}
|
||||||
|
{disableSwatches}
|
||||||
|
{open} />
|
||||||
|
</div>
|
||||||
|
<div on:click|self={() => (open = false)} class="overlay" />
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<div class="color-preview preview-error" style={errorPreviewStyle}>
|
||||||
|
<span>×</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.color-preview-container{
|
.color-preview-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-preview {
|
.color-preview {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border: 1px solid #dedada;
|
border: 1px solid #dedada;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-error {
|
.preview-error {
|
||||||
background: #cccccc;
|
background: #cccccc;
|
||||||
color: #808080;
|
color: #808080;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker-container {
|
.picker-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay{
|
.overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
export let text = "";
|
export let text = ""
|
||||||
export let selected = false;
|
export let selected = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="flatbutton" class:selected on:click>{text}</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.flatbutton {
|
.flatbutton {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -25,5 +27,3 @@
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="flatbutton" class:selected on:click>{text}</div>
|
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
export let value = "";
|
export let value = ""
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input on:input type="text" {value} maxlength="25" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input on:input on:change type="text" {value} maxlength="25" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
div {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -22,7 +29,3 @@
|
||||||
font-weight: 550;
|
font-weight: 550;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div>
|
|
||||||
<input on:input on:change type="text" {value} maxlength="25" />
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,42 +1,58 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, createEventDispatcher } from "svelte";
|
import { onMount, createEventDispatcher } from "svelte"
|
||||||
import CheckedBackground from "./CheckedBackground.svelte"
|
import CheckedBackground from "./CheckedBackground.svelte"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let h = 0;
|
export let h = 0
|
||||||
export let s = 0;
|
export let s = 0
|
||||||
export let v = 0;
|
export let v = 0
|
||||||
export let a = 1;
|
export let a = 1
|
||||||
|
|
||||||
let palette;
|
let palette
|
||||||
|
|
||||||
let paletteHeight, paletteWidth = 0;
|
|
||||||
|
|
||||||
|
let paletteHeight,
|
||||||
|
paletteWidth = 0
|
||||||
|
|
||||||
function handleClick(event) {
|
function handleClick(event) {
|
||||||
const { left, top } = palette.getBoundingClientRect();
|
const { left, top } = palette.getBoundingClientRect()
|
||||||
let clickX = (event.clientX - left)
|
let clickX = event.clientX - left
|
||||||
let clickY = (event.clientY - top)
|
let clickY = event.clientY - top
|
||||||
if((clickX > 0 && clickY > 0) && (clickX < paletteWidth && clickY < paletteHeight)) {
|
if (
|
||||||
|
clickX > 0 &&
|
||||||
|
clickY > 0 &&
|
||||||
|
clickX < paletteWidth &&
|
||||||
|
clickY < paletteHeight
|
||||||
|
) {
|
||||||
let s = (clickX / paletteWidth) * 100
|
let s = (clickX / paletteWidth) * 100
|
||||||
let v = 100 - ((clickY / paletteHeight) * 100)
|
let v = 100 - (clickY / paletteHeight) * 100
|
||||||
dispatch("change", {s, v})
|
dispatch("change", { s, v })
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$: pickerX = (s * paletteWidth) / 100;
|
$: pickerX = (s * paletteWidth) / 100
|
||||||
$: pickerY = paletteHeight * ((100 - v) / 100)
|
$: pickerY = paletteHeight * ((100 - v) / 100)
|
||||||
|
|
||||||
$: paletteGradient = `linear-gradient(to top, rgba(0, 0, 0, 1), transparent),
|
$: paletteGradient = `linear-gradient(to top, rgba(0, 0, 0, 1), transparent),
|
||||||
linear-gradient(to left, hsla(${h}, 100%, 50%, ${a}), rgba(255, 255, 255, ${a}))
|
linear-gradient(to left, hsla(${h}, 100%, 50%, ${a}), rgba(255, 255, 255, ${a}))
|
||||||
`;
|
`
|
||||||
$: style = `background: ${paletteGradient};`;
|
$: style = `background: ${paletteGradient};`
|
||||||
|
|
||||||
$: pickerStyle = `transform: translate(${pickerX - 8}px, ${pickerY - 8}px);`
|
$: pickerStyle = `transform: translate(${pickerX - 8}px, ${pickerY - 8}px);`
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<CheckedBackground width="100%">
|
||||||
|
<div
|
||||||
|
bind:this={palette}
|
||||||
|
bind:clientHeight={paletteHeight}
|
||||||
|
bind:clientWidth={paletteWidth}
|
||||||
|
on:click={handleClick}
|
||||||
|
class="palette"
|
||||||
|
{style}>
|
||||||
|
<div class="picker" style={pickerStyle} />
|
||||||
|
</div>
|
||||||
|
</CheckedBackground>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.palette {
|
.palette {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -55,9 +71,3 @@
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<CheckedBackground width="100%">
|
|
||||||
<div bind:this={palette} bind:clientHeight={paletteHeight} bind:clientWidth={paletteWidth} on:click={handleClick} class="palette" {style}>
|
|
||||||
<div class="picker" style={pickerStyle} />
|
|
||||||
</div>
|
|
||||||
</CheckedBackground>
|
|
||||||
|
|
|
@ -1,37 +1,62 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, createEventDispatcher } from "svelte";
|
import { onMount, createEventDispatcher } from "svelte"
|
||||||
import dragable from "./drag.js";
|
import dragable from "./drag.js"
|
||||||
|
|
||||||
export let value = 1;
|
export let value = 1
|
||||||
export let type = "hue";
|
export let type = "hue"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let slider;
|
let slider
|
||||||
let sliderWidth = 0;
|
let sliderWidth = 0
|
||||||
|
|
||||||
function onSliderChange(mouseX, isDrag = false) {
|
function onSliderChange(mouseX, isDrag = false) {
|
||||||
const { left, width } = slider.getBoundingClientRect();
|
const { left, width } = slider.getBoundingClientRect()
|
||||||
let clickPosition = mouseX - left;
|
let clickPosition = mouseX - left
|
||||||
|
|
||||||
let percentageClick = (clickPosition / sliderWidth).toFixed(2)
|
let percentageClick = (clickPosition / sliderWidth).toFixed(2)
|
||||||
|
|
||||||
if (percentageClick >= 0 && percentageClick <= 1) {
|
if (percentageClick >= 0 && percentageClick <= 1) {
|
||||||
let value =
|
let value = type === "hue" ? 360 * percentageClick : percentageClick
|
||||||
type === "hue"
|
|
||||||
? 360 * percentageClick
|
|
||||||
: percentageClick;
|
|
||||||
|
|
||||||
dispatch("change", {color: value, isDrag});
|
dispatch("change", { color: value, isDrag })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: thumbPosition =
|
$: thumbPosition =
|
||||||
type === "hue" ? sliderWidth * (value / 360) : sliderWidth * value;
|
type === "hue" ? sliderWidth * (value / 360) : sliderWidth * value
|
||||||
|
|
||||||
$: style = `transform: translateX(${thumbPosition - 6}px);`;
|
$: style = `transform: translateX(${thumbPosition - 6}px);`
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={slider}
|
||||||
|
bind:clientWidth={sliderWidth}
|
||||||
|
on:click={event => handleClick(event.clientX)}
|
||||||
|
class="color-format-slider"
|
||||||
|
class:hue={type === 'hue'}
|
||||||
|
class:alpha={type === 'alpha'}>
|
||||||
|
<div
|
||||||
|
use:dragable
|
||||||
|
on:drag={e => handleClick(e.detail)}
|
||||||
|
class="slider-thumb"
|
||||||
|
{style} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
bind:this={slider}
|
||||||
|
bind:clientWidth={sliderWidth}
|
||||||
|
on:click={event => onSliderChange(event.clientX)}
|
||||||
|
class="color-format-slider"
|
||||||
|
class:hue={type === 'hue'}
|
||||||
|
class:alpha={type === 'alpha'}>
|
||||||
|
<div
|
||||||
|
use:dragable
|
||||||
|
on:drag={e => onSliderChange(e.detail, true)}
|
||||||
|
on:dragend
|
||||||
|
class="slider-thumb"
|
||||||
|
{style} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.color-format-slider {
|
.color-format-slider {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -69,21 +94,6 @@
|
||||||
border: 1px solid #777676;
|
border: 1px solid #777676;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
cursor:grab;
|
cursor: grab;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div
|
|
||||||
bind:this={slider}
|
|
||||||
bind:clientWidth={sliderWidth}
|
|
||||||
on:click={event => onSliderChange(event.clientX)}
|
|
||||||
class="color-format-slider"
|
|
||||||
class:hue={type === 'hue'}
|
|
||||||
class:alpha={type === 'alpha'}>
|
|
||||||
<div
|
|
||||||
use:dragable
|
|
||||||
on:drag={e => onSliderChange(e.detail, true)}
|
|
||||||
on:dragend
|
|
||||||
class="slider-thumb"
|
|
||||||
{style} />
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,15 +1,35 @@
|
||||||
<script>
|
<script>
|
||||||
import {createEventDispatcher} from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from "svelte/transition"
|
||||||
import CheckedBackground from "./CheckedBackground.svelte"
|
import CheckedBackground from "./CheckedBackground.svelte"
|
||||||
|
|
||||||
export let hovered = false
|
export let hovered = false
|
||||||
export let color = "#fff"
|
export let color = "#fff"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="space">
|
||||||
|
<CheckedBackground borderRadius="6px">
|
||||||
|
<div
|
||||||
|
in:fade
|
||||||
|
class="swatch"
|
||||||
|
style={`background: ${color};`}
|
||||||
|
on:click|self
|
||||||
|
on:mouseover={() => (hovered = true)}
|
||||||
|
on:mouseleave={() => (hovered = false)}>
|
||||||
|
{#if hovered}
|
||||||
|
<div
|
||||||
|
in:fade
|
||||||
|
class="remove-icon"
|
||||||
|
on:click|self={() => dispatch('removeswatch')}>
|
||||||
|
<span on:click|self={() => dispatch('removeswatch')}>×</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</CheckedBackground>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.swatch {
|
.swatch {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -21,7 +41,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.space {
|
.space {
|
||||||
padding: 3px 5px;
|
padding: 3px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove-icon {
|
.remove-icon {
|
||||||
|
@ -29,7 +49,7 @@
|
||||||
right: 0;
|
right: 0;
|
||||||
top: -5px;
|
top: -5px;
|
||||||
right: -4px;
|
right: -4px;
|
||||||
width:10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #800000;
|
background-color: #800000;
|
||||||
|
@ -39,15 +59,3 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="space">
|
|
||||||
<CheckedBackground borderRadius="6px">
|
|
||||||
<div in:fade class="swatch" style={`background: ${color};`} on:click|self on:mouseover={() => hovered = true} on:mouseleave={() => hovered = false}>
|
|
||||||
{#if hovered}
|
|
||||||
<div in:fade class="remove-icon" on:click|self={()=> dispatch("removeswatch")}>
|
|
||||||
<span on:click|self={()=> dispatch("removeswatch")}>×</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</CheckedBackground>
|
|
||||||
</div>
|
|
|
@ -257,21 +257,21 @@
|
||||||
|
|
||||||
.menu li:not(.disabled) {
|
.menu li:not(.disabled) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--ink-light);
|
color: var(--grey-7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu li:not(.disabled):hover {
|
.menu li:not(.disabled):hover {
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
background-color: var(--grey-light);
|
background-color: var(--grey-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.disabled {
|
.disabled {
|
||||||
color: var(--grey-dark);
|
color: var(--grey-4);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hr-style {
|
.hr-style {
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
color: var(--grey-dark);
|
color: var(--grey-4);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { setContext, onMount } from "svelte"
|
import { setContext, onMount } from "svelte"
|
||||||
import PropsView from "./PropsView.svelte"
|
import PropsView from "./PropsView.svelte"
|
||||||
|
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import IconButton from "components/common/IconButton.svelte"
|
import IconButton from "components/common/IconButton.svelte"
|
||||||
import {
|
import {
|
||||||
|
@ -29,7 +30,10 @@
|
||||||
let selectedCategory = categories[0]
|
let selectedCategory = categories[0]
|
||||||
|
|
||||||
$: components = $store.components
|
$: components = $store.components
|
||||||
$: componentInstance = $store.currentComponentInfo
|
$: componentInstance =
|
||||||
|
$store.currentView !== "component"
|
||||||
|
? { ...$store.currentPreviewItem, ...$store.currentComponentInfo }
|
||||||
|
: $store.currentComponentInfo
|
||||||
$: componentDefinition = $store.components[componentInstance._component]
|
$: componentDefinition = $store.components[componentInstance._component]
|
||||||
$: componentPropDefinition =
|
$: componentPropDefinition =
|
||||||
flattenedPanel.find(
|
flattenedPanel.find(
|
||||||
|
@ -44,7 +48,22 @@
|
||||||
componentPropDefinition.properties[selectedCategory.value]
|
componentPropDefinition.properties[selectedCategory.value]
|
||||||
|
|
||||||
const onStyleChanged = store.setComponentStyle
|
const onStyleChanged = store.setComponentStyle
|
||||||
const onPropChanged = store.setComponentProp
|
|
||||||
|
function onPropChanged(key, value) {
|
||||||
|
if ($store.currentView !== "component") {
|
||||||
|
store.setPageOrScreenProp(key, value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
store.setComponentProp(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
$: isComponentOrScreen =
|
||||||
|
$store.currentView === "component" ||
|
||||||
|
$store.currentFrontEndType === "screen"
|
||||||
|
$: isNotScreenslot = componentInstance._component !== "##builtin/screenslot"
|
||||||
|
|
||||||
|
$: displayName =
|
||||||
|
isComponentOrScreen && componentInstance._instanceName && isNotScreenslot
|
||||||
|
|
||||||
function walkProps(component, action) {
|
function walkProps(component, action) {
|
||||||
action(component)
|
action(component)
|
||||||
|
@ -79,6 +98,12 @@
|
||||||
{categories}
|
{categories}
|
||||||
{selectedCategory} />
|
{selectedCategory} />
|
||||||
|
|
||||||
|
{#if displayName}
|
||||||
|
<div class="instance-name">
|
||||||
|
<strong>{componentInstance._instanceName}</strong>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="component-props-container">
|
<div class="component-props-container">
|
||||||
{#if selectedCategory.value === 'design'}
|
{#if selectedCategory.value === 'design'}
|
||||||
<DesignView {panelDefinition} {componentInstance} {onStyleChanged} />
|
<DesignView {panelDefinition} {componentInstance} {onStyleChanged} />
|
||||||
|
@ -87,8 +112,8 @@
|
||||||
{componentInstance}
|
{componentInstance}
|
||||||
{componentDefinition}
|
{componentDefinition}
|
||||||
{panelDefinition}
|
{panelDefinition}
|
||||||
|
displayNameField={displayName}
|
||||||
onChange={onPropChanged}
|
onChange={onPropChanged}
|
||||||
onScreenPropChange={store.setPageOrScreenProp}
|
|
||||||
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
||||||
{:else if selectedCategory.value === 'events'}
|
{:else if selectedCategory.value === 'events'}
|
||||||
<EventsEditor component={componentInstance} />
|
<EventsEditor component={componentInstance} />
|
||||||
|
@ -117,8 +142,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-props-container {
|
.component-props-container {
|
||||||
margin-top: 20px;
|
margin-top: 10px;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.instance-name {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0 0 1px 0;
|
border-width: 0 0 1px 0;
|
||||||
border-color: var(--lightslate);
|
border-color: var(--grey-1);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,12 +48,12 @@
|
||||||
|
|
||||||
.component > .title {
|
.component > .title {
|
||||||
font-size: 13pt;
|
font-size: 13pt;
|
||||||
color: var(--secondary100);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
.component > .description {
|
.component > .description {
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
color: var(--primary75);
|
color: var(--blue);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -22,48 +22,35 @@
|
||||||
trimChars(" "),
|
trimChars(" "),
|
||||||
])
|
])
|
||||||
|
|
||||||
const lastPartOfName = c => {
|
|
||||||
if (!c) return ""
|
|
||||||
const name = c.name ? c.name : c._component ? c._component : c
|
|
||||||
return last(name.split("/"))
|
|
||||||
}
|
|
||||||
|
|
||||||
const isComponentSelected = (current, comp) => current === comp
|
|
||||||
|
|
||||||
$: _screens = pipe(screens, [
|
|
||||||
map(c => ({ component: c, title: lastPartOfName(c) })),
|
|
||||||
sortBy("title"),
|
|
||||||
])
|
|
||||||
|
|
||||||
const changeScreen = screen => {
|
const changeScreen = screen => {
|
||||||
store.setCurrentScreen(screen.title)
|
store.setCurrentScreen(screen.props._instanceName)
|
||||||
$goto(`./:page/${screen.title}`)
|
$goto(`./:page/${screen.title}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
{#each _screens as screen}
|
{#each screens as screen}
|
||||||
<div
|
<div
|
||||||
class="budibase__nav-item component"
|
class="budibase__nav-item component"
|
||||||
class:selected={$store.currentComponentInfo._id === screen.component.props._id}
|
class:selected={$store.currentComponentInfo._id === screen.props._id}
|
||||||
on:click|stopPropagation={() => changeScreen(screen)}>
|
on:click|stopPropagation={() => changeScreen(screen)}>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="icon"
|
class="icon"
|
||||||
class:rotate={$store.currentPreviewItem.name !== screen.title}>
|
class:rotate={$store.currentPreviewItem.name !== screen.props._instanceName}>
|
||||||
{#if screen.component.props._children.length}
|
{#if screen.props._children.length}
|
||||||
<ArrowDownIcon />
|
<ArrowDownIcon />
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<i class="ri-artboard-2-fill icon" />
|
<i class="ri-artboard-2-fill icon" />
|
||||||
|
|
||||||
<span class="title">{screen.title}</span>
|
<span class="title">{screen.props._instanceName}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $store.currentPreviewItem.name === screen.title && screen.component.props._children}
|
{#if $store.currentPreviewItem.props._instanceName && $store.currentPreviewItem.props._instanceName === screen.props._instanceName && screen.props._children}
|
||||||
<ComponentsHierarchyChildren
|
<ComponentsHierarchyChildren
|
||||||
components={screen.component.props._children}
|
components={screen.props._children}
|
||||||
currentComponent={$store.currentComponentInfo} />
|
currentComponent={$store.currentComponentInfo} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -77,8 +64,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin-left: 10px;
|
margin-left: 14px;
|
||||||
margin-top: 2px;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
@ -87,9 +73,8 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
width: 20px;
|
width: 18px;
|
||||||
margin-top: 2px;
|
color: var(--grey-7);
|
||||||
color: var(--ink-light);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon:nth-of-type(2) {
|
.icon:nth-of-type(2) {
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
const get_name = s => (!s ? "" : last(s.split("/")))
|
const get_name = s => (!s ? "" : last(s.split("/")))
|
||||||
|
|
||||||
const get_capitalised_name = name => pipe(name, [get_name, capitalise])
|
const get_capitalised_name = name => pipe(name, [get_name, capitalise])
|
||||||
|
const isScreenslot = name => name === "##builtin/screenslot"
|
||||||
|
|
||||||
const selectComponent = component => {
|
const selectComponent = component => {
|
||||||
// Set current component
|
// Set current component
|
||||||
|
@ -39,10 +40,10 @@
|
||||||
<div
|
<div
|
||||||
class="budibase__nav-item item"
|
class="budibase__nav-item item"
|
||||||
class:selected={currentComponent === component}
|
class:selected={currentComponent === component}
|
||||||
style="padding-left: {level * 20 + 53}px">
|
style="padding-left: {level * 20 + 40}px">
|
||||||
<div class="nav-item">
|
<div class="nav-item">
|
||||||
<i class="icon ri-arrow-right-circle-fill" />
|
<i class="icon ri-arrow-right-circle-fill" />
|
||||||
{get_capitalised_name(component._component)}
|
{isScreenslot(component._component) ? 'Screenslot' : component._instanceName}
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<ComponentDropdownMenu {component} />
|
<ComponentDropdownMenu {component} />
|
||||||
|
@ -73,7 +74,7 @@
|
||||||
padding: 0px 5px 0px 15px;
|
padding: 0px 5px 0px 15px;
|
||||||
margin: auto 0px;
|
margin: auto 0px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
height: 35px;
|
height: 36px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +91,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.item:hover {
|
.item:hover {
|
||||||
background: var(--grey-light);
|
background: var(--grey-1);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.item:hover .actions {
|
.item:hover .actions {
|
||||||
|
@ -105,7 +106,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: var(--ink-light);
|
color: var(--grey-7);
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 20px 5px 20px 10px;
|
padding: 20px 5px 20px 10px;
|
||||||
border-left: solid 1px var(--grey);
|
border-left: solid 1px var(--grey-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.switcher {
|
.switcher {
|
||||||
|
@ -70,8 +70,8 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
color: var(--ink-lighter);
|
color: var(--grey-5);
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
{ value: "normal", text: "Normal" },
|
{ value: "normal", text: "Normal" },
|
||||||
{ value: "hover", text: "Hover" },
|
{ value: "hover", text: "Hover" },
|
||||||
{ value: "active", text: "Active" },
|
{ value: "active", text: "Active" },
|
||||||
{ value: "selected", text: "Selected" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
$: propertyGroupNames = Object.keys(panelDefinition)
|
$: propertyGroupNames = Object.keys(panelDefinition)
|
||||||
|
@ -31,26 +30,26 @@
|
||||||
<FlatButtonGroup value={selectedCategory} {buttonProps} {onChange} />
|
<FlatButtonGroup value={selectedCategory} {buttonProps} {onChange} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="positioned-wrapper">
|
<div class="positioned-wrapper">
|
||||||
<div class="design-view-property-groups">
|
<div class="design-view-property-groups">
|
||||||
{#if propertyGroupNames.length > 0}
|
{#if propertyGroupNames.length > 0}
|
||||||
{#each propertyGroupNames as groupName}
|
{#each propertyGroupNames as groupName}
|
||||||
<PropertyGroup
|
<PropertyGroup
|
||||||
name={groupName}
|
name={groupName}
|
||||||
properties={getProperties(groupName)}
|
properties={getProperties(groupName)}
|
||||||
styleCategory={selectedCategory}
|
styleCategory={selectedCategory}
|
||||||
{onStyleChanged}
|
{onStyleChanged}
|
||||||
{componentDefinition}
|
{componentDefinition}
|
||||||
{componentInstance} />
|
{componentInstance} />
|
||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="no-design">
|
<div class="no-design">
|
||||||
<span>This component does not have any design properties</span>
|
<span>This component does not have any design properties</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.design-view-container {
|
.design-view-container {
|
||||||
|
@ -64,7 +63,7 @@
|
||||||
flex: 0 0 50px;
|
flex: 0 0 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.positioned-wrapper{
|
.positioned-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
|
|
||||||
.title > div:nth-child(1) {
|
.title > div:nth-child(1) {
|
||||||
grid-column-start: name;
|
grid-column-start: name;
|
||||||
color: var(--secondary100);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title > div:nth-child(2) {
|
.title > div:nth-child(2) {
|
||||||
|
|
|
@ -165,7 +165,7 @@
|
||||||
padding: 30px 40px;
|
padding: 30px 40px;
|
||||||
border-bottom-left-radius: 5px;
|
border-bottom-left-radius: 5px;
|
||||||
border-bottom-right-radius: 50px;
|
border-bottom-right-radius: 50px;
|
||||||
background-color: var(--grey-light);
|
background-color: var(--grey-1);
|
||||||
}
|
}
|
||||||
.save {
|
.save {
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
|
|
||||||
.newevent {
|
.newevent {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid var(--grey-dark);
|
border: 1px solid var(--grey-4);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.newevent:hover {
|
.newevent:hover {
|
||||||
background: var(--grey-light);
|
background: var(--grey-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
@ -155,7 +155,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
color: var(--button-text);
|
color: var(--blue);
|
||||||
background: var(--background-button) !important;
|
background: var(--grey-1) !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import { EVENT_TYPE_MEMBER_NAME } from "components/common/eventHandlers"
|
import { EVENT_TYPE_MEMBER_NAME } from "components/common/eventHandlers"
|
||||||
import { store, workflowStore } from "builderStore"
|
import { store, workflowStore } from "builderStore"
|
||||||
import { ArrowDownIcon } from "components/common/Icons/"
|
import { ArrowDownIcon } from "components/common/Icons/"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let parameter
|
export let parameter
|
||||||
|
|
||||||
|
@ -25,6 +26,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{#if parameter.name === 'workflow'}
|
{#if parameter.name === 'workflow'}
|
||||||
<Select on:change bind:value={parameter.value}>
|
<Select on:change bind:value={parameter.value}>
|
||||||
|
<option value="" />
|
||||||
{#each $workflowStore.workflows.filter(wf => wf.live) as workflow}
|
{#each $workflowStore.workflows.filter(wf => wf.live) as workflow}
|
||||||
<option value={workflow._id}>{workflow.name}</option>
|
<option value={workflow._id}>{workflow.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -3,20 +3,13 @@
|
||||||
export let value = ""
|
export let value = ""
|
||||||
export let text = ""
|
export let text = ""
|
||||||
export let icon = ""
|
export let icon = ""
|
||||||
export let padding = "8px 5px;"
|
|
||||||
export let onClick = value => {}
|
export let onClick = value => {}
|
||||||
export let selected = false
|
export let selected = false
|
||||||
export let fontWeight = ""
|
|
||||||
|
|
||||||
$: style = buildStyle({ padding, fontWeight })
|
|
||||||
$: useIcon = !!icon
|
$: useIcon = !!icon
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="flatbutton" class:selected on:click={() => onClick(value || text)}>
|
||||||
class="flatbutton"
|
|
||||||
{style}
|
|
||||||
class:selected
|
|
||||||
on:click={() => onClick(value || text)}>
|
|
||||||
{#if useIcon}
|
{#if useIcon}
|
||||||
<i class={icon} />
|
<i class={icon} />
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -29,25 +22,27 @@
|
||||||
<style>
|
<style>
|
||||||
.flatbutton {
|
.flatbutton {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
max-height: 36px;
|
||||||
padding: 8px 2px;
|
padding: 8px 2px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
color: var(--ink-light);
|
color: var(--grey-7);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-family: Roboto;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
margin-left: 5px;
|
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
background: var(--ink-light);
|
background: var(--grey-3);
|
||||||
color: #ffffff;
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 20px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
<style>
|
<style>
|
||||||
.flatbutton-group {
|
.flatbutton-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-container {
|
.button-container {
|
||||||
|
|
|
@ -14,10 +14,7 @@
|
||||||
|
|
||||||
<PagesList />
|
<PagesList />
|
||||||
|
|
||||||
<button class="newscreen" on:click={newScreen}>
|
<button class="newscreen" on:click={newScreen}>Create New Screen</button>
|
||||||
<i class="icon ri-add-circle-fill" />
|
|
||||||
Create New Screen
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<PageLayout layout={$store.pages[$store.currentPageName]} />
|
<PageLayout layout={$store.pages[$store.currentPageName]} />
|
||||||
|
|
||||||
|
@ -30,23 +27,26 @@
|
||||||
<style>
|
<style>
|
||||||
.newscreen {
|
.newscreen {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid var(--grey-dark);
|
border: 1px solid var(--purple);
|
||||||
border-radius: 3px;
|
border-radius: 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 36px;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
margin: 20px 0px 12px 0px;
|
margin: 20px 0px 12px 0px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: white;
|
background: var(--purple);
|
||||||
color: var(--ink);
|
color: var(--white);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 2ms;
|
transition: all 3ms;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.newscreen:hover {
|
.newscreen:hover {
|
||||||
background: var(--grey-light);
|
background: var(--purple-light);
|
||||||
|
color: var(--purple);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue