Compare commits
17 Commits
1.0.2
...
json-schema
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d2cafdf11 | |||
| e9c2e82bc3 | |||
| 49c1d5efd0 | |||
| 0e19c38f23 | |||
| b4521e92b8 | |||
| 2e95fca8ad | |||
| 02224eb580 | |||
| e74e1e7956 | |||
| d824e52dce | |||
| 322c48b78b | |||
| c649077e37 | |||
| b8efa5f586 | |||
| 8526fd471f | |||
| bb77a37ff7 | |||
| 1f03eebd08 | |||
| 137c338719 | |||
| 842bd71a18 |
+6
-3
@@ -11,13 +11,16 @@
|
|||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
"guzzlehttp/guzzle": "^7.2",
|
"guzzlehttp/guzzle": "^7.2",
|
||||||
"intervention/image": "^2.7",
|
"intervention/image": "^2.7",
|
||||||
"phpoption/phpoption": "^1.9",
|
|
||||||
"spatie/image-optimizer": "^1.6",
|
"spatie/image-optimizer": "^1.6",
|
||||||
"staudenmeir/laravel-cte": "^1.0",
|
"staudenmeir/laravel-cte": "^1.0",
|
||||||
"ext-pdo": "*"
|
"ext-pdo": "*",
|
||||||
|
"opis/json-schema": "^2.3",
|
||||||
|
"symfony/yaml": "^7.0",
|
||||||
|
"spatie/laravel-data": "^4.4"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "^1.8"
|
"phpstan/phpstan": "^1.8",
|
||||||
|
"laravel/framework": "^10.10"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
Generated
+5803
-477
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@
|
|||||||
export let data;
|
export let data;
|
||||||
// export let layout;
|
// export let layout;
|
||||||
export let channel;
|
export let channel;
|
||||||
|
export let sidebar;
|
||||||
export let axios;
|
export let axios;
|
||||||
export let readableSchemas;
|
export let readableSchemas;
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Navbar schema={data.schema}/>
|
<Navbar {sidebar}/>
|
||||||
|
|
||||||
<svelte:component this={components[view]} {title} {...data}/>
|
<svelte:component this={components[view]} {title} {...data}/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import Avatar from "./account/Avatar.svelte";
|
import Avatar from "./account/Avatar.svelte";
|
||||||
import NavbarMenu from "./NavbarMenu.svelte";
|
|
||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
|
|
||||||
export let schema;
|
export let sidebar;
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
const readableSchemas = getContext("readableSchemas");
|
|
||||||
const user = getContext("user");
|
const user = getContext("user");
|
||||||
|
|
||||||
let contentIsOpen = false;
|
let contentIsOpen = false;
|
||||||
const fileSchemas = readableSchemas.filter((sc) => sc.type === "files");
|
|
||||||
const otherSchemas = readableSchemas.filter((sc) => !sc.isEntry && sc.type === "collection");
|
|
||||||
|
|
||||||
let filesIsActive = false;
|
|
||||||
let otherIsActive = false;
|
|
||||||
if(schema){
|
|
||||||
filesIsActive = fileSchemas.filter(s => s.name === schema.name).length > 0;
|
|
||||||
otherIsActive = otherSchemas.filter(s => s.name === schema.name).length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="lx-nav">
|
<nav class="lx-nav">
|
||||||
@@ -29,12 +14,7 @@
|
|||||||
<button on:click={(e) => contentIsOpen = true} class="btn btn-primary btn-sm d-xxl-none">« Content</button>
|
<button on:click={(e) => contentIsOpen = true} class="btn btn-primary btn-sm d-xxl-none">« Content</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center ">
|
<div class="d-flex align-items-center ">
|
||||||
<a class="nav-item" href="{channel.lucentUrl}">{channel.name}</a>
|
|
||||||
<a class="nav-item" href="{channel.lucentUrl}/members">Members</a>
|
|
||||||
|
|
||||||
{#if channel.generateCommand}
|
|
||||||
<a href="{channel.lucentUrl}/build-report" class="btn btn-outline-primary btn-sm d-">Build website</a>
|
|
||||||
{/if}
|
|
||||||
<!-- <div>-->
|
<!-- <div>-->
|
||||||
<!-- <form method="GET">-->
|
<!-- <form method="GET">-->
|
||||||
<!-- <input type="search" name="filter[search_regex]" placeholder="Search"-->
|
<!-- <input type="search" name="filter[search_regex]" placeholder="Search"-->
|
||||||
@@ -42,7 +22,12 @@
|
|||||||
<!-- </form>-->
|
<!-- </form>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="d-flex align-items-center ">
|
||||||
|
<a class="nav-item" href="{channel.lucentUrl}/members">Members</a>
|
||||||
|
|
||||||
|
{#if channel.generateCommand}
|
||||||
|
<a href="{channel.lucentUrl}/build-report" class="btn btn-outline-primary btn-sm d-">Build website</a>
|
||||||
|
{/if}
|
||||||
<a class="nav-item" href="{channel.lucentUrl}/profile">
|
<a class="nav-item" href="{channel.lucentUrl}/profile">
|
||||||
<Avatar side="28" name={user.name}/>
|
<Avatar side="28" name={user.name}/>
|
||||||
</a>
|
</a>
|
||||||
@@ -51,76 +36,12 @@
|
|||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="offcanvas offcanvas-start d-xxl-block show border-0 bg-light-subtle" class:d-none={!contentIsOpen}
|
<div class="offcanvas offcanvas-start d-xxl-block show border-0 bg-primary-subtle " class:d-none={!contentIsOpen}
|
||||||
style="padding-top:36px " data-bs-scroll="true"
|
data-bs-scroll="true"
|
||||||
data-bs-backdrop="false"
|
data-bs-backdrop="false"
|
||||||
tabindex="-1" aria-labelledby="offcanvasScrollingLabel">
|
tabindex="-1" aria-labelledby="offcanvasScrollingLabel">
|
||||||
<!-- <div class="offcanvas-header">-->
|
|
||||||
<!-- <h5 class="offcanvas-title" id="offcanvasScrollingLabel">Content</h5>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<div class="offcanvas-body">
|
<div class="offcanvas-body">
|
||||||
<button on:click={(e) => contentIsOpen = false} class="btn btn-primary btn-sm d-xxl-none mb-4">« close</button>
|
<button on:click={(e) => contentIsOpen = false} class="btn btn-primary btn-sm d-xxl-none mb-4">« close</button>
|
||||||
<div class="accordion">
|
{@html sidebar}
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header" id="panelsStayOpen-headingMain">
|
|
||||||
<button class="accordion-button" type="button" data-bs-toggle="collapse"
|
|
||||||
data-bs-target="#panelsStayOpen-collapseMain" aria-expanded="true"
|
|
||||||
aria-controls="panelsStayOpen-collapseMain">
|
|
||||||
Main
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="panelsStayOpen-collapseMain" class="accordion-collapse collapse show"
|
|
||||||
aria-labelledby="panelsStayOpen-headingMain">
|
|
||||||
<div class="accordion-body">
|
|
||||||
<NavbarMenu
|
|
||||||
schemas={ readableSchemas.filter((sc) => sc.isEntry)}
|
|
||||||
schema={schema}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if otherSchemas.length > 0}
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header" id="panelsStayOpen-headingOther">
|
|
||||||
<button class="accordion-button" class:collapsed={!otherIsActive} type="button" data-bs-toggle="collapse"
|
|
||||||
data-bs-target="#panelsStayOpen-collapseOther" aria-expanded={otherIsActive}
|
|
||||||
aria-controls="panelsStayOpen-collapseOther">
|
|
||||||
Other
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="panelsStayOpen-collapseOther" class="accordion-collapse collapse"
|
|
||||||
class:show={otherIsActive}
|
|
||||||
aria-labelledby="panelsStayOpen-headingOther">
|
|
||||||
<div class="accordion-body">
|
|
||||||
<NavbarMenu
|
|
||||||
schemas={ otherSchemas}
|
|
||||||
schema={schema}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if fileSchemas.length > 0}
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header" id="panelsStayOpen-headingFS">
|
|
||||||
<button class="accordion-button " class:collapsed={!filesIsActive} type="button" data-bs-toggle="collapse"
|
|
||||||
data-bs-target="#panelsStayOpen-collapseFS" aria-expanded={filesIsActive}
|
|
||||||
aria-controls="panelsStayOpen-collapseFS">
|
|
||||||
Filesystem
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="panelsStayOpen-collapseFS" class="accordion-collapse collapse" class:show={filesIsActive}
|
|
||||||
aria-labelledby="panelsStayOpen-headingFS">
|
|
||||||
<div class="accordion-body">
|
|
||||||
<NavbarMenu
|
|
||||||
schemas={ fileSchemas}
|
|
||||||
schema={schema}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {getContext} from "svelte";
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
|
||||||
export let schemas;
|
|
||||||
export let schema;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="list-group list-group-flush">
|
|
||||||
|
|
||||||
{#each schemas as aschema}
|
|
||||||
<a class="list-group-item list-group-item-action" class:active={aschema.name === schema?.name}
|
|
||||||
aria-current="page"
|
|
||||||
href="{channel.lucentUrl}/content/{aschema.name}">{aschema.label}</a>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
import {onDestroy, onMount} from "svelte";
|
||||||
|
import offcanvas from "bootstrap/js/src/offcanvas.js";
|
||||||
|
export let title = "";
|
||||||
|
let offCanvasEl;
|
||||||
|
let offCanvasInstance;
|
||||||
|
|
||||||
|
export function show() {
|
||||||
|
if(!offCanvasInstance){
|
||||||
|
offCanvasInstance = new offcanvas(offCanvasEl);
|
||||||
|
}
|
||||||
|
offCanvasInstance.show();
|
||||||
|
}
|
||||||
|
onMount(()=>{
|
||||||
|
offCanvasInstance = new offcanvas(offCanvasEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
export function hide() {
|
||||||
|
offCanvasInstance.hide();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={offCanvasEl} class="offcanvas offcanvas-end" tabindex="-1"
|
||||||
|
aria-labelledby="offcanvasEditContent">
|
||||||
|
<div class="offcanvas-header">
|
||||||
|
<h5 class="offcanvas-title">{title}</h5>
|
||||||
|
<button type="button" on:click={hide} class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="offcanvas-body" style="overflow: auto">
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<script>
|
||||||
|
import {uniqueId} from "lodash";
|
||||||
|
|
||||||
|
export let label = "";
|
||||||
|
let id = uniqueId();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id={id} checked>
|
||||||
|
<label class="form-check-label" for={id}>{label}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
import Preview from "../files/Preview.svelte";
|
|
||||||
import {selectRecord} from "./functions/recordSelect.js";
|
import {selectRecord} from "./functions/recordSelect.js";
|
||||||
|
import Preview from "../newPreview/Preview.svelte";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
|
|
||||||
export let schema;
|
|
||||||
export let records;
|
export let records;
|
||||||
export let isWritable;
|
export let isWritable;
|
||||||
export let selected = [];
|
export let selected = [];
|
||||||
@@ -17,42 +16,42 @@
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
<div class="row" style="max-width:1000px">
|
<div class="row" style="max-width:1000px">
|
||||||
{#each records as record (record.id)}
|
{#each records as queryRecord (queryRecord.record.id)}
|
||||||
<div class="col-6 col-md-4">
|
<div class="col-6 col-md-4">
|
||||||
<div
|
<div
|
||||||
class="file-wrapper rounded p-2 mb-4 bg-light"
|
class="file-wrapper rounded p-2 mb-4 bg-light"
|
||||||
class:selected={selected.includes(record)}
|
class:selected={selected.includes(queryRecord)}
|
||||||
>
|
>
|
||||||
{#if isWritable}
|
{#if isWritable}
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
on:change={() => select(record)}
|
on:change={() => select(queryRecord.record)}
|
||||||
class="form-check-input "
|
class="form-check-input "
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={selected.find(
|
checked={selected.find(
|
||||||
(r) => r.id === record.id
|
(r) => r.id === queryRecord.record.id
|
||||||
)}
|
)}
|
||||||
value={record}
|
value={queryRecord}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
<Preview {record} size="medium"/>
|
<Preview record={queryRecord.record} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="{channel.lucentUrl}/records/{record.id}"
|
href="{channel.lucentUrl}/records/{queryRecord.record.id}"
|
||||||
title={record._file.path}
|
title={queryRecord.record._file.path}
|
||||||
class="d-block text-center overflow-hidden text-nowrap my-2 "
|
class="d-block text-center overflow-hidden text-nowrap my-2 "
|
||||||
style="
|
style="
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #333;
|
color: #333;
|
||||||
">{record._file.path}</a
|
">{queryRecord.record._file.path}</a
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="lx-small-text text-muted d-block text-center"
|
class="lx-small-text text-muted d-block text-center"
|
||||||
>{record._file.mime}</span
|
>{queryRecord.record._file.mime}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<script>
|
<script>
|
||||||
import Tools from "./tools/Tools.svelte";
|
// import Tools from "./tools/Tools.svelte";
|
||||||
import Pagination from "./pagination/Pagination.svelte";
|
import Pagination from "./pagination/Pagination.svelte";
|
||||||
import ActionsOnSelected from "./ActionsOnSelected.svelte";
|
import ActionsOnSelected from "./ActionsOnSelected.svelte";
|
||||||
import Table from "./Table.svelte";
|
import Table from "./Table.svelte";
|
||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
import Grid from "./Grid.svelte";
|
import Grid from "./Grid.svelte";
|
||||||
|
import Tools from "./tools/Tools.svelte";
|
||||||
|
|
||||||
const axios = getContext("axios");
|
const axios = getContext("axios");
|
||||||
export let schema;
|
export let schema;
|
||||||
export let users;
|
export let users;
|
||||||
export let records;
|
export let records;
|
||||||
export let graph;
|
|
||||||
// export let visibleFields;
|
// export let visibleFields;
|
||||||
export let systemFields;
|
export let systemFields;
|
||||||
export let sortParam;
|
export let sortParam;
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper-large transparent ">
|
<div class="wrapper-large transparent ">
|
||||||
<div class="lx-card mb-4 {inModal ? 'mt-0' : 'mt-5'}">
|
<div class="lx-card mb-4 mt-0">
|
||||||
<h3 class="header-normal mb-5 ">
|
<h3 class="header-normal mb-5 ">
|
||||||
{schema.label}
|
{schema.label}
|
||||||
</h3>
|
</h3>
|
||||||
@@ -62,7 +62,6 @@
|
|||||||
{sortField}
|
{sortField}
|
||||||
{operators}
|
{operators}
|
||||||
{filter}
|
{filter}
|
||||||
{graph}
|
|
||||||
{inModal}
|
{inModal}
|
||||||
{modalUrl}
|
{modalUrl}
|
||||||
{isWritable}
|
{isWritable}
|
||||||
@@ -73,7 +72,6 @@
|
|||||||
{#if schema.type === "collection"}
|
{#if schema.type === "collection"}
|
||||||
<Table
|
<Table
|
||||||
{records}
|
{records}
|
||||||
{graph}
|
|
||||||
{schema}
|
{schema}
|
||||||
{sortParam}
|
{sortParam}
|
||||||
{sortField}
|
{sortField}
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
|
|
||||||
export let schema;
|
export let schema;
|
||||||
export let users;
|
export let users;
|
||||||
export let graph;
|
export let queryRecord;
|
||||||
export let record;
|
|
||||||
export let sortParam;
|
export let sortParam;
|
||||||
export let sortField;
|
export let sortField;
|
||||||
export let visibleColumns;
|
export let visibleColumns;
|
||||||
@@ -20,7 +19,7 @@
|
|||||||
class="field-ui-{field.info.name}"
|
class="field-ui-{field.info.name}"
|
||||||
class:is-sort={field.name === sortField.name}
|
class:is-sort={field.name === sortField.name}
|
||||||
>
|
>
|
||||||
<RenderField {record} {schema} {graph} {field}/>
|
<RenderField {queryRecord} {field}/>
|
||||||
</td>
|
</td>
|
||||||
{/each}
|
{/each}
|
||||||
{#if schema.visible.includes("status")}
|
{#if schema.visible.includes("status")}
|
||||||
@@ -28,7 +27,7 @@
|
|||||||
class="text-center"
|
class="text-center"
|
||||||
class:is-sort={"-status" == sortParam || "status" == sortParam}
|
class:is-sort={"-status" == sortParam || "status" == sortParam}
|
||||||
>
|
>
|
||||||
<Status status={record.status}/>
|
<Status status={queryRecord.record.status}/>
|
||||||
</td>
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
{#if schema.visible.includes("_sys.createdBy")}
|
{#if schema.visible.includes("_sys.createdBy")}
|
||||||
@@ -36,7 +35,7 @@
|
|||||||
class="text-center"
|
class="text-center"
|
||||||
class:is-sort={"-_sys.createdBy" == sortParam || "_sys.createdBy" == sortParam}
|
class:is-sort={"-_sys.createdBy" == sortParam || "_sys.createdBy" == sortParam}
|
||||||
>
|
>
|
||||||
<Avatar name={usernameById(users, record._sys.createdBy)} side={24}/>
|
<Avatar name={usernameById(users, queryRecord.record._sys.createdBy)} side={24}/>
|
||||||
</td>
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
{#if schema.visible.includes("_sys.updatedBy")}
|
{#if schema.visible.includes("_sys.updatedBy")}
|
||||||
@@ -44,16 +43,16 @@
|
|||||||
class="text-center"
|
class="text-center"
|
||||||
class:is-sort={"-_sys.updatedBy" == sortParam || "_sys.updatedBy" == sortParam}
|
class:is-sort={"-_sys.updatedBy" == sortParam || "_sys.updatedBy" == sortParam}
|
||||||
>
|
>
|
||||||
<Avatar name={usernameById(users, record._sys.updatedBy)} side={24}/>
|
<Avatar name={usernameById(users, queryRecord.record._sys.updatedBy)} side={24}/>
|
||||||
</td>
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
{#if schema.visible.includes("_sys.createdAt")}
|
{#if schema.visible.includes("_sys.createdAt")}
|
||||||
<td class:is-sort={"-_sys.createdAt" == sortParam || "_sys.createdAt" == sortParam}>
|
<td class:is-sort={"-_sys.createdAt" == sortParam || "_sys.createdAt" == sortParam}>
|
||||||
{friendlyDate(record._sys.createdAt)}
|
{friendlyDate(queryRecord.record._sys.createdAt)}
|
||||||
</td>
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
{#if schema.visible.includes("_sys.updatedAt")}
|
{#if schema.visible.includes("_sys.updatedAt")}
|
||||||
<td class:is-sort={"-_sys.updatedAt" == sortParam || "_sys.updatedAt" == sortParam}>
|
<td class:is-sort={"-_sys.updatedAt" == sortParam || "_sys.updatedAt" == sortParam}>
|
||||||
{friendlyDate(record._sys.updatedAt)}
|
{friendlyDate(queryRecord.record._sys.updatedAt)}
|
||||||
</td>
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -29,17 +29,13 @@
|
|||||||
file: File,
|
file: File,
|
||||||
};
|
};
|
||||||
export let field;
|
export let field;
|
||||||
export let schema;
|
export let queryRecord;
|
||||||
export let record;
|
|
||||||
export let graph;
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={renderElements[field.info.name]}
|
this={renderElements[field.info.name]}
|
||||||
value={record.data[field.name]}
|
value={queryRecord.record.data[field.name]}
|
||||||
{record}
|
{queryRecord}
|
||||||
{graph}
|
|
||||||
{schema}
|
|
||||||
{field}
|
{field}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
export let schema;
|
export let schema;
|
||||||
export let users;
|
export let users;
|
||||||
export let records;
|
export let records;
|
||||||
export let graph;
|
|
||||||
export let systemFields;
|
export let systemFields;
|
||||||
export let sortParam;
|
export let sortParam;
|
||||||
export let sortField;
|
export let sortField;
|
||||||
@@ -64,7 +63,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each records as record (record.id)}
|
{#each records as queryRecord (queryRecord.record.id)}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="title-td">
|
<td class="title-td">
|
||||||
<div
|
<div
|
||||||
@@ -74,13 +73,13 @@
|
|||||||
{#if isWritable}
|
{#if isWritable}
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
on:change={() => select(record)}
|
on:change={() => select(queryRecord.record)}
|
||||||
class="form-check-input "
|
class="form-check-input "
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={selected.find(
|
checked={selected.find(
|
||||||
(r) => r.id === record.id
|
(r) => r.id === queryRecord.record.id
|
||||||
)}
|
)}
|
||||||
value={record}
|
value={queryRecord}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -88,20 +87,20 @@
|
|||||||
<a
|
<a
|
||||||
|
|
||||||
class="me-2 text-decoration-none text-dark fs-6"
|
class="me-2 text-decoration-none text-dark fs-6"
|
||||||
href="{channel.lucentUrl}/records/{record.id}"
|
href="{channel.lucentUrl}/records/{queryRecord.record.id}"
|
||||||
target={inModal ? "_blank" : "_self"}
|
target={inModal ? "_blank" : "_self"}
|
||||||
title={previewTitle(channel.schemas, record, graph)}
|
title={previewTitle(queryRecord.record)}
|
||||||
data-bs-toggle="tooltip" data-bs-placement="left"
|
data-bs-toggle="tooltip" data-bs-placement="left"
|
||||||
|
|
||||||
>
|
>
|
||||||
{previewTitle(channel.schemas, record, graph)}
|
{previewTitle(queryRecord.record)}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Avatar
|
<Avatar
|
||||||
name={usernameById(
|
name={usernameById(
|
||||||
users,
|
users,
|
||||||
record._sys.updatedBy
|
queryRecord.record._sys.updatedBy
|
||||||
)}
|
)}
|
||||||
side={24}
|
side={24}
|
||||||
/>
|
/>
|
||||||
@@ -109,8 +108,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<RecordRow
|
<RecordRow
|
||||||
{record}
|
{queryRecord}
|
||||||
{graph}
|
|
||||||
{schema}
|
{schema}
|
||||||
{visibleColumns}
|
{visibleColumns}
|
||||||
{sortParam}
|
{sortParam}
|
||||||
|
|||||||
@@ -1,18 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import Preview from "../../files/Preview.svelte";
|
import Preview from "../../files/Preview.svelte";
|
||||||
|
|
||||||
export let record;
|
export let queryRecord;
|
||||||
export let field;
|
export let field;
|
||||||
export let graph;
|
|
||||||
|
|
||||||
|
let filePreviews = queryRecord?._children[field.name];
|
||||||
|
|
||||||
let filePreviews = graph.edges?.filter((ed) => ed.field === field.name && ed.source === record.id)
|
|
||||||
.map((ed) => graph.records.find((r) => r.id === ed.target));
|
|
||||||
// if (edges[0]) {
|
|
||||||
// firstRecord = record._children.find((r) => r.data.id === edges[0].to);
|
|
||||||
// }
|
|
||||||
|
|
||||||
console.log(filePreviews)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- {#if firstRecord}
|
<!-- {#if firstRecord}
|
||||||
|
|||||||
@@ -1,25 +1,16 @@
|
|||||||
<script>
|
<script>
|
||||||
import PreviewCardSmall from "../../records/PreviewCardSmall.svelte";
|
import PreviewCardSmall from "../../records/PreviewCardSmall.svelte";
|
||||||
|
|
||||||
export let record;
|
export let queryRecord;
|
||||||
export let field;
|
export let field;
|
||||||
export let schemas;
|
$: recordEdges = queryRecord?._children[field.name];
|
||||||
export let graph;
|
|
||||||
|
|
||||||
$: recordEdges =
|
|
||||||
graph.edges
|
|
||||||
?.filter((ed) => ed.field === field.name && ed.source === record.id)
|
|
||||||
.map((edge) => {
|
|
||||||
return graph.records.find((r) => r.id === edge.target);
|
|
||||||
})
|
|
||||||
.filter((record) => (!record ? false : true)) ?? [];
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="references">
|
<div class="references">
|
||||||
{#each recordEdges as recordEdge}
|
{#each recordEdges as recordEdge}
|
||||||
<span class="mr-3">
|
<span class="mr-3">
|
||||||
<PreviewCardSmall {schemas} {graph} record={recordEdge}/>
|
<PreviewCardSmall record={recordEdge}/>
|
||||||
</span>
|
</span>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
export let value;
|
export let value;
|
||||||
export let inModal;
|
export let inModal;
|
||||||
export let modalUrl;
|
export let modalUrl;
|
||||||
export let graph;
|
export let records
|
||||||
|
|
||||||
let filter = {
|
let filter = {
|
||||||
label: "",
|
label: "",
|
||||||
@@ -51,13 +51,13 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterRecord = extractFilterRecord(graph, value);
|
const filterRecord = extractFilterRecord(records, value);
|
||||||
|
|
||||||
function extractFilterRecord(graph, value) {
|
function extractFilterRecord(records, value) {
|
||||||
if (!filter.isReference) {
|
if (!filter.isReference) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return graph.records.find(r => r.id === value);
|
return records.find(r => r.id === value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFilter(k) {
|
function removeFilter(k) {
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
<span class="applied-filter d-inline-block border border-primary rounded lx-small-text me-1 px-2 py-1">
|
<span class="applied-filter d-inline-block border border-primary rounded lx-small-text me-1 px-2 py-1">
|
||||||
<div class="d-flex align-items-center justify-content-center">
|
<div class="d-flex align-items-center justify-content-center">
|
||||||
{#if filter.isReference && filterRecord}
|
{#if filter.isReference && filterRecord}
|
||||||
{filter.label} is {previewTitle(channel.schemas, filterRecord)}
|
{filter.label} is {previewTitle(filterRecord)}
|
||||||
{:else}
|
{:else}
|
||||||
{filter.label} {operators.find((o) => o.name === filter.operator)?.symbol ?? ""} {value}
|
{filter.label} {operators.find((o) => o.name === filter.operator)?.symbol ?? ""} {value}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
on:keypress={(e) => apply(e, option)}
|
on:keypress={(e) => apply(e, option)}
|
||||||
>
|
>
|
||||||
<span class="dropdown-item">
|
<span class="dropdown-item">
|
||||||
{previewTitle(channel.schemas, option)}
|
{previewTitle( option)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
export let modalUrl;
|
export let modalUrl;
|
||||||
export let isWritable;
|
export let isWritable;
|
||||||
export let records;
|
export let records;
|
||||||
export let graph;
|
|
||||||
export let systemFields = [];
|
export let systemFields = [];
|
||||||
// export let visibleFields = [];
|
// export let visibleFields = [];
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@
|
|||||||
if (inModal) {
|
if (inModal) {
|
||||||
dispatch("refresh", url);
|
dispatch("refresh", url);
|
||||||
} else {
|
} else {
|
||||||
window.location = url;
|
window.location.href = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -152,7 +151,7 @@
|
|||||||
value={v}
|
value={v}
|
||||||
{inModal}
|
{inModal}
|
||||||
{modalUrl}
|
{modalUrl}
|
||||||
{graph}
|
{records}
|
||||||
on:refresh
|
on:refresh
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<script>
|
||||||
|
import {createEventDispatcher, getContext} from "svelte";
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
const channel = getContext("channel");
|
||||||
|
let isOpen = false;
|
||||||
|
|
||||||
|
export function open() {
|
||||||
|
isOpen = true;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<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">
|
||||||
|
|
||||||
|
|
||||||
|
<button
|
||||||
|
on:click|preventDefault={(e) => (isOpen = false)}
|
||||||
|
type="button"
|
||||||
|
class="btn-close"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Close"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.modal-dialog {
|
||||||
|
width: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
margin: 40px auto;
|
||||||
|
width: auto;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
export function sortByField(from, to, edges, fieldName) {
|
export function sortByField(from, to, queryRecords, fieldName) {
|
||||||
if (from === to) {
|
if (from === to) {
|
||||||
return edges;
|
return queryRecords;
|
||||||
}
|
}
|
||||||
let edgesTosort = edges?.filter((ed) => ed.field === fieldName && ed.depth === 1 ) ?? [];
|
let edgesTosort = queryRecords?.filter((qr) => qr.edge.field === fieldName && qr.edge.depth === 1 ) ?? [];
|
||||||
let remainingEdge = edges?.filter((ed) => !(ed.field === fieldName && ed.depth === 1)) ?? [];
|
let remainingEdge = queryRecords?.filter((qr) => !(qr.edge.field === fieldName && qr.edge.depth === 1)) ?? [];
|
||||||
|
|
||||||
edgesTosort = array_move(edgesTosort,from, to);
|
edgesTosort = array_move(edgesTosort,from, to);
|
||||||
return [...remainingEdge, ...edgesTosort];
|
return [...remainingEdge, ...edgesTosort];
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<div class="wrapper-normal transparent">
|
<div class="wrapper-normal transparent">
|
||||||
|
|
||||||
<h3 class="header-small mb-4 mt-5">Latest Content changes</h3>
|
<h3 class="header-small mb-4 ">Latest Content changes</h3>
|
||||||
{#if records.length > 0}
|
{#if records.length > 0}
|
||||||
<div class="lx-card mb-4">
|
<div class="lx-card mb-4">
|
||||||
<div class="lx-table p-0">
|
<div class="lx-table p-0">
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
export let sortableClass = "";
|
export let sortableClass = "";
|
||||||
// export let handle;
|
// export let handle;
|
||||||
export let isTable = false;
|
export let isTable = false;
|
||||||
export let sortableInstance;
|
export let sortableInstance = null;
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let sortableContainer;
|
let sortableContainer;
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
easing: "cubic-bezier(1, 0, 0, 1)",
|
easing: "cubic-bezier(1, 0, 0, 1)",
|
||||||
onUpdate: function (/**Event*/ evt) {
|
onUpdate: function (/**Event*/ evt) {
|
||||||
// reorder(evt.oldIndex,evt.newIndex);
|
// reorder(evt.oldIndex,evt.newIndex);
|
||||||
console.log(evt)
|
// console.log(evt)
|
||||||
dispatch("update", {
|
dispatch("update", {
|
||||||
source: evt.oldIndex,
|
source: evt.oldIndex,
|
||||||
target: evt.newIndex,
|
target: evt.newIndex,
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<script>
|
||||||
|
import File from "./includes/File.svelte";
|
||||||
|
import {previewTitle} from "../records/Preview.js";
|
||||||
|
import {getContext} from "svelte";
|
||||||
|
import Status from "../records/Status.svelte";
|
||||||
|
|
||||||
|
const channel = getContext("channel");
|
||||||
|
export let record;
|
||||||
|
|
||||||
|
export let edge = null;
|
||||||
|
let schema = channel.schemas.find(s => s.name === record.schema);
|
||||||
|
let types = ["inline", "card"];
|
||||||
|
export let type = "inline";
|
||||||
|
if (!types.includes(type)) {
|
||||||
|
console.error("unknown preview type")
|
||||||
|
}
|
||||||
|
export let editable = false;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<div class="preview-card">
|
||||||
|
{#if edge?.data}
|
||||||
|
<div class="preview-card-edge">Edge Data</div>
|
||||||
|
{/if}
|
||||||
|
<div class="d-flex column-gap-3">
|
||||||
|
{#if record._file}
|
||||||
|
<div>
|
||||||
|
<File {record}/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="d-flex flex-md-column " style="line-height: 22px">
|
||||||
|
<a class="text-decoration-none" target="_blank" href="{channel.lucentUrl}/records/{record.id}">{previewTitle(record)}</a>
|
||||||
|
<span class="d-flex gap-1 text-muted">
|
||||||
|
{#if record.status === "draft"}
|
||||||
|
<Status status={record.status}/>
|
||||||
|
{/if}
|
||||||
|
{schema.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.preview-card {
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-card-edge {
|
||||||
|
position: absolute;
|
||||||
|
top: -28px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 0px 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 7px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!--{#if record._file && type === "inline"}-->
|
||||||
|
<!--<!– <FilePreviewInline {record} {edge} {editable}/>–>-->
|
||||||
|
<!--{:else if record._file && type === "card"}-->
|
||||||
|
<!-- <FilePreviewCard {record} {edge} {editable}/>-->
|
||||||
|
<!--{:else if type === "inline"}-->
|
||||||
|
<!--<!– <DocPreviewCard {record} {edge} {editable}/>–>-->
|
||||||
|
<!--{:else if type === "card"}-->
|
||||||
|
<!--<!– <DocPreviewCard {record} {edge} {editable}/>–>-->
|
||||||
|
<!--{/if}-->
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<script>
|
||||||
|
import {imgurl} from "../../files/imageserver.js";
|
||||||
|
import {getContext} from "svelte";
|
||||||
|
import Icon from "../../common/Icon.svelte";
|
||||||
|
const channel = getContext("channel");
|
||||||
|
export let record;
|
||||||
|
|
||||||
|
|
||||||
|
export let size = "tiny";
|
||||||
|
let imageSide;
|
||||||
|
let fileSide;
|
||||||
|
let fontSize;
|
||||||
|
if (size === "large") {
|
||||||
|
imageSide = 256;
|
||||||
|
fileSide = 32;
|
||||||
|
fontSize = "20";
|
||||||
|
} else if (size === "medium") {
|
||||||
|
imageSide = 128;
|
||||||
|
fileSide = 12;
|
||||||
|
fontSize = "17";
|
||||||
|
} else if (size === "small") {
|
||||||
|
imageSide = 64;
|
||||||
|
fileSide = 12;
|
||||||
|
fontSize = "15";
|
||||||
|
} else if (size === "tiny") {
|
||||||
|
imageSide = 42;
|
||||||
|
fileSide = 12;
|
||||||
|
fontSize = "13";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if record}
|
||||||
|
{#if record._file.mime.startsWith("image")}
|
||||||
|
<!-- href={imgurl(record)} -->
|
||||||
|
<a
|
||||||
|
href="{channel.lucentUrl}/records/{record.id}"
|
||||||
|
title={record._file.path}
|
||||||
|
class="d-flex align-items-center justify-content-center "
|
||||||
|
style="width:{imageSide}px;height:{imageSide}px"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="rounded w-100"
|
||||||
|
src={imgurl(record)}
|
||||||
|
alt={record._file.path}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<a
|
||||||
|
href="{channel.lucentUrl}/records/{record.id}"
|
||||||
|
title={record._file.path}
|
||||||
|
class="btn btn-outline-primary btn-sm d-flex align-items-center justify-content-center"
|
||||||
|
style="width:{imageSide}px;height:{imageSide}px"
|
||||||
|
>
|
||||||
|
<Icon icon="file" width={fileSide} height={fileSide}/>
|
||||||
|
<span class="ms-2" style="font-size:{fontSize}px"
|
||||||
|
>.{record._file.path.split(".").pop()}</span
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
|
||||||
export let schema;
|
export let schema;
|
||||||
export let isCreateMode;
|
|
||||||
export let active = "";
|
export let active = "";
|
||||||
|
|
||||||
let tabs = schema.groups?.map((group) => {
|
let tabs = schema.groups?.map((group) => {
|
||||||
@@ -11,28 +9,12 @@
|
|||||||
label: "Main",
|
label: "Main",
|
||||||
name: "",
|
name: "",
|
||||||
};
|
};
|
||||||
let graphTab = {
|
|
||||||
label: "Graph",
|
|
||||||
name: "_graph",
|
|
||||||
};
|
|
||||||
if (isCreateMode) {
|
|
||||||
tabs = [mainTab, ...tabs];
|
|
||||||
} else {
|
|
||||||
tabs = [mainTab, ...tabs, graphTab];
|
|
||||||
}
|
|
||||||
|
|
||||||
function showGraph(e) {
|
tabs = [mainTab, ...tabs];
|
||||||
e.preventDefault();
|
|
||||||
active = "_graph";
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeTab(e, tabName) {
|
function changeTab(e, tabName) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (tabName == "_graph") {
|
active = tabName;
|
||||||
showGraph(e);
|
|
||||||
} else {
|
|
||||||
active = tabName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,131 +1,60 @@
|
|||||||
<script>
|
<script>
|
||||||
import {afterUpdate, getContext, onMount} from "svelte";
|
import {getContext} from "svelte";
|
||||||
import {isEqual} from "lodash";
|
|
||||||
import Manager from "./Manager.svelte";
|
import Manager from "./Manager.svelte";
|
||||||
import EditHeader from "./EditHeader.svelte"
|
|
||||||
import StatusSelect from "./StatusSelect.svelte"
|
|
||||||
import FilePreview from "./FilePreview.svelte"
|
import FilePreview from "./FilePreview.svelte"
|
||||||
import ContentTabs from "./ContentTabs.svelte"
|
import Form from "./form/Form.svelte";
|
||||||
import FormField from "./FormField.svelte"
|
import axios from "axios";
|
||||||
import Graph from "./Graph.svelte"
|
|
||||||
import Info from "./Info.svelte"
|
|
||||||
import ErrorAlert from "../common/ErrorAlert.svelte"
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
|
|
||||||
export let schema;
|
export let schema;
|
||||||
export let record;
|
export let record;
|
||||||
export let graph = {
|
export let graph = [];
|
||||||
records: [],
|
|
||||||
edges: []
|
|
||||||
};
|
|
||||||
export let recordHistory;
|
export let recordHistory;
|
||||||
export let isCreateMode;
|
export let isCreateMode;
|
||||||
export let isWritable = false;
|
// export let isWritable = false;
|
||||||
export let users;
|
// export let users;
|
||||||
let originalContent;
|
|
||||||
let activeContentTab = "";
|
|
||||||
$: hasUnsavedData = false;
|
|
||||||
$: validationErrors = null;
|
$: validationErrors = null;
|
||||||
$: errorMessage = validationErrors
|
$: errorMessage = null;
|
||||||
? `Record submission failed. ${
|
|
||||||
Object.entries(validationErrors).length
|
|
||||||
} error(s)`
|
|
||||||
: null;
|
|
||||||
|
|
||||||
let activeFields = schema.fields.filter(
|
let form;
|
||||||
(f) => f.name !== "id"
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
setOriginalContent();
|
|
||||||
});
|
|
||||||
|
|
||||||
function setOriginalContent() {
|
|
||||||
originalContent = {
|
|
||||||
data: JSON.parse(JSON.stringify(record.data)),
|
|
||||||
schema: record.schema,
|
|
||||||
status: record.status,
|
|
||||||
_sys: JSON.parse(JSON.stringify(record._sys)),
|
|
||||||
_file: JSON.parse(JSON.stringify(record._file)),
|
|
||||||
edges: JSON.parse(JSON.stringify(graph.edges)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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: record.data,
|
|
||||||
schema: record.schema,
|
|
||||||
status: record.status,
|
|
||||||
_sys: record._sys,
|
|
||||||
_file: record._file,
|
|
||||||
edges: graph.edges,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function save(e) {
|
function save(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
let status = e.detail.status
|
||||||
console.log("SAVE: Attempt");
|
console.log("SAVE: Attempt");
|
||||||
validationErrors = null;
|
validationErrors = null;
|
||||||
errorMessage = "";
|
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
if (!hasUnsavedData && !isCreateMode) {
|
|
||||||
resolve(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!record) {
|
|
||||||
resolve(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove trashed edges
|
// remove trashed edges
|
||||||
graph.edges = graph.edges?.filter((edge) => !edge._isTrashed && edge.source === record.id);
|
let replaceEdges = graph
|
||||||
|
.map((queryRecord) => queryRecord.edge)
|
||||||
|
.filter((edge) => !edge._isTrashed && edge.source === record.id);
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.post(channel.lucentUrl + "/records", {
|
.post(channel.lucentUrl + "/records", {
|
||||||
record: record,
|
schemaName: record.schema,
|
||||||
edges: graph.edges,
|
updateEdges: true,
|
||||||
|
id: record.id,
|
||||||
|
data: record.data,
|
||||||
|
edges: replaceEdges,
|
||||||
|
status: status,
|
||||||
isCreateMode: isCreateMode,
|
isCreateMode: isCreateMode,
|
||||||
})
|
})
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
console.log("SAVE: SAVED");
|
console.log("SAVE: SAVED");
|
||||||
|
|
||||||
if (isCreateMode) {
|
if (isCreateMode) {
|
||||||
window.location = channel.lucentUrl + "/records/" + record.id;
|
window.location.href = channel.lucentUrl + "/records/" + record.id;
|
||||||
} else {
|
} else {
|
||||||
record = response.data.records[0] ?? null;
|
record = response.data.record ?? null;
|
||||||
if (!record) {
|
if (!record) {
|
||||||
// means trashed
|
// means trashed
|
||||||
hasUnsavedData = false;
|
|
||||||
window.location = channel.lucentUrl;
|
window.location = channel.lucentUrl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
graph = response.data;
|
graph = [...response.data.graph];
|
||||||
setOriginalContent();
|
form.setOriginalData();
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(null);
|
resolve(null);
|
||||||
@@ -137,91 +66,36 @@
|
|||||||
errorMessage = error.response.data.error;
|
errorMessage = error.response.data.error;
|
||||||
} else {
|
} else {
|
||||||
validationErrors = error.response.data.error;
|
validationErrors = error.response.data.error;
|
||||||
console.log(validationErrors)
|
// console.log(validationErrors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolve(null);
|
resolve(null);
|
||||||
// msgSuccess = null;
|
|
||||||
// msgError = error.response.data.error;
|
|
||||||
// submitted = false;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:beforeunload={beforeUnload}/>
|
|
||||||
|
|
||||||
<div class="wrapper-normal transparent">
|
<div class="wrapper-normal transparent">
|
||||||
<Manager managerRecords={recordHistory} {graph}/>
|
<Manager managerRecords={recordHistory} {graph}/>
|
||||||
<EditHeader {schema} {record} {isCreateMode} {graph} bind:activeContentTab/>
|
|
||||||
|
|
||||||
{#if !["_graph", "_info"].includes(activeContentTab) && isWritable}
|
<FilePreview {record} {schema}/>
|
||||||
<div class="shadow-lg "
|
|
||||||
style="position:fixed;bottom:0;left:0px;width:100%;background: rgb(206, 223, 210);z-index:1050"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="d-flex mt-3 mb-3 align-items-center justify-content-center"
|
|
||||||
>
|
|
||||||
<StatusSelect bind:status={record.status} {record} {schema}/>
|
|
||||||
{#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>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<ErrorAlert message={errorMessage}/>
|
|
||||||
|
|
||||||
<div class=" mt-4" style="margin-bottom:150px">
|
<div class=" mt-4" style="margin-bottom:150px">
|
||||||
<ContentTabs
|
<Form
|
||||||
{schema}
|
bind:this={form}
|
||||||
{isCreateMode}
|
data={record.data}
|
||||||
bind:active={activeContentTab}
|
status={record.status}
|
||||||
/>
|
bind:graph
|
||||||
{#if !["_graph", "_info"].includes(activeContentTab)}
|
{schema}
|
||||||
<FilePreview {record} {schema}/>
|
{record}
|
||||||
<!-- <fieldset disabled="disabled"> -->
|
{isCreateMode}
|
||||||
{#each activeFields as field (field.name)}
|
{errorMessage}
|
||||||
{#if activeContentTab === field.group}
|
{validationErrors}
|
||||||
<FormField
|
on:save={save}
|
||||||
bind:data={record.data}
|
/>
|
||||||
bind:graph={graph}
|
<!-- <Graph {graph} {record}/>-->
|
||||||
{field}
|
<!-- <Info {record} {graph} {users} {schema}/>-->
|
||||||
{schema}
|
|
||||||
{record}
|
|
||||||
{validationErrors}
|
|
||||||
{isCreateMode}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
<!-- </fieldset> -->
|
|
||||||
{:else if activeContentTab === "_graph"}
|
|
||||||
<Graph {graph} {record}/>
|
|
||||||
{:else if activeContentTab === "_info"}
|
|
||||||
<Info {record} {graph} {users} {schema}/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,45 +2,49 @@
|
|||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
import Icon from "../common/Icon.svelte";
|
import Icon from "../common/Icon.svelte";
|
||||||
import {previewTitle} from "./Preview";
|
import {previewTitle} from "./Preview";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
export let schema;
|
export let schema;
|
||||||
export let graph;
|
|
||||||
export let record;
|
export let record;
|
||||||
|
export let title;
|
||||||
export let isCreateMode;
|
export let isCreateMode;
|
||||||
export let activeContentTab;
|
|
||||||
|
|
||||||
function clone(e) {
|
function clone(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
axios.post(channel.lucentUrl + "/records/clone/" + record.id).then(response => {
|
axios.post(channel.lucentUrl + "/records/clone/" + record.id).then(response => {
|
||||||
window.location = channel.lucentUrl + "/records/" + response.data.id;
|
window.location.href = channel.lucentUrl + "/records/" + response.data.id;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h3 class="header-normal mt-5 mb-0">
|
<h3 class="header-normal mb-0">
|
||||||
<a
|
<a
|
||||||
class="text-muted d-block text-decoration-none fs-6 mb-1"
|
class="text-muted d-block text-decoration-none fs-6 mb-1"
|
||||||
href="{channel.lucentUrl}/content/{schema.name}"
|
href="{channel.lucentUrl}/content/{schema.name}"
|
||||||
>{schema.label.toUpperCase()}</a
|
>{schema.label.toUpperCase()}</a
|
||||||
>
|
>
|
||||||
|
|
||||||
<span class="text-dark d-block">
|
<span class="text-dark d-block">
|
||||||
{#if !isCreateMode}
|
{#if !isCreateMode}
|
||||||
{previewTitle(channel.schemas, record, graph)}
|
{#if record}
|
||||||
|
{previewTitle(record)}
|
||||||
|
{:else}
|
||||||
|
{ title}
|
||||||
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
New Record
|
New Record
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{#if !isCreateMode}
|
{#if !isCreateMode && !!record}
|
||||||
<div class="dropdown d-inline-block">
|
<div class="dropdown d-inline-block">
|
||||||
<button
|
<button
|
||||||
class="btn btn-link btn-sm"
|
class="btn btn-link btn-sm"
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
>
|
||||||
<Icon icon="ellipsis"/>
|
<Icon icon="ellipsis"/>
|
||||||
</button>
|
</button>
|
||||||
@@ -48,25 +52,25 @@
|
|||||||
|
|
||||||
<h6 class="dropdown-header">Record Actions</h6>
|
<h6 class="dropdown-header">Record Actions</h6>
|
||||||
<a
|
<a
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
href="{channel.lucentUrl}/records/new?schema={schema.name}"
|
href="{channel.lucentUrl}/records/new?schema={schema.name}"
|
||||||
>Create new</a
|
>Create new</a
|
||||||
>
|
>
|
||||||
{#if !isCreateMode}
|
{#if !isCreateMode}
|
||||||
<a
|
<a
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
on:click={clone}
|
on:click={clone}
|
||||||
href={channel.lucentUrl}
|
href={channel.lucentUrl}
|
||||||
>
|
>
|
||||||
Clone
|
Clone
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
<a
|
<!-- <a-->
|
||||||
on:click|preventDefault={(e) =>
|
<!-- on:click|preventDefault={(e) =>-->
|
||||||
(activeContentTab = "_info")}
|
<!-- (activeContentTab = "_info")}-->
|
||||||
class="dropdown-item"
|
<!-- class="dropdown-item"-->
|
||||||
href="{channel.lucentUrl}">Revisions</a
|
<!-- href="{channel.lucentUrl}">Revisions</a-->
|
||||||
>
|
<!-- >-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
<script>
|
<script>
|
||||||
import Text from "./elements/Text.svelte";
|
import Text from "./form/fields/Text.svelte";
|
||||||
import Slug from "./elements/Slug.svelte";
|
import Slug from "./form/fields/Slug.svelte";
|
||||||
import Reference from "./elements/Reference.svelte";
|
import Reference from "./elements/Reference.svelte";
|
||||||
import ReferenceInline from "./elements/ReferenceInline.svelte";
|
import ReferenceInline from "./elements/ReferenceInline.svelte";
|
||||||
import Block from "./block/Block.svelte";
|
import Block from "./block/Block.svelte";
|
||||||
import Color from "./elements/Color.svelte";
|
import Color from "./form/fields/Color.svelte";
|
||||||
import Checkbox from "./elements/Checkbox.svelte";
|
import Checkbox from "./form/fields/Checkbox.svelte";
|
||||||
import Number from "./elements/Number.svelte";
|
import Number from "./form/fields/Number.svelte";
|
||||||
import Url from "./elements/Url.svelte";
|
import Date from "./form/fields/Date.svelte";
|
||||||
import Date from "./elements/Date.svelte";
|
import UUID from "./form/fields/UUID.svelte";
|
||||||
import UUID from "./elements/UUID.svelte";
|
import File from "./form/references/File.svelte";
|
||||||
import File from "./elements/File.svelte";
|
import Textarea from "./form/fields/Textarea.svelte";
|
||||||
import Textarea from "./elements/Textarea.svelte";
|
import Datetime from "./form/fields/Datetime.svelte";
|
||||||
import Datetime from "./elements/Datetime.svelte";
|
import RichEditor from "./form/fields/RichEditor.svelte";
|
||||||
import RichEditor from "./elements/RichEditor.svelte";
|
import Json from "./form/fields/JSON.svelte";
|
||||||
import Json from "./elements/JSON.svelte";
|
import Markdown from "./form/fields/Markdown.svelte";
|
||||||
import Markdown from "./elements/Markdown.svelte";
|
import FieldHeader from "./form/FieldHeader.svelte";
|
||||||
import FieldHeader from "./elements/FieldHeader.svelte";
|
|
||||||
import ReferenceTable from "./elements/ReferenceTable.svelte";
|
import ReferenceTable from "./elements/ReferenceTable.svelte";
|
||||||
import ReferenceTags from "./elements/ReferenceTags.svelte";
|
import ReferenceTags from "./form/references/ReferenceTags.svelte";
|
||||||
|
|
||||||
const formElements = {
|
const formElements = {
|
||||||
text: Text,
|
text: Text,
|
||||||
@@ -28,7 +27,6 @@
|
|||||||
color: Color,
|
color: Color,
|
||||||
checkbox: Checkbox,
|
checkbox: Checkbox,
|
||||||
number: Number,
|
number: Number,
|
||||||
url: Url,
|
|
||||||
date: Date,
|
date: Date,
|
||||||
datetime: Datetime,
|
datetime: Datetime,
|
||||||
uuid: UUID,
|
uuid: UUID,
|
||||||
@@ -48,7 +46,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card editor-field">
|
<div class="card editor-field">
|
||||||
<FieldHeader {schema} {field} {id}/>
|
<FieldHeader {field} {id}/>
|
||||||
{#if field.info.name === "reference" && field.layout === "inline"}
|
{#if field.info.name === "reference" && field.layout === "inline"}
|
||||||
<ReferenceInline
|
<ReferenceInline
|
||||||
bind:graph
|
bind:graph
|
||||||
@@ -72,16 +70,8 @@
|
|||||||
{field}
|
{field}
|
||||||
{validationErrors}
|
{validationErrors}
|
||||||
/>
|
/>
|
||||||
{:else if field.info.name === "reference"}
|
{:else if ["reference","file"].includes(field.info.name)}
|
||||||
<Reference
|
<File bind:graph {record} {field} />
|
||||||
bind:graph
|
|
||||||
{id}
|
|
||||||
{record}
|
|
||||||
{field}
|
|
||||||
{validationErrors}
|
|
||||||
/>
|
|
||||||
{:else if field.info.name === "file"}
|
|
||||||
<File bind:graph {record} {field} {validationErrors}/>
|
|
||||||
{:else if field.info.name === "block"}
|
{:else if field.info.name === "block"}
|
||||||
<Block
|
<Block
|
||||||
bind:graph
|
bind:graph
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import FormField from "./FormField.svelte";
|
import FormField from "./FormField.svelte";
|
||||||
import FilePreview from "./FilePreview.svelte";
|
import FilePreview from "./FilePreview.svelte";
|
||||||
import ContentTabs from "./ContentTabs.svelte";
|
import ContentTabs from "./ContentTabs.svelte";
|
||||||
import StatusSelect from "./StatusSelect.svelte";
|
import StatusSelect from "./form/StatusSelect.svelte";
|
||||||
import ErrorAlert from "../common/ErrorAlert.svelte";
|
import ErrorAlert from "../common/ErrorAlert.svelte";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
|
|||||||
@@ -1,31 +1,17 @@
|
|||||||
import Mustache from "mustache";
|
import Mustache from "mustache";
|
||||||
import {stripHtml} from "../../helpers";
|
import {stripHtml} from "../../helpers";
|
||||||
|
import {getContext} from "svelte";
|
||||||
|
|
||||||
export function previewTitle(schemas, record, graph) {
|
export function previewTitle(record) {
|
||||||
let schema = schemas.find((aSchema) => aSchema.name === record?.schema);
|
const channel = getContext("channel");
|
||||||
|
let schema = channel.schemas.find((aSchema) => aSchema.name === record?.schema);
|
||||||
|
|
||||||
if (!schema?.titleTemplate) {
|
if (!schema?.titleTemplate) {
|
||||||
return noTemplate(schema, record);
|
return noTemplate(schema, record);
|
||||||
}
|
}
|
||||||
|
|
||||||
let recordData = record.data;
|
|
||||||
let template = Mustache.parse(schema.titleTemplate);
|
let template = Mustache.parse(schema.titleTemplate);
|
||||||
|
let render = Mustache.render(schema.titleTemplate, record.data);
|
||||||
let referencePreviews = template
|
|
||||||
.filter(segment => segment[0] === "name") // keep only template tags
|
|
||||||
.map((segment) => segment[1]) // map to fieldNames
|
|
||||||
.filter(fieldName => { // keep only references
|
|
||||||
let schemaField = schema.fields.find(f => f.name === fieldName)
|
|
||||||
return schemaField?.info.name === "reference";
|
|
||||||
}).reduce((carry, field) => { // map to records
|
|
||||||
let edge = graph.edges.find(edge => edge.source === record.id && edge.field === field)
|
|
||||||
let referenceRecord = graph.records.find(rec => rec.id === edge?.target)
|
|
||||||
carry[field] = previewTitle(schemas, referenceRecord, graph);
|
|
||||||
return carry;
|
|
||||||
}, {});
|
|
||||||
recordData = {...recordData, ...referencePreviews}
|
|
||||||
|
|
||||||
let render = Mustache.render(schema.titleTemplate, recordData);
|
|
||||||
|
|
||||||
if (!render || render === "") {
|
if (!render || render === "") {
|
||||||
return noTemplate(schema, record);
|
return noTemplate(schema, record);
|
||||||
@@ -34,16 +20,35 @@ export function previewTitle(schemas, record, graph) {
|
|||||||
return stripHtml(render.slice(0, 300));
|
return stripHtml(render.slice(0, 300));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function previewEdgeTitle(edge) {
|
||||||
|
const channel = getContext("channel");
|
||||||
|
let edgeSchemaName = channel.schemas
|
||||||
|
.find((aSchema) => aSchema.name === edge?.sourceSchema)
|
||||||
|
.fields.find(f => f.name === edge.field).data;
|
||||||
|
let schema = channel.schemas.find((aSchema) => aSchema.name === edgeSchemaName);
|
||||||
|
if (!schema?.titleTemplate) {
|
||||||
|
return noTemplate(schema, edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
let template = Mustache.parse(schema.titleTemplate);
|
||||||
|
let render = Mustache.render(schema.titleTemplate, edge.data);
|
||||||
|
|
||||||
|
if (!render || render === "") {
|
||||||
|
return noTemplate(schema, edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stripHtml(render.slice(0, 300));
|
||||||
|
}
|
||||||
|
|
||||||
function noTemplate(schema, record) {
|
function noTemplate(schema, record) {
|
||||||
if (schema?.type === "files") {
|
if (schema?.type === "files") {
|
||||||
return record._file.path;
|
return record._file.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = stripHtml(
|
let title = stripHtml(
|
||||||
record?.data[schema.fields.filter((f) => f.info.name === "text")[0]?.name]
|
record?.data[schema.fields.filter((f) => f.info.name === "text")[0]?.name]
|
||||||
).slice(0, 300);
|
).slice(0, 300);
|
||||||
|
if (title === "") {
|
||||||
if(title == ""){
|
|
||||||
return "Untitled";
|
return "Untitled";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,65 +1,81 @@
|
|||||||
<script>
|
<script>
|
||||||
import Icon from "../common/Icon.svelte";
|
import Icon from "../common/Icon.svelte";
|
||||||
|
|
||||||
import { getContext, createEventDispatcher } from "svelte";
|
import {getContext, createEventDispatcher} from "svelte";
|
||||||
import Preview from "../files/Preview.svelte";
|
import {previewEdgeTitle, previewTitle} from "./Preview";
|
||||||
import { previewTitle } from "./Preview";
|
|
||||||
import Status from "./Status.svelte";
|
import Status from "./Status.svelte";
|
||||||
|
import Preview from "../newPreview/Preview.svelte";
|
||||||
|
import EdgeData from "./form/references/EdgeData.svelte";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
export let graph;
|
|
||||||
export let record;
|
export let record;
|
||||||
|
export let field;
|
||||||
|
export let edge = null;
|
||||||
|
export let editable = false;
|
||||||
export let classes = "";
|
export let classes = "";
|
||||||
export let hasDelete = false;
|
export let hasDelete = false;
|
||||||
|
|
||||||
|
let edgeData;
|
||||||
|
|
||||||
let schema = channel.schemas.find((aschema) => aschema.name === record.schema);
|
let schema = channel.schemas.find((aschema) => aschema.name === record.schema);
|
||||||
let cardTitle = previewTitle(channel.schemas, record, graph);
|
let cardTitle = previewTitle(record);
|
||||||
|
|
||||||
function remove(e) {
|
function remove(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
dispatch("remove", record.id);
|
dispatch("remove", record.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function edit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
edgeData.openEdit();
|
||||||
|
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
{#if editable}
|
||||||
|
<div
|
||||||
|
class="card mb-2 bg-light w-50 "
|
||||||
|
style="border-color:{schema.color ?? '#ccc'}; border-width: 1px;"
|
||||||
|
>
|
||||||
|
<div class="card-body">
|
||||||
|
<span class="text-muted d-block">Relation Data</span>
|
||||||
|
{previewEdgeTitle(edge)}
|
||||||
|
<div class="position-absolute d-flex end-0" style="top:5px">
|
||||||
|
<button
|
||||||
|
class="trash-button text-dark btn btn-sm btn-link"
|
||||||
|
on:click={edit}
|
||||||
|
>
|
||||||
|
<Icon icon="pencil"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<EdgeData bind:this={edgeData} {record} {field} bind:edge/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="card mb-2 bg-light w-100 {classes}"
|
||||||
|
style="border-color:{schema.color ?? '#ccc'}; border-width: 1px;"
|
||||||
|
>
|
||||||
|
<div class="card-body">
|
||||||
|
<Preview {record} type="card"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if hasDelete}
|
||||||
|
<div class="position-absolute d-flex end-0" style="top:5px">
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="trash-button text-dark btn btn-sm btn-link"
|
||||||
|
on:click={remove}
|
||||||
|
>
|
||||||
|
<Icon icon="trash-can"/>
|
||||||
|
</button>
|
||||||
|
|
||||||
class="card mb-2 bg-light {classes}"
|
|
||||||
style="border-color:{schema.color ?? '#ccc'}; border-width: 1px;"
|
|
||||||
>
|
|
||||||
<div class="card-body d-flex">
|
|
||||||
{#if schema.type === "files"}
|
|
||||||
<div style="max-width:94px;margin-right:15px">
|
|
||||||
<Preview {record} size="small" />
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="overflow-hidden">
|
|
||||||
<a
|
|
||||||
class="title-link m-0 fs-5 text-decoration-none text-dark d-block"
|
|
||||||
href="{channel.lucentUrl}/records/{record.id}"
|
|
||||||
title={cardTitle}
|
|
||||||
>
|
|
||||||
{cardTitle}
|
|
||||||
</a>
|
|
||||||
<small class="text-muted">
|
|
||||||
{schema.label}
|
|
||||||
</small>
|
|
||||||
<small class="text-muted">
|
|
||||||
{#if record.status === "draft"}
|
|
||||||
<Status status={record.status} />
|
|
||||||
{/if}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if hasDelete}
|
|
||||||
<div class="position-absolute end-0" style="top:5px">
|
|
||||||
<button
|
|
||||||
class="trash-button text-dark btn btn-sm btn-link"
|
|
||||||
on:click={remove}
|
|
||||||
><Icon icon="trash-can" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -67,9 +83,11 @@
|
|||||||
.card .trash-button {
|
.card .trash-button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card:hover .trash-button {
|
.card:hover .trash-button {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-link {
|
.title-link {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@@ -4,10 +4,9 @@
|
|||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
export let record;
|
export let record;
|
||||||
export let graph;
|
|
||||||
$: schema = channel.schemas.find((aschema) => aschema.name === record.schema);
|
$: schema = channel.schemas.find((aschema) => aschema.name === record.schema);
|
||||||
|
|
||||||
$: title = previewTitle(channel.schemas, record, graph);
|
$: title = previewTitle( record);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if record?.data}
|
{#if record?.data}
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {getContext} from "svelte";
|
|
||||||
import {getStatus, getStatusList} from "./StatusText";
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
|
||||||
export let status = "draft";
|
|
||||||
export let record;
|
|
||||||
export let schema;
|
|
||||||
let dropdown;
|
|
||||||
$: currentStatus = getStatus(status);
|
|
||||||
const statusList = Object.values(getStatusList());
|
|
||||||
|
|
||||||
function updateStatus(e, statusValue) {
|
|
||||||
// e.preventDefault();
|
|
||||||
status = statusValue;
|
|
||||||
dropdown.click();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- 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>
|
|
||||||
{#if channel.previewTarget}
|
|
||||||
<a href="{channel.previewTargetUrl}?schema={schema.name}&id={record.id}" target="_blank" class="btn btn-info ms-3">
|
|
||||||
Preview
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
import {sortByField} from "../../../edges/sortEdges";
|
import {sortByField} from "../../../edges/sortEdges";
|
||||||
import ReferenceInlineButtons from "../../elements/ReferenceInlineButtons.svelte"
|
import ReferenceInlineButtons from "../../elements/ReferenceInlineButtons.svelte"
|
||||||
import Sortable from "../../../libs/Sortable.svelte";
|
import Sortable from "../../../libs/Sortable.svelte";
|
||||||
import {insertEdges} from "../../elements/reference";
|
import {insertEdges} from "../../form/references/reference.js";
|
||||||
import BrowseModal from "../../elements/BrowseModal.svelte";
|
import BrowseModal from "../../form/references/BrowseModal.svelte";
|
||||||
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import {sortByField} from "../../../edges/sortEdges";
|
import {sortByField} from "../../../edges/sortEdges";
|
||||||
import ReferenceInlineButtons from "../../elements/ReferenceInlineButtons.svelte"
|
import ReferenceInlineButtons from "../../elements/ReferenceInlineButtons.svelte"
|
||||||
import Sortable from "../../../libs/Sortable.svelte";
|
import Sortable from "../../../libs/Sortable.svelte";
|
||||||
import {insertEdges} from "../../elements/reference";
|
import {insertEdges} from "../../form/references/reference.js";
|
||||||
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { deepEqual } from 'fast-equals';
|
||||||
|
export function isEqual(obj1, obj2) {
|
||||||
|
return deepEqual(obj1, obj2);
|
||||||
|
// if (obj1 === obj2) return true;
|
||||||
|
//
|
||||||
|
// if (Array.isArray(obj1) && Array.isArray(obj2)) {
|
||||||
|
//
|
||||||
|
// if(obj1.length !== obj2.length) return false;
|
||||||
|
//
|
||||||
|
// return obj1.every((elem, index) => {
|
||||||
|
// return isEqual(elem, obj2[index]);
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if(typeof obj1 === "object" && typeof obj2 === "object" && obj1 !== null && obj2 !== null) {
|
||||||
|
// if(Array.isArray(obj1) || Array.isArray(obj2)) return false;
|
||||||
|
//
|
||||||
|
// const keys1 = Object.keys(obj1)
|
||||||
|
// const keys2 = Object.keys(obj2)
|
||||||
|
//
|
||||||
|
// if(keys1.length !== keys2.length || !keys1.every(key => keys2.includes(key))) return false;
|
||||||
|
//
|
||||||
|
// for(let key in obj1) {
|
||||||
|
// if (!isEqual(obj1[key], obj2[key])) { return false; }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return true;
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return false;
|
||||||
|
}
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {getContext} from "svelte";
|
|
||||||
import {uniqBy} from "lodash";
|
|
||||||
import {sortByField} from "../../edges/sortEdges";
|
|
||||||
import PreviewCard from "../PreviewCard.svelte";
|
|
||||||
import Sortable from "../../libs/Sortable.svelte";
|
|
||||||
import BrowseModal from "./BrowseModal.svelte";
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
|
||||||
export let field;
|
|
||||||
export let record;
|
|
||||||
export let graph
|
|
||||||
|
|
||||||
let browseModal;
|
|
||||||
|
|
||||||
$: 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 collections = channel.schemas.filter((aschema) =>
|
|
||||||
field.collections.includes(aschema.name)
|
|
||||||
);
|
|
||||||
|
|
||||||
function removeReference(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
graph.edges = graph.edges.filter(
|
|
||||||
(edge) => !(edge.target === e.detail && edge.field === field.name)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function openBrowseModal(e, schema) {
|
|
||||||
e.preventDefault();
|
|
||||||
browseModal.open(schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function reorder(e) {
|
|
||||||
graph.edges = await sortByField(e.detail.source, e.detail.target, graph.edges, field.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function insert(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
browseModal.close();
|
|
||||||
const recordsToInsert = e.detail.records;
|
|
||||||
const action = e.detail.action;
|
|
||||||
let newEdges = recordsToInsert.map((r) => {
|
|
||||||
return {
|
|
||||||
target: r.id,
|
|
||||||
source: record.id,
|
|
||||||
sourceSchema: record.schema,
|
|
||||||
targetSchema: r.schema,
|
|
||||||
field: field.name,
|
|
||||||
rank: ""
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let replacedEdges = graph.edges ?? [];
|
|
||||||
if (action === "replace") {
|
|
||||||
replacedEdges = replacedEdges.filter((e) => e.field !== field.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
graph.records = uniqBy([...graph.records, ...recordsToInsert], (r) => r.id);
|
|
||||||
graph.edges = uniqBy([...replacedEdges, ...newEdges], (e) => e.target + e.field);
|
|
||||||
}
|
|
||||||
</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 references.length > 0}
|
|
||||||
<Sortable sortableClass="row row-cols-3 mt-3" on:update={reorder}>
|
|
||||||
{#each references as reference (reference.id)}
|
|
||||||
<div class="col mb-3">
|
|
||||||
<PreviewCard
|
|
||||||
classes="h-100"
|
|
||||||
record={reference}
|
|
||||||
hasDelete={true}
|
|
||||||
on:remove={removeReference}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</Sortable>
|
|
||||||
{/if}
|
|
||||||
<BrowseModal bind:this={browseModal} on:insert={insert}/>
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
import {insertEdges} from "./reference";
|
import {insertEdges} from "../form/references/reference.js";
|
||||||
import PreviewCard from "../PreviewCard.svelte";
|
import PreviewCard from "../PreviewCard.svelte";
|
||||||
import {getErrorMessage} from "./errorMessage";
|
import {getErrorMessage} from "../form/errorMessage.js";
|
||||||
import {sortByField} from "../../edges/sortEdges";
|
import {sortByField} from "../../edges/sortEdges";
|
||||||
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
|
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
|
||||||
import Sortable from "../../libs/Sortable.svelte";
|
import Sortable from "../../libs/Sortable.svelte";
|
||||||
@@ -14,11 +14,7 @@
|
|||||||
export let validationErrors;
|
export let validationErrors;
|
||||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||||
|
|
||||||
$: references = graph.edges
|
$: currentReferences = graph._children[field.name] ?? [];
|
||||||
.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 collections = channel.schemas.filter((aschema) =>
|
let collections = channel.schemas.filter((aschema) =>
|
||||||
field.collections.includes(aschema.name)
|
field.collections.includes(aschema.name)
|
||||||
@@ -56,9 +52,9 @@
|
|||||||
on:save={insert}
|
on:save={insert}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if references.length > 0}
|
{#if currentReferences.length > 0}
|
||||||
<Sortable sortableClass="row row-cols-3 mt-3" on:update={reorder}>
|
<Sortable sortableClass="row row-cols-3 mt-3" on:update={reorder}>
|
||||||
{#each references as reference (reference.id)}
|
{#each currentReferences as reference (reference.id)}
|
||||||
<div class="col mb-3">
|
<div class="col mb-3">
|
||||||
<PreviewCard
|
<PreviewCard
|
||||||
classes="h-100"
|
classes="h-100"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
import {uniqBy} from "lodash";
|
import {uniqBy} from "lodash";
|
||||||
import PreviewCardInline from "../PreviewCardInline.svelte";
|
import PreviewCardInline from "../PreviewCardInline.svelte";
|
||||||
import {getErrorMessage} from "./errorMessage";
|
import {getErrorMessage} from "../form/errorMessage.js";
|
||||||
import {sortByField} from "../../edges/sortEdges";
|
import {sortByField} from "../../edges/sortEdges";
|
||||||
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
|
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
|
||||||
import {flip} from "svelte/animate";
|
import {flip} from "svelte/animate";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import {createEventDispatcher, getContext} from "svelte";
|
import {createEventDispatcher, getContext} from "svelte";
|
||||||
import Icon from "../../common/Icon.svelte";
|
import Icon from "../../common/Icon.svelte";
|
||||||
import InlineEdit from "../InlineEdit.svelte";
|
import InlineEdit from "../InlineEdit.svelte";
|
||||||
import BrowseModal from "./BrowseModal.svelte";
|
import BrowseModal from "../form/references/BrowseModal.svelte";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
// export let field;
|
// export let field;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
import {previewTitle} from "../Preview";
|
import {previewTitle} from "../Preview";
|
||||||
import {getErrorMessage} from "./errorMessage";
|
import {getErrorMessage} from "../form/errorMessage.js";
|
||||||
import {sortByField} from "../../edges/sortEdges";
|
import {sortByField} from "../../edges/sortEdges";
|
||||||
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
|
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
|
||||||
import Sortable from "../../libs/Sortable.svelte";
|
import Sortable from "../../libs/Sortable.svelte";
|
||||||
import RenderField from "../../content/RenderField.svelte";
|
import RenderField from "../../content/RenderField.svelte";
|
||||||
import Icon from "../../common/Icon.svelte";
|
import Icon from "../../common/Icon.svelte";
|
||||||
import {insertEdges} from "./reference.js";
|
import {insertEdges} from "../form/references/reference.js";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
export let field;
|
export let field;
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { uniqueId } from "lodash";
|
|
||||||
import { getContext } from "svelte";
|
|
||||||
const channelurl = getContext("channelurl");
|
|
||||||
export let field;
|
|
||||||
export let value;
|
|
||||||
export let schema;
|
|
||||||
let id = uniqueId();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="mb-0">
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<label for={id} class="form-label">{field.label}</label>
|
|
||||||
<a
|
|
||||||
class="text-decoration-none"
|
|
||||||
href="{channelurl}/schemas/{schema.name}/fields/edit/{field.name}"
|
|
||||||
><code class="text-primary opacity-50">{field.name}</code></a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="url"
|
|
||||||
{id}
|
|
||||||
class="form-control"
|
|
||||||
bind:value
|
|
||||||
placeholder="https://www.example.com"
|
|
||||||
/>
|
|
||||||
{#if field.help}
|
|
||||||
<small class=" text-primary opacity-50">{field.help}</small>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export function getErrorMessage(validationErrors, fieldName) {
|
|
||||||
return validationErrors && validationErrors[fieldName]
|
|
||||||
? validationErrors[fieldName].message
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import {uniqBy} from "lodash";
|
|
||||||
|
|
||||||
export function insertEdges(graph, sourceRecord, targetRecords, fieldName, action = "") {
|
|
||||||
let newEdges = targetRecords.map((r) => {
|
|
||||||
return {
|
|
||||||
target: r.id,
|
|
||||||
source: sourceRecord.id,
|
|
||||||
sourceSchema: sourceRecord.schema,
|
|
||||||
targetSchema: r.schema,
|
|
||||||
field: fieldName,
|
|
||||||
depth: 1,
|
|
||||||
rank: ""
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let replacedEdges = graph.edges;
|
|
||||||
if (action === "replace") {
|
|
||||||
replacedEdges = replacedEdges.filter((edge) => edge.field !== field.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
graph.records = uniqBy([...graph.records, ...targetRecords], (r) => r.id);
|
|
||||||
graph.edges = uniqBy([...replacedEdges, ...newEdges], (edge) => edge.source + edge.target + edge.field + edge.depth);
|
|
||||||
return graph;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
<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", {
|
||||||
|
status: status
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
export let validationErrors = null;
|
||||||
|
export let 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.target+r.edge.field) ?? [])).sort(),
|
||||||
|
};
|
||||||
|
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.target+r.edge.field).sort() ?? [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:beforeunload={beforeUnload}/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<EditHeader {schema} {record} {isCreateMode} {title}/>
|
||||||
|
|
||||||
|
<div class=" mt-4" style="margin-bottom:150px">
|
||||||
|
<SaveButtons on:save={save} bind:status {hasUnsavedData} {isCreateMode}/>
|
||||||
|
<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,51 @@
|
|||||||
|
<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-between"
|
||||||
|
>
|
||||||
|
|
||||||
|
<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,64 @@
|
|||||||
|
<script>
|
||||||
|
import {getStatus, getStatusList} from "../StatusText.js";
|
||||||
|
import SwitchButton from "../../common/SwitchButton.svelte";
|
||||||
|
|
||||||
|
export let status = "draft";
|
||||||
|
let dropdown;
|
||||||
|
$: currentStatus = getStatus(status);
|
||||||
|
const statusList = Object.values(getStatusList());
|
||||||
|
|
||||||
|
function updateStatus(e, statusValue) {
|
||||||
|
// e.preventDefault();
|
||||||
|
status = statusValue;
|
||||||
|
dropdown.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchStatus(e){
|
||||||
|
console.log("Asf")
|
||||||
|
if(currentStatus.value === "draft"){
|
||||||
|
status = "published"
|
||||||
|
}
|
||||||
|
|
||||||
|
if(currentStatus.value === "published"){
|
||||||
|
status = "draft"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{#if status}
|
||||||
|
<!-- Example split danger button -->
|
||||||
|
<div class="form-check form-switch" >
|
||||||
|
<input on:click={switchStatus} class="form-check-input" type="checkbox" role="switch" id="record-status-switch" checked={status === "published"}>
|
||||||
|
<label class="form-check-label" for=record-status-switch>{currentStatus.text}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <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}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getErrorMessage } from "./errorMessage";
|
import { getErrorMessage } from "../form.js";
|
||||||
export let id;
|
export let id;
|
||||||
export let field;
|
export let field;
|
||||||
export let value;
|
export let value;
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getErrorMessage } from "./errorMessage";
|
import { getErrorMessage } from "../form.js";
|
||||||
export let field;
|
export let field;
|
||||||
export let value;
|
export let value;
|
||||||
export let isCreateMode;
|
export let isCreateMode;
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
import {debounce} from "lodash";
|
import {debounce} from "lodash";
|
||||||
import {previewTitle} from "../Preview";
|
import {previewTitle} from "../../Preview.js";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
export let field;
|
export let field;
|
||||||
+2
-2
@@ -4,8 +4,8 @@
|
|||||||
import flatpickr from "flatpickr";
|
import flatpickr from "flatpickr";
|
||||||
import "flatpickr/dist/flatpickr.css";
|
import "flatpickr/dist/flatpickr.css";
|
||||||
import "flatpickr/dist/themes/light.css";
|
import "flatpickr/dist/themes/light.css";
|
||||||
import {getErrorMessage} from "./errorMessage";
|
import { getErrorMessage } from "../form.js";
|
||||||
import Icon from "../../common/Icon.svelte";
|
import Icon from "../../../common/Icon.svelte";
|
||||||
|
|
||||||
export let field;
|
export let field;
|
||||||
export let value;
|
export let value;
|
||||||
+2
-2
@@ -4,8 +4,8 @@
|
|||||||
import flatpickr from "flatpickr";
|
import flatpickr from "flatpickr";
|
||||||
import "flatpickr/dist/flatpickr.css";
|
import "flatpickr/dist/flatpickr.css";
|
||||||
import "flatpickr/dist/themes/light.css";
|
import "flatpickr/dist/themes/light.css";
|
||||||
import {getErrorMessage} from "./errorMessage";
|
import { getErrorMessage } from "../form.js";
|
||||||
import Icon from "../../common/Icon.svelte";
|
import Icon from "../../../common/Icon.svelte";
|
||||||
|
|
||||||
export let field;
|
export let field;
|
||||||
export let value;
|
export let value;
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import Codemirror from "../../libs/Codemirror.svelte";
|
import Codemirror from "../../../libs/Codemirror.svelte";
|
||||||
import { getErrorMessage } from "./errorMessage";
|
import { getErrorMessage } from "../form.js";
|
||||||
|
|
||||||
|
|
||||||
export let value;
|
export let value;
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import Codemirror from "../../libs/CodemirrorMarkdown.svelte";
|
import Codemirror from "../../../libs/CodemirrorMarkdown.svelte";
|
||||||
import { getErrorMessage } from "./errorMessage";
|
import { getErrorMessage } from "../form.js";
|
||||||
|
|
||||||
|
|
||||||
export let value;
|
export let value;
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import Datalist from "./Datalist.svelte";
|
import Datalist from "./Datalist.svelte";
|
||||||
import {getErrorMessage} from "./errorMessage";
|
import { getErrorMessage } from "../form.js";
|
||||||
|
|
||||||
export let field;
|
export let field;
|
||||||
export let value;
|
export let value;
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import Tinymce from "../../libs/Tinymce.svelte";
|
import Tinymce from "../../../libs/Tinymce.svelte";
|
||||||
import {getErrorMessage} from "./errorMessage";
|
import { getErrorMessage } from "../form.js";
|
||||||
|
|
||||||
export let value;
|
export let value;
|
||||||
export let field;
|
export let field;
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getErrorMessage } from "./errorMessage";
|
import { getErrorMessage } from "../form.js";
|
||||||
export let field;
|
export let field;
|
||||||
export let value;
|
export let value;
|
||||||
export let isCreateMode;
|
export let isCreateMode;
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import Datalist from "./Datalist.svelte";
|
import Datalist from "./Datalist.svelte";
|
||||||
import Selectlist from "./Selectlist.svelte";
|
import Selectlist from "./Selectlist.svelte";
|
||||||
import {getErrorMessage} from "./errorMessage";
|
import { getErrorMessage } from "../form.js";
|
||||||
|
|
||||||
export let field;
|
export let field;
|
||||||
export let value;
|
export let value;
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { getErrorMessage } from "./errorMessage";
|
import { getErrorMessage } from "../form.js";
|
||||||
export let field;
|
export let field;
|
||||||
export let value;
|
export let value;
|
||||||
export let isCreateMode;
|
export let isCreateMode;
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import Icon from "../../common/Icon.svelte";
|
import Icon from "../../../common/Icon.svelte";
|
||||||
import { getErrorMessage } from "./errorMessage";
|
import { getErrorMessage } from "../form.js";
|
||||||
const channelurl = getContext("channelurl");
|
const channelurl = getContext("channelurl");
|
||||||
export let validationErrors;
|
export let validationErrors;
|
||||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function getErrorMessage(validationErrors, fieldName) {
|
||||||
|
return validationErrors?.find((e) => e.fieldName === fieldName)?.message;
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import {createEventDispatcher, getContext} from "svelte";
|
import {createEventDispatcher, getContext} from "svelte";
|
||||||
import Index from "../../content/Index.svelte";
|
import Index from "../../../content/Index.svelte";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
import {getContext} from "svelte";
|
||||||
|
import Form from "../Form.svelte";
|
||||||
|
import OffCanvas from "../../../common/OffCanvas.svelte";
|
||||||
|
import PreviewCard from "../../PreviewCard.svelte";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export let field;
|
||||||
|
export let record;
|
||||||
|
export let edge;
|
||||||
|
let form;
|
||||||
|
let offCanvas;
|
||||||
|
$: validationErrors = null;
|
||||||
|
$: errorMessage = null;
|
||||||
|
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("SAVE: Attempt");
|
||||||
|
validationErrors = null;
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
axios
|
||||||
|
.put(channel.lucentUrl + "/edges", edge)
|
||||||
|
.then(function (response) {
|
||||||
|
console.log("SAVE: SAVED");
|
||||||
|
edge = response.data;
|
||||||
|
form.setOriginalData();
|
||||||
|
resolve(null);
|
||||||
|
offCanvas.hide();
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
// setOriginalContent();
|
||||||
|
if (error.response) {
|
||||||
|
if (typeof error.response.data.error === "string") {
|
||||||
|
errorMessage = error.response.data.error;
|
||||||
|
} else {
|
||||||
|
validationErrors = error.response.data.error;
|
||||||
|
console.log(validationErrors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve(null);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<OffCanvas bind:this={offCanvas}>
|
||||||
|
<div class="p-4">
|
||||||
|
<PreviewCard
|
||||||
|
{record}
|
||||||
|
hasDelete={false}
|
||||||
|
editable={false}
|
||||||
|
{field}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form
|
||||||
|
bind:this={form}
|
||||||
|
data={edge.data}
|
||||||
|
title={"Relational Data for " + field.info.label}
|
||||||
|
{schema}
|
||||||
|
isCreateMode={false}
|
||||||
|
{errorMessage}
|
||||||
|
{validationErrors}
|
||||||
|
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}
|
||||||
|
bind:edge={reference.edge}
|
||||||
|
on:remove={removeReference}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</Sortable>
|
||||||
|
{/if}
|
||||||
|
<BrowseModal bind:this={browseModal} on:insert={insert}/>
|
||||||
+8
-8
@@ -1,14 +1,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
import {uniqBy, debounce} from "lodash";
|
import {uniqBy, debounce} from "lodash";
|
||||||
import {previewTitle} from "../Preview";
|
import {previewTitle} from "../../Preview.js";
|
||||||
import {getErrorMessage} from "./errorMessage";
|
import {getErrorMessage} from "../errorMessage.js";
|
||||||
import {sortByField} from "../../edges/sortEdges";
|
import {sortByField} from "../../../edges/sortEdges.js";
|
||||||
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
|
import ReferenceInlineButtons from "../../elements/ReferenceInlineButtons.svelte";
|
||||||
import Sortable from "../../libs/Sortable.svelte";
|
import Sortable from "../../../libs/Sortable.svelte";
|
||||||
import RenderField from "../../content/RenderField.svelte";
|
import RenderField from "../../../content/RenderField.svelte";
|
||||||
import Icon from "../../common/Icon.svelte";
|
import Icon from "../../../common/Icon.svelte";
|
||||||
import Datalist from "./Datalist.svelte";
|
import Datalist from "../fields/Datalist.svelte";
|
||||||
import {insertEdges} from "./reference.js";
|
import {insertEdges} from "./reference.js";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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,
|
||||||
|
data: {},
|
||||||
|
rank: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (action === "replace") {
|
||||||
|
existingRecords = existingRecords.filter(queryRecord => queryRecord.edge.field !== fieldName)
|
||||||
|
}
|
||||||
|
existingRecords = [...existingRecords ?? [], ...newQueryRecords];
|
||||||
|
return uniqBy(existingRecords, (r) => r.record.id);
|
||||||
|
}
|
||||||
Generated
+10
-1
@@ -5,7 +5,8 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-markdown": "^6.2.2"
|
"@codemirror/lang-markdown": "^6.2.2",
|
||||||
|
"fast-equals": "^5.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@codemirror/commands": "^6.1.2",
|
"@codemirror/commands": "^6.1.2",
|
||||||
@@ -853,6 +854,14 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-equals": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
|
|||||||
+2
-1
@@ -29,6 +29,7 @@
|
|||||||
"vite": "^3.2.3"
|
"vite": "^3.2.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-markdown": "^6.2.2"
|
"@codemirror/lang-markdown": "^6.2.2",
|
||||||
|
"fast-equals": "^5.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-14
@@ -1,16 +1,74 @@
|
|||||||
.lx-nav{
|
.lx-nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: rgba(255,255,255,1);
|
//background-color: rgba(255,255,255,1);
|
||||||
margin-bottom:0px ;
|
margin-bottom: 0px;
|
||||||
.nav-item{
|
|
||||||
padding:16px 0;
|
.nav-item {
|
||||||
margin: 0 16px;
|
padding: 16px 0;
|
||||||
color: $primary;
|
margin: 0 16px;
|
||||||
|
color: $primary;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
a{
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
.accordion {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-item {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-button:not(.collapsed) {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.offcanvas-body {
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-button:not(.collapsed) {
|
||||||
|
background: rgba(var(--lucent-bg), 0.2) !important;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion .list-group-item {
|
||||||
|
margin-left: 20px;
|
||||||
|
border-left: 2px solid rgba(var(--lucent-bg-dark), 0.2);
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion .list-group-item:hover {
|
||||||
|
background: rgba(var(--lucent-bg-dark), 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion .list-group-item.active {
|
||||||
|
border-left: 2px solid rgba(var(--lucent-bg-dark), 0.8);
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: none;
|
||||||
|
background: rgba(var(--lucent-bg-dark), 0.1) !important;
|
||||||
|
color: var(--bs-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-logo{
|
||||||
|
padding: 5px 20px;
|
||||||
|
border-bottom: 1px solid rgba(var(--lucent-bg-dark), 0.2);
|
||||||
|
}
|
||||||
|
.sidebar-logo a{
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 25px;
|
||||||
|
color: var(--bs-primary);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.record-status-bar {
|
||||||
|
//position: fixed;
|
||||||
|
//bottom: 0;
|
||||||
|
//left: 0px;
|
||||||
|
//width: 100%;
|
||||||
|
//background: rgb(var(--lucent-bg-dark)); // old 206, 223, 210
|
||||||
|
//z-index: 1050;
|
||||||
|
}
|
||||||
+12
-74
@@ -3,86 +3,21 @@
|
|||||||
/* SCSS HEX */
|
/* SCSS HEX */
|
||||||
$green-crayola: #0dab76ff;
|
$green-crayola: #0dab76ff;
|
||||||
$green-pigment: #139a43ff;
|
$green-pigment: #139a43ff;
|
||||||
$lincoln-green: #0b5d1eff;
|
$lincoln-green: #3e5d8f;
|
||||||
$forest-green-traditional: #053b06ff;
|
$forest-green-traditional: #053b06ff;
|
||||||
$black: #000000ff;
|
$black: #000000ff;
|
||||||
|
|
||||||
/* SCSS Gradient */
|
:root {
|
||||||
$gradient-top: linear-gradient(
|
/* #f0f0f0 in decimal RGB */
|
||||||
0deg,
|
--lucent-bg:216, 223, 233; //old 11, 93, 30
|
||||||
#0dab76ff,
|
--lucent-bg-dark:34, 44, 60; //old 11, 93, 30
|
||||||
#139a43ff,
|
}
|
||||||
#0b5d1eff,
|
|
||||||
#053b06ff,
|
|
||||||
#000000ff
|
|
||||||
);
|
|
||||||
$gradient-right: linear-gradient(
|
|
||||||
90deg,
|
|
||||||
#0dab76ff,
|
|
||||||
#139a43ff,
|
|
||||||
#0b5d1eff,
|
|
||||||
#053b06ff,
|
|
||||||
#000000ff
|
|
||||||
);
|
|
||||||
$gradient-bottom: linear-gradient(
|
|
||||||
180deg,
|
|
||||||
#0dab76ff,
|
|
||||||
#139a43ff,
|
|
||||||
#0b5d1eff,
|
|
||||||
#053b06ff,
|
|
||||||
#000000ff
|
|
||||||
);
|
|
||||||
$gradient-left: linear-gradient(
|
|
||||||
270deg,
|
|
||||||
#0dab76ff,
|
|
||||||
#139a43ff,
|
|
||||||
#0b5d1eff,
|
|
||||||
#053b06ff,
|
|
||||||
#000000ff
|
|
||||||
);
|
|
||||||
$gradient-top-right: linear-gradient(
|
|
||||||
45deg,
|
|
||||||
#0dab76ff,
|
|
||||||
#139a43ff,
|
|
||||||
#0b5d1eff,
|
|
||||||
#053b06ff,
|
|
||||||
#000000ff
|
|
||||||
);
|
|
||||||
$gradient-bottom-right: linear-gradient(
|
|
||||||
135deg,
|
|
||||||
#0dab76ff,
|
|
||||||
#139a43ff,
|
|
||||||
#0b5d1eff,
|
|
||||||
#053b06ff,
|
|
||||||
#000000ff
|
|
||||||
);
|
|
||||||
$gradient-top-left: linear-gradient(
|
|
||||||
225deg,
|
|
||||||
#0dab76ff,
|
|
||||||
#139a43ff,
|
|
||||||
#0b5d1eff,
|
|
||||||
#053b06ff,
|
|
||||||
#000000ff
|
|
||||||
);
|
|
||||||
$gradient-bottom-left: linear-gradient(
|
|
||||||
315deg,
|
|
||||||
#0dab76ff,
|
|
||||||
#139a43ff,
|
|
||||||
#0b5d1eff,
|
|
||||||
#053b06ff,
|
|
||||||
#000000ff
|
|
||||||
);
|
|
||||||
$gradient-radial: radial-gradient(
|
|
||||||
#0dab76ff,
|
|
||||||
#139a43ff,
|
|
||||||
#0b5d1eff,
|
|
||||||
#053b06ff,
|
|
||||||
#000000ff
|
|
||||||
);
|
|
||||||
|
|
||||||
$primary: $lincoln-green;
|
$primary: $lincoln-green;
|
||||||
$table-striped-bg-factor: 0.03;
|
$table-striped-bg-factor: 0.03;
|
||||||
$dropdown-bg: rgb(206, 223, 210);
|
$dropdown-bg: rgb(var(--lucent-bg)); //rgb(206, 223, 210)
|
||||||
|
|
||||||
|
|
||||||
@import "../node_modules/bootstrap/scss/bootstrap";
|
@import "../node_modules/bootstrap/scss/bootstrap";
|
||||||
@import "./sidebar";
|
@import "./sidebar";
|
||||||
@@ -96,6 +31,9 @@ $dropdown-bg: rgb(206, 223, 210);
|
|||||||
@import "./nav";
|
@import "./nav";
|
||||||
@import "./files";
|
@import "./files";
|
||||||
@import "./revisions";
|
@import "./revisions";
|
||||||
|
@import "./record-status-bar";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: rgba(11, 93, 30, 0.04);
|
background-color: rgba(11, 93, 30, 0.04);
|
||||||
|
|||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "lucent-package",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace Lucent\Account;
|
namespace Lucent\Account;
|
||||||
|
|
||||||
use Lucent\Channel\ChannelService;
|
use Lucent\Channel\ChannelService;
|
||||||
use Lucent\Primitive\Collection;
|
use Lucent\Support\Collection;
|
||||||
|
|
||||||
readonly class AccountService
|
readonly class AccountService
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ namespace Lucent\Account;
|
|||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Lucent\Primitive\Collection;
|
use Lucent\Support\Collection;
|
||||||
use PhpOption\Option;
|
use Lucent\Support\Option\Option;
|
||||||
|
|
||||||
class UserRepo
|
class UserRepo
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,10 +14,7 @@ class ArrayContainer implements ArrayAccess, JsonSerializable
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get(string $key, mixed $default = null): mixed
|
|
||||||
{
|
|
||||||
return $this->data[$key] ?? $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function offsetSet($offset, $value): void
|
public function offsetSet($offset, $value): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace Lucent\Channel;
|
namespace Lucent\Channel;
|
||||||
|
|
||||||
use Lucent\Primitive\Collection;
|
use Lucent\Schema\Schema\Schema;
|
||||||
use Lucent\Schema\Schema;
|
use Lucent\Support\Collection;
|
||||||
|
|
||||||
final class Channel
|
final class Channel
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
namespace Lucent\Channel;
|
namespace Lucent\Channel;
|
||||||
|
|
||||||
|
|
||||||
use Lucent\Primitive\Collection;
|
use Lucent\Schema\Schema\Schema;
|
||||||
use Lucent\Schema\Schema;
|
|
||||||
use Lucent\Schema\SchemaService;
|
use Lucent\Schema\SchemaService;
|
||||||
use PhpOption\Option;
|
use Lucent\Support\Collection;
|
||||||
|
use Lucent\Support\Option\Option;
|
||||||
|
|
||||||
final class ChannelService
|
final class ChannelService
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use Exception;
|
|||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Intervention\Image\ImageManager;
|
use Intervention\Image\ImageManager;
|
||||||
use Lucent\Channel\ChannelService;
|
use Lucent\Channel\ChannelService;
|
||||||
use Lucent\Schema\Schema;
|
use Lucent\Schema\Schema\Schema;
|
||||||
use Lucent\Schema\Type;
|
use Lucent\Schema\Schema\Type;
|
||||||
|
|
||||||
class RebuildThumbnails extends Command
|
class RebuildThumbnails extends Command
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace Lucent\Commands;
|
namespace Lucent\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Lucent\Edge\EdgeService;
|
use Lucent\Graph\Edge\EdgeService;
|
||||||
use Lucent\Query\Query;
|
use Lucent\Query\Query;
|
||||||
|
|
||||||
class RemoveOrphanEdges extends Command
|
class RemoveOrphanEdges extends Command
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Lucent\Id;
|
namespace Lucent\CommonData;
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
+8
-6
@@ -1,13 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"env" => env("LUCENT_ENV", "production"),
|
"env" => env("LUCENT_ENV", "production"),
|
||||||
"schemas_path" => env("LUCENT_SCHEMAS_PATH", "app/Lucent"),
|
"schemas_path" => env("LUCENT_SCHEMAS_PATH", "app/Lucent"),
|
||||||
"database" => env('LUCENT_DB_CONNECTION', env('DB_CONNECTION',"sqlite")),
|
"json_schemas_path" => env("LUCENT_JSON_SCHEMA_PATH", "json_schema"),
|
||||||
"name" => env("LUCENT_NAME", "Lucent"),
|
"sidebar_path" => env("LUCENT_SIDEBAR_PATH", "app/Lucent"),
|
||||||
|
"database" => env('LUCENT_DB_CONNECTION', env('DB_CONNECTION', "sqlite")),
|
||||||
|
"name" => env("LUCENT_NAME", "Lucent"),
|
||||||
"url" => env("LUCENT_URL", env('APP_URL')),
|
"url" => env("LUCENT_URL", env('APP_URL')),
|
||||||
"previewTarget" => env("LUCENT_PREVIEW_TARGET", "previewTarget"),
|
"previewTarget" => env("LUCENT_PREVIEW_TARGET", "previewTarget"),
|
||||||
"generateCommand" => env("LUCENT_GENERATE_COMMAND", "generate:static"),
|
"generateCommand" => env("LUCENT_GENERATE_COMMAND", "generate:static"),
|
||||||
"imageFilters" => [],
|
"imageFilters" => [],
|
||||||
"canInvite" => ["admin"],
|
"canInvite" => ["admin"],
|
||||||
"canBuild" => ["admin"],
|
"canBuild" => ["admin"],
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ return new class extends Migration {
|
|||||||
$table->string('status');
|
$table->string('status');
|
||||||
$table->jsonb('data');
|
$table->jsonb('data');
|
||||||
$table->jsonb('_sys');
|
$table->jsonb('_sys');
|
||||||
$table->jsonb('_file');
|
$table->jsonb('_file')->nullable();
|
||||||
|
|
||||||
$table->index(['schema', 'status']);
|
$table->index(['schema', 'status']);
|
||||||
});
|
});
|
||||||
+1
@@ -21,6 +21,7 @@ return new class extends Migration {
|
|||||||
$table->jsonb('_file');
|
$table->jsonb('_file');
|
||||||
$table->jsonb('_edges');
|
$table->jsonb('_edges');
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('edges', function (Blueprint $table) {
|
||||||
|
$table->jsonb('data')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('records', function (Blueprint $table) {
|
||||||
|
|
||||||
|
$table->dropColumn("data");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('revisions', function (Blueprint $table) {
|
||||||
|
$table->dropColumn("schema");
|
||||||
|
$table->dropColumn("_file");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('revisions', function (Blueprint $table) {
|
||||||
|
$table->string('schema');
|
||||||
|
$table->jsonb('_file');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Lucent\Edge;
|
|
||||||
|
|
||||||
use Lucent\LucentException;
|
|
||||||
use Lucent\Validator\Validator as LucentValidator;
|
|
||||||
|
|
||||||
final class Edge
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @throws LucentException
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
|
|
||||||
public string $source,
|
|
||||||
public string $target,
|
|
||||||
public string $sourceSchema,
|
|
||||||
public string $targetSchema,
|
|
||||||
public string $field,
|
|
||||||
public string $rank = "a",
|
|
||||||
public int $depth = 0,
|
|
||||||
)
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
LucentValidator::single("source", $source, "required|uuid");
|
|
||||||
LucentValidator::single("target", $target, "required|uuid");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function equal(Edge $edge): bool
|
|
||||||
{
|
|
||||||
return $this->targetSchema === $edge->targetSchema && $this->field === $edge->field && $this->target === $edge->target && $this->source === $edge->source;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toArray(): array
|
|
||||||
{
|
|
||||||
return json_decode(json_encode($this), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toDB(): array
|
|
||||||
{
|
|
||||||
$data = $this->toArray();
|
|
||||||
unset($data["depth"]);
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function fromArray(array $data): Edge
|
|
||||||
{
|
|
||||||
|
|
||||||
return new Edge(
|
|
||||||
|
|
||||||
source: data_get($data, 'source'),
|
|
||||||
target: data_get($data, 'target'),
|
|
||||||
sourceSchema: data_get($data, 'sourceSchema'),
|
|
||||||
targetSchema: data_get($data, 'targetSchema'),
|
|
||||||
field: data_get($data, 'field'),
|
|
||||||
rank: data_get($data, 'rank'),
|
|
||||||
depth: data_get($data, 'depth', 0),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Lucent\Edge;
|
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends \Illuminate\Support\Collection<int|string, Edge>
|
|
||||||
*/
|
|
||||||
final class EdgeCollection extends Collection
|
|
||||||
{
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
Edge ...$array
|
|
||||||
) {
|
|
||||||
parent::__construct($array);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Edge[]
|
|
||||||
**/
|
|
||||||
public function toArray(): array
|
|
||||||
{
|
|
||||||
return collect($this)->values()->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function fromArray(array $data): EdgeCollection
|
|
||||||
{
|
|
||||||
$edges = array_map([Edge::class, 'fromArray'], $data);
|
|
||||||
return new EdgeCollection(...$edges);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
<?php namespace Lucent\Edge;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Lucent\LucentException;
|
|
||||||
use PDOException;
|
|
||||||
use stdClass;
|
|
||||||
|
|
||||||
class EdgeRepo
|
|
||||||
{
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function insert(Edge $edge): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
DB::table("edges")->insert($edge->toDB());
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
if ($e->getCode() == 23505) {
|
|
||||||
throw new LucentException("Edge already exists");
|
|
||||||
}
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(string $from, EdgeCollection $edges): void
|
|
||||||
{
|
|
||||||
$edgesDB = collect($edges)->map(fn($e) => $e->toDB())->toArray();
|
|
||||||
DB::table("edges")->where("source", $from)->delete();
|
|
||||||
DB::table("edges")->insert($edgesDB);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function findAll(): EdgeCollection
|
|
||||||
{
|
|
||||||
$edges = DB::table("edges")->get();
|
|
||||||
return new EdgeCollection(...$edges->map([$this, 'mapEdge'])->toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function mapEdge(stdClass $edge): Edge
|
|
||||||
{
|
|
||||||
|
|
||||||
return new Edge(
|
|
||||||
source: $edge->source,
|
|
||||||
target: $edge->target,
|
|
||||||
sourceSchema: $edge->sourceSchema,
|
|
||||||
targetSchema: $edge->targetSchema,
|
|
||||||
field: $edge->field,
|
|
||||||
rank: $edge->rank,
|
|
||||||
depth: $edge->depth ?? 0
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remove(Edge $edge): void
|
|
||||||
{
|
|
||||||
DB::table("edges")
|
|
||||||
->where("source", $edge->source)
|
|
||||||
->where("target", $edge->target)
|
|
||||||
->where("sourceSchema", $edge->sourceSchema)
|
|
||||||
->where("targetSchema", $edge->targetSchema)
|
|
||||||
->where("field", $edge->field)
|
|
||||||
->delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<?php namespace Lucent\Edge;
|
|
||||||
|
|
||||||
use Lucent\LucentException;
|
|
||||||
|
|
||||||
class EdgeService
|
|
||||||
{
|
|
||||||
public function __construct(public EdgeRepo $edgeRepo)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws LucentException
|
|
||||||
*/
|
|
||||||
public function create(
|
|
||||||
string $source,
|
|
||||||
string $target,
|
|
||||||
string $sourceSchema,
|
|
||||||
string $targetSchema,
|
|
||||||
string $field,
|
|
||||||
string $rank,
|
|
||||||
): Edge
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
$edge = new Edge(
|
|
||||||
|
|
||||||
source: $source,
|
|
||||||
target: $target,
|
|
||||||
sourceSchema: $sourceSchema,
|
|
||||||
targetSchema: $targetSchema,
|
|
||||||
field: $field,
|
|
||||||
rank: $rank,
|
|
||||||
);
|
|
||||||
$this->edgeRepo->insert($edge);
|
|
||||||
return $edge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(string $from, EdgeCollection $edges): void
|
|
||||||
{
|
|
||||||
$this->edgeRepo->update($from, $edges);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function findAll(): EdgeCollection
|
|
||||||
{
|
|
||||||
return $this->edgeRepo->findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remove(Edge $edge): void
|
|
||||||
{
|
|
||||||
$this->edgeRepo->remove($edge);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -4,11 +4,12 @@ namespace Lucent\File;
|
|||||||
|
|
||||||
use Illuminate\Http\UploadedFile;
|
use Illuminate\Http\UploadedFile;
|
||||||
use Lucent\Channel\ChannelService;
|
use Lucent\Channel\ChannelService;
|
||||||
|
use Lucent\Graph\Record\FileInfo;
|
||||||
|
use Lucent\Graph\Record\QueryRecord;
|
||||||
use Lucent\LucentException;
|
use Lucent\LucentException;
|
||||||
use Lucent\Record\File;
|
use Lucent\Schema\Schema\Schema;
|
||||||
use Lucent\Record\QueryRecord;
|
use Lucent\Schema\Schema\Type;
|
||||||
use Lucent\Schema\Schema;
|
use Lucent\Support\Result\Result;
|
||||||
use Lucent\Schema\Type;
|
|
||||||
|
|
||||||
class FileService
|
class FileService
|
||||||
{
|
{
|
||||||
@@ -24,6 +25,15 @@ class FileService
|
|||||||
return $this->channelService->channel->url. "/storage/".$file->_file->path;
|
return $this->channelService->channel->url. "/storage/".$file->_file->path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Schema $schema
|
||||||
|
* @param string $uploadFromUrl
|
||||||
|
* @return Result<FileInfo|string>
|
||||||
|
*/
|
||||||
|
public function createFromUrl(Schema $schema, string $uploadFromUrl): Result{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws LucentException
|
* @throws LucentException
|
||||||
*/
|
*/
|
||||||
@@ -48,7 +58,7 @@ class FileService
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new FileUploadResult(
|
return new FileUploadResult(
|
||||||
recordFile: File::fromArray($file), duplicateId: "", isDuplicate: false
|
recordFile: FileInfo::fromArray($file), duplicateId: "", isDuplicate: false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
namespace Lucent\File;
|
namespace Lucent\File;
|
||||||
|
|
||||||
use Lucent\Record\File;
|
use Lucent\Graph\Record\FileInfo;
|
||||||
|
|
||||||
class FileUploadResult
|
class FileUploadResult
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public ?File $recordFile,
|
public ?FileInfo $recordFile,
|
||||||
public string $duplicateId,
|
public string $duplicateId,
|
||||||
public bool $isDuplicate,
|
public bool $isDuplicate,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use Exception;
|
|||||||
use Illuminate\Log\Logger;
|
use Illuminate\Log\Logger;
|
||||||
use Intervention\Image\ImageManager;
|
use Intervention\Image\ImageManager;
|
||||||
use Lucent\Channel\ChannelService;
|
use Lucent\Channel\ChannelService;
|
||||||
use Lucent\Record\QueryRecord;
|
use Lucent\Graph\Record\QueryRecord;
|
||||||
|
|
||||||
class ImageService
|
class ImageService
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ use Illuminate\Support\Facades\DB;
|
|||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Intervention\Image\ImageManagerStatic;
|
use Intervention\Image\ImageManagerStatic;
|
||||||
|
use Lucent\Graph\Record\FileInfo as RecordFile;
|
||||||
use Lucent\LucentException;
|
use Lucent\LucentException;
|
||||||
use Lucent\Record\File as RecordFile;
|
use Lucent\Schema\Schema\Schema;
|
||||||
use Lucent\Schema\Schema;
|
|
||||||
use Spatie\ImageOptimizer\OptimizerChainFactory;
|
use Spatie\ImageOptimizer\OptimizerChainFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user