[#3403] added option to import/export a subset of collections

This commit is contained in:
Gani Georgiev
2024-02-12 11:37:48 +02:00
parent d4a2f05075
commit 959c6b6d6c
37 changed files with 301 additions and 157 deletions
@@ -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 {
+52
View File
@@ -0,0 +1,52 @@
.export-list {
display: flex;
flex-direction: column;
gap: 15px;
width: 220px;
min-height: 0;
flex-shrink: 0;
overflow: auto;
padding: 10px;
background: var(--baseAlt1Color);
border-radius: var(--baseRadius);
.list-item {
margin: 0;
width: 100%;
}
.form-field {
margin: 0;
label {
width: 100%;
display: block !important;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
}
.export-preview {
position: relative;
flex-grow: 1;
border-radius: var(--baseRadius);
overflow: hidden;
.copy-schema {
position: absolute;
right: 15px;
top: 10px;
}
.code-wrapper {
height: 100%;
code {
min-height: 100%;
}
}
}
.export-panel {
display: flex;
width: 100%;
height: 550px;
align-items: stretch;
gap: 15px;
}
+2
View File
@@ -43,3 +43,5 @@
@import 'schema_field';
@import 'file_picker';
@import 'collections_export';