diff --git a/front/js/actions/debounce.svelte.js b/front/js/actions/debounce.svelte.js
new file mode 100644
index 0000000..e695e1b
--- /dev/null
+++ b/front/js/actions/debounce.svelte.js
@@ -0,0 +1,139 @@
+/* eslint-disable no-undefined,no-param-reassign,no-shadow */
+
+/**
+ * Throttle execution of a function. Especially useful for rate limiting
+ * execution of handlers on events like resize and scroll.
+ *
+ * @param {number} delay - A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher)
+ * are most useful.
+ * @param {Function} callback - A function to be executed after delay milliseconds. The `this` context and all arguments are passed through,
+ * as-is, to `callback` when the throttled-function is executed.
+ * @param {object} [options] - An object to configure options.
+ * @param {boolean} [options.noTrailing] - Optional, defaults to false. If noTrailing is true, callback will only execute every `delay` milliseconds
+ * while the throttled-function is being called. If noTrailing is false or unspecified, callback will be executed
+ * one final time after the last throttled-function call. (After the throttled-function has not been called for
+ * `delay` milliseconds, the internal counter is reset).
+ * @param {boolean} [options.noLeading] - Optional, defaults to false. If noLeading is false, the first throttled-function call will execute callback
+ * immediately. If noLeading is true, the first the callback execution will be skipped. It should be noted that
+ * callback will never executed if both noLeading = true and noTrailing = true.
+ * @param {boolean} [options.debounceMode] - If `debounceMode` is true (at begin), schedule `clear` to execute after `delay` ms. If `debounceMode` is
+ * false (at end), schedule `callback` to execute after `delay` ms.
+ *
+ * @returns {Function} A new, throttled, function.
+ */
+export function throttle(delay, callback, options) {
+ const {
+ noTrailing = false,
+ noLeading = false,
+ debounceMode = undefined,
+ } = options || {};
+ /*
+ * After wrapper has stopped being called, this timeout ensures that
+ * `callback` is executed at the proper times in `throttle` and `end`
+ * debounce modes.
+ */
+ let timeoutID;
+ let cancelled = false;
+
+ // Keep track of the last time `callback` was executed.
+ let lastExec = 0;
+
+ // Function to clear existing timeout
+ function clearExistingTimeout() {
+ if (timeoutID) {
+ clearTimeout(timeoutID);
+ }
+ }
+
+ // Function to cancel next exec
+ function cancel(options) {
+ const { upcomingOnly = false } = options || {};
+ clearExistingTimeout();
+ cancelled = !upcomingOnly;
+ }
+
+ /*
+ * The `wrapper` function encapsulates all of the throttling / debouncing
+ * functionality and when executed will limit the rate at which `callback`
+ * is executed.
+ */
+ function wrapper(...arguments_) {
+ let self = this;
+ let elapsed = Date.now() - lastExec;
+
+ if (cancelled) {
+ return;
+ }
+
+ // Execute `callback` and update the `lastExec` timestamp.
+ function exec() {
+ lastExec = Date.now();
+ callback.apply(self, arguments_);
+ }
+
+ /*
+ * If `debounceMode` is true (at begin) this is used to clear the flag
+ * to allow future `callback` executions.
+ */
+ function clear() {
+ timeoutID = undefined;
+ }
+
+ if (!noLeading && debounceMode && !timeoutID) {
+ /*
+ * Since `wrapper` is being called for the first time and
+ * `debounceMode` is true (at begin), execute `callback`
+ * and noLeading != true.
+ */
+ exec();
+ }
+
+ clearExistingTimeout();
+
+ if (debounceMode === undefined && elapsed > delay) {
+ if (noLeading) {
+ /*
+ * In throttle mode with noLeading, if `delay` time has
+ * been exceeded, update `lastExec` and schedule `callback`
+ * to execute after `delay` ms.
+ */
+ lastExec = Date.now();
+ if (!noTrailing) {
+ timeoutID = setTimeout(debounceMode ? clear : exec, delay);
+ }
+ } else {
+ /*
+ * In throttle mode without noLeading, if `delay` time has been exceeded, execute
+ * `callback`.
+ */
+ exec();
+ }
+ } else if (noTrailing !== true) {
+ /*
+ * In trailing throttle mode, since `delay` time has not been
+ * exceeded, schedule `callback` to execute `delay` ms after most
+ * recent execution.
+ *
+ * If `debounceMode` is true (at begin), schedule `clear` to execute
+ * after `delay` ms.
+ *
+ * If `debounceMode` is false (at end), schedule `callback` to
+ * execute after `delay` ms.
+ */
+ timeoutID = setTimeout(
+ debounceMode ? clear : exec,
+ debounceMode === undefined ? delay - elapsed : delay,
+ );
+ }
+ }
+
+ wrapper.cancel = cancel;
+
+ // Return the wrapper function.
+ return wrapper;
+}
+
+export function debounce(delay, callback, options) {
+ const { atBegin = false } = options || {};
+ return throttle(delay, callback, { debounceMode: atBegin !== false });
+}
diff --git a/front/js/entry/FieldEditEntry/FieldEditEntry.svelte b/front/js/entry/FieldEditEntry/FieldEditEntry.svelte
index 74749d6..c53c274 100644
--- a/front/js/entry/FieldEditEntry/FieldEditEntry.svelte
+++ b/front/js/entry/FieldEditEntry/FieldEditEntry.svelte
@@ -82,6 +82,10 @@
Developers will use this to reference the field
+
+ Help text
+
+
Is Translatable
+
+
+
+ Required
+
+
+
+ Readonly
+
+
+
+ Hidden
+
+
{#if data.field.type === "text"}
diff --git a/front/js/entry/FieldEditEntry/TextFieldProps.svelte b/front/js/entry/FieldEditEntry/TextFieldProps.svelte
index f223721..296b856 100644
--- a/front/js/entry/FieldEditEntry/TextFieldProps.svelte
+++ b/front/js/entry/FieldEditEntry/TextFieldProps.svelte
@@ -2,41 +2,12 @@
let { field } = $props();
-
-
-
- Required
-
-
-
- Readonly
-
-
-
- Hidden
-
-
Default
-
- Help text
-
-
+
Min characters
diff --git a/front/js/entry/RecordEditEntry/LocaleChooser.svelte b/front/js/entry/RecordEditEntry/LocaleChooser.svelte
new file mode 100644
index 0000000..fb2a1b8
--- /dev/null
+++ b/front/js/entry/RecordEditEntry/LocaleChooser.svelte
@@ -0,0 +1,32 @@
+
+
+
+ Locales: {selectedLocaleNames.join(", ")}
+
+
diff --git a/front/js/entry/RecordEditEntry/RecordEditEntry.svelte b/front/js/entry/RecordEditEntry/RecordEditEntry.svelte
new file mode 100644
index 0000000..4358872
--- /dev/null
+++ b/front/js/entry/RecordEditEntry/RecordEditEntry.svelte
@@ -0,0 +1,57 @@
+
+
+
+
+{#snippet body()}
+
+ {#each data.fields as field}
+
+ {#if field.type === "text"}
+ f.id === field.fieldId && f.locale === "main",
+ )}
+ schemaField={field}
+ locale="main"
+ dataField={data.draftData.find(
+ (f) => f.id === field.id && f.locale === "main",
+ )}
+ >
+ {#if field.translatable}
+ {#each selectedLocales as locale (locale)}
+
+ f.fieldId === field.id &&
+ f.locale === locale,
+ )}
+ schemaField={field}
+ {locale}
+ dataField={data.draftData.find(
+ (f) => f.id === field.id && f.locale === locale,
+ )}
+ >
+ {/each}
+ {/if}
+ {/if}
+
+ {/each}
+{/snippet}
diff --git a/front/js/entry/RecordEditEntry/fields/TextField.svelte b/front/js/entry/RecordEditEntry/fields/TextField.svelte
new file mode 100644
index 0000000..54e84ea
--- /dev/null
+++ b/front/js/entry/RecordEditEntry/fields/TextField.svelte
@@ -0,0 +1,132 @@
+
+
+
+
+
+ {#if locale !== "main"}
+ {getLocaleName(channel, locale)} >
+ {/if}
+ {schemaField.name}
+
+
+ {#if hasErrors}
+ {validationErrors[0].message}
+ {:else if schemaField.help != ""}
+ {schemaField.help}
+ {/if}
+
+
diff --git a/front/js/entry/RecordEditEntry/locale.svelte.js b/front/js/entry/RecordEditEntry/locale.svelte.js
new file mode 100644
index 0000000..28f08cf
--- /dev/null
+++ b/front/js/entry/RecordEditEntry/locale.svelte.js
@@ -0,0 +1,11 @@
+export function getSelectedLocales() {
+ let value = $state(localStorage.getItem("selectedLocales"));
+ if (value == "" || !value) {
+ return [];
+ }
+ return value.split(",");
+}
+
+export function getLocaleName(channel, id) {
+ return channel.locales.find((locale) => locale.id === id).name;
+}
diff --git a/front/js/main.js b/front/js/main.js
index b33cbca..c4ccf30 100644
--- a/front/js/main.js
+++ b/front/js/main.js
@@ -9,7 +9,7 @@ import Profile from "./svelte/account/Profile.svelte";
import SetupEntry from "./entry/SetupEntry/SetupEntry.svelte";
import Members from "./svelte/members/Members.svelte";
import RecordNotFound from "./svelte/records/NotFound.svelte";
-import RecordEdit from "./svelte/records/Edit.svelte";
+import RecordEditEntry from "./entry/RecordEditEntry/RecordEditEntry.svelte";
import ContentEntry from "./entry/ContentEntry/ContentEntry.svelte";
import HomeEntry from "./entry/HomeEntry/HomeEntry.svelte";
import SchemaEntry from "./entry/SchemaEntry/SchemaEntry.svelte";
@@ -21,7 +21,7 @@ import { createApp } from "./app";
const entryComponents = {
members: Members,
- recordEdit: RecordEdit,
+ recordEdit: RecordEditEntry,
recordNotFound: RecordNotFound,
contentIndex: ContentEntry,
homeIndex: HomeEntry,
diff --git a/src/Core/Data/Record.php b/src/Core/Data/Record.php
index 6161580..0ccff81 100644
--- a/src/Core/Data/Record.php
+++ b/src/Core/Data/Record.php
@@ -11,8 +11,6 @@ class Record
public function __construct(
public string $id,
public string $schemaId,
- public array $draftData,
- public array $liveData,
public Carbon $createdAt,
public string $createdBy,
public ?Carbon $publishedAt,
diff --git a/src/Core/Data/RecordError.php b/src/Core/Data/RecordError.php
new file mode 100644
index 0000000..afaa40b
--- /dev/null
+++ b/src/Core/Data/RecordError.php
@@ -0,0 +1,10 @@
+ $field->recordId,
+ "id" => $field->id,
+ "locale" => $field->locale,
+ "mode" => $field->mode->value,
+ "value" => $field->value,
+ "updated_at" => $field->updatedAt->toJSON(),
+ "updated_by" => $field->updatedBy,
+ ];
+ }
+}
diff --git a/src/Core/Record/RecordModule.php b/src/Core/Record/RecordModule.php
index 8f5a13b..a32b55a 100644
--- a/src/Core/Record/RecordModule.php
+++ b/src/Core/Record/RecordModule.php
@@ -7,17 +7,25 @@ use stdClass;
class RecordModule
{
+ public static function updateField(
+ Record $record,
+ RecordField $field,
+ ): Record {
+ $recordFields = collect($record->draftData)->filter(
+ fn(RecordField $rf) => !(
+ $rf->id == $field->id && $rf->locale == $field->locale
+ ),
+ );
+ $recordFields->push($field);
+ $record->draftData = $recordFields->values()->toArray();
+ return $record;
+ }
+
public static function toDb(Record $record): array
{
return [
"id" => $record->id,
"schema_id" => $record->schemaId,
- "draft_data" => json_encode(
- array_map(static::recordFieldToDb(...), $record->draftData),
- ),
- "live_data" => json_encode(
- array_map(static::recordFieldToDb(...), $record->liveData),
- ),
"created_at" => $record->createdAt->toJSON(),
"created_by" => $record->createdBy,
"published_at" => empty($record->publishedAt)
@@ -33,14 +41,9 @@ class RecordModule
public static function fromDb(stdClass $data): Record
{
- $draftDataArr = json_decode(data_get($data, "draft_data"), true);
- $liveDataArr = json_decode(data_get($data, "live_data"), true);
-
return new Record(
id: data_get($data, "id"),
schemaId: data_get($data, "schema_id"),
- draftData: array_map(static::recordFieldFromDb(...), $draftDataArr),
- liveData: array_map(static::recordFieldFromDb(...), $liveDataArr),
createdAt: Carbon::parse(data_get($data, "created_at")),
createdBy: data_get($data, "created_by"),
publishedAt: empty(data_get($data, "published_at"))
@@ -53,32 +56,4 @@ class RecordModule
trashedBy: data_get($data, "trashed_by"),
);
}
-
- public static function recordFieldFromDb(array $field): RecordField
- {
- return new RecordField(
- id: data_get($field, "id"),
- locale: data_get($field, "locale", []),
- value: data_get($field, "value"),
- createdAt: Carbon::parse(data_get($field, "created_at")),
- createdBy: data_get($field, "created_by"),
- updatedAt: Carbon::parse(data_get($field, "updated_at")),
- updatedBy: data_get($field, "updated_by"),
- version: data_get($field, "version"),
- );
- }
-
- public static function recordFieldToDb(RecordField $field): array
- {
- return [
- "id" => $field->id,
- "locale" => $field->locale,
- "value" => $field->value,
- "created_at" => $field->createdAt->toJSON(),
- "created_by" => $field->createdBy,
- "updated_at" => $field->updatedAt->toJSON(),
- "updated_by" => $field->updatedBy,
- "version" => $field->version,
- ];
- }
}
diff --git a/src/Core/Record/RecordValidationModule.php b/src/Core/Record/RecordValidationModule.php
new file mode 100644
index 0000000..a5376ac
--- /dev/null
+++ b/src/Core/Record/RecordValidationModule.php
@@ -0,0 +1,96 @@
+translatable) {
+ foreach ($locales as $locale) {
+ $res = static::validateField(
+ $locale["id"],
+ $schemaField,
+ $recordFields,
+ );
+ $errors = array_merge($errors, $res);
+ }
+ }
+ }
+
+ return collect($errors)
+ ->filter(fn($err) => $err !== null)
+ ->values()
+ ->toArray();
+ }
+
+ /**
+ *
+ * @param Field $schemaField
+ * @param RecordField[] $recordFields
+ * @return array
+ */
+ public static function validateField(
+ string $locale,
+ Field $schemaField,
+ array $recordFields,
+ ): array {
+ // General Validations
+ $error = static::validateRequired($locale, $schemaField, $recordFields);
+ // Type specific
+
+ return collect([$error])
+ ->filter(fn($err) => $err !== null)
+ ->values()
+ ->toArray();
+ }
+
+ /**
+ *
+ * @param Field $schemaField
+ * @param RecordField[] $recordFields
+ * @return ?RecordError
+ */
+ public static function validateRequired(
+ string $locale,
+ Field $schemaField,
+ array $recordFields,
+ ): ?RecordError {
+ if ($schemaField->required === false) {
+ return null;
+ }
+ $recordField = collect($recordFields)->first(
+ fn(RecordField $field) => $field->id === $schemaField->id &&
+ $field->locale === $locale &&
+ $field->mode === RecordMode::DRAFT,
+ );
+
+ if (empty($recordField) || empty($recordField->value)) {
+ return new RecordError(
+ $schemaField->id,
+ $locale,
+ "This field is required",
+ );
+ }
+
+ return null;
+ }
+}
diff --git a/src/Core/Repository/FieldRepo.php b/src/Core/Repository/FieldRepo.php
index 6351de0..3839a5e 100644
--- a/src/Core/Repository/FieldRepo.php
+++ b/src/Core/Repository/FieldRepo.php
@@ -20,6 +20,19 @@ class FieldRepo
->toArray();
}
+ /**
+ * @return Field[]
+ */
+ public static function findBySchemaId(string $schemaId): array
+ {
+ return DB::table(self::TABLE_NAME)
+ ->where("schema_id", $schemaId)
+ ->orderBy("rank")
+ ->get()
+ ->map(FieldModule::fromDb(...))
+ ->toArray();
+ }
+
public static function findOne(string $id): ?Field
{
return DB::table(self::TABLE_NAME)
diff --git a/src/Core/Repository/RecordFieldRepo.php b/src/Core/Repository/RecordFieldRepo.php
new file mode 100644
index 0000000..c9deddf
--- /dev/null
+++ b/src/Core/Repository/RecordFieldRepo.php
@@ -0,0 +1,48 @@
+insert(
+ RecordFieldModule::toDb($recordField),
+ );
+ }
+
+ public static function delete(RecordField $field): void
+ {
+ DB::table(self::TABLE_NAME)
+ ->where("id", $field->id)
+ ->where("locale", $field->locale)
+ ->where("mode", $field->mode->value)
+ ->where("record_id", $field->recordId)
+ ->delete();
+ }
+
+ public static function findOne(string $id): ?RecordField
+ {
+ return DB::table(self::TABLE_NAME)
+ ->where("id", $id)
+ ->get()
+ ->map(RecordFieldModule::fromDb(...))
+ ->first();
+ }
+
+ /**
+ * @return RecordField[]
+ */
+ public static function findByRecordId(string $schemaId): array
+ {
+ return DB::table(self::TABLE_NAME)
+ ->where("record_id", $schemaId)
+ ->get()
+ ->map(RecordFieldModule::fromDb(...))
+ ->toArray();
+ }
+}
diff --git a/src/Core/Repository/RecordRepo.php b/src/Core/Repository/RecordRepo.php
index 26a910d..1f97fbe 100644
--- a/src/Core/Repository/RecordRepo.php
+++ b/src/Core/Repository/RecordRepo.php
@@ -12,4 +12,13 @@ class RecordRepo
{
DB::table(self::TABLE_NAME)->insert(RecordModule::toDb($record));
}
+
+ public static function findOne(string $id): ?Record
+ {
+ return DB::table(self::TABLE_NAME)
+ ->where("id", $id)
+ ->get()
+ ->map(RecordModule::fromDb(...))
+ ->first();
+ }
}
diff --git a/src/Core/Schema/Data/Field.php b/src/Core/Schema/Data/Field.php
index 2dc9411..6497a27 100644
--- a/src/Core/Schema/Data/Field.php
+++ b/src/Core/Schema/Data/Field.php
@@ -10,7 +10,11 @@ class Field
public string $name,
public string $type,
public string $schemaId,
+ public string $help,
public bool $translatable,
+ public bool $required,
+ public bool $readonly,
+ public bool $hidden,
public int $rank,
public IFieldProp $props,
) {}
diff --git a/src/Core/Schema/Data/FieldProp/FieldProp.php b/src/Core/Schema/Data/FieldProp/FieldProp.php
index c4554c8..9a352b0 100644
--- a/src/Core/Schema/Data/FieldProp/FieldProp.php
+++ b/src/Core/Schema/Data/FieldProp/FieldProp.php
@@ -5,15 +5,7 @@ class FieldProp
public static function fromType(string $type): IFieldProp
{
return match ($type) {
- "text" => new TextFieldProp(
- required: false,
- readonly: false,
- hidden: false,
- min: 0,
- max: 0,
- help: "",
- default: "",
- ),
+ "text" => new TextFieldProp(min: 0, max: 0, default: ""),
default => new InvalidFieldProp(),
};
}
@@ -28,12 +20,8 @@ class FieldProp
{
return match ($type) {
"text" => new TextFieldProp(
- required: $props["required"] ?? false,
- readonly: $props["readonly"] ?? false,
- hidden: $props["hidden"] ?? false,
min: $props["min"] ?? 0,
max: $props["max"] ?? 0,
- help: $props["help"] ?? "",
default: $props["default"] ?? "",
),
default => new InvalidFieldProp(),
diff --git a/src/Core/Schema/Data/FieldProp/TextFieldProp.php b/src/Core/Schema/Data/FieldProp/TextFieldProp.php
index 28cdb3b..1d6a065 100644
--- a/src/Core/Schema/Data/FieldProp/TextFieldProp.php
+++ b/src/Core/Schema/Data/FieldProp/TextFieldProp.php
@@ -3,12 +3,8 @@
class TextFieldProp implements IFieldProp
{
public function __construct(
- public bool $required,
- public bool $readonly,
- public bool $hidden,
public string $default,
public int $min,
public int $max,
- public string $help,
) {}
}
diff --git a/src/Core/Schema/FieldModule.php b/src/Core/Schema/FieldModule.php
index 88439cc..d463211 100644
--- a/src/Core/Schema/FieldModule.php
+++ b/src/Core/Schema/FieldModule.php
@@ -23,7 +23,11 @@ class FieldModule
"alias" => $field->alias,
"name" => $field->name,
"type" => $field->type,
+ "help" => $field->help,
"rank" => $field->rank,
+ "required" => $field->required,
+ "readonly" => $field->readonly,
+ "hidden" => $field->hidden,
"translatable" => $field->translatable,
"props" => json_encode($field->props),
"schema_id" => $field->schemaId,
@@ -37,8 +41,12 @@ class FieldModule
alias: data_get($data, "alias"),
name: data_get($data, "name"),
type: data_get($data, "type"),
+ help: data_get($data, "help"),
schemaId: data_get($data, "schema_id"),
rank: data_get($data, "rank"),
+ required: data_get($data, "required"),
+ readonly: data_get($data, "readonly"),
+ hidden: data_get($data, "hidden"),
translatable: data_get($data, "translatable"),
props: FieldProp::fromDb(
data_get($data, "type"),
diff --git a/src/Http/Controller/FieldController.php b/src/Http/Controller/FieldController.php
index 1042fa8..5351b7f 100644
--- a/src/Http/Controller/FieldController.php
+++ b/src/Http/Controller/FieldController.php
@@ -47,6 +47,10 @@ class FieldController
$schemaId = $request->input("schemaId");
$name = $request->input("name");
$alias = $request->input("alias");
+ $help = $request->input("help") ?? "";
+ $required = $request->input("required", false);
+ $readonly = $request->input("readonly", false);
+ $hidden = $request->input("hidden", false);
$fieldType = $request->input("fieldType");
$fields = FieldRepo::all();
$newRank = collect($fields)->last()->rank + 1;
@@ -75,8 +79,12 @@ class FieldController
schemaId: $schemaId,
name: $name,
alias: $alias,
+ help: $help,
type: $fieldType,
props: $fieldProps,
+ required: $required,
+ readonly: $readonly,
+ hidden: $hidden,
rank: $newRank,
translatable: false,
);
@@ -137,7 +145,11 @@ class FieldController
schemaId: $field->schemaId,
name: data_get($fieldData, "name"),
alias: data_get($fieldData, "alias"),
+ help: data_get($fieldData, "help") ?? "",
translatable: data_get($fieldData, "translatable"),
+ required: data_get($fieldData, "required"),
+ readonly: data_get($fieldData, "readonly"),
+ hidden: data_get($fieldData, "hidden"),
type: $field->type,
rank: $field->rank,
props: $fieldProps,
diff --git a/src/Http/Controller/RecordController.php b/src/Http/Controller/RecordController.php
index a277f28..5a5dc90 100644
--- a/src/Http/Controller/RecordController.php
+++ b/src/Http/Controller/RecordController.php
@@ -8,7 +8,9 @@ use Lucent\Account\AccountService;
use Lucent\Channel\ChannelService;
use Lucent\Core\Auth\AuthModule;
use Lucent\Core\Data\RecordField;
+use Lucent\Core\Repository\FieldRepo;
use Lucent\Core\Repository\SchemaRepo;
+use Lucent\Core\Repository\RecordFieldRepo;
use Lucent\Id\Id;
use Lucent\LucentException;
use Lucent\Query\Operator\OperatorRegistry;
@@ -17,6 +19,8 @@ use Lucent\Record\InputData\EdgeInputData;
use Lucent\Record\InputData\RecordInputData;
use Lucent\Record\Manager;
use Lucent\Core\Repository\RecordRepo;
+use Lucent\Core\Channel\ChannelModule;
+use Lucent\Core\Record\RecordValidationModule;
use Lucent\Record\RecordService;
use Lucent\Record\Status;
use Lucent\Schema\Ui\Reference;
@@ -24,6 +28,8 @@ use Lucent\Schema\Validator\ValidatorException;
use Lucent\Svelte\Svelte;
use Lucent\ViewModel\ViewModel;
use Lucent\Core\Data\Record;
+use Lucent\Core\Data\RecordMode;
+use Illuminate\Support\Facades\DB;
use function Lucent\Response\fail;
use function Lucent\Response\ok;
@@ -219,60 +225,57 @@ class RecordController
public function edit(Request $request)
{
- $rid = $request->route("rid");
+ $recordId = $request->route("id");
- $graph = $this->query
- ->filter(["id" => $rid])
- ->limit(1)
- ->skip(0)
- ->childrenDepth(2)
- ->childrenLimit(200)
- ->parentsDepth(1)
- ->parentsLimit(200)
- ->run();
+ // $graph = $this->query
+ // ->filter(["id" => $rid])
+ // ->limit(1)
+ // ->skip(0)
+ // ->childrenDepth(2)
+ // ->childrenLimit(200)
+ // ->parentsDepth(1)
+ // ->parentsLimit(200)
+ // ->run();
- if ($graph->records->isEmpty()) {
- return $this->svelte->render(
- layout: "channel",
- view: "recordNotFound",
- title: "Record Not Found",
- );
- }
+ // if ($graph->records->isEmpty()) {
+ // return $this->svelte->render(
+ // layout: "channel",
+ // view: "recordNotFound",
+ // title: "Record Not Found",
+ // );
+ // }
- $record = $graph->records->first();
+ // $record = $graph->records->first();
+ //
+ $record = RecordRepo::findOne($recordId);
+ $recordFields = RecordFieldRepo::findByRecordId($recordId);
+ $draftData = collect($recordFields)
+ ->where("mode", RecordMode::DRAFT)
+ ->values()
+ ->toArray();
- if (
- !in_array(
- $record->schema,
- $this->accountService->currentReadableSchemas(),
- )
- ) {
- return $this->svelte->render(
- layout: "channel",
- view: "recordNotFound",
- title: "Schema Not Found",
- );
- }
+ $schemas = SchemaRepo::all();
+ $schema = collect($schemas)->firstWhere("id", $record->schemaId);
+ $fields = FieldRepo::findBySchemaId($record->schemaId);
+ $locales = ChannelModule::get()->locales;
- $schema = $this->channelService->getSchema($record->schema)->get();
- $recordHistory = $this->recordManager
- ->fromSession($request->session())
- ->push($rid)
- ->getRecords($rid);
- return $this->svelte->render(
- layout: "channel",
+ $validationErrors = RecordValidationModule::validate(
+ $locales,
+ $fields,
+ $draftData,
+ );
+
+ return Svelte::view(
view: "recordEdit",
title: "Edit Record",
data: [
+ "schemas" => $schemas,
"schema" => $schema,
- "graph" => toArray($graph),
+ "fields" => $fields,
+ // "graph" => toArray($graph),
"record" => toArray($record),
- "users" => $this->accountService->all(),
- "recordHistory" => $recordHistory,
- "isWritable" => in_array(
- $record->schema,
- $this->accountService->currentWritableSchemas(),
- ),
+ "draftData" => $draftData,
+ "validationErrors" => $validationErrors,
],
);
}
@@ -310,26 +313,14 @@ class RecordController
public function postCreate(Request $request)
{
$schemaId = $request->input("schemaId");
+ $fields = FieldRepo::findBySchemaId($schemaId);
$title = $request->input("title");
$now = Carbon::now();
$userId = AuthModule::getCurrentUserId();
- $titleField = new RecordField(
- id: "_title",
- locale: "main",
- value: $title,
- createdAt: $now,
- createdBy: $userId,
- updatedAt: $now,
- updatedBy: $userId,
- version: 1,
- );
-
$record = new Record(
id: Id::new(),
schemaId: $schemaId,
- draftData: [$titleField],
- liveData: [],
createdAt: $now,
createdBy: $userId,
publishedAt: null,
@@ -339,9 +330,59 @@ class RecordController
);
RecordRepo::insert($record);
+
+ $titleField = collect($fields)->where("alias", "_title")->first();
+
+ $titleFieldData = new RecordField(
+ recordId: $record->id,
+ id: $titleField->id,
+ locale: "main",
+ mode: RecordMode::DRAFT,
+ value: $title,
+ updatedAt: $now,
+ updatedBy: $userId,
+ );
+
+ RecordFieldRepo::insert($titleFieldData);
+
return ok(toArray($record));
}
+ public function saveField(Request $request)
+ {
+ $recordId = $request->input("recordId");
+ $id = $request->input("id");
+ $locale = $request->input("locale");
+ $value = $request->input("value") ?? "";
+ $now = Carbon::now();
+ $field = FieldRepo::findOne($id);
+ $userId = AuthModule::getCurrentUserId();
+ $recordField = new RecordField(
+ recordId: $recordId,
+ id: $field->id,
+ locale: $locale,
+ mode: RecordMode::DRAFT,
+ value: $value,
+ updatedAt: $now,
+ updatedBy: $userId,
+ );
+ DB::transaction(function () use ($recordField) {
+ RecordFieldRepo::delete($recordField);
+ RecordFieldRepo::insert($recordField);
+ });
+
+ $validationErrors = RecordValidationModule::validateField(
+ $locale,
+ $field,
+ [$recordField],
+ );
+
+ return ok([
+ "recordField" => toArray($recordField),
+ "validationErrors" => $validationErrors,
+ ]);
+ }
+
public function save(Request $request)
{
$recordId = $request->input("record.id");
diff --git a/src/Http/Controller/SchemaController.php b/src/Http/Controller/SchemaController.php
index 99c19d7..61a11bd 100644
--- a/src/Http/Controller/SchemaController.php
+++ b/src/Http/Controller/SchemaController.php
@@ -54,15 +54,11 @@ class SchemaController
name: "Title",
alias: "_title",
type: "text",
- props: new TextFieldProp(
- required: true,
- readonly: false,
- hidden: false,
- default: "",
- min: 1,
- max: 255,
- help: "",
- ),
+ help: "",
+ required: true,
+ readonly: false,
+ hidden: false,
+ props: new TextFieldProp(default: "", min: 1, max: 255),
rank: 0,
translatable: false,
);
diff --git a/src/Http/web.php b/src/Http/web.php
index 779eb36..4ce87de 100644
--- a/src/Http/web.php
+++ b/src/Http/web.php
@@ -72,6 +72,10 @@ Route::group(
// RECORD
Route::get("records/{id}", [RecordController::class, "edit"]);
Route::post("records", [RecordController::class, "postCreate"]);
+ Route::post("records/fields", [
+ RecordController::class,
+ "saveField",
+ ]);
});
//OLD