This commit is contained in:
2023-10-07 21:18:18 +03:00
parent f2faec67fe
commit 956b8dc4e3
12 changed files with 378 additions and 113 deletions
+9
View File
@@ -17,6 +17,7 @@
import Json from "./elements/JSON.svelte"; import Json from "./elements/JSON.svelte";
import FieldHeader from "./elements/FieldHeader.svelte"; import FieldHeader from "./elements/FieldHeader.svelte";
import ReferenceTable from "./elements/ReferenceTable.svelte"; import ReferenceTable from "./elements/ReferenceTable.svelte";
import ReferenceTags from "./elements/ReferenceTags.svelte";
const formElements = { const formElements = {
text: Text, text: Text,
@@ -61,6 +62,14 @@
{field} {field}
{validationErrors} {validationErrors}
/> />
{:else if field.info.name === "reference" && field.layout === "tags"}
<ReferenceTags
bind:graph
{id}
{record}
{field}
{validationErrors}
/>
{:else if field.info.name === "reference"} {:else if field.info.name === "reference"}
<Reference <Reference
bind:graph bind:graph
@@ -1,12 +1,15 @@
<script> <script>
import { createEventDispatcher } from "svelte"; import {createEventDispatcher, getContext} from "svelte";
import Icon from "../../common/Icon.svelte"; import Icon from "../../common/Icon.svelte";
import InlineEdit from "../InlineEdit.svelte"; import InlineEdit from "../InlineEdit.svelte";
import BrowseModal from "./BrowseModal.svelte"; import BrowseModal from "./BrowseModal.svelte";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
// export let field; // export let field;
// export let buttonLabel = ""; // export let buttonLabel = "";
// export let buttonClass = ""; // export let buttonClass = "";
const channel = getContext("channel");
export let schemas; export let schemas;
export let recordId; export let recordId;
$: showOptions = false; $: showOptions = false;
@@ -41,7 +44,7 @@
function createInlineReference(e, schemaUId) { function createInlineReference(e, schemaUId) {
e.preventDefault(); e.preventDefault();
axios axios
.get("/records/newInline?schema=" + schemaUId) .get(channel.lucentUrl + "/records/newInline?schema=" + schemaUId)
.then((response) => { .then((response) => {
inLineCreateRecord = response.data; inLineCreateRecord = response.data;
showOptions = false; showOptions = false;
@@ -54,34 +57,36 @@
{#if schemas.length > 1} {#if schemas.length > 1}
<button <button
type="button" type="button"
class:is-first={!recordId} class:is-first={!recordId}
class=" btn btn-lg btn-link text-decoration-none inline-card-button" class=" btn btn-lg btn-link text-decoration-none inline-card-button"
on:click|preventDefault={(e) => (showOptions = !showOptions)} on:click|preventDefault={(e) => (showOptions = !showOptions)}
> >
<Icon width={24} height={24} icon="circle-plus" /> <Icon width={24} height={24} icon="circle-plus"/>
</button> </button>
{#if showOptions} {#if showOptions}
<div class="bg-light lx-card d-flex"> <div class="bg-light lx-card d-flex">
{#each schemas as schema} {#each schemas as schema}
<div <div
class="lx-card p-4 text-center me-4" class="lx-card p-4 text-center me-4"
style="max-width: 250px;" style="max-width: 250px;"
> >
<p>{schema.label}</p> <p>{schema.label}</p>
<div class="mb-2"> <div class="mb-2">
<button <button
class="btn btn-sm btn-primary" class="btn btn-sm btn-primary"
on:click={(e) => on:click={(e) =>
createInlineReference(e, schema.name)} createInlineReference(e, schema.name)}
>New >New
</button> </button>
<button <button
class="btn btn-sm btn-outline-primary" class="btn btn-sm btn-outline-primary"
on:click={(e) => openBrowseModal(e, schema.name)} on:click={(e) => openBrowseModal(e, schema.name)}
><Icon icon="magnifying-glass" /></button >
<Icon icon="magnifying-glass"/>
</button
> >
</div> </div>
</div> </div>
@@ -92,14 +97,16 @@
<div class="pb-2 text-start"> <div class="pb-2 text-start">
<div class="mb-2"> <div class="mb-2">
<button <button
class="btn btn-sm btn-primary" class="btn btn-sm btn-primary"
on:click={(e) => createInlineReference(e, schemas[0].name)} on:click={(e) => createInlineReference(e, schemas[0].name)}
>New >New
</button> </button>
<button <button
class="btn btn-sm btn-outline-primary" class="btn btn-sm btn-outline-primary"
on:click={(e) => openBrowseModal(e, schemas[0].name)} on:click={(e) => openBrowseModal(e, schemas[0].name)}
><Icon icon="magnifying-glass" /></button >
<Icon icon="magnifying-glass"/>
</button
> >
</div> </div>
</div> </div>
@@ -107,25 +114,28 @@
{#if inLineCreateRecord} {#if inLineCreateRecord}
<InlineEdit <InlineEdit
{...inLineCreateRecord} {...inLineCreateRecord}
on:cancel={(e) => (inLineCreateRecord = null)} on:cancel={(e) => (inLineCreateRecord = null)}
on:inlinesaved={save} on:inlinesaved={save}
/> />
{/if} {/if}
<BrowseModal bind:this={browseModal} on:insert={insert} /> <BrowseModal bind:this={browseModal} on:insert={insert}/>
<style> <style>
:global(.inline-card-wrapper) { :global(.inline-card-wrapper) {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
:global(.inline-card-wrapper .inline-card-button) { :global(.inline-card-wrapper .inline-card-button) {
visibility: hidden; visibility: hidden;
} }
:global(.inline-card-wrapper .inline-card-button.is-first) { :global(.inline-card-wrapper .inline-card-button.is-first) {
visibility: visible; visibility: visible;
} }
:global(.inline-card-wrapper:hover .inline-card-button) { :global(.inline-card-wrapper:hover .inline-card-button) {
visibility: visible; visibility: visible;
} }
@@ -0,0 +1,179 @@
<script>
import {getContext} from "svelte";
import {uniqBy, debounce} from "lodash";
import {previewTitle} from "../Preview";
import {getErrorMessage} from "./errorMessage";
import {sortByField} from "../../edges/sortEdges";
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
import Sortable from "../../libs/Sortable.svelte";
import RenderField from "../../content/RenderField.svelte";
import Icon from "../../common/Icon.svelte";
import Datalist from "./Datalist.svelte";
const channel = getContext("channel");
export let field;
export let id;
export let record;
export let graph;
export let validationErrors;
$: errorMessage = getErrorMessage(validationErrors, field.name);
$: references = graph.edges
.filter((edge) => edge.field === field.name)
.map((edge) => {
return graph.records.find((increc) => increc.id == edge.target && record.id == edge.source);
}).filter((rec) => (rec?.id ? true : false)) ?? [];
let search = ""
$: searchOptions = []
function removeReference(e, recordId) {
e.preventDefault();
graph.edges = graph.edges.filter(
(edge) => !(edge.target === recordId && edge.field === field.name)
);
}
function saveNew(e, newValue) {
e.preventDefault();
axios
.post(channel.lucentUrl + "/records", {
isCreateMode: true,
record: {
schema: field.collections[0],
status: "published",
data: {
[field.searchField]: newValue
}
},
})
.then((response) => {
searchOptions = [];
insert(e,response.data.records[0]);
console.log(response)
})
.catch((error) => {
searchOptions = [];
console.log(error);
});
}
function insert(e, insertRecord) {
e.preventDefault();
const recordsToInsert = [insertRecord];
const action = e.detail.action;
let newEdges = recordsToInsert.map((r) => {
return {
target: r.id,
source: record.id,
sourceSchema: record.schema,
targetSchema: r.schema,
field: field.name,
rank: ""
};
});
let replacedEdges = graph.edges;
if (action === "replace") {
replacedEdges = replacedEdges.filter((edge) => edge.field !== field.name);
}
graph.records = uniqBy([...graph.records, ...recordsToInsert], (r) => r.id);
graph.edges = uniqBy([...replacedEdges, ...newEdges], (edge) => edge.target + edge.field);
}
const updateResults = debounce((e) => {
axios
.get(channel.lucentUrl + "/records/suggestions", {
params: {
schema: field.collections[0],
field: field.searchField,
value: search,
ui: "text",
},
})
.then((response) => {
searchOptions = response.data;
})
.catch((error) => {
searchOptions = [];
console.log(error);
});
}, 500);
</script>
{#if errorMessage}
<div class="invalid-feedback d-block mb-3">
{errorMessage}
</div>
{/if}
<input
type="search"
{id}
on:keyup={updateResults}
class="form-control dropdown-toggle"
class:is-invalid={errorMessage}
bind:value={search}
placeholder={"Search for "+field.label}
data-bs-toggle="dropdown"
autocomplete="off"
readonly={field.readonly && !isCreateMode}
/>
<div class="dropdown-menu w-100">
{#if searchOptions}
{#each searchOptions as option (option.id)}
<div
on:click={(e) => insert(e, option)}
on:keypress={(e) => insert(e, option)}
>
<span class="dropdown-item">
{previewTitle(channel.schemas, option)}
</span>
</div>
{:else}
Start typing...
{/each}
{/if}
{#if search }
<div
on:click={(e) => saveNew(e,search)}
on:keypress={(e) => saveNew(e,search)}
>
<span class="dropdown-item">
Add "{search}"
</span>
</div>
{/if}
</div>
{#if references.length > 0}
<div class="d-flex">
{#each references as record (record.id)}
<span class="badge rounded-pill bg-light text-dark fs-6 mt-3">
<div class="d-flex align-items-center ">
{previewTitle(channel.schemas, record)}
<button
on:click|preventDefault={(e) => removeReference(e, record.id)}
type="button"
class="btn-close btn-sm ms-1"
style="font-size:10px"
aria-label="Close"
/>
</div>
</span>
{/each}
</div>
{/if}
+1 -1
View File
@@ -6,5 +6,5 @@ return [
"url" => env("LUCENT_URL", "http://localhost:8000/"), "url" => env("LUCENT_URL", "http://localhost:8000/"),
"previewTarget" => env("LUCENT_PREVIEW_TARGET", "previewTarget"), "previewTarget" => env("LUCENT_PREVIEW_TARGET", "previewTarget"),
"generateCommand" => env("LUCENT_GENERATE_COMMAND", "generate:static"), "generateCommand" => env("LUCENT_GENERATE_COMMAND", "generate:static"),
"imageFilters" => [], "imageFilters" => []
]; ];
+2 -1
View File
@@ -71,11 +71,12 @@ class ImageService
$image->filter(new $this->channelService->channel->imageFilters[$template]); $image->filter(new $this->channelService->channel->imageFilters[$template]);
try { try {
$image->encode('webp', 75); $image->encode('webp', 75);
$image->save($templateFilePath);
} catch (Exception $e) { } catch (Exception $e) {
$this->logger->error($e->getMessage()); $this->logger->error($e->getMessage());
return $this->notFoundImage; return $this->notFoundImage;
} }
$image->save($templateFilePath);
return $templateUri; return $templateUri;
} }
+66 -75
View File
@@ -163,23 +163,20 @@ class RecordController extends Controller
] ]
); );
} }
//
//
// public function newInline(Request $request) public function newInline(Request $request)
// { {
// $schema = $this->channelService->getSchema($request->input("schema"));
// $channel = ChannelRepo::current(); $record = $this->recordService->createEmpty($schema);
// $schema = $channel->schemas->where("name.value", $request->input("schema"))->first(); $queryRecord = QueryRecord::fromRecord($record);
// $record = Record::createEmpty($schema, AuthService::currentUserId($request));
// $queryRecord = QueryRecord::fromRecord($record); return [
// "schema" => $schema,
// return [ "record" => $queryRecord,
// "schemas" => $channel->schemas, "isCreateMode" => true,
// "schema" => $schema, ];
// "record" => $queryRecord, }
// "isCreateMode" => true,
// ];
// }
public function edit(Request $request) public function edit(Request $request)
{ {
@@ -224,72 +221,66 @@ class RecordController extends Controller
} }
//
// public function editInline(Request $request) public function editInline(Request $request)
// { {
// $channel = ChannelRepo::current(); $rid = $request->route("rid");
// $rid = $request->route("rid");
// $graph = $this->query
// $queryResult = $this->query ->filter(["id" => $rid])
// ->filter(["id" => $rid]) ->limit(1)
// ->limit(1) ->childrenDepth(2)
// ->childrenDepth(2) ->parentsDepth(1)
// ->parentsDepth(1) ->tree();
// ->run();
// return ok(
// $graph = $queryResult->getQueryRecords($channel->schemas); [
// $record = $graph->records[0]; "graph" => $graph->toArray(),
// return ok( "record" => $graph->first()->toArray(),
// [ ]
// "graph" => $graph->toArray(), );
// "record" => $record->toArray(), }
// ]
// );
// } public function suggestions(Request $request)
// {
// $arguments = [
// public function suggestions(Request $request) "schema" => $request->input("schema"),
// { ];
// $arguments = [
// "_sys.schema" => $request->input("schema"), if ($request->input("value")) {
// ]; if (in_array($request->input("ui"), ["text", "date"])) {
// $arguments["data.".$request->input("field") . "_regex"] = $request->input("value");
// if ($request->input("value")) { } elseif ($request->input("ui") == "number") {
// if (in_array($request->input("ui"), ["text", "date"])) { $arguments["data.".$request->input("field") . "_eqnum"] = floatval($request->input("value"));
// $arguments[$request->input("field") . "_regex"] = $request->input("value"); } elseif ($request->input("ui") == "date") {
// } elseif ($request->input("ui") == "number") { }
// $arguments[$request->input("field") . "_eqnum"] = floatval($request->input("value")); }
// } elseif ($request->input("ui") == "date") {
// } $records = $this->query
// } ->filter($arguments)
// ->limit(10)
// ->tree();
// $queryResult = $this->query
// ->filter($arguments) if ($records->isEmpty()) {
// ->limit(10) return ok([]);
// ->run(); }
//
// if (!$queryResult->hasResults()) { return ok($records->toArray());
// return ok([]); }
// }
// $schemas = $this->schemaRepo->all();
// $graph = $queryResult->getQueryRecords($schemas);
//
// return ok($graph->records->toArray());
// }
//
public function save(Request $request) public function save(Request $request)
{ {
$recordId = $request->input("record.id");
try { try {
if ($request->input("isCreateMode")) { if ($request->input("isCreateMode")) {
$this->recordService->create( $recordId = $this->recordService->create(
schemaName: $request->input("record.schema"), schemaName: $request->input("record.schema"),
data: $request->input("record.data"), data: $request->input("record.data"),
id: $request->input("record.id"), id:$recordId ?? "",
file: $request->input("record._file") ?? [], file: $request->input("record._file") ?? [],
edges: $request->input("edges"), edges: $request->input("edges") ?? [],
status: $request->input("record.status"), status: $request->input("record.status"),
uploadFromUrl: "" uploadFromUrl: ""
); );
@@ -304,7 +295,7 @@ class RecordController extends Controller
} }
$newGraph = $this->query $newGraph = $this->query
->filter(["id" => $request->input("record.id")]) ->filter(["id" => $recordId])
->limit(10) ->limit(10)
->childrenDepth(2) ->childrenDepth(2)
->parentsDepth(1) ->parentsDepth(1)
+1 -1
View File
@@ -75,7 +75,7 @@ readonly class RecordService
} }
$record = new Record( $record = new Record(
id: $id ?? Id::new(), id: empty($id) ? Id::new() : $id,
schema: $schema->name, schema: $schema->name,
status: Status::from($status), status: Status::from($status),
_sys: System::newRecord($this->authService->currentUserId()), _sys: System::newRecord($this->authService->currentUserId()),
+1
View File
@@ -21,6 +21,7 @@ class Reference implements FieldInterface, MinMaxInterface
public ?int $min = null, public ?int $min = null,
public ?int $max = null, public ?int $max = null,
public array $collections = [], public array $collections = [],
public string $searchField = "",
public string $layout = "", public string $layout = "",
public string $group = "", public string $group = "",
) )
+34
View File
@@ -3,9 +3,15 @@
use Illuminate\Contracts\Filesystem\Filesystem; use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
class StaticGenerator class StaticGenerator
{ {
/**
* @var array<string> $directoriesIndex
*/
public array $directoriesIndex = [];
public function __construct( public function __construct(
public Writer $writer, public Writer $writer,
@@ -16,8 +22,36 @@ class StaticGenerator
public function run(callable $callback): void public function run(callable $callback): void
{ {
echo "Start".PHP_EOL; echo "Start".PHP_EOL;
$this->removePreviousDirectories();
echo "Removing previous data".PHP_EOL;
$callback($this->writer); $callback($this->writer);
echo "Finito".PHP_EOL; echo "Finito".PHP_EOL;
} }
private function removePreviousDirectories() :void{
if(!file_exists(storage_path("lucent/directories.log"))){
return;
}
$directories = file_get_contents(storage_path("lucent/directories.log"));
$directoriesArr = array_reverse(explode(PHP_EOL,$directories));
foreach ($directoriesArr as $directory){
if(empty($directory) || $directory === "/") {
continue;
}
if(!file_exists(public_path($directory."/index.html"))){
continue;
}
unlink(public_path($directory."/index.html"));
rmdir(public_path($directory));
}
if(file_exists(public_path("index.html"))){
unlink(public_path("index.html"));
}
unlink(storage_path("lucent/directories.log"));
}
} }
+4 -4
View File
@@ -22,19 +22,19 @@ class Writer
} }
file_put_contents($filepath, $html); file_put_contents($filepath, $html);
file_put_contents(storage_path("lucent/directories.log"), $path.PHP_EOL, FILE_APPEND);
echo "Path: /$path".PHP_EOL; echo "Path: /$path".PHP_EOL;
} }
public function recordIterator(callable $query, callable $parser, int $skip = 0): int public function recordIterator(callable $query, callable $parser, int $skip = 0, $limit = 100): int
{ {
$limit = 100;
// logger("fetching $skip"); // logger("fetching $skip");
$records = $query($limit, $skip); $records = $query($limit, $skip);
$parser($records); $parser($records,$limit, $skip);
if ($records->hasResults()) { if ($records->count() > 0) {
return $this->recordIterator($query, $parser, $skip + $limit); return $this->recordIterator($query, $parser, $skip + $limit);
} }
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace Lucent\Support;
/**
* An in-memory memoizer that keeps the cached values around for the lifetime of this instance.
*/
class Memoize
{
/**
* The saved results
*
* @var array
*/
private array $cache = [];
/**
* $cacheTime is ignored - this will keep the results around for the lifetime of this instance.
*
* @see Memoize::memoizeCallable
*
* @param string $key
* @param callable $compute
*
* @return mixed
*/
public function memoizeCallable(string $key, callable $compute) :mixed
{
if (array_key_exists($key, $this->cache)) {
return $this->cache[$key];
}
$result = $compute();
$this->cache[$key] = $result;
return $result;
}
}
+8 -6
View File
@@ -7,13 +7,15 @@
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title') - Lucent Data Platform</title> <title>@yield('title') - Lucent Data Platform</title>
<!-- if development --> <!-- if development -->
{{-- @php--}} @php
{{-- echo '<script type="module" crossorigin src="http://127.0.0.1:5173/@vite/client"></script>';--}} echo '<script type="module" crossorigin src="http://127.0.0.1:5173/@vite/client"></script>';
{{-- @endphp--}} @endphp
{{-- <script type="module" crossorigin src="http://127.0.0.1:5173/main.js"></script>--}} <script type="module" crossorigin src="http://127.0.0.1:5173/main.js"></script>
<!-- if production --> <!-- if production -->
<link rel="stylesheet" href="/vendor/lucent/dist/{{ $manifest['main.css']["file"] }}" /> {{-- <link rel="stylesheet" href="/vendor/lucent/dist/{{ $manifest['main.css']["file"] }}" />--}}
<script type="module" src="/vendor/lucent/dist/{{ $manifest['main.js']["file"] }}"></script> {{-- <script type="module" src="/vendor/lucent/dist/{{ $manifest['main.js']["file"] }}"></script>--}}
<link rel="icon" type="image/x-icon" href="/favicon.ico"> <link rel="icon" type="image/x-icon" href="/favicon.ico">
</head> </head>