Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab1517cc8f | |||
| 9f724a3243 | |||
| ae65ca47f6 | |||
| 74d2fcc4fa | |||
| 82174afdea | |||
| ffc39f078d | |||
| 7c4e19afbc | |||
| 7b10bfca1d | |||
| 0e5ac08641 | |||
| 1505aaa909 | |||
| d9e2c4954a | |||
| 97ad9de3d2 | |||
| 9e140be0ec | |||
| a737c2d571 | |||
| c43c29eb14 |
@@ -9,8 +9,8 @@ include_toc: true
|
||||
|
||||
### Requirements
|
||||
|
||||
- PHP 8.2
|
||||
- Laravel 10
|
||||
- PHP 8.3
|
||||
- Laravel 11
|
||||
- Postgres or Sqlite database
|
||||
- ImageMagick
|
||||
|
||||
@@ -82,7 +82,9 @@ return [
|
||||
### Database
|
||||
|
||||
The recommended database for small website is sqlite. But you can also use postresql
|
||||
Make sure to delete the existing migration scripts in your database/migrations folder.
|
||||
|
||||
> [!CAUTION]
|
||||
> Make sure to delete the existing migration scripts in your database/migrations folder.
|
||||
|
||||
Then run:
|
||||
|
||||
@@ -90,6 +92,26 @@ Then run:
|
||||
php artisan migrate
|
||||
```
|
||||
|
||||
### File Storage
|
||||
|
||||
You can use your local filesystem or s3 compatible storage. Lucent expects you to have a valid configuration inside ``config/filesystems.php``
|
||||
|
||||
example:
|
||||
|
||||
```php
|
||||
return [
|
||||
'disks' => [
|
||||
'lucent' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => env('APP_URL').'/storage',
|
||||
'visibility' => 'public',
|
||||
'throw' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### First user
|
||||
|
||||
To create your first user, head to your localhost:8000/lucent
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# Upgrade from 1.1.* to 1.2.0
|
||||
|
||||
## lucent.php config file
|
||||
|
||||
There is now an array of commands, accepting more than one.
|
||||
|
||||
from
|
||||
```php
|
||||
"generateCommand" => env("LUCENT_GENERATE_COMMAND", "generate:static"),
|
||||
```
|
||||
|
||||
to
|
||||
```php
|
||||
"commands" => [
|
||||
"generate:static" => "Build Website",
|
||||
],
|
||||
```
|
||||
## config/filesystems.php
|
||||
|
||||
Lucent has its own filesystem.
|
||||
|
||||
You should now add:
|
||||
|
||||
```
|
||||
'lucent' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => env('APP_URL').'/storage',
|
||||
'visibility' => 'public',
|
||||
'throw' => false,
|
||||
],
|
||||
```
|
||||
+3
-3
@@ -8,14 +8,14 @@
|
||||
"ext-zip": "*",
|
||||
"ext-sqlite3": "*",
|
||||
"ext-imagick": "*",
|
||||
"ext-pdo": "*",
|
||||
"php": "^8.3",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"intervention/image": "^2.7",
|
||||
"phpoption/phpoption": "^1.9",
|
||||
"spatie/image-optimizer": "^1.6",
|
||||
"staudenmeir/laravel-cte": "^1.0",
|
||||
"ext-pdo": "*",
|
||||
"mustache/mustache": "^2.14"
|
||||
"staudenmeir/laravel-cte": "^1.0"
|
||||
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.8",
|
||||
|
||||
+5
-11
@@ -24,7 +24,8 @@ There are 3 types of schemas
|
||||
- **fields**: The list of your fields. Look the field reference for more
|
||||
- **isEntry**: If this schema is important, it will show be visible on the main the sidebar. Default: false _optional_
|
||||
- **sortBy**: The default sorting in the content browser _optional_
|
||||
- **titleTemplate**: Mustache code to customize the preview field _optional_
|
||||
- **cardTitle**: Mustache code to customize the preview field _optional_
|
||||
- **cardImage**: Field name of image you want to use as a preview image _optional_
|
||||
- **revisions**: How many revisions are going to be kept for each record _optional_
|
||||
- **read**: Array of user groups that have read permissions _optional_
|
||||
- **write**: Array of user groups that have write permissions _optional_
|
||||
@@ -40,20 +41,12 @@ There are 3 types of schemas
|
||||
- **fields**: The list of your fields. Look the field reference for more
|
||||
- **isEntry**: If this schema is important, it will show be visible on the main the sidebar _optional_
|
||||
- **sortBy**: The default sorting in the content browser _optional_
|
||||
- **titleTemplate**: Mustache code to customize the preview field _optional_
|
||||
- **cardTitle**: Mustache code to customize the preview field _optional_
|
||||
- **revisions**: How many revisions are going to be kept for each record _optional_
|
||||
- **read**: Array of user groups that have read permissions _optional_
|
||||
- **write**: Array of user groups that have write permissions _optional_
|
||||
|
||||
|
||||
## Block Reference
|
||||
|
||||
- **name**: The ID of the collection. Camelcase and plural is the recommended format ex. blogPosts
|
||||
- **label**: The friendly name of the schema
|
||||
- **type**: The type of the collection. Should be "block"
|
||||
- **fields**: The list of your fields. Look the field reference for more
|
||||
|
||||
|
||||
A full Collection example without the fields:
|
||||
|
||||
```json
|
||||
@@ -74,7 +67,8 @@ A full Collection example without the fields:
|
||||
"SEO"
|
||||
],
|
||||
"sortBy": "-_sys.createdAt",
|
||||
"titleTemplate": "{{name}} {{slug}}",
|
||||
"schemaTitle": "{{name}} {{slug}}",
|
||||
"schemaImage": "cover",
|
||||
"revisions": 15,
|
||||
"read": [
|
||||
"admin",
|
||||
|
||||
Vendored
-198
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
+342
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"main.js": {
|
||||
"file": "assets/main-0h36XLim.js",
|
||||
"file": "assets/main-D9joEh0I.js",
|
||||
"name": "main",
|
||||
"src": "main.js",
|
||||
"isEntry": true,
|
||||
"css": [
|
||||
"assets/main-CaexgiEy.css"
|
||||
"assets/main-BnJW41Dx.css"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,25 @@
|
||||
<script>
|
||||
import {getContext, onMount} from "svelte";
|
||||
|
||||
import axios from "axios";
|
||||
|
||||
const channel = getContext("channel");
|
||||
export let title;
|
||||
export let command;
|
||||
$: date = "";
|
||||
$: logs = "";
|
||||
|
||||
let anchorEl;
|
||||
let inProgress = false;
|
||||
|
||||
function connect() {
|
||||
const eventSource = new EventSource(channel.lucentUrl + "/build-report-source");
|
||||
const eventSource = new EventSource(channel.lucentUrl + "/command-report-source/" + command.signature );
|
||||
|
||||
eventSource.onmessage = function (event) {
|
||||
inProgress = true;
|
||||
const data = JSON.parse(event.data);
|
||||
date = data.date;
|
||||
logs = data.logs;
|
||||
|
||||
anchorEl.scrollIntoView()
|
||||
}
|
||||
eventSource.onerror = (e) => {
|
||||
console.log(e)
|
||||
@@ -28,8 +31,7 @@
|
||||
function buildWebsite(e) {
|
||||
e.preventDefault();
|
||||
inProgress = true;
|
||||
|
||||
axios.post(channel.lucentUrl + "/build").then(response => {
|
||||
axios.post(channel.lucentUrl + "/command/" + command.signature).then(response => {
|
||||
connect()
|
||||
})
|
||||
|
||||
@@ -46,26 +48,36 @@
|
||||
|
||||
<h3 class="header-small mb-5">{title}</h3>
|
||||
|
||||
<button on:click={buildWebsite} class="button primary mb-3" disabled={inProgress}>Start Build
|
||||
<button on:click={buildWebsite} class="button primary mb-3" disabled={inProgress}>Start
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
{#if inProgress}
|
||||
<span class="badge text-bg-warning">
|
||||
Build in progress
|
||||
Action in progress
|
||||
</span>
|
||||
{/if}
|
||||
{#if !inProgress && logs}
|
||||
<span class="badge text-bg-info">
|
||||
Build completed
|
||||
Action completed
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<pre>{logs}</pre>
|
||||
<pre class="logs">{logs}
|
||||
<div bind:this={anchorEl}> </div>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.logs{
|
||||
max-height: 70vh;
|
||||
overflow: scroll;
|
||||
background: var(--p90);
|
||||
color: var(--p10);
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -113,8 +113,21 @@
|
||||
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12l4-4m-4 4 4 4"/>',
|
||||
viewBox: "0 0 24 24",
|
||||
},
|
||||
"list": {
|
||||
path: '<path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M9 8h10M9 12h10M9 16h10M4.99 8H5m-.02 4h.01m0 4H5"/>',
|
||||
viewBox: "0 0 24 24",
|
||||
},
|
||||
"ordered-list": {
|
||||
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6h8m-8 6h8m-8 6h8M4 16a2 2 0 1 1 3.321 1.5L4 20h5M4 5l2-1v6m-2 0h4"/>',
|
||||
viewBox: "0 0 24 24",
|
||||
},
|
||||
"italic": {
|
||||
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m8.874 19 6.143-14M6 19h6.33m-.66-14H18"/>',
|
||||
viewBox: "0 0 24 24",
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export let width = 16;
|
||||
export let height = 16;
|
||||
export let icon = "";
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
export let inModal;
|
||||
export let modalUrl;
|
||||
export let graph;
|
||||
|
||||
let filter = {
|
||||
label: "",
|
||||
operator: "",
|
||||
@@ -58,6 +57,7 @@
|
||||
const filterRecord = extractFilterRecord(graph, value);
|
||||
|
||||
function extractFilterRecord(graph, value) {
|
||||
|
||||
if (!filter.isReference) {
|
||||
return null;
|
||||
}
|
||||
@@ -82,7 +82,7 @@
|
||||
{#if filter.isReference && filterRecord}
|
||||
{filter.label} is {previewTitle(channel.schemas, filterRecord)}
|
||||
{:else}
|
||||
{filter.label} {operators.find((o) => o.name === filter.operator)?.symbol ?? ""} {value}
|
||||
{filter.label} {operators.find((o) => o.name === filter.operator)?.symbol ?? ""} {operators.find((o) => o.name === filter.operator)?.hasValue ? value : ""}
|
||||
{/if}
|
||||
|
||||
<button
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
export let inModal;
|
||||
export let modalUrl;
|
||||
|
||||
|
||||
let dropdown;
|
||||
let search = "";
|
||||
let systemFieldsFiltered = systemFields;
|
||||
@@ -70,6 +69,13 @@
|
||||
activeOperator = operators.find(o => o.name === "eq")
|
||||
}
|
||||
|
||||
function selectOperator(e, operator) {
|
||||
activeOperator = operator;
|
||||
if (!operator.hasValue) {
|
||||
applyFilter(e)
|
||||
}
|
||||
}
|
||||
|
||||
function applyFilter(e) {
|
||||
e.preventDefault();
|
||||
let filterPrefix = "";
|
||||
@@ -146,7 +152,7 @@
|
||||
<div class="selected-filter">field: {activeField.label}</div>
|
||||
|
||||
{#each activeOperators as operator}
|
||||
<button class="dropdown-item button" on:click={e => activeOperator = operator }>
|
||||
<button class="dropdown-item button" on:click={e => selectOperator(e,operator)}>
|
||||
{operator.label}
|
||||
</button>
|
||||
{/each}
|
||||
@@ -214,8 +220,8 @@
|
||||
required
|
||||
/>
|
||||
|
||||
<button class="button applied-filter">
|
||||
Submit
|
||||
<button class="button applied-filter">
|
||||
Submit
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
<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)
|
||||
</script>
|
||||
|
||||
|
||||
<div class="top-nav ">
|
||||
<a class="top-nav-item" href="{channel.lucentUrl}/members">Members</a>
|
||||
|
||||
{#if channel.generateCommand}
|
||||
<a href="{channel.lucentUrl}/build-report" class="top-nav-item">Build website</a>
|
||||
{#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>
|
||||
{/each}
|
||||
</Dropdown>
|
||||
|
||||
{/if}
|
||||
<!-- <div>-->
|
||||
<!-- <form method="GET">-->
|
||||
@@ -19,8 +27,8 @@
|
||||
<!-- class="form-control" required/>-->
|
||||
<!-- </form>-->
|
||||
<!-- </div>-->
|
||||
<a href="{channel.lucentUrl}/profile">
|
||||
<Avatar side="28" name={user.name}/>
|
||||
</a>
|
||||
<a href="{channel.lucentUrl}/profile">
|
||||
<Avatar side="28" name={user.name}/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -100,8 +100,8 @@
|
||||
tinymce.init({...config, ...additionalConfig});
|
||||
});
|
||||
|
||||
export function insertMedia(html){
|
||||
activeEditor.execCommand('InsertHTML', false, html);
|
||||
export function insertMedia(info){
|
||||
activeEditor.execCommand('InsertHTML', false, info.html);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
import {Editor} from '@tiptap/core'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Dropcursor from '@tiptap/extension-dropcursor'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import Heading from '@tiptap/extension-heading'
|
||||
import HardBreak from '@tiptap/extension-hard-break'
|
||||
import Blockquote from '@tiptap/extension-blockquote';
|
||||
import CodeBlock from '@tiptap/extension-code-block';
|
||||
import Bold from '@tiptap/extension-bold';
|
||||
import BulletList from '@tiptap/extension-bullet-list';
|
||||
import Code from '@tiptap/extension-code';
|
||||
@@ -19,6 +22,8 @@
|
||||
import TableCell from '@tiptap/extension-table-cell';
|
||||
import TableHeader from '@tiptap/extension-table-header';
|
||||
import Underline from '@tiptap/extension-underline';
|
||||
import Image from '@tiptap/extension-image';
|
||||
import Icon from "../common/Icon.svelte";
|
||||
|
||||
let element;
|
||||
let editor;
|
||||
@@ -32,19 +37,22 @@
|
||||
Paragraph,
|
||||
Text,
|
||||
Bold,
|
||||
ListItem,
|
||||
BulletList,
|
||||
Code,
|
||||
CodeBlock,
|
||||
History,
|
||||
Italic,
|
||||
ListItem,
|
||||
HardBreak,
|
||||
OrderedList,
|
||||
ListItem,
|
||||
Strike,
|
||||
Table,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
Underline,
|
||||
Dropcursor,
|
||||
Image,
|
||||
Heading.configure({
|
||||
levels: [1, 2, 3],
|
||||
}),
|
||||
@@ -56,7 +64,11 @@
|
||||
// force re-render so `editor.isActive` works as expected
|
||||
editor = editor;
|
||||
},
|
||||
onUpdate: ({editor}) => {
|
||||
value = editor.getHTML()
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
@@ -64,33 +76,99 @@
|
||||
editor.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
export function insertMedia(info){
|
||||
editor.chain().focus().setImage({ src: info.url }).run()
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if editor}
|
||||
<button
|
||||
on:click={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
||||
class:active={editor.isActive('heading', { level: 1 })}
|
||||
>
|
||||
H1
|
||||
</button>
|
||||
<button
|
||||
on:click={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
||||
class:active={editor.isActive('heading', { level: 2 })}
|
||||
>
|
||||
H2
|
||||
</button>
|
||||
<button
|
||||
on:click={() => editor.chain().focus().setParagraph().run()}
|
||||
class:active={editor.isActive('paragraph')}
|
||||
>
|
||||
P
|
||||
</button>
|
||||
<button
|
||||
on:click={() => editor.chain().focus().toggleBold().run()}
|
||||
class:active={editor.isActive('bold')}
|
||||
>
|
||||
Bold
|
||||
</button>
|
||||
<div class="editor-toolbar">
|
||||
<button
|
||||
class="button"
|
||||
on:click={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
||||
class:active={editor.isActive('heading', { level: 1 })}
|
||||
>
|
||||
H1
|
||||
</button>
|
||||
<button
|
||||
class="button"
|
||||
on:click={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
||||
class:active={editor.isActive('heading', { level: 2 })}
|
||||
>
|
||||
H2
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="button"
|
||||
on:click={() => editor.chain().focus().toggleBold().run()}
|
||||
class:active={editor.isActive('bold')}
|
||||
>
|
||||
B
|
||||
</button>
|
||||
<button
|
||||
class="button"
|
||||
on:click={() => editor.chain().focus().toggleItalic().run()}
|
||||
class:active={editor.isActive('italic')}
|
||||
>
|
||||
<em>IT</em>
|
||||
</button>
|
||||
<button
|
||||
class="button"
|
||||
on:click={() => editor.chain().focus().toggleUnderline().run()}
|
||||
class:active={editor.isActive('underline')}
|
||||
>
|
||||
<u>U</u>
|
||||
</button>
|
||||
<button
|
||||
class="button"
|
||||
on:click={() => editor.chain().focus().toggleStrike().run()}
|
||||
class:active={editor.isActive('strike')}
|
||||
>
|
||||
<s>S</s>
|
||||
</button>
|
||||
<button
|
||||
class="button"
|
||||
on:click={() => editor.commands.unsetAllMarks()}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
<button
|
||||
class="button"
|
||||
on:click={() => editor.chain().focus().toggleCode().run()}
|
||||
class:active={editor.isActive('code')}
|
||||
>
|
||||
Code
|
||||
</button>
|
||||
<button
|
||||
class="button"
|
||||
on:click={() => editor.chain().focus().toggleBulletList().run()}
|
||||
class:active={editor.isActive('bulletList')}
|
||||
>
|
||||
<Icon icon="list"></Icon>
|
||||
</button>
|
||||
<button
|
||||
class="button"
|
||||
on:click={() => editor.chain().focus().toggleOrderedList().run()}
|
||||
class:active={editor.isActive('orderedList')}
|
||||
>
|
||||
<Icon icon="ordered-list"></Icon>
|
||||
</button>
|
||||
<button
|
||||
class="button"
|
||||
on:click={() => editor.chain().focus().toggleBlockquote().run()}
|
||||
class:active={editor.isActive('blockquote')}
|
||||
>
|
||||
""
|
||||
</button>
|
||||
<button
|
||||
class="button"
|
||||
on:click={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||
class:active={editor.isActive('codeBlock')}
|
||||
>
|
||||
cb
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div bind:this={element} class="content"/>
|
||||
@@ -1,21 +1,53 @@
|
||||
<script>
|
||||
import {onMount} from "svelte";
|
||||
import {onDestroy, onMount} from "svelte";
|
||||
import Trix from "trix"
|
||||
import customcss from "./tinymce.css?inline";
|
||||
import "trix/dist/trix.css"
|
||||
|
||||
export let value = "";
|
||||
let textareaEl;
|
||||
let lastVal;
|
||||
let editorWrapper;
|
||||
let activeEditor;
|
||||
export let field;
|
||||
let editor;
|
||||
|
||||
|
||||
function updateValue(e) {
|
||||
value = e.target.value;
|
||||
}
|
||||
|
||||
export function insertMedia(info){
|
||||
if(info.record._file.width > 0){
|
||||
var attachment = new Trix.Attachment({ content: info.html })
|
||||
editor.editor.insertAttachment(attachment)
|
||||
}else{
|
||||
editor.editor.insertHTML(`<a href="${info.originalUrl}">${info.record._file.originalName}</a>`)
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
editor.addEventListener("trix-file-accept", (e) => {
|
||||
e.preventDefault();
|
||||
})
|
||||
})
|
||||
// onDestroy(() => {
|
||||
// editor.removeEventListener("trix-before-initialize")
|
||||
// })
|
||||
|
||||
|
||||
Trix.config.blockAttributes.default.breakOnReturn = false
|
||||
console.log(Trix.config)
|
||||
|
||||
</script>
|
||||
|
||||
<div bind:this={editorWrapper} class="tox-wrapper">
|
||||
<input bind:this={textareaEl} id="x" bind:value type="hidden">
|
||||
<trix-editor class="trix-content content" input="x"></trix-editor>
|
||||
<div class="tox-wrapper">
|
||||
<input id="x-{field.name}" {value} type="hidden">
|
||||
<trix-editor
|
||||
bind:this={editor}
|
||||
class=" content"
|
||||
input="x-{field.name}"
|
||||
role="textbox"
|
||||
tabindex="0"
|
||||
on:trix-change={updateValue}
|
||||
|
||||
></trix-editor>
|
||||
</div>
|
||||
|
||||
@@ -9,13 +9,17 @@
|
||||
export let value;
|
||||
export let isCreateMode;
|
||||
export let validationErrors;
|
||||
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||
|
||||
export let id;
|
||||
let wrapperDiv;
|
||||
let pickerInput;
|
||||
let pickerInstance;
|
||||
let flatpickrOptions = {
|
||||
appendTo: wrapperDiv,
|
||||
static: true,
|
||||
allowInput: true,
|
||||
altInput: true,
|
||||
altFormat: "Y-m-d H:i:S",
|
||||
@@ -40,7 +44,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="mb-0">
|
||||
<div class="mb-0" bind:this={wrapperDiv}>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
|
||||
function createInlineReference(e, schemaUId) {
|
||||
e.preventDefault();
|
||||
inLineCreateRecord = null;
|
||||
axios
|
||||
.get(channel.lucentUrl + "/records/newInline?schema=" + schemaUId)
|
||||
.then((response) => {
|
||||
@@ -59,27 +60,29 @@
|
||||
<div
|
||||
style="display: flex;align-items: center;gap:4px"
|
||||
>
|
||||
{#each schemas as schema}
|
||||
<Dropdown>
|
||||
<div slot="button" class:is-first={!recordId}>
|
||||
{schema.label}
|
||||
</div>
|
||||
<Dropdown>
|
||||
<div slot="button">New</div>
|
||||
{#each schemas as schema}
|
||||
<button
|
||||
class=" button"
|
||||
on:click={(e) =>
|
||||
createInlineReference(e, schema.name)}
|
||||
>Create New Record
|
||||
>{schema.label}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
</Dropdown>
|
||||
<Dropdown>
|
||||
<div slot="button"> <Icon icon="magnifying-glass"/></div>
|
||||
{#each schemas as schema}
|
||||
<button
|
||||
class="button"
|
||||
on:click={(e) => openBrowseModal(e, schema.name)}
|
||||
>
|
||||
<Icon icon="magnifying-glass"/>
|
||||
Search
|
||||
</button
|
||||
>
|
||||
</Dropdown>
|
||||
{/each}
|
||||
>{schema.label}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
</Dropdown>
|
||||
</div>
|
||||
{:else}
|
||||
<div style="display:flex;align-items: center;gap: 4px">
|
||||
@@ -100,9 +103,9 @@
|
||||
|
||||
<DialogRecord bind:this={dialogRecord}>
|
||||
{#if inLineCreateRecord}
|
||||
|
||||
<InlineEdit
|
||||
{...inLineCreateRecord}
|
||||
isCreateMode={true}
|
||||
on:cancel={(e) => (inLineCreateRecord = null)}
|
||||
on:inlinesaved={save}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import Tinymce from "../../libs/Tinymce.svelte";
|
||||
import RichEditorFiles from "./RichEditorFiles.svelte";
|
||||
import {getErrorMessage} from "./errorMessage";
|
||||
import Trix from "../../libs/Trix.svelte";
|
||||
|
||||
export let value;
|
||||
export let field;
|
||||
@@ -24,8 +25,8 @@
|
||||
|
||||
<div class="mb-0">
|
||||
|
||||
|
||||
<Tinymce bind:this={editor} bind:value {additionalConfig}/>
|
||||
<Trix {field} bind:this={editor} bind:value></Trix>
|
||||
<!-- <Tinymce bind:this={editor} bind:value {additionalConfig}/>-->
|
||||
{#if field.collections.length > 0}
|
||||
<RichEditorFiles
|
||||
bind:graph
|
||||
@@ -38,7 +39,7 @@
|
||||
|
||||
</RichEditorFiles>
|
||||
{/if}
|
||||
<!-- <TipTap bind:value />-->
|
||||
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="invalid-feedback d-block">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import {createEventDispatcher, getContext} from "svelte";
|
||||
import Preview from "../../files/Preview.svelte";
|
||||
import {previewTitle} from "./../Preview";
|
||||
import {htmlurl} from "../../files/imageserver.js"
|
||||
import {fileurl, htmlurl} from "../../files/imageserver.js"
|
||||
import Status from "./../Status.svelte";
|
||||
import Dropdown from "../../common/Dropdown.svelte";
|
||||
|
||||
@@ -25,8 +25,13 @@
|
||||
|
||||
function insert(e, preset) {
|
||||
e.preventDefault();
|
||||
let html = htmlurl(channel,record, preset)
|
||||
dispatch("editor-insert", html);
|
||||
let html = htmlurl(channel, record, preset)
|
||||
dispatch("editor-insert", {
|
||||
html: html,
|
||||
url: channel.filesUrl + `/templates/${preset}/${record._file.path}`,
|
||||
originalUrl: channel.filesUrl + "/" + record._file.path,
|
||||
record: record
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
Generated
+2562
-587
File diff suppressed because it is too large
Load Diff
+3
-20
@@ -11,40 +11,23 @@
|
||||
"@codemirror/lang-markdown": "^6.2.5",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
||||
"@tiptap/core": "^2.6.4",
|
||||
"@tiptap/extension-blockquote": "^2.6.4",
|
||||
"@tiptap/extension-bold": "^2.6.4",
|
||||
"@tiptap/extension-bullet-list": "^2.6.4",
|
||||
"@tiptap/extension-code": "^2.6.4",
|
||||
"@tiptap/extension-document": "^2.6.4",
|
||||
"@tiptap/extension-heading": "^2.6.4",
|
||||
"@tiptap/extension-history": "^2.6.4",
|
||||
"@tiptap/extension-italic": "^2.6.4",
|
||||
"@tiptap/extension-list-item": "^2.6.4",
|
||||
"@tiptap/extension-ordered-list": "^2.6.4",
|
||||
"@tiptap/extension-paragraph": "^2.6.4",
|
||||
"@tiptap/extension-strike": "^2.6.4",
|
||||
"@tiptap/extension-table": "^2.6.4",
|
||||
"@tiptap/extension-table-cell": "^2.6.4",
|
||||
"@tiptap/extension-table-header": "^2.6.4",
|
||||
"@tiptap/extension-table-row": "^2.6.4",
|
||||
"@tiptap/extension-text": "^2.6.4",
|
||||
"@tiptap/extension-underline": "^2.6.4",
|
||||
"@tiptap/pm": "^2.6.4",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
overflow: visible;
|
||||
position: absolute;
|
||||
border-radius: 12px;
|
||||
z-index: 20;
|
||||
z-index: 22;
|
||||
background: var(--p20);
|
||||
transition: 600ms;
|
||||
flex-grow: 1;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.record-edit {
|
||||
position: relative;
|
||||
|
||||
max-width: 900px;
|
||||
.invalid-feedback {
|
||||
color: var(--text-error);
|
||||
font-size: 15px;
|
||||
@@ -44,7 +44,7 @@
|
||||
margin: 6px 0;
|
||||
border-color: transparent;
|
||||
|
||||
.button {
|
||||
.button:not(.primary) {
|
||||
background: var(--p30);
|
||||
|
||||
&:hover {
|
||||
@@ -53,9 +53,10 @@
|
||||
}
|
||||
|
||||
dialog {
|
||||
.button {
|
||||
.button:not(.primary) {
|
||||
background: var(--p20);
|
||||
|
||||
|
||||
&:hover {
|
||||
background: var(--p30);
|
||||
}
|
||||
@@ -142,4 +143,5 @@
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
.tiptap {
|
||||
width: 100%;
|
||||
background: var(--p20);
|
||||
border: 1px solid var(--p50);
|
||||
border-radius: 0 0 5px 5px;
|
||||
padding: 15px 15px;
|
||||
font-size: 16px;
|
||||
|
||||
:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
|
||||
&:focus {
|
||||
background: var(--p10);
|
||||
|
||||
}
|
||||
|
||||
img {
|
||||
&.ProseMirror-selectednode {
|
||||
box-shadow: 0 0 1px 2px var(--p70);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.editor-field {
|
||||
.editor-toolbar {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
background: var(--p30);
|
||||
border-radius: 5px 5px 0 0;
|
||||
padding: 5px 7px;
|
||||
|
||||
.button:not(.primary) {
|
||||
font-weight: 700;
|
||||
|
||||
&.active {
|
||||
background: var(--p40);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
.tiptap {
|
||||
li > p {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
trix-editor {
|
||||
background: var(--p20)!important;
|
||||
border: 1px solid var(--p50)!important;
|
||||
border-radius: 0 0 5px 5px!important;
|
||||
padding: 15px 15px!important;
|
||||
& > div {
|
||||
margin-bottom: 14px;
|
||||
font-size: 16px;
|
||||
line-height: 23px;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: var(--p10)!important;
|
||||
|
||||
}
|
||||
figure.attachment{
|
||||
display: flex!important;
|
||||
flex-direction: column!important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.attachment {
|
||||
background: var(--p20);
|
||||
padding: 12px 0;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
[data-trix-mutable].attachment img {
|
||||
box-shadow: 0 0 1px 2px var(--p70) !important;
|
||||
|
||||
}
|
||||
|
||||
.trix-button--remove {
|
||||
box-shadow: none !important;
|
||||
border: 2px solid var(--p40) !important;
|
||||
}
|
||||
|
||||
.trix-button--remove:hover {
|
||||
border: 2px solid var(--p40);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--p80);
|
||||
}
|
||||
}
|
||||
|
||||
trix-toolbar {
|
||||
.trix-button-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.trix-button-group {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
display: flex !important;
|
||||
gap: 4px;
|
||||
}
|
||||
.trix-button-group--history-tools,.trix-button-group--file-tools
|
||||
{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.trix-button {
|
||||
border-radius: 6px !important;
|
||||
background: var(--p30) !important;
|
||||
padding: 14px 22px !important;
|
||||
margin: 0 !important;
|
||||
cursor: pointer;
|
||||
border: 0px solid var(--p30) !important;
|
||||
font-size: 14px !important;
|
||||
min-height: 27px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 4px;
|
||||
color: var(--text) !important;
|
||||
|
||||
&:before{
|
||||
background-size: 22px!important;
|
||||
}
|
||||
|
||||
&:hover{
|
||||
background: var(--p40) !important;
|
||||
}
|
||||
&.trix-active{
|
||||
background: var(--p50) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,14 @@
|
||||
align-items: center;
|
||||
background: var(--p30);
|
||||
font-size: 16px;
|
||||
padding: 3px 12px 6px;
|
||||
padding: 3px 12px 3px;
|
||||
color: var(--text);
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
&:focus{
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
background: var(--p40);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
margin: 20px 0 20px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
.tab{
|
||||
list-style: none;
|
||||
|
||||
|
||||
@@ -12,6 +12,15 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
h1{
|
||||
font-size: 24px;
|
||||
line-height: 34px;
|
||||
}
|
||||
|
||||
h2{
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0 0 0 16px;
|
||||
@@ -31,6 +40,54 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
code{
|
||||
background: var(--p30);
|
||||
padding: 0 6px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
img{
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
blockquote{
|
||||
border:1px solid var(--p30);
|
||||
border-radius: 12px;
|
||||
padding: 12px 40px;
|
||||
position: relative;
|
||||
|
||||
&::before{
|
||||
content: "\201C";
|
||||
color: var(--p60);
|
||||
font-size:4em;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
&::after{
|
||||
content: '';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--grey-light);
|
||||
border-radius: 0.5rem;
|
||||
color: var(--white);
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
margin: 1.5rem 0;
|
||||
padding: 0.75rem 1rem;
|
||||
|
||||
code {
|
||||
background: none;
|
||||
color: inherit;
|
||||
font-size: 0.8rem;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.lx-small-text {
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
@import "./table";
|
||||
@import "./avatar";
|
||||
@import "./codemirror";
|
||||
@import "./rich";
|
||||
@import "./layout";
|
||||
@import "./wrappers";
|
||||
@import "./toolbar";
|
||||
@@ -104,3 +105,6 @@ a {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.flatpickr-wrapper {
|
||||
display: block!important;
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
<h2 class="mb-5">Enter Lucent</h2>
|
||||
|
||||
<form hx-post="/lucent/login" >
|
||||
@csrf
|
||||
<p>Submit your email address and you will receive a <b>login link</b> to your email</p>
|
||||
<p>Don't forget to check your spam folder</p>
|
||||
<div class="mt-5 mb-3">
|
||||
|
||||
+15
-3
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Lucent\Channel;
|
||||
|
||||
use Lucent\Channel\Data\UserCommand;
|
||||
use Lucent\Primitive\Collection;
|
||||
use Lucent\Schema\Schema;
|
||||
|
||||
@@ -13,20 +14,31 @@ final class Channel
|
||||
|
||||
/**
|
||||
* @param Collection<Schema> $schemas
|
||||
* @param Collection<UserCommand> $commands
|
||||
*/
|
||||
function __construct(
|
||||
public string $name,
|
||||
public string $url,
|
||||
public string $previewTarget,
|
||||
public string $generateCommand,
|
||||
public Collection $commands,
|
||||
public Collection $schemas,
|
||||
public array $imageFilters,
|
||||
public array $roles,
|
||||
)
|
||||
{
|
||||
$this->lucentUrl = $url . "/lucent";
|
||||
$this->filesUrl = $url . "/storage";
|
||||
$this->previewTargetUrl = $url . "/". $previewTarget;
|
||||
$this->filesUrl = $this->makeFilesUrl();
|
||||
$this->previewTargetUrl = $url . "/" . $previewTarget;
|
||||
}
|
||||
|
||||
|
||||
private function makeFilesUrl(): string
|
||||
{
|
||||
return match (config("filesystems.disks.lucent.driver")) {
|
||||
"s3" => config("filesystems.disks.lucent.endpoint") . "/" . config("filesystems.disks.lucent.bucket"),
|
||||
"local" => $this->url . "/storage" . config("filesystems.disks.lucent.endpoint"),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Lucent\Channel;
|
||||
|
||||
|
||||
use Lucent\Channel\Data\UserCommand;
|
||||
use Lucent\Primitive\Collection;
|
||||
use Lucent\Schema\Schema;
|
||||
use Lucent\Schema\SchemaService;
|
||||
@@ -13,7 +14,7 @@ final class ChannelService
|
||||
public Channel $channel;
|
||||
|
||||
private function __construct(
|
||||
public SchemaService $schemaService
|
||||
public SchemaService $schemaService,
|
||||
)
|
||||
{
|
||||
|
||||
@@ -29,11 +30,16 @@ final class ChannelService
|
||||
$schemaService = new SchemaService();
|
||||
$schemasCollection = (new Collection($schemasArray["schemas"] ?? []))->map([$schemaService, 'fromArray']);
|
||||
|
||||
$userCommands = [];
|
||||
foreach (config("lucent.commands") ?? [] as $signature => $desc) {
|
||||
$userCommands[] = new UserCommand($desc, $signature);
|
||||
}
|
||||
|
||||
$channel = new Channel(
|
||||
name: config("lucent.name") ?? "",
|
||||
url: rtrim(config("lucent.url") ?? "", "/"),
|
||||
previewTarget: rtrim(config("lucent.previewTarget") ?? "", "/"),
|
||||
generateCommand: config("lucent.generateCommand") ?? "",
|
||||
commands: Collection::make($userCommands),
|
||||
schemas: $schemasCollection,
|
||||
imageFilters: config("lucent.imageFilters") ?? [],
|
||||
roles: $schemasArray["roles"] ?? []
|
||||
@@ -63,7 +69,7 @@ final class ChannelService
|
||||
*/
|
||||
public function schemasReadableByRoles(array $roles): array
|
||||
{
|
||||
$schemasAllRead = $this->channel->schemas->filter(fn(Schema $schema) => empty($schema->read))->values()->pluck("name");
|
||||
$schemasAllRead = $this->channel->schemas->filter(fn(Schema $schema) => empty($schema->read))->values()->pluck("name");
|
||||
$schemasCanRead = $this->channel->schemas->filter(fn(Schema $schema) => count(array_intersect($schema->read ?? [], $roles)) > 0)->values()->pluck("name");
|
||||
$schemasCanWrite = $this->channel->schemas->filter(fn(Schema $schema) => count(array_intersect($schema->write ?? [], $roles)) > 0)->values()->pluck("name");
|
||||
return $schemasAllRead->merge($schemasCanRead)->merge($schemasCanWrite)->unique()->values()->toArray();
|
||||
@@ -76,8 +82,8 @@ final class ChannelService
|
||||
*/
|
||||
public function schemasWritableByRoles(array $roles): array
|
||||
{
|
||||
$schemasAllRead = $this->channel->schemas->filter(fn(Schema $schema) => empty($schema->write ?? []))->values()->pluck("name");
|
||||
$schemasCanWrite = $this->channel->schemas->filter(fn(Schema $schema) => count(array_intersect($schema->write ?? [], $roles)) > 0)->values()->pluck("name");
|
||||
$schemasAllRead = $this->channel->schemas->filter(fn(Schema $schema) => empty($schema->write ?? []))->values()->pluck("name");
|
||||
$schemasCanWrite = $this->channel->schemas->filter(fn(Schema $schema) => count(array_intersect($schema->write ?? [], $roles)) > 0)->values()->pluck("name");
|
||||
return $schemasAllRead->merge($schemasCanWrite)->unique()->values()->toArray();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Channel\Data;
|
||||
|
||||
class UserCommand
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $signature,
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Command;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Lucent\Command\Data\CommandLogItem;
|
||||
|
||||
class CommandRepo
|
||||
{
|
||||
public function findBySignature($signature): ?CommandLogItem
|
||||
{
|
||||
|
||||
$row = DB::table("command_logs")->where("signature", $signature)->first();
|
||||
if (empty($row)) {
|
||||
return null;
|
||||
}
|
||||
return CommandLogItem::fromDB($row);
|
||||
}
|
||||
|
||||
|
||||
public function upsertCommand(CommandLogItem $commandLogItem): void
|
||||
{
|
||||
$foundCommandLogItem = $this->findBySignature($commandLogItem->signature);
|
||||
if (empty($foundCommandLogItem)) {
|
||||
DB::table("command_logs")->insert(toArray($commandLogItem));
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table("command_logs")->where("signature", $commandLogItem->signature)->update(toArray($commandLogItem));
|
||||
}
|
||||
|
||||
public function appendToLogs(string $signature, string $line): void
|
||||
{
|
||||
$res = DB::update(
|
||||
'update command_logs set logs = logs || ? where signature = ?',
|
||||
[$line, $signature]
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Command;
|
||||
|
||||
use Lucent\Command\Data\CommandLogItem;
|
||||
use Lucent\Id\Id;
|
||||
use Lucent\LucentException;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
|
||||
|
||||
class CommandService
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
private CommandRepo $commandRepo,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function run(string $signature): CommandLogItem
|
||||
{
|
||||
$commandLogItem = $this->commandRepo->findBySignature($signature);
|
||||
|
||||
if (empty($commandLogItem)) {
|
||||
$commandLogItem = new CommandLogItem(
|
||||
id: Id::new(),
|
||||
signature: $signature,
|
||||
pid: null,
|
||||
logs: ""
|
||||
);
|
||||
} elseif ($this->commandIsRunning($commandLogItem->pid)) {
|
||||
throw new LucentException('Command is already running');
|
||||
}
|
||||
|
||||
$commandLogItem->pid = $this->runCommand($signature);
|
||||
$commandLogItem->logs = "";
|
||||
$this->commandRepo->upsertCommand($commandLogItem);
|
||||
return $commandLogItem;
|
||||
}
|
||||
|
||||
public function logWriter(string $signature): callable
|
||||
{
|
||||
return function (string $line) use($signature) {
|
||||
$this->commandRepo->appendToLogs($signature, $line.PHP_EOL);
|
||||
};
|
||||
}
|
||||
|
||||
private function runCommand(string $signature): int
|
||||
{
|
||||
$phpBinaryFinder = new PhpExecutableFinder();
|
||||
$phpBinaryPath = $phpBinaryFinder->find();
|
||||
$pid = (int)shell_exec("cd " . base_path() . " && $phpBinaryPath artisan {$signature} > /dev/null 2>&1 & echo $!");
|
||||
return $pid;
|
||||
}
|
||||
|
||||
public function commandIsRunning(int $pid): bool
|
||||
{
|
||||
return file_exists("/proc/$pid");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Command\Data;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class CommandLogItem
|
||||
{
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public string $signature,
|
||||
public ?int $pid,
|
||||
public string $logs,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public static function fromDB(stdClass $data): self
|
||||
{
|
||||
return new self(
|
||||
id: $data->id,
|
||||
signature: $data->signature,
|
||||
pid: $data->pid,
|
||||
logs: $data->logs,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,19 +16,13 @@ class CompileSchemas extends Command
|
||||
protected $description = 'Compiles schemas';
|
||||
|
||||
|
||||
public function __construct(
|
||||
public SchemaService $schemaService
|
||||
)
|
||||
public function handle(SchemaService $schemaService)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$configDir = base_path(config('lucent.schemas_path'));
|
||||
$schemasDirIterator = new DirectoryIterator($configDir);
|
||||
$schemas = [];
|
||||
|
||||
foreach ($schemasDirIterator as $file) {
|
||||
if ($file->getExtension() !== "json") {
|
||||
continue;
|
||||
@@ -46,7 +40,7 @@ class CompileSchemas extends Command
|
||||
|
||||
$schemas = collect($schemas)->sortBy("label")->values();
|
||||
$roles = $schemas
|
||||
->map([$this->schemaService, 'fromArray'])
|
||||
->map([$schemaService, 'fromArray'])
|
||||
->whereIn("type", [Type::COLLECTION, Type::FILES])
|
||||
->reduce(fn($carry, Schema $schema) => array_merge(
|
||||
$carry,
|
||||
|
||||
+40
-6
@@ -1,15 +1,49 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"env" => env("LUCENT_ENV", "production"),
|
||||
"schemas_path" => env("LUCENT_SCHEMAS_PATH", "app/Lucent"),
|
||||
"database" => env('LUCENT_DB_CONNECTION', env('DB_CONNECTION',"sqlite")),
|
||||
"name" => env("LUCENT_NAME", "Lucent"),
|
||||
"env" => env("LUCENT_ENV", "production"),
|
||||
"schemas_path" => env("LUCENT_SCHEMAS_PATH", "app/Lucent"),
|
||||
"database" => env('LUCENT_DB_CONNECTION', env('DB_CONNECTION', "sqlite")),
|
||||
"name" => env("LUCENT_NAME", "Lucent"),
|
||||
"url" => env("LUCENT_URL", env('APP_URL')),
|
||||
"previewTarget" => env("LUCENT_PREVIEW_TARGET", "previewTarget"),
|
||||
"generateCommand" => env("LUCENT_GENERATE_COMMAND", "generate:static"),
|
||||
"previewTarget" => env("LUCENT_PREVIEW_TARGET", "previewTarget"),
|
||||
/*
|
||||
* Make available laravel artisan commands for admin users
|
||||
* example:
|
||||
* [
|
||||
* "command1:signature" => "Description 1"
|
||||
* "command2:signature" => "Description 2"
|
||||
* ]
|
||||
*
|
||||
* */
|
||||
"commands" => [],
|
||||
/*
|
||||
* Image filter will be available both for rich editor fields
|
||||
* and throughout your application
|
||||
*
|
||||
* example:
|
||||
* [
|
||||
* "filterName" => Filter::class
|
||||
* ]
|
||||
*
|
||||
* */
|
||||
"imageFilters" => [],
|
||||
"canInvite" => ["admin"],
|
||||
"canBuild" => ["admin"],
|
||||
"systemUserId" => "",
|
||||
"schemaFields" => [
|
||||
\Lucent\Schema\Ui\Checkbox::class,
|
||||
\Lucent\Schema\Ui\Color::class,
|
||||
\Lucent\Schema\Ui\Date::class,
|
||||
\Lucent\Schema\Ui\Datetime::class,
|
||||
\Lucent\Schema\Ui\File::class,
|
||||
\Lucent\Schema\Ui\Json::class,
|
||||
\Lucent\Schema\Ui\Markdown::class,
|
||||
\Lucent\Schema\Ui\Number::class,
|
||||
\Lucent\Schema\Ui\Reference::class,
|
||||
\Lucent\Schema\Ui\Rich::class,
|
||||
\Lucent\Schema\Ui\Slug::class,
|
||||
\Lucent\Schema\Ui\Text::class,
|
||||
\Lucent\Schema\Ui\Textarea::class
|
||||
]
|
||||
];
|
||||
|
||||
+3
@@ -5,6 +5,9 @@ use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
|
||||
protected $connection = 'lucentdb';
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
+2
@@ -6,6 +6,8 @@ use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'lucentdb';
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
+2
@@ -5,6 +5,8 @@ use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
|
||||
protected $connection = 'lucentdb';
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
+2
@@ -5,6 +5,8 @@ use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
|
||||
protected $connection = 'lucentdb';
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
+2
@@ -5,6 +5,8 @@ use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
|
||||
protected $connection = 'lucentdb';
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
+5
-2
@@ -5,6 +5,9 @@ use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
|
||||
protected $connection = 'lucentdb';
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
@@ -14,7 +17,7 @@ return new class extends Migration {
|
||||
{
|
||||
Schema::table('records', function (Blueprint $table) {
|
||||
$table->dropIndex(['schema', 'status']);
|
||||
$table->index(['schema', '_sys->updatedAt','status']);
|
||||
$table->index(['schema', '_sys->updatedAt', 'status']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,7 +30,7 @@ return new class extends Migration {
|
||||
{
|
||||
Schema::table('records', function (Blueprint $table) {
|
||||
|
||||
$table->dropIndex(['schema', '_sys->updatedAt','status']);
|
||||
$table->dropIndex(['schema', '_sys->updatedAt', 'status']);
|
||||
$table->index(['schema', 'status']);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
|
||||
protected $connection = 'lucentdb';
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('command_logs', function (Blueprint $table) {
|
||||
$table->uuid('id')->primary();
|
||||
$table->string('signature');
|
||||
$table->integer('pid')->nullable();
|
||||
$table->text('logs');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('command_logs');
|
||||
}
|
||||
};
|
||||
+10
-11
@@ -14,7 +14,6 @@ use Lucent\LucentException;
|
||||
use Lucent\Record\FileData as RecordFile;
|
||||
use Lucent\Record\QueryRecord;
|
||||
use Lucent\Schema\FilesSchema;
|
||||
use Lucent\Schema\Schema;
|
||||
use Spatie\ImageOptimizer\OptimizerChainFactory;
|
||||
|
||||
class FileService
|
||||
@@ -28,7 +27,7 @@ class FileService
|
||||
|
||||
public function getPath(QueryRecord $file): string
|
||||
{
|
||||
return $this->channelService->channel->url . "/storage/" . $file->_file->path;
|
||||
return $this->channelService->channel->filesUrl . "/" . $file->_file->path;
|
||||
}
|
||||
|
||||
public function createFromUrl(FilesSchema $schema, string $url): FileUploadResult
|
||||
@@ -111,15 +110,16 @@ class FileService
|
||||
|
||||
public function loadDisk(): Filesystem
|
||||
{
|
||||
return Storage::disk('lucent');
|
||||
return Storage::build([
|
||||
'driver' => 'local',
|
||||
'driver' => 'lucent',
|
||||
// 'key' => config("filesystems.disks.s3.key"),
|
||||
// 'secret' => config("filesystems.disks.s3.secret"),
|
||||
// 'region' => config("filesystems.disks.s3.region"),
|
||||
// 'bucket' => config("filesystems.disks.s3.bucket"),
|
||||
// // 'url' => $schema->objectStorageUrl,
|
||||
// 'endpoint' => $schema->objectStorageEndpoint,
|
||||
'use_path_style_endpoint' => false,
|
||||
// 'use_path_style_endpoint' => false,
|
||||
'visibility' => 'public', // now managed by aws policy
|
||||
'root' => storage_path('app/public'),
|
||||
'throw' => true,
|
||||
@@ -140,12 +140,10 @@ class FileService
|
||||
|
||||
private function createThumbnail(Filesystem $disk, string $schemaPath, string $filename, UploadedFile $file): void
|
||||
{
|
||||
|
||||
|
||||
$thumbDir = storage_path("app/public/thumbs/" . $schemaPath . "/");
|
||||
if (!file_exists($thumbDir)) {
|
||||
make_dir_r($thumbDir);
|
||||
}
|
||||
$thumbDir = "thumbs/" . $schemaPath . "/";
|
||||
// if (!file_exists($thumbDir)) {
|
||||
// make_dir_r($thumbDir);
|
||||
// }
|
||||
|
||||
try {
|
||||
ImageManagerStatic::configure(['driver' => 'imagick']);
|
||||
@@ -157,7 +155,8 @@ class FileService
|
||||
|
||||
$image->fit(300, 300);
|
||||
try {
|
||||
$image->encode('webp', 75)->save($thumbDir . $filename);
|
||||
$this->loadDisk()->put($thumbDir . $filename, $image->encode('webp', 75));
|
||||
// $image->encode('webp', 75)->save($thumbDir . $filename);
|
||||
} catch (Exception $e) {
|
||||
logger($e->getMessage());
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class ImageService
|
||||
|
||||
public function __construct(
|
||||
public ImageManager $imageManager,
|
||||
public FileService $fileService,
|
||||
public ChannelService $channelService,
|
||||
public Logger $logger
|
||||
)
|
||||
@@ -50,28 +51,18 @@ class ImageService
|
||||
|
||||
private function createTemplate(string $originalPath, string $template): string
|
||||
{
|
||||
$originalFilePath = public_path("storage/" . $originalPath);
|
||||
$templateUri = "/templates/" . $template . "/" . $originalPath;
|
||||
$templateFilePath = public_path("storage/" . $templateUri);
|
||||
if (!file_exists($originalFilePath)) {
|
||||
return $this->notFoundImage;
|
||||
}
|
||||
|
||||
if (!file_exists(pathinfo($templateFilePath, PATHINFO_DIRNAME))) {
|
||||
$this->make_dir(pathinfo($templateFilePath, PATHINFO_DIRNAME));
|
||||
}
|
||||
|
||||
try {
|
||||
$image = $this->imageManager->make($originalFilePath);
|
||||
$image = $this->imageManager->make( $this->fileService->loadDisk()->get($originalPath));
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
return $this->notFoundImage;
|
||||
return $originalPath;
|
||||
}
|
||||
|
||||
$image = $image->filter(new $this->channelService->channel->imageFilters[$template]);
|
||||
try {
|
||||
$image = $this->imageManager->make((string)$image->encode('webp', 75));
|
||||
$image->save($templateFilePath);
|
||||
$templateUri = "/templates/" . $template . "/" . $originalPath;
|
||||
$this->fileService->loadDisk()->put($templateUri, $image->encode('webp', 75));
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
return $this->notFoundImage;
|
||||
@@ -80,10 +71,4 @@ class ImageService
|
||||
return $templateUri;
|
||||
}
|
||||
|
||||
private function make_dir(string $path): void
|
||||
{
|
||||
is_dir($path) || mkdir($path, 0777, true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -5,64 +5,62 @@ namespace Lucent\Http\Controller;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Lucent\Channel\ChannelService;
|
||||
use Lucent\Command\CommandRepo;
|
||||
use Lucent\Command\CommandService;
|
||||
use Lucent\Svelte\Svelte;
|
||||
use function Lucent\Response\ok;
|
||||
|
||||
class BuildController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
public readonly Svelte $svelte,
|
||||
public readonly ChannelService $channelService,
|
||||
private readonly Svelte $svelte,
|
||||
private readonly ChannelService $channelService,
|
||||
private readonly CommandService $commandService,
|
||||
private readonly CommandRepo $commandRepo,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function build()
|
||||
public function build(Request $request)
|
||||
{
|
||||
$commandSignature = $request->route("signature");
|
||||
$this->commandService->run($commandSignature);
|
||||
|
||||
$buildLogFile = storage_path("lucent/build.log");
|
||||
if(file_exists($buildLogFile)){
|
||||
unlink($buildLogFile);
|
||||
}
|
||||
|
||||
exec("cd " . base_path() . " && php8.3 artisan {$this->channelService->channel->generateCommand} > " . $buildLogFile . " 2>&1 & echo $!", $op);
|
||||
$pid = (int)$op[0];
|
||||
return redirect($this->channelService->channel->lucentUrl . "/build-report");
|
||||
}
|
||||
|
||||
public function report(): View
|
||||
public function report(Request $request): View
|
||||
{
|
||||
$commandSignature = $request->route("signature");
|
||||
$command = $this->channelService->channel->commands->firstWhere("signature", $commandSignature);
|
||||
|
||||
return $this->svelte->render(
|
||||
layout: "channel",
|
||||
view: "buildReport",
|
||||
title: "Build Report",
|
||||
title: $command->name,
|
||||
data: [
|
||||
"command" => $command,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function reportSource()
|
||||
public function reportSource(Request $request)
|
||||
{
|
||||
return response()->stream(function () {
|
||||
$commandSignature = $request->route("signature");
|
||||
return response()->stream(function () use ($commandSignature) {
|
||||
while (true) {
|
||||
// sleep(1); // 50ms
|
||||
|
||||
$commandLogItem = $this->commandRepo->findBySignature($commandSignature);
|
||||
$data["date"] = date("Y-m-d H:i:s");
|
||||
$data["logs"] = file_get_contents(storage_path("lucent/build.log"));
|
||||
$data["logs"] = $commandLogItem->logs ?? "";
|
||||
// $lines = explode("\n",$data["logs"]);
|
||||
|
||||
echo 'data: ' .json_encode($data);
|
||||
echo 'data: ' . json_encode($data);
|
||||
echo "\n\n";
|
||||
|
||||
ob_flush();
|
||||
flush();
|
||||
|
||||
if(str_contains($data["logs"],"Finito")){
|
||||
break;
|
||||
}
|
||||
|
||||
if(str_contains($data["logs"],"Exception")){
|
||||
logger($this->commandService->commandIsRunning($commandLogItem->pid));
|
||||
if (!$this->commandService->commandIsRunning($commandLogItem->pid)) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -79,5 +77,4 @@ class BuildController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use Lucent\Account\AccountService;
|
||||
use Lucent\Account\AuthService;
|
||||
use Lucent\Channel\ChannelService;
|
||||
use Lucent\LucentException;
|
||||
use Lucent\Query\Operator;
|
||||
use Lucent\Query\Operator\OperatorRegistry;
|
||||
use Lucent\Query\Query;
|
||||
use Lucent\Record\InputData\EdgeInputData;
|
||||
use Lucent\Record\InputData\RecordInputData;
|
||||
@@ -25,13 +25,14 @@ use function Lucent\Response\ok;
|
||||
class RecordController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly RecordService $recordService,
|
||||
private readonly AccountService $accountService,
|
||||
private readonly AuthService $authService,
|
||||
private readonly ChannelService $channelService,
|
||||
private readonly Svelte $svelte,
|
||||
private readonly Query $query,
|
||||
private readonly Manager $recordManager
|
||||
private readonly RecordService $recordService,
|
||||
private readonly AccountService $accountService,
|
||||
private readonly AuthService $authService,
|
||||
private readonly ChannelService $channelService,
|
||||
private readonly Svelte $svelte,
|
||||
private readonly Query $query,
|
||||
private readonly Manager $recordManager,
|
||||
private readonly OperatorRegistry $operatorRegistry
|
||||
)
|
||||
{
|
||||
}
|
||||
@@ -79,7 +80,6 @@ class RecordController extends Controller
|
||||
|
||||
$records = $graph->getRootRecords()->toArray();
|
||||
|
||||
|
||||
$data = [
|
||||
"schemas" => $this->channelService->channel->schemas,
|
||||
"schema" => $schema,
|
||||
@@ -87,7 +87,7 @@ class RecordController extends Controller
|
||||
"records" => $records,
|
||||
"graph" => toArray($graph),
|
||||
"systemFields" => array_values(System::list()),
|
||||
"operators" => array_values(Operator::list()),
|
||||
"operators" => $this->operatorRegistry->all(),
|
||||
"sortParam" => $sort,
|
||||
"sortField" => $schema->fields->merge(array_values(System::list()))->firstWhere(fn($field) => $field->name === $sort || "-" . $field->name === $sort || "data." . $field->name === $sort || "-data." . $field->name === $sort),
|
||||
"limit" => $limit,
|
||||
@@ -165,7 +165,7 @@ class RecordController extends Controller
|
||||
|
||||
$schema = $this->channelService->channel->schemas->where("name", $request->input("schema"))->first();
|
||||
$recordHistory = $this->recordManager->fromSession($request->session())->getRecords();
|
||||
$record = $this->recordService->createEmpty($schema, $this->authService->currentUserId());
|
||||
$record = $this->recordService->createEmpty($schema);
|
||||
$queryRecord = QueryRecord::fromRecord($record);
|
||||
return $this->svelte->render(
|
||||
layout: "channel",
|
||||
@@ -200,6 +200,7 @@ class RecordController extends Controller
|
||||
"schema" => $schema,
|
||||
"record" => $queryRecord,
|
||||
"isCreateMode" => true,
|
||||
"isWritable" => in_array($record->schema, $this->accountService->currentWritableSchemas())
|
||||
];
|
||||
}
|
||||
|
||||
@@ -302,7 +303,7 @@ class RecordController extends Controller
|
||||
id: $request->input("record.id"),
|
||||
data: $request->input("record.data"),
|
||||
status: Status::from($request->input("record.status")),
|
||||
edges: array_map(EdgeInputData::fromArray(...), $request->input("edges") ?? []),
|
||||
edges: array_map(EdgeInputData::fromArray(...), $request->input("edges") ?? []),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -33,9 +33,9 @@ Route::group([
|
||||
Route::get('/profile', [AccountController::class, 'profile']);
|
||||
Route::post('/account/update-name', [AccountController::class, 'updateName']);
|
||||
Route::post('/account/update-email', [AccountController::class, 'updateEmail']);
|
||||
Route::get('/build-report', [BuildController::class, 'report']);
|
||||
Route::get('/build-report-source', [BuildController::class, 'reportSource']);
|
||||
Route::post('/build', [BuildController::class, 'build']);
|
||||
Route::get('/command-report/{signature}', [BuildController::class, 'report']);
|
||||
Route::get('/command-report-source/{signature}', [BuildController::class, 'reportSource']);
|
||||
Route::post('/command/{signature}', [BuildController::class, 'build']);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Lucent\Edge\Event\EdgesUpdated;
|
||||
|
||||
class LucentEventServiceProvider extends ServiceProvider
|
||||
{
|
||||
protected $listen = [
|
||||
EdgesUpdated::class => [
|
||||
SendEmailVerificationNotification::class,
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -12,7 +12,6 @@ use Lucent\Commands\CompileSchemas;
|
||||
use Lucent\Commands\LiveLink;
|
||||
use Lucent\Commands\RebuildThumbnails;
|
||||
use Lucent\Commands\RemoveOrphanEdges;
|
||||
use Lucent\Event\Dispatcher;
|
||||
use Lucent\File\FileService;
|
||||
use Lucent\File\ImageService;
|
||||
use Lucent\Query\DatabaseGraph\DatabaseGraph;
|
||||
@@ -30,22 +29,20 @@ class LucentServiceProvider extends ServiceProvider
|
||||
return ChannelService::fromConfig();
|
||||
});
|
||||
|
||||
$this->app->singleton(Dispatcher::class, function () {
|
||||
return new Dispatcher();
|
||||
});
|
||||
|
||||
$this->app->bind(ImageManager::class, function () {
|
||||
return new ImageManager(['driver' => 'imagick']);
|
||||
});
|
||||
|
||||
|
||||
|
||||
$this->app->bind(DatabaseGraph::class, function () {
|
||||
return match (config("lucent.database")) {
|
||||
$dbConnection = config("lucent.database");
|
||||
return match (config("database.connections.$dbConnection.driver")) {
|
||||
"sqlite" => new SqliteDatabaseGraph(),
|
||||
"pgsql" => new PgsqlDatabaseGraph(),
|
||||
};
|
||||
});
|
||||
|
||||
$this->app->register(LucentEventServiceProvider::class);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
class BuilderConverter
|
||||
{
|
||||
public function for(Argument $argument): IBuilderConverter
|
||||
{
|
||||
return new ($argument->operator->converter)($argument);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class Equals implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->where($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhere($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
private function formatValue(): string
|
||||
{
|
||||
return trim($this->argument->value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class EqualsFalse implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->where($this->argument->field, false);
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhere($this->argument->field, false);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class EqualsNumber implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
|
||||
return $builder->where($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhere($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
private function formatValue(): int|float
|
||||
{
|
||||
$value = trim($this->argument->value);
|
||||
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class EqualsTrue implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->where($this->argument->field, true);
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhere($this->argument->field, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class Exists implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->whereNot($this->argument->field, "")->whereNotNull($this->argument->field);
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->whereNot($this->argument->field, "")->whereNotNull($this->argument->field);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class Filter implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->whereJsonContains($this->argument->field, [$this->argument->value]);
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhereJsonContains($this->argument->field, [$this->argument->value]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class GreaterThan implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
|
||||
return $builder->where($this->argument->field, ">", $this->formatValue());
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhere($this->argument->field, ">", $this->formatValue());
|
||||
}
|
||||
|
||||
private function formatValue(): int|float
|
||||
{
|
||||
$value = trim($this->argument->value);
|
||||
if (is_numeric($value)) {
|
||||
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||
}
|
||||
return $value;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class GreaterThanEquals implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
|
||||
return $builder->where($this->argument->field, ">=", $this->formatValue());
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhere($this->argument->field, ">=", $this->formatValue());
|
||||
}
|
||||
|
||||
private function formatValue(): int|float
|
||||
{
|
||||
$value = trim($this->argument->value);
|
||||
if (is_numeric($value)) {
|
||||
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||
}
|
||||
return $value;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
interface IBuilderConverter
|
||||
{
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder;
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class In implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->whereIn($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhereIn($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
private function formatValue(): array
|
||||
{
|
||||
$value = $this->argument->value;
|
||||
if (is_string($value)) {
|
||||
$value = explode(",", $value);
|
||||
}
|
||||
return array_map(fn($v) => trim($v), $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class InNum implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->whereIn($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhereIn($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
private function formatValue(): array
|
||||
{
|
||||
$value = $this->argument->value;
|
||||
if (is_string($value)) {
|
||||
$value = explode(",", $value);
|
||||
}
|
||||
return array_map(fn($v) => $this->formatNumber($v), $value);
|
||||
}
|
||||
|
||||
private function formatNumber(string $value): float|int
|
||||
{
|
||||
$value = trim($value);
|
||||
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class IsNull implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->whereNull($this->argument->field);
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhereNull($this->argument->field);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class LessThan implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
|
||||
return $builder->where($this->argument->field, "<", $this->formatValue());
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhere($this->argument->field, "<", $this->formatValue());
|
||||
}
|
||||
|
||||
private function formatValue(): int|float
|
||||
{
|
||||
$value = trim($this->argument->value);
|
||||
if (is_numeric($value)) {
|
||||
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||
}
|
||||
return $value;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class LessThanEquals implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
|
||||
return $builder->where($this->argument->field, "<=", $this->formatValue());
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhere($this->argument->field, "<=", $this->formatValue());
|
||||
}
|
||||
|
||||
private function formatValue(): int|float
|
||||
{
|
||||
$value = trim($this->argument->value);
|
||||
if (is_numeric($value)) {
|
||||
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||
}
|
||||
return $value;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class NotEquals implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->whereNot($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhereNot($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
private function formatValue(): string
|
||||
{
|
||||
return trim($this->argument->value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class NotEqualsFalse implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->whereNot($this->argument->field, false);
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhereNot($this->argument->field, false);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class NotEqualsNumber implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->whereNot($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhereNot($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
private function formatValue(): int|float
|
||||
{
|
||||
$value = trim($this->argument->value);
|
||||
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class NotEqualsTrue implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->whereNot($this->argument->field, true);
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhereNot($this->argument->field, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class NotExists implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->where(fn($b) => $b->where($this->argument->field, "")->orWhereNull($this->argument->field));
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhere(fn($b) => $b->where($this->argument->field, "")->orWhereNull($this->argument->field));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class NotIn implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->whereNotIn($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhereNotIn($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
private function formatValue(): array
|
||||
{
|
||||
$value = $this->argument->value;
|
||||
if (is_string($value)) {
|
||||
$value = explode(",", $value);
|
||||
}
|
||||
return array_map(fn($v) => trim($v), $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class NotInNum implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->whereNotIn($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhereNotIn($this->argument->field, $this->formatValue());
|
||||
}
|
||||
|
||||
private function formatValue(): array
|
||||
{
|
||||
$value = $this->argument->value;
|
||||
if (is_string($value)) {
|
||||
$value = explode(",", $value);
|
||||
}
|
||||
return array_map(fn($v) => $this->formatNumber($v), $value);
|
||||
}
|
||||
|
||||
private function formatNumber(string $value): float|int
|
||||
{
|
||||
$value = trim($value);
|
||||
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class NotNull implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->whereNotNull($this->argument->field);
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhereNotNull($this->argument->field);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\BuilderConverter;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
|
||||
readonly class Regex implements IBuilderConverter
|
||||
{
|
||||
|
||||
public function __construct(private Argument $argument)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAndQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
|
||||
return $builder->where($this->argument->field, "like", $this->formatValue());
|
||||
}
|
||||
|
||||
public function toOrQueryBuilder(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orWhere($this->argument->field, "like", $this->formatValue());
|
||||
}
|
||||
|
||||
private function formatValue(): string
|
||||
{
|
||||
return "%" . strtolower(trim($this->argument->value)) . "%";
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
namespace Lucent\Query\Filter;
|
||||
|
||||
use Lucent\Query\Operator\Operator;
|
||||
|
||||
class Argument
|
||||
{
|
||||
public function __construct(
|
||||
public string $field,
|
||||
public string $operator,
|
||||
public mixed $value,
|
||||
){}
|
||||
public string $field,
|
||||
public Operator $operator,
|
||||
public mixed $value,
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
+16
-139
@@ -5,19 +5,26 @@ namespace Lucent\Query;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Lucent\Query\BuilderConverter\BuilderConverter;
|
||||
use Lucent\Query\Filter\AndFilter;
|
||||
use Lucent\Query\Filter\Argument;
|
||||
use Lucent\Query\Filter\Filter;
|
||||
use Lucent\Query\Filter\OrFilter;
|
||||
use Lucent\Query\Operator\In;
|
||||
use Lucent\Query\Operator\OperatorDetector;
|
||||
use function explode;
|
||||
|
||||
final class FilterParser
|
||||
{
|
||||
|
||||
public function __construct(public Application $app)
|
||||
public function __construct(
|
||||
public Application $app,
|
||||
public BuilderConverter $builderConverter,
|
||||
public OperatorDetector $operatorDetector,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $arguments
|
||||
* @return array<Argument>
|
||||
@@ -25,105 +32,11 @@ final class FilterParser
|
||||
private function formatArguments(array $arguments): array
|
||||
{
|
||||
return collect($arguments)->reduce(function ($c, $v, $k) {
|
||||
$c[] = $this->formatArgument($v, $k);
|
||||
$c[] = $this->operatorDetector->detect($v, $k);
|
||||
return $c;
|
||||
}, []);
|
||||
}
|
||||
|
||||
private function formatArgument(mixed $value, string $filter): Argument
|
||||
{
|
||||
|
||||
$operator = $this->detectOperator($filter);
|
||||
|
||||
$field = $this->detectField($filter, $operator);
|
||||
$formattedValue = match ($operator) {
|
||||
"eq" => $this->formatText($value),
|
||||
"ne" => $this->formatText($value),
|
||||
"eqnum" => $this->formatNumber($value),
|
||||
"nenum" => $this->formatNumber($value),
|
||||
"object" => $this->formatText($value),
|
||||
"in" => $this->formatListString($value),
|
||||
"nin" => $this->formatListString($value),
|
||||
"innum" => $this->formatListNum($value),
|
||||
"ninnum" => $this->formatListNum($value),
|
||||
"eqtrue" => true,
|
||||
"eqfalse" => false,
|
||||
"netrue" => true,
|
||||
"nefalse" => false,
|
||||
"regex" => "%" . strtolower($value) . "%",
|
||||
"gt" => is_numeric($value) ? floatval($value) : $value,
|
||||
"gte" => is_numeric($value) ? floatval($value) : $value,
|
||||
"lt" => is_numeric($value) ? floatval($value) : $value,
|
||||
"lte" => is_numeric($value) ? floatval($value) : $value,
|
||||
"null" => null,
|
||||
"nnull" => null,
|
||||
"exists" => true,
|
||||
"nexists" => false,
|
||||
default => $value,
|
||||
};
|
||||
|
||||
$matchedOperator = Operator::list()[$operator];
|
||||
return new Argument(
|
||||
field: str_replace(".", "->", $field),
|
||||
operator: $matchedOperator->db,
|
||||
value: $formattedValue
|
||||
);
|
||||
}
|
||||
|
||||
private function formatText(string $value): string
|
||||
{
|
||||
return trim($value);
|
||||
}
|
||||
|
||||
private function formatNumber(string $value): float
|
||||
{
|
||||
return floatval($value);
|
||||
}
|
||||
|
||||
|
||||
private function formatListString(mixed $value): array
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$value = explode(",", $value);
|
||||
}
|
||||
|
||||
return array_map(fn($v) => $this->formatText($v), $value);
|
||||
}
|
||||
|
||||
private function formatListNum(mixed $value): array
|
||||
{
|
||||
if (\is_string($value)) {
|
||||
$value = explode(",", $value);
|
||||
}
|
||||
|
||||
return \array_map(fn($v) => $this->formatNumber($v), $value);
|
||||
}
|
||||
|
||||
|
||||
private function detectOperator(string $filter): string
|
||||
{
|
||||
$exploded = \explode("_", $filter);
|
||||
$candidate = end($exploded);
|
||||
$operatorsListNames = collect(Operator::list())->map(fn($o) => $o->name)->toArray();
|
||||
|
||||
if (\in_array($candidate, $operatorsListNames)) {
|
||||
return $candidate;
|
||||
}
|
||||
return 'eq';
|
||||
}
|
||||
|
||||
private function detectField(string $filter, string $operator): string
|
||||
{
|
||||
|
||||
$exploded = explode("_", $filter);
|
||||
$candidate = array_pop($exploded);
|
||||
if ($candidate === $operator) {
|
||||
return implode("_", $exploded);
|
||||
}
|
||||
return $filter;
|
||||
}
|
||||
|
||||
|
||||
private function formatReferences(array $referenceArguments): Argument
|
||||
{
|
||||
$subqueries = collect($referenceArguments)->reduce(function ($c, $v, $k) {
|
||||
@@ -151,7 +64,7 @@ final class FilterParser
|
||||
|
||||
return new Argument(
|
||||
field: "id",
|
||||
operator: "in",
|
||||
operator: new In(),
|
||||
value: $sourceIds
|
||||
);
|
||||
|
||||
@@ -174,7 +87,6 @@ final class FilterParser
|
||||
private function parseArguments(Filter $arguments): array
|
||||
{
|
||||
[$normalArguments, $referenceArguments] = $this->separateMainFromReferenceArguments($arguments);
|
||||
|
||||
$formattedArguments = $this->formatArguments($normalArguments);
|
||||
if (!empty($referenceArguments)) {
|
||||
$formattedArguments[] = $this->formatReferences($referenceArguments);
|
||||
@@ -198,26 +110,8 @@ final class FilterParser
|
||||
*/
|
||||
private function parseAnd(Builder $builder, array $arguments): Builder
|
||||
{
|
||||
foreach ($arguments as $argument) {
|
||||
if ($argument->operator == "in") {
|
||||
$builder->whereIn($argument->field, $argument->value);
|
||||
} else if ($argument->operator == "nin") {
|
||||
$builder->whereNotIn($argument->field, $argument->value);
|
||||
} else if ($argument->operator == "exists") {
|
||||
$builder->where($argument->field, "!=", "");
|
||||
$builder->where($argument->field, "!=", null);
|
||||
} elseif ($argument->operator == "filter") {
|
||||
$builder->whereJsonContains($argument->field, [$argument->value]);
|
||||
// target result
|
||||
// filter[data.previousNames_object]=previousNames&filter[previousNames.name_eq]=alpha&filter[previousNames.id_eqnum]=24
|
||||
// $query->whereJsonContains("data->previousNames", [["name" => "alpha", "id" => 24]]);
|
||||
// $query->whereJsonContains($filter["field"], [$objectFilters]);
|
||||
} else {
|
||||
$builder->where($argument->field, $argument->operator, $argument->value);
|
||||
}
|
||||
}
|
||||
|
||||
return $builder;
|
||||
return collect($arguments)
|
||||
->reduce(fn($cBuilder, Argument $arg) => $this->builderConverter->for($arg)->toAndQueryBuilder($cBuilder), $builder);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,27 +119,10 @@ final class FilterParser
|
||||
*/
|
||||
private function parseOr(Builder $builder, array $arguments): Builder
|
||||
{
|
||||
$builder->where(function (Builder $orBuilder) use ($arguments) {
|
||||
foreach ($arguments as $argument) {
|
||||
if ($argument->operator == "in") {
|
||||
$orBuilder->orWhereIn($argument->field, $argument->value);
|
||||
} else if ($argument->operator == "nin") {
|
||||
$orBuilder->orWhereNotIn($argument->field, $argument->value);
|
||||
} else if ($argument->operator == "exists") {
|
||||
$orBuilder->where($argument->field, "!=", "");
|
||||
$orBuilder->where($argument->field, "!=", null);
|
||||
} elseif ($argument->operator == "filter") {
|
||||
$orBuilder->whereJsonContains($argument->field, [$argument->value]);
|
||||
// target result
|
||||
// filter[data.previousNames_object]=previousNames&filter[previousNames.name_eq]=alpha&filter[previousNames.id_eqnum]=24
|
||||
// $query->whereJsonContains("data->previousNames", [["name" => "alpha", "id" => 24]]);
|
||||
// $query->whereJsonContains($filter["field"], [$objectFilters]);
|
||||
} else {
|
||||
$orBuilder->orWhere($argument->field, $argument->operator, $argument->value);
|
||||
}
|
||||
}
|
||||
return $builder->where(function (Builder $orBuilder) use ($arguments) {
|
||||
return collect($arguments)
|
||||
->reduce(fn($cBuilder, Argument $arg) => $this->builderConverter->for($arg)->toOrQueryBuilder($cBuilder), $orBuilder);
|
||||
});
|
||||
return $builder;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query;
|
||||
|
||||
|
||||
final class Operator
|
||||
{
|
||||
/**
|
||||
* @psalm-param string[] $uis
|
||||
*/
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $label,
|
||||
public string $symbol,
|
||||
public string $db,
|
||||
public array $uis,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, Operator>
|
||||
*/
|
||||
public static function list(): array
|
||||
{
|
||||
|
||||
return [
|
||||
"regex" => new Operator(
|
||||
name: "regex",
|
||||
label: "Search",
|
||||
symbol: "~",
|
||||
db: 'like',
|
||||
uis: ["id", "text", "textarea", "url", "color", "date", "datetime"],
|
||||
),
|
||||
"eq" => new Operator(
|
||||
name: "eq",
|
||||
label: "Equals",
|
||||
symbol: "is",
|
||||
db: '=',
|
||||
uis: ["id", "text", "textarea", "url", "color", "date", "datetime", "reference"],
|
||||
),
|
||||
"ne" => new Operator(
|
||||
name: "ne",
|
||||
label: "Not Equals",
|
||||
symbol: "is not",
|
||||
db: '!=',
|
||||
uis: ["id", "text", "textarea", "url", "color", "date", "datetime"],
|
||||
),
|
||||
"eqnum" => new Operator(
|
||||
name: "eqnum",
|
||||
label: "Equals number",
|
||||
symbol: "is",
|
||||
db: '=',
|
||||
uis: ["number"],
|
||||
),
|
||||
"neqnum" => new Operator(
|
||||
name: "nenum",
|
||||
label: "Not Equals number",
|
||||
symbol: "is not",
|
||||
db: '$ne',
|
||||
uis: ["number"],
|
||||
),
|
||||
"filter" => new Operator(
|
||||
name: "filter",
|
||||
label: "Equals Object",
|
||||
symbol: "is",
|
||||
db: 'filter',
|
||||
uis: [],
|
||||
),
|
||||
"eqtrue" => new Operator(
|
||||
name: "eqtrue",
|
||||
label: "Equals true",
|
||||
symbol: "is",
|
||||
db: '=',
|
||||
uis: ["checkbox"],
|
||||
),
|
||||
"eqfalse" => new Operator(
|
||||
name: "eqfalse",
|
||||
label: "Equals false",
|
||||
symbol: "is not",
|
||||
db: '=',
|
||||
uis: ["checkbox"],
|
||||
),
|
||||
"netrue" => new Operator(
|
||||
name: "netrue",
|
||||
label: "Not equals true",
|
||||
symbol: "!=",
|
||||
db: '$ne',
|
||||
uis: ["checkbox"],
|
||||
),
|
||||
"nefalse" => new Operator(
|
||||
name: "nefalse",
|
||||
label: "Not equals false",
|
||||
symbol: "!=",
|
||||
db: '$ne',
|
||||
uis: ["checkbox"],
|
||||
),
|
||||
"in" => new Operator(
|
||||
name: "in",
|
||||
label: "In list",
|
||||
symbol: "in",
|
||||
db: 'in',
|
||||
uis: ["id", "text", "textarea", "url", "color", "date", "datetime"],
|
||||
),
|
||||
"innum" => new Operator(
|
||||
name: "innum",
|
||||
label: "In list of numbers",
|
||||
symbol: "in",
|
||||
db: '$in',
|
||||
uis: ["number"],
|
||||
),
|
||||
"nin" => new Operator(
|
||||
name: "nin",
|
||||
label: "Not in list",
|
||||
symbol: "not in",
|
||||
db: 'nin',
|
||||
uis: ["id", "text", "textarea", "url", "color", "date", "datetime"],
|
||||
),
|
||||
"ninnum" => new Operator(
|
||||
name: "ninnum",
|
||||
label: "Not In list of numbers",
|
||||
symbol: "not in",
|
||||
db: '$nin',
|
||||
uis: ["number"],
|
||||
),
|
||||
"lt" => new Operator(
|
||||
name: "lt",
|
||||
label: "Less than",
|
||||
symbol: "<",
|
||||
db: '<',
|
||||
uis: ["number", "date", "datetime"],
|
||||
),
|
||||
"lte" => new Operator(
|
||||
name: "lte",
|
||||
label: "Less than equals",
|
||||
symbol: "<=",
|
||||
db: '<=',
|
||||
uis: ["number", "date", "datetime"],
|
||||
),
|
||||
"gt" => new Operator(
|
||||
name: "gt",
|
||||
label: "Greater than",
|
||||
symbol: ">",
|
||||
db: '>',
|
||||
uis: ["number", "date", "datetime"],
|
||||
),
|
||||
"gte" => new Operator(
|
||||
name: "gte",
|
||||
label: "Greater than equals",
|
||||
symbol: ">=",
|
||||
db: '>=',
|
||||
uis: ["number", "date", "datetime"],
|
||||
),
|
||||
"null" => new Operator(
|
||||
name: "null",
|
||||
label: "Is null",
|
||||
symbol: "=",
|
||||
db: '$eq',
|
||||
uis: ["*"],
|
||||
),
|
||||
"nnull" => new Operator(
|
||||
name: "nnull",
|
||||
label: "Not null",
|
||||
symbol: "!=",
|
||||
db: '$ne',
|
||||
uis: ["*"],
|
||||
),
|
||||
"exists" => new Operator(
|
||||
name: "exists",
|
||||
label: "Exists",
|
||||
symbol: "exists",
|
||||
db: 'exists',
|
||||
uis: ["*"],
|
||||
),
|
||||
"nexists" => new Operator(
|
||||
name: "nexists",
|
||||
label: "Not exists",
|
||||
symbol: "not exists",
|
||||
db: '$exists',
|
||||
uis: ["*"],
|
||||
),
|
||||
|
||||
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class Equals extends Operator
|
||||
{
|
||||
|
||||
public string $name = "eq";
|
||||
public string $label = "Equals";
|
||||
public string $symbol = "is";
|
||||
public array $uis = ["id", "text", "textarea", "url", "color", "date", "datetime", "reference"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\Equals::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class EqualsFalse extends Operator
|
||||
{
|
||||
|
||||
public string $name = "eqfalse";
|
||||
public string $label = "Equals False";
|
||||
public string $symbol = "is";
|
||||
public array $uis = ["checkbox"];
|
||||
public bool $hasValue = false;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\EqualsFalse::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class EqualsNumber extends Operator
|
||||
{
|
||||
|
||||
public string $name = "eqnum";
|
||||
public string $label = "Equals Number";
|
||||
public string $symbol = "is";
|
||||
public array $uis = ["number"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\EqualsNumber::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class EqualsTrue extends Operator
|
||||
{
|
||||
|
||||
public string $name = "eqtrue";
|
||||
public string $label = "Equals True";
|
||||
public string $symbol = "is";
|
||||
public array $uis = ["checkbox"];
|
||||
public bool $hasValue = false;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\EqualsTrue::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class Exists extends Operator
|
||||
{
|
||||
|
||||
public string $name = "exists";
|
||||
public string $label = "Exists";
|
||||
public string $symbol = "exists";
|
||||
public array $uis = ["*"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\Exists::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class Filter extends Operator
|
||||
{
|
||||
public string $name = "filter";
|
||||
public string $label = "Equals Object";
|
||||
public string $symbol = "is";
|
||||
public array $uis = [];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\Filter::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class GreaterThan extends Operator
|
||||
{
|
||||
|
||||
public string $name = "gt";
|
||||
public string $label = "Greater than";
|
||||
public string $symbol = ">";
|
||||
public array $uis = ["number", "date", "datetime"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\GreaterThan::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class GreaterThanEquals extends Operator
|
||||
{
|
||||
|
||||
public string $name = "gte";
|
||||
public string $label = "Greater than equals";
|
||||
public string $symbol = ">";
|
||||
public array $uis = ["number", "date", "datetime"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\GreaterThanEquals::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class In extends Operator
|
||||
{
|
||||
public string $name = "in";
|
||||
public string $label = "In list";
|
||||
public string $symbol = "in";
|
||||
public array $uis = ["id", "text", "textarea", "url", "color", "date", "datetime"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\In::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class InNum extends Operator
|
||||
{
|
||||
public string $name = "innum";
|
||||
public string $label = "In number list";
|
||||
public string $symbol = "in";
|
||||
public array $uis = ["number"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\NotInNum::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class IsNull extends Operator
|
||||
{
|
||||
|
||||
public string $name = "null";
|
||||
public string $label = "Is Null";
|
||||
public string $symbol = "is null";
|
||||
public array $uis = ["*"];
|
||||
public bool $hasValue = false;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\IsNull::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class LessThan extends Operator
|
||||
{
|
||||
|
||||
public string $name = "lt";
|
||||
public string $label = "Less than";
|
||||
public string $symbol = ">";
|
||||
public array $uis = ["number", "date", "datetime"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\LessThan::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class LessThanEquals extends Operator
|
||||
{
|
||||
|
||||
public string $name = "lte";
|
||||
public string $label = "Less than equals";
|
||||
public string $symbol = ">";
|
||||
public array $uis = ["number", "date", "datetime"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\LessThanEquals::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class NotEquals extends Operator
|
||||
{
|
||||
|
||||
public string $name = "ne";
|
||||
public string $label = "Not Equals";
|
||||
public string $symbol = "is not";
|
||||
public array $uis = ["id", "text", "textarea", "url", "color", "date", "datetime", "reference"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\NotEquals::class;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class NotEqualsFalse extends Operator
|
||||
{
|
||||
|
||||
public string $name = "nefalse";
|
||||
public string $label = "Not Equals False";
|
||||
public string $symbol = "is";
|
||||
public array $uis = ["checkbox"];
|
||||
public bool $hasValue = false;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\NotEqualsFalse::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class NotEqualsNumber extends Operator
|
||||
{
|
||||
|
||||
public string $name = "neqnum";
|
||||
public string $label = "Not Equals Number";
|
||||
public string $symbol = "is";
|
||||
public array $uis = ["number"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\NotEqualsNumber::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class NotEqualsTrue extends Operator
|
||||
{
|
||||
|
||||
public string $name = "netrue";
|
||||
public string $label = "Not Equals True";
|
||||
public string $symbol = "is";
|
||||
public array $uis = ["checkbox"];
|
||||
public bool $hasValue = false;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\NotEqualsTrue::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class NotExists extends Operator
|
||||
{
|
||||
|
||||
public string $name = "nexists";
|
||||
public string $label = "Not Exists";
|
||||
public string $symbol = "not exists";
|
||||
public array $uis = ["*"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\NotExists::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class NotIn extends Operator
|
||||
{
|
||||
public string $name = "nin";
|
||||
public string $label = "Not in list";
|
||||
public string $symbol = "in";
|
||||
public array $uis = ["id", "text", "textarea", "url", "color", "date", "datetime"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\NotIn::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query\Operator;
|
||||
|
||||
class NotInNum extends Operator
|
||||
{
|
||||
public string $name = "ninnum";
|
||||
public string $label = "Not in number list";
|
||||
public string $symbol = "not in";
|
||||
public array $uis = ["number"];
|
||||
public bool $hasValue = true;
|
||||
public string $converter = \Lucent\Query\BuilderConverter\InNum::class;
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user