This commit is contained in:
2023-10-13 21:06:23 +03:00
parent 129e3af471
commit 9e37a7f730
34 changed files with 845 additions and 780 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -1,14 +1,14 @@
{
"main.js": {
"file": "assets/main.55e6cd6b.js",
"file": "assets/main.4387b1b7.js",
"src": "main.js",
"isEntry": true,
"css": [
"assets/main.e0c13bde.css"
"assets/main.0c108e59.css"
]
},
"main.css": {
"file": "assets/main.e0c13bde.css",
"file": "assets/main.0c108e59.css",
"src": "main.css"
}
}
+30 -12
View File
@@ -40,17 +40,17 @@
<!-- </div>-->
<div class="offcanvas-body">
<div class="accordion" id="accordionPanelsStayOpenExample">
<div class="accordion">
<div class="accordion-item">
<h2 class="accordion-header" id="panelsStayOpen-headingOne">
<h2 class="accordion-header" id="panelsStayOpen-headingMain">
<button class="accordion-button" type="button" data-bs-toggle="collapse"
data-bs-target="#panelsStayOpen-collapseOne" aria-expanded="true"
aria-controls="panelsStayOpen-collapseOne">
data-bs-target="#panelsStayOpen-collapseMain" aria-expanded="true"
aria-controls="panelsStayOpen-collapseMain">
Main
</button>
</h2>
<div id="panelsStayOpen-collapseOne" class="accordion-collapse collapse show"
aria-labelledby="panelsStayOpen-headingOne">
<div id="panelsStayOpen-collapseMain" class="accordion-collapse collapse show"
aria-labelledby="panelsStayOpen-headingMain">
<div class="accordion-body">
<NavbarMenu
schemas={ channel.schemas.filter((sc) => sc.isEntry)}
@@ -60,18 +60,36 @@
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="panelsStayOpen-headingTwo">
<h2 class="accordion-header" id="panelsStayOpen-headingOther">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#panelsStayOpen-collapseTwo" aria-expanded="false"
aria-controls="panelsStayOpen-collapseTwo">
data-bs-target="#panelsStayOpen-collapseOther" aria-expanded="false"
aria-controls="panelsStayOpen-collapseOther">
Other
</button>
</h2>
<div id="panelsStayOpen-collapseTwo" class="accordion-collapse collapse"
aria-labelledby="panelsStayOpen-headingTwo">
<div id="panelsStayOpen-collapseOther" class="accordion-collapse collapse"
aria-labelledby="panelsStayOpen-headingOther">
<div class="accordion-body">
<NavbarMenu
schemas={ channel.schemas.filter((sc) => !sc.isEntry)}
schemas={ channel.schemas.filter((sc) => !sc.isEntry && sc.type !== "files")}
schema={schema}
/>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="panelsStayOpen-headingFS">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#panelsStayOpen-collapseFS" aria-expanded="false"
aria-controls="panelsStayOpen-collapseFS">
Filesystem
</button>
</h2>
<div id="panelsStayOpen-collapseFS" class="accordion-collapse collapse"
aria-labelledby="panelsStayOpen-headingFS">
<div class="accordion-body">
<NavbarMenu
schemas={ channel.schemas.filter((sc) => sc.type === "files")}
schema={schema}
/>
</div>
+27 -4
View File
@@ -4,9 +4,11 @@
import Icon from "../../common/Icon.svelte";
import SortFields from "./SortFields.svelte";
import AppliedFilter from "./AppliedFilter.svelte";
import {getContext} from "svelte";
import {getContext,createEventDispatcher} from "svelte";
const channel = getContext("channel");
const dispatch = createEventDispatcher();
export let sort;
export let schema;
export let operators;
@@ -21,6 +23,21 @@
let csvUrl = url.pathname + "/csv?" + url.searchParams.toString();
function search(e){
e.preventDefault();
const data = new FormData(e.target);
let filterKey = data.keys().next().value;
let filterValue = data.values().next().value;
const url = new URL(modalUrl ?? window.location.href);
url.searchParams.set("skip", "0");
url.searchParams.set(filterKey, filterValue);
if (inModal) {
dispatch("refresh", url);
} else {
window.location = url;
}
}
function uploadComplete(e) {
records = e.detail;
}
@@ -49,9 +66,15 @@
on:refresh
/>
<form method="GET">
<input type="search" name="filter[data.{schema.fields[0].name}_regex]" placeholder="Search"
class="form-control" required>
<form method="GET" on:submit={search}>
{#if schema.fields[0]?.name}
<input type="search" name="filter[data.{schema.fields[0].name }_regex]" placeholder="Search"
class="form-control" required>
{:else}
<input type="search" name="filter[_file.originalName_regex]" placeholder="Search"
class="form-control" required>
{/if}
</form>
+2 -2
View File
@@ -51,13 +51,13 @@
<ReferenceInline
bind:graph
{record}
{schema}
{field}
{validationErrors}
/>
{:else if field.info.name === "reference" && field.layout === "table"}
<ReferenceTable
bind:graph
{id}
{record}
{field}
{validationErrors}
@@ -73,8 +73,8 @@
{:else if field.info.name === "reference"}
<Reference
bind:graph
{id}
{record}
{schema}
{field}
{validationErrors}
/>
+3 -4
View File
@@ -8,9 +8,8 @@
const channel = getContext("channel");
export let graph;
export let record;
let parentEdgesByField = graph.edges
.filter((edge) => edge.source !== record.id && edge.depth === 0)
let parentEdgesByField = graph.parentEdges
.filter((edge) => edge.source !== record.id && edge.depth === 1)
.reduce((carry, edge) => {
let schemaField = edge.sourceSchema + edge.field;
@@ -32,7 +31,7 @@
}
return carry;
}, {});
console.log(parentEdgesByField)
let childrenEdgesByField = graph.edges
.filter((edge) => edge.source === record.id && edge.depth === 0)
.reduce((carry, edge) => {
+28 -29
View File
@@ -1,5 +1,5 @@
<script>
import {afterUpdate, createEventDispatcher, onMount,} from "svelte";
import {afterUpdate, createEventDispatcher, onMount,getContext} from "svelte";
import {isEqual} from "lodash";
import FormField from "./FormField.svelte";
@@ -8,9 +8,9 @@
import StatusSelect from "./StatusSelect.svelte";
import ErrorAlert from "../common/ErrorAlert.svelte";
const channel = getContext("channel");
const dispatch = createEventDispatcher();
export let schema;
export let schemas;
export let record;
export let graph = {
records: [],
@@ -18,7 +18,7 @@
};
export let isCreateMode;
let originalContent;
let activeContentTab = "_default";
let activeContentTab = "";
let hasUnsavedData = false;
$: validationErrors = null;
$: errorMessage = validationErrors
@@ -28,7 +28,7 @@
: null;
let activeFields = schema.fields.filter(
(f) => f.trashed === false && f.name !== "id"
(f) => f.name !== "id"
);
let tabname = "_default";
@@ -116,7 +116,7 @@
axios
.post("/records", {
.post(channel.lucentUrl + "/records", {
record: record,
edges: graph.edges,
isCreateMode: isCreateMode,
@@ -159,24 +159,23 @@
<div class=" mt-1">
<ContentTabs
{schema}
{isCreateMode}
bind:active={activeContentTab}
{record}
{schema}
{isCreateMode}
bind:active={activeContentTab}
{record}
/>
<FilePreview {record} {schema}/>
<!-- <fieldset disabled="disabled"> -->
{#each activeFields as field (field.name)}
{#if fieldToTabs[activeContentTab].includes(field.name)}
{#if activeContentTab === field.group}
<FormField
bind:data={record.data}
bind:graph={graph}
{field}
{schema}
{schemas}
{record}
{validationErrors}
{isCreateMode}
bind:data={record.data}
bind:graph={graph}
{field}
{schema}
{record}
{validationErrors}
{isCreateMode}
/>
{/if}
{/each}
@@ -189,26 +188,26 @@
{/if}
{#if isCreateMode}
<button
class="ms-2 btn btn-primary btn-spinner"
on:click={save}
class="ms-2 btn btn-primary btn-spinner"
on:click={save}
>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
Add
</button>
{:else}
<button
disabled={!hasUnsavedData}
class="ms-2 btn btn-primary btn-spinner"
on:click={save}
disabled={!hasUnsavedData}
class="ms-2 btn btn-primary btn-spinner"
on:click={save}
>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
Save
</button>
+1 -4
View File
@@ -63,10 +63,7 @@
</div>
<style>
.button-file {
width: 64px;
height: 65px;
}
.card .trash-button {
display: none;
}
@@ -2,30 +2,32 @@
import Icon from "../common/Icon.svelte";
import {createEventDispatcher, onMount} from "svelte";
import {createEventDispatcher, onMount, getContext} from "svelte";
import Preview from "../files/Preview.svelte";
import InlineEdit from "./InlineEdit.svelte";
import Reference from "../content/elements/Reference.svelte";
import File from "../content/elements/File.svelte";
const channel = getContext("channel");
const dispatch = createEventDispatcher();
export let isFirst;
export let isLast;
export let toDelete = false;
export let schemas;
export let record;
let editRecord;
let schema = schemas.find((aschema) => aschema.name === record.schema);
let editGraph;
let schema = channel.schemas.find((aschema) => aschema.name === record.schema);
$: editMode = false;
$: expanded = false;
function editInline(e) {
e.preventDefault();
axios
.get("/records/editInline/" + record.id)
.get(channel.lucentUrl + "/records/editInline/" + record.id)
.then((response) => {
record = response.data;
editRecord = response.data;
editRecord = response.data.record;
editGraph = response.data.graph;
editMode = true;
})
.catch((error) => {
@@ -76,7 +78,7 @@
function deleteFromChannel(e) {
e.preventDefault();
axios
.post("/records/status/trashed", [record])
.post(channel.lucentUrl +"/records/status/trashed", [record])
.then((response) => {
dispatch("remove", record.id);
})
@@ -92,13 +94,13 @@
<p>Item was removed from the current record.</p>
<p>
<button
class="btn btn-sm btn-outline border border-1 border-dark"
on:click={undo}>Undo
class="btn btn-sm btn-outline border border-1 border-dark"
on:click={undo}>Undo
</button
>
<button
class="btn btn-sm btn-danger "
on:click={deleteFromChannel}
class="btn btn-sm btn-danger "
on:click={deleteFromChannel}
>Delete completely from channel
</button
>
@@ -110,26 +112,26 @@
</div>
{:else if editMode === true}
<InlineEdit
{schema}
{schemas}
record={editRecord}
isCreateMode={false}
on:cancel={cancel}
on:inlinesaved={handleInlinesaved}
{schema}
record={editRecord}
graph={editGraph}
isCreateMode={false}
on:cancel={cancel}
on:inlinesaved={handleInlinesaved}
/>
{:else}
<div class="lx-card mt-4 bg-primary bg-opacity-10">
<div class="actions">
<small class="text-muted">{schema.label}</small>
<button
class="btn btn-sm btn-link"
on:click|preventDefault={editInline}
class="btn btn-sm btn-link"
on:click|preventDefault={editInline}
>
<Icon icon="pencil" width={12} height={12}/>
</button>
<button
class="btn btn-sm btn-link"
on:click={(e) => (expanded = !expanded)}
class="btn btn-sm btn-link"
on:click={(e) => (expanded = !expanded)}
>
{#if expanded}
<Icon icon="compress" width={12} height={12}/>
@@ -139,19 +141,19 @@
</button>
<div class="dropdown d-inline-block">
<button
class="btn btn-link btn-sm"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
class="btn btn-link btn-sm"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<Icon icon="ellipsis"/>
</button>
<div class="dropdown-menu">
<a
class="dropdown-item"
href="/records/{record.id}"
target="_blank"
class="dropdown-item"
href="/records/{record.id}"
target="_blank"
>Edit in new tab
</a>
<button class="dropdown-item" on:click={trash}>
@@ -162,16 +164,16 @@
{#if !isFirst}
<button
class="btn btn-sm btn-outline-primary border-0"
on:click|preventDefault={moveup}
class="btn btn-sm btn-outline-primary border-0"
on:click|preventDefault={moveup}
>
<Icon icon="circle-chevron-up"/>
</button>
{/if}
{#if !isLast}
<button
class="btn btn-sm btn-outline-primary border-0"
on:click|preventDefault={movedn}
class="btn btn-sm btn-outline-primary border-0"
on:click|preventDefault={movedn}
>
<Icon icon="circle-chevron-down"/>
</button>
@@ -189,7 +191,7 @@
>{field.label}</span
>
{#if field.ui === "reference"}
<Reference {record} {schemas} {field}/>
<Reference {record} {field}/>
{:else if field.ui === "file"}
<File {record} {field}/>
{:else}
-113
View File
@@ -1,113 +0,0 @@
<script>
import Mustache from "mustache";
import { imgurl } from "../files/imageserver";
import { uniqBy } from "lodash";
import { onMount, getContext } from "svelte";
import { Network } from "vis-network/esnext";
import "vis-network/styles/vis-network.css";
const channelurl = getContext("channelurl");
export let schemas;
export let recordGraph;
let network;
let allEdges = recordGraph._graph.edges.map((e) => {
e.fieldLabel = schemas
.find((s) => s.uid === e.fromSchema)
.fields.find((f) => f.name === e.field).label;
return e;
});
let nodes = recordGraph._graph.nodes.map((r) => {
let nodeOptions = {
id: r.data.id,
label: renderTitle(
schemas.find((s) => s.uid === r._sys.schema),
r
),
borderWidth: 0,
color: {
background:
r.data.id === recordGraph.data.id ? "#0b5d1e" : "#eeeeee",
},
font: {
multi: true,
color: r.data.id === recordGraph.data.id ? "#fff" : "#333",
},
};
nodeOptions.shape = "box";
if (r._file?.path) {
nodeOptions.shape = "image";
nodeOptions.image = imgurl(r, 64, 64, "crop");
}
return nodeOptions;
});
nodes = uniqBy(nodes, (n) => n.id);
// create an array with edges
let networkEdges = allEdges.map((e) => {
return {
from: e.from,
to: e.to,
label: e.fieldLabel,
arrows: {
to: {
enabled: true,
type: "arrow",
scaleFactor: 0.5,
},
},
font: { align: "middle" },
};
});
let data = {
nodes: nodes,
edges: networkEdges,
};
let options = {
physics: false,
layout: {
hierarchical: {
enabled: true,
nodeSpacing: 150,
treeSpacing: 200,
direction: "UD",
sortMethod: "directed",
},
},
};
onMount(() => {
let networkInstance = new Network(network, data, options);
networkInstance.on("doubleClick", function (params) {
if (params.nodes[0]) {
window.location = channelurl + "/records/" + params.nodes[0];
}
});
});
function renderTitle(schema, record) {
const template = `${schema.titleTemplate}\n <i>${schema.label}</i>`;
Mustache.parse(template);
let recordData = { ...record, ...record.data };
return Mustache.render(template, recordData);
}
</script>
<div class="lx-card">
<div class="network-container" bind:this={network} />
</div>
<style>
.network-container {
width: 100%;
height: 80vh;
max-height: 800px;
}
</style>
@@ -1,6 +1,6 @@
<script>
import {getContext} from "svelte";
import {uniqBy} from "lodash";
import {insertEdges} from "./reference";
import PreviewCard from "../PreviewCard.svelte";
import {getErrorMessage} from "./errorMessage";
import {sortByField} from "../../edges/sortEdges";
@@ -37,26 +37,7 @@
function insert(e) {
e.preventDefault();
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((edge) => edge.field !== field.name);
}
graph.records = uniqBy([...graph.records, ...recordsToInsert], (r) => r.id);
graph.edges = uniqBy([...replacedEdges, ...newEdges], (edge) => edge.target + edge.field);
graph = insertEdges(graph,record,e.detail.records,field.name,e.detail.action);
}
</script>
@@ -68,7 +49,6 @@
{/if}
<div class="inline-card-wrapper">
<ReferenceInlineButtons
{field}
buttonClass="mt-2"
recordId={null}
schemas={collections}
@@ -1,6 +1,5 @@
<script>
import {getContext} from "svelte";
import {uniqBy} from "lodash";
import {previewTitle} from "../Preview";
import {getErrorMessage} from "./errorMessage";
import {sortByField} from "../../edges/sortEdges";
@@ -8,10 +7,10 @@
import Sortable from "../../libs/Sortable.svelte";
import RenderField from "../../content/RenderField.svelte";
import Icon from "../../common/Icon.svelte";
import {insertEdges} from "./reference.js";
const channel = getContext("channel");
export let field;
export let record;
export let graph;
export let validationErrors;
@@ -46,26 +45,8 @@
function insert(e) {
e.preventDefault();
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((edge) => edge.field !== field.name);
}
graph.records = uniqBy([...graph.records, ...recordsToInsert], (r) => r.id);
graph.edges = uniqBy([...replacedEdges, ...newEdges], (edge) => edge.target + edge.field);
graph = insertEdges(graph,record,e.detail.records,field.name,e.detail.action);
console.log(graph)
}
$:visibleColumns = [];
@@ -118,7 +99,7 @@
<div class="d-flex align-items-center">
<a
class="me-2 text-decoration-none text-dark fs-6"
href="/records/{record.id}"
href="{channel.lucentUrl}/records/{record.id}"
target="_blank"
>
{previewTitle(channel.schemas, record)}
@@ -9,6 +9,7 @@
import RenderField from "../../content/RenderField.svelte";
import Icon from "../../common/Icon.svelte";
import Datalist from "./Datalist.svelte";
import {insertEdges} from "./reference.js";
const channel = getContext("channel");
export let field;
@@ -62,26 +63,7 @@
function insert(e, insertRecord) {
e.preventDefault();
const recordsToInsert = [insertRecord];
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((edge) => edge.field !== field.name);
}
graph.records = uniqBy([...graph.records, ...recordsToInsert], (r) => r.id);
graph.edges = uniqBy([...replacedEdges, ...newEdges], (edge) => edge.target + edge.field);
graph = insertEdges(graph,record,[insertRecord],field.name,e.detail.action);
}
const updateResults = debounce((e) => {
@@ -2,22 +2,38 @@
export let field;
export let value;
export let search;
function select(e, option) {
function select(e, option) {
e.preventDefault();
value = option;
search = "";
}
</script>
{#if field.selectOptions}
{#each field.selectOptions as suggestion (suggestion)}
<div
on:click={(e) => select(e, suggestion)}
on:keypress={(e) => select(e, suggestion)}
>
{#if Array.isArray(field.selectOptions)}
{#each field.selectOptions as suggestion (suggestion)}
<div
on:click={(e) => select(e, suggestion)}
on:keypress={(e) => select(e, suggestion)}
>
<span class="dropdown-item">
{suggestion}
</span>
</div>
{/each}
</div>
{/each}
{:else}
{#each Object.entries(field.selectOptions) as [k, v] (k)}
<div
on:click={(e) => select(e, k)}
on:keypress={(e) => select(e, k)}
>
<span class="dropdown-item">
{v}
</span>
</div>
{/each}
{/if}
{/if}
+6 -2
View File
@@ -75,7 +75,6 @@
autocomplete="off"
readonly={field.readonly && !isCreateMode}
/>
<div class="dropdown-menu w-100">
<Selectlist
{field}
@@ -87,7 +86,12 @@
{#if value}
<span class="badge rounded-pill bg-light text-dark fs-6 mt-3">
<div class="d-flex align-items-center ">
{value}
{#if Array.isArray(field.selectOptions)}
{value}
{:else}
{field.selectOptions[value]}
{/if}
<button
on:click|preventDefault={(e) => (value = "")}
type="button"
@@ -0,0 +1,23 @@
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,
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);
return graph;
}