crazy stuff

This commit is contained in:
2026-01-07 21:42:18 +02:00
parent 57b0727788
commit 874ddd33e2
41 changed files with 5580 additions and 5039 deletions
+8
View File
@@ -0,0 +1,8 @@
{
"project_name": "Lucent Package",
"languages": {
"PHP": {
"language_servers": ["phpactor"],
},
},
}
+2 -1
View File
@@ -15,7 +15,8 @@
"phpoption/phpoption": "^1.9",
"spatie/image-optimizer": "^1.6",
"staudenmeir/laravel-cte": "^1.0",
"mustache/mustache": "^2.14"
"mustache/mustache": "^2.14",
"yosymfony/toml": "^1.0"
},
"require-dev": {
Generated
+111 -1
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e8fb1bee28ad339453d50110f7fea2f5",
"content-hash": "93994bb8d362147341e2fed15f80b93f",
"packages": [
{
"name": "brick/math",
@@ -5657,6 +5657,116 @@
"source": "https://github.com/webmozarts/assert/tree/1.11.0"
},
"time": "2022-06-03T18:03:27+00:00"
},
{
"name": "yosymfony/parser-utils",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/yosymfony/parser-utils.git",
"reference": "00bec9a12722b21f2baf7f9db35f127e90c162c9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yosymfony/parser-utils/zipball/00bec9a12722b21f2baf7f9db35f127e90c162c9",
"reference": "00bec9a12722b21f2baf7f9db35f127e90c162c9",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^6"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"psr-4": {
"Yosymfony\\ParserUtils\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Victor Puertas",
"email": "vpgugr@gmail.com",
"homepage": "http://yosymfony.com"
}
],
"description": "Parser utilities",
"homepage": "http://github.com/yosymfony/toml",
"keywords": [
"lexer",
"parser"
],
"support": {
"issues": "https://github.com/yosymfony/parser-utils/issues",
"source": "https://github.com/yosymfony/parser-utils/tree/master"
},
"time": "2018-06-29T15:31:11+00:00"
},
{
"name": "yosymfony/toml",
"version": "v1.0.4",
"source": {
"type": "git",
"url": "https://github.com/yosymfony/toml.git",
"reference": "bdab92ad920d0e36810a3a3e4a998d23f3498f8e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yosymfony/toml/zipball/bdab92ad920d0e36810a3a3e4a998d23f3498f8e",
"reference": "bdab92ad920d0e36810a3a3e4a998d23f3498f8e",
"shasum": ""
},
"require": {
"php": ">=7.1",
"yosymfony/parser-utils": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"Yosymfony\\Toml\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Victor Puertas",
"email": "vpgugr@gmail.com",
"homepage": "http://yosymfony.com"
}
],
"description": "A PHP parser for TOML compatible with specification 0.4.0",
"homepage": "http://github.com/yosymfony/toml",
"keywords": [
"mojombo",
"parser",
"toml"
],
"support": {
"issues": "https://github.com/yosymfony/toml/issues",
"source": "https://github.com/yosymfony/toml/tree/master"
},
"time": "2018-08-08T15:08:14+00:00"
}
],
"packages-dev": [
+10
View File
@@ -0,0 +1,10 @@
@import "./pico.min.css";
main {
display: flex;
gap: 40px;
aside {
width: 250px;
padding: 20px;
}
}
+4
View File
File diff suppressed because one or more lines are too long
+36
View File
@@ -0,0 +1,36 @@
<script>
import { getContext, onMount } from "svelte";
import RecordRow from "./RecordRow.svelte";
import ChannelLayout from "../../layouts/ChannelLayout.svelte";
import { get } from "../../modules/remote";
let { channel, user } = $props();
let records = $state([]);
let graph = $state(null);
let users = $state([]);
onMount(() => {
get(channel.lucentUrl + "/home/records", {}, (data) => {
records = data.records;
graph = data.graph;
users = data.users;
});
});
</script>
<ChannelLayout {body} {channel} {user}></ChannelLayout>
{#snippet body()}
<h3 class="header-small mb-4 mt-5">Latest Content changes</h3>
{#if records.length > 0}
<div class="table">
<table class="">
<tbody>
{#each records as record (record.id)}
<tr>
<RecordRow {channel} {graph} {record} {users}
></RecordRow>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
{/snippet}
+41
View File
@@ -0,0 +1,41 @@
<script>
import { formatDistanceToNow, parseJSON } from "date-fns";
import Avatar from "../../svelte/account/Avatar.svelte";
import { previewTitle } from "../../svelte/records/Preview";
import Preview from "../../svelte/files/Preview.svelte";
import { usernameById } from "../../svelte/account/users";
let { channel, users, record, graph } = $props();
let schema = $derived(
channel.schemas.find((s) => s.name === record.schema),
);
let frieldlyUpdatedAt = formatDistanceToNow(
parseJSON(record._sys.updatedAt),
{ addSuffix: true },
);
</script>
<td>
<div class="row-name">
{#if record.status === "draft"}
<span class="status">DRAFT</span>
{/if}
{#if schema.type === "files"}
<!-- <Preview {record} size="tiny" showFilename={true} /> -->
{:else}
<a href="{channel.lucentUrl}/records/{record.id}">
<!-- {previewTitle(channel.schemas, record, graph)} -->
</a>
{/if}
</div>
</td>
<td><a href="{channel.lucentUrl}/content/{schema.name}">{schema.label}</a> </td>
<td>
<div style="display: flex;gap: 14px">
<Avatar name={usernameById(users, record._sys.updatedBy)} side={24} />
<div class="ms-2">
{frieldlyUpdatedAt}
</div>
</div>
</td>
+15
View File
@@ -0,0 +1,15 @@
<script>
import Header from "./Header.svelte";
import Navbar from "./Navbar.svelte";
let { body, channel, user } = $props();
</script>
<Header {channel} {user}></Header>
<main>
<aside class="sidebar-content">
<Navbar {channel}></Navbar>
</aside>
<div class="main-content">
{@render body()}
</div>
</main>
@@ -1,26 +1,23 @@
<script>
import Avatar from "../account/Avatar.svelte";
import {getContext} from "svelte";
import Dropdown from "../common/Dropdown.svelte";
const channel = getContext("channel");
const user = getContext("user");
console.log( channel.commands)
import Avatar from "../common/Avatar.svelte";
// import Dropdown from "../svelte/common/Dropdown.svelte";
let { channel, user } = $props();
</script>
<div class="top-nav ">
<div class="top-nav">
<a class="top-nav-item" href="{channel.lucentUrl}/members">Members</a>
{#if channel.commands.length > 0}
<!-- {#if channel.commands.length > 0}
<Dropdown>
<div slot="button">Actions</div>
{#each channel.commands as command}
<a href="{channel.lucentUrl}/command-report/{command.signature}" class="top-nav-item">{command.name}</a>
<a
href="{channel.lucentUrl}/command-report/{command.signature}"
class="top-nav-item">{command.name}</a
>
{/each}
</Dropdown>
{/if}
{/if} -->
<!-- <div>-->
<!-- <form method="GET">-->
<!-- <input type="search" name="filter[search_regex]" placeholder="Search"-->
@@ -28,7 +25,6 @@
<!-- </form>-->
<!-- </div>-->
<a href="{channel.lucentUrl}/profile">
<Avatar side="28" name={user.name}/>
<Avatar side="28" name={user.name} />
</a>
</div>
+9
View File
@@ -0,0 +1,9 @@
<script>
let { channel } = $props();
</script>
<div class="sidebar-top">
<a class="logo" href={channel.lucentUrl}>{channel.name}</a>
<a class="nav-item" href="{channel.lucentUrl}/profile"> </a>
</div>
<div class="sidebar"></div>
+49 -39
View File
@@ -1,54 +1,64 @@
import {axiosInstance} from "./bootstrap";
import "../sass/app.scss";
import Account from "./svelte/Account.svelte";
import Channel from "./svelte/Channel.svelte";
import Mustache from "mustache";
import 'htmx.org';
import { axiosInstance } from "./bootstrap";
import { mount, setContext, unmount } from "svelte";
Mustache.escape = function (value) {
return value;
};
// import "../sass/app.scss";
import "../css/app.css";
import Register from "./svelte/account/Register.svelte";
import Login from "./svelte/account/Login.svelte";
import Verify from "./svelte/account/Verify.svelte";
import Profile from "./svelte/account/Profile.svelte";
import SetupIndex from "./svelte/setup/Index.svelte";
import Members from "./svelte/members/Members.svelte";
import RecordNotFound from "./svelte/records/NotFound.svelte";
import RecordEdit from "./svelte/records/Edit.svelte";
import ContentIndex from "./svelte/content/Index.svelte";
import HomeEntry from "./entry/HomeEntry/HomeEntry.svelte";
import BuildReport from "./svelte/build/Report.svelte";
// Define all components
const entryComponents = {
account: Account,
channel: Channel,
members: Members,
recordEdit: RecordEdit,
recordNotFound: RecordNotFound,
contentIndex: ContentIndex,
homeIndex: HomeEntry,
buildReport: BuildReport,
register: Register,
login: Login,
verify: Verify,
profile: Profile,
setup: SetupIndex,
};
let loadedComponents = [];
let loadSvelte = function () {
loadedComponents.map((comp) => comp.$destroy());
loadedComponents = [];
loadedComponents.map((comp) => unmount(comp));
loadedComponents = [];
const elements = document.body.querySelectorAll(".lucent-component");
if (elements.length === 0) {
return;
const elements = document.body.querySelectorAll(".lucent-component");
if (elements.length === 0) {
return;
}
const loadElement = function (element) {
const jsonData = document.getElementById("json-data").innerHTML;
const props = JSON.parse(jsonData);
const [__, view] = Object.entries(entryComponents).find(
([key, _]) => props.view === key,
);
if (!view) {
return [];
}
const loadElement = function (element) {
const componentId = element.attributes["data-layout"].value;
const [_, component] = Object.entries(entryComponents).find(
([key, _]) => componentId === key
);
if (!component) {
return [];
}
// props.axios = axiosInstance;
const jsonData = document.getElementById(
"json-" + componentId
).innerHTML;
const props = JSON.parse(jsonData);
props.axios = axiosInstance;
const compOptions = {
target: element,
props: props,
};
loadedComponents = [...loadedComponents, new component(compOptions)];
const compOptions = {
target: element,
props: props,
};
Array.from(elements).map(loadElement);
loadedComponents = [...loadedComponents, mount(view, compOptions)];
};
Array.from(elements).map(loadElement);
};
// document.addEventListener("turbo:load", loadSvelte);
document.addEventListener("DOMContentLoaded", loadSvelte);
+111
View File
@@ -0,0 +1,111 @@
import axios from "axios";
class Errors {
constructor(data) {
this.data = data ?? [];
}
first() {
return this.data[0] ?? null;
}
all() {
return this.data;
}
isEmpty() {
return this.data.length === 0;
}
isNotEmpty() {
return !this.isEmpty();
}
}
function makeErrors(errs) {
return new Errors(errs);
}
export function post(url, postData, callback) {
axios
.post(url, postData)
.then((res) => {
if (res.data.redirect !== undefined) {
// Turbo.visit(link(res.data.redirect));
return;
}
const errors = makeErrors(res.data.errors);
if (errors.isNotEmpty()) {
callback?.(null, errors);
} else {
callback?.(res.data, errors);
}
})
.catch((err) => {
const errors = makeErrors(["something went wrong"]);
callback?.(null, errors);
});
}
export function get(url, urlParams, callback) {
let params = url.endsWith("query")
? {
params: { query: JSON.stringify(urlParams) },
paramsSerializer: (params) => serializeParams(params),
}
: {
params: urlParams,
};
return axios
.get(url, params)
.then((res) => {
callback(res.data);
})
.catch((err) => {
console.log(err);
let errors = makeErrors(["something went wrong"]);
if (!err) {
errors = makeErrors([]);
}
callback(null, errors);
});
}
function serializeParams(obj, prefix = "") {
const params = [];
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
const paramKey = prefix ? `${prefix}[${key}]` : key;
if (
typeof value === "object" &&
value !== null &&
!Array.isArray(value)
) {
// Nested object
params.push(serializeParams(value, paramKey));
} else if (Array.isArray(value)) {
// Array
value.forEach((item, index) => {
if (typeof item === "object" && item !== null) {
params.push(serializeParams(item, `${paramKey}[${index}]`));
} else {
params.push(
`${encodeURIComponent(`${paramKey}[${index}]`)}=${encodeURIComponent(item)}`,
);
}
});
} else {
// Simple value
params.push(
`${encodeURIComponent(paramKey)}=${encodeURIComponent(value)}`,
);
}
}
}
return params.flat().join("&");
}
@@ -1,7 +1,6 @@
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
import { range } from "lodash";
import NavItem from "./NavItem.svelte";
export let inModal;
export let modalUrl;
@@ -1,8 +1,6 @@
<script>
import {createEventDispatcher, getContext} from "svelte";
import {debounce} from "lodash";
import {previewTitle} from "../../records/Preview";
import { createEventDispatcher, getContext } from "svelte";
import { previewTitle } from "../../records/Preview";
const channel = getContext("channel");
const dispatch = createEventDispatcher();
@@ -10,9 +8,8 @@
export let value = "";
export let field;
let search = ""
$: searchOptions = []
let search = "";
$: searchOptions = [];
const updateResults = debounce((e) => {
axios
@@ -35,46 +32,36 @@
function apply(e, newOption) {
e.preventDefault();
value = newOption.id
value = newOption.id;
dispatch("addFilter");
value = ""
value = "";
}
</script>
<div class="reference-tags">
<input
type="search"
on:keyup={updateResults}
bind:value={search}
placeholder={"Search for "+field.label}
autocomplete="off"
type="search"
on:keyup={updateResults}
bind:value={search}
placeholder={"Search for " + field.label}
autocomplete="off"
/>
<div class="reference-tags-results">
{#if searchOptions}
{#each searchOptions as option (option.id)}
<div
class="reference-tags-option"
role="button"
tabindex="0"
on:click={(e) => apply(e, option)}
on:keypress={(e) => apply(e, option)}
class="reference-tags-option"
role="button"
tabindex="0"
on:click={(e) => apply(e, option)}
on:keypress={(e) => apply(e, option)}
>
{previewTitle(channel.schemas, option)}
</div>
{:else}
<div
class="start-typing">
Start typing...
</div>
<div class="start-typing">Start typing...</div>
{/each}
{/if}
</div>
</div>
-41
View File
@@ -1,41 +0,0 @@
<script>
import {getContext, onMount} from "svelte";
import RecordRow from "./RecordRow.svelte"
const channel = getContext("channel");
let records = [];
let graph = null;
let users = [];
onMount(() => {
axios
.get(channel.lucentUrl + "/home/records")
.then((response) => {
records = response.data.records;
graph = response.data.graph;
users = response.data.users;
})
.catch((error) => {
console.log(error);
});
});
</script>
<h3 class="header-small mb-4 mt-5">Latest Content changes</h3>
{#if records.length > 0}
<div class="table">
<table class="">
<tbody>
{#each records as record (record.id)}
<tr>
<RecordRow {graph} {record} {users}/>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
-50
View File
@@ -1,50 +0,0 @@
<script>
import {formatDistanceToNow, parseJSON} from "date-fns";
import Avatar from "../account/Avatar.svelte";
import {previewTitle} from "../records/Preview";
import Preview from "../files/Preview.svelte";
import {usernameById} from "../account/users";
import {getContext} from "svelte";
const channel = getContext("channel");
export let users;
export let graph;
export let record;
let schema = channel.schemas.find((s) => s.name === record.schema);
let frieldlyUpdatedAt = formatDistanceToNow(
parseJSON(record._sys.updatedAt),
{addSuffix: true}
);
</script>
<td>
<div class="row-name">
{#if record.status === "draft"}
<span class="status">DRAFT</span>
{/if}
{#if schema.type === "files"}
<Preview {record} size="tiny" showFilename={true}/>
{:else}
<a
href="{channel.lucentUrl}/records/{record.id}"
>
{previewTitle(channel.schemas, record, graph)}
</a>
{/if}
</div>
</td>
<td><a
href="{channel.lucentUrl}/content/{schema.name}">{schema.label}</a
>
</td>
<td>
<div style="display: flex;gap: 14px">
<Avatar name={usernameById(users, record._sys.updatedBy)} side={24}/>
<div class="ms-2">
{frieldlyUpdatedAt}
</div>
</div>
</td>
-39
View File
@@ -1,39 +0,0 @@
<script>
import NavbarMenu from "./NavbarMenu.svelte";
import {getContext} from "svelte";
export let schema;
const channel = getContext("channel");
const readableSchemas = getContext("readableSchemas");
const fileSchemas = readableSchemas.filter((sc) => sc.type === "files");
const otherSchemas = readableSchemas.filter((sc) => !sc.isEntry && sc.type === "collection");
</script>
<div class="sidebar-top">
<a class="logo" href="{channel.lucentUrl}">{channel.name}</a>
<a class="nav-item" href="{channel.lucentUrl}/profile">
</a>
</div>
<div class="sidebar">
<NavbarMenu
title="Content"
schemas={ readableSchemas.filter((sc) => sc.isEntry)}
schema={schema}
expanded={true}
/>
<NavbarMenu
title="Files"
schemas={ fileSchemas}
schema={schema}
/>
<NavbarMenu
title="Other"
schemas={ otherSchemas}
schema={schema}
/>
</div>
+47 -57
View File
@@ -1,14 +1,13 @@
<script>
import {afterUpdate, getContext, onMount} from "svelte";
import {isEqual} from "lodash";
import { afterUpdate, getContext, onMount } from "svelte";
import axios from "axios";
import EditHeader from "./header/EditHeader.svelte"
import FilePreview from "./FilePreview.svelte"
import ContentTabs from "./header/ContentTabs.svelte"
import FormField from "./FormField.svelte"
import Graph from "./Graph.svelte"
import Info from "./Info.svelte"
import ErrorAlert from "../common/ErrorAlert.svelte"
import EditHeader from "./header/EditHeader.svelte";
import FilePreview from "./FilePreview.svelte";
import ContentTabs from "./header/ContentTabs.svelte";
import FormField from "./FormField.svelte";
import Graph from "./Graph.svelte";
import Info from "./Info.svelte";
import ErrorAlert from "../common/ErrorAlert.svelte";
import Title from "./header/Title.svelte";
const channel = getContext("channel");
@@ -17,7 +16,7 @@
export let record;
export let graph = {
records: [],
edges: []
edges: [],
};
// export let recordHistory;
export let isCreateMode;
@@ -29,14 +28,11 @@
$: validationErrors = null;
$: errorMessage = validationErrors
? `Record submission failed. ${
Object.entries(validationErrors).length
} error(s)`
Object.entries(validationErrors).length
} error(s)`
: null;
let activeFields = schema.fields.filter(
(f) => f.name !== "id"
);
let activeFields = schema.fields.filter((f) => f.name !== "id");
onMount(() => {
setOriginalContent();
@@ -104,7 +100,9 @@
}
// remove trashed edges
graph.edges = graph.edges?.filter((edge) => !edge._isTrashed && edge.source === record.id);
graph.edges = graph.edges?.filter(
(edge) => !edge._isTrashed && edge.source === record.id,
);
axios
.post(channel.lucentUrl + "/records", {
record: record,
@@ -115,7 +113,8 @@
console.log("SAVE: SAVED");
if (isCreateMode) {
window.location = channel.lucentUrl + "/records/" + record.id;
window.location =
channel.lucentUrl + "/records/" + record.id;
} else {
record = response.data.records[0] ?? null;
if (!record) {
@@ -137,7 +136,7 @@
errorMessage = error.response.data.error;
} else {
validationErrors = error.response.data.error;
console.log(validationErrors)
console.log(validationErrors);
}
}
resolve(null);
@@ -149,70 +148,61 @@
}
</script>
<svelte:window on:beforeunload={beforeUnload}/>
<svelte:window on:beforeunload={beforeUnload} />
<div class="record-edit">
<div class="tools-header">
<!-- <Manager managerRecords={recordHistory} {graph}/>-->
<EditHeader {schema} bind:record {isCreateMode} bind:activeContentTab/>
<EditHeader {schema} bind:record {isCreateMode} bind:activeContentTab />
{#if isCreateMode}
<button
class="button primary btn-spinner"
on:click={save}
>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
<button class="button primary btn-spinner" on:click={save}>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
Create
</button>
{:else if hasUnsavedData}
<button
type="button"
class="button primary ms-2 btn btn-primary btn-spinner"
on:click={save}
type="button"
class="button primary ms-2 btn btn-primary btn-spinner"
on:click={save}
>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
Save
</button>
{/if}
</div>
<Title {schema} {record} {isCreateMode}/>
<Title {schema} {record} {isCreateMode} />
<ErrorAlert message={errorMessage}/>
<ErrorAlert message={errorMessage} />
<div class=" mt-4" style="margin-bottom:150px;position:relative;">
<ContentTabs
{schema}
{isCreateMode}
bind:active={activeContentTab}
/>
<ContentTabs {schema} {isCreateMode} bind:active={activeContentTab} />
{#if !["_graph", "_info"].includes(activeContentTab)}
<FilePreview {record} {schema}/>
<FilePreview {record} {schema} />
{#each activeFields as field (field.name)}
{#if activeContentTab === field.group}
<FormField
bind:data={record.data}
bind:graph={graph}
{field}
{schema}
{record}
{validationErrors}
{isCreateMode}
bind:data={record.data}
bind:graph
{field}
{schema}
{record}
{validationErrors}
{isCreateMode}
/>
{/if}
{/each}
{:else if activeContentTab === "_graph"}
<Graph {graph} {record}/>
<Graph {graph} {record} />
{:else if activeContentTab === "_info"}
<Info {record} {graph} {users} {schema}/>
<Info {record} {graph} {users} {schema} />
{/if}
</div>
</div>
+53 -71
View File
@@ -1,11 +1,10 @@
<script>
import {friendlyDate} from "../../helpers";
import { friendlyDate } from "../../helpers";
import Avatar from "../account/Avatar.svelte";
import {usernameById} from "../account/users";
import {isEqual} from "lodash";
import { usernameById } from "../account/users";
import Icon from "../common/Icon.svelte";
import RevisionCell from "./revisions/RevisionCell.svelte";
import {getContext} from "svelte";
import { getContext } from "svelte";
import RevisionEdgeRow from "./revisions/RevisionEdgeRow.svelte";
const channel = getContext("channel");
@@ -30,27 +29,27 @@
});
function getEdgesByField(fieldsWithDiff, revision) {
edgeFieldsDiff = graph.edges.filter((e) => e.depth === 1).reduce((c, e) => {
if (!c[e.field]) {
c[e.field] = {
record: [],
revision: [],
edgeFieldsDiff = graph.edges
.filter((e) => e.depth === 1)
.reduce((c, e) => {
if (!c[e.field]) {
c[e.field] = {
record: [],
revision: [],
};
}
}
c[e.field]["record"].push(e)
return c;
}, {});
c[e.field]["record"].push(e);
return c;
}, {});
edgeFieldsDiff = revision._edges.reduce((c, e) => {
if (!c[e.field]) {
c[e.field] = {
record: [],
revision: [],
}
};
}
c[e.field]["revision"].push(e)
c[e.field]["revision"].push(e);
return c;
}, edgeFieldsDiff);
}
@@ -62,7 +61,7 @@
fieldsWithDiff = schema.fields.filter((f) => {
return !isEqual(selectedRevision.data[f.name], record.data[f.name]);
});
getEdgesByField(fieldsWithDiff, revision)
getEdgesByField(fieldsWithDiff, revision);
revisionSection.scrollIntoView();
}
@@ -71,7 +70,7 @@
rollbackError = "";
axios
.post(
`${channel.lucentUrl}/records/${record.id}/rollback/${selectedRevision._sys.version}`
`${channel.lucentUrl}/records/${record.id}/rollback/${selectedRevision._sys.version}`,
)
.then((response) => {
window.location.reload();
@@ -84,7 +83,7 @@
}
</script>
<div class="lx-card ">
<div class="lx-card">
<div class="row">
<div class="col-8">
<div>
@@ -98,24 +97,22 @@
<div>
<span class="label text-end text-muted"> created </span>
<Avatar
name={usernameById(users, record._sys.createdBy)}
side={24}
name={usernameById(users, record._sys.createdBy)}
side={24}
/>
{friendlyDate(record._sys.createdAt)}
</div>
<div>
<span class="label text-end text-muted">updated </span>
<span class="label text-end text-muted">updated </span>
<Avatar
name={usernameById(users, record._sys.updatedBy)}
side={24}
name={usernameById(users, record._sys.updatedBy)}
side={24}
/>
{friendlyDate(record._sys.updatedAt)}
</div>
</div>
<div class="col-4">
<span class="label d-block text-muted "
>Rules for this schema
</span>
<span class="label d-block text-muted">Rules for this schema </span>
<small>
Each record maintains the last {schema.revisions}
versions
@@ -125,33 +122,31 @@
</div>
<div class="revisions">
{#if schema.revisions > 0}
<div class="header-small mb-3">Revisions</div>
<div class="header-small mb-3">Revisions</div>
{#each revisions as revision}
{#if revision._sys.version !== record._sys.version}
<div
class="revision"
class:active={revision._sys.version ===
class="revision"
class:active={revision._sys.version ===
selectedRevision?._sys.version}
>
<div class="version">
<span>version {revision._sys.version}</span>
<Avatar
name={usernameById(users, revision._sys.updatedBy)}
side={24}
name={usernameById(users, revision._sys.updatedBy)}
side={24}
/>
{friendlyDate(revision._sys.updatedAt)}
</div>
<div class="col-3 text-center">
<button
disabled={revision._sys.version ===
disabled={revision._sys.version ===
selectedRevision?._sys.version}
class="button"
on:click={(e) => compare(e, revision)}
>Compare
</button
>
class="button"
on:click={(e) => compare(e, revision)}
>Compare
</button>
</div>
</div>
{/if}
@@ -169,15 +164,13 @@
<p class="text-center fw-bold mb-3 mt-5">
If you choose to rollback to this revision
</p>
<button
on:click={rollback}
class="button"
>
<button on:click={rollback} class="button">
Rollback to version {selectedRevision._sys.version}
</button>
{#if rollbackError}
<span class="d-block text-danger mt-3">{rollbackError}</span>
<span class="d-block text-danger mt-3">{rollbackError}</span
>
{/if}
<div class="mt-3">
{#each fieldsWithDiff as field}
@@ -188,31 +181,28 @@
<!-- <div class="d-block" style="width:200px;">
{field.label}
</div> -->
<div
class="revision-field"
style="overflow:hidden"
>
<div class="revision-field" style="overflow:hidden">
<div class="compare-left">
<RevisionCell
{field}
side={record.data[field.name]}
colorClass="text-danger"
{field}
side={record.data[field.name]}
colorClass="text-danger"
/>
</div>
<div class="compare-center">
<span class="me-1">{field.label}</span>
<Icon
icon="angle-right"
width="12"
height="12"
icon="angle-right"
width="12"
height="12"
/>
</div>
<div class="compare-right">
<RevisionCell
edges={selectedRevision._edges}
{field}
side={selectedRevision.data[field.name]}
colorClass="text-success"
edges={selectedRevision._edges}
{field}
side={selectedRevision.data[field.name]}
colorClass="text-success"
/>
</div>
</div>
@@ -226,22 +216,16 @@
{/if}
<div class="mt-3">
<p class="text-center fw-bold mb-3 mt-5">
Record References
</p>
<p class="text-center fw-bold mb-3 mt-5">Record References</p>
{#each Object.entries(edgeFieldsDiff) as [field, edges]}
<div
class="revision-references"
style="overflow:hidden"
>
<div class="revision-references" style="overflow:hidden">
<div class="reference-field">
{field}:
</div>
<div class="reference-compare">
<p class="">Record</p>
{#each edges.record as edge}
<RevisionEdgeRow {edge}/>
<RevisionEdgeRow {edge} />
{:else}
<p>No references</p>
{/each}
@@ -249,7 +233,7 @@
<div class="reference-compare">
<p class="text-success">Revision</p>
{#each edges.revision as edge}
<RevisionEdgeRow {edge}/>
<RevisionEdgeRow {edge} />
{:else}
<p>No references</p>
{/each}
@@ -258,7 +242,5 @@
{/each}
</div>
</div>
{/if}
</div>
+41 -46
View File
@@ -1,7 +1,11 @@
<script>
import {afterUpdate, createEventDispatcher, getContext, onMount} from "svelte";
import {
afterUpdate,
createEventDispatcher,
getContext,
onMount,
} from "svelte";
import {isEqual} from "lodash";
import FormField from "./FormField.svelte";
import FilePreview from "./FilePreview.svelte";
import ContentTabs from "./header/ContentTabs.svelte";
@@ -16,7 +20,7 @@
export let record;
export let graph = {
records: [],
edges: []
edges: [],
};
export let isCreateMode;
let originalContent;
@@ -25,13 +29,11 @@
$: validationErrors = null;
$: errorMessage = validationErrors
? `Record submission failed. ${
Object.entries(validationErrors).length
} error(s)`
Object.entries(validationErrors).length
} error(s)`
: null;
let activeFields = schema.fields.filter(
(f) => f.name !== "id"
);
let activeFields = schema.fields.filter((f) => f.name !== "id");
let tabname = "_default";
let fieldToTabs = schema.fields.reduce((c, f) => {
@@ -114,8 +116,10 @@
return;
}
// remove trashed edges
graph.edges = graph.edges?.filter((edge) => !edge._isTrashed && edge.source === record.id) ?? [];
graph.edges =
graph.edges?.filter(
(edge) => !edge._isTrashed && edge.source === record.id,
) ?? [];
axios
.post(channel.lucentUrl + "/records", {
@@ -150,64 +154,55 @@
}
</script>
<svelte:window on:beforeunload={beforeUnload}/>
<svelte:window on:beforeunload={beforeUnload} />
<div class="inline-edit record-edit">
<div class="tools-header">
<EditHeader {schema} bind:record {isCreateMode} bind:activeContentTab/>
<EditHeader {schema} bind:record {isCreateMode} bind:activeContentTab />
{#if isCreateMode}
<button
class="button primary btn-spinner"
on:click={save}
>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
<button class="button primary btn-spinner" on:click={save}>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
Create
</button>
{:else if hasUnsavedData}
<button
type="button"
class="button primary ms-2 btn btn-primary btn-spinner"
on:click={save}
type="button"
class="button primary ms-2 btn btn-primary btn-spinner"
on:click={save}
>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
Save
</button>
{/if}
</div>
<Title {schema} {record} {isCreateMode}/>
<ErrorAlert message={errorMessage}/>
<Title {schema} {record} {isCreateMode} />
<ErrorAlert message={errorMessage} />
<div class=" mt-4" style="margin-bottom:150px;position:relative;">
<ContentTabs
{schema}
{isCreateMode}
bind:active={activeContentTab}
/>
<FilePreview {record} {schema}/>
<ContentTabs {schema} {isCreateMode} bind:active={activeContentTab} />
<FilePreview {record} {schema} />
<!-- <fieldset disabled="disabled"> -->
{#each activeFields as field (field.name)}
{#if activeContentTab === field.group}
<FormField
bind:data={record.data}
bind:graph={graph}
{field}
{schema}
{record}
{validationErrors}
{isCreateMode}
bind:data={record.data}
bind:graph
{field}
{schema}
{record}
{validationErrors}
{isCreateMode}
/>
{/if}
{/each}
<!-- </fieldset> -->
</div>
</div>
@@ -1,9 +1,8 @@
<script>
import {getContext} from "svelte";
import {debounce} from "lodash";
import {previewTitle} from "../Preview";
import {getErrorMessage} from "./errorMessage";
import {insertEdges} from "./reference.js";
import { getContext } from "svelte";
import { previewTitle } from "../Preview";
import { getErrorMessage } from "./errorMessage";
import { insertEdges } from "./reference.js";
import Icon from "../../common/Icon.svelte";
const channel = getContext("channel");
@@ -15,20 +14,24 @@
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 = []
$: 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)
(edge) => !(edge.target === recordId && edge.field === field.name),
);
}
@@ -41,14 +44,14 @@
schema: field.collections[0],
status: "published",
data: {
[field.searchField]: newValue
}
[field.searchField]: newValue,
},
},
})
.then((response) => {
searchOptions = [];
insert(e, response.data.records[0]);
console.log(response)
console.log(response);
})
.catch((error) => {
searchOptions = [];
@@ -58,10 +61,16 @@
function insert(e, insertRecord) {
e.preventDefault();
graph = insertEdges(graph, record, [insertRecord], field.name, e.detail.action);
search = ""
searchEl.focus()
searchEl.blur()
graph = insertEdges(
graph,
record,
[insertRecord],
field.name,
e.detail.action,
);
search = "";
searchEl.focus();
searchEl.blur();
}
const updateResults = debounce((e) => {
@@ -82,9 +91,8 @@
console.log(error);
});
}, 500);
</script>
<div class="reference-tags">
{#if errorMessage}
<div class="invalid-feedback d-block mb-3">
@@ -93,44 +101,39 @@
{/if}
<input
type="search"
bind:this={searchEl}
{id}
on:keyup={updateResults}
class:is-invalid={errorMessage}
bind:value={search}
placeholder={"Search for "+field.label}
autocomplete="off"
type="search"
bind:this={searchEl}
{id}
on:keyup={updateResults}
class:is-invalid={errorMessage}
bind:value={search}
placeholder={"Search for " + field.label}
autocomplete="off"
/>
<div class="reference-tags-results">
{#if searchOptions}
{#each searchOptions as option (option.id)}
<div
class="reference-tags-option"
role="button"
tabindex="0"
on:click={(e) => insert(e, option)}
on:keypress={(e) => insert(e, option)}
>
{previewTitle(channel.schemas, option ,graph)}
</div>
{:else}
<div
class="start-typing">
Start typing...
</div>
{/each}
{/if}
{#if search }
<div
class="reference-tags-option"
role="button"
tabindex="0"
on:click={(e) => saveNew(e,search)}
on:keypress={(e) => saveNew(e,search)}
on:click={(e) => insert(e, option)}
on:keypress={(e) => insert(e, option)}
>
{previewTitle(channel.schemas, option, graph)}
</div>
{:else}
<div class="start-typing">Start typing...</div>
{/each}
{/if}
{#if search}
<div
class="reference-tags-option"
role="button"
tabindex="0"
on:click={(e) => saveNew(e, search)}
on:keypress={(e) => saveNew(e, search)}
>
Add "{search}"
</div>
@@ -142,26 +145,23 @@
<div style="display: flex;align-items: center;gap: 4px">
{#each references as record (record.id)}
<span class="reference-tags-selected-value">
<a
class="record-title"
href="{channel.lucentUrl}/records/{record.id}"
>
{previewTitle(channel.schemas, record)}
</a>
<a
class="record-title"
href="{channel.lucentUrl}/records/{record.id}"
>
{previewTitle(channel.schemas, record)}
</a>
<button
on:click|preventDefault={(e) => removeReference(e, record.id)}
type="button"
class="button-text"
aria-label="Close"
on:click|preventDefault={(e) =>
removeReference(e, record.id)}
type="button"
class="button-text"
aria-label="Close"
>
<Icon width={12} height={12} icon="close"></Icon>
</button>
<Icon width={12} height={12} icon="close"></Icon>
</button>
</span>
{/each}
</div>
{/if}
+8 -10
View File
@@ -1,5 +1,4 @@
<script>
import { v4 as uuidv4 } from "uuid";
import { getContext } from "svelte";
import Icon from "../../common/Icon.svelte";
import { getErrorMessage } from "./errorMessage";
@@ -14,12 +13,11 @@
function generateId(e) {
e.preventDefault();
value = uuidv4();
value = randomUUID();
}
</script>
<div class="mb-0">
<div class="d-flex justify-content-between">
<input
type="text"
@@ -31,13 +29,13 @@
{readonly}
/>
{#if !readonly}
<button
class="btn btn-primary ms-2"
title="Generate a new UUIDv4"
on:click={generateId}
>
<Icon icon="dice" />
</button>
<button
class="btn btn-primary ms-2"
title="Generate a new UUIDv4"
on:click={generateId}
>
<Icon icon="dice" />
</button>
{/if}
</div>
+2 -3
View File
@@ -1,11 +1,10 @@
<script>
import { uniqueId } from "lodash";
import { getContext } from "svelte";
const channelurl = getContext("channelurl");
export let field;
export let value;
export let schema;
let id = uniqueId();
let id = randomUUID();
</script>
<div class="mb-0">
@@ -25,6 +24,6 @@
placeholder="https://www.example.com"
/>
{#if field.help}
<small class=" text-primary opacity-50">{field.help}</small>
<small class=" text-primary opacity-50">{field.help}</small>
{/if}
</div>
+28 -21
View File
@@ -1,24 +1,31 @@
import {uniqBy} from "lodash";
export function insertEdges(
graph,
sourceRecord,
targetRecords,
fieldName,
action = "",
) {
let newEdges = targetRecords.map((r) => {
return {
target: r.id,
source: sourceRecord.id,
sourceSchema: sourceRecord.schema,
targetSchema: r.schema,
field: fieldName,
depth: 1,
rank: "",
};
});
export function insertEdges(graph, sourceRecord, targetRecords, fieldName, action = "") {
let newEdges = targetRecords.map((r) => {
return {
target: r.id,
source: sourceRecord.id,
sourceSchema: sourceRecord.schema,
targetSchema: r.schema,
field: fieldName,
depth: 1,
rank: ""
};
});
let replacedEdges = graph.edges;
if (action === "replace") {
replacedEdges = replacedEdges.filter((edge) => edge.field !== field.name);
}
let replacedEdges = graph.edges;
if (action === "replace") {
replacedEdges = replacedEdges.filter((edge) => edge.field !== field.name);
}
graph.records = uniqBy([...graph.records, ...targetRecords], (r) => r.id);
graph.edges = uniqBy([...replacedEdges, ...newEdges], (edge) => edge.source + edge.target + edge.field + edge.depth);
return graph;
graph.records = uniqBy([...graph.records, ...targetRecords], (r) => r.id);
graph.edges = uniqBy(
[...replacedEdges, ...newEdges],
(edge) => edge.source + edge.target + edge.field + edge.depth,
);
return graph;
}
+4611 -4376
View File
File diff suppressed because it is too large Load Diff
+28 -32
View File
@@ -1,34 +1,30 @@
{
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"@codemirror/commands": "^6.6.0",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-markdown": "^6.2.5",
"@codemirror/state": "^6.4.1",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"axios": "^1.7.4",
"codemirror": "^6.0.1",
"date-fns": "^3.6.0",
"flatpickr": "^4.6.13",
"fuse.js": "^7.0.0",
"htmx.org": "^2.0.1",
"install": "^0.13.0",
"laravel-vite-plugin": "^1.0.5",
"lodash": "^4.17.21",
"mustache": "^4.2.0",
"npm": "^10.8.2",
"postcss": "8.4.31",
"sass": "^1.77.8",
"sortablejs": "^1.15.2",
"svelte": "^4.2.18",
"tinymce": "^6.8.4",
"trix": "^2.1.5",
"uuid": "^10.0.0",
"vite": "5.2.6"
}
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"@codemirror/commands": "^6.6.0",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-markdown": "^6.2.5",
"@codemirror/state": "^6.4.1",
"@sveltejs/vite-plugin-svelte": "^6.2.3",
"axios": "^1.7.4",
"codemirror": "^6.0.1",
"date-fns": "^3.6.0",
"flatpickr": "^4.6.13",
"fuse.js": "^7.0.0",
"install": "^0.13.0",
"mustache": "^4.2.0",
"npm": "^10.8.2",
"postcss": "8.4.31",
"sass": "^1.77.8",
"sortablejs": "^1.15.2",
"svelte": "^5.46.1",
"tinymce": "^6.8.4",
"trix": "^2.1.5",
"vite": "^7.3.1"
}
}
+30 -6
View File
@@ -1,7 +1,31 @@
@extends('lucent::layouts.'.$layout)
@section('title')
{{ $title }}
@endsection
@section('content')
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title> {{ $title }} - Lucent Data Platform</title>
@if(config("lucent.env") == "production")
<!-- if production -->
<link rel="stylesheet" href="{{url('vendor/lucent/dist/'.$manifest['main.js']["css"][0])}}"/>
<script type="module" src="{{url('vendor/lucent/dist/'.$manifest['main.js']["file"])}}"></script>
@else
<!-- if development -->
@php
echo '<script type="module" crossorigin src="http://127.0.0.1:5173/@vite/client"></script>';
@endphp
<script type="module" crossorigin src="http://127.0.0.1:5173/main.js"></script>
@endif
<link rel="icon" type="image/x-icon" href="{{url('favicon.ico')}}">
</head>
<body>
{!! $svelte !!}
@endsection
</body>
</html>
+6
View File
@@ -0,0 +1,6 @@
{
"name": "lucent-package",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}
+1 -3
View File
@@ -10,9 +10,7 @@ use JsonSerializable;
*/
class ArrayContainer implements ArrayAccess, JsonSerializable
{
public function __construct(public array $data)
{
}
public function __construct(public array $data) {}
public function get(string $key, mixed $default = null): mixed
{
+10 -32
View File
@@ -7,61 +7,39 @@ use Illuminate\Console\Command;
use Lucent\Schema\Schema;
use Lucent\Schema\SchemaService;
use Lucent\Schema\Type;
use Yosymfony\Toml\Toml;
class CompileSchemas extends Command
{
protected $signature = "lucent:schemas";
protected $signature = 'lucent:schemas';
protected $description = 'Compiles schemas';
protected $description = "Compiles schemas";
public function handle(SchemaService $schemaService)
{
$configDir = base_path(config('lucent.schemas_path'));
$configDir = base_path(config("lucent.schemas_path"));
$schemasDirIterator = new DirectoryIterator($configDir);
$schemas = [];
foreach ($schemasDirIterator as $file) {
if ($file->getExtension() !== "json") {
if ($file->getExtension() !== "toml") {
continue;
}
$schemaData = Toml::ParseFile(
$configDir . "/" . $file->getFilename(),
);
$schemaJson = file_get_contents($configDir . "/" . $file->getFilename());
$schema = json_decode($schemaJson, true);
if (empty($schema)) {
$this->error("Invalid JSON " . $file->getFilename());
return 0;
}
$schemas[] = $schema;
$schemas[] = $schemaData;
}
$schemas = collect($schemas)->sortBy("label")->values();
$roles = $schemas
->map([$schemaService, 'fromArray'])
->whereIn("type", [Type::COLLECTION, Type::FILES])
->reduce(fn($carry, Schema $schema) => array_merge(
$carry,
$schema->read,
$schema->write,
config("lucent.canInvite") ?? [],
config("lucent.canBuild") ?? [],
), []);
$json = [
"schemas" => $schemas->toArray(),
"roles" => collect($roles)->push("admin")->push("removed")->unique()->values()->toArray()
];
if (!file_exists(storage_path("lucent"))) {
mkdir(storage_path("lucent"));
}
file_put_contents(schemas_path(), json_encode($json));
file_put_contents(schemas_path(), json_encode($schemas));
$this->info("Lucent Schemas were updated");
}
}
+84
View File
@@ -0,0 +1,84 @@
<?php
namespace Lucent\Commands;
use Illuminate\Console\Command;
class DeveloperMode extends Command
{
protected $signature = "lucent:developer {enable} {path}";
protected $description = "Change composer.json to enable or disable the developer repository";
public function handle(): void
{
$enable = $this->argument("enable") === "enable";
$path = $this->argument("path");
if ($enable) {
$this->enable($path);
} else {
$this->disable($path);
}
}
private function getComposerJson(): array
{
return json_decode(file_get_contents(base_path("composer.json")), true);
}
private function replaceComposerJson(array $composerJson): void
{
file_put_contents(
base_path("composer.json"),
json_encode(
$composerJson,
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES,
),
);
}
private function enable(string $path): void
{
$productionRepo = $this->productionRepository();
$composerJson = $this->getComposerJson();
$composerJson["repositories"] = array_filter(
$composerJson["repositories"],
fn($repo) => $repo["url"] !== $productionRepo["url"],
);
$composerJson["repositories"][] = $this->devRepository($path);
$this->replaceComposerJson($composerJson);
}
private function disable(string $path): void
{
$devRepo = $this->devRepository($path);
$composerJson = $this->getComposerJson();
$composerJson["repositories"] = array_filter(
$composerJson["repositories"],
fn($repo) => $repo["url"] !== $devRepo["url"],
);
$composerJson["repositories"][] = $this->productionRepository();
$this->replaceComposerJson($composerJson);
}
private function productionRepository(): array
{
return [
"type" => "vcs",
"url" =>
"ssh://git@code.radical-elements.com:2727/lucent/lucent-laravel.git",
];
}
private function devRepository(string $localPath): array
{
return [
"type" => "path",
"url" => $localPath,
"options" => [
"symlink" => true,
],
];
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php namespace Lucent\Core\Schema\Data;
class Field
{
public function __construct(
public string $id,
public string $label,
public string $type,
) {}
}
+15
View File
@@ -0,0 +1,15 @@
<?php namespace Lucent\Core\Schema\Data;
class Schema
{
/**
* @param string $id
* @param string $label
* @param Field[] $fields
*/
public function __construct(
public string $id,
public string $label,
public array $fields,
) {}
}
+9
View File
@@ -0,0 +1,9 @@
<?php namespace Lucent\Core\Schema;
class SchemaModule
{
public function __construct()
{
// Constructor logic here
}
}
+11 -15
View File
@@ -14,21 +14,14 @@ use function Lucent\Response\ok;
class HomeController extends Controller
{
public function __construct(
private readonly Svelte $svelte,
private readonly Svelte $svelte,
private readonly AccountService $accountService,
private readonly Query $query,
)
{
}
private readonly Query $query,
) {}
public function home(): View
{
return $this->svelte->render(
layout: "channel",
view: "homeIndex",
title: "Records",
);
return $this->svelte->render(view: "homeIndex", title: "Records");
}
public function records(Request $request): Response
@@ -38,10 +31,13 @@ class HomeController extends Controller
$sort = data_get($urlParams, "sort") ?? "-_sys.updatedAt";
$filter = data_get($urlParams, "filter") ?? [];
$arguments = array_merge([
"schema_in" => $this->accountService->currentReadableSchemas(),
"status_in" => ["draft", "published"]
], $filter);
$arguments = array_merge(
[
"schema_in" => $this->accountService->currentReadableSchemas(),
"status_in" => ["draft", "published"],
],
$filter,
);
$limit = 20;
+28 -31
View File
@@ -1,18 +1,13 @@
<?php
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Lucent\Account\AccountService;
use Lucent\Channel\ChannelService;
use Lucent\LucentException;
use Lucent\Setup\Data\SetupStep;
use Lucent\Setup\Data\SetupStepStatus;
use Lucent\Setup\Setup;
use Lucent\Setup\Step\ComposerStep;
use Lucent\Setup\Step\DatabaseSetupStep;
use Lucent\Setup\Step\IStep;
@@ -21,52 +16,54 @@ use Lucent\Setup\Step\LucentConfigStep;
use Lucent\Setup\Step\StorageLinkSetupStep;
use Lucent\Setup\Step\StorageSetupStep;
use Lucent\Svelte\Svelte;
use function Lucent\Response\fail;
use function Lucent\Response\ok;
class SetupController
{
public function __construct(
private readonly AccountService $accountService,
private readonly ChannelService $channelService,
private readonly Svelte $svelte,
)
{
}
private readonly Svelte $svelte,
) {}
public function setup(Request $request): View|RedirectResponse
{
$steps = array_reduce(
[
new ComposerStep(),
new LucentConfigStep(),
new LaravelEnvStep(),
new StorageSetupStep(),
new StorageLinkSetupStep(),
new DatabaseSetupStep(),
],
fn(array $carry, IStep $setupStep) => array_merge($carry, [
$setupStep(),
]),
[],
);
$allSuccess = array_reduce(
$steps,
fn(bool $carry, SetupStep $step) => !$carry
? false
: $step->status === SetupStepStatus::SUCCESS,
true,
);
$steps = array_reduce([
new ComposerStep,
new LucentConfigStep,
new LaravelEnvStep,
new StorageSetupStep,
new StorageLinkSetupStep,
new DatabaseSetupStep,
], fn(array $carry, IStep $setupStep) => array_merge($carry, [$setupStep()]), []);
$allSuccess = array_reduce($steps, fn(bool $carry, SetupStep $step) => !$carry ? false : $step->status === SetupStepStatus::SUCCESS, true);
if($allSuccess){
if ($allSuccess) {
if ($this->accountService->countUsers() > 0) {
return redirect($this->channelService->channel->lucentUrl . "/login");
return redirect(
$this->channelService->channel->lucentUrl . "/login",
);
}
}
return $this->svelte->render(
layout: "account",
view: "setup",
title: "Setup Lucent",
data: [
"steps" => $steps,
"allSuccess" => $allSuccess,
]
],
);
}
}
+42 -28
View File
@@ -15,9 +15,9 @@ use Lucent\Commands\LiveLink;
use Lucent\Commands\RebuildThumbnails;
use Lucent\Commands\RemoveOrphanEdges;
use Lucent\Commands\SetupDatabase;
use Lucent\Commands\DeveloperMode;
use Lucent\Commands\UpgradeFiles122;
use Lucent\File\FileService;
use Lucent\File\ImageService;
use Lucent\Query\DatabaseGraph\DatabaseGraph;
use Lucent\Query\DatabaseGraph\PgsqlDatabaseGraph;
use Lucent\Query\DatabaseGraph\SqliteDatabaseGraph;
@@ -34,11 +34,9 @@ class LucentServiceProvider extends ServiceProvider
});
$this->app->bind(ImageManager::class, function () {
return new ImageManager(['driver' => 'imagick']);
return new ImageManager(["driver" => "imagick"]);
});
$this->app->bind(DatabaseGraph::class, function () {
$dbConnection = config("lucent.database");
return match (config("database.connections.$dbConnection.driver")) {
@@ -46,9 +44,6 @@ class LucentServiceProvider extends ServiceProvider
"pgsql" => new PgsqlDatabaseGraph(),
};
});
}
/**
@@ -56,20 +51,29 @@ class LucentServiceProvider extends ServiceProvider
*/
public function boot(Router $router): void
{
$manifestPath = public_path('vendor/lucent/dist/manifest.json');
$manifestPath = public_path("vendor/lucent/dist/manifest.json");
$manifest = null;
if (file_exists($manifestPath)) {
$manifest = json_decode(file_get_contents(public_path('vendor/lucent/dist/manifest.json')), true);
$manifest = json_decode(
file_get_contents(
public_path("vendor/lucent/dist/manifest.json"),
),
true,
);
}
$router->aliasMiddleware(
"lucent.auth",
\Lucent\Http\Middleware\AuthMiddleware::class,
);
$router->aliasMiddleware(
"lucent.guest",
\Lucent\Http\Middleware\GuestMiddleware::class,
);
$router->aliasMiddleware('lucent.auth', \Lucent\Http\Middleware\AuthMiddleware::class);
$router->aliasMiddleware('lucent.guest', \Lucent\Http\Middleware\GuestMiddleware::class);
$this->loadViewsFrom(__DIR__ . '/../front/views', 'lucent');
$this->loadRoutesFrom(__DIR__ . '/Http/web.php');
$this->loadRoutesFrom(__DIR__ . '/Http/api.php');
$this->loadViewsFrom(__DIR__ . "/../front/views", "lucent");
$this->loadRoutesFrom(__DIR__ . "/Http/web.php");
$this->loadRoutesFrom(__DIR__ . "/Http/api.php");
if ($this->app->runningInConsole()) {
$this->commands([
@@ -81,22 +85,32 @@ class LucentServiceProvider extends ServiceProvider
GenerateCollectionSchema::class,
GenerateFileSchema::class,
UpgradeFiles122::class,
DeveloperMode::class,
]);
}
View::share('manifest', $manifest);
View::share('file', app()->make(FileService::class));
Blade::anonymousComponentPath(__DIR__ . '../front/views/components', "lucent");
$this->publishes([
__DIR__ . '/Config/main.php' => config_path('lucent.php'),
],"lucent-config");
$this->publishes([
__DIR__ . '/../front/dist' => public_path('vendor/lucent/dist'),
__DIR__ . '/../front/public' => public_path('vendor/lucent/public'),
], 'lucent');
View::share("manifest", $manifest);
View::share("file", app()->make(FileService::class));
Blade::anonymousComponentPath(
__DIR__ . "../front/views/components",
"lucent",
);
$this->publishes(
[
__DIR__ . "/Config/main.php" => config_path("lucent.php"),
],
"lucent-config",
);
$this->publishes(
[
__DIR__ . "/../front/dist" => public_path("vendor/lucent/dist"),
__DIR__ . "/../front/public" => public_path(
"vendor/lucent/public",
),
],
"lucent",
);
}
}
+23 -22
View File
@@ -7,42 +7,43 @@ use Illuminate\Contracts\View\View;
use Lucent\Account\AccountService;
use Lucent\Channel\ChannelService;
class Svelte
class Svelte
{
public function __construct(
public ChannelService $channelService,
public AccountService $accountService
)
{
}
function render(string $layout, string $view, string $title = "", mixed $data = []): View|Factory
{
public AccountService $accountService,
) {}
function render(
string $view,
string $title = "",
mixed $data = [],
): View|Factory {
$context = [];
$context["user"] = session('user');
$context["user"] = session("user");
$context["view"] = $view;
$context["layout"] = $layout;
$context["title"] = $title;
$context["data"] = $data;
$context["channel"] = $this->channelService->channel;
$context["readableSchemas"] = $this->accountService->currentReadableSchemas();
$context[
"readableSchemas"
] = $this->accountService->currentReadableSchemas();
$json = json_encode($context);
$divTag = sprintf('<div class="lucent-component" data-layout="%s"></div>', $layout);
$jsonTag = sprintf('<script type="application/json" id="json-%s">%s</script>', $layout, $json);
$divTag = '<div class="lucent-component"></div>';
$jsonTag = sprintf(
'<script type="application/json" id="json-data">%s</script>',
$json,
);
$svelte = $divTag . $jsonTag;
return view('lucent::svelte', [
'svelte' => $svelte,
'view' => $view,
'data' => $data,
'title' => $title,
'layout' => $layout,
'channel' => $this->channelService->channel,
return view("lucent::svelte", [
"svelte" => $svelte,
"view" => $view,
"data" => $data,
"title" => $title,
"channel" => $this->channelService->channel,
]);
}
}