added predefined mime types list and other minor ui improvements

This commit is contained in:
Gani Georgiev
2023-01-24 20:58:24 +02:00
parent e5477961ad
commit ecfae2e5c9
53 changed files with 554 additions and 343 deletions
+5 -1
View File
@@ -10,6 +10,7 @@
import SortHeader from "@/components/base/SortHeader.svelte";
import FormattedDate from "@/components/base/FormattedDate.svelte";
import HorizontalScroller from "@/components/base/HorizontalScroller.svelte";
import CopyIcon from "@/components/base/CopyIcon.svelte";
import SettingsSidebar from "@/components/settings/SettingsSidebar.svelte";
import AdminUpsertPanel from "@/components/admins/AdminUpsertPanel.svelte";
@@ -148,7 +149,10 @@
</td>
<td class="col-type-text col-field-id">
<span class="label">{admin.id}</span>
<div class="label">
<CopyIcon value={admin.id} />
<span class="txt">{admin.id}</span>
</div>
{#if admin.id === $loggedAdmin.id}
<span class="label label-warning m-l-5">You</span>
{/if}
+41
View File
@@ -0,0 +1,41 @@
<script>
import { onMount } from "svelte";
import CommonHelper from "@/utils/CommonHelper";
import tooltip from "@/actions/tooltip";
export let value = "";
export let idleClasses = "ri-file-copy-line txt-sm link-hint";
export let successClasses = "ri-check-line txt-sm txt-success";
export let successDuration = 500; // ms
let copyTimeout;
function copy() {
if (!value) {
return;
}
CommonHelper.copyToClipboard(value);
clearTimeout(copyTimeout);
copyTimeout = setTimeout(() => {
clearTimeout(copyTimeout);
copyTimeout = null;
}, successDuration);
}
onMount(() => {
return () => {
if (copyTimeout) {
clearTimeout(copyTimeout);
}
};
});
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<i
class={copyTimeout ? successClasses : idleClasses}
use:tooltip={!copyTimeout ? "Copy" : ""}
on:click|stopPropagation={copy}
/>
@@ -0,0 +1,6 @@
<script>
export let item = {}; // {ext, mimeType}
</script>
<span class="txt">{item.ext || "N/A"}</span>
<small class="txt-hint">{item.mimeType}</small>
+4 -4
View File
@@ -93,13 +93,13 @@
dispatch("show");
document.body.classList.add("overlay-active");
} else {
clearTimeout(contentScrollThrottle);
oldFocusedElem?.focus();
dispatch("hide");
if (getHolder().querySelectorAll(".overlay-panel-container.active").length <= 1) {
document.body.classList.remove("overlay-active");
}
clearTimeout(contentScrollThrottle);
oldFocusedElem?.focus();
dispatch("hide");
}
await tick();
+1 -1
View File
@@ -11,9 +11,9 @@
export let items = [];
export let multiple = false;
export let disabled = false;
export let closable = true;
export let selected = multiple ? [] : undefined;
export let toggle = multiple; // toggle option on click
export let closable = true; // close the dropdown on option select/deselect
export let labelComponent = undefined; // custom component to use for each selected option label
export let labelComponentProps = {}; // props to pass to the custom option component
export let optionComponent = undefined; // custom component to use for each dropdown option item
@@ -20,7 +20,6 @@
import JsonOptions from "@/components/collections/schema/JsonOptions.svelte";
import FileOptions from "@/components/collections/schema/FileOptions.svelte";
import RelationOptions from "@/components/collections/schema/RelationOptions.svelte";
import UserOptions from "@/components/collections/schema/UserOptions.svelte";
const dispatch = createEventDispatcher();
@@ -275,8 +274,6 @@
<FileOptions {key} bind:options={field.options} />
{:else if field.type === "relation"}
<RelationOptions {key} bind:options={field.options} />
{:else if field.type === "user"}
<UserOptions {key} bind:options={field.options} />
{/if}
</div>
@@ -3,11 +3,16 @@
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import Toggler from "@/components/base/Toggler.svelte";
import ObjectSelect from "@/components/base/ObjectSelect.svelte";
import MimeTypeSelectOption from "@/components/base/MimeTypeSelectOption.svelte";
import MultipleValueInput from "@/components/base/MultipleValueInput.svelte";
import baseMimeTypesList from "@/mimes.js";
export let key = "";
export let options = {};
let mimeTypesList = baseMimeTypesList.slice();
$: if (CommonHelper.isEmpty(options)) {
// load defaults
options = {
@@ -16,6 +21,30 @@
thumbs: [],
mimeTypes: [],
};
} else {
appendMissingMimeTypes();
}
// append any previously set custom mime types to the predefined
// list for backward compatibility
function appendMissingMimeTypes() {
if (CommonHelper.isEmpty(options.mimeTypes)) {
return;
}
const missing = [];
for (const v of options.mimeTypes) {
if (!!mimeTypesList.find((item) => item.mimeType === v)) {
continue; // exist
}
missing.push({ mimeType: v });
}
if (missing.length) {
mimeTypesList = mimeTypesList.concat(missing);
}
}
</script>
@@ -37,7 +66,7 @@
<div class="col-sm-12">
<Field class="form-field" name="schema.{key}.options.mimeTypes" let:uniqueId>
<label for={uniqueId}>
<span class="txt">Mime types</span>
<span class="txt">Allowed mime types</span>
<i
class="ri-information-line link-hint"
use:tooltip={{
@@ -46,23 +75,28 @@
}}
/>
</label>
<MultipleValueInput
<ObjectSelect
id={uniqueId}
placeholder="eg. image/png, application/pdf..."
bind:value={options.mimeTypes}
multiple
searchable
closable={false}
selectionKey="mimeType"
selectPlaceholder="No restriction"
items={mimeTypesList}
labelComponent={MimeTypeSelectOption}
optionComponent={MimeTypeSelectOption}
bind:keyOfSelected={options.mimeTypes}
/>
<div class="help-block">
<span class="txt">Use comma as separator.</span>
<button type="button" class="inline-flex flex-gap-0">
<span class="txt link-primary">Choose presets</span>
<i class="ri-arrow-drop-down-fill" />
<Toggler class="dropdown dropdown-sm dropdown-nowrap">
<Toggler class="dropdown dropdown-sm dropdown-nowrap dropdown-left">
<button
type="button"
class="dropdown-item closable"
on:click={() => {
options.mimeTypes = [
"image/jpg",
"image/jpeg",
"image/png",
"image/svg+xml",
@@ -115,15 +149,6 @@
>
<span class="txt">Archives (zip, 7zip, rar)</span>
</button>
<a
href="https://github.com/gabriel-vasile/mimetype/blob/master/supported_mimes.md"
class="btn btn-sm btn-hint closable"
target="_blank"
rel="noreferrer noopener"
on:click|stopPropagation
>
List with all supported mimetypes
</a>
</Toggler>
</button>
</div>
@@ -1,36 +0,0 @@
<script>
import CommonHelper from "@/utils/CommonHelper";
import Field from "@/components/base/Field.svelte";
import ObjectSelect from "@/components/base/ObjectSelect.svelte";
const defaultOptions = [
{ label: "False", value: false },
{ label: "True", value: true },
];
export let key = "";
export let options = {};
// load defaults
$: if (CommonHelper.isEmpty(options)) {
options = {
maxSelect: 1,
cascadeDelete: false,
};
}
</script>
<div class="grid">
<div class="col-sm-6">
<Field class="form-field required" name="schema.{key}.options.maxSelect" let:uniqueId>
<label for={uniqueId}>Max select</label>
<input type="number" id={uniqueId} step="1" min="1" required bind:value={options.maxSelect} />
</Field>
</div>
<div class="col-sm-6">
<Field class="form-field" name="schema.{key}.options.cascadeDelete" let:uniqueId>
<label for={uniqueId}>Delete record on user delete</label>
<ObjectSelect id={uniqueId} items={defaultOptions} bind:keyOfSelected={options.cascadeDelete} />
</Field>
</div>
</div>
@@ -42,6 +42,8 @@
let initialFormHash = "";
let activeTab = TAB_FORM;
$: hasEditorField = !!collection?.schema?.find((f) => f.type === "editor");
$: hasFileChanges =
CommonHelper.hasNonEmptyProps(uploadedFilesMap) ||
CommonHelper.hasNonEmptyProps(deletedFileIndexesMap);
@@ -223,7 +225,11 @@
<OverlayPanel
bind:this={recordPanel}
class="overlay-panel-lg record-panel {collection?.isAuth && !record.isNew ? 'colored-header' : ''}"
class="
record-panel
{hasEditorField ? 'overlay-panel-xl' : 'overlay-panel-lg'}
{collection?.isAuth && !record.isNew ? 'colored-header' : ''}
"
beforeHide={() => {
if (hasChanges && confirmClose) {
confirm("You have unsaved changes. Do you really want to close the panel?", () => {
+5 -1
View File
@@ -9,6 +9,7 @@
import SortHeader from "@/components/base/SortHeader.svelte";
import Toggler from "@/components/base/Toggler.svelte";
import Field from "@/components/base/Field.svelte";
import CopyIcon from "@/components/base/CopyIcon.svelte";
import FormattedDate from "@/components/base/FormattedDate.svelte";
import HorizontalScroller from "@/components/base/HorizontalScroller.svelte";
import RecordFieldCell from "@/components/records/RecordFieldCell.svelte";
@@ -379,7 +380,10 @@
{#if !hiddenColumns.includes("@id")}
<td class="col-type-text col-field-id">
<div class="flex flex-gap-5">
<span class="label">{record.id}</span>
<div class="label">
<CopyIcon value={record.id} />
<div class="txt">{record.id}</div>
</div>
{#if collection.isAuth}
{#if record.verified}
@@ -27,6 +27,7 @@
id={uniqueId}
toggle={!field.required || isMultiple}
multiple={isMultiple}
closable={!isMultiple}
items={field.options?.values}
searchable={field.options?.values > 5}
bind:selected={value}
@@ -43,12 +43,6 @@
<span class="txt">{title}</span>
</div>
{#if config.enabled}
<span class="label label-success">Enabled</span>
{:else}
<span class="label label-hint">Disabled</span>
{/if}
<div class="flex-fill" />
{#if hasErrors}
@@ -58,6 +52,12 @@
use:tooltip={{ text: "Has errors", position: "left" }}
/>
{/if}
{#if config.enabled}
<span class="label label-success">Enabled</span>
{:else}
<span class="label label-hint">Disabled</span>
{/if}
</svelte:fragment>
<Field class="form-field form-field-toggle m-b-0" name="{key}.enabled" let:uniqueId>
@@ -65,28 +65,26 @@
<label for={uniqueId}>Enable</label>
</Field>
{#if config.enabled}
<div class="grid" transition:slide|local={{ duration: 200 }}>
<div class="col-12 spacing" />
<div class="col-lg-6">
<Field class="form-field required" name="{key}.clientId" let:uniqueId>
<label for={uniqueId}>Client ID</label>
<input type="text" id={uniqueId} bind:value={config.clientId} required />
</Field>
</div>
<div class="col-lg-6">
<Field class="form-field required" name="{key}.clientSecret" let:uniqueId>
<label for={uniqueId}>Client Secret</label>
<RedactedPasswordInput bind:value={config.clientSecret} id={uniqueId} required />
</Field>
</div>
{#if optionsComponent}
<div class="col-lg-12">
<svelte:component this={optionsComponent} {key} bind:config />
</div>
{/if}
<div class="grid">
<div class="col-12 spacing" />
<div class="col-lg-6">
<Field class="form-field required" name="{key}.clientId" let:uniqueId>
<label for={uniqueId}>Client ID</label>
<input type="text" id={uniqueId} bind:value={config.clientId} required />
</Field>
</div>
{/if}
<div class="col-lg-6">
<Field class="form-field required" name="{key}.clientSecret" let:uniqueId>
<label for={uniqueId}>Client Secret</label>
<RedactedPasswordInput bind:value={config.clientSecret} id={uniqueId} required />
</Field>
</div>
{#if optionsComponent}
<div class="col-lg-12">
<svelte:component this={optionsComponent} {key} bind:config />
</div>
{/if}
</div>
</Accordion>
@@ -86,7 +86,7 @@
<div class="wrapper">
<form class="panel" autocomplete="off" on:submit|preventDefault={save}>
<h6 class="m-b-base">Manage the allowed users sign-in/sign-up methods.</h6>
<h6 class="m-b-base">Manage the allowed users OAuth2 sign-in/sign-up methods.</h6>
{#if isLoading}
<div class="loader" />
@@ -122,7 +122,6 @@
class="btn btn-expanded"
class:btn-loading={isSaving}
disabled={!hasChanges || isSaving}
on:click={() => save()}
>
<span class="txt">Save changes</span>
</button>
@@ -10,13 +10,7 @@
<div class="col-lg-12">
<Field class="form-field required" name="{key}.authUrl" let:uniqueId>
<label for={uniqueId}>Auth URL</label>
<input
type="url"
id={uniqueId}
required
placeholder="https://login.microsoftonline.com/YOUR_DIRECTORY_TENANT_ID/oauth2/v2.0/authorize"
bind:value={config.authUrl}
/>
<input type="url" id={uniqueId} required bind:value={config.authUrl} />
<div class="help-block">
Eg. {`https://login.microsoftonline.com/YOUR_DIRECTORY_TENANT_ID/oauth2/v2.0/authorize`}
</div>
@@ -25,13 +19,7 @@
<div class="col-lg-12">
<Field class="form-field required" name="{key}.tokenUrl" let:uniqueId>
<label for={uniqueId}>Token URL</label>
<input
type="text"
id={uniqueId}
required
placeholder="https://login.microsoftonline.com/YOUR_DIRECTORY_TENANT_ID/oauth2/v2.0/token"
bind:value={config.tokenUrl}
/>
<input type="text" id={uniqueId} required bind:value={config.tokenUrl} />
<div class="help-block">
Eg. {`https://login.microsoftonline.com/YOUR_DIRECTORY_TENANT_ID/oauth2/v2.0/token`}
</div>