This commit is contained in:
2023-10-04 13:32:30 +03:00
parent 215d238505
commit 1ca5f4e521
82 changed files with 519 additions and 1889 deletions
-73
View File
@@ -1,73 +0,0 @@
<?php
namespace Lucent\Field;
use Lucent\LucentException;
class Field
{
/**
* @param string[] $collections
*/
function __construct(
public FieldName $name,
public string $label,
public string $ui,
public string $help = "",
public bool $trashed = false,
public bool $locked = false,
public string $regex = "",
public string $mime = "",
public bool $readonly = false,
public bool $nullable = false,
public bool $required = false,
public int $decimals = 0,
public array $collections = [],
public mixed $min = null,
public mixed $max = null,
public mixed $default = null,
public string $optionsFrom = "",
public string $optionsField = "",
public bool $optionsSuggest = true,
public bool $unique = false, // not used
public string $layout = "",
) {
}
public function toArray(): array
{
return \json_decode(\json_encode($this), true);
}
/**
* @throws LucentException
*/
public static function fromArray(array $data): Field
{
return new Field(
name: new FieldName($data["name"]),
label:$data["label"],
ui: $data["ui"],
help:$data["help"] ?? "",
trashed:$data["trashed"] ?? false,
locked:$data["locked"] ?? false,
regex:$data["regex"] ?? "" ,
mime:$data["mime"] ?? "" ,
readonly:$data["readonly"] ?? false,
nullable:$data["nullable"] ?? false,
required:$data["required"] ??false,
decimals:$data["decimals"] ?? 0,
collections:$data["collections"] ?? [],
min:$data["min"] ?? null,
max:$data["max"] ?? null,
default:$data["default"] ?? null,
optionsFrom:$data["optionsFrom"] ?? "" ,
optionsField:$data["optionsField"] ?? "" ,
optionsSuggest:$data["optionsSuggest"] ?? true,
unique:$data["unique"] ?? false,
layout:$data["layout"] ?? "" ,
);
}
}
-43
View File
@@ -1,43 +0,0 @@
<?php
namespace Lucent\Field;
use Illuminate\Support\Collection;
/**
* @extends \Illuminate\Support\Collection<int|string, Field>
*/
final class FieldCollection extends Collection
{
public function __construct(
Field ...$array
) {
parent::__construct($array);
}
/**
* @return Schema[]
**/
public function toArray(): array
{
return collect($this)->values()->toArray();
}
public function findByName(string $name): ?Field
{
return $this->firstWhere("name", $name);
}
public static function fromArray(array $data): FieldCollection
{
$items = array_map([Field::class, 'fromArray'], $data);
return new FieldCollection(...$items);
}
public static function fromDB(string $data): FieldCollection
{
return FieldCollection::fromArray(\json_decode($data,true));
}
}
-167
View File
@@ -1,167 +0,0 @@
<?php
namespace Lucent\Field;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Lucent\Channel\ChannelRepo;
use Lucent\LucentException;
use Lucent\Schema\Type as SchemaType;
use function Lucent\Response\fail;
use function Lucent\Response\ok;
use function Lucent\Svelte\svelte;
class FieldController extends Controller
{
public function __construct(
private readonly FieldService $fieldService
)
{
}
public function new(Request $request)
{
$channel = ChannelRepo::current();
$schemaName = $request->route("schemaName");
$collections = collect($channel->schemas)->where("type", SchemaType::COLLECTION)->values()->toArray();
$filesSchemas = collect($channel->schemas)->where("type", SchemaType::FILES)->values()->toArray();
$newField = new Field(
name: new FieldName(""),
label: "",
ui: "text"
);
return svelte(
layout: "channel",
view: "fieldNew",
title: "New field",
data: [
"schemas" => $channel->schemas,
"schema" => $channel->schemas->where("name", $schemaName)->first(),
"collections" => $collections,
"filesSchemas" => $filesSchemas,
"field" => $newField->toArray(),
"uiList" => UI::values(),
]
);
}
public function create(Request $request)
{
try {
$this->fieldService->create(
schemaName: $request->route("schemaName"),
data: $request->input("data")
);
} catch (LucentException $th) {
return fail($th);
}
return ok();
}
public function edit(Request $request)
{
$channel = ChannelRepo::current();
$schemaName = $request->route("schemaName");
$schema = $channel->schemas->where("name.value", $schemaName)->first();
$collections = collect($channel->schemas)->where("type", SchemaType::COLLECTION)->values()->toArray();
$filesSchemas = collect($channel->schemas)->where("type", SchemaType::FILES)->values()->toArray();
$field = collect($schema->fields)->where("name.value", $request->route("fid"))->first();
return svelte(
layout: "channel",
view: "fieldEdit",
title: "Edit field",
data: [
"schemas" => $channel->schemas,
"schema" => $schema,
"collections" => $collections,
"filesSchemas" => $filesSchemas,
"field" => $field,
"uiList" => UI::values(),
]
);
}
public function replace(Request $request)
{
$schema = $this->fieldService->replace(
schemaName: $request->route("schemaName"),
// fields:$request->input("fields")
fields: json_decode($request->input("fields"), true)
);
return ok($schema->toArray());
}
public function update(Request $request)
{
try {
$this->fieldService->update(
schemaName: $request->route("schemaName"),
data: $request->input("data")
);
} catch (LucentException $th) {
return fail($th);
}
return ok();
}
public function move(Request $request)
{
$newSchema = $this->fieldService->move(
schemaName: $request->route("schemaName"),
source: $request->input("source"),
target: $request->input("target") ?? "",
);
return ok($newSchema->toArray());
}
public function trash(Request $request)
{
try {
$newSchema = $this->fieldService->trash(
schemaName: $request->route("schemaName"),
fieldName: $request->input("field"),
);
} catch (LucentException $th) {
return fail($th);
}
return ok($newSchema->toArray());
}
public function restore(Request $request)
{
try {
$newSchema = $this->fieldService->restore(
schemaName: $request->route("schemaName"),
fieldName: $request->input("field"),
);
} catch (LucentException $th) {
return fail($th);
}
return ok($newSchema->toArray());
}
public function delete(Request $request)
{
try {
$newSchema = $this->fieldService->delete(
schemaName: $request->route("schemaName"),
fieldName: $request->input("field"),
);
} catch (LucentException $th) {
return fail($th);
}
return ok($newSchema->toArray());
}
}
-42
View File
@@ -1,42 +0,0 @@
<?php
namespace Lucent\Field;
use JsonSerializable;
use Lucent\LucentException;
use Lucent\Validator\Validator;
class FieldName implements JsonSerializable
{
public string $value;
/**
* @throws LucentException
*/
function __construct(string $value)
{
Validator::single("Name", $value, "min:2|max:50|alpha_dash");
$this->value = $value;
}
public function value(): string
{
return $this->value;
}
public function equals(FieldName $name): bool
{
return $this->value === $name->value;
}
public function __toString(): string
{
return $this->value;
}
public function jsonSerialize(): string
{
return $this->value;
}
}
-222
View File
@@ -1,222 +0,0 @@
<?php
namespace Lucent\Field;
use Lucent\Channel\ChannelRepo;
use Lucent\LucentException;
use Lucent\Primitive\Collection;
use Lucent\Schema\Schema;
use Lucent\Schema\SchemaRepo;
readonly class FieldService
{
public function __construct(
private SchemaRepo $schemaRepo
)
{
}
/**
* @throws LucentException
*/
public function create(
string $schemaName,
array $data
): Schema
{
if (empty($data["name"]) || empty($data["label"])) {
throw new LucentException("Name and Label are required");
}
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name.value", $schemaName)->first();
$field = Field::fromArray($data);
$this->validateNameUnique($schema, $field->name);
$schema->fields->push($field);
$this->schemaRepo->update($schema);
return $schema;
}
/**
* @throws LucentException
*/
public function update(
string $schemaName,
array $data
): Schema
{
if (empty($data["name"]) || empty($data["label"])) {
throw new LucentException("Name and Label are required");
}
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name", $schemaName)->first();
$field = Field::fromArray($data);
if ($field->locked) {
throw new LucentException("Locked fields can't get updated");
}
$schema->fields = $schema->fields->map(function (Field $aField) use ($field) {
if (!$aField->name->equals($field->name)) {
return $aField;
}
return $field;
});
$this->schemaRepo->update($schema);
return $schema;
}
public function move(
string $schemaName,
string $source,
string $target,
): Schema
{
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name", $schemaName)->first();
if ($source === $target) {
return $schema;
}
$sourceField = $schema->fields->where("name", $source)->first();
$fieldsWithoutSource = $schema->fields
->where("name", "!=", $source)
->values()
->toArray();
if (empty($target)) {
$schema->fields = new Collection([$sourceField, ...$fieldsWithoutSource]);
} else {
$newFields = collect($fieldsWithoutSource)->reduce(function ($carry, $afield) use ($sourceField, $target) {
$carry[] = $afield;
if ($afield->name->value === $target) {
$carry[] = $sourceField;
}
return $carry;
}, []);
$schema->fields = new Collection($newFields);
}
$this->schemaRepo->update($schema);
return $schema;
}
/**
* @throws LucentException
*/
public function trash(
string $schemaName,
string $fieldName,
): Schema
{
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name.value", $schemaName)->first();
$field = $schema->fields->where("name", $fieldName)->first();
if ($field->trashed) {
throw new LucentException("Field is already in trash");
}
if ($field->locked) {
throw new LucentException("Locked fields can't get trashed");
}
$schema->fields = $schema->fields->map(function (Field $aField) use ($fieldName) {
if ($aField->name->value !== $fieldName) {
return $aField;
}
$aField->trashed = true;
return $aField;
});
$this->schemaRepo->update($schema);
return $schema;
}
/**
* @throws LucentException
*/
public function restore(
string $schemaName,
string $fieldName,
): Schema
{
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name.value", $schemaName)->first();
$field = $schema->fields->where("name", $fieldName)->first();
if (!$field->trashed) {
throw new LucentException("You can only restore trashed fields");
}
$schema->fields = $schema->fields->map(function (Field $aField) use ($fieldName) {
if ($aField->name->value !== $fieldName) {
return $aField;
}
$aField->trashed = false;
return $aField;
});
$this->schemaRepo->update($schema);
return $schema;
}
/**
* @throws LucentException
*/
public function delete(
string $schemaName,
string $fieldName,
): Schema
{
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name.value", $schemaName)->first();
$field = $schema->fields->where("name", $fieldName)->first();
if (!$field->trashed) {
throw new LucentException("You can only delete trashed fields");
}
$schema->fields = $schema->fields
->filter(fn(Field $aField) => $aField->name->value !== $fieldName)
->values();
$this->schemaRepo->update($schema);
return $schema;
}
public function replace(
string $schemaName,
array $fields
): Schema
{
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name", $schemaName)->first();
$schema->fields = new Collection();
$newFields = collect($fields)->map(function ($fieldData) use ($schema) {
$field = Field::fromArray($fieldData);
$this->validateNameUnique($schema, $field->name);
return $field;
})->toArray();
$schema->fields = new Collection($newFields);
$this->schemaRepo->update($schema);
return $schema;
}
/**
* @throws LucentException
*/
private function validateNameUnique(Schema $schema, string $name): void
{
$fieldExists = collect($schema->fields)->where("name.value", $name)->first();
if (!empty($fieldExists)) {
throw new LucentException("Field with name $name exists in schema $schema->label");
}
}
}
-99
View File
@@ -1,99 +0,0 @@
<?php
namespace Lucent\Field;
readonly class System
{
function __construct(
public string $name,
public string $label,
public string $ui,
public bool $files,
)
{
}
public static function getById(string $id): System
{
return self::list()[$id];
}
/**
* @return array<string,System>
* */
public static function list(): array
{
return [
"_sys.status" => new System(
name: "_sys.status",
label: "Status",
ui: "status",
files: false,
),
"_sys.createdBy" => new System(
name: "_sys.createdBy",
label: "Created by",
ui: "user",
files: false,
),
"_sys.updatedBy" => new System(
name: "_sys.updatedBy",
label: "Updated by",
ui: "user",
files: false,
),
"_sys.createdAt" => new System(
name: "_sys.createdAt",
label: "Created at",
ui: "datetime",
files: false,
),
"_sys.updatedAt" => new System(
name: "_sys.updatedAt",
label: "Updated At",
ui: "datetime",
files: false,
),
"_file.path" => new System(
name: "_file.path",
label: "Filename",
ui: "text",
files: true,
),
"_file.mime" => new System(
name: "_file.mime",
label: "Mime Type",
ui: "text",
files: true,
),
"_file.width" => new System(
name: "_file.width",
label: "Dimensions",
ui: "text",
files: true,
),
"_file.size" => new System(
name: "_file.size",
label: "Size",
ui: "text",
files: true,
),
"_file.originalName" => new System(
name: "_file.originalName",
label: "Original Name",
ui: "text",
files: true,
),
"_file.checksum" => new System(
name: "_file.checksum",
label: "Checksum",
ui: "text",
files: true,
),
];
}
}
-298
View File
@@ -1,298 +0,0 @@
<?php
namespace Lucent\Field;
use Lucent\Schema\FieldType;
readonly class UI
{
function __construct(
public string $name,
public string $label,
public FieldType $type,
public bool $regex,
public bool $mime,
public bool $required,
public bool $nullable,
public bool $decimals,
public bool $collections,
public bool $min,
public bool $max,
public bool $default,
public bool $locked,
public bool $readonly,
public bool $options,
public bool $layout,
)
{
}
public static function getById(string $id): array
{
return self::buildUis()[$id];
}
public static function values(): array
{
return array_values(self::buildUis());
}
public static function buildUis(): array
{
return [
"uuid" => new UI(
name: "uuid",
label: "UUID",
type: FieldType::STRING,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: false,
max: false,
default: false,
locked: true,
readonly: true,
options: false,
layout: false,
),
"text" => new UI(
name: "text",
label: "Text",
type: FieldType::STRING,
regex: true,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: true,
layout: false,
),
"textarea" => new UI(
name: "textarea",
label: "Textarea",
type: FieldType::STRING,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: false,
layout: false,
),
"color" => new UI(
name: "color",
label: "Color",
type: FieldType::STRING,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: false,
max: false,
default: true,
locked: true,
readonly: true,
options: true,
layout: false,
),
"rich" => new UI(
name: "rich",
label: "Rich editor",
type: FieldType::STRING,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: false,
layout: false,
),
"block" => new UI(
name: "block",
label: "Block editor",
type: FieldType::JSON,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: true,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: false,
layout: false,
),
"file" => new UI(
name: "file",
label: "File",
type: FieldType::FILE,
regex: false,
mime: true,
required: false,
nullable: false,
decimals: false,
collections: true,
min: true,
max: true,
default: false,
locked: false,
readonly: false, // feature for later
options: false,
layout: false,
),
"reference" => new UI(
name: "reference",
label: "Reference",
type: FieldType::REFERENCE,
regex: false,
mime: false,
required: false,
nullable: false,
decimals: false,
collections: true,
min: true,
max: true,
default: false,
locked: false,
readonly: false, // feature for later
options: false,
layout: true,
),
"checkbox" => new UI(
name: "checkbox",
label: "Checkbox",
type: FieldType::BOOLEAN,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: false,
max: false,
default: true,
locked: true,
readonly: true,
options: false,
layout: false,
),
"number" => new UI(
name: "number",
label: "Number",
type: FieldType::NUMBER,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: true,
collections: false,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: true,
layout: false,
),
"date" => new UI(
name: "date",
label: "Date",
type: FieldType::STRING,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: true,
layout: false,
),
"datetime" => new UI(
name: "datetime",
label: "Datetime",
type: FieldType::STRING,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: true,
layout: false,
),
"json" => new UI(
name: "json",
label: "JSON",
type: FieldType::JSON,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: false,
max: false,
default: true,
locked: true,
readonly: true,
options: false,
layout: false,
),
"tab" => new UI(
name: "tab",
label: "Tab",
type: FieldType::TAB,
regex: false,
mime: false,
required: false,
nullable: false,
decimals: false,
collections: false,
min: false,
max: false,
default: false,
locked: true,
readonly: false,
options: false,
layout: false,
),
];
}
}