diff --git a/daos/collection.go b/daos/collection.go
index 8ca72840..0e381e7b 100644
--- a/daos/collection.go
+++ b/daos/collection.go
@@ -190,22 +190,28 @@ func (dao *Dao) ImportCollections(
mappedImported := make(map[string]*models.Collection, len(importedCollections))
for _, imported := range importedCollections {
- // normalize ids
+ // generate id if not set
if !imported.HasId() {
- // generate id if not set
imported.MarkAsNew()
imported.RefreshId()
- } else if _, ok := mappedExisting[imported.GetId()]; !ok {
- imported.MarkAsNew()
}
- // extend existing schema
- if existing, ok := mappedExisting[imported.GetId()]; ok && !deleteMissing {
- schema, _ := existing.Schema.Clone()
- for _, f := range imported.Schema.Fields() {
- schema.AddField(f) // add or replace
+ if existing, ok := mappedExisting[imported.GetId()]; ok {
+ // preserve original created date
+ if !existing.Created.IsZero() {
+ imported.Created = existing.Created
}
- imported.Schema = *schema
+
+ // extend existing schema
+ if !deleteMissing {
+ schema, _ := existing.Schema.Clone()
+ for _, f := range imported.Schema.Fields() {
+ schema.AddField(f) // add or replace
+ }
+ imported.Schema = *schema
+ }
+ } else {
+ imported.MarkAsNew()
}
mappedImported[imported.GetId()] = imported
diff --git a/ui/src/components/collections/CollectionsDiffTable.svelte b/ui/src/components/collections/CollectionsDiffTable.svelte
index 43cec496..58452ee2 100644
--- a/ui/src/components/collections/CollectionsDiffTable.svelte
+++ b/ui/src/components/collections/CollectionsDiffTable.svelte
@@ -5,14 +5,25 @@
export let collectionA = new Collection();
export let collectionB = new Collection();
export let deleteMissing = false;
+ let schemaA = [];
+ let schemaB = [];
+ let removedFields = [];
+ let sharedFields = [];
+ let addedFields = [];
$: isDeleteDiff = !collectionB?.id && !collectionB?.name;
$: isCreateDiff = !isDeleteDiff && !collectionA?.id;
- $: schemaA = Array.isArray(collectionA?.schema) ? collectionA?.schema : [];
+ $: schemaA = Array.isArray(collectionA?.schema) ? collectionA?.schema.concat() : [];
- $: schemaB = Array.isArray(collectionB?.schema) ? collectionB?.schema : [];
+ $: if (
+ typeof collectionA?.schema !== "undefined" ||
+ typeof collectionB?.schema !== "undefined" ||
+ typeof deleteMissing !== "undefined"
+ ) {
+ setSchemaB();
+ }
$: removedFields = schemaA.filter((fieldA) => {
return !schemaB.find((fieldB) => fieldA.id == fieldB.id);
@@ -26,20 +37,21 @@
return !schemaA.find((fieldA) => fieldA.id == fieldB.id);
});
- $: if (typeof deleteMissing !== "undefined") {
- normalizeSchemaB();
- }
-
- $: hasAnyChange = detectChanges(collectionA, collectionB);
+ $: hasAnyChange = CommonHelper.hasCollectionChanges(collectionA, collectionB, deleteMissing);
const mainModelProps = Object.keys(new Collection().export()).filter(
(key) => !["schema", "created", "updated"].includes(key)
);
- function normalizeSchemaB() {
- schemaB = Array.isArray(collectionB?.schema) ? collectionB?.schema : [];
+ function setSchemaB() {
+ schemaB = Array.isArray(collectionB?.schema) ? collectionB?.schema.concat() : [];
+
if (!deleteMissing) {
- schemaB = schemaB.concat(removedFields);
+ schemaB = schemaB.concat(
+ schemaA.filter((fieldA) => {
+ return !schemaB.find((fieldB) => fieldA.id == fieldB.id);
+ })
+ );
}
}
@@ -54,29 +66,6 @@
return null;
}
- function detectChanges() {
- // added or removed fields
- if (addedFields?.length || (deleteMissing && removedFields?.length)) {
- return true;
- }
-
- // changes in the main model props
- for (let prop of mainModelProps) {
- if (hasChanges(collectionA?.[prop], collectionB?.[prop])) {
- return true;
- }
- }
-
- // changes in the schema fields
- for (let field of sharedFields) {
- if (hasChanges(field, CommonHelper.findByKey(schemaA, "id", field.id))) {
- return true;
- }
- }
-
- return false;
- }
-
function hasChanges(valA, valB) {
// direct match
if (valA === valB) {
@@ -88,7 +77,7 @@
function displayValue(value) {
if (typeof value === "undefined") {
- return "N/A";
+ return "";
}
return CommonHelper.isObject(value) ? JSON.stringify(value, null, 4) : value;
@@ -97,21 +86,21 @@
{#if !collectionA?.id}
-
{collectionB?.name}
Added
+
{collectionB?.name}
{:else if !collectionB?.id}
+
Deleted
{collectionA?.name}
-
Removed
{:else}
+ {#if hasAnyChange}
+ Changed
+ {/if}
{#if collectionA.name !== collectionB.name}
{collectionA.name}
{/if}
{collectionB.name}
- {#if hasAnyChange}
- Changed
- {/if}
{/if}
@@ -152,9 +141,9 @@
{#each removedFields as field}
- schema.{field.name}
+ field: {field.name}
- Removed -
+ Deleted -
All stored data related to {field.name} will be deleted!
@@ -176,7 +165,7 @@
{#each sharedFields as field}
- schema.{field.name}
+ field: {field.name}
{#if hasChanges(getFieldById(schemaA, field.id), getFieldById(schemaB, field.id))}
Changed
{/if}
@@ -199,7 +188,7 @@
{#each addedFields as field}
- schema.{field.name}
+ field: {field.name}
Added
diff --git a/ui/src/components/collections/docs/CreateApiDocs.svelte b/ui/src/components/collections/docs/CreateApiDocs.svelte
index 2bf02a70..53f78784 100644
--- a/ui/src/components/collections/docs/CreateApiDocs.svelte
+++ b/ui/src/components/collections/docs/CreateApiDocs.svelte
@@ -12,8 +12,7 @@
$: adminsOnly = collection?.createRule === null;
- $: backendAbsUrl =
- window.location.href.substring(0, window.location.href.indexOf("/_")) || ApiClient.baseUrl;
+ $: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: responses = [
{
diff --git a/ui/src/components/collections/docs/DeleteApiDocs.svelte b/ui/src/components/collections/docs/DeleteApiDocs.svelte
index 5e3b7dc3..9a7e38e1 100644
--- a/ui/src/components/collections/docs/DeleteApiDocs.svelte
+++ b/ui/src/components/collections/docs/DeleteApiDocs.svelte
@@ -11,8 +11,7 @@
$: adminsOnly = collection?.deleteRule === null;
- $: backendAbsUrl =
- window.location.href.substring(0, window.location.href.indexOf("/_")) || ApiClient.baseUrl;
+ $: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: if (collection?.id) {
responses.push({
diff --git a/ui/src/components/collections/docs/ListApiDocs.svelte b/ui/src/components/collections/docs/ListApiDocs.svelte
index 0a949802..3a971dbf 100644
--- a/ui/src/components/collections/docs/ListApiDocs.svelte
+++ b/ui/src/components/collections/docs/ListApiDocs.svelte
@@ -13,8 +13,7 @@
$: adminsOnly = collection?.listRule === null;
- $: backendAbsUrl =
- window.location.href.substring(0, window.location.href.indexOf("/_")) || ApiClient.baseUrl;
+ $: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: if (collection?.id) {
responses.push({
diff --git a/ui/src/components/collections/docs/RealtimeApiDocs.svelte b/ui/src/components/collections/docs/RealtimeApiDocs.svelte
index 64836e67..0e5edb53 100644
--- a/ui/src/components/collections/docs/RealtimeApiDocs.svelte
+++ b/ui/src/components/collections/docs/RealtimeApiDocs.svelte
@@ -7,8 +7,7 @@
export let collection = new Collection();
- $: backendAbsUrl =
- window.location.href.substring(0, window.location.href.indexOf("/_")) || ApiClient.baseUrl;
+ $: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
diff --git a/ui/src/components/collections/docs/UpdateApiDocs.svelte b/ui/src/components/collections/docs/UpdateApiDocs.svelte
index ea024960..76da6a3f 100644
--- a/ui/src/components/collections/docs/UpdateApiDocs.svelte
+++ b/ui/src/components/collections/docs/UpdateApiDocs.svelte
@@ -12,8 +12,7 @@
$: adminsOnly = collection?.updateRule === null;
- $: backendAbsUrl =
- window.location.href.substring(0, window.location.href.indexOf("/_")) || ApiClient.baseUrl;
+ $: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: responses = [
{
diff --git a/ui/src/components/collections/docs/ViewApiDocs.svelte b/ui/src/components/collections/docs/ViewApiDocs.svelte
index ca45df40..954c020c 100644
--- a/ui/src/components/collections/docs/ViewApiDocs.svelte
+++ b/ui/src/components/collections/docs/ViewApiDocs.svelte
@@ -12,8 +12,7 @@
$: adminsOnly = collection?.viewRule === null;
- $: backendAbsUrl =
- window.location.href.substring(0, window.location.href.indexOf("/_")) || ApiClient.baseUrl;
+ $: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: if (collection?.id) {
responses.push({
diff --git a/ui/src/components/settings/ImportPopup.svelte b/ui/src/components/settings/ImportPopup.svelte
index 0c5e0c1f..10ecec30 100644
--- a/ui/src/components/settings/ImportPopup.svelte
+++ b/ui/src/components/settings/ImportPopup.svelte
@@ -35,13 +35,19 @@
function loadPairs() {
pairs = [];
- // add deleted and modified collections
+ // add modified and deleted (if deleteMissing is set)
for (const oldCollection of oldCollections) {
const newCollection = CommonHelper.findByKey(newCollections, "id", oldCollection.id) || null;
- pairs.push({
- old: oldCollection,
- new: newCollection,
- });
+ if (
+ (deleteMissing && !newCollection?.id) ||
+ (newCollection?.id &&
+ CommonHelper.hasCollectionChanges(oldCollection, newCollection, deleteMissing))
+ ) {
+ pairs.push({
+ old: oldCollection,
+ new: newCollection,
+ });
+ }
}
// add only new collections
@@ -61,7 +67,7 @@
const deletedFieldNames = [];
if (deleteMissing) {
for (const old of oldCollections) {
- const imported = !CommonHelper.findByKey(newCollections, "id", old.id);
+ const imported = CommonHelper.findByKey(newCollections, "id", old.id);
if (!imported) {
// add all fields
deletedFieldNames.push(old.name + ".*");
@@ -70,7 +76,7 @@
const schema = Array.isArray(old.schema) ? old.schema : [];
for (const field of schema) {
if (!CommonHelper.findByKey(imported.schema, "id", field.id)) {
- deletedFieldNames.push(old.name + "." + field.name);
+ deletedFieldNames.push(`${old.name}.${field.name} (${field.id})`);
}
}
}
diff --git a/ui/src/components/settings/PageImportCollections.svelte b/ui/src/components/settings/PageImportCollections.svelte
index 9557fa78..e5f96f2e 100644
--- a/ui/src/components/settings/PageImportCollections.svelte
+++ b/ui/src/components/settings/PageImportCollections.svelte
@@ -19,8 +19,8 @@
let isLoadingFile = false;
let newCollections = [];
let oldCollections = [];
- let deleteMissing = false;
- let collectionsToChange = [];
+ let deleteMissing = true;
+ let collectionsToUpdate = [];
let isLoadingOldCollections = false;
$: if (typeof schemas !== "undefined") {
@@ -33,19 +33,19 @@
newCollections.length === newCollections.filter((item) => !!item.id && !!item.name).length;
$: collectionsToDelete = oldCollections.filter((collection) => {
- return isValid && !CommonHelper.findByKey(newCollections, "id", collection.id);
+ return isValid && deleteMissing && !CommonHelper.findByKey(newCollections, "id", collection.id);
});
$: collectionsToAdd = newCollections.filter((collection) => {
return isValid && !CommonHelper.findByKey(oldCollections, "id", collection.id);
});
- $: if (typeof newCollections !== "undefined") {
- loadCollectionsToModify();
+ $: if (typeof newCollections !== "undefined" || typeof deleteMissing !== "undefined") {
+ loadCollectionsToUpdate();
}
$: hasChanges =
- !!schemas && (collectionsToDelete.length || collectionsToAdd.length || collectionsToChange.length);
+ !!schemas && (collectionsToDelete.length || collectionsToAdd.length || collectionsToUpdate.length);
$: canImport = !isLoadingOldCollections && isValid && hasChanges;
@@ -62,8 +62,13 @@
const oldSchema = Array.isArray(old.schema) ? old.schema : [];
const newSchema = Array.isArray(collection.schema) ? collection.schema : [];
for (const field of newSchema) {
- const oldField = CommonHelper.findByKey(oldSchema, "name", field.name);
- if (oldField && field.id != oldField.id) {
+ const oldFieldById = CommonHelper.findByKey(oldSchema, "id", field.id);
+ if (oldFieldById) {
+ continue;
+ }
+
+ const oldFieldByName = CommonHelper.findByKey(oldSchema, "name", field.name);
+ if (oldFieldByName && field.id != oldFieldByName.id) {
return true;
}
}
@@ -90,8 +95,8 @@
isLoadingOldCollections = false;
}
- function loadCollectionsToModify() {
- collectionsToChange = [];
+ function loadCollectionsToUpdate() {
+ collectionsToUpdate = [];
if (!isValid) {
return;
@@ -103,12 +108,12 @@
// no old collection
!oldCollection?.id ||
// no changes
- JSON.stringify(oldCollection) === JSON.stringify(newCollection)
+ !CommonHelper.hasCollectionChanges(oldCollection, newCollection, deleteMissing)
) {
continue;
}
- collectionsToChange.push({
+ collectionsToUpdate.push({
new: newCollection,
old: oldCollection,
});
@@ -194,7 +199,7 @@
};
reader.onerror = (err) => {
- console.log(err);
+ console.warn(err);
addErrorToast("Failed to load the imported JSON.");
isLoadingFile = false;
@@ -269,13 +274,18 @@
{/if}
-
-
-
- Delete all collections and fields that are not present in the above imported
- configuration
-
-
+ {#if false}
+
+
+
+ Delete missing collections and schema fields
+
+ {/if}
{#if isValid && newCollections.length && !hasChanges}
@@ -304,8 +314,8 @@
{/each}
{/if}
- {#if collectionsToChange.length}
- {#each collectionsToChange as pair (pair.old.id + pair.new.id)}
+ {#if collectionsToUpdate.length}
+ {#each collectionsToUpdate as pair (pair.old.id + pair.new.id)}
Changed
@@ -336,13 +346,15 @@
{/if}
{#if idReplacableCollections.length}
-
+
- Some of the imported collections shares the same name but has different IDs.
+ Some of the imported collections shares the same name and/or fields but are
+ imported with different IDs. You can replace them in the import if you want
+ to.
replaceIds()}
>
- Replace and keep old ids
+ Replace with original ids
{/if}
diff --git a/ui/src/scss/_bulkbar.scss b/ui/src/scss/_bulkbar.scss
index 517598f6..a3b9b55a 100644
--- a/ui/src/scss/_bulkbar.scss
+++ b/ui/src/scss/_bulkbar.scss
@@ -1,6 +1,6 @@
.bulkbar {
position: sticky;
- bottom: -10px;
+ bottom: var(--baseSpacing);
z-index: 101;
gap: 10px;
display: flex;
diff --git a/ui/src/scss/_vars.scss b/ui/src/scss/_vars.scss
index 0cae5d6c..88999d92 100644
--- a/ui/src/scss/_vars.scss
+++ b/ui/src/scss/_vars.scss
@@ -26,7 +26,7 @@
--warningColor: #ff8e3c;
--warningAltColor: #ffe7d6;
- --overlayColor: rgba(88, 95, 101, 0.3);
+ --overlayColor: rgba(70, 85, 100, 0.3);
--tooltipColor: rgba(0, 0, 0, 0.85);
--shadowColor: rgba(0, 0, 0, 0.05);
diff --git a/ui/src/utils/CommonHelper.js b/ui/src/utils/CommonHelper.js
index e1ebdd3e..185c0f23 100644
--- a/ui/src/utils/CommonHelper.js
+++ b/ui/src/utils/CommonHelper.js
@@ -874,4 +874,70 @@ export default class CommonHelper {
return 'String';
}
}
+
+ /**
+ * Returns an API url address extract from the current running instance.
+ *
+ * @param {String} fallback Fallback url that will be used if the extractions fail.
+ * @return {String}
+ */
+ static getApiExampleUrl(fallback) {
+ let url = window.location.href.substring(0, window.location.href.indexOf("/_")) || fallback || '/';
+
+ // for broader compatibility replace localhost with 127.0.0.1
+ // (see https://github.com/pocketbase/js-sdk/issues/21)
+ return url.replace('//localhost', '//127.0.0.1');
+ }
+
+ /**
+ * Checks if the provided 2 collections has any change (ignoring root schema fields order).
+ *
+ * @param {Collection} oldCollection
+ * @param {Collection} newCollection
+ * @param {Boolean} withDeleteMissing Skip missing schema fields from the newCollection.
+ * @return {Boolean}
+ */
+ static hasCollectionChanges(oldCollection, newCollection, withDeleteMissing = false) {
+ oldCollection = oldCollection || {};
+ newCollection = newCollection || {};
+
+ if (oldCollection.id != newCollection.id) {
+ return true;
+ }
+
+ for (let prop in oldCollection) {
+ if (prop !== 'schema' && JSON.stringify(oldCollection[prop]) !== JSON.stringify(newCollection[prop])) {
+ return true;
+ }
+ }
+
+ const oldSchema = Array.isArray(oldCollection.schema) ? oldCollection.schema : [];
+ const newSchema = Array.isArray(newCollection.schema) ? newCollection.schema : [];
+ const removedFields = oldSchema.filter((oldField) => {
+ return oldField?.id && !CommonHelper.findByKey(newSchema, "id", oldField.id);
+ });
+ const addedFields = newSchema.filter((newField) => {
+ return newField?.id && !CommonHelper.findByKey(oldSchema, "id", newField.id);
+ });
+ const changedFields = newSchema.filter((newField) => {
+ const oldField = CommonHelper.isObject(newField) && CommonHelper.findByKey(oldSchema, "id", newField.id);
+ if (!oldField) {
+ return false;
+ }
+
+ for (let prop in oldField) {
+ if (JSON.stringify(newField[prop]) != JSON.stringify(oldField[prop])) {
+ return true;
+ }
+ }
+
+ return false;
+ });
+
+ return !!(
+ addedFields.length ||
+ changedFields.length ||
+ (withDeleteMissing && removedFields.length)
+ );
+ }
}