2022-08-05 11:00:38 +08:00
|
|
|
<script>
|
2022-08-06 04:25:16 +08:00
|
|
|
import { tick } from "svelte";
|
2022-08-05 11:00:38 +08:00
|
|
|
import ApiClient from "@/utils/ApiClient";
|
|
|
|
import CommonHelper from "@/utils/CommonHelper";
|
|
|
|
import { pageTitle } from "@/stores/app";
|
2022-08-06 13:03:34 +08:00
|
|
|
import { addErrorToast } from "@/stores/toasts";
|
|
|
|
import { setErrors } from "@/stores/errors";
|
2022-08-09 21:16:09 +08:00
|
|
|
import PageWrapper from "@/components/base/PageWrapper.svelte";
|
2022-08-05 11:00:38 +08:00
|
|
|
import Field from "@/components/base/Field.svelte";
|
|
|
|
import SettingsSidebar from "@/components/settings/SettingsSidebar.svelte";
|
2022-08-06 13:03:34 +08:00
|
|
|
import ImportPopup from "@/components/settings/ImportPopup.svelte";
|
2022-08-05 11:00:38 +08:00
|
|
|
|
|
|
|
$pageTitle = "Import collections";
|
|
|
|
|
|
|
|
let fileInput;
|
2022-08-06 13:03:34 +08:00
|
|
|
let importPopup;
|
2022-08-05 11:00:38 +08:00
|
|
|
|
2022-08-06 23:15:18 +08:00
|
|
|
let schemas = "";
|
2022-08-05 11:00:38 +08:00
|
|
|
let isLoadingFile = false;
|
|
|
|
let newCollections = [];
|
|
|
|
let oldCollections = [];
|
2022-08-06 04:25:16 +08:00
|
|
|
let collectionsToModify = [];
|
2022-08-05 11:00:38 +08:00
|
|
|
let isLoadingOldCollections = false;
|
|
|
|
|
2022-08-06 23:15:18 +08:00
|
|
|
$: if (typeof schemas !== "undefined") {
|
|
|
|
loadNewCollections(schemas);
|
2022-08-05 11:00:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
$: isValid =
|
2022-08-06 23:15:18 +08:00
|
|
|
!!schemas &&
|
2022-08-05 11:00:38 +08:00
|
|
|
newCollections.length &&
|
|
|
|
newCollections.length === newCollections.filter((item) => !!item.id && !!item.name).length;
|
|
|
|
|
|
|
|
$: collectionsToDelete = oldCollections.filter((collection) => {
|
2022-08-06 04:25:16 +08:00
|
|
|
return isValid && !CommonHelper.findByKey(newCollections, "id", collection.id);
|
2022-08-05 11:00:38 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
$: collectionsToAdd = newCollections.filter((collection) => {
|
2022-08-06 04:25:16 +08:00
|
|
|
return isValid && !CommonHelper.findByKey(oldCollections, "id", collection.id);
|
2022-08-05 11:00:38 +08:00
|
|
|
});
|
|
|
|
|
2022-08-06 04:25:16 +08:00
|
|
|
$: if (typeof newCollections !== "undefined") {
|
|
|
|
loadCollectionsToModify();
|
|
|
|
}
|
2022-08-05 11:00:38 +08:00
|
|
|
|
2022-08-06 04:25:16 +08:00
|
|
|
$: hasChanges =
|
2022-08-06 23:15:18 +08:00
|
|
|
!!schemas && (collectionsToDelete.length || collectionsToAdd.length || collectionsToModify.length);
|
2022-08-06 04:25:16 +08:00
|
|
|
|
|
|
|
$: canImport = !isLoadingOldCollections && isValid && hasChanges;
|
2022-08-05 11:00:38 +08:00
|
|
|
|
|
|
|
loadOldCollections();
|
|
|
|
|
|
|
|
async function loadOldCollections() {
|
|
|
|
isLoadingOldCollections = true;
|
|
|
|
|
|
|
|
try {
|
2022-08-06 04:25:16 +08:00
|
|
|
oldCollections = await ApiClient.collections.getFullList(200);
|
2022-08-05 11:00:38 +08:00
|
|
|
// delete timestamps
|
|
|
|
for (let collection of oldCollections) {
|
|
|
|
delete collection.created;
|
|
|
|
delete collection.updated;
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
ApiClient.errorResponseHandler(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
isLoadingOldCollections = false;
|
|
|
|
}
|
|
|
|
|
2022-08-06 04:25:16 +08:00
|
|
|
function loadCollectionsToModify() {
|
|
|
|
collectionsToModify = [];
|
|
|
|
|
|
|
|
if (!isValid) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let newCollection of newCollections) {
|
|
|
|
const oldCollection = CommonHelper.findByKey(oldCollections, "id", newCollection.id);
|
|
|
|
if (
|
|
|
|
// no old collection
|
|
|
|
!oldCollection?.id ||
|
|
|
|
// no changes
|
|
|
|
JSON.stringify(oldCollection) === JSON.stringify(newCollection)
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
collectionsToModify.push({
|
|
|
|
new: newCollection,
|
|
|
|
old: oldCollection,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-05 11:00:38 +08:00
|
|
|
function loadNewCollections() {
|
|
|
|
newCollections = [];
|
|
|
|
|
|
|
|
try {
|
2022-08-06 23:15:18 +08:00
|
|
|
newCollections = JSON.parse(schemas);
|
2022-08-05 11:00:38 +08:00
|
|
|
} catch (_) {}
|
|
|
|
|
|
|
|
if (!Array.isArray(newCollections)) {
|
|
|
|
newCollections = [];
|
2022-08-06 04:25:16 +08:00
|
|
|
} else {
|
|
|
|
newCollections = CommonHelper.filterDuplicatesByKey(newCollections);
|
2022-08-05 11:00:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// delete timestamps
|
|
|
|
for (let collection of newCollections) {
|
|
|
|
delete collection.created;
|
|
|
|
delete collection.updated;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadFile(file) {
|
|
|
|
isLoadingFile = true;
|
|
|
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
2022-08-06 04:25:16 +08:00
|
|
|
reader.onload = async (event) => {
|
2022-08-05 11:00:38 +08:00
|
|
|
isLoadingFile = false;
|
|
|
|
fileInput.value = ""; // reset
|
2022-08-06 04:25:16 +08:00
|
|
|
|
2022-08-06 23:15:18 +08:00
|
|
|
schemas = event.target.result;
|
2022-08-06 04:25:16 +08:00
|
|
|
|
|
|
|
await tick();
|
|
|
|
|
|
|
|
if (!newCollections.length) {
|
2022-08-07 16:14:49 +08:00
|
|
|
addErrorToast("Invalid collections configuration.");
|
2022-08-06 04:25:16 +08:00
|
|
|
clear();
|
|
|
|
}
|
2022-08-05 11:00:38 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
reader.onerror = (err) => {
|
|
|
|
console.log(err);
|
|
|
|
addErrorToast("Failed to load the imported JSON.");
|
|
|
|
|
|
|
|
isLoadingFile = false;
|
|
|
|
fileInput.value = ""; // reset
|
|
|
|
};
|
|
|
|
|
|
|
|
reader.readAsText(file);
|
|
|
|
}
|
|
|
|
|
2022-08-06 04:25:16 +08:00
|
|
|
function clear() {
|
2022-08-06 23:15:18 +08:00
|
|
|
schemas = "";
|
2022-08-06 04:25:16 +08:00
|
|
|
fileInput.value = "";
|
2022-08-06 13:03:34 +08:00
|
|
|
setErrors({});
|
2022-08-06 04:25:16 +08:00
|
|
|
}
|
2022-08-05 11:00:38 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<SettingsSidebar />
|
|
|
|
|
2022-08-09 21:16:09 +08:00
|
|
|
<PageWrapper>
|
2022-08-05 11:00:38 +08:00
|
|
|
<header class="page-header">
|
|
|
|
<nav class="breadcrumbs">
|
|
|
|
<div class="breadcrumb-item">Settings</div>
|
|
|
|
<div class="breadcrumb-item">{$pageTitle}</div>
|
|
|
|
</nav>
|
|
|
|
</header>
|
|
|
|
|
|
|
|
<div class="wrapper">
|
|
|
|
<div class="panel">
|
2022-08-06 04:25:16 +08:00
|
|
|
{#if isLoadingOldCollections}
|
|
|
|
<div class="loader" />
|
|
|
|
{:else}
|
2022-08-09 21:16:09 +08:00
|
|
|
<input
|
|
|
|
bind:this={fileInput}
|
|
|
|
type="file"
|
|
|
|
class="hidden"
|
|
|
|
accept=".json"
|
|
|
|
on:change={() => {
|
|
|
|
if (fileInput.files.length) {
|
|
|
|
loadFile(fileInput.files[0]);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
2022-08-06 04:25:16 +08:00
|
|
|
|
2022-08-09 21:16:09 +08:00
|
|
|
<div class="content txt-xl m-b-base">
|
2022-08-06 04:25:16 +08:00
|
|
|
<p>
|
2022-08-07 16:14:49 +08:00
|
|
|
Paste below the collections configuration you want to import or
|
2022-08-06 04:25:16 +08:00
|
|
|
<button
|
|
|
|
class="btn btn-outline btn-sm m-l-5"
|
|
|
|
class:btn-loading={isLoadingFile}
|
|
|
|
on:click={() => {
|
|
|
|
fileInput.click();
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<span class="txt">Load from JSON file</span>
|
|
|
|
</button>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<Field class="form-field {!isValid ? 'field-error' : ''}" name="collections" let:uniqueId>
|
2022-08-06 23:15:18 +08:00
|
|
|
<label for={uniqueId} class="p-b-10">Collections</label>
|
2022-08-06 04:25:16 +08:00
|
|
|
<textarea
|
|
|
|
id={uniqueId}
|
|
|
|
class="code"
|
|
|
|
spellcheck="false"
|
|
|
|
rows="15"
|
|
|
|
required
|
2022-08-06 23:15:18 +08:00
|
|
|
bind:value={schemas}
|
2022-08-06 04:25:16 +08:00
|
|
|
/>
|
|
|
|
|
2022-08-06 23:15:18 +08:00
|
|
|
{#if !!schemas && !isValid}
|
2022-08-07 16:14:49 +08:00
|
|
|
<div class="help-block help-block-error">Invalid collections configuration.</div>
|
2022-08-06 04:25:16 +08:00
|
|
|
{/if}
|
|
|
|
</Field>
|
|
|
|
|
|
|
|
{#if isValid && newCollections.length && !hasChanges}
|
|
|
|
<div class="alert alert-info">
|
|
|
|
<div class="icon">
|
|
|
|
<i class="ri-information-line" />
|
|
|
|
</div>
|
|
|
|
<div class="content">
|
2022-08-07 16:14:49 +08:00
|
|
|
<string>Your collections configuration is already up-to-date!</string>
|
2022-08-06 04:25:16 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
{#if isValid && newCollections.length && hasChanges}
|
2022-08-06 13:03:34 +08:00
|
|
|
<h5 class="section-title">Detected changes</h5>
|
2022-08-06 04:25:16 +08:00
|
|
|
|
2022-08-06 13:03:34 +08:00
|
|
|
<div class="list">
|
2022-08-06 04:25:16 +08:00
|
|
|
{#if collectionsToDelete.length}
|
|
|
|
{#each collectionsToDelete as collection (collection.id)}
|
|
|
|
<div class="list-item">
|
|
|
|
<span class="label label-danger list-label">Deleted</span>
|
|
|
|
<strong>{collection.name}</strong>
|
2022-08-06 13:03:34 +08:00
|
|
|
{#if collection.id}
|
|
|
|
<small class="txt-hint">({collection.id})</small>
|
|
|
|
{/if}
|
2022-08-06 04:25:16 +08:00
|
|
|
</div>
|
|
|
|
{/each}
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
{#if collectionsToModify.length}
|
2022-08-06 23:15:18 +08:00
|
|
|
{#each collectionsToModify as pair (pair.old.id + pair.new.id)}
|
2022-08-06 04:25:16 +08:00
|
|
|
<div class="list-item">
|
|
|
|
<span class="label label-warning list-label">Modified</span>
|
|
|
|
<strong>
|
2022-08-06 23:15:18 +08:00
|
|
|
{#if pair.old.name !== pair.new.name}
|
|
|
|
<span class="txt-strikethrough txt-hint">{pair.old.name}</span> -
|
2022-08-06 04:25:16 +08:00
|
|
|
{/if}
|
2022-08-06 23:15:18 +08:00
|
|
|
{pair.new.name}
|
2022-08-06 04:25:16 +08:00
|
|
|
</strong>
|
2022-08-06 23:15:18 +08:00
|
|
|
{#if pair.new.id}
|
|
|
|
<small class="txt-hint">({pair.new.id})</small>
|
2022-08-06 13:03:34 +08:00
|
|
|
{/if}
|
2022-08-06 04:25:16 +08:00
|
|
|
</div>
|
|
|
|
{/each}
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
{#if collectionsToAdd.length}
|
|
|
|
{#each collectionsToAdd as collection (collection.id)}
|
|
|
|
<div class="list-item">
|
|
|
|
<span class="label label-success list-label">New</span>
|
|
|
|
<strong>{collection.name}</strong>
|
2022-08-06 13:03:34 +08:00
|
|
|
{#if collection.id}
|
|
|
|
<small class="txt-hint">({collection.id})</small>
|
|
|
|
{/if}
|
2022-08-06 04:25:16 +08:00
|
|
|
</div>
|
|
|
|
{/each}
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
<div class="flex m-t-base">
|
2022-08-06 23:15:18 +08:00
|
|
|
{#if !!schemas}
|
2022-08-06 13:03:34 +08:00
|
|
|
<button type="button" class="btn btn-secondary link-hint" on:click={() => clear()}>
|
|
|
|
<span class="txt">Clear</span>
|
|
|
|
</button>
|
|
|
|
{/if}
|
2022-08-06 04:25:16 +08:00
|
|
|
<div class="flex-fill" />
|
|
|
|
<button
|
|
|
|
type="button"
|
2022-08-06 13:03:34 +08:00
|
|
|
class="btn btn-expanded btn-warning m-l-auto"
|
2022-08-06 04:25:16 +08:00
|
|
|
disabled={!canImport}
|
2022-08-06 13:03:34 +08:00
|
|
|
on:click={() => importPopup?.show(oldCollections, newCollections)}
|
2022-08-06 04:25:16 +08:00
|
|
|
>
|
2022-08-06 13:03:34 +08:00
|
|
|
<span class="txt">Review</span>
|
2022-08-06 04:25:16 +08:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
{/if}
|
2022-08-05 11:00:38 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-08-09 21:16:09 +08:00
|
|
|
</PageWrapper>
|
2022-08-05 11:00:38 +08:00
|
|
|
|
2022-08-06 13:03:34 +08:00
|
|
|
<ImportPopup bind:this={importPopup} on:submit={() => clear()} />
|
2022-08-06 04:25:16 +08:00
|
|
|
|
2022-08-05 11:00:38 +08:00
|
|
|
<style>
|
2022-08-06 04:25:16 +08:00
|
|
|
.list-label {
|
|
|
|
min-width: 65px;
|
2022-08-05 11:00:38 +08:00
|
|
|
}
|
|
|
|
</style>
|