custom notifications, models + fields keyed by ID

This commit is contained in:
Martin McKeaveney 2020-06-24 12:41:26 +01:00
parent e1b88e6620
commit cde00356b3
17 changed files with 163 additions and 139 deletions

View File

@ -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",

View File

@ -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} />

View File

@ -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()

View File

@ -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")
}

View File

@ -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>

View File

@ -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}

View File

@ -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>

View File

@ -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;

View File

@ -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}

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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"