files
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
import SchemaLayout from "../../layouts/SchemaLayout.svelte";
|
import SchemaLayout from "../../layouts/SchemaLayout.svelte";
|
||||||
import TextFieldProps from "./TextFieldProps.svelte";
|
import TextFieldProps from "./TextFieldProps.svelte";
|
||||||
import RelationFieldProps from "./RelationFieldProps.svelte";
|
import RelationFieldProps from "./RelationFieldProps.svelte";
|
||||||
|
import FileFieldProps from "./FileFieldProps.svelte";
|
||||||
import DeleteButton from "../../common/DeleteButton.svelte";
|
import DeleteButton from "../../common/DeleteButton.svelte";
|
||||||
import { post } from "../../modules/remote";
|
import { post } from "../../modules/remote";
|
||||||
import { getApp } from "../../app";
|
import { getApp } from "../../app";
|
||||||
@@ -128,6 +129,8 @@
|
|||||||
{:else if data.field.type === "relation"}
|
{:else if data.field.type === "relation"}
|
||||||
<RelationFieldProps field={data.field} schemas={data.schemas}
|
<RelationFieldProps field={data.field} schemas={data.schemas}
|
||||||
></RelationFieldProps>
|
></RelationFieldProps>
|
||||||
|
{:else if data.field.type === "file"}
|
||||||
|
<FileFieldProps field={data.field}></FileFieldProps>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button type="submit">Update</button>
|
<button type="submit">Update</button>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<script>
|
||||||
|
let { field } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<label>
|
||||||
|
Min items
|
||||||
|
<input type="number" bind:value={field.props.min} />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Max items
|
||||||
|
<input type="number" bind:value={field.props.max} />
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import TextField from "./fields/TextField.svelte";
|
import TextField from "./fields/TextField.svelte";
|
||||||
import RelationField from "./fields/RelationField.svelte";
|
import RelationField from "./fields/RelationField.svelte";
|
||||||
|
import FileField from "./fields/FileField.svelte";
|
||||||
let {
|
let {
|
||||||
fields,
|
fields,
|
||||||
record,
|
record,
|
||||||
@@ -45,6 +46,14 @@
|
|||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if field.type === "file"}
|
||||||
|
{@render fileField(field, "main")}
|
||||||
|
{#if field.translatable}
|
||||||
|
{#each selectedLocales as locale (locale)}
|
||||||
|
{@render fileField(field, locale)}
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
@@ -70,3 +79,15 @@
|
|||||||
edgeRecordPreviews={findFieldEdges(field, locale)}
|
edgeRecordPreviews={findFieldEdges(field, locale)}
|
||||||
></RelationField>
|
></RelationField>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet fileField(field, locale)}
|
||||||
|
<FileField
|
||||||
|
{channel}
|
||||||
|
{record}
|
||||||
|
validationError={findFieldValidationError(field, locale)}
|
||||||
|
schemaField={field}
|
||||||
|
{locale}
|
||||||
|
dataField={findDataField(field, locale)}
|
||||||
|
edgeRecordPreviews={findFieldEdges(field, locale)}
|
||||||
|
></FileField>
|
||||||
|
{/snippet}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<script>
|
||||||
|
let { schemaField, validationError } = $props();
|
||||||
|
let hasError = $derived(!!validationError);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if hasError}
|
||||||
|
<small id={schemaField.id + "-help"}>{validationError.message}</small>
|
||||||
|
{:else if schemaField.help != ""}
|
||||||
|
<small id={schemaField.id + "-help"}>{schemaField.help}</small>
|
||||||
|
{/if}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<script>
|
||||||
|
import { getLocaleName } from "../locale.svelte.js";
|
||||||
|
let { channel, schemaField, locale } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if locale !== "main"}
|
||||||
|
{getLocaleName(channel, locale)} >
|
||||||
|
{/if}
|
||||||
|
{schemaField.name} <br />
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
<script>
|
||||||
|
import { get, post } from "../../../modules/remote";
|
||||||
|
import { uploadFile } from "../../../modules/upload";
|
||||||
|
import { getApp } from "../../../app";
|
||||||
|
import FieldLabel from "./FieldLabel.svelte";
|
||||||
|
import FieldError from "./FieldError.svelte";
|
||||||
|
import Sortable from "../../../common/Sortable.svelte";
|
||||||
|
let {
|
||||||
|
channel,
|
||||||
|
record,
|
||||||
|
schemaField,
|
||||||
|
dataField,
|
||||||
|
locale,
|
||||||
|
validationError,
|
||||||
|
edgeRecordPreviews,
|
||||||
|
} = $props();
|
||||||
|
let originalValue = dataField?.value ?? schemaField.props.default;
|
||||||
|
let newValue = $state(originalValue);
|
||||||
|
let valuesChanged = $derived(newValue !== originalValue);
|
||||||
|
let errorMessage = $state("");
|
||||||
|
|
||||||
|
let filesInProgress = $state([]);
|
||||||
|
let uploadInProgress = $derived(filesInProgress.length > 0);
|
||||||
|
// let validationErrorState = $state(validationError);
|
||||||
|
const app = getApp();
|
||||||
|
|
||||||
|
let suggestionsLoaded = $state(false);
|
||||||
|
let suggestions = $state([]);
|
||||||
|
let selectedRecordIds = $state([]);
|
||||||
|
let dialog = $state();
|
||||||
|
|
||||||
|
function handleModalOpen(e) {
|
||||||
|
// Add logic to handle adding a record
|
||||||
|
dialog.showModal();
|
||||||
|
if (suggestionsLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// get(app.url("records/files"), { recordId: record.id }, (data, err) => {
|
||||||
|
suggestionsLoaded = true;
|
||||||
|
// suggestions = data;
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleModalClose(e) {
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInsertSelected() {
|
||||||
|
suggestionsLoaded = false;
|
||||||
|
post(
|
||||||
|
app.url("edges/many"),
|
||||||
|
{
|
||||||
|
toIds: selectedRecordIds,
|
||||||
|
from: record.id,
|
||||||
|
fieldId: schemaField.id,
|
||||||
|
locale: locale,
|
||||||
|
},
|
||||||
|
(data, err) => {
|
||||||
|
suggestionsLoaded = true;
|
||||||
|
dialog.close();
|
||||||
|
edgeRecordPreviews = data.edgeRecordPreviews;
|
||||||
|
validationError = data.validationError;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSortUpdate(updatedEdges) {
|
||||||
|
// let updatedFieldIds = updatedFields.map((f) => f.id);
|
||||||
|
// fields = fields.filter((f) => !updatedFieldIds.includes(f.id));
|
||||||
|
// fields = [...fields, ...updatedFields];
|
||||||
|
post(
|
||||||
|
app.url("records/sort-edges"),
|
||||||
|
{
|
||||||
|
ids: updatedEdges.map((e) => e.edge.id),
|
||||||
|
},
|
||||||
|
(data, err) => {
|
||||||
|
edgeRecordPreviews = updatedEdges;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRemoveEdge(edgeId) {
|
||||||
|
post(
|
||||||
|
app.url("edges/delete"),
|
||||||
|
{
|
||||||
|
id: edgeId,
|
||||||
|
from: record.id,
|
||||||
|
fieldId: schemaField.id,
|
||||||
|
locale: locale,
|
||||||
|
},
|
||||||
|
(data, err) => {
|
||||||
|
edgeRecordPreviews = edgeRecordPreviews.filter(
|
||||||
|
(e) => e.edge.id !== edgeId,
|
||||||
|
);
|
||||||
|
validationError = data.validationError;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFilesUpload(e) {
|
||||||
|
let files = e.target.files ? [...e.target.files] : [];
|
||||||
|
|
||||||
|
let filesUploaded = files.map((file) => {
|
||||||
|
let fileInProgress = {
|
||||||
|
pct: 0,
|
||||||
|
hasFailed: false,
|
||||||
|
name: file.name,
|
||||||
|
};
|
||||||
|
filesInProgress.push(fileInProgress);
|
||||||
|
|
||||||
|
const progress = ({ pct, isComplete }) => {
|
||||||
|
filesInProgress.find((f) => f.name === file.name).pct = pct;
|
||||||
|
if (isComplete) {
|
||||||
|
filesInProgress = filesInProgress.filter(
|
||||||
|
(f) => f.name !== file.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const error = (errorMessage) => {
|
||||||
|
filesInProgress.find((f) => f.name === file.name).hasFailed =
|
||||||
|
true;
|
||||||
|
};
|
||||||
|
|
||||||
|
uploadFile(
|
||||||
|
file,
|
||||||
|
record.id,
|
||||||
|
schemaField.id,
|
||||||
|
locale,
|
||||||
|
progress,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="min-width: 400px;">
|
||||||
|
<label>
|
||||||
|
<FieldLabel {locale} {channel} {schemaField}></FieldLabel>
|
||||||
|
</label>
|
||||||
|
<FieldError {schemaField} {validationError}></FieldError>
|
||||||
|
<button onclick={handleModalOpen}>Choose files</button>
|
||||||
|
|
||||||
|
<dialog bind:this={dialog}>
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<button onclick={handleModalClose} aria-label="Close" rel="prev"
|
||||||
|
></button>
|
||||||
|
<p>
|
||||||
|
<strong>Records</strong>
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
{#if suggestionsLoaded}
|
||||||
|
<form>
|
||||||
|
<button onclick={handleInsertSelected}>
|
||||||
|
Insert selected
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<input
|
||||||
|
oninput={handleFilesUpload}
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
disabled={uploadInProgress}
|
||||||
|
/>
|
||||||
|
{#each filesInProgress as fileInProgress}
|
||||||
|
<div>
|
||||||
|
<span>{fileInProgress.name}</span>
|
||||||
|
<progress value={fileInProgress.pct} max="100" />
|
||||||
|
{#if fileInProgress.hasFailed}
|
||||||
|
<span>Error</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
{#each suggestions as suggestion}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
value={suggestion.id}
|
||||||
|
bind:group={selectedRecordIds}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="#">{suggestion.title}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="#">
|
||||||
|
{suggestion.schemaName}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
{:else}
|
||||||
|
<progress />
|
||||||
|
{/if}
|
||||||
|
</article>
|
||||||
|
</dialog>
|
||||||
|
<div>
|
||||||
|
{#if edgeRecordPreviews.length == 0}
|
||||||
|
No relations exist
|
||||||
|
{:else}
|
||||||
|
<Sortable
|
||||||
|
onUpdate={handleSortUpdate}
|
||||||
|
items={edgeRecordPreviews}
|
||||||
|
itemKey="edge.id"
|
||||||
|
>
|
||||||
|
{#snippet itemView(edgeRecordPreview)}
|
||||||
|
<div>
|
||||||
|
<a href="#">{edgeRecordPreview.recordPreview.title}</a>
|
||||||
|
{edgeRecordPreview.recordPreview.schemaName}
|
||||||
|
<button
|
||||||
|
onclick={(e) =>
|
||||||
|
handleRemoveEdge(edgeRecordPreview.edge.id)}
|
||||||
|
>remove</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
</Sortable>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import { get, post } from "../../../modules/remote";
|
import { get, post } from "../../../modules/remote";
|
||||||
import { getApp } from "../../../app";
|
import { getApp } from "../../../app";
|
||||||
import { getLocaleName } from "../locale.svelte.js";
|
import FieldLabel from "./FieldLabel.svelte";
|
||||||
|
import FieldError from "./FieldError.svelte";
|
||||||
|
import Sortable from "../../../common/Sortable.svelte";
|
||||||
let {
|
let {
|
||||||
channel,
|
channel,
|
||||||
record,
|
record,
|
||||||
@@ -16,7 +18,6 @@
|
|||||||
let valuesChanged = $derived(newValue !== originalValue);
|
let valuesChanged = $derived(newValue !== originalValue);
|
||||||
let errorMessage = $state("");
|
let errorMessage = $state("");
|
||||||
// let validationErrorState = $state(validationError);
|
// let validationErrorState = $state(validationError);
|
||||||
let hasError = $derived(!!validationError);
|
|
||||||
const app = getApp();
|
const app = getApp();
|
||||||
|
|
||||||
let suggestionsLoaded = $state(false);
|
let suggestionsLoaded = $state(false);
|
||||||
@@ -57,7 +58,41 @@
|
|||||||
(data, err) => {
|
(data, err) => {
|
||||||
suggestionsLoaded = true;
|
suggestionsLoaded = true;
|
||||||
dialog.close();
|
dialog.close();
|
||||||
edgeRecordPreviews = data;
|
edgeRecordPreviews = data.edgeRecordPreviews;
|
||||||
|
validationError = data.validationError;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSortUpdate(updatedEdges) {
|
||||||
|
// let updatedFieldIds = updatedFields.map((f) => f.id);
|
||||||
|
// fields = fields.filter((f) => !updatedFieldIds.includes(f.id));
|
||||||
|
// fields = [...fields, ...updatedFields];
|
||||||
|
post(
|
||||||
|
app.url("records/sort-edges"),
|
||||||
|
{
|
||||||
|
ids: updatedEdges.map((e) => e.edge.id),
|
||||||
|
},
|
||||||
|
(data, err) => {
|
||||||
|
edgeRecordPreviews = updatedEdges;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRemoveEdge(edgeId) {
|
||||||
|
post(
|
||||||
|
app.url("edges/delete"),
|
||||||
|
{
|
||||||
|
id: edgeId,
|
||||||
|
from: record.id,
|
||||||
|
fieldId: schemaField.id,
|
||||||
|
locale: locale,
|
||||||
|
},
|
||||||
|
(data, err) => {
|
||||||
|
edgeRecordPreviews = edgeRecordPreviews.filter(
|
||||||
|
(e) => e.edge.id !== edgeId,
|
||||||
|
);
|
||||||
|
validationError = data.validationError;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -65,17 +100,9 @@
|
|||||||
|
|
||||||
<div style="min-width: 400px;">
|
<div style="min-width: 400px;">
|
||||||
<label>
|
<label>
|
||||||
{#if locale !== "main"}
|
<FieldLabel {locale} {channel} {schemaField}></FieldLabel>
|
||||||
{getLocaleName(channel, locale)} >
|
|
||||||
{/if}
|
|
||||||
{schemaField.name} <br />
|
|
||||||
</label>
|
</label>
|
||||||
{#if hasError}
|
<FieldError {schemaField} {validationError}></FieldError>
|
||||||
<small id={schemaField.id + "-help"}>{validationError.message}</small>
|
|
||||||
{:else if schemaField.help != ""}
|
|
||||||
<small id={schemaField.id + "-help"}>{schemaField.help}</small>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<button onclick={handleModalOpen}>Choose record</button>
|
<button onclick={handleModalOpen}>Choose record</button>
|
||||||
|
|
||||||
<dialog bind:this={dialog}>
|
<dialog bind:this={dialog}>
|
||||||
@@ -125,12 +152,23 @@
|
|||||||
{#if edgeRecordPreviews.length == 0}
|
{#if edgeRecordPreviews.length == 0}
|
||||||
No relations exist
|
No relations exist
|
||||||
{:else}
|
{:else}
|
||||||
{#each edgeRecordPreviews as edgeRecordPreview}
|
<Sortable
|
||||||
<div>
|
onUpdate={handleSortUpdate}
|
||||||
<a href="#">{edgeRecordPreview.recordPreview.title}</a>
|
items={edgeRecordPreviews}
|
||||||
{edgeRecordPreview.recordPreview.schemaName}
|
itemKey="edge.id"
|
||||||
</div>
|
>
|
||||||
{/each}
|
{#snippet itemView(edgeRecordPreview)}
|
||||||
|
<div>
|
||||||
|
<a href="#">{edgeRecordPreview.recordPreview.title}</a>
|
||||||
|
{edgeRecordPreview.recordPreview.schemaName}
|
||||||
|
<button
|
||||||
|
onclick={(e) =>
|
||||||
|
handleRemoveEdge(edgeRecordPreview.edge.id)}
|
||||||
|
>remove</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
</Sortable>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import { post } from "../../../modules/remote";
|
import { post } from "../../../modules/remote";
|
||||||
import { getApp } from "../../../app";
|
import { getApp } from "../../../app";
|
||||||
import { getLocaleName } from "../locale.svelte.js";
|
import FieldLabel from "./FieldLabel.svelte";
|
||||||
|
import FieldError from "./FieldError.svelte";
|
||||||
let { channel, record, schemaField, dataField, locale, validationError } =
|
let { channel, record, schemaField, dataField, locale, validationError } =
|
||||||
$props();
|
$props();
|
||||||
let originalValue = dataField?.value ?? schemaField.props.default;
|
let originalValue = dataField?.value ?? schemaField.props.default;
|
||||||
let newValue = $state(originalValue);
|
let newValue = $state(originalValue);
|
||||||
let valuesChanged = $derived(newValue !== originalValue);
|
let valuesChanged = $derived(newValue !== originalValue);
|
||||||
let errorMessage = $state("");
|
|
||||||
// let validationErrorState = $state(validationError);
|
// let validationErrorState = $state(validationError);
|
||||||
let hasError = $derived(!!validationError);
|
|
||||||
const app = getApp();
|
const app = getApp();
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
@@ -81,33 +80,9 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- <div style="position: relative;">
|
|
||||||
{#if field.selectOptions}
|
|
||||||
<Autocomplete {field} bind:value></Autocomplete>
|
|
||||||
{:else}
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
{id}
|
|
||||||
class="form-control"
|
|
||||||
class:is-invalid={errorMessage}
|
|
||||||
bind:value
|
|
||||||
autocomplete="off"
|
|
||||||
readonly={field.readonly && !isCreateMode}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if errorMessage}
|
|
||||||
<div class="invalid-feedback d-block">
|
|
||||||
{errorMessage}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div> -->
|
|
||||||
<div style="min-width: 400px;">
|
<div style="min-width: 400px;">
|
||||||
<label>
|
<label>
|
||||||
{#if locale !== "main"}
|
<FieldLabel {locale} {channel} {schemaField}></FieldLabel>
|
||||||
{getLocaleName(channel, locale)} >
|
|
||||||
{/if}
|
|
||||||
{schemaField.name} <br />
|
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -120,14 +95,8 @@
|
|||||||
onblur={handleBlur}
|
onblur={handleBlur}
|
||||||
oncompositionstart={handleCompositionStart}
|
oncompositionstart={handleCompositionStart}
|
||||||
oncompositionend={handleCompositionEnd}
|
oncompositionend={handleCompositionEnd}
|
||||||
aria-invalid={hasError ? "true" : ""}
|
|
||||||
/>
|
/>
|
||||||
{#if hasError}
|
<!-- aria-invalid={hasError ? "true" : ""} -->
|
||||||
<small id={schemaField.id + "-help"}
|
<FieldError {schemaField} {validationError}></FieldError>
|
||||||
>{validationError.message}</small
|
|
||||||
>
|
|
||||||
{:else if schemaField.help != ""}
|
|
||||||
<small id={schemaField.id + "-help"}>{schemaField.help}</small>
|
|
||||||
{/if}
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { getApp } from "../app";
|
||||||
|
|
||||||
|
export function uploadFile(file, recordId, fieldId, locale, progress, error) {
|
||||||
|
const app = getApp();
|
||||||
|
const csrf = document.querySelector('meta[name="csrf-token"]').content;
|
||||||
|
const chunkSize = 2 * 1024 * 1024; // 2MB
|
||||||
|
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||||
|
|
||||||
|
// Start the recursive process
|
||||||
|
sendChunk(0, null);
|
||||||
|
|
||||||
|
function sendChunk(currentChunk, fileId) {
|
||||||
|
const start = currentChunk * chunkSize;
|
||||||
|
const end = Math.min(start + chunkSize, file.size);
|
||||||
|
const chunk = file.slice(start, end);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", chunk);
|
||||||
|
if (fileId) {
|
||||||
|
formData.append("fileId", fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
formData.append("recordId", recordId);
|
||||||
|
formData.append("fieldId", fieldId);
|
||||||
|
formData.append("isLast", currentChunk === totalChunks - 1);
|
||||||
|
formData.append("filename", file.name);
|
||||||
|
formData.append("locale", locale);
|
||||||
|
formData.append("_token", csrf);
|
||||||
|
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("POST", app.url("upload"), true);
|
||||||
|
|
||||||
|
// Success Callback
|
||||||
|
xhr.onload = function () {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
const response = JSON.parse(xhr.responseText);
|
||||||
|
let fileId = response.fileId;
|
||||||
|
const nextChunk = currentChunk + 1;
|
||||||
|
if (nextChunk < totalChunks) {
|
||||||
|
progress({
|
||||||
|
pct: Math.round((nextChunk / totalChunks) * 100),
|
||||||
|
isComplete: false,
|
||||||
|
});
|
||||||
|
sendChunk(nextChunk, fileId);
|
||||||
|
} else {
|
||||||
|
progress({
|
||||||
|
pct: 100,
|
||||||
|
isComplete: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error("Upload failed at chunk " + currentChunk);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send(formData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php namespace Lucent\Core\Data;
|
||||||
|
|
||||||
|
class File
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $id,
|
||||||
|
public string $name,
|
||||||
|
public int $size,
|
||||||
|
public int $width,
|
||||||
|
public int $height,
|
||||||
|
public string $mime,
|
||||||
|
public string $checksum,
|
||||||
|
public ?string $recordId,
|
||||||
|
public bool $isShared,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php namespace Lucent\Core\Data;
|
||||||
|
|
||||||
|
class RecordFile
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $id,
|
||||||
|
public string $recordId,
|
||||||
|
public string $fileId,
|
||||||
|
public string $fieldId,
|
||||||
|
public RecordMode $mode,
|
||||||
|
public string $locale,
|
||||||
|
public int $rank,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php namespace Lucent\Core\File;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Lucent\Core\Data\File;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
|
class FileModule
|
||||||
|
{
|
||||||
|
public static function toDb(File $file): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"id" => $file->id,
|
||||||
|
"name" => $file->name,
|
||||||
|
"size" => $file->size,
|
||||||
|
"width" => $file->width,
|
||||||
|
"height" => $file->height,
|
||||||
|
"mime" => $file->mime,
|
||||||
|
"checksum" => $file->checksum,
|
||||||
|
"record_id" => $file->recordId,
|
||||||
|
"is_shared" => $file->isShared,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromDb(stdClass $data): File
|
||||||
|
{
|
||||||
|
return new File(
|
||||||
|
id: data_get($data, "id"),
|
||||||
|
name: data_get($data, "name"),
|
||||||
|
size: data_get($data, "size"),
|
||||||
|
width: data_get($data, "width"),
|
||||||
|
height: data_get($data, "height"),
|
||||||
|
mime: data_get($data, "mime"),
|
||||||
|
checksum: data_get($data, "checksum"),
|
||||||
|
recordId: data_get($data, "recordId"),
|
||||||
|
isShared: data_get($data, "isShared"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php namespace Lucent\Core\File;
|
||||||
|
|
||||||
|
use Lucent\Core\Data\RecordFile;
|
||||||
|
use Lucent\Core\Data\RecordMode;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
|
class RecordFileModule
|
||||||
|
{
|
||||||
|
public static function toDb(RecordFile $file): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"id" => $file->id,
|
||||||
|
"record_id" => $file->recordId,
|
||||||
|
"file_id" => $file->fileId,
|
||||||
|
"field_id" => $file->fieldId,
|
||||||
|
"mode" => $file->mode,
|
||||||
|
"locale" => $file->locale,
|
||||||
|
"rank" => $file->rank,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromDb(stdClass $data): RecordFile
|
||||||
|
{
|
||||||
|
return new RecordFile(
|
||||||
|
id: data_get($data, "id"),
|
||||||
|
recordId: data_get($data, "recordId"),
|
||||||
|
fileId: data_get($data, "file_id"),
|
||||||
|
fieldId: data_get($data, "field_id"),
|
||||||
|
mode: RecordMode::from(data_get($data, "mode")),
|
||||||
|
locale: data_get($data, "locale"),
|
||||||
|
rank: data_get($data, "rank"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,16 +14,23 @@ class RecordValidationModule
|
|||||||
* @param string[] $locales
|
* @param string[] $locales
|
||||||
* @param Field[] $schemaFields
|
* @param Field[] $schemaFields
|
||||||
* @param RecordField[] $recordFields
|
* @param RecordField[] $recordFields
|
||||||
|
* @param Edge[] $recordEdges
|
||||||
* @return RecordError[]
|
* @return RecordError[]
|
||||||
*/
|
*/
|
||||||
public static function validate(
|
public static function validate(
|
||||||
array $locales,
|
array $locales,
|
||||||
array $schemaFields,
|
array $schemaFields,
|
||||||
array $recordFields,
|
array $recordFields,
|
||||||
|
array $recordEdges,
|
||||||
): array {
|
): array {
|
||||||
$errors = [];
|
$errors = [];
|
||||||
foreach ($schemaFields as $schemaField) {
|
foreach ($schemaFields as $schemaField) {
|
||||||
$res = static::validateField("main", $schemaField, $recordFields);
|
$res = static::validateField(
|
||||||
|
"main",
|
||||||
|
$schemaField,
|
||||||
|
$recordFields,
|
||||||
|
$recordEdges,
|
||||||
|
);
|
||||||
$errors[] = $res;
|
$errors[] = $res;
|
||||||
if ($schemaField->translatable) {
|
if ($schemaField->translatable) {
|
||||||
foreach ($locales as $locale) {
|
foreach ($locales as $locale) {
|
||||||
@@ -31,6 +38,7 @@ class RecordValidationModule
|
|||||||
$locale["id"],
|
$locale["id"],
|
||||||
$schemaField,
|
$schemaField,
|
||||||
$recordFields,
|
$recordFields,
|
||||||
|
$recordEdges,
|
||||||
);
|
);
|
||||||
$errors[] = $res;
|
$errors[] = $res;
|
||||||
}
|
}
|
||||||
@@ -47,25 +55,39 @@ class RecordValidationModule
|
|||||||
*
|
*
|
||||||
* @param Field $schemaField
|
* @param Field $schemaField
|
||||||
* @param RecordField[] $recordFields
|
* @param RecordField[] $recordFields
|
||||||
|
* @param Edge[] $recordEdges
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function validateField(
|
public static function validateField(
|
||||||
string $locale,
|
string $locale,
|
||||||
Field $schemaField,
|
Field $schemaField,
|
||||||
array $recordFields,
|
array $recordFields,
|
||||||
|
array $recordEdges,
|
||||||
): ?RecordError {
|
): ?RecordError {
|
||||||
// General Validations
|
// General Validations
|
||||||
$error = static::validateRequired($locale, $schemaField, $recordFields);
|
$error = static::validateRequired(
|
||||||
|
$locale,
|
||||||
|
$schemaField,
|
||||||
|
$recordFields,
|
||||||
|
$recordEdges,
|
||||||
|
);
|
||||||
|
|
||||||
if (!empty($error)) {
|
if (!empty($error)) {
|
||||||
return $error;
|
return $error;
|
||||||
}
|
}
|
||||||
// Type specific
|
// Type specific
|
||||||
|
|
||||||
$error = match ($schemaField->type) {
|
$error = match ($schemaField->type) {
|
||||||
"text" => static::validateText(
|
"text" => static::validateText(
|
||||||
$locale,
|
$locale,
|
||||||
$schemaField,
|
$schemaField,
|
||||||
$recordFields,
|
$recordFields,
|
||||||
),
|
),
|
||||||
|
"relation" => static::validateRelation(
|
||||||
|
$locale,
|
||||||
|
$schemaField,
|
||||||
|
$recordEdges,
|
||||||
|
),
|
||||||
default => static::validateText(
|
default => static::validateText(
|
||||||
$locale,
|
$locale,
|
||||||
$schemaField,
|
$schemaField,
|
||||||
@@ -80,16 +102,37 @@ class RecordValidationModule
|
|||||||
*
|
*
|
||||||
* @param Field $schemaField
|
* @param Field $schemaField
|
||||||
* @param RecordField[] $recordFields
|
* @param RecordField[] $recordFields
|
||||||
|
* @param Edge[] $recordEdges
|
||||||
* @return ?RecordError
|
* @return ?RecordError
|
||||||
*/
|
*/
|
||||||
public static function validateRequired(
|
public static function validateRequired(
|
||||||
string $locale,
|
string $locale,
|
||||||
Field $schemaField,
|
Field $schemaField,
|
||||||
array $recordFields,
|
array $recordFields,
|
||||||
|
array $recordEdges,
|
||||||
): ?RecordError {
|
): ?RecordError {
|
||||||
if ($schemaField->required === false) {
|
if ($schemaField->required === false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return match ($schemaField->type) {
|
||||||
|
"relation" => static::validateRequiredRelation(
|
||||||
|
$locale,
|
||||||
|
$schemaField,
|
||||||
|
$recordEdges,
|
||||||
|
),
|
||||||
|
default => static::validateRequiredField(
|
||||||
|
$locale,
|
||||||
|
$schemaField,
|
||||||
|
$recordFields,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function validateRequiredField(
|
||||||
|
string $locale,
|
||||||
|
Field $schemaField,
|
||||||
|
array $recordFields,
|
||||||
|
): ?RecordError {
|
||||||
$recordField = static::findField($recordFields, $schemaField, $locale);
|
$recordField = static::findField($recordFields, $schemaField, $locale);
|
||||||
|
|
||||||
if (empty($recordField) || empty($recordField->value)) {
|
if (empty($recordField) || empty($recordField->value)) {
|
||||||
@@ -102,6 +145,27 @@ class RecordValidationModule
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
private static function validateRequiredRelation(
|
||||||
|
string $locale,
|
||||||
|
Field $schemaField,
|
||||||
|
array $recordEdges,
|
||||||
|
): ?RecordError {
|
||||||
|
if (
|
||||||
|
collect($recordEdges)
|
||||||
|
->where("fieldId", $schemaField->id)
|
||||||
|
->where("locale", $locale)
|
||||||
|
->where("mode", RecordMode::DRAFT)
|
||||||
|
->isEmpty()
|
||||||
|
) {
|
||||||
|
return new RecordError(
|
||||||
|
$schemaField->id,
|
||||||
|
$locale,
|
||||||
|
"This field is required",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -151,6 +215,45 @@ class RecordValidationModule
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param Field $schemaField
|
||||||
|
* @param Edge[] $recordEdges
|
||||||
|
* @return ?RecordError
|
||||||
|
*/
|
||||||
|
public static function validateRelation(
|
||||||
|
string $locale,
|
||||||
|
Field $schemaField,
|
||||||
|
array $recordEdges,
|
||||||
|
): ?RecordError {
|
||||||
|
$count = collect($recordEdges)
|
||||||
|
->where("fieldId", $schemaField->id)
|
||||||
|
->where("locale", $locale)
|
||||||
|
->where("mode", RecordMode::DRAFT)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
if ($schemaField->props->min > 0) {
|
||||||
|
if ($count < $schemaField->props->min) {
|
||||||
|
return new RecordError(
|
||||||
|
$schemaField->id,
|
||||||
|
$locale,
|
||||||
|
"You have to have at least {$schemaField->props->min} related records",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($schemaField->props->max > 0) {
|
||||||
|
if ($count > $schemaField->props->max) {
|
||||||
|
return new RecordError(
|
||||||
|
$schemaField->id,
|
||||||
|
$locale,
|
||||||
|
"You have to have at most {$schemaField->props->max} related records",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static function findField(
|
private static function findField(
|
||||||
array $recordFields,
|
array $recordFields,
|
||||||
Field $schemaField,
|
Field $schemaField,
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ class EdgeRepo
|
|||||||
->delete();
|
->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function delete(string $id): void
|
||||||
|
{
|
||||||
|
DB::table(self::TABLE_NAME)->where("id", $id)->delete();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Edge[]
|
* @return Edge[]
|
||||||
*/
|
*/
|
||||||
@@ -41,6 +46,22 @@ class EdgeRepo
|
|||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ?Edge
|
||||||
|
*/
|
||||||
|
public static function findOne(string $id): ?Edge
|
||||||
|
{
|
||||||
|
$edges = DB::table(self::TABLE_NAME)->where("id", $id)->get();
|
||||||
|
return $edges->map(EdgeModule::fromDb(...))->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function update(Edge $edge): void
|
||||||
|
{
|
||||||
|
DB::table(self::TABLE_NAME)
|
||||||
|
->where("id", $edge->id)
|
||||||
|
->update(EdgeModule::toDb($edge));
|
||||||
|
}
|
||||||
|
|
||||||
public static function findLastOfField(
|
public static function findLastOfField(
|
||||||
string $from,
|
string $from,
|
||||||
string $fieldId,
|
string $fieldId,
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php namespace Lucent\Core\Repository;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Lucent\Core\Data\File;
|
||||||
|
use Lucent\Core\File\FileModule;
|
||||||
|
|
||||||
|
class FileRepo
|
||||||
|
{
|
||||||
|
const TABLE_NAME = "files";
|
||||||
|
|
||||||
|
public static function insert(File $file): void
|
||||||
|
{
|
||||||
|
DB::table(self::TABLE_NAME)->insert(FileModule::toDb($file));
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static function update(File $file): void
|
||||||
|
// {
|
||||||
|
// DB::table(self::TABLE_NAME)
|
||||||
|
// ->where("id", $file->id)
|
||||||
|
// ->update(FileModule::toDb($file));
|
||||||
|
// }
|
||||||
|
|
||||||
|
public static function delete(string $fileId): void
|
||||||
|
{
|
||||||
|
DB::table(self::TABLE_NAME)->where("id", $fileId)->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php namespace Lucent\Core\Repository;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Lucent\Core\Data\RecordFile;
|
||||||
|
use Lucent\Core\File\RecordFileModule;
|
||||||
|
|
||||||
|
class RecordFileRepo
|
||||||
|
{
|
||||||
|
const TABLE_NAME = "records_files";
|
||||||
|
|
||||||
|
public static function insert(RecordFile $file): void
|
||||||
|
{
|
||||||
|
DB::table(self::TABLE_NAME)->insert(RecordFileModule::toDb($file));
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static function update(File $file): void
|
||||||
|
// {
|
||||||
|
// DB::table(self::TABLE_NAME)
|
||||||
|
// ->where("id", $file->id)
|
||||||
|
// ->update(FileModule::toDb($file));
|
||||||
|
// }
|
||||||
|
|
||||||
|
public static function delete(string $fileId): void
|
||||||
|
{
|
||||||
|
DB::table(self::TABLE_NAME)->where("id", $fileId)->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -71,6 +71,7 @@ class RecordRepo
|
|||||||
->where("records_data.mode", RecordMode::DRAFT->value)
|
->where("records_data.mode", RecordMode::DRAFT->value)
|
||||||
->orderBy("created_at", "desc")
|
->orderBy("created_at", "desc")
|
||||||
->limit(5)
|
->limit(5)
|
||||||
|
->distinct()
|
||||||
->get()
|
->get()
|
||||||
->map(fn($row) => RecordModule::recordPreviewFromDb($schemas, $row))
|
->map(fn($row) => RecordModule::recordPreviewFromDb($schemas, $row))
|
||||||
->toArray();
|
->toArray();
|
||||||
@@ -147,6 +148,7 @@ class RecordRepo
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
->orderBy("edges.rank", "asc")
|
->orderBy("edges.rank", "asc")
|
||||||
|
->distinct()
|
||||||
->get()
|
->get()
|
||||||
|
|
||||||
->map(
|
->map(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class FieldProp
|
|||||||
return match ($type) {
|
return match ($type) {
|
||||||
"text" => new TextFieldProp(min: 0, max: 0, default: ""),
|
"text" => new TextFieldProp(min: 0, max: 0, default: ""),
|
||||||
"relation" => new RelationFieldProp(schemas: [], min: 0, max: 0),
|
"relation" => new RelationFieldProp(schemas: [], min: 0, max: 0),
|
||||||
|
"file" => new FileFieldProp(min: 0, max: 0),
|
||||||
default => new InvalidFieldProp(),
|
default => new InvalidFieldProp(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -30,6 +31,10 @@ class FieldProp
|
|||||||
min: $props["min"] ?? 0,
|
min: $props["min"] ?? 0,
|
||||||
max: $props["max"] ?? 0,
|
max: $props["max"] ?? 0,
|
||||||
),
|
),
|
||||||
|
"file" => new FileFieldProp(
|
||||||
|
min: $props["min"] ?? 0,
|
||||||
|
max: $props["max"] ?? 0,
|
||||||
|
),
|
||||||
default => new InvalidFieldProp(),
|
default => new InvalidFieldProp(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?php namespace Lucent\Core\Schema\Data\FieldProp;
|
||||||
|
|
||||||
|
class FileFieldProp implements IFieldProp
|
||||||
|
{
|
||||||
|
public function __construct(public int $min, public int $max) {}
|
||||||
|
}
|
||||||
+12
-16
@@ -7,10 +7,7 @@ use stdClass;
|
|||||||
|
|
||||||
class EdgeRepo
|
class EdgeRepo
|
||||||
{
|
{
|
||||||
|
public function __construct() {}
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function insert(Edge $edge): void
|
public function insert(Edge $edge): void
|
||||||
{
|
{
|
||||||
@@ -22,7 +19,6 @@ class EdgeRepo
|
|||||||
}
|
}
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,7 +37,6 @@ class EdgeRepo
|
|||||||
}
|
}
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,25 +51,26 @@ class EdgeRepo
|
|||||||
Database::make()->table("edges")->insert($edgesDB);
|
Database::make()->table("edges")->insert($edgesDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return list<Edge>
|
* @return list<Edge>
|
||||||
*/
|
*/
|
||||||
public function findAll(): array
|
public function findAll(): array
|
||||||
{
|
{
|
||||||
$edges = Database::make()->table("edges")->get();
|
$edges = Database::make()->table("edges")->get();
|
||||||
return $edges->map([$this, 'mapEdge'])->toArray();
|
return $edges->map([$this, "mapEdge"])->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findForSource(string $recordId): array
|
public function findForSource(string $recordId): array
|
||||||
{
|
{
|
||||||
$edges = Database::make()->table("edges")->where("source", $recordId)->get();
|
$edges = Database::make()
|
||||||
return $edges->map([$this, 'mapEdge'])->toArray();
|
->table("edges")
|
||||||
|
->where("source", $recordId)
|
||||||
|
->get();
|
||||||
|
return $edges->map([$this, "mapEdge"])->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mapEdge(stdClass $edge): Edge
|
public function mapEdge(stdClass $edge): Edge
|
||||||
{
|
{
|
||||||
|
|
||||||
return new Edge(
|
return new Edge(
|
||||||
source: $edge->source,
|
source: $edge->source,
|
||||||
target: $edge->target,
|
target: $edge->target,
|
||||||
@@ -82,14 +78,14 @@ class EdgeRepo
|
|||||||
targetSchema: $edge->targetSchema,
|
targetSchema: $edge->targetSchema,
|
||||||
field: $edge->field,
|
field: $edge->field,
|
||||||
rank: $edge->rank,
|
rank: $edge->rank,
|
||||||
depth: $edge->depth ?? 0
|
depth: $edge->depth ?? 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function remove(Edge $edge): void
|
public function remove(Edge $edge): void
|
||||||
{
|
{
|
||||||
Database::make()->table("edges")
|
Database::make()
|
||||||
|
->table("edges")
|
||||||
->where("source", $edge->source)
|
->where("source", $edge->source)
|
||||||
->where("target", $edge->target)
|
->where("target", $edge->target)
|
||||||
->where("sourceSchema", $edge->sourceSchema)
|
->where("sourceSchema", $edge->sourceSchema)
|
||||||
@@ -100,7 +96,8 @@ class EdgeRepo
|
|||||||
|
|
||||||
public function findLastEdgeRank(string $source, string $field): string
|
public function findLastEdgeRank(string $source, string $field): string
|
||||||
{
|
{
|
||||||
$data = Database::make()->table("edges")
|
$data = Database::make()
|
||||||
|
->table("edges")
|
||||||
->where("source", $source)
|
->where("source", $source)
|
||||||
->where("field", $field)
|
->where("field", $field)
|
||||||
->orderBy("rank", "desc")
|
->orderBy("rank", "desc")
|
||||||
@@ -108,5 +105,4 @@ class EdgeRepo
|
|||||||
|
|
||||||
return $data->rank ?? "";
|
return $data->rank ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,17 @@ use Illuminate\Http\Request;
|
|||||||
use Lucent\Core\Repository\EdgeRepo;
|
use Lucent\Core\Repository\EdgeRepo;
|
||||||
use Lucent\Core\Repository\SchemaRepo;
|
use Lucent\Core\Repository\SchemaRepo;
|
||||||
use Lucent\Core\Repository\RecordRepo;
|
use Lucent\Core\Repository\RecordRepo;
|
||||||
|
use Lucent\Core\Repository\FieldRepo;
|
||||||
use Lucent\Core\Data\Edge;
|
use Lucent\Core\Data\Edge;
|
||||||
use Lucent\Core\Data\RecordMode;
|
use Lucent\Core\Data\RecordMode;
|
||||||
use Lucent\Id\Id;
|
use Lucent\Id\Id;
|
||||||
|
use Lucent\Core\Record\RecordValidationModule;
|
||||||
use function Lucent\Response\fail;
|
use function Lucent\Response\fail;
|
||||||
use function Lucent\Response\ok;
|
use function Lucent\Response\ok;
|
||||||
|
|
||||||
class EdgeController extends Controller
|
class EdgeController extends Controller
|
||||||
{
|
{
|
||||||
|
// Inserts edges to record, maybe I should move that to RecordController
|
||||||
public function postCreateMany(Request $request)
|
public function postCreateMany(Request $request)
|
||||||
{
|
{
|
||||||
$from = $request->input("from");
|
$from = $request->input("from");
|
||||||
@@ -46,7 +49,48 @@ class EdgeController extends Controller
|
|||||||
$locale,
|
$locale,
|
||||||
$from,
|
$from,
|
||||||
);
|
);
|
||||||
|
$field = FieldRepo::findOne($fieldId);
|
||||||
|
|
||||||
return ok($edgeRecordPreviews);
|
$recordEdges = array_map(fn($e) => $e->edge, $edgeRecordPreviews);
|
||||||
|
$validationError = RecordValidationModule::validateField(
|
||||||
|
$locale,
|
||||||
|
$field,
|
||||||
|
[],
|
||||||
|
$recordEdges,
|
||||||
|
);
|
||||||
|
|
||||||
|
return ok([
|
||||||
|
"edgeRecordPreviews" => $edgeRecordPreviews,
|
||||||
|
"validationError" => $validationError,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postDelete(Request $request)
|
||||||
|
{
|
||||||
|
$id = $request->input("id");
|
||||||
|
$fieldId = $request->input("fieldId");
|
||||||
|
$locale = $request->input("locale");
|
||||||
|
$from = $request->input("from");
|
||||||
|
EdgeRepo::delete($id);
|
||||||
|
$schemas = SchemaRepo::all();
|
||||||
|
$edgeRecordPreviews = RecordRepo::findEdgeRecordPreviewsForField(
|
||||||
|
$schemas,
|
||||||
|
$fieldId,
|
||||||
|
$locale,
|
||||||
|
$from,
|
||||||
|
);
|
||||||
|
$field = FieldRepo::findOne($fieldId);
|
||||||
|
|
||||||
|
$recordEdges = array_map(fn($e) => $e->edge, $edgeRecordPreviews);
|
||||||
|
$validationError = RecordValidationModule::validateField(
|
||||||
|
$locale,
|
||||||
|
$field,
|
||||||
|
[],
|
||||||
|
$recordEdges,
|
||||||
|
);
|
||||||
|
|
||||||
|
return ok([
|
||||||
|
"validationError" => $validationError,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use Lucent\Core\Data\RecordField;
|
|||||||
use Lucent\Core\Query\QueryModule;
|
use Lucent\Core\Query\QueryModule;
|
||||||
use Lucent\Core\Repository\FieldRepo;
|
use Lucent\Core\Repository\FieldRepo;
|
||||||
use Lucent\Core\Repository\SchemaRepo;
|
use Lucent\Core\Repository\SchemaRepo;
|
||||||
|
use Lucent\Core\Repository\EdgeRepo;
|
||||||
use Lucent\Core\Repository\RecordFieldRepo;
|
use Lucent\Core\Repository\RecordFieldRepo;
|
||||||
use Lucent\Core\Record\RecordModule;
|
use Lucent\Core\Record\RecordModule;
|
||||||
use Lucent\Core\Record\RecordFieldModule;
|
use Lucent\Core\Record\RecordFieldModule;
|
||||||
@@ -190,12 +191,6 @@ class RecordController
|
|||||||
$fields = FieldRepo::findBySchemaId($record->schemaId);
|
$fields = FieldRepo::findBySchemaId($record->schemaId);
|
||||||
$locales = ChannelModule::get()->locales;
|
$locales = ChannelModule::get()->locales;
|
||||||
|
|
||||||
$validationErrors = RecordValidationModule::validate(
|
|
||||||
$locales,
|
|
||||||
$fields,
|
|
||||||
$draftData,
|
|
||||||
);
|
|
||||||
|
|
||||||
$recordStatus = RecordModule::getStatus($record);
|
$recordStatus = RecordModule::getStatus($record);
|
||||||
$edgeRecordPreviews = RecordRepo::findEdgeRecordPreviewsByRecordId(
|
$edgeRecordPreviews = RecordRepo::findEdgeRecordPreviewsByRecordId(
|
||||||
$schemas,
|
$schemas,
|
||||||
@@ -210,6 +205,13 @@ class RecordController
|
|||||||
->where("edge.mode", RecordMode::LIVE)
|
->where("edge.mode", RecordMode::LIVE)
|
||||||
->values()
|
->values()
|
||||||
->toArray();
|
->toArray();
|
||||||
|
$recordEdges = array_map(fn($e) => $e->edge, $edgeRecordPreviewsDraft);
|
||||||
|
$validationErrors = RecordValidationModule::validate(
|
||||||
|
$locales,
|
||||||
|
$fields,
|
||||||
|
$draftData,
|
||||||
|
$recordEdges,
|
||||||
|
);
|
||||||
|
|
||||||
return Svelte::view(
|
return Svelte::view(
|
||||||
view: "recordEdit",
|
view: "recordEdit",
|
||||||
@@ -306,6 +308,7 @@ class RecordController
|
|||||||
$locale,
|
$locale,
|
||||||
$field,
|
$field,
|
||||||
[$recordField],
|
[$recordField],
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
return ok([
|
return ok([
|
||||||
@@ -314,6 +317,19 @@ class RecordController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function postSortEdges(Request $request)
|
||||||
|
{
|
||||||
|
$edgeIds = $request->input("ids");
|
||||||
|
foreach ($edgeIds as $index => $id) {
|
||||||
|
$edge = EdgeRepo::findOne($id);
|
||||||
|
if ($edge) {
|
||||||
|
$edge->rank = $index;
|
||||||
|
EdgeRepo::update($edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response()->json(["ids" => $edgeIds], 200);
|
||||||
|
}
|
||||||
|
|
||||||
public function clone(Request $request)
|
public function clone(Request $request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Http\Controller;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Lucent\Core\Data\File;
|
||||||
|
use Lucent\Core\Data\RecordFile;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Lucent\Channel\ChannelService;
|
||||||
|
use Lucent\Core\Data\RecordMode;
|
||||||
|
use Lucent\Core\Repository\FileRepo;
|
||||||
|
use Lucent\Core\Repository\RecordFileRepo;
|
||||||
|
use Lucent\File\FileService;
|
||||||
|
use Lucent\File\ImageService;
|
||||||
|
use Lucent\Id\Id;
|
||||||
|
use Lucent\Query\Query;
|
||||||
|
use Lucent\Record\InputData\RecordInputData;
|
||||||
|
use Lucent\Record\RecordService;
|
||||||
|
use Lucent\Record\Status;
|
||||||
|
use function Lucent\Response\fail;
|
||||||
|
use function Lucent\Response\ok;
|
||||||
|
|
||||||
|
class UploadController
|
||||||
|
{
|
||||||
|
public function upload(Request $request)
|
||||||
|
{
|
||||||
|
// 1. Basic validation
|
||||||
|
if (!$request->hasFile("file")) {
|
||||||
|
return response()->json(["error" => "No file provided"], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $request->file("file");
|
||||||
|
$fileId = $request->input("fileId") ?? Id::new();
|
||||||
|
|
||||||
|
$filename = $request->input("filename");
|
||||||
|
$isLast = $request->input("isLast") === "true";
|
||||||
|
$recordId = $request->input("recordId");
|
||||||
|
$fieldId = $request->input("fieldId");
|
||||||
|
$locale = $request->input("locale");
|
||||||
|
|
||||||
|
// 2. Define path (e.g., storage/app/chunks/myvideo.mp4)
|
||||||
|
$extension = $extension = pathinfo($filename, PATHINFO_EXTENSION);
|
||||||
|
$tempPath = "chunks/" . $fileId . "." . $extension;
|
||||||
|
$fullTempPath = Storage::disk("local")->path($tempPath);
|
||||||
|
// 3. Append the raw chunk data to the file
|
||||||
|
// This creates the file if it doesn't exist, or adds to it if it does.
|
||||||
|
Storage::disk("local")->append(
|
||||||
|
$tempPath,
|
||||||
|
file_get_contents($file->getRealPath()),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Finalize
|
||||||
|
if ($isLast) {
|
||||||
|
$dimensions = getimagesize($fullTempPath);
|
||||||
|
$width = $dimensions[0] ?? 0;
|
||||||
|
$height = $dimensions[1] ?? 0;
|
||||||
|
|
||||||
|
$file = new File(
|
||||||
|
id: $fileId,
|
||||||
|
name: $filename,
|
||||||
|
size: Storage::disk("local")->size($tempPath),
|
||||||
|
width: $width,
|
||||||
|
height: $height,
|
||||||
|
mime: Storage::disk("local")->mimeType($tempPath),
|
||||||
|
checksum: hash_file("md5", $fullTempPath),
|
||||||
|
recordId: $recordId,
|
||||||
|
isShared: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
$finalPath = "uploads/" . $fileId . "." . $extension;
|
||||||
|
Storage::disk("local")->move($tempPath, $finalPath);
|
||||||
|
|
||||||
|
FileRepo::insert($file);
|
||||||
|
|
||||||
|
$recordFile = new RecordFile(
|
||||||
|
id: Id::new(),
|
||||||
|
recordId: $recordId,
|
||||||
|
fileId: $fileId,
|
||||||
|
fieldId: $fieldId,
|
||||||
|
mode: RecordMode::DRAFT,
|
||||||
|
locale: $locale,
|
||||||
|
rank: 0,
|
||||||
|
);
|
||||||
|
RecordFileRepo::insert($recordFile);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
"status" => "finished",
|
||||||
|
"fileId" => $fileId,
|
||||||
|
"location" => Storage::url($finalPath),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
"status" => "chunk_saved",
|
||||||
|
"fileId" => $fileId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ use Lucent\Http\Controller\EdgeController;
|
|||||||
use Lucent\Http\Controller\FieldController;
|
use Lucent\Http\Controller\FieldController;
|
||||||
use Lucent\Http\Controller\FileController;
|
use Lucent\Http\Controller\FileController;
|
||||||
use Lucent\Http\Controller\HomeController;
|
use Lucent\Http\Controller\HomeController;
|
||||||
|
use Lucent\Http\Controller\UploadController;
|
||||||
use Lucent\Http\Controller\MemberController;
|
use Lucent\Http\Controller\MemberController;
|
||||||
use Lucent\Http\Controller\RecordController;
|
use Lucent\Http\Controller\RecordController;
|
||||||
use Lucent\Http\Controller\RevisionController;
|
use Lucent\Http\Controller\RevisionController;
|
||||||
@@ -99,11 +100,20 @@ Route::group(
|
|||||||
"untrash",
|
"untrash",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
Route::post("records/sort-edges", [
|
||||||
|
RecordController::class,
|
||||||
|
"postSortEdges",
|
||||||
|
]);
|
||||||
|
|
||||||
// EDGES
|
// EDGES
|
||||||
Route::post("edges/many", [
|
Route::post("edges/many", [
|
||||||
EdgeController::class,
|
EdgeController::class,
|
||||||
"postCreateMany",
|
"postCreateMany",
|
||||||
]);
|
]);
|
||||||
|
Route::post("edges/delete", [EdgeController::class, "postDelete"]);
|
||||||
|
|
||||||
|
// UPLOAD
|
||||||
|
Route::post("upload", [UploadController::class, "upload"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
//OLD
|
//OLD
|
||||||
|
|||||||
Reference in New Issue
Block a user