custom notifications, models + fields keyed by ID
This commit is contained in:
parent
e1b88e6620
commit
cde00356b3
|
@ -50,7 +50,6 @@
|
|||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@beyonk/svelte-notifications": "^2.0.3",
|
||||
"@budibase/bbui": "^1.8.0",
|
||||
"@budibase/client": "^0.0.32",
|
||||
"@nx-js/compiler-util": "^2.0.0",
|
||||
|
|
|
@ -4,30 +4,11 @@
|
|||
import { Router, basepath } from "@sveltech/routify"
|
||||
import { routes } from "../routify/routes"
|
||||
import { store, initialise } from "builderStore"
|
||||
import AppNotification, {
|
||||
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)
|
||||
})
|
||||
import NotificationDisplay from "components/common/Notification.svelte";
|
||||
|
||||
$basepath = "/_builder"
|
||||
</script>
|
||||
|
||||
<AppNotification />
|
||||
|
||||
<!-- svelte-notifications -->
|
||||
<NotificationDisplay />
|
||||
|
||||
<Router {routes} />
|
||||
|
|
|
@ -62,16 +62,6 @@ export const getBackendUiStore = () => {
|
|||
save: async ({ 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 response = await api.post(SAVE_MODEL_URL, updatedModel)
|
||||
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 = []
|
||||
|
||||
let records = []
|
||||
let model = {}
|
||||
|
||||
let linkedRecords = new Set(linked)
|
||||
|
||||
$: linked = [...linkedRecords]
|
||||
$: FIELDS_TO_HIDE = [
|
||||
"modelId",
|
||||
"type",
|
||||
"_id",
|
||||
"_rev",
|
||||
$backendUiStore.selectedModel.name,
|
||||
modelId
|
||||
]
|
||||
$: 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()
|
||||
}
|
||||
|
||||
|
@ -49,9 +47,9 @@
|
|||
{#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(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">
|
||||
<span>{key}</span>
|
||||
<span>{model.schema[key].name}</span>
|
||||
<p>{record[key]}</p>
|
||||
</div>
|
||||
{/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"
|
||||
|
||||
export let ids = []
|
||||
export let header
|
||||
export let field
|
||||
|
||||
let records = []
|
||||
let open = false
|
||||
let model
|
||||
|
||||
$: FIELDS_TO_HIDE = [
|
||||
"modelId",
|
||||
"type",
|
||||
"_id",
|
||||
"_rev",
|
||||
$backendUiStore.selectedModel._id,
|
||||
$backendUiStore.selectedModel.name,
|
||||
]
|
||||
$: 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()
|
||||
|
@ -42,15 +38,15 @@
|
|||
{#if open}
|
||||
<div class="popover" transition:fade>
|
||||
<header>
|
||||
<h3>{header}</h3>
|
||||
<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(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">
|
||||
<span>{key}</span>
|
||||
<span>{model.schema[key].name}</span>
|
||||
<p>{record[key]}</p>
|
||||
</div>
|
||||
{/each}
|
||||
|
@ -62,6 +58,10 @@
|
|||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
display: relative;
|
||||
}
|
||||
|
||||
header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
|
|
@ -133,7 +133,7 @@
|
|||
{#each headers as header}
|
||||
<td>
|
||||
{#if schema[header].type === "link"}
|
||||
<LinkedRecord header={schema[header].name} ids={row[header]} />
|
||||
<LinkedRecord field={schema[header]} ids={row[header]} />
|
||||
{:else}
|
||||
{row[header]}
|
||||
{/if}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
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 { Button } from "@budibase/bbui"
|
||||
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
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 * as api from "../api"
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { getContext, onMount } from "svelte"
|
||||
import { Button, Switcher } from "@budibase/bbui"
|
||||
import { notifier } from "@beyonk/svelte-notifications"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import api from "builderStore/api"
|
||||
import ModelFieldEditor from "./ModelFieldEditor.svelte"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
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"
|
||||
|
||||
export let onClosed
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { fade } from "svelte/transition"
|
||||
import { onMount, getContext } from "svelte"
|
||||
import { backendUiStore, workflowStore } from "builderStore"
|
||||
import { notifier } from "@beyonk/svelte-notifications"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte"
|
||||
import DeleteWorkflowModal from "./DeleteWorkflowModal.svelte"
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import { workflowStore, backendUiStore } from "builderStore"
|
||||
import { notifier } from "@beyonk/svelte-notifications"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import Flowchart from "./flowchart/FlowChart.svelte"
|
||||
|
||||
let selectedWorkflow
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
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"
|
||||
|
||||
export let onClosed
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import Modal from "svelte-simple-modal"
|
||||
import { notifier } from "@beyonk/svelte-notifications"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { onMount, getContext } from "svelte"
|
||||
import { backendUiStore, workflowStore } from "builderStore"
|
||||
import CreateWorkflowModal from "./CreateWorkflowModal.svelte"
|
||||
|
|
Loading…
Reference in New Issue