edit schema wip
This commit is contained in:
@@ -8,3 +8,9 @@ main {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
button.danger {
|
||||
background-color: #d93526;
|
||||
border-color: #d93526;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<script>
|
||||
let { onDelete, text } = $props();
|
||||
let isClicked = $state(false);
|
||||
|
||||
function handleTryDelete(e) {
|
||||
e.preventDefault();
|
||||
isClicked = true;
|
||||
}
|
||||
|
||||
function handleCancel(e) {
|
||||
e.preventDefault();
|
||||
isClicked = false;
|
||||
}
|
||||
|
||||
function handleRealDelete(e) {
|
||||
e.preventDefault();
|
||||
onDelete();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !isClicked}
|
||||
<form onsubmit={handleTryDelete}>
|
||||
<button class="danger" type="submit">
|
||||
{@render text()}
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
{#if isClicked}
|
||||
Are you sure?
|
||||
<form onsubmit={handleCancel}>
|
||||
<button class="secondary" type="submit">No</button>
|
||||
</form>
|
||||
<form onsubmit={handleRealDelete}>
|
||||
<button class="danger" type="submit">Yes</button>
|
||||
</form>
|
||||
{/if}
|
||||
@@ -0,0 +1,150 @@
|
||||
<script>
|
||||
import { arrayMoveElement } from "../helpers";
|
||||
import { flip } from "svelte/animate";
|
||||
|
||||
let {
|
||||
sortableClass,
|
||||
onUpdate,
|
||||
items,
|
||||
itemView,
|
||||
itemCssClass,
|
||||
itemKey = "id",
|
||||
type = "list",
|
||||
handleClass = null,
|
||||
disabled = false,
|
||||
} = $props();
|
||||
// let sortableInstance = $state();
|
||||
let sortableContainer = $state();
|
||||
let draggedItem = $state();
|
||||
|
||||
function handleDragStart(event, item) {
|
||||
if (disabled) {
|
||||
return false;
|
||||
}
|
||||
draggedItem = item;
|
||||
// Set data required for drag operation
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
event.dataTransfer.setData("text/plain", item.id);
|
||||
// Set a ghost drag image
|
||||
event.currentTarget.classList.add("dragging");
|
||||
}
|
||||
|
||||
// Handle drag over another item
|
||||
function handleDragOver(event) {
|
||||
event.preventDefault();
|
||||
event.target.closest(".draggable-item").classList.add("dragover");
|
||||
event.dataTransfer.dropEffect = "move";
|
||||
}
|
||||
|
||||
// Handle dropping the item
|
||||
function handleDrop(event, targetItem) {
|
||||
event.preventDefault();
|
||||
|
||||
if (draggedItem === targetItem) return;
|
||||
// Find positions of dragged and target items
|
||||
const draggedIndex = items.findIndex(
|
||||
(item) => getItem(item, itemKey) === getItem(draggedItem, itemKey),
|
||||
);
|
||||
const targetIndex = items.findIndex(
|
||||
(item) => getItem(item, itemKey) === getItem(targetItem, itemKey),
|
||||
);
|
||||
|
||||
onUpdate(
|
||||
arrayMoveElement([...items], draggedIndex, targetIndex),
|
||||
draggedIndex,
|
||||
targetIndex,
|
||||
);
|
||||
}
|
||||
|
||||
function getItem(item, key) {
|
||||
if (key === null) {
|
||||
return item;
|
||||
}
|
||||
if (key.includes(".")) {
|
||||
return key.split(".").reduce((a, b) => a[b] ?? null, item);
|
||||
}
|
||||
return item[key];
|
||||
}
|
||||
|
||||
function handleDragEnd(event) {
|
||||
event.target.classList.remove("dragging");
|
||||
sortableContainer
|
||||
.querySelector(".dragover")
|
||||
?.classList.remove("dragover");
|
||||
draggedItem = null;
|
||||
const draggableItem = event.target.closest(".draggable-item");
|
||||
draggableItem.draggable = false;
|
||||
}
|
||||
|
||||
function handleDragLeave(event) {
|
||||
// event.target.classList.remove("dragover");
|
||||
const draggableItem = event.target.closest(".draggable-item");
|
||||
draggableItem.classList.remove("dragover");
|
||||
draggableItem.draggable = false;
|
||||
}
|
||||
|
||||
function handleMouseDown(e) {
|
||||
const handleEl = e.target.closest("." + handleClass);
|
||||
if (!handleEl) {
|
||||
return true;
|
||||
}
|
||||
const draggableItem = e.target.closest(".draggable-item");
|
||||
draggableItem.draggable = true;
|
||||
}
|
||||
|
||||
function handleMouseUp(e) {
|
||||
const handleEl = e.target.closest("." + handleClass);
|
||||
if (!handleEl) {
|
||||
return true;
|
||||
}
|
||||
const draggableItem = e.target.closest(".draggable-item");
|
||||
draggableItem.draggable = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if type === "table"}
|
||||
<tbody
|
||||
class="sortable-container {sortableClass}"
|
||||
bind:this={sortableContainer}
|
||||
>
|
||||
{#each items as item (getItem(item, itemKey))}
|
||||
<tr
|
||||
animate:flip={{ duration: 100 }}
|
||||
class="draggable-item {itemCssClass}"
|
||||
draggable="false"
|
||||
onmousedown={handleMouseDown}
|
||||
onmouseup={handleMouseUp}
|
||||
ondragstart={(e) => handleDragStart(e, item)}
|
||||
ondragover={handleDragOver}
|
||||
ondragleave={handleDragLeave}
|
||||
ondrop={(e) => handleDrop(e, item)}
|
||||
ondragend={handleDragEnd}
|
||||
>
|
||||
{@render itemView(item)}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
{:else}
|
||||
<div
|
||||
class="sortable-container {sortableClass}"
|
||||
bind:this={sortableContainer}
|
||||
>
|
||||
{#each items as item (getItem(item, itemKey))}
|
||||
<div
|
||||
animate:flip={{ duration: 100 }}
|
||||
role="listitem"
|
||||
class="draggable-item {itemCssClass}"
|
||||
draggable="false"
|
||||
onmousedown={handleMouseDown}
|
||||
onmouseup={handleMouseUp}
|
||||
ondragstart={(e) => handleDragStart(e, item)}
|
||||
ondragover={handleDragOver}
|
||||
ondragleave={handleDragLeave}
|
||||
ondrop={(e) => handleDrop(e, item)}
|
||||
ondragend={handleDragEnd}
|
||||
>
|
||||
{@render itemView(item)}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -2,14 +2,13 @@
|
||||
import ChannelLayout from "../../layouts/ChannelLayout.svelte";
|
||||
import { post } from "../../modules/remote";
|
||||
import { getApp } from "../../app";
|
||||
let { channel, user, data } = $props();
|
||||
let { channel, user, data, newRank } = $props();
|
||||
let name = $state("");
|
||||
let alias = $state("");
|
||||
const app = getApp();
|
||||
|
||||
function handleSchemaCreate(e) {
|
||||
function handleCreate(e) {
|
||||
e.preventDefault();
|
||||
console.log(data);
|
||||
post(
|
||||
app.url("fields"),
|
||||
{
|
||||
@@ -33,7 +32,7 @@
|
||||
{#snippet body()}
|
||||
<h3 class="header-small mb-4 mt-5">Create a <em>{data.type}</em> field</h3>
|
||||
|
||||
<form onsubmit={handleSchemaCreate}>
|
||||
<form onsubmit={handleCreate}>
|
||||
<fieldset>
|
||||
<label>
|
||||
Name
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<script>
|
||||
import ChannelLayout from "../../layouts/ChannelLayout.svelte";
|
||||
import TextFieldProps from "./TextFieldProps.svelte";
|
||||
import DeleteButton from "../../common/DeleteButton.svelte";
|
||||
import { post } from "../../modules/remote";
|
||||
import { getApp } from "../../app";
|
||||
let { channel, user, data } = $props();
|
||||
|
||||
const app = getApp();
|
||||
|
||||
function handleUpdate(e) {
|
||||
e.preventDefault();
|
||||
post(
|
||||
app.url("fields/update"),
|
||||
{
|
||||
field: data.field,
|
||||
},
|
||||
(data, err) => {
|
||||
if (err.isEmpty()) {
|
||||
Turbo.visit(app.url("schemas"));
|
||||
} else {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
post(
|
||||
app.url("fields/delete"),
|
||||
{
|
||||
fieldId: data.field.id,
|
||||
},
|
||||
(data, err) => {
|
||||
if (err.isEmpty()) {
|
||||
Turbo.visit(app.url("schemas"));
|
||||
} else {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<ChannelLayout {body} {channel} {user}></ChannelLayout>
|
||||
{#snippet body()}
|
||||
<h3 class="header-small mb-4 mt-5">
|
||||
Edit <em>{data.field.type}</em> field {data.field.name}
|
||||
</h3>
|
||||
|
||||
<form onsubmit={handleUpdate}>
|
||||
<fieldset>
|
||||
<label>
|
||||
Name
|
||||
<input
|
||||
bind:value={data.field.name}
|
||||
placeholder="ex. Description"
|
||||
minlength="2"
|
||||
maxlength="30"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Alias
|
||||
<input
|
||||
bind:value={data.field.alias}
|
||||
placeholder="ex. description"
|
||||
minlength="2"
|
||||
maxlength="30"
|
||||
required
|
||||
aria-describedby="alias-helper"
|
||||
/>
|
||||
<small id="alias-helper">
|
||||
Developers will use this to reference the field
|
||||
</small>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
{#if data.field.type === "text"}
|
||||
<TextFieldProps field={data.field}></TextFieldProps>
|
||||
{/if}
|
||||
|
||||
<button type="submit">Update</button>
|
||||
</form>
|
||||
<DeleteButton onDelete={handleDelete}>
|
||||
{#snippet text()}
|
||||
Delete field
|
||||
{/snippet}
|
||||
</DeleteButton>
|
||||
{/snippet}
|
||||
@@ -0,0 +1,49 @@
|
||||
<script>
|
||||
let { field } = $props();
|
||||
</script>
|
||||
|
||||
<fieldset>
|
||||
<label>
|
||||
<input
|
||||
bind:checked={field.props.required}
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
/>
|
||||
Required
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
bind:checked={field.props.readonly}
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
/>
|
||||
Readonly
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
bind:checked={field.props.hidden}
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
/>
|
||||
Hidden
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label>
|
||||
Default
|
||||
<input bind:value={field.props.default} />
|
||||
</label>
|
||||
<label>
|
||||
Help text
|
||||
<input bind:value={field.props.help} />
|
||||
</label>
|
||||
<label>
|
||||
Min characters
|
||||
<input type="number" bind:value={field.props.min} />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Max characters
|
||||
<input type="number" bind:value={field.props.max} />
|
||||
</label>
|
||||
</fieldset>
|
||||
@@ -0,0 +1,61 @@
|
||||
<script>
|
||||
import ChannelLayout from "../../layouts/ChannelLayout.svelte";
|
||||
import Sortable from "../../common/Sortable.svelte";
|
||||
import { post } from "../../modules/remote";
|
||||
import { getApp } from "../../app";
|
||||
let { channel, user, data } = $props();
|
||||
const app = getApp();
|
||||
|
||||
function handleSchemaCreate(e) {
|
||||
e.preventDefault();
|
||||
// post(
|
||||
// channel.lucentUrl + "/schemas",
|
||||
// {
|
||||
// name: newSchemaName,
|
||||
// alias: newSchemaAlias,
|
||||
// },
|
||||
// (data, err) => {
|
||||
// if (err.isEmpty()) {
|
||||
// Turbo.visit(window.location.href);
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
}
|
||||
</script>
|
||||
|
||||
<ChannelLayout {body} {channel} {user}></ChannelLayout>
|
||||
{#snippet body()}
|
||||
<h3>Edit Schema</h3>
|
||||
|
||||
<form onsubmit={handleSchemaCreate}>
|
||||
<fieldset>
|
||||
<label>
|
||||
Name
|
||||
<input
|
||||
bind:value={data.schema.name}
|
||||
placeholder="ex. Blog Posts"
|
||||
minlength="2"
|
||||
maxlength="30"
|
||||
required
|
||||
/>
|
||||
<small id="alias-helper">Plural is recommended</small>
|
||||
</label>
|
||||
<label>
|
||||
Alias
|
||||
<input
|
||||
bind:value={data.schema.alias}
|
||||
placeholder="ex. blog_posts"
|
||||
minlength="2"
|
||||
maxlength="30"
|
||||
required
|
||||
aria-describedby="alias-helper"
|
||||
/>
|
||||
<small id="alias-helper">
|
||||
Developers will use this to reference the field
|
||||
</small>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Update</button>
|
||||
</form>
|
||||
{/snippet}
|
||||
@@ -1,10 +1,12 @@
|
||||
<script>
|
||||
import ChannelLayout from "../../layouts/ChannelLayout.svelte";
|
||||
import Sortable from "../../common/Sortable.svelte";
|
||||
import { post } from "../../modules/remote";
|
||||
import { getApp } from "../../app";
|
||||
let { channel, user, data } = $props();
|
||||
let newSchemaName = $state("");
|
||||
let newSchemaAlias = $state("");
|
||||
let fields = $state(data.fields);
|
||||
const app = getApp();
|
||||
|
||||
function handleSchemaCreate(e) {
|
||||
@@ -22,6 +24,20 @@
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function handleSortUpdate(updatedFields) {
|
||||
let updatedFieldIds = updatedFields.map((f) => f.id);
|
||||
fields = fields.filter((f) => !updatedFieldIds.includes(f.id));
|
||||
fields = [...fields, ...updatedFields];
|
||||
|
||||
post(
|
||||
channel.lucentUrl + "/fields/reorder",
|
||||
{
|
||||
ids: updatedFieldIds,
|
||||
},
|
||||
(data, err) => {},
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<ChannelLayout {body} {channel} {user}></ChannelLayout>
|
||||
@@ -65,15 +81,32 @@
|
||||
<div style="display: flex;gap:20px">
|
||||
{#each data.schemas as schema}
|
||||
<article style="min-width: 300px;">
|
||||
<header>{schema.name}</header>
|
||||
<header>
|
||||
<a href={app.url("schemas/edit/" + schema.id)}
|
||||
>{schema.name}</a
|
||||
>
|
||||
</header>
|
||||
<details>
|
||||
<summary>Fields</summary>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Title</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<Sortable
|
||||
type="table"
|
||||
onUpdate={handleSortUpdate}
|
||||
items={fields.filter(
|
||||
(field) => field.schemaId === schema.id,
|
||||
)}
|
||||
>
|
||||
{#snippet itemView(field)}
|
||||
<td
|
||||
><a
|
||||
href={app.url(
|
||||
"fields/edit/" + field.id,
|
||||
)}>{field.name}</a
|
||||
></td
|
||||
>
|
||||
<td>{field.type}</td>
|
||||
{/snippet}
|
||||
</Sortable>
|
||||
</table>
|
||||
</details>
|
||||
<details>
|
||||
|
||||
+103
-29
@@ -1,52 +1,126 @@
|
||||
import {formatDistanceToNow, parseJSON, format, parse} from "date-fns";
|
||||
import { formatDistanceToNow, parseJSON, format, parse } from "date-fns";
|
||||
|
||||
export function friendlyDate(date) {
|
||||
return formatDistanceToNow(parseJSON(date), {addSuffix: true});
|
||||
return formatDistanceToNow(parseJSON(date), { addSuffix: true });
|
||||
}
|
||||
|
||||
export function readableDate(date) {
|
||||
if(!date){
|
||||
return "";
|
||||
}
|
||||
return format(parseJSON(date), "dd MMM yyyy");
|
||||
if (!date) {
|
||||
return "";
|
||||
}
|
||||
return format(parseJSON(date), "dd MMM yyyy");
|
||||
}
|
||||
|
||||
export function readableDatetime(date) {
|
||||
if(!date){
|
||||
return "";
|
||||
}
|
||||
if (!date) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return format(parseJSON(date), "dd MMM yyyy HH:mm");
|
||||
return format(parseJSON(date), "dd MMM yyyy HH:mm");
|
||||
}
|
||||
|
||||
|
||||
export function stripHtml(html = "") {
|
||||
let tmp = document.createElement("div");
|
||||
tmp.innerHTML = html;
|
||||
return tmp.textContent || tmp.innerText || "";
|
||||
let tmp = document.createElement("div");
|
||||
tmp.innerHTML = html;
|
||||
return tmp.textContent || tmp.innerText || "";
|
||||
}
|
||||
|
||||
|
||||
export function randomId(length = 10) {
|
||||
return Math.random().toString(36).substring(2, length + 2);
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
.substring(2, length + 2);
|
||||
}
|
||||
|
||||
export function clickOutside(node) {
|
||||
|
||||
const handleClick = event => {
|
||||
if (node && !node.contains(event.target) && !event.defaultPrevented) {
|
||||
node.dispatchEvent(
|
||||
new CustomEvent('click_outside', node)
|
||||
)
|
||||
}
|
||||
const handleClick = (event) => {
|
||||
if (node && !node.contains(event.target) && !event.defaultPrevented) {
|
||||
node.dispatchEvent(new CustomEvent("click_outside", node));
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleClick, true);
|
||||
document.addEventListener("click", handleClick, true);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
}
|
||||
}
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener("click", handleClick, true);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function arrayUnique(array) {
|
||||
return array.filter((value, index) => array.indexOf(value) === index);
|
||||
}
|
||||
|
||||
export function arrayUniqueBy(items, uniqueBy) {
|
||||
const ids = new Set(items.map((item) => item[uniqueBy]));
|
||||
return [...ids].map((id) => items.find((i) => i[uniqueBy] === id));
|
||||
}
|
||||
|
||||
export function arrayMoveElement(array, from, to) {
|
||||
if (from === to) {
|
||||
return array;
|
||||
}
|
||||
|
||||
const item = array.find((v, i) => i === from);
|
||||
const arrayWithout = array.filter((v, i) => i !== from);
|
||||
if (from > to) {
|
||||
return arrayWithout.reduce((c, v, i) => {
|
||||
if (i === to) {
|
||||
return [...c, item, v];
|
||||
}
|
||||
return [...c, v];
|
||||
}, []);
|
||||
}
|
||||
|
||||
return arrayWithout.reduce((c, v, i) => {
|
||||
if (i + 1 === to) {
|
||||
return [...c, v, item];
|
||||
}
|
||||
return [...c, v];
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function isEqual(db, ed) {
|
||||
let isObject = (x) =>
|
||||
typeof x === "object" && !Array.isArray(x) && x !== null;
|
||||
let isArray = (x) => x?.constructor === Array;
|
||||
let isEmpty = (x) => x === null || x === undefined || x == [];
|
||||
const db_value = db ?? null;
|
||||
const ed_value = ed ?? null;
|
||||
|
||||
if (isObject(db_value)) {
|
||||
let keys = Object.keys(db_value);
|
||||
return keys.reduce((acc, k) => {
|
||||
if (acc === false) {
|
||||
return false;
|
||||
}
|
||||
return isEqual(db_value?.[k], ed_value?.[k]);
|
||||
}, true);
|
||||
}
|
||||
if (isArray(db_value)) {
|
||||
return db_value.reduce((c, v, i) => {
|
||||
if (c === false) {
|
||||
return false;
|
||||
}
|
||||
return isEqual(v, ed_value?.[i]);
|
||||
}, true);
|
||||
}
|
||||
|
||||
if (isEmpty(db_value) && isEmpty(ed_value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (db_value == ed_value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
// const ok = Object.keys,
|
||||
// tx = typeof x,
|
||||
// ty = typeof y;
|
||||
// return x && y && tx === "object" && tx === ty
|
||||
// ? ok(x).length === ok(y).length &&
|
||||
// ok(x).every((key) => isEqual(x[key], y[key]))
|
||||
// : x === y;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import ContentIndex from "./svelte/content/Index.svelte";
|
||||
import HomeEntry from "./entry/HomeEntry/HomeEntry.svelte";
|
||||
import SchemaEntry from "./entry/SchemaEntry/SchemaEntry.svelte";
|
||||
import FieldCreateEntry from "./entry/FieldCreateEntry/FieldCreateEntry.svelte";
|
||||
import FieldEditEntry from "./entry/FieldEditEntry/FieldEditEntry.svelte";
|
||||
import SchemaEditEntry from "./entry/SchemaEditEntry/SchemaEditEntry.svelte";
|
||||
import BuildReport from "./svelte/build/Report.svelte";
|
||||
import { createApp } from "./app";
|
||||
|
||||
@@ -31,6 +33,8 @@ const entryComponents = {
|
||||
setup: SetupIndex,
|
||||
schemas: SchemaEntry,
|
||||
fieldCreate: FieldCreateEntry,
|
||||
fieldEdit: FieldEditEntry,
|
||||
schemaEdit: SchemaEditEntry,
|
||||
};
|
||||
Turbo.start();
|
||||
|
||||
|
||||
@@ -10,19 +10,40 @@ class FieldRepo
|
||||
const TABLE_NAME = "fields";
|
||||
|
||||
/**
|
||||
* @@return Field[]
|
||||
* @return Field[]
|
||||
*/
|
||||
public static function all(): array
|
||||
{
|
||||
return DB::table(self::TABLE_NAME)
|
||||
->orderBy("name")
|
||||
->orderBy("rank")
|
||||
->get()
|
||||
->map(SchemaModule::fromDb(...))
|
||||
->map(FieldModule::fromDb(...))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public static function findOne(string $id): ?Field
|
||||
{
|
||||
return DB::table(self::TABLE_NAME)
|
||||
->where("id", $id)
|
||||
->get()
|
||||
->map(FieldModule::fromDb(...))
|
||||
->first();
|
||||
}
|
||||
|
||||
public static function insert(Field $field): void
|
||||
{
|
||||
DB::table(self::TABLE_NAME)->insert(FieldModule::toDb($field));
|
||||
}
|
||||
|
||||
public static function update(Field $field): void
|
||||
{
|
||||
DB::table(self::TABLE_NAME)
|
||||
->where("id", $field->id)
|
||||
->update(FieldModule::toDb($field));
|
||||
}
|
||||
|
||||
public static function delete(string $fieldId): void
|
||||
{
|
||||
DB::table(self::TABLE_NAME)->where("id", $fieldId)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,15 @@ class SchemaRepo
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public static function findOne(string $id): ?Schema
|
||||
{
|
||||
return DB::table(self::TABLE_NAME)
|
||||
->where("id", $id)
|
||||
->get()
|
||||
->map(SchemaModule::fromDb(...))
|
||||
->first();
|
||||
}
|
||||
|
||||
public static function insert(Schema $schema): void
|
||||
{
|
||||
DB::table(self::TABLE_NAME)->insert(SchemaModule::toDb($schema));
|
||||
|
||||
@@ -10,6 +10,7 @@ class Field
|
||||
public string $name,
|
||||
public string $type,
|
||||
public string $schemaId,
|
||||
public int $rank,
|
||||
public IFieldProp $props,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,15 @@ class FieldProp
|
||||
};
|
||||
}
|
||||
|
||||
public static function fromDb(string $propsJson): IFieldProp
|
||||
public static function fromDb(string $type, string $propsJson): IFieldProp
|
||||
{
|
||||
$props = json_decode($propsJson, true);
|
||||
return match ($props["type"]) {
|
||||
return self::fromArray($type, $props);
|
||||
}
|
||||
|
||||
public static function fromArray(string $type, array $props): IFieldProp
|
||||
{
|
||||
return match ($type) {
|
||||
"text" => new TextFieldProp(
|
||||
required: $props["required"] ?? false,
|
||||
readonly: $props["readonly"] ?? false,
|
||||
|
||||
@@ -23,6 +23,7 @@ class FieldModule
|
||||
"alias" => $field->alias,
|
||||
"name" => $field->name,
|
||||
"type" => $field->type,
|
||||
"rank" => $field->rank,
|
||||
"props" => json_encode($field->props),
|
||||
"schema_id" => $field->schemaId,
|
||||
];
|
||||
@@ -36,7 +37,11 @@ class FieldModule
|
||||
name: data_get($data, "name"),
|
||||
type: data_get($data, "type"),
|
||||
schemaId: data_get($data, "schema_id"),
|
||||
props: FieldProp::fromDb(data_get($data, "props")),
|
||||
rank: data_get($data, "rank"),
|
||||
props: FieldProp::fromDb(
|
||||
data_get($data, "type"),
|
||||
data_get($data, "props"),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ class FieldController extends Controller
|
||||
$name = $request->input("name");
|
||||
$alias = $request->input("alias");
|
||||
$fieldType = $request->input("fieldType");
|
||||
$fields = FieldRepo::all();
|
||||
$newRank = collect($fields)->last()->rank + 1;
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
"name" => "required|string|max:30|min:2",
|
||||
@@ -77,9 +79,98 @@ class FieldController extends Controller
|
||||
alias: $alias,
|
||||
type: $fieldType,
|
||||
props: $fieldProps,
|
||||
rank: $newRank,
|
||||
);
|
||||
|
||||
FieldRepo::insert($field);
|
||||
return response()->json(["field" => $field], 201);
|
||||
}
|
||||
|
||||
public function edit(Request $request)
|
||||
{
|
||||
$fieldId = $request->route("id");
|
||||
$fields = FieldRepo::all();
|
||||
$field = collect($fields)->firstWhere("id", $fieldId);
|
||||
|
||||
if (empty($field)) {
|
||||
return response()->json(["errors" => ["Field not found"]], 404);
|
||||
}
|
||||
|
||||
$schemas = SchemaRepo::all();
|
||||
$schema = collect($schemas)->firstWhere("id", $field->schemaId);
|
||||
|
||||
return $this->svelte->render(
|
||||
view: "fieldEdit",
|
||||
title: "Edit Field",
|
||||
data: [
|
||||
"schemas" => $schemas,
|
||||
"schema" => $schema,
|
||||
"field" => $field,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function postUpdate(Request $request)
|
||||
{
|
||||
$fieldData = $request->input("field");
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
"field.name" => "required|string|max:30|min:2",
|
||||
"field.alias" => "required|alpha_dash:ascii|max:30|min:2",
|
||||
]);
|
||||
if ($validator->fails()) {
|
||||
return response()->json(["errors" => $validator->errors()], 422);
|
||||
}
|
||||
|
||||
$field = FieldRepo::findOne(data_get($fieldData, "id"));
|
||||
|
||||
if (empty($field)) {
|
||||
return response()->json(["errors" => ["Field not found"]], 404);
|
||||
}
|
||||
|
||||
$fieldProps = FieldProp::fromArray(
|
||||
$field->type,
|
||||
data_get($fieldData, "props"),
|
||||
);
|
||||
|
||||
$field = new Field(
|
||||
id: $field->id,
|
||||
schemaId: $field->schemaId,
|
||||
name: data_get($fieldData, "name"),
|
||||
alias: data_get($fieldData, "alias"),
|
||||
type: $field->type,
|
||||
rank: $field->rank,
|
||||
props: $fieldProps,
|
||||
);
|
||||
|
||||
FieldRepo::update($field);
|
||||
return response()->json(["field" => $field], 200);
|
||||
}
|
||||
|
||||
public function postDelete(Request $request)
|
||||
{
|
||||
$fieldId = $request->input("fieldId");
|
||||
$field = FieldRepo::findOne($fieldId);
|
||||
|
||||
if (empty($field)) {
|
||||
return response()->json(["errors" => ["Field not found"]], 404);
|
||||
}
|
||||
|
||||
FieldRepo::delete($fieldId);
|
||||
return response()->json([], 200);
|
||||
}
|
||||
|
||||
public function postReorder(Request $request)
|
||||
{
|
||||
$ids = $request->input("ids");
|
||||
|
||||
foreach ($ids as $index => $id) {
|
||||
$field = FieldRepo::findOne($id);
|
||||
if ($field) {
|
||||
$field->rank = $index;
|
||||
FieldRepo::update($field);
|
||||
}
|
||||
}
|
||||
return response()->json(["ids" => $ids], 200);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use Illuminate\Http\Request;
|
||||
use Lucent\Account\AccountService;
|
||||
use Lucent\Account\AuthService;
|
||||
use Lucent\Core\Repository\SchemaRepo;
|
||||
use Lucent\Core\Repository\FieldRepo;
|
||||
use Lucent\Core\Schema\Data\Schema;
|
||||
use Lucent\Id\Id;
|
||||
use Lucent\Svelte\Svelte;
|
||||
@@ -23,12 +24,14 @@ class SchemaController extends Controller
|
||||
public function home()
|
||||
{
|
||||
$schemas = SchemaRepo::all();
|
||||
$fields = FieldRepo::all();
|
||||
|
||||
return $this->svelte->render(
|
||||
view: "schemas",
|
||||
title: "Schemas",
|
||||
data: [
|
||||
"schemas" => $schemas,
|
||||
"fields" => $fields,
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -58,4 +61,22 @@ class SchemaController extends Controller
|
||||
201,
|
||||
);
|
||||
}
|
||||
|
||||
public function edit(Request $request)
|
||||
{
|
||||
$id = $request->route("id");
|
||||
$schema = SchemaRepo::findOne($id);
|
||||
|
||||
if (empty($schema)) {
|
||||
return response()->json(["errors" => ["Schema not found"]], 404);
|
||||
}
|
||||
|
||||
return $this->svelte->render(
|
||||
view: "schemaEdit",
|
||||
title: "Edit Schema",
|
||||
data: [
|
||||
"schema" => $schema,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,9 +61,23 @@ Route::group(
|
||||
"build",
|
||||
]);
|
||||
Route::get("/schemas", [SchemaController::class, "home"]);
|
||||
Route::get("/schemas/edit/{id}", [SchemaController::class, "edit"]);
|
||||
Route::post("/schemas", [SchemaController::class, "postCreate"]);
|
||||
Route::get("/fields/create", [FieldController::class, "create"]);
|
||||
Route::get("/fields/edit/{id}", [FieldController::class, "edit"]);
|
||||
Route::post("/fields", [FieldController::class, "postCreate"]);
|
||||
Route::post("/fields/update", [
|
||||
FieldController::class,
|
||||
"postUpdate",
|
||||
]);
|
||||
Route::post("/fields/delete", [
|
||||
FieldController::class,
|
||||
"postDelete",
|
||||
]);
|
||||
Route::post("/fields/reorder", [
|
||||
FieldController::class,
|
||||
"postReorder",
|
||||
]);
|
||||
});
|
||||
|
||||
Route::middleware(["lucent.auth"])->group(function () {
|
||||
|
||||
Reference in New Issue
Block a user