refactor edit and edges
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
<script>
|
||||
export let field;
|
||||
export let id;
|
||||
</script>
|
||||
|
||||
<div class="mb-1">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<label for={id} class="form-label"
|
||||
>{field.label}</label
|
||||
>
|
||||
{#if field.help}
|
||||
<small class=" text-primary opacity-50">{field.help}</small>
|
||||
{/if}
|
||||
</div>
|
||||
<span
|
||||
tabindex="-1"
|
||||
class="text-decoration-none"
|
||||
><code class="text-primary opacity-50">{field.name}</code>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,122 @@
|
||||
<script>
|
||||
import {afterUpdate, createEventDispatcher, onMount} from "svelte";
|
||||
import {isEqual} from "../deepEquality.js";
|
||||
import ContentTabs from "../ContentTabs.svelte"
|
||||
import ErrorAlert from "../../common/ErrorAlert.svelte"
|
||||
import FormField from "./FormField.svelte";
|
||||
import ReferenceField from "./ReferenceField.svelte";
|
||||
import SaveButtons from "./SaveButtons.svelte";
|
||||
import EditHeader from "../EditHeader.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
function save(){
|
||||
dispatch("save");
|
||||
|
||||
}
|
||||
|
||||
export let title = null;
|
||||
export let schema;
|
||||
export let record;
|
||||
export let data;
|
||||
export let status = null;
|
||||
export let graph = [];
|
||||
export let isCreateMode;
|
||||
let originalContent;
|
||||
let activeContentTab = "";
|
||||
$: hasUnsavedData = false;
|
||||
$: validationErrors = null;
|
||||
$: errorMessage = validationErrors
|
||||
? `Record submission failed. ${
|
||||
Object.entries(validationErrors).length
|
||||
} error(s)`
|
||||
: null;
|
||||
|
||||
|
||||
export function setOriginalData(){
|
||||
originalContent = {
|
||||
data: JSON.parse(JSON.stringify(data)),
|
||||
status: status,
|
||||
edges: JSON.parse(JSON.stringify(graph.map(r => r.edge))),
|
||||
};
|
||||
hasUnsavedData = checkUnsavedData();
|
||||
|
||||
}
|
||||
|
||||
onMount(()=>{
|
||||
setOriginalData()
|
||||
})
|
||||
|
||||
|
||||
afterUpdate(() => {
|
||||
hasUnsavedData = checkUnsavedData();
|
||||
});
|
||||
|
||||
function beforeUnload(e) {
|
||||
// Cancel the event as stated by the standard.
|
||||
// e.preventDefault();
|
||||
// console.log(hasUnsavedData);
|
||||
if (hasUnsavedData) {
|
||||
return (e.returnValue =
|
||||
"You have unsaved changes. Are you sure you want to exit?");
|
||||
}
|
||||
// Chrome requires returnValue to be set.
|
||||
// e.returnValue = "";
|
||||
delete e["returnValue"];
|
||||
// more compatibility
|
||||
// return true;
|
||||
return "...";
|
||||
}
|
||||
|
||||
function checkUnsavedData() {
|
||||
|
||||
if (isCreateMode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !isEqual(originalContent, {
|
||||
data: data,
|
||||
status: status,
|
||||
edges: graph.map(r => r.edge),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<svelte:window on:beforeunload={beforeUnload}/>
|
||||
|
||||
<div >
|
||||
<EditHeader {schema} {record} {isCreateMode} {title}/>
|
||||
|
||||
<div class=" mt-4" style="margin-bottom:150px">
|
||||
<SaveButtons on:save={save} status={status} {hasUnsavedData} />
|
||||
<ErrorAlert message={errorMessage}/>
|
||||
<ContentTabs
|
||||
{schema}
|
||||
bind:active={activeContentTab}
|
||||
/>
|
||||
|
||||
<!-- <fieldset disabled="disabled"> -->
|
||||
{#each schema.fields as field (field.name)}
|
||||
{#if activeContentTab === field.group}
|
||||
{#if ["reference", "file"].includes(field.info.name)}
|
||||
<ReferenceField
|
||||
bind:graph={graph}
|
||||
{field}
|
||||
{record}
|
||||
/>
|
||||
{:else}
|
||||
<FormField
|
||||
bind:data={data}
|
||||
{field}
|
||||
{validationErrors}
|
||||
{isCreateMode}
|
||||
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
<!-- </fieldset> -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,50 @@
|
||||
<script>
|
||||
import Text from "./fields/Text.svelte";
|
||||
import Slug from "./fields/Slug.svelte";
|
||||
import Color from "./fields/Color.svelte";
|
||||
import Checkbox from "./fields/Checkbox.svelte";
|
||||
import Number from "./fields/Number.svelte";
|
||||
import Date from "./fields/Date.svelte";
|
||||
import UUID from "./fields/UUID.svelte";
|
||||
import Textarea from "./fields/Textarea.svelte";
|
||||
import Datetime from "./fields/Datetime.svelte";
|
||||
import RichEditor from "./fields/RichEditor.svelte";
|
||||
import Json from "./fields/JSON.svelte";
|
||||
import Markdown from "./fields/Markdown.svelte";
|
||||
import FieldHeader from "./FieldHeader.svelte";
|
||||
|
||||
const formElements = {
|
||||
text: Text,
|
||||
slug: Slug,
|
||||
textarea: Textarea,
|
||||
rich: RichEditor,
|
||||
color: Color,
|
||||
checkbox: Checkbox,
|
||||
number: Number,
|
||||
date: Date,
|
||||
datetime: Datetime,
|
||||
uuid: UUID,
|
||||
json: Json,
|
||||
markdown: Markdown,
|
||||
};
|
||||
|
||||
export let field;
|
||||
export let data;
|
||||
export let validationErrors;
|
||||
export let isCreateMode;
|
||||
let formElement = formElements[field.info.name];
|
||||
const uniqueId = `field-${field.name}-id`;
|
||||
</script>
|
||||
|
||||
<div class="card editor-field">
|
||||
<FieldHeader {field} id={uniqueId}/>
|
||||
|
||||
<svelte:component
|
||||
this={formElement}
|
||||
bind:value={data[field.name]}
|
||||
{field}
|
||||
{validationErrors}
|
||||
{isCreateMode}
|
||||
id={uniqueId}
|
||||
/>
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script>
|
||||
|
||||
import File from "./references/Reference.svelte";
|
||||
import FieldHeader from "./FieldHeader.svelte";
|
||||
|
||||
export let field;
|
||||
export let record;
|
||||
export let graph;
|
||||
// export let validationErrors;
|
||||
// export let isCreateMode;
|
||||
const id = `field-${field.name}-${record.id}`;
|
||||
</script>
|
||||
|
||||
<div class="card editor-field">
|
||||
<FieldHeader {field} {id}/>
|
||||
<File bind:graph {record} {field} />
|
||||
</div>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script>
|
||||
import StatusSelect from "./StatusSelect.svelte"
|
||||
import {createEventDispatcher} from "svelte";
|
||||
export let status;
|
||||
export let isCreateMode;
|
||||
export let hasUnsavedData;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
function save(){
|
||||
dispatch("save");
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="record-status-bar">
|
||||
<div
|
||||
class="d-flex mt-3 mb-3 align-items-center justify-content-center"
|
||||
>
|
||||
|
||||
<StatusSelect bind:status={status}/>
|
||||
{#if isCreateMode}
|
||||
<button
|
||||
class="ms-2 btn btn-primary btn-spinner"
|
||||
on:click={save}
|
||||
>
|
||||
<span
|
||||
class="spinner-border spinner-border-sm"
|
||||
role="status"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Create
|
||||
</button>
|
||||
{:else if hasUnsavedData}
|
||||
<button
|
||||
type="button"
|
||||
class="ms-2 btn btn-primary btn-spinner"
|
||||
on:click={save}
|
||||
>
|
||||
<span
|
||||
class="spinner-border spinner-border-sm"
|
||||
role="status"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Save
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script>
|
||||
import {getStatus, getStatusList} from "../StatusText.js";
|
||||
|
||||
export let status = "draft";
|
||||
let dropdown;
|
||||
$: currentStatus = getStatus(status);
|
||||
const statusList = Object.values(getStatusList());
|
||||
|
||||
function updateStatus(e, statusValue) {
|
||||
// e.preventDefault();
|
||||
status = statusValue;
|
||||
dropdown.click();
|
||||
}
|
||||
</script>
|
||||
{#if status}
|
||||
<!-- Example split danger button -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="btn-group dropup">
|
||||
<button type="button" class="btn btn-{currentStatus.bg}"
|
||||
>{currentStatus.text}</button
|
||||
>
|
||||
<button
|
||||
bind:this={dropdown}
|
||||
type="button"
|
||||
class="btn btn-{currentStatus.bg} dropdown-toggle dropdown-toggle-split"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<div class="dropdown-header">Change status to</div>
|
||||
{#each statusList as astatus}
|
||||
{#if astatus.value !== status}
|
||||
<button
|
||||
type="button"
|
||||
class="dropdown-item my-2 rounded w-100 bg-{astatus.bg} text-{astatus.color}"
|
||||
on:click={(e) => updateStatus(e, astatus.value)}
|
||||
>
|
||||
{astatus.text}
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,56 @@
|
||||
<script>
|
||||
import { getErrorMessage } from "../form.js";
|
||||
export let id;
|
||||
export let field;
|
||||
export let value;
|
||||
export let isCreateMode;
|
||||
export let validationErrors;
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
class:is-invalid={errorMessage}
|
||||
bind:group={value}
|
||||
id="{id}-1"
|
||||
value={true}
|
||||
disabled={field.readonly && !isCreateMode}
|
||||
/>
|
||||
<label class="form-check-label" for="{id}-1">Yes</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
id="{id}-2"
|
||||
class:is-invalid={errorMessage}
|
||||
bind:group={value}
|
||||
value={false}
|
||||
disabled={field.readonly && !isCreateMode}
|
||||
/>
|
||||
<label class="form-check-label" for="{id}-2">No</label>
|
||||
</div>
|
||||
{#if field.nullable}
|
||||
<div class="form-check form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
class:is-invalid={errorMessage}
|
||||
id="{id}-3"
|
||||
type="radio"
|
||||
bind:group={value}
|
||||
value={null}
|
||||
disabled={field.readonly && !isCreateMode}
|
||||
/>
|
||||
<label class="form-check-label" for="{id}-3">Don't Know</label>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,37 @@
|
||||
<script>
|
||||
import { getErrorMessage } from "../form.js";
|
||||
export let field;
|
||||
export let value;
|
||||
export let isCreateMode;
|
||||
export let validationErrors;
|
||||
export let id;
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
</script>
|
||||
|
||||
<div class="mb-0">
|
||||
<div class="input-group ">
|
||||
<div style="width:64px;">
|
||||
<input
|
||||
type="color"
|
||||
{id}
|
||||
class="form-control form-control-color"
|
||||
disabled={field.readonly && !isCreateMode}
|
||||
bind:value
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
class:is-invalid={errorMessage}
|
||||
{id}
|
||||
class="form-control"
|
||||
bind:value
|
||||
readonly={field.readonly && !isCreateMode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,70 @@
|
||||
<script>
|
||||
import {getContext} from "svelte";
|
||||
import {debounce} from "lodash";
|
||||
import {previewTitle} from "../../Preview.js";
|
||||
|
||||
const channel = getContext("channel");
|
||||
export let field;
|
||||
|
||||
export let value;
|
||||
export let search;
|
||||
$: options = [];
|
||||
export const update = debounce((e) => {
|
||||
axios
|
||||
.get("/records/suggestions", {
|
||||
params: {
|
||||
schema: field.optionsFrom,
|
||||
field: field.optionsField,
|
||||
value: search,
|
||||
ui: field.ui,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
options = response.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
}, 500);
|
||||
|
||||
function select(e, option) {
|
||||
e.preventDefault();
|
||||
value = option.data[field.optionsField];
|
||||
search = "";
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if field.optionsFrom}
|
||||
{#each options as option (option.id)}
|
||||
<div
|
||||
on:click={(e) => select(e, option)}
|
||||
on:keypress={(e) => select(e, option)}
|
||||
>
|
||||
<span class="dropdown-item">
|
||||
{previewTitle(channel.schemas, option)}
|
||||
<small class="text-muted "
|
||||
>{option.data[field.optionsField]}</small
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
{#if search && field.optionsSuggest}
|
||||
<div
|
||||
on:click={(e) => {
|
||||
value = search;
|
||||
search = "";
|
||||
}}
|
||||
on:keypress={(e) => {
|
||||
value = search;
|
||||
search = "";
|
||||
}}
|
||||
>
|
||||
<span class="dropdown-item">
|
||||
Add "{search}"
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
No results
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
@@ -0,0 +1,112 @@
|
||||
<script>
|
||||
import Datalist from "./Datalist.svelte";
|
||||
import {onMount} from "svelte";
|
||||
import flatpickr from "flatpickr";
|
||||
import "flatpickr/dist/flatpickr.css";
|
||||
import "flatpickr/dist/themes/light.css";
|
||||
import { getErrorMessage } from "../form.js";
|
||||
import Icon from "../../../common/Icon.svelte";
|
||||
|
||||
export let field;
|
||||
export let value;
|
||||
export let id;
|
||||
export let isCreateMode;
|
||||
export let validationErrors;
|
||||
$: search = "";
|
||||
$: listMode = field.optionsFrom && !(field.readonly && !isCreateMode);
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
let list;
|
||||
let pickerInput;
|
||||
let pickerInstance;
|
||||
let flatpickrOptions = {
|
||||
enableTime: false,
|
||||
allowInput: true,
|
||||
dateFormat: "Y-m-d",
|
||||
};
|
||||
|
||||
if (field.min) {
|
||||
flatpickrOptions.minDate = field.min;
|
||||
}
|
||||
|
||||
if (field.max) {
|
||||
flatpickrOptions.maxDate = field.max;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!field.readonly || isCreateMode) {
|
||||
if (listMode) {
|
||||
flatpickrOptions.clickOpens = false;
|
||||
}
|
||||
pickerInstance = flatpickr(pickerInput, flatpickrOptions);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="mb-0">
|
||||
|
||||
{#if listMode}
|
||||
<div class="dropdown d-flex">
|
||||
<input
|
||||
type="search"
|
||||
{id}
|
||||
on:keyup={list.update}
|
||||
on:focus={list.update}
|
||||
class="form-control dropdown-toggle"
|
||||
class:is-invalid={errorMessage}
|
||||
bind:value={search}
|
||||
bind:this={pickerInput}
|
||||
placeholder="Search for options"
|
||||
data-bs-toggle="dropdown"
|
||||
autocomplete="off"
|
||||
readonly={field.readonly && !isCreateMode}
|
||||
/>
|
||||
<button
|
||||
class="btn btn-light ms-1"
|
||||
on:click|preventDefault={(e) => pickerInstance.open()}
|
||||
>
|
||||
<Icon icon="calendar"/>
|
||||
</button>
|
||||
<ul class="dropdown-menu w-100">
|
||||
{#if field.optionsFrom}
|
||||
<Datalist
|
||||
{field}
|
||||
bind:this={list}
|
||||
bind:value
|
||||
bind:search
|
||||
/>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
{#if value}
|
||||
<span class="badge rounded-pill bg-light text-dark fs-6 mt-3">
|
||||
<div class="d-flex align-items-center ">
|
||||
{value}
|
||||
<button
|
||||
on:click|preventDefault={(e) => (value = "")}
|
||||
type="button"
|
||||
class="btn-close btn-sm ms-1"
|
||||
style="font-size:10px"
|
||||
aria-label="Close"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
{id}
|
||||
class="form-control"
|
||||
class:is-invalid={errorMessage}
|
||||
bind:value
|
||||
bind:this={pickerInput}
|
||||
autocomplete="off"
|
||||
readonly={field.readonly && !isCreateMode}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,121 @@
|
||||
<script>
|
||||
import Datalist from "./Datalist.svelte";
|
||||
import {onMount} from "svelte";
|
||||
import flatpickr from "flatpickr";
|
||||
import "flatpickr/dist/flatpickr.css";
|
||||
import "flatpickr/dist/themes/light.css";
|
||||
import { getErrorMessage } from "../form.js";
|
||||
import Icon from "../../../common/Icon.svelte";
|
||||
|
||||
export let field;
|
||||
export let value;
|
||||
export let isCreateMode;
|
||||
export let validationErrors;
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
$: search = "";
|
||||
$: listMode = field.optionsFrom && !(field.readonly && !isCreateMode);
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
|
||||
export let id;
|
||||
let list;
|
||||
let pickerInput;
|
||||
let pickerInstance;
|
||||
let flatpickrOptions = {
|
||||
enableTime: false,
|
||||
allowInput: true,
|
||||
altInput: true,
|
||||
altFormat: "Y-m-d H:i:S",
|
||||
dateFormat: "Z",
|
||||
enableTime: true,
|
||||
time_24hr: true,
|
||||
enableSeconds: true,
|
||||
};
|
||||
|
||||
if (field.min) {
|
||||
flatpickrOptions.minDate = field.min;
|
||||
}
|
||||
|
||||
if (field.max) {
|
||||
flatpickrOptions.maxDate = field.max;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!field.readonly || isCreateMode) {
|
||||
if (listMode) {
|
||||
flatpickrOptions.clickOpens = false;
|
||||
}
|
||||
pickerInstance = flatpickr(pickerInput, flatpickrOptions);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="mb-0">
|
||||
{#if listMode}
|
||||
<div class="dropdown d-flex">
|
||||
<input
|
||||
type="search"
|
||||
{id}
|
||||
on:keyup={list.update}
|
||||
on:focus={list.update}
|
||||
class="form-control dropdown-toggle"
|
||||
class:is-invalid={errorMessage}
|
||||
bind:value={search}
|
||||
bind:this={pickerInput}
|
||||
placeholder="Search for options"
|
||||
data-bs-toggle="dropdown"
|
||||
autocomplete="off"
|
||||
readonly={field.readonly && !isCreateMode}
|
||||
/>
|
||||
<button
|
||||
class="btn btn-light ms-1"
|
||||
on:click|preventDefault={(e) => pickerInstance.open()}
|
||||
>
|
||||
<Icon icon="calendar"/>
|
||||
</button>
|
||||
<ul class="dropdown-menu w-100">
|
||||
{#if field.optionsFrom}
|
||||
<Datalist
|
||||
{field}
|
||||
bind:this={list}
|
||||
bind:value
|
||||
bind:search
|
||||
/>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
{#if value}
|
||||
<span class="badge rounded-pill bg-light text-dark fs-6 mt-3">
|
||||
<div class="d-flex align-items-center ">
|
||||
{value}
|
||||
<button
|
||||
on:click|preventDefault={(e) => (value = "")}
|
||||
type="button"
|
||||
class="btn-close btn-sm ms-1"
|
||||
style="font-size:10px"
|
||||
aria-label="Close"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
{id}
|
||||
class="form-control"
|
||||
class:is-invalid={errorMessage}
|
||||
bind:value
|
||||
bind:this={pickerInput}
|
||||
autocomplete="off"
|
||||
readonly={field.readonly && !isCreateMode}
|
||||
/>
|
||||
{/if}
|
||||
<small class=" text-primary opacity-50"
|
||||
>Dates are displayed according to your timezone: {timezone}</small
|
||||
>
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script>
|
||||
import Codemirror from "../../../libs/Codemirror.svelte";
|
||||
import { getErrorMessage } from "../form.js";
|
||||
|
||||
|
||||
export let value;
|
||||
export let field;
|
||||
export let isCreateMode;
|
||||
// export let id;
|
||||
export let validationErrors;
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
|
||||
</script>
|
||||
|
||||
<div class="mb-3">
|
||||
|
||||
<Codemirror bind:value editable={!field.readonly || isCreateMode} />
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script>
|
||||
import Codemirror from "../../../libs/CodemirrorMarkdown.svelte";
|
||||
import { getErrorMessage } from "../form.js";
|
||||
|
||||
|
||||
export let value;
|
||||
export let field;
|
||||
export let isCreateMode;
|
||||
// export let id;
|
||||
export let validationErrors;
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
|
||||
</script>
|
||||
|
||||
<div class="mb-3">
|
||||
|
||||
<Codemirror bind:value editable={!field.readonly || isCreateMode} />
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,92 @@
|
||||
<script>
|
||||
import Datalist from "./Datalist.svelte";
|
||||
import { getErrorMessage } from "../form.js";
|
||||
|
||||
export let field;
|
||||
export let value;
|
||||
export let schemas;
|
||||
export let validationErrors;
|
||||
export let isCreateMode;
|
||||
export let id;
|
||||
$: search = "";
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
|
||||
let list;
|
||||
|
||||
function fixDecimals(e) {
|
||||
const number = e.currentTarget.value;
|
||||
const formattedNumber = formatNumber(number);
|
||||
value = isNaN(formattedNumber) ? null : formattedNumber;
|
||||
}
|
||||
|
||||
function formatNumber(number) {
|
||||
return parseFloat(number).toFixed(field.decimals);
|
||||
}
|
||||
|
||||
$: listMode = field.optionsFrom && !(field.readonly && !isCreateMode);
|
||||
</script>
|
||||
|
||||
<div class="mb-0">
|
||||
|
||||
{#if listMode}
|
||||
<div class="dropdown">
|
||||
<input
|
||||
type="number"
|
||||
{id}
|
||||
on:keyup={list.update}
|
||||
on:focus={list.update}
|
||||
bind:value={search}
|
||||
placeholder="Search for options"
|
||||
class="form-control dropdown-toggle"
|
||||
class:is-invalid={errorMessage}
|
||||
data-bs-toggle="dropdown"
|
||||
autocomplete="off"
|
||||
readonly={field.readonly && !isCreateMode}
|
||||
/>
|
||||
|
||||
<ul class="dropdown-menu w-100">
|
||||
{#if field.optionsFrom}
|
||||
<Datalist
|
||||
{field}
|
||||
bind:this={list}
|
||||
{schemas}
|
||||
bind:value
|
||||
bind:search
|
||||
/>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{#if value}
|
||||
<span class="badge rounded-pill bg-light text-dark fs-6 mt-3">
|
||||
<div class="d-flex align-items-center ">
|
||||
{value}
|
||||
<button
|
||||
on:click|preventDefault={(e) => (value = "")}
|
||||
type="button"
|
||||
class="btn-close btn-sm ms-1"
|
||||
style="font-size:10px"
|
||||
aria-label="Close"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<input
|
||||
type="number"
|
||||
{id}
|
||||
class="form-control"
|
||||
class:is-invalid={errorMessage}
|
||||
on:change={fixDecimals}
|
||||
bind:value
|
||||
autocomplete="off"
|
||||
readonly={field.readonly && !isCreateMode}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script>
|
||||
import Tinymce from "../../../libs/Tinymce.svelte";
|
||||
import { getErrorMessage } from "../form.js";
|
||||
|
||||
export let value;
|
||||
export let field;
|
||||
export let isCreateMode;
|
||||
export let schema;
|
||||
export let validationErrors;
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
let additionalConfig = {
|
||||
readonly: field.readonly && !isCreateMode,
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="mb-0">
|
||||
<Tinymce bind:value {additionalConfig} {schema}/>
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,39 @@
|
||||
<script>
|
||||
export let field;
|
||||
export let value;
|
||||
export let search;
|
||||
|
||||
function select(e, option) {
|
||||
e.preventDefault();
|
||||
value = option;
|
||||
search = "";
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{#if field.selectOptions}
|
||||
{#if Array.isArray(field.selectOptions)}
|
||||
{#each field.selectOptions as suggestion (suggestion)}
|
||||
<div
|
||||
on:click={(e) => select(e, suggestion)}
|
||||
on:keypress={(e) => select(e, suggestion)}
|
||||
>
|
||||
<span class="dropdown-item">
|
||||
{suggestion}
|
||||
</span>
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each Object.entries(field.selectOptions) as [k, v] (k)}
|
||||
<div
|
||||
on:click={(e) => select(e, k)}
|
||||
on:keypress={(e) => select(e, k)}
|
||||
>
|
||||
<span class="dropdown-item">
|
||||
{v}
|
||||
</span>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -0,0 +1,30 @@
|
||||
<script>
|
||||
import { getErrorMessage } from "../form.js";
|
||||
export let field;
|
||||
export let value;
|
||||
export let isCreateMode;
|
||||
export let validationErrors;
|
||||
let thisEl;
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
export let id;
|
||||
|
||||
</script>
|
||||
|
||||
<div class="mb-0">
|
||||
<input
|
||||
type="text"
|
||||
{id}
|
||||
class="form-control"
|
||||
class:is-invalid={errorMessage}
|
||||
bind:value
|
||||
autocomplete="off"
|
||||
readonly={field.readonly && !isCreateMode}
|
||||
/>
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<script>
|
||||
import Datalist from "./Datalist.svelte";
|
||||
import Selectlist from "./Selectlist.svelte";
|
||||
import { getErrorMessage } from "../form.js";
|
||||
|
||||
export let field;
|
||||
export let value;
|
||||
export let isCreateMode;
|
||||
export let validationErrors;
|
||||
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
$: search = "";
|
||||
export let id;
|
||||
let list;
|
||||
|
||||
$: listMode = field.optionsFrom && !(field.readonly && !isCreateMode);
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="mb-0">
|
||||
{#if listMode}
|
||||
<div class="dropdown">
|
||||
<input
|
||||
type="search"
|
||||
{id}
|
||||
on:keyup={list.update}
|
||||
on:focus={list.update}
|
||||
class="form-control dropdown-toggle"
|
||||
class:is-invalid={errorMessage}
|
||||
bind:value={search}
|
||||
placeholder="Search for options"
|
||||
data-bs-toggle="dropdown"
|
||||
autocomplete="off"
|
||||
readonly={field.readonly && !isCreateMode}
|
||||
/>
|
||||
|
||||
<div class="dropdown-menu w-100">
|
||||
{#if field.optionsFrom}
|
||||
<Datalist
|
||||
{field}
|
||||
bind:this={list}
|
||||
bind:value
|
||||
bind:search
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if value}
|
||||
<span class="badge rounded-pill bg-light text-dark fs-6 mt-3">
|
||||
<div class="d-flex align-items-center ">
|
||||
{value}
|
||||
<button
|
||||
on:click|preventDefault={(e) => (value = "")}
|
||||
type="button"
|
||||
class="btn-close btn-sm ms-1"
|
||||
style="font-size:10px"
|
||||
aria-label="Close"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
{/if}
|
||||
{:else if field.selectOptions}
|
||||
<div class="dropdown">
|
||||
<input
|
||||
type="search"
|
||||
{id}
|
||||
class="form-control dropdown-toggle"
|
||||
class:is-invalid={errorMessage}
|
||||
bind:value={search}
|
||||
placeholder="Search for options"
|
||||
data-bs-toggle="dropdown"
|
||||
autocomplete="off"
|
||||
readonly={field.readonly && !isCreateMode}
|
||||
/>
|
||||
<div class="dropdown-menu w-100">
|
||||
<Selectlist
|
||||
{field}
|
||||
bind:value
|
||||
bind:search
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if value}
|
||||
<span class="badge rounded-pill bg-light text-dark fs-6 mt-3">
|
||||
<div class="d-flex align-items-center ">
|
||||
{#if Array.isArray(field.selectOptions)}
|
||||
{value}
|
||||
{:else}
|
||||
{field.selectOptions[value]}
|
||||
{/if}
|
||||
|
||||
<button
|
||||
on:click|preventDefault={(e) => (value = "")}
|
||||
type="button"
|
||||
class="btn-close btn-sm ms-1"
|
||||
style="font-size:10px"
|
||||
aria-label="Close"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
{id}
|
||||
class="form-control"
|
||||
class:is-invalid={errorMessage}
|
||||
bind:value
|
||||
autocomplete="off"
|
||||
readonly={field.readonly && !isCreateMode}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,53 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { getErrorMessage } from "../form.js";
|
||||
export let field;
|
||||
export let value;
|
||||
export let isCreateMode;
|
||||
export let validationErrors;
|
||||
let thisEl;
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
export let id;
|
||||
|
||||
function resize(e) {
|
||||
let el;
|
||||
if (e.target) {
|
||||
el = e.target;
|
||||
} else {
|
||||
el = e;
|
||||
}
|
||||
el.style.overflow = "hidden";
|
||||
el.style.height = "1px";
|
||||
el.style.height = +el.scrollHeight + "px";
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
resize(thisEl);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="mb-0">
|
||||
<textarea
|
||||
bind:value
|
||||
bind:this={thisEl}
|
||||
{id}
|
||||
class="form-control"
|
||||
on:input={resize}
|
||||
on:focus={resize}
|
||||
rows="2"
|
||||
class:is-invalid={errorMessage}
|
||||
readonly={field.readonly && !isCreateMode}
|
||||
/>
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
textarea {
|
||||
resize: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,49 @@
|
||||
<script>
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { getContext } from "svelte";
|
||||
import Icon from "../../../common/Icon.svelte";
|
||||
import { getErrorMessage } from "../form.js";
|
||||
const channelurl = getContext("channelurl");
|
||||
export let validationErrors;
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
export let field;
|
||||
export let value;
|
||||
export let id;
|
||||
export let isCreateMode;
|
||||
let readonly = field.readonly && !isCreateMode;
|
||||
|
||||
function generateId(e) {
|
||||
e.preventDefault();
|
||||
value = uuidv4();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mb-0">
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<input
|
||||
type="text"
|
||||
{id}
|
||||
class="form-control"
|
||||
class:is-invalid={errorMessage}
|
||||
bind:value
|
||||
autocomplete="off"
|
||||
{readonly}
|
||||
/>
|
||||
{#if !readonly}
|
||||
<button
|
||||
class="btn btn-primary ms-2"
|
||||
title="Generate a new UUIDv4"
|
||||
on:click={generateId}
|
||||
>
|
||||
<Icon icon="dice" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
export function getErrorMessage(validationErrors, fieldName) {
|
||||
return validationErrors?.find((e) => e.fieldName === fieldName)?.message;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<script>
|
||||
import {createEventDispatcher, getContext} from "svelte";
|
||||
import Index from "../../../content/Index.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const channel = getContext("channel");
|
||||
$: data = {};
|
||||
let isOpen = false;
|
||||
let selectedRecords = [];
|
||||
// onMount(() => {
|
||||
// load();
|
||||
// });
|
||||
|
||||
export function open(schema) {
|
||||
isOpen = true;
|
||||
load(schema);
|
||||
}
|
||||
|
||||
export function close() {
|
||||
isOpen = false;
|
||||
selectedRecords = [];
|
||||
}
|
||||
|
||||
function load(schema) {
|
||||
axios
|
||||
.get(channel.lucentUrl + "/content/" + schema)
|
||||
.then((response) => {
|
||||
data = response.data;
|
||||
})
|
||||
.catch((error) => console.log(error));
|
||||
}
|
||||
|
||||
function insert(e) {
|
||||
e.preventDefault();
|
||||
dispatch("insert", {
|
||||
records: selectedRecords,
|
||||
action: "insert",
|
||||
});
|
||||
}
|
||||
|
||||
function replace(e) {
|
||||
e.preventDefault();
|
||||
dispatch("insert", {
|
||||
records: selectedRecords,
|
||||
action: "replace",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if data.schema}
|
||||
<div
|
||||
class="modal fade show"
|
||||
tabindex="-1"
|
||||
class:d-block={isOpen}
|
||||
aria-modal="true"
|
||||
role="dialog"
|
||||
style="background: rgba(100,100,100,.6);"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="d-flex align-items-center">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary me-1"
|
||||
on:click={insert}
|
||||
disabled={selectedRecords.length === 0}
|
||||
>
|
||||
Insert
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary me-3"
|
||||
on:click={replace}
|
||||
disabled={selectedRecords.length === 0}
|
||||
>
|
||||
Replace
|
||||
</button>
|
||||
{#if selectedRecords.length > 0}
|
||||
<span class="">
|
||||
{selectedRecords.length} records selected
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<button
|
||||
on:click|preventDefault={(e) => (isOpen = false)}
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<Index {...data} bind:selected={selectedRecords}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.modal-dialog {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
margin: 40px auto;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script>
|
||||
|
||||
import {getContext} from "svelte";
|
||||
import Form from "../Form.svelte";
|
||||
import OffCanvas from "../../../common/OffCanvas.svelte";
|
||||
|
||||
export let field;
|
||||
export let edge;
|
||||
let form;
|
||||
let offCanvas;
|
||||
|
||||
const channel = getContext("channel");
|
||||
let schema = channel.schemas.find(s => s.name === field.data);
|
||||
|
||||
export function openEdit() {
|
||||
offCanvas.show();
|
||||
}
|
||||
|
||||
|
||||
|
||||
function save(e){
|
||||
e.preventDefault();
|
||||
console.log("yo")
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<OffCanvas bind:this={offCanvas}>
|
||||
<Form
|
||||
bind:this={form}
|
||||
data={edge.data}
|
||||
{schema}
|
||||
isCreateMode={false}
|
||||
on:save={save}
|
||||
/>
|
||||
</OffCanvas>
|
||||
@@ -0,0 +1,98 @@
|
||||
<script>
|
||||
import {getContext} from "svelte";
|
||||
import PreviewCard from "../../PreviewCard.svelte";
|
||||
import Sortable from "../../../libs/Sortable.svelte";
|
||||
import BrowseModal from "./BrowseModal.svelte";
|
||||
import {insertEdges} from "./reference.js";
|
||||
import {sortByField} from "../../../edges/sortEdges.js";
|
||||
|
||||
const channel = getContext("channel");
|
||||
export let field;
|
||||
export let record;
|
||||
export let graph;
|
||||
|
||||
let browseModal;
|
||||
$: currentReferences = graph.filter((queryRecord)=> field.name === queryRecord.edge.field) ?? [];
|
||||
|
||||
let collections = channel.schemas.filter((aschema) =>
|
||||
field.collections.includes(aschema.name)
|
||||
);
|
||||
|
||||
function removeReference(e) {
|
||||
e.preventDefault();
|
||||
graph = graph.filter(
|
||||
(queryRecord) => !(queryRecord.edge.target === e.detail && queryRecord.edge.field === field.name)
|
||||
);
|
||||
}
|
||||
|
||||
function openBrowseModal(e, schema) {
|
||||
e.preventDefault();
|
||||
browseModal.open(schema);
|
||||
}
|
||||
|
||||
async function reorder(e) {
|
||||
graph = await sortByField(e.detail.source, e.detail.target, graph, field.name);
|
||||
}
|
||||
|
||||
function insert(e) {
|
||||
e.preventDefault();
|
||||
browseModal.close();
|
||||
|
||||
const recordsToInsert = e.detail.records;
|
||||
const action = e.detail.action;
|
||||
graph = insertEdges(graph, record, recordsToInsert, field.name, action);
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mb-0">
|
||||
{#if field.collections.length === 1}
|
||||
<button
|
||||
class="btn btn-outline-primary"
|
||||
on:click={(e) => openBrowseModal(e, collections[0].name)}
|
||||
>
|
||||
Browse
|
||||
</button>
|
||||
{:else}
|
||||
<div class="dropdown d-inline-block">
|
||||
<button
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
Browse
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{#each collections as collection}
|
||||
<li>
|
||||
<!-- {`${channelurl}/content/${collection.name}?parent=${record.id}&parentfield=${field.name}`} -->
|
||||
<a
|
||||
class="dropdown-item"
|
||||
on:click={(e) =>
|
||||
openBrowseModal(e, collection.name)}
|
||||
href="/">{collection.label}</a
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if currentReferences.length > 0}
|
||||
<Sortable sortableClass="mt-3" on:update={reorder}>
|
||||
{#each currentReferences as reference (reference.record.id)}
|
||||
<div class="mb-1">
|
||||
<PreviewCard
|
||||
record={reference.record}
|
||||
hasDelete={true}
|
||||
editable={!!field?.data}
|
||||
{field}
|
||||
edge={reference.edge}
|
||||
on:remove={removeReference}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</Sortable>
|
||||
{/if}
|
||||
<BrowseModal bind:this={browseModal} on:insert={insert}/>
|
||||
@@ -0,0 +1,161 @@
|
||||
<script>
|
||||
import {getContext} from "svelte";
|
||||
import {uniqBy, debounce} from "lodash";
|
||||
import {previewTitle} from "../../Preview.js";
|
||||
import {getErrorMessage} from "../errorMessage.js";
|
||||
import {sortByField} from "../../../edges/sortEdges.js";
|
||||
import ReferenceInlineButtons from "../../elements/ReferenceInlineButtons.svelte";
|
||||
import Sortable from "../../../libs/Sortable.svelte";
|
||||
import RenderField from "../../../content/RenderField.svelte";
|
||||
import Icon from "../../../common/Icon.svelte";
|
||||
import Datalist from "../fields/Datalist.svelte";
|
||||
import {insertEdges} from "./reference.js";
|
||||
|
||||
const channel = getContext("channel");
|
||||
export let field;
|
||||
export let id;
|
||||
export let record;
|
||||
export let graph;
|
||||
|
||||
export let validationErrors;
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
|
||||
$: references = graph.edges
|
||||
.filter((edge) => edge.field === field.name)
|
||||
.map((edge) => {
|
||||
return graph.records.find((increc) => increc.id == edge.target && record.id == edge.source);
|
||||
}).filter((rec) => (rec?.id ? true : false)) ?? [];
|
||||
|
||||
let search = ""
|
||||
$: searchOptions = []
|
||||
|
||||
|
||||
function removeReference(e, recordId) {
|
||||
e.preventDefault();
|
||||
graph.edges = graph.edges.filter(
|
||||
(edge) => !(edge.target === recordId && edge.field === field.name)
|
||||
);
|
||||
}
|
||||
|
||||
function saveNew(e, newValue) {
|
||||
e.preventDefault();
|
||||
axios
|
||||
.post(channel.lucentUrl + "/records", {
|
||||
isCreateMode: true,
|
||||
record: {
|
||||
schema: field.collections[0],
|
||||
status: "published",
|
||||
data: {
|
||||
[field.searchField]: newValue
|
||||
}
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
searchOptions = [];
|
||||
insert(e,response.data.records[0]);
|
||||
console.log(response)
|
||||
})
|
||||
.catch((error) => {
|
||||
searchOptions = [];
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
function insert(e, insertRecord) {
|
||||
e.preventDefault();
|
||||
graph = insertEdges(graph,record,[insertRecord],field.name,e.detail.action);
|
||||
}
|
||||
|
||||
const updateResults = debounce((e) => {
|
||||
axios
|
||||
.get(channel.lucentUrl + "/records/suggestions", {
|
||||
params: {
|
||||
schema: field.collections[0],
|
||||
field: field.searchField,
|
||||
value: search,
|
||||
ui: "text",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
searchOptions = response.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
searchOptions = [];
|
||||
console.log(error);
|
||||
});
|
||||
}, 500);
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block mb-3">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<input
|
||||
type="search"
|
||||
{id}
|
||||
on:keyup={updateResults}
|
||||
class="form-control dropdown-toggle"
|
||||
class:is-invalid={errorMessage}
|
||||
bind:value={search}
|
||||
placeholder={"Search for "+field.label}
|
||||
data-bs-toggle="dropdown"
|
||||
autocomplete="off"
|
||||
readonly={field.readonly && !isCreateMode}
|
||||
/>
|
||||
|
||||
<div class="dropdown-menu w-100">
|
||||
|
||||
{#if searchOptions}
|
||||
{#each searchOptions as option (option.id)}
|
||||
<div
|
||||
on:click={(e) => insert(e, option)}
|
||||
on:keypress={(e) => insert(e, option)}
|
||||
>
|
||||
<span class="dropdown-item">
|
||||
{previewTitle(channel.schemas, option)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
|
||||
Start typing...
|
||||
{/each}
|
||||
{/if}
|
||||
{#if search }
|
||||
<div
|
||||
on:click={(e) => saveNew(e,search)}
|
||||
on:keypress={(e) => saveNew(e,search)}
|
||||
>
|
||||
<span class="dropdown-item">
|
||||
Add "{search}"
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
{#if references.length > 0}
|
||||
<div class="d-flex">
|
||||
{#each references as record (record.id)}
|
||||
<span class="badge rounded-pill bg-light text-dark fs-6 mt-3">
|
||||
<div class="d-flex align-items-center ">
|
||||
{previewTitle(channel.schemas, record)}
|
||||
<button
|
||||
on:click|preventDefault={(e) => removeReference(e, record.id)}
|
||||
type="button"
|
||||
class="btn-close btn-sm ms-1"
|
||||
style="font-size:10px"
|
||||
aria-label="Close"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import {uniqBy} from "lodash";
|
||||
|
||||
export function insertEdges(existingRecords, sourceRecord, targetRecords, fieldName, action = "") {
|
||||
let newQueryRecords = targetRecords.map((r) => {
|
||||
return {
|
||||
record: r,
|
||||
edge: {
|
||||
target: r.id,
|
||||
source: sourceRecord.id,
|
||||
sourceSchema: sourceRecord.schema,
|
||||
targetSchema: r.schema,
|
||||
field: fieldName,
|
||||
rank: ""
|
||||
}
|
||||
|
||||
};
|
||||
});
|
||||
if (action === "replace") {
|
||||
existingRecords = existingRecords.filter(queryRecord => queryRecord.edge.field !== fieldName)
|
||||
}
|
||||
existingRecords = [...existingRecords ?? [], ...newQueryRecords];
|
||||
return uniqBy(existingRecords, (r) => r.record.id);
|
||||
}
|
||||
Reference in New Issue
Block a user