This commit is contained in:
2023-10-02 23:10:49 +03:00
commit c6cb488379
255 changed files with 18731 additions and 0 deletions
@@ -0,0 +1,60 @@
<script>
import {createEventDispatcher} from "svelte";
const dispatch = createEventDispatcher();
export let schema;
export let operators;
export let key;
export let value;
export let inModal;
export let modalUrl;
export let systemFields;
let filterSplit = key.split("_");
let operator = filterSplit[filterSplit.length - 1] ?? "eq";
let fieldName = key.replace("_" + operator, "");
let filterField = schema.fields.find((f) => f.name === fieldName);
let filterLabel = filterField?.label ?? fieldName;
function removeFilter(e, k) {
e.preventDefault();
let filterKey = `filter[${k}]`;
const url = new URL(modalUrl ?? window.location.href);
url.searchParams.set("skip", "0");
url.searchParams.delete(filterKey);
if (inModal) {
dispatch("refresh", url);
} else {
window.location = url;
}
}
</script>
<span
class="applied-filter d-inline-block border border-primary rounded lx-small-text me-1 px-2 py-1"
style="line-height:22px ;"
>
<div class="d-flex align-items-center justify-content-center">
{filterLabel}
{operators.find((o) => o.name === operator)?.symbol ?? ""}
{value}
<button
on:click={(e) => removeFilter(e, key)}
type="button"
class="btn-close btn-close ms-1"
aria-label="Close"
/>
</div>
</span>
<style>
.applied-filter {
background-color: #fff;
}
.applied-filter:hover {
opacity: .8;
background-color: #eee;
}
</style>
@@ -0,0 +1,131 @@
<script>
import Icon from "../../common/Icon.svelte";
import {createEventDispatcher} from "svelte";
const dispatch = createEventDispatcher();
export let schema;
export let systemFields = [];
export let operators;
export let inModal;
export let modalUrl;
let search = "";
let systemFieldsFiltered = systemFields;
if (schema.type == "collection") {
systemFieldsFiltered = systemFields.filter((f) => f.files === false);
}
let filterableFields = [...schema.fields, ...systemFieldsFiltered].filter(
(f) => !["file", "json", "tab"].includes(f.ui)
);
let selectedField;
let selectedInput = "";
$: operatorsFiltered = operators.filter(
(o) => o.uis.includes(selectedField?.ui) || o.uis[0] == "*"
);
$: selectedOperator = operatorsFiltered[0];
function addFilter(e) {
e.preventDefault();
let filterPrefix = "";
if (schema.fields.find(f => f.name === selectedField.name)) {
filterPrefix = "data.";
}
let filterKey = `filter[${filterPrefix + selectedField.name}_${selectedOperator.name}]`;
const url = new URL(modalUrl ?? window.location.href);
url.searchParams.set("skip", "0");
url.searchParams.set(filterKey, selectedInput);
if (inModal) {
dispatch("refresh", url);
} else {
window.location = url;
}
}
function submitSearch(e) {
e.preventDefault();
let filterKeyValue = search.split("=")[0] ?? "";
if (!filterKeyValue) {
return;
}
let filterKey = `filter[${filterKeyValue}]`;
let filterValue = search.split("=")[1] ?? "";
if (!filterValue) {
return;
}
const url = new URL(modalUrl ?? window.location.href);
url.searchParams.set("skip", "0");
url.searchParams.set(filterKey, filterValue);
if (inModal) {
dispatch("refresh", url);
} else {
window.location = url;
}
}
</script>
<div class="mx-2 d-flex align-items-center">
<div class="btn-group">
<button
class="btn btn-sm btn-outline-primary dropdown-toggle d-flex align-items-center"
type="button"
data-bs-toggle="dropdown"
data-bs-auto-close="outside"
aria-expanded="false"
>
<Icon icon="filter"/>
<span class="ms-1">Filter</span>
</button>
<div class="dropdown-menu" style="width:300px;">
<div class="px-3 py-1 d-flex align-items-center">
<select bind:value={selectedField} class="form-select">
{#each filterableFields as field}
<option value={field}>{field.label}</option>
{/each}
</select>
</div>
<div class="px-3 py-1 d-flex align-items-center">
<select class="form-select" bind:value={selectedOperator}>
{#each operatorsFiltered as operator}
<option value={operator}>{operator.label}</option>
{/each}
</select>
</div>
<div class="px-3 py-1 d-flex align-items-center">
<input
type="text"
class="form-control"
bind:value={selectedInput}
/>
</div>
<div class="px-3 py-1 d-flex align-items-center">
<button
on:click={addFilter}
class="btn btn-outline-primary"
type="button"
>
Add filter
</button>
</div>
<hr/>
<div><h6 class="dropdown-header">Advanced filters</h6></div>
<form on:submit={submitSearch}>
<div class="px-3 py-1 d-flex align-items-center">
<input
bind:value={search}
type="search"
class="form-control"
placeholder="Advanced filters"
required
/>
</div>
</form>
</div>
</div>
</div>
@@ -0,0 +1,133 @@
<script>
import Icon from "../../common/Icon.svelte";
import {createEventDispatcher} from "svelte";
const dispatch = createEventDispatcher();
export let schema;
export let sort;
export let inModal;
export let modalUrl;
export let systemFields = [];
$: activeField = [...schema.fields, ...systemFields].find(
(f) => f.name === sort || "-" + f.name === sort || "data." + f.name === sort || "-data." + f.name === sort
);
$: sortableFields = schema.fields.filter(
(f) => !["reference", "file", "json", "id", "tab"].includes(f.ui)
);
$: systemFieldsFiltered = systemFields;
$: if (schema.type === "collection") {
systemFieldsFiltered = systemFields.filter((f) => f.files === false);
}
function sortField(fieldSort) {
const url = new URL(modalUrl ?? window.location.href);
url.searchParams.set("sort", fieldSort);
if (inModal) {
dispatch("refresh", url);
} else {
window.location = url;
}
}
function sortAsc(e, field) {
e.preventDefault();
let prefix = systemFields.includes(el => el.name === field.name) ? "" : "data.";
return sortField(prefix + field.name);
}
function sortDesc(e, field) {
e.preventDefault();
let prefix = systemFields.includes(el => el.name === field.name) ? "" : "data.";
return sortField("-" + prefix + field.name);
}
</script>
<div class=" ">
<button
class="btn btn-sm btn-outline-primary dropdown-toggle d-flex align-items-center"
type="button"
data-bs-toggle="dropdown"
data-bs-auto-close="outside"
aria-expanded="false"
>
{#if sort.startsWith("-")}
<Icon icon="arrow-down-wide-short"/>
{:else}
<Icon icon="arrow-up-short-wide"/>
{/if}
<span class="ms-1">{activeField.label}</span>
</button>
<div class="dropdown-menu" style="width:auto;max-width:800px;">
<div class="row">
{#each sortableFields as field}
<div class="col-4 px-3 py-1 d-flex align-items-center">
<div class="btn-group w-100">
<button
on:click={(e) => sortAsc(e, field)}
title="Sort Ascending"
class="btn btn-sm {field.name == sort
? 'btn-primary'
: 'btn-outline-primary'} "
>
<Icon icon="arrow-up-short-wide"/>
</button>
<button
on:click={(e) => sortDesc(e, field)}
title="Sort Descending"
class="btn btn-sm {'-' + field.name == sort
? 'btn-primary'
: 'btn-outline-primary'} "
>
<Icon icon="arrow-down-wide-short"/>
</button>
<button
title="Sort Ascending"
on:click={(e) => sortAsc(e, field)}
class="btn btn-sm btn-outline-primary w-100 text-nowrap"
style="overflow: hidden;"
>
{field.label}
</button>
</div>
</div>
{/each}
</div>
<h6 class="dropdown-header px-0">System</h6>
<div class="row">
{#each systemFieldsFiltered as field}
<div class="col-4 px-3 py-1 d-flex align-items-center">
<div class="btn-group w-100">
<button
on:click={(e) => sortAsc(e, field)}
title="Sort Ascending"
class="btn btn-sm {field.name == sort
? 'btn-primary'
: 'btn-outline-primary'} "
>
<Icon icon="arrow-up-short-wide"/>
</button>
<button
on:click={(e) => sortDesc(e, field)}
title="Sort Descending"
class="btn btn-sm {'-' + field.name == sort
? 'btn-primary'
: 'btn-outline-primary'} "
>
<Icon icon="arrow-down-wide-short"/>
</button>
<button
title="Sort Ascending"
on:click={(e) => sortAsc(e, field)}
class="btn btn-sm btn-outline-primary w-100 text-nowrap"
style="overflow: hidden;"
>
{field.label}
</button>
</div>
</div>
{/each}
</div>
</div>
</div>
+115
View File
@@ -0,0 +1,115 @@
<script>
import FilterFields from "./FilterFields.svelte";
import Uploader from "../../files/Uploader.svelte";
import Icon from "../../common/Icon.svelte";
import SortFields from "./SortFields.svelte";
import AppliedFilter from "./AppliedFilter.svelte";
import {getContext} from "svelte";
const channel = getContext("channel");
export let sort;
export let schema;
export let operators;
export let filter;
export let inModal;
export let modalUrl;
export let records;
export let systemFields = [];
export let visibleFields = [];
let url = new URL(window.location.href);
let csvUrl = url.pathname + "/csv?" + url.searchParams.toString();
function uploadComplete(e) {
records = e.detail;
}
</script>
<div class="mb-3 d-flex align-items-center justify-content-between">
<div class=" d-flex">
<SortFields
{schema}
{sort}
{systemFields}
{inModal}
{modalUrl}
on:refresh
/>
<FilterFields
bind:schema
{systemFields}
{operators}
{filter}
{inModal}
{modalUrl}
on:refresh
/>
{#if Object.entries(filter).length > 0}
{#each Object.entries(filter) as [k, v]}
<AppliedFilter
{schema}
{operators}
key={k}
value={v}
{inModal}
{modalUrl}
{systemFields}
on:refresh
/>
{/each}
{/if}
</div>
<div class="d-flex align-items-center ">
{#if schema.type === "collection"}
{#if !inModal}
<a
href="{channel.lucentUrl}/records/new?schema={schema.name}"
class="btn btn-sm btn-primary"
>
New Record
</a>
{/if}
{:else }
<div class="d-inline-block ms-1">
<Uploader {schema} on:uploadComplete={uploadComplete}/>
</div>
{/if}
{#if !inModal}
<div class="dropdown d-inline-block">
<button
class="btn btn-link btn-sm"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<Icon icon="ellipsis-vertical"/>
</button>
<ul class="dropdown-menu">
<li>
<a
class="dropdown-item"
href={csvUrl}
>Export to CSV</a
>
</li>
<li>
<a
class="dropdown-item"
href="{channel.lucentUrl}/content/{schema.name}?filter[_sys.status_in]=trashed"
>View trashed records</a
>
</li>
</ul>
</div>
{/if}
</div>
</div>