From d56b8fcb9048d81f02755c15a24b966fc3ac352e Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Wed, 10 Aug 2022 16:16:59 +0300 Subject: [PATCH] updated import popup handling and api preview examples --- daos/collection.go | 26 ++++--- .../collections/CollectionsDiffTable.svelte | 75 ++++++++----------- .../collections/docs/CreateApiDocs.svelte | 3 +- .../collections/docs/DeleteApiDocs.svelte | 3 +- .../collections/docs/ListApiDocs.svelte | 3 +- .../collections/docs/RealtimeApiDocs.svelte | 3 +- .../collections/docs/UpdateApiDocs.svelte | 3 +- .../collections/docs/ViewApiDocs.svelte | 3 +- ui/src/components/settings/ImportPopup.svelte | 20 +++-- .../settings/PageImportCollections.svelte | 62 ++++++++------- ui/src/scss/_bulkbar.scss | 2 +- ui/src/scss/_vars.scss | 2 +- ui/src/utils/CommonHelper.js | 66 ++++++++++++++++ 13 files changed, 172 insertions(+), 99 deletions(-) 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} - - - - + {#if false} + + + + + + {/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.
{/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) + ); + } }