merge v0.23.0-rc changes

This commit is contained in:
Gani Georgiev committed 2024-09-29 21:09:46 +03:00
1 parent ad92992324
commit 844f18cac3
753 files changed
+85107 -63362

No files matched your search

@@ -1,7 +1,7 @@
<script>
import { createEventDispatcher } from "svelte";
import CommonHelper from "@/utils/CommonHelper";
import Toggler from "@/components/base/Toggler.svelte";
import CommonHelper from "@/utils/CommonHelper";
import { createEventDispatcher } from "svelte";
let classes = "";
export { classes as class }; // export reserved keyword
@@ -35,7 +35,7 @@
icon: CommonHelper.getFieldTypeIcon("email"),
},
{
label: "Url",
label: "URL",
value: "url",
icon: CommonHelper.getFieldTypeIcon("url"),
},
@@ -44,6 +44,11 @@
value: "date",
icon: CommonHelper.getFieldTypeIcon("date"),
},
{
label: "Autodate",
value: "autodate",
icon: CommonHelper.getFieldTypeIcon("autodate"),
},
{
label: "Select",
value: "select",
@@ -64,6 +69,11 @@
value: "json",
icon: CommonHelper.getFieldTypeIcon("json"),
},
// {
// label: "Password",
// value: "password",
// icon: CommonHelper.getFieldTypeIcon("password"),
// },
];
function select(fieldType) {
@@ -21,34 +21,43 @@
number: "Nonzero",
};
// @todo refactor once the UI is dynamic
const authHideNonemptyToggle = ["password", "tokenKey", "id", "autodate"];
const authHideHiddenToggle = ["password", "tokenKey", "id", "email"];
const authHidePresentableToggle = ["password", "tokenKey"];
export let key = "";
export let field = CommonHelper.initSchemaField();
export let draggable = true;
export let collection = {};
let nameInput;
let showOptions = false;
$: if (field.toDelete) {
$: isAuthCollection = collection?.type == "auth";
$: if (field._toDelete) {
// reset the name if it was previously deleted
if (field.originalName && field.name !== field.originalName) {
field.name = field.originalName;
if (field._originalName && field.name !== field._originalName) {
field.name = field._originalName;
}
}
$: if (!field.originalName && field.name) {
field.originalName = field.name;
$: if (!field._originalName && field.name) {
field._originalName = field.name;
}
$: if (typeof field.toDelete === "undefined") {
field.toDelete = false; // normalize
$: if (typeof field._toDelete === "undefined") {
field._toDelete = false; // normalize
}
$: if (field.required) {
field.nullable = false;
}
$: interactive = !field.toDelete && !(field.id && field.system);
$: interactive = !field._toDelete;
$: hasErrors = !CommonHelper.isEmpty(CommonHelper.getNestedVal($errors, `schema.${key}`));
$: hasErrors = !CommonHelper.isEmpty(CommonHelper.getNestedVal($errors, `fields.${key}`));
$: requiredLabel = customRequiredLabels[field?.type] || "Nonempty";
@@ -57,19 +66,19 @@
collapse();
dispatch("remove");
} else {
field.toDelete = true;
field._toDelete = true;
}
}
function restore() {
field.toDelete = false;
field._toDelete = false;
// reset all errors since the error index key would have been changed
setErrors({});
}
function duplicate() {
if (!field.toDelete) {
if (!field._toDelete) {
collapse();
dispatch("duplicate");
}
@@ -126,44 +135,49 @@
class="schema-field"
class:required={field.required}
class:expanded={interactive && showOptions}
class:deleted={field.toDelete}
class:deleted={field._toDelete}
transition:slide={{ duration: 150 }}
>
<div class="schema-field-header">
{#if interactive}
{#if interactive && draggable}
<div class="drag-handle-wrapper" draggable={true} aria-label="Sort">
<span class="drag-handle" />
</div>
{/if}
<Field
class="form-field required m-0 {!interactive ? 'disabled' : ''}"
name="schema.{key}.name"
name="fields.{key}.name"
inlineError
>
{#if field.required}
<div class="field-labels">
<div class="field-labels">
{#if field.required}
<span class="label label-success">{requiredLabel}</span>
</div>
{/if}
{/if}
{#if field.hidden}
<span class="label label-danger">Hidden</span>
{/if}
</div>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="form-field-addon prefix no-pointer-events field-type-icon"
class:txt-disabled={!interactive}
class="form-field-addon prefix field-type-icon"
class:txt-disabled={!interactive || field.system}
use:tooltip={field.type + (field.system ? " (system)" : "")}
on:click={() => nameInput?.focus()}
>
<i class={CommonHelper.getFieldTypeIcon(field.type)} />
</div>
<!-- svelte-ignore a11y-autofocus -->
<input
bind:this={nameInput}
type="text"
required
disabled={!interactive}
readonly={field.id && field.system}
disabled={!interactive || field.system}
spellcheck="false"
autofocus={!field.id}
placeholder="Field name"
value={field.name}
title="System field"
on:input={(e) => {
const oldName = field.name;
field.name = normalizeFieldName(e.target.value);
@@ -178,10 +192,10 @@
<span class="separator" />
</slot>
{#if field.toDelete}
{#if field._toDelete}
<button
type="button"
class="btn btn-sm btn-circle btn-warning btn-transparent options-trigger"
class="btn btn-sm btn-circle btn-success btn-transparent options-trigger"
aria-label="Restore"
use:tooltip={"Restore"}
on:click={restore}
@@ -191,7 +205,7 @@
{:else if interactive}
<button
type="button"
aria-label="Toggle field options"
aria-label="Toggle {field.name} field options"
class="btn btn-sm btn-circle options-trigger {showOptions
? 'btn-secondary'
: 'btn-transparent'}"
@@ -206,49 +220,83 @@
</div>
{#if interactive && showOptions}
<div class="schema-field-options" transition:slide={{ duration: 150 }}>
<div class="schema-field-options" transition:slide={{ delay: 10, duration: 150 }}>
<div class="hidden-empty m-b-sm">
<slot name="options" {interactive} {hasErrors} />
</div>
<div class="schema-field-options-footer">
<Field class="form-field form-field-toggle" name="requried" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={field.required} />
<label for={uniqueId}>
<span class="txt">{requiredLabel}</span>
<i
class="ri-information-line link-hint"
use:tooltip={{
text: `Requires the field value NOT to be ${CommonHelper.zeroDefaultStr(
field,
)}.`,
}}
/>
</label>
</Field>
<!-- @todo move to each field after the refactoring -->
{#if !field.primaryKey && field.type != "autodate" && (!isAuthCollection || !authHideNonemptyToggle.includes(field.name))}
<Field class="form-field form-field-toggle" name="requried" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={field.required} />
<label for={uniqueId}>
<span class="txt">{requiredLabel}</span>
<i
class="ri-information-line link-hint"
use:tooltip={{
text: `Requires the field value NOT to be ${CommonHelper.zeroDefaultStr(
field,
)}.`,
}}
/>
</label>
</Field>
{/if}
<Field class="form-field form-field-toggle" name="presentable" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={field.presentable} />
<label for={uniqueId}>
<span class="txt">Presentable</span>
<i
class="ri-information-line link-hint"
use:tooltip={{
text: `Whether the field should be preferred in the Admin UI relation listings (default to auto).`,
{#if !field.primaryKey && (!isAuthCollection || !authHideHiddenToggle.includes(field.name))}
<Field class="form-field form-field-toggle" name="hidden" let:uniqueId>
<input
type="checkbox"
id={uniqueId}
bind:checked={field.hidden}
on:change={(e) => {
if (e.target.checked) {
field.presentable = false;
}
}}
/>
</label>
</Field>
<label for={uniqueId}>
<span class="txt">Hidden</span>
<i
class="ri-information-line link-hint"
use:tooltip={{
text: `Hide from the JSON API response and filters.`,
}}
/>
</label>
</Field>
{/if}
{#if !isAuthCollection || !authHidePresentableToggle.includes(field.name)}
<Field class="form-field form-field-toggle m-0" name="presentable" let:uniqueId>
<input
type="checkbox"
id={uniqueId}
bind:checked={field.presentable}
disabled={field.hidden}
/>
<label for={uniqueId}>
<span class="txt">Presentable</span>
<i
class="ri-information-line {field.hidden ? 'txt-disabled' : 'link-hint'}"
use:tooltip={{
text: `Whether the field should be preferred in the Superuser UI relation listings (default to auto).`,
}}
/>
</label>
</Field>
{/if}
<slot name="optionsFooter" {interactive} {hasErrors} />
{#if !field.toDelete}
{#if !field._toDelete && !field.primaryKey}
<div class="m-l-auto txt-right">
<div class="inline-flex flex-gap-sm flex-nowrap">
<div
tabindex="0"
role="button"
aria-label="More"
title="More field options"
class="btn btn-circle btn-sm btn-transparent"
>
<i class="ri-more-line" aria-hidden="true" />
@@ -263,14 +311,16 @@
>
<span class="txt">Duplicate</span>
</button>
<button
type="button"
class="dropdown-item"
role="menuitem"
on:click|preventDefault={remove}
>
<span class="txt">Remove</span>
</button>
{#if !field.system}
<button
type="button"
class="dropdown-item"
role="menuitem"
on:click|preventDefault={remove}
>
<span class="txt">Remove</span>
</button>
{/if}
</Toggler>
</div>
</div>
@@ -0,0 +1,78 @@
<script>
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import ObjectSelect from "@/components/base/ObjectSelect.svelte";
import SchemaField from "@/components/collections/schema/SchemaField.svelte";
const ON_CREATE = 1;
const ON_UPDATE = 2;
const ON_CREATE_UPDATE = 3;
const options = [
{ label: "Create", value: ON_CREATE },
{ label: "Update", value: ON_UPDATE },
{ label: "Create/Update", value: ON_CREATE_UPDATE },
];
export let field;
export let key = "";
let selectedOption = optionFromField();
$: updateField(selectedOption);
function optionFromField() {
if (field.onCreate && field.onUpdate) {
return ON_CREATE_UPDATE;
}
if (field.onUpdate) {
return ON_UPDATE;
}
return ON_CREATE;
}
function updateField(option) {
switch (option) {
case ON_CREATE:
field.onCreate = true;
field.onUpdate = false;
break;
case ON_UPDATE:
field.onCreate = false;
field.onUpdate = true;
break;
case ON_CREATE_UPDATE:
field.onCreate = true;
field.onUpdate = true;
break;
}
}
</script>
<SchemaField bind:field {key} on:rename on:remove on:duplicate {...$$restProps}>
<svelte:fragment let:interactive>
<div class="separator" />
<Field
class="form-field form-field-single-multiple-select form-field-autodate-select {!interactive
? 'readonly'
: ''}"
inlineError
let:uniqueId
>
<div use:tooltip={{ text: "Auto set on:", position: "top" }}>
<ObjectSelect
id={uniqueId}
items={options}
disabled={field.system}
readonly={!interactive}
bind:keyOfSelected={selectedOption}
/>
</div>
</Field>
<div class="separator" />
</svelte:fragment>
</SchemaField>
@@ -1,27 +1,27 @@
<script>
import Flatpickr from "svelte-flatpickr";
import CommonHelper from "@/utils/CommonHelper";
import Field from "@/components/base/Field.svelte";
import SchemaField from "@/components/collections/schema/SchemaField.svelte";
import CommonHelper from "@/utils/CommonHelper";
import Flatpickr from "svelte-flatpickr";
export let field;
export let key = "";
let pickerMinValue = field?.options?.min;
let pickerMaxValue = field?.options?.max;
let pickerMinValue = field?.min;
let pickerMaxValue = field?.max;
$: if (pickerMinValue != field?.options?.min) {
pickerMinValue = field?.options?.min;
$: if (pickerMinValue != field?.min) {
pickerMinValue = field?.min;
}
$: if (pickerMaxValue != field?.options?.max) {
pickerMaxValue = field?.options?.max;
$: if (pickerMaxValue != field?.max) {
pickerMaxValue = field?.max;
}
// ensure that value is set even on manual input edit
function onClose(e, key) {
if (e.detail && e.detail.length == 3) {
field.options[key] = e.detail[1];
field[key] = e.detail[1];
}
}
</script>
@@ -30,26 +30,26 @@
<svelte:fragment slot="options">
<div class="grid grid-sm">
<div class="col-sm-6">
<Field class="form-field" name="schema.{key}.options.min" let:uniqueId>
<Field class="form-field" name="fields.{key}.min" let:uniqueId>
<label for={uniqueId}>Min date (UTC)</label>
<Flatpickr
id={uniqueId}
options={CommonHelper.defaultFlatpickrOptions()}
bind:value={pickerMinValue}
bind:formattedValue={field.options.min}
bind:formattedValue={field.min}
on:close={(e) => onClose(e, "min")}
/>
</Field>
</div>
<div class="col-sm-6">
<Field class="form-field" name="schema.{key}.options.max" let:uniqueId>
<Field class="form-field" name="fields.{key}.max" let:uniqueId>
<label for={uniqueId}>Max date (UTC)</label>
<Flatpickr
id={uniqueId}
options={CommonHelper.defaultFlatpickrOptions()}
bind:value={pickerMaxValue}
bind:formattedValue={field.options.max}
bind:formattedValue={field.max}
on:close={(e) => onClose(e, "max")}
/>
</Field>
@@ -1,27 +1,29 @@
<script>
import CommonHelper from "@/utils/CommonHelper";
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import SchemaField from "@/components/collections/schema/SchemaField.svelte";
export let field;
export let key = "";
$: if (CommonHelper.isEmpty(field.options)) {
loadDefaults();
}
function loadDefaults() {
field.options = {
convertUrls: false,
};
}
</script>
<SchemaField bind:field {key} on:rename on:remove on:duplicate {...$$restProps}>
<svelte:fragment slot="optionsFooter">
<Field class="form-field form-field-toggle" name="schema.{key}.options.convertUrls" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={field.options.convertUrls} />
<svelte:fragment slot="options">
<Field class="form-field m-b-sm" name="fields.{key}.maxSize" let:uniqueId>
<label for={uniqueId}>Max size <small>(bytes)</small></label>
<input
type="number"
id={uniqueId}
step="1"
min="0"
value={field.maxSize || ""}
on:input={(e) => (field.maxSize = e.target.value << 0)}
placeholder="Default to max ~5MB"
/>
</Field>
<Field class="form-field form-field-toggle" name="fields.{key}.convertURLs" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={field.convertURLs} />
<label for={uniqueId}>
<span class="txt">Strip urls domain</span>
<i
@@ -1,9 +1,9 @@
<script>
import CommonHelper from "@/utils/CommonHelper";
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import MultipleValueInput from "@/components/base/MultipleValueInput.svelte";
import SchemaField from "@/components/collections/schema/SchemaField.svelte";
import CommonHelper from "@/utils/CommonHelper";
export let field;
export let key = "";
@@ -13,7 +13,7 @@
<svelte:fragment slot="options">
<div class="grid grid-sm">
<div class="col-sm-6">
<Field class="form-field" name="schema.{key}.options.exceptDomains" let:uniqueId>
<Field class="form-field" name="fields.{key}.exceptDomains" let:uniqueId>
<label for={uniqueId}>
<span class="txt">Except domains</span>
<i
@@ -26,16 +26,16 @@
</label>
<MultipleValueInput
id={uniqueId}
disabled={!CommonHelper.isEmpty(field.options.onlyDomains)}
bind:value={field.options.exceptDomains}
disabled={!CommonHelper.isEmpty(field.onlyDomains)}
bind:value={field.exceptDomains}
/>
<div class="help-block">Use comma as separator.</div>
</Field>
</div>
<div class="col-sm-6">
<Field class="form-field" name="schema.{key}.options.onlyDomains" let:uniqueId>
<label for="{uniqueId}.options.onlyDomains">
<Field class="form-field" name="fields.{key}.onlyDomains" let:uniqueId>
<label for="{uniqueId}.onlyDomains">
<span class="txt">Only domains</span>
<i
class="ri-information-line link-hint"
@@ -46,9 +46,9 @@
/>
</label>
<MultipleValueInput
id="{uniqueId}.options.onlyDomains"
disabled={!CommonHelper.isEmpty(field.options.exceptDomains)}
bind:value={field.options.onlyDomains}
id="{uniqueId}.onlyDomains"
disabled={!CommonHelper.isEmpty(field.exceptDomains)}
bind:value={field.onlyDomains}
/>
<div class="help-block">Use comma as separator.</div>
</Field>
@@ -18,10 +18,10 @@
];
let mimeTypesList = baseMimeTypesList.slice();
let isSingle = field.options?.maxSelect <= 1;
let isSingle = field.maxSelect <= 1;
let oldIsSingle = isSingle;
$: if (CommonHelper.isEmpty(field.options)) {
$: if (typeof field.maxSelect == "undefined") {
loadDefaults();
} else {
appendMissingMimeTypes();
@@ -30,19 +30,17 @@
$: if (oldIsSingle != isSingle) {
oldIsSingle = isSingle;
if (isSingle) {
field.options.maxSelect = 1;
field.maxSelect = 1;
} else {
field.options.maxSelect = field.options?.values?.length || 99;
field.maxSelect = 99;
}
}
function loadDefaults() {
field.options = {
maxSelect: 1,
maxSize: 5242880,
thumbs: [],
mimeTypes: [],
};
field.maxSelect = 1;
field.thumbs = [];
field.mimeTypes = [];
isSingle = true;
oldIsSingle = isSingle;
}
@@ -50,13 +48,13 @@
// append any previously set custom mime types to the predefined
// list for backward compatibility
function appendMissingMimeTypes() {
if (CommonHelper.isEmpty(field.options.mimeTypes)) {
if (CommonHelper.isEmpty(field.mimeTypes)) {
return;
}
const missing = [];
for (const v of field.options.mimeTypes) {
for (const v of field.mimeTypes) {
if (!!mimeTypesList.find((item) => item.mimeType === v)) {
continue; // exist
}
@@ -93,7 +91,7 @@
<svelte:fragment slot="options">
<div class="grid grid-sm">
<div class="col-sm-12">
<Field class="form-field" name="schema.{key}.options.mimeTypes" let:uniqueId>
<Field class="form-field" name="fields.{key}.mimeTypes" let:uniqueId>
<label for={uniqueId}>
<span class="txt">Allowed mime types</span>
<i
@@ -114,7 +112,7 @@
items={mimeTypesList}
labelComponent={MimeTypeSelectOption}
optionComponent={MimeTypeSelectOption}
bind:keyOfSelected={field.options.mimeTypes}
bind:keyOfSelected={field.mimeTypes}
/>
<div class="help-block">
<div tabindex="0" role="button" class="inline-flex flex-gap-0">
@@ -126,7 +124,7 @@
class="dropdown-item closable"
role="menuitem"
on:click={() => {
field.options.mimeTypes = [
field.mimeTypes = [
"image/jpeg",
"image/png",
"image/svg+xml",
@@ -142,7 +140,7 @@
class="dropdown-item closable"
role="menuitem"
on:click={() => {
field.options.mimeTypes = [
field.mimeTypes = [
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@@ -158,7 +156,7 @@
class="dropdown-item closable"
role="menuitem"
on:click={() => {
field.options.mimeTypes = [
field.mimeTypes = [
"video/mp4",
"video/x-ms-wmv",
"video/quicktime",
@@ -173,7 +171,7 @@
class="dropdown-item closable"
role="menuitem"
on:click={() => {
field.options.mimeTypes = [
field.mimeTypes = [
"application/zip",
"application/x-7z-compressed",
"application/x-rar-compressed",
@@ -189,7 +187,7 @@
</div>
<div class={!isSingle ? "col-sm-6" : "col-sm-8"}>
<Field class="form-field" name="schema.{key}.options.thumbs" let:uniqueId>
<Field class="form-field" name="fields.{key}.thumbs" let:uniqueId>
<label for={uniqueId}>
<span class="txt">Thumb sizes</span>
<i
@@ -202,8 +200,8 @@
</label>
<MultipleValueInput
id={uniqueId}
placeholder="eg. 50x50, 480x720"
bind:value={field.options.thumbs}
placeholder="e.g. 50x50, 480x720"
bind:value={field.thumbs}
/>
<div class="help-block">
<span class="txt">Use comma as separator.</span>
@@ -214,27 +212,27 @@
<ul class="m-0">
<li>
<strong>WxH</strong>
(eg. 100x50) - crop to WxH viewbox (from center)
(e.g. 100x50) - crop to WxH viewbox (from center)
</li>
<li>
<strong>WxHt</strong>
(eg. 100x50t) - crop to WxH viewbox (from top)
(e.g. 100x50t) - crop to WxH viewbox (from top)
</li>
<li>
<strong>WxHb</strong>
(eg. 100x50b) - crop to WxH viewbox (from bottom)
(e.g. 100x50b) - crop to WxH viewbox (from bottom)
</li>
<li>
<strong>WxHf</strong>
(eg. 100x50f) - fit inside a WxH viewbox (without cropping)
(e.g. 100x50f) - fit inside a WxH viewbox (without cropping)
</li>
<li>
<strong>0xH</strong>
(eg. 0x50) - resize to H height preserving the aspect ratio
(e.g. 0x50) - resize to H height preserving the aspect ratio
</li>
<li>
<strong>Wx0</strong>
(eg. 100x0) - resize to W width preserving the aspect ratio
(e.g. 100x0) - resize to W width preserving the aspect ratio
</li>
</ul>
</Toggler>
@@ -244,16 +242,24 @@
</div>
<div class={!isSingle ? "col-sm-3" : "col-sm-4"}>
<Field class="form-field required" name="schema.{key}.options.maxSize" let:uniqueId>
<Field class="form-field" name="fields.{key}.maxSize" let:uniqueId>
<label for={uniqueId}>Max file size</label>
<input type="number" id={uniqueId} step="1" min="0" bind:value={field.options.maxSize} />
<input
type="number"
id={uniqueId}
step="1"
min="0"
value={field.maxSize || ""}
on:input={(e) => (field.maxSize = e.target.value << 0)}
placeholder="Default to max ~5MB"
/>
<div class="help-block">Must be in bytes.</div>
</Field>
</div>
{#if !isSingle}
<div class="col-sm-3">
<Field class="form-field required" name="schema.{key}.options.maxSelect" let:uniqueId>
<Field class="form-field" name="fields.{key}.maxSelect" let:uniqueId>
<label for={uniqueId}>Max select</label>
<input
id={uniqueId}
@@ -261,29 +267,31 @@
step="1"
min="2"
required
bind:value={field.options.maxSelect}
placeholder="Default to single"
bind:value={field.maxSelect}
/>
</Field>
</div>
{/if}
</div>
</svelte:fragment>
<svelte:fragment slot="optionsFooter">
<Field class="form-field form-field-toggle" name="schema.{key}.options.protected" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={field.options.protected} />
<label for={uniqueId}>
<span class="txt">Protected</span>
</label>
<a
href={import.meta.env.PB_PROTECTED_FILE_DOCS}
class="toggle-info txt-sm txt-hint m-l-5"
target="_blank"
rel="noopener"
>
(Learn more)
</a>
</Field>
<Field class="form-field form-field-toggle" name="fields.{key}.protected" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={field.protected} />
<label for={uniqueId}>
<span class="txt">Protected</span>
</label>
<small class="txt-hint">
it will require View API rule permissions and file token to be accessible
<a
href={import.meta.env.PB_PROTECTED_FILE_DOCS}
class="toggle-info"
target="_blank"
rel="noopener"
>
(Learn more)
</a>
</small>
</Field>
</div>
</svelte:fragment>
</SchemaField>
@@ -1,30 +1,27 @@
<script>
import { slide } from "svelte/transition";
import CommonHelper from "@/utils/CommonHelper";
import Field from "@/components/base/Field.svelte";
import SchemaField from "@/components/collections/schema/SchemaField.svelte";
import { slide } from "svelte/transition";
export let field;
export let key = "";
let showInfo = false;
$: if (CommonHelper.isEmpty(field.options)) {
loadDefaults();
}
function loadDefaults() {
field.options = {
maxSize: 2000000,
};
}
</script>
<SchemaField bind:field {key} on:rename on:remove on:duplicate {...$$restProps}>
<svelte:fragment slot="options">
<Field class="form-field required m-b-sm" name="schema.{key}.options.maxSize" let:uniqueId>
<Field class="form-field m-b-sm" name="fields.{key}.maxSize" let:uniqueId>
<label for={uniqueId}>Max size <small>(bytes)</small></label>
<input type="number" id={uniqueId} step="1" min="0" bind:value={field.options.maxSize} />
<input
type="number"
id={uniqueId}
step="1"
min="0"
value={field.maxSize || ""}
on:input={(e) => (field.maxSize = e.target.value << 0)}
placeholder="Default to max ~5MB"
/>
</Field>
<button
@@ -11,29 +11,24 @@
<svelte:fragment slot="options">
<div class="grid grid-sm">
<div class="col-sm-6">
<Field class="form-field" name="schema.{key}.options.min" let:uniqueId>
<Field class="form-field" name="fields.{key}.min" let:uniqueId>
<label for={uniqueId}>Min</label>
<input type="number" id={uniqueId} bind:value={field.options.min} />
<input type="number" id={uniqueId} bind:value={field.min} />
</Field>
</div>
<div class="col-sm-6">
<Field class="form-field" name="schema.{key}.options.max" let:uniqueId>
<Field class="form-field" name="fields.{key}.max" let:uniqueId>
<label for={uniqueId}>Max</label>
<input
type="number"
id={uniqueId}
min={field.options.min}
bind:value={field.options.max}
/>
<input type="number" id={uniqueId} min={field.min} bind:value={field.max} />
</Field>
</div>
</div>
</svelte:fragment>
<svelte:fragment slot="optionsFooter">
<Field class="form-field form-field-toggle" name="schema.{key}.options.noDecimal" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={field.options.noDecimal} />
<Field class="form-field form-field-toggle" name="fields.{key}.onlyInt" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={field.onlyInt} />
<label for={uniqueId}>
<span class="txt">No decimals</span>
<i
@@ -0,0 +1,76 @@
<script>
import Field from "@/components/base/Field.svelte";
import SchemaField from "@/components/collections/schema/SchemaField.svelte";
import CommonHelper from "@/utils/CommonHelper";
export let field;
export let key = "";
$: if (CommonHelper.isEmpty(field.id)) {
loadDefaults();
}
function loadDefaults() {
field.cost = 11;
}
</script>
<SchemaField bind:field {key} on:rename on:remove on:duplicate {...$$restProps}>
<svelte:fragment slot="options">
<div class="grid grid-sm">
<div class="col-sm-6">
<Field class="form-field" name="fields.{key}.min" let:uniqueId>
<label for={uniqueId}>Min length</label>
<input
type="number"
id={uniqueId}
step="1"
min="0"
placeholder="No min limit"
value={field.min || ""}
on:input={(e) => (field.min = e.target.value << 0)}
/>
</Field>
</div>
<div class="col-sm-6">
<Field class="form-field" name="fields.{key}.max" let:uniqueId>
<label for={uniqueId}>Max length</label>
<input
type="number"
id={uniqueId}
step="1"
placeholder="Up to 71 chars"
min={field.min || 0}
max="71"
value={field.max || ""}
on:input={(e) => (field.max = e.target.value << 0)}
/>
</Field>
</div>
<div class="col-sm-6">
<Field class="form-field" name="fields.{key}.cost" let:uniqueId>
<label for={uniqueId}>Bcrypt cost</label>
<input
type="number"
id={uniqueId}
placeholder="Default to 10"
step="1"
min="6"
max="31"
value={field.cost || ""}
on:input={(e) => (field.cost = e.target.value << 0)}
/>
</Field>
</div>
<div class="col-sm-6">
<Field class="form-field" name="fields.{key}.pattern" let:uniqueId>
<label for={uniqueId}>Validation pattern</label>
<input type="text" id={uniqueId} placeholder="ex. ^\w+$" bind:value={field.pattern} />
</Field>
</div>
</div>
</svelte:fragment>
</SchemaField>
@@ -1,8 +1,6 @@
<script>
import CommonHelper from "@/utils/CommonHelper";
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import Select from "@/components/base/Select.svelte";
import ObjectSelect from "@/components/base/ObjectSelect.svelte";
import CollectionUpsertPanel from "@/components/collections/CollectionUpsertPanel.svelte";
import SchemaField from "@/components/collections/schema/SchemaField.svelte";
@@ -22,34 +20,32 @@
];
let upsertPanel = null;
let isSingle = field.options?.maxSelect == 1;
let isSingle = field.maxSelect <= 1;
let oldIsSingle = isSingle;
$: selectCollections = $collections.filter((c) => c.type != "view");
$: selectCollections = $collections.filter((c) => !c.system && c.type != "view");
// load defaults
$: if (CommonHelper.isEmpty(field.options)) {
$: if (typeof field.maxSelect == "undefined") {
loadDefaults();
}
$: if (oldIsSingle != isSingle) {
oldIsSingle = isSingle;
if (isSingle) {
field.options.minSelect = null;
field.options.maxSelect = 1;
field.minSelect = 0;
field.maxSelect = 1;
} else {
field.options.maxSelect = null;
field.maxSelect = 999;
}
}
$: selectedColection = $collections.find((c) => c.id == field.options.collectionId) || null;
$: selectedColection = $collections.find((c) => c.id == field.collectionId) || null;
function loadDefaults() {
field.options = {
maxSelect: 1,
collectionId: null,
cascadeDelete: false,
};
field.maxSelect = 1;
field.collectionId = null;
field.cascadeDelete = false;
isSingle = true;
oldIsSingle = isSingle;
}
@@ -62,7 +58,7 @@
<Field
class="form-field required {!interactive ? 'readonly' : ''}"
inlineError
name="schema.{key}.options.collectionId"
name="fields.{key}.collectionId"
let:uniqueId
>
<ObjectSelect
@@ -73,7 +69,7 @@
selectionKey="id"
items={selectCollections}
readonly={!interactive || field.id}
bind:keyOfSelected={field.options.collectionId}
bind:keyOfSelected={field.collectionId}
>
<svelte:fragment slot="afterOptions">
<hr />
@@ -111,35 +107,36 @@
<div class="grid grid-sm">
{#if !isSingle}
<div class="col-sm-6">
<Field class="form-field" name="schema.{key}.options.minSelect" let:uniqueId>
<Field class="form-field" name="fields.{key}.minSelect" let:uniqueId>
<label for={uniqueId}>Min select</label>
<input
type="number"
id={uniqueId}
step="1"
min="1"
min="0"
placeholder="No min limit"
bind:value={field.options.minSelect}
value={field.minSelect || ""}
on:input={(e) => (field.minSelect = e.target.value << 0)}
/>
</Field>
</div>
<div class="col-sm-6">
<Field class="form-field" name="schema.{key}.options.maxSelect" let:uniqueId>
<Field class="form-field" name="fields.{key}.maxSelect" let:uniqueId>
<label for={uniqueId}>Max select</label>
<input
type="number"
id={uniqueId}
step="1"
placeholder="No max limit"
min={field.options.minSelect || 2}
bind:value={field.options.maxSelect}
placeholder="Default to single"
min={field.minSelect || 1}
bind:value={field.maxSelect}
/>
</Field>
</div>
{/if}
<div class="col-sm-12">
<Field class="form-field" name="schema.{key}.options.cascadeDelete" let:uniqueId>
<Field class="form-field" name="fields.{key}.cascadeDelete" let:uniqueId>
<label for={uniqueId}>
<span class="txt">Cascade delete</span>
<!-- prettier-ignore -->
@@ -157,7 +154,7 @@
<ObjectSelect
id={uniqueId}
items={defaultOptions}
bind:keyOfSelected={field.options.cascadeDelete}
bind:keyOfSelected={field.cascadeDelete}
/>
</Field>
</div>
@@ -169,7 +166,7 @@
bind:this={upsertPanel}
on:save={(e) => {
if (e?.detail?.collection?.id && e.detail.collection.type != "view") {
field.options.collectionId = e.detail.collection.id;
field.collectionId = e.detail.collection.id;
}
}}
/>
@@ -1,10 +1,9 @@
<script>
import CommonHelper from "@/utils/CommonHelper";
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import MultipleValueInput from "@/components/base/MultipleValueInput.svelte";
import ObjectSelect from "@/components/base/ObjectSelect.svelte";
import SchemaField from "@/components/collections/schema/SchemaField.svelte";
import MultipleValueInput from "@/components/base/MultipleValueInput.svelte";
export let field;
export let key = "";
@@ -14,27 +13,25 @@
{ label: "Multiple", value: false },
];
let isSingle = field.options?.maxSelect <= 1;
let isSingle = field.maxSelect <= 1;
let oldIsSingle = isSingle;
$: if (CommonHelper.isEmpty(field.options)) {
$: if (typeof field.maxSelect == "undefined") {
loadDefaults();
}
$: if (oldIsSingle != isSingle) {
oldIsSingle = isSingle;
if (isSingle) {
field.options.maxSelect = 1;
field.maxSelect = 1;
} else {
field.options.maxSelect = field.options?.values?.length || 2;
field.maxSelect = field.values?.length || 2;
}
}
function loadDefaults() {
field.options = {
maxSelect: 1,
values: [],
};
field.maxSelect = 1;
field.values = [];
isSingle = true;
oldIsSingle = isSingle;
}
@@ -47,7 +44,7 @@
<Field
class="form-field required {!interactive ? 'readonly' : ''}"
inlineError
name="schema.{key}.options.values"
name="fields.{key}.values"
let:uniqueId
>
<div use:tooltip={{ text: "Choices (comma separated)", position: "top-left", delay: 700 }}>
@@ -56,7 +53,7 @@
placeholder="Choices: eg. optionA, optionB"
required
readonly={!interactive}
bind:value={field.options.values}
bind:value={field.values}
/>
</div>
</Field>
@@ -81,15 +78,16 @@
<svelte:fragment slot="options">
{#if !isSingle}
<Field class="form-field required" name="schema.{key}.options.maxSelect" let:uniqueId>
<Field class="form-field" name="fields.{key}.maxSelect" let:uniqueId>
<label for={uniqueId}>Max select</label>
<input
id={uniqueId}
type="number"
step="1"
min="2"
required
bind:value={field.options.maxSelect}
max={field.values.length}
placeholder="Default to single"
bind:value={field.maxSelect}
/>
</Field>
{/if}
@@ -1,6 +1,7 @@
<script>
import SchemaField from "@/components/collections/schema/SchemaField.svelte";
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import SchemaField from "@/components/collections/schema/SchemaField.svelte";
export let field;
export let key = "";
@@ -9,37 +10,73 @@
<SchemaField bind:field {key} on:rename on:remove on:duplicate {...$$restProps}>
<svelte:fragment slot="options">
<div class="grid grid-sm">
<div class="col-sm-3">
<Field class="form-field" name="schema.{key}.options.min" let:uniqueId>
<label for={uniqueId}>Min length</label>
<input type="number" id={uniqueId} step="1" min="0" bind:value={field.options.min} />
</Field>
</div>
<div class="col-sm-3">
<Field class="form-field" name="schema.{key}.options.max" let:uniqueId>
<label for={uniqueId}>Max length</label>
<div class="col-sm-6">
<Field class="form-field" name="fields.{key}.min" let:uniqueId>
<label for={uniqueId}>
<span class="txt">Min length</span>
<i
class="ri-information-line link-hint"
use:tooltip={"Clear the field or set it to 0 for no limit."}
/>
</label>
<input
type="number"
id={uniqueId}
step="1"
min={field.options.min || 0}
bind:value={field.options.max}
min="0"
placeholder="No min limit"
value={field.min || ""}
on:input={(e) => (field.min = e.target.value << 0)}
/>
</Field>
</div>
<div class="col-sm-6">
<Field class="form-field" name="schema.{key}.options.pattern" let:uniqueId>
<label for={uniqueId}>Regex pattern</label>
<Field class="form-field" name="fields.{key}.max" let:uniqueId>
<label for={uniqueId}>
<span class="txt">Max length</span>
<i
class="ri-information-line link-hint"
use:tooltip={"Clear the field or set it to 0 to fallback to the default limit."}
/>
</label>
<input
type="text"
type="number"
id={uniqueId}
placeholder={"Valid Go regular expression, eg. ^\\w+$"}
bind:value={field.options.pattern}
step="1"
placeholder="Default to max 5000 characters"
min={field.min || 0}
value={field.max || ""}
on:input={(e) => (field.max = e.target.value << 0)}
/>
</Field>
</div>
<div class="col-sm-6">
<Field class="form-field" name="fields.{key}.pattern" let:uniqueId>
<label for={uniqueId}>Validation pattern</label>
<input type="text" id={uniqueId} bind:value={field.pattern} />
<div class="help-block">
<p>Ex. <code>{"^[a-z0-9]+$"}</code></p>
</div>
</Field>
</div>
<div class="col-sm-6">
<Field class="form-field" name="fields.{key}.pattern" let:uniqueId>
<label for={uniqueId}>
<span class="txt">Autogenerate pattern</span>
<i
class="ri-information-line link-hint"
use:tooltip={"Set and autogenerate text matching the pattern on missing record create value."}
/>
</label>
<input type="text" id={uniqueId} bind:value={field.autogeneratePattern} />
<div class="help-block">
<p>Ex. <code>{"[a-z0-9]{30}"}</code></p>
</div>
</Field>
</div>
</div>
</svelte:fragment>
</SchemaField>