Add backup/restore for tags

This commit is contained in:
Cohee 2023-11-15 00:59:44 +02:00
parent 9bef9f4332
commit 22161c2264
1 changed files with 117 additions and 6 deletions

View File

@ -7,11 +7,12 @@ import {
getCharacters, getCharacters,
entitiesFilter, entitiesFilter,
printCharacters, printCharacters,
getEmptyBlock,
} from "../script.js"; } from "../script.js";
import { FILTER_TYPES, FilterHelper } from "./filters.js"; import { FILTER_TYPES, FilterHelper } from "./filters.js";
import { groupCandidatesFilter, selected_group } from "./group-chats.js"; import { groupCandidatesFilter, groups, selected_group } from "./group-chats.js";
import { onlyUnique, uuidv4 } from "./utils.js"; import { download, onlyUnique, parseJsonFile, uuidv4 } from "./utils.js";
export { export {
tags, tags,
@ -482,9 +483,20 @@ function onViewTagsListClick() {
$(list).append(` $(list).append(`
<div class="title_restorable alignItemsBaseline"> <div class="title_restorable alignItemsBaseline">
<h3>Tag Management</h3> <h3>Tag Management</h3>
<div class="menu_button menu_button_icon tag_view_create"> <div class="flex-container alignItemsBaseline">
<i class="fa-solid fa-plus"></i> <div class="menu_button menu_button_icon tag_view_backup" title="Save your tags to a file">
<span data-i18n="Create">Create</span> <i class="fa-solid fa-file-export"></i>
<span data-i18n="Backup">Backup</span>
</div>
<div class="menu_button menu_button_icon tag_view_restore" title="Restore tags from a file">
<i class="fa-solid fa-file-import"></i>
<span data-i18n="Restore">Restore</span>
</div>
<div class="menu_button menu_button_icon tag_view_create" title="Create a new tag">
<i class="fa-solid fa-plus"></i>
<span data-i18n="Create">Create</span>
</div>
<input type="file" id="tag_view_restore_input" hidden accept=".json">
</div> </div>
</div> </div>
<div class="justifyLeft m-b-1"> <div class="justifyLeft m-b-1">
@ -494,13 +506,110 @@ function onViewTagsListClick() {
</small> </small>
</div>`); </div>`);
for (const tag of tags.slice().sort((a, b) => a?.name?.toLowerCase()?.localeCompare(b?.name?.toLowerCase()))) { const sortedTags = tags.slice().sort((a, b) => a?.name?.toLowerCase()?.localeCompare(b?.name?.toLowerCase()));
for (const tag of sortedTags) {
appendViewTagToList(list, tag, everything); appendViewTagToList(list, tag, everything);
} }
callPopup(list, 'text'); callPopup(list, 'text');
} }
async function onTagRestoreFileSelect(e) {
const file = e.target.files[0];
if (!file) {
console.log('Tag restore: No file selected.');
return;
}
const data = await parseJsonFile(file);
if (!data) {
toastr.warning('Tag restore: Empty file data.');
console.log('Tag restore: File data empty.');
return;
}
if (!data.tags || !data.tag_map || !Array.isArray(data.tags) || typeof data.tag_map !== 'object') {
toastr.warning('Tag restore: Invalid file format.');
console.log('Tag restore: Invalid file format.');
return;
}
const warnings = [];
// Import tags
for (const tag of data.tags) {
if (!tag.id || !tag.name) {
warnings.push(`Tag object is invalid: ${JSON.stringify(tag)}.`);
continue;
}
if (tags.find(x => x.id === tag.id)) {
warnings.push(`Tag with id ${tag.id} already exists.`);
continue;
}
tags.push(tag);
}
// Import tag_map
for (const key of Object.keys(data.tag_map)) {
const tagIds = data.tag_map[key];
if (!Array.isArray(tagIds)) {
warnings.push(`Tag map for key ${key} is invalid: ${JSON.stringify(tagIds)}.`);
continue;
}
// Verify that the key points to a valid character or group.
const characterExists = characters.some(x => String(x.avatar) === String(key));
const groupExists = groups.some(x => String(x.id) === String(key));
if (!characterExists && !groupExists) {
warnings.push(`Tag map key ${key} does not exist.`);
continue;
}
// Get existing tag ids for this key or empty array.
const existingTagIds = tag_map[key] || [];
// Merge existing and new tag ids. Remove duplicates.
tag_map[key] = existingTagIds.concat(tagIds).filter(onlyUnique);
// Verify that all tags exist. Remove tags that don't exist.
tag_map[key] = tag_map[key].filter(x => tags.some(y => String(y.id) === String(x)));
}
if (warnings.length) {
toastr.success('Tags restored with warnings. Check console for details.');
console.warn(`TAG RESTORE REPORT\n====================\n${warnings.join('\n')}`);
} else {
toastr.success('Tags restored successfully.');
}
$('#tag_view_restore_input').val('');
saveSettingsDebounced();
printCharacters(true);
onViewTagsListClick();
}
function onBackupRestoreClick() {
$('#tag_view_restore_input')
.off('change')
.on('change', onTagRestoreFileSelect)
.trigger('click');
}
function onTagsBackupClick() {
const timestamp = new Date().toISOString().split('T')[0].replace(/-/g, '');
const filename = `tags_${timestamp}.json`;
const data = {
tags: tags,
tag_map: tag_map,
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
download(blob, filename, 'application/json');
}
function onTagCreateClick() { function onTagCreateClick() {
const tag = createNewTag('New Tag'); const tag = createNewTag('New Tag');
appendViewTagToList($('#tag_view_list'), tag, []); appendViewTagToList($('#tag_view_list'), tag, []);
@ -623,4 +732,6 @@ $(document).ready(() => {
$(document).on("click", ".tag_delete", onTagDeleteClick); $(document).on("click", ".tag_delete", onTagDeleteClick);
$(document).on("input", ".tag_view_name", onTagRenameInput); $(document).on("input", ".tag_view_name", onTagRenameInput);
$(document).on("click", ".tag_view_create", onTagCreateClick); $(document).on("click", ".tag_view_create", onTagCreateClick);
$(document).on("click", ".tag_view_backup", onTagsBackupClick);
$(document).on("click", ".tag_view_restore", onBackupRestoreClick);
}); });