custom notifications, models + fields keyed by ID
This commit is contained in:
parent
e1b88e6620
commit
cde00356b3
|
@ -50,7 +50,6 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@beyonk/svelte-notifications": "^2.0.3",
|
|
||||||
"@budibase/bbui": "^1.8.0",
|
"@budibase/bbui": "^1.8.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",
|
||||||
|
|
|
@ -4,30 +4,11 @@
|
||||||
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.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} />
|
||||||
|
|
|
@ -62,16 +62,6 @@ export const getBackendUiStore = () => {
|
||||||
save: async ({ model }) => {
|
save: async ({ model }) => {
|
||||||
const updatedModel = cloneDeep(model)
|
const updatedModel = cloneDeep(model)
|
||||||
|
|
||||||
// // TODO: refactor
|
|
||||||
// for (let key in updatedModel.schema) {
|
|
||||||
// const field = updatedModel.schema[key]
|
|
||||||
// // TODO: use IDs
|
|
||||||
// if (field.name && field.name !== key) {
|
|
||||||
// updatedModel.schema[field.name] = field
|
|
||||||
// delete updatedModel.schema[key]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
const SAVE_MODEL_URL = `/api/models`
|
const SAVE_MODEL_URL = `/api/models`
|
||||||
const response = await api.post(SAVE_MODEL_URL, updatedModel)
|
const response = await api.post(SAVE_MODEL_URL, updatedModel)
|
||||||
const savedModel = await response.json()
|
const savedModel = await response.json()
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
|
export const notificationStore = writable()
|
||||||
|
|
||||||
|
export function send(message, type = 'default', timeout) {
|
||||||
|
notificationStore.set({ type, message, timeout })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const notifier = {
|
||||||
|
danger: msg => send(msg, "danger"),
|
||||||
|
warning: msg => send(msg, "warning"),
|
||||||
|
info: msg => send(msg, "info"),
|
||||||
|
success: msg => send(msg, "success")
|
||||||
|
}
|
|
@ -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>
|
|
|
@ -8,22 +8,20 @@
|
||||||
export let linked = []
|
export let linked = []
|
||||||
|
|
||||||
let records = []
|
let records = []
|
||||||
|
let model = {}
|
||||||
|
|
||||||
let linkedRecords = new Set(linked)
|
let linkedRecords = new Set(linked)
|
||||||
|
|
||||||
$: linked = [...linkedRecords]
|
$: linked = [...linkedRecords]
|
||||||
$: FIELDS_TO_HIDE = [
|
$: FIELDS_TO_HIDE = [$backendUiStore.selectedModel._id]
|
||||||
"modelId",
|
$: schema = $backendUiStore.selectedModel.schema
|
||||||
"type",
|
|
||||||
"_id",
|
|
||||||
"_rev",
|
|
||||||
$backendUiStore.selectedModel.name,
|
|
||||||
modelId
|
|
||||||
]
|
|
||||||
|
|
||||||
async function fetchRecords() {
|
async function fetchRecords() {
|
||||||
const FETCH_RECORDS_URL = `/api/${modelId}/records`
|
const FETCH_RECORDS_URL = `/api/${modelId}/records`
|
||||||
const response = await api.get(FETCH_RECORDS_URL)
|
const response = await api.get(FETCH_RECORDS_URL)
|
||||||
|
const modelResponse = await api.get(`/api/models/${modelId}`)
|
||||||
|
|
||||||
|
model = await modelResponse.json()
|
||||||
records = await response.json()
|
records = await response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,9 +47,9 @@
|
||||||
{#each records as record}
|
{#each records as record}
|
||||||
<div class="linked-record" on:click={() => linkRecord(record._id)}>
|
<div class="linked-record" on:click={() => linkRecord(record._id)}>
|
||||||
<div class="fields" class:selected={linkedRecords.has(record._id)}>
|
<div class="fields" class:selected={linkedRecords.has(record._id)}>
|
||||||
{#each Object.keys(record).filter(key => !FIELDS_TO_HIDE.includes(key)) as key}
|
{#each Object.keys(model.schema).filter(key => !FIELDS_TO_HIDE.includes(key)) as key}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span>{key}</span>
|
<span>{model.schema[key].name}</span>
|
||||||
<p>{record[key]}</p>
|
<p>{record[key]}</p>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
<script>
|
||||||
|
import { notificationStore } from "builderStore/store/notifications"
|
||||||
|
import { onMount, onDestroy } from "svelte"
|
||||||
|
import { fade } from "svelte/transition"
|
||||||
|
|
||||||
|
export let themes = {
|
||||||
|
danger: "#bb2124",
|
||||||
|
success: "#22bb33",
|
||||||
|
warning: "#f0ad4e",
|
||||||
|
info: "#5bc0de",
|
||||||
|
default: "#aaaaaa",
|
||||||
|
}
|
||||||
|
|
||||||
|
export let timeout = 3000
|
||||||
|
|
||||||
|
let count = 0
|
||||||
|
let notifications = []
|
||||||
|
let unsubscribe
|
||||||
|
|
||||||
|
function createToast(msg, theme, to) {
|
||||||
|
const background = themes[theme] || themes["default"]
|
||||||
|
const id = count
|
||||||
|
notifications = [
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
msg,
|
||||||
|
background,
|
||||||
|
timeout: to || timeout,
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
...notifications,
|
||||||
|
]
|
||||||
|
setTimeout(() => removeNotification(id), to || timeout)
|
||||||
|
count = count + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe = notificationStore.subscribe(value => {
|
||||||
|
if (!value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
createToast(value.message, value.type, value.timeout)
|
||||||
|
notificationStore.set()
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(unsubscribe)
|
||||||
|
|
||||||
|
function removeNotification(id) {
|
||||||
|
notifications = notifications.filter(t => t.id != id)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ul class="notifications">
|
||||||
|
{#each notifications as toast (toast.id)}
|
||||||
|
<li class="toast" style="background: {toast.background};" out:fade>
|
||||||
|
<div class="content">{toast.msg}</div>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.notifications) > .toast {
|
||||||
|
position: relative;
|
||||||
|
margin: 10px;
|
||||||
|
min-width: 40vw;
|
||||||
|
position: relative;
|
||||||
|
animation: animate-in 350ms forwards;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.notifications) > .toast > .content {
|
||||||
|
padding: 10px;
|
||||||
|
display: block;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.notifications) > .toast:before,
|
||||||
|
:global(.notifications) > .toast:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
top: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
border-radius: 100px / 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.notifications) > .toast:after {
|
||||||
|
right: 10px;
|
||||||
|
left: auto;
|
||||||
|
transform: skew(8deg) rotate(3deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animate-in {
|
||||||
|
0% {
|
||||||
|
width: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.15) translateY(20px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
width: 40vw;
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -5,25 +5,21 @@
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
|
||||||
export let ids = []
|
export let ids = []
|
||||||
export let header
|
export let field
|
||||||
|
|
||||||
let records = []
|
let records = []
|
||||||
let open = false
|
let open = false
|
||||||
|
let model
|
||||||
|
|
||||||
$: FIELDS_TO_HIDE = [
|
$: FIELDS_TO_HIDE = [$backendUiStore.selectedModel._id, field.modelId]
|
||||||
"modelId",
|
|
||||||
"type",
|
|
||||||
"_id",
|
|
||||||
"_rev",
|
|
||||||
$backendUiStore.selectedModel._id,
|
|
||||||
$backendUiStore.selectedModel.name,
|
|
||||||
]
|
|
||||||
|
|
||||||
async function fetchRecords() {
|
async function fetchRecords() {
|
||||||
const response = await api.post("/api/records/search", {
|
const response = await api.post("/api/records/search", {
|
||||||
keys: ids,
|
keys: ids,
|
||||||
})
|
})
|
||||||
|
const modelResponse = await api.get(`/api/models/${field.modelId}`)
|
||||||
records = await response.json()
|
records = await response.json()
|
||||||
|
model = await modelResponse.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
$: ids && fetchRecords()
|
$: ids && fetchRecords()
|
||||||
|
@ -42,15 +38,15 @@
|
||||||
{#if open}
|
{#if open}
|
||||||
<div class="popover" transition:fade>
|
<div class="popover" transition:fade>
|
||||||
<header>
|
<header>
|
||||||
<h3>{header}</h3>
|
<h3>{field.name}</h3>
|
||||||
<i class="ri-close-circle-fill" on:click={toggleOpen} />
|
<i class="ri-close-circle-fill" on:click={toggleOpen} />
|
||||||
</header>
|
</header>
|
||||||
{#each records as record}
|
{#each records as record}
|
||||||
<div class="linked-record">
|
<div class="linked-record">
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
{#each Object.keys(record).filter(key => !FIELDS_TO_HIDE.includes(key)) as key}
|
{#each Object.keys(model.schema).filter(key => !FIELDS_TO_HIDE.includes(key)) as key}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span>{key}</span>
|
<span>{model.schema[key].name}</span>
|
||||||
<p>{record[key]}</p>
|
<p>{record[key]}</p>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -62,6 +58,10 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
section {
|
||||||
|
display: relative;
|
||||||
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -133,7 +133,7 @@
|
||||||
{#each headers as header}
|
{#each headers as header}
|
||||||
<td>
|
<td>
|
||||||
{#if schema[header].type === "link"}
|
{#if schema[header].type === "link"}
|
||||||
<LinkedRecord header={schema[header].name} ids={row[header]} />
|
<LinkedRecord field={schema[header]} ids={row[header]} />
|
||||||
{:else}
|
{:else}
|
||||||
{row[header]}
|
{row[header]}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
import { notifier } from "@beyonk/svelte-notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import { compose, map, get, flatten } from "lodash/fp"
|
import { compose, map, get, flatten } from "lodash/fp"
|
||||||
import { Button } from "@budibase/bbui"
|
import { Button } from "@budibase/bbui"
|
||||||
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
import { notifier } from "@beyonk/svelte-notifications"
|
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"
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
import { getContext, onMount } from "svelte"
|
||||||
import { Button, Switcher } from "@budibase/bbui"
|
import { Button, Switcher } from "@budibase/bbui"
|
||||||
import { notifier } from "@beyonk/svelte-notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import ModelFieldEditor from "./ModelFieldEditor.svelte"
|
import ModelFieldEditor from "./ModelFieldEditor.svelte"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, backendUiStore, workflowStore } from "builderStore"
|
import { store, backendUiStore, workflowStore } from "builderStore"
|
||||||
import { notifier } from "@beyonk/svelte-notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
|
|
||||||
export let onClosed
|
export let onClosed
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import { onMount, getContext } from "svelte"
|
import { onMount, getContext } from "svelte"
|
||||||
import { backendUiStore, workflowStore } from "builderStore"
|
import { backendUiStore, workflowStore } from "builderStore"
|
||||||
import { notifier } from "@beyonk/svelte-notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte"
|
import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte"
|
||||||
import DeleteWorkflowModal from "./DeleteWorkflowModal.svelte"
|
import DeleteWorkflowModal from "./DeleteWorkflowModal.svelte"
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { workflowStore, backendUiStore } from "builderStore"
|
import { workflowStore, backendUiStore } from "builderStore"
|
||||||
import { notifier } from "@beyonk/svelte-notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import Flowchart from "./flowchart/FlowChart.svelte"
|
import Flowchart from "./flowchart/FlowChart.svelte"
|
||||||
|
|
||||||
let selectedWorkflow
|
let selectedWorkflow
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, backendUiStore, workflowStore } from "builderStore"
|
import { store, backendUiStore, workflowStore } from "builderStore"
|
||||||
import { notifier } from "@beyonk/svelte-notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
|
|
||||||
export let onClosed
|
export let onClosed
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import Modal from "svelte-simple-modal"
|
import Modal from "svelte-simple-modal"
|
||||||
import { notifier } from "@beyonk/svelte-notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import { onMount, getContext } from "svelte"
|
import { onMount, getContext } from "svelte"
|
||||||
import { backendUiStore, workflowStore } from "builderStore"
|
import { backendUiStore, workflowStore } from "builderStore"
|
||||||
import CreateWorkflowModal from "./CreateWorkflowModal.svelte"
|
import CreateWorkflowModal from "./CreateWorkflowModal.svelte"
|
||||||
|
|
Loading…
Reference in New Issue