[#3403] added option to import/export a subset of collections
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
import { addInfoToast } from "@/stores/toasts";
|
||||
import PageWrapper from "@/components/base/PageWrapper.svelte";
|
||||
import CodeBlock from "@/components/base/CodeBlock.svelte";
|
||||
import Field from "@/components/base/Field.svelte";
|
||||
import SettingsSidebar from "@/components/settings/SettingsSidebar.svelte";
|
||||
|
||||
$pageTitle = "Export collections";
|
||||
@@ -13,9 +14,14 @@
|
||||
|
||||
let previewContainer;
|
||||
let collections = [];
|
||||
let bulkSelected = {};
|
||||
let isLoadingCollections = false;
|
||||
|
||||
$: schema = JSON.stringify(collections, null, 4);
|
||||
$: schema = JSON.stringify(Object.values(bulkSelected), null, 4);
|
||||
|
||||
$: totalBulkSelected = Object.keys(bulkSelected).length;
|
||||
|
||||
$: areAllSelected = collections.length && totalBulkSelected === collections.length;
|
||||
|
||||
loadCollections();
|
||||
|
||||
@@ -27,11 +33,14 @@
|
||||
$cancelKey: uniqueId,
|
||||
sort: "updated",
|
||||
});
|
||||
|
||||
// delete timestamps
|
||||
for (let collection of collections) {
|
||||
delete collection.created;
|
||||
delete collection.updated;
|
||||
}
|
||||
|
||||
selectAll();
|
||||
} catch (err) {
|
||||
ApiClient.error(err);
|
||||
}
|
||||
@@ -40,13 +49,43 @@
|
||||
}
|
||||
|
||||
function download() {
|
||||
CommonHelper.downloadJson(collections, "pb_schema");
|
||||
CommonHelper.downloadJson(Object.values(bulkSelected), "pb_schema");
|
||||
}
|
||||
|
||||
function copy() {
|
||||
CommonHelper.copyToClipboard(schema);
|
||||
addInfoToast("The configuration was copied to your clipboard!", 3000);
|
||||
}
|
||||
|
||||
function toggleSelectAll() {
|
||||
if (areAllSelected) {
|
||||
deselectAll();
|
||||
} else {
|
||||
selectAll();
|
||||
}
|
||||
}
|
||||
|
||||
function deselectAll() {
|
||||
bulkSelected = {};
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
bulkSelected = {};
|
||||
|
||||
for (const collection of collections) {
|
||||
bulkSelected[collection.id] = collection;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSelectCollection(collection) {
|
||||
if (!bulkSelected[collection.id]) {
|
||||
bulkSelected[collection.id] = collection;
|
||||
} else {
|
||||
delete bulkSelected[collection.id];
|
||||
}
|
||||
|
||||
bulkSelected = bulkSelected; // trigger reactivity
|
||||
}
|
||||
</script>
|
||||
|
||||
<SettingsSidebar />
|
||||
@@ -71,37 +110,74 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div
|
||||
bind:this={previewContainer}
|
||||
tabindex="0"
|
||||
class="export-preview"
|
||||
on:keydown={(e) => {
|
||||
// select all
|
||||
if (e.ctrlKey && e.code === "KeyA") {
|
||||
e.preventDefault();
|
||||
const selection = window.getSelection();
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(previewContainer);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-transparent fade copy-schema"
|
||||
on:click={() => copy()}
|
||||
>
|
||||
<span class="txt">Copy</span>
|
||||
</button>
|
||||
<div class="export-panel">
|
||||
<div class="export-list">
|
||||
<div class="list-item list-item-section">
|
||||
<Field class="form-field" let:uniqueId>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={uniqueId}
|
||||
disabled={!collections.length}
|
||||
checked={areAllSelected}
|
||||
on:change={() => toggleSelectAll()}
|
||||
/>
|
||||
<label for={uniqueId}>Select all</label>
|
||||
</Field>
|
||||
</div>
|
||||
{#each collections as collection (collection.id)}
|
||||
<div class="list-item list-item-collection">
|
||||
<Field class="form-field" let:uniqueId>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={uniqueId}
|
||||
checked={bulkSelected[collection.id]}
|
||||
on:change={() => toggleSelectCollection(collection)}
|
||||
/>
|
||||
<label for={uniqueId} title={collection.name}>{collection.name}</label>
|
||||
</Field>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<CodeBlock content={schema} />
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
bind:this={previewContainer}
|
||||
tabindex="0"
|
||||
class="export-preview"
|
||||
on:keydown={(e) => {
|
||||
// select all
|
||||
if (e.ctrlKey && e.code === "KeyA") {
|
||||
e.preventDefault();
|
||||
const selection = window.getSelection();
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(previewContainer);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-transparent fade copy-schema"
|
||||
disabled={!totalBulkSelected}
|
||||
on:click={() => copy()}
|
||||
>
|
||||
<span class="txt">Copy</span>
|
||||
</button>
|
||||
|
||||
<CodeBlock content={schema} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex m-t-base">
|
||||
<div class="flex-fill" />
|
||||
<button type="button" class="btn btn-expanded" on:click={() => download()}>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-expanded"
|
||||
disabled={!totalBulkSelected}
|
||||
on:click={() => download()}
|
||||
>
|
||||
<i class="ri-download-line" />
|
||||
<span class="txt">Download as JSON</span>
|
||||
</button>
|
||||
@@ -110,15 +186,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
|
||||
<style>
|
||||
.export-preview {
|
||||
position: relative;
|
||||
height: 500px;
|
||||
}
|
||||
.export-preview .copy-schema {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -22,8 +22,9 @@
|
||||
let deleteMissing = true;
|
||||
let collectionsToUpdate = [];
|
||||
let isLoadingOldCollections = false;
|
||||
let mergeWithOldCollections = false; // an alternative to the default deleteMissing option
|
||||
|
||||
$: if (typeof schemas !== "undefined") {
|
||||
$: if (typeof schemas !== "undefined" && mergeWithOldCollections !== null) {
|
||||
loadNewCollections(schemas);
|
||||
}
|
||||
|
||||
@@ -33,7 +34,12 @@
|
||||
newCollections.length === newCollections.filter((item) => !!item.id && !!item.name).length;
|
||||
|
||||
$: collectionsToDelete = oldCollections.filter((collection) => {
|
||||
return isValid && deleteMissing && !CommonHelper.findByKey(newCollections, "id", collection.id);
|
||||
return (
|
||||
isValid &&
|
||||
!mergeWithOldCollections &&
|
||||
deleteMissing &&
|
||||
!CommonHelper.findByKey(newCollections, "id", collection.id)
|
||||
);
|
||||
});
|
||||
|
||||
$: collectionsToAdd = newCollections.filter((collection) => {
|
||||
@@ -128,7 +134,7 @@
|
||||
newCollections = [];
|
||||
|
||||
try {
|
||||
newCollections = JSON.parse(schemas);
|
||||
newCollections = newCollections.concat(JSON.parse(schemas));
|
||||
} catch (_) {}
|
||||
|
||||
if (!Array.isArray(newCollections)) {
|
||||
@@ -223,6 +229,14 @@
|
||||
fileInput.value = "";
|
||||
setErrors({});
|
||||
}
|
||||
|
||||
function review() {
|
||||
const collectionsToImport = !mergeWithOldCollections
|
||||
? newCollections
|
||||
: CommonHelper.filterDuplicatesByKey(oldCollections.concat(newCollections));
|
||||
|
||||
importPopup?.show(oldCollections, collectionsToImport, deleteMissing);
|
||||
}
|
||||
</script>
|
||||
|
||||
<SettingsSidebar />
|
||||
@@ -283,8 +297,18 @@
|
||||
{/if}
|
||||
</Field>
|
||||
|
||||
<Field class="form-field form-field-toggle" let:uniqueId>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={uniqueId}
|
||||
bind:checked={mergeWithOldCollections}
|
||||
disabled={!isValid}
|
||||
/>
|
||||
<label for={uniqueId}>Merge with the existing collections</label>
|
||||
</Field>
|
||||
|
||||
{#if false}
|
||||
<!-- for now hide the delete control and eventually enable/remove based on the users feedback -->
|
||||
<!-- for now hide the explicit delete control and eventually enable/remove based on the users feedback -->
|
||||
<Field class="form-field form-field-toggle" let:uniqueId>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -391,7 +415,7 @@
|
||||
type="button"
|
||||
class="btn btn-expanded btn-warning m-l-auto"
|
||||
disabled={!canImport}
|
||||
on:click={() => importPopup?.show(oldCollections, newCollections, deleteMissing)}
|
||||
on:click={review}
|
||||
>
|
||||
<span class="txt">Review</span>
|
||||
</button>
|
||||
@@ -401,7 +425,7 @@
|
||||
</div>
|
||||
</PageWrapper>
|
||||
|
||||
<ImportPopup bind:this={importPopup} on:submit={() => clear()} />
|
||||
<ImportPopup bind:this={importPopup} on:submit={clear} />
|
||||
|
||||
<style>
|
||||
.list-label {
|
||||
|
||||
Reference in New Issue
Block a user