added view collection type
This commit is contained in:
@@ -93,6 +93,12 @@
|
||||
if (!collection?.options.allowOAuth2Auth) {
|
||||
delete tabs["auth-with-oauth2"];
|
||||
}
|
||||
} else if (collection.isView) {
|
||||
tabs = Object.assign({}, baseTabs);
|
||||
delete tabs.create;
|
||||
delete tabs.update;
|
||||
delete tabs.delete;
|
||||
delete tabs.realtime;
|
||||
} else {
|
||||
tabs = Object.assign({}, baseTabs);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { Collection } from "pocketbase";
|
||||
import { errors, removeError } from "@/stores/errors";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import Field from "@/components/base/Field.svelte";
|
||||
|
||||
export let collection = new Collection();
|
||||
|
||||
let codeEditorComponent;
|
||||
let isCodeEditorComponentLoading = false;
|
||||
let schemaErrors = [];
|
||||
|
||||
$: checkSchemaErrors($errors);
|
||||
|
||||
function checkSchemaErrors(errs) {
|
||||
schemaErrors = [];
|
||||
|
||||
const raw = CommonHelper.getNestedVal(errs, "schema", null);
|
||||
|
||||
if (CommonHelper.isEmpty(raw)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// generic schema error
|
||||
// ---
|
||||
if (raw?.message) {
|
||||
schemaErrors.push(raw?.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// schema fields error
|
||||
// ---
|
||||
const columns = CommonHelper.extractColumnsFromQuery(collection?.options?.query);
|
||||
// remove base system fields
|
||||
CommonHelper.removeByValue(columns, "id");
|
||||
CommonHelper.removeByValue(columns, "created");
|
||||
CommonHelper.removeByValue(columns, "updated");
|
||||
|
||||
for (let idx in raw) {
|
||||
for (let key in raw[idx]) {
|
||||
const message = raw[idx][key].message;
|
||||
const fieldName = columns[idx] || idx;
|
||||
|
||||
schemaErrors.push(CommonHelper.sentenize(fieldName + ": " + message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
isCodeEditorComponentLoading = true;
|
||||
|
||||
try {
|
||||
codeEditorComponent = (await import("@/components/base/CodeEditor.svelte")).default;
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
|
||||
isCodeEditorComponentLoading = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<Field class="form-field required {schemaErrors.length ? 'error' : ''}" name="options.query" let:uniqueId>
|
||||
<label for={uniqueId}>Select query</label>
|
||||
|
||||
{#if isCodeEditorComponentLoading}
|
||||
<textarea disabled rows="7" placeholder="Loading..." />
|
||||
{:else}
|
||||
<svelte:component
|
||||
this={codeEditorComponent}
|
||||
id={uniqueId}
|
||||
placeholder="eg. SELECT id, name from posts"
|
||||
language="sql"
|
||||
minHeight="150"
|
||||
on:change={() => {
|
||||
if (schemaErrors.length) {
|
||||
removeError("schema");
|
||||
}
|
||||
}}
|
||||
bind:value={collection.options.query}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="help-block">
|
||||
<ul>
|
||||
<li>Wildcard (<code>*</code>) columns are not supported.</li>
|
||||
<li>
|
||||
The query must have a unique <code>id</code> column.
|
||||
<br />
|
||||
If your query doesn't have a suitable one, you can use
|
||||
<code>(ROW_NUMBER() OVER()) as id</code>.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{#if schemaErrors.length}
|
||||
<div class="help-block help-block-error">
|
||||
<div class="content">
|
||||
{#each schemaErrors as err}
|
||||
<p>{err}</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</Field>
|
||||
@@ -1,10 +1,13 @@
|
||||
<script>
|
||||
import { slide } from "svelte/transition";
|
||||
import { Collection } from "pocketbase";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import RuleField from "@/components/collections/RuleField.svelte";
|
||||
|
||||
export let collection = new Collection();
|
||||
|
||||
$: fields = CommonHelper.getAllCollectionIdentifiers(collection);
|
||||
|
||||
let showFiltersInfo = false;
|
||||
</script>
|
||||
|
||||
@@ -31,15 +34,8 @@
|
||||
<div class="content">
|
||||
<p class="m-b-0">The following record fields are available:</p>
|
||||
<div class="inline-flex flex-gap-5">
|
||||
<code>id</code>
|
||||
<code>created</code>
|
||||
<code>updated</code>
|
||||
{#each collection.schema as field}
|
||||
{#if field.type === "relation" || field.type === "user"}
|
||||
<code>{field.name}.*</code>
|
||||
{:else}
|
||||
<code>{field.name}</code>
|
||||
{/if}
|
||||
{#each fields as name}
|
||||
<code>{name}</code>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -84,14 +80,16 @@
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
<RuleField label="View action" formKey="viewRule" {collection} bind:rule={collection.viewRule} />
|
||||
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
<RuleField label="Create action" formKey="createRule" {collection} bind:rule={collection.createRule} />
|
||||
{#if !collection?.isView}
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
<RuleField label="Create action" formKey="createRule" {collection} bind:rule={collection.createRule} />
|
||||
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
<RuleField label="Update action" formKey="updateRule" {collection} bind:rule={collection.updateRule} />
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
<RuleField label="Update action" formKey="updateRule" {collection} bind:rule={collection.updateRule} />
|
||||
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
<RuleField label="Delete action" formKey="deleteRule" {collection} bind:rule={collection.deleteRule} />
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
<RuleField label="Delete action" formKey="deleteRule" {collection} bind:rule={collection.deleteRule} />
|
||||
{/if}
|
||||
|
||||
{#if collection?.isAuth}
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
$: deletedFields = collection?.schema.filter((field) => field.id && field.toDelete) || [];
|
||||
|
||||
$: showChanges = isCollectionRenamed || !collection?.isView;
|
||||
|
||||
export async function show(collectionToCheck) {
|
||||
collection = collectionToCheck;
|
||||
|
||||
@@ -50,8 +52,8 @@
|
||||
</div>
|
||||
<div class="content txt-bold">
|
||||
<p>
|
||||
If any of the following changes is part of another collection rule or filter, you'll have to
|
||||
update it manually!
|
||||
If any of the collection changes is part of another collection rule, filter or view query,
|
||||
you'll have to update it manually!
|
||||
</p>
|
||||
{#if deletedFields.length}
|
||||
<p>All data associated with the removed fields will be permanently deleted!</p>
|
||||
@@ -59,36 +61,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6>Changes:</h6>
|
||||
<ul class="changes-list">
|
||||
{#if isCollectionRenamed}
|
||||
<li>
|
||||
<div class="inline-flex">
|
||||
Renamed collection
|
||||
<strong class="txt-strikethrough txt-hint">{collection.originalName}</strong>
|
||||
<i class="ri-arrow-right-line txt-sm" />
|
||||
<strong class="txt"> {collection.name}</strong>
|
||||
</div>
|
||||
</li>
|
||||
{/if}
|
||||
{#if showChanges}
|
||||
<h6>Changes:</h6>
|
||||
<ul class="changes-list">
|
||||
{#if isCollectionRenamed}
|
||||
<li>
|
||||
<div class="inline-flex">
|
||||
Renamed collection
|
||||
<strong class="txt-strikethrough txt-hint">{collection.originalName}</strong>
|
||||
<i class="ri-arrow-right-line txt-sm" />
|
||||
<strong class="txt"> {collection.name}</strong>
|
||||
</div>
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
{#each renamedFields as field}
|
||||
<li>
|
||||
<div class="inline-flex">
|
||||
Renamed field
|
||||
<strong class="txt-strikethrough txt-hint">{field.originalName}</strong>
|
||||
<i class="ri-arrow-right-line txt-sm" />
|
||||
<strong class="txt"> {field.name}</strong>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
{#if !collection?.isView}
|
||||
{#each renamedFields as field}
|
||||
<li>
|
||||
<div class="inline-flex">
|
||||
Renamed field
|
||||
<strong class="txt-strikethrough txt-hint">{field.originalName}</strong>
|
||||
<i class="ri-arrow-right-line txt-sm" />
|
||||
<strong class="txt"> {field.name}</strong>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
|
||||
{#each deletedFields as field}
|
||||
<li class="txt-danger">
|
||||
Removed field <span class="txt-bold">{field.name}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{#each deletedFields as field}
|
||||
<li class="txt-danger">
|
||||
Removed field <span class="txt-bold">{field.name}</span>
|
||||
</li>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { Collection } from "pocketbase";
|
||||
import { createEventDispatcher, tick } from "svelte";
|
||||
import { scale } from "svelte/transition";
|
||||
import { Collection } from "pocketbase";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import ApiClient from "@/utils/ApiClient";
|
||||
import { errors, setErrors, removeError } from "@/stores/errors";
|
||||
@@ -14,18 +14,21 @@
|
||||
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
|
||||
import CollectionFieldsTab from "@/components/collections/CollectionFieldsTab.svelte";
|
||||
import CollectionRulesTab from "@/components/collections/CollectionRulesTab.svelte";
|
||||
import CollectionQueryTab from "@/components/collections/CollectionQueryTab.svelte";
|
||||
import CollectionAuthOptionsTab from "@/components/collections/CollectionAuthOptionsTab.svelte";
|
||||
import CollectionUpdateConfirm from "@/components/collections/CollectionUpdateConfirm.svelte";
|
||||
|
||||
const TAB_FIELDS = "fields";
|
||||
const TAB_SCHEMA = "schema";
|
||||
const TAB_RULES = "api_rules";
|
||||
const TAB_OPTIONS = "options";
|
||||
|
||||
const TYPE_BASE = "base";
|
||||
const TYPE_AUTH = "auth";
|
||||
const TYPE_VIEW = "view";
|
||||
|
||||
const collectionTypes = {};
|
||||
collectionTypes[TYPE_BASE] = "Base";
|
||||
collectionTypes[TYPE_VIEW] = "View";
|
||||
collectionTypes[TYPE_AUTH] = "Auth";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
@@ -37,14 +40,16 @@
|
||||
let collection = new Collection();
|
||||
let isSaving = false;
|
||||
let confirmClose = false; // prevent close recursion
|
||||
let activeTab = TAB_FIELDS;
|
||||
let activeTab = TAB_SCHEMA;
|
||||
let initialFormHash = calculateFormHash(collection);
|
||||
let schemaTabError = "";
|
||||
|
||||
$: schemaTabError =
|
||||
$: if ($errors.schema || $errors.options?.query) {
|
||||
// extract the direct schema field error, otherwise - return a generic message
|
||||
typeof CommonHelper.getNestedVal($errors, "schema.message", null) === "string"
|
||||
? CommonHelper.getNestedVal($errors, "schema.message")
|
||||
: "Has errors";
|
||||
schemaTabError = CommonHelper.getNestedVal($errors, "schema.message") || "Has errors";
|
||||
} else {
|
||||
schemaTabError = "";
|
||||
}
|
||||
|
||||
$: isSystemUpdate = !collection.isNew && collection.system;
|
||||
|
||||
@@ -54,7 +59,7 @@
|
||||
|
||||
$: if (activeTab === TAB_OPTIONS && collection.type !== TYPE_AUTH) {
|
||||
// reset selected tab
|
||||
changeTab(TAB_FIELDS);
|
||||
changeTab(TAB_SCHEMA);
|
||||
}
|
||||
|
||||
export function changeTab(newTab) {
|
||||
@@ -66,7 +71,7 @@
|
||||
|
||||
confirmClose = true;
|
||||
|
||||
changeTab(TAB_FIELDS);
|
||||
changeTab(TAB_SCHEMA);
|
||||
|
||||
return collectionPanel?.show();
|
||||
}
|
||||
@@ -301,7 +306,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm p-r-10 p-l-10 {collection.isNew
|
||||
? 'btn-secondary'
|
||||
? 'btn-outline'
|
||||
: 'btn-transparent'}"
|
||||
disabled={!collection.isNew}
|
||||
>
|
||||
@@ -339,11 +344,11 @@
|
||||
<button
|
||||
type="button"
|
||||
class="tab-item"
|
||||
class:active={activeTab === TAB_FIELDS}
|
||||
on:click={() => changeTab(TAB_FIELDS)}
|
||||
class:active={activeTab === TAB_SCHEMA}
|
||||
on:click={() => changeTab(TAB_SCHEMA)}
|
||||
>
|
||||
<span class="txt">Fields</span>
|
||||
{#if !CommonHelper.isEmpty($errors?.schema)}
|
||||
<span class="txt">{collection?.isView ? "Query" : "Fields"}</span>
|
||||
{#if !CommonHelper.isEmpty(schemaTabError)}
|
||||
<i
|
||||
class="ri-error-warning-fill txt-danger"
|
||||
transition:scale|local={{ duration: 150, start: 0.7 }}
|
||||
@@ -390,8 +395,12 @@
|
||||
|
||||
<div class="tabs-content">
|
||||
<!-- avoid rerendering the fields tab -->
|
||||
<div class="tab-item" class:active={activeTab === TAB_FIELDS}>
|
||||
<CollectionFieldsTab bind:collection />
|
||||
<div class="tab-item" class:active={activeTab === TAB_SCHEMA}>
|
||||
{#if collection.isView}
|
||||
<CollectionQueryTab bind:collection />
|
||||
{:else}
|
||||
<CollectionFieldsTab bind:collection />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if activeTab === TAB_RULES}
|
||||
@@ -431,7 +440,7 @@
|
||||
align-items: center;
|
||||
min-height: var(--smBtnHeight);
|
||||
}
|
||||
.tabs-content {
|
||||
z-index: 3; /* autocomplete dropdown overlay fix */
|
||||
.tabs-content:focus-within {
|
||||
z-index: 9; /* autocomplete dropdown overlay fix */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,21 +8,21 @@
|
||||
let collectionPanel;
|
||||
let searchTerm = "";
|
||||
|
||||
$: if ($collections) {
|
||||
scrollIntoView();
|
||||
}
|
||||
|
||||
$: normalizedSearch = searchTerm.replace(/\s+/g, "").toLowerCase();
|
||||
|
||||
$: hasSearch = searchTerm !== "";
|
||||
|
||||
$: filteredCollections = $collections.filter((collection) => {
|
||||
$: filtered = $collections.filter((collection) => {
|
||||
return (
|
||||
collection.id == searchTerm ||
|
||||
collection.name.replace(/\s+/g, "").toLowerCase().includes(normalizedSearch)
|
||||
);
|
||||
});
|
||||
|
||||
$: if ($collections) {
|
||||
scrollIntoView();
|
||||
}
|
||||
|
||||
function selectCollection(collection) {
|
||||
$activeCollection = collection;
|
||||
}
|
||||
@@ -59,9 +59,9 @@
|
||||
<div
|
||||
class="sidebar-content"
|
||||
class:fade={$isCollectionsLoading}
|
||||
class:sidebar-content-compact={filteredCollections.length > 20}
|
||||
class:sidebar-content-compact={filtered.length > 20}
|
||||
>
|
||||
{#each filteredCollections as collection (collection.id)}
|
||||
{#each filtered as collection (collection.id)}
|
||||
<a
|
||||
href="/collections?collectionId={collection.id}"
|
||||
class="sidebar-list-item"
|
||||
@@ -69,7 +69,6 @@
|
||||
use:link
|
||||
>
|
||||
<i class={CommonHelper.getCollectionTypeIcon(collection.type)} />
|
||||
|
||||
<span class="txt">{collection.name}</span>
|
||||
</a>
|
||||
{:else}
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
<div class="grid">
|
||||
<div class="col-sm-6">
|
||||
<Field
|
||||
class="form-field required {field.id ? 'disabled' : ''}"
|
||||
class="form-field required {field.id ? 'readonly' : ''}"
|
||||
name="schema.{key}.type"
|
||||
let:uniqueId
|
||||
>
|
||||
|
||||
@@ -114,9 +114,9 @@
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
min-width: 135px;
|
||||
padding: 10px 10px;
|
||||
padding: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
background: rgba(53, 71, 104, 0.1);
|
||||
background: rgba(53, 71, 104, 0.08);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user