Skip to content

Commit

Permalink
feat: backup/restore/cleanup storage
Browse files Browse the repository at this point in the history
  • Loading branch information
pilar6195 committed Jun 20, 2024
1 parent a6c7fdb commit dbebd18
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 15 deletions.
6 changes: 5 additions & 1 deletion src/anilist-extras.user.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import '@/utils/Polyfill';
import '@/utils/Logs';
import { purgeUnusedSettings } from './utils/Settings';
import { observe, addStyles, getMalId } from './utils/Helpers';
import { anilistModules, malModules, activeModules, ModuleEmitter, ModuleEvents } from './utils/ModuleLoader';

/* Anilist Modules */
import '@/modules/anilist/settingsPage';
import '@/modules/anilist/addMalLink';
import '@/modules/anilist/addMalScore';
import '@/modules/anilist/addAniListScore';
import '@/modules/anilist/addMalScoreAndLink';
import '@/modules/anilist/addAniListScore';
import '@/modules/anilist/addViewToggles';
import '@/modules/anilist/addMalCharacters';
import '@/modules/anilist/addOpEdSongs';
Expand Down Expand Up @@ -41,6 +42,9 @@ addStyles(`
}
`);

// Clean up unused settings.
purgeUnusedSettings();

/* eslint-disable promise/prefer-await-to-then */

let currentPage: URL;
Expand Down
130 changes: 126 additions & 4 deletions src/modules/anilist/settingsPage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Storage from '@/utils/Storage';
import Cache from '@/utils/Cache';
import SettingsManager from '@/utils/Settings';
import SettingsManager, { purgeUnusedSettings } from '@/utils/Settings';
import {
$,
waitFor,
Expand Down Expand Up @@ -357,8 +357,111 @@ registerModule.anilist({
}
}

createElement('div', {
const restoreSettingsInput = createElement('input', {
attributes: {
type: 'file',
accept: '.json',
},
events: {
change(event) {
const file = (event.target as HTMLInputElement).files?.[0];

if (!file) return;

if (file.type !== 'application/json') {
// eslint-disable-next-line no-alert
alert('Invalid file type. Please select a valid JSON file.');
return;
}

const fileReader = new FileReader();

fileReader.onload = event => {
const result = event.target?.result;
try {
const fileContents = JSON.parse(result as string);

if (!fileContents.alextrasMeta) {
// eslint-disable-next-line no-alert
alert('Invalid JSON file. Please select a valid JSON file.');
return;
}

// Make sure we keep the API token if it exists.
const apiToken = Storage.get('apiToken');
if (apiToken) {
fileContents.apiToken = apiToken;
}

localStorage.setItem('anilist-extras', JSON.stringify(fileContents));
// eslint-disable-next-line no-alert
alert('AniList Extras settings have been restored. Page will refresh.');
location.reload();
} catch {
// eslint-disable-next-line no-alert
alert('Invalid JSON file. Please select a valid JSON file.');
}
};

fileReader.readAsText(file);
},
},
});

createElement('section', {
children: [
createElement('div', {
attributes: {
class: 'button',
},
textContent: 'Backup Settings',
events: {
async click() {
purgeUnusedSettings();

const storage = Storage.getAll();

// Remove API token from backup.
if (storage.apiToken) {
delete storage.apiToken;
}

storage.alextrasMeta = {
version: ALEXTRAS_VERSION,
createdAt: new Date().toISOString(),
};

const settingsBackup = JSON.stringify(storage, null, '\t');

const settingsBlob = new Blob([settingsBackup], {
type: 'application/json',
});

const aElement = createElement('a', {
attributes: {
download: `anilist-extras-settings-${new Date().toISOString()}.json`,
href: URL.createObjectURL(settingsBlob),
},
});

aElement.click();
},
},
}),
createElement('div', {
attributes: {
class: 'button',
},
styles: {
position: 'relative',
},
textContent: 'Restore Settings',
events: {
click() {
restoreSettingsInput.click();
},
},
}),
createElement('div', {
attributes: {
class: 'button danger',
Expand Down Expand Up @@ -387,11 +490,18 @@ registerModule.anilist({
},
},
}),
createElement('div', {
styles: {
marginTop: '15px',
color: 'rgb(var(--color-red))',
},
textContent: 'Restoring settings will overwrite your current settings.',
}),
],
appendTo: settingsContainer,
});

createElement('div', {
createElement('section', {
children: [
createElement('h4', {
textContent: 'AniList Extras Version: ',
Expand Down Expand Up @@ -437,7 +547,7 @@ registerModule.anilist({
styles: {
color: 'rgb(var(--color-blue))',
},
textContent: 'https://github.com/pilar6195/AniList-Extras',
textContent: 'pilar6195/AniList-Extras',
}),
],
}),
Expand All @@ -462,6 +572,18 @@ addStyles(`
gap: 10px;
}
@media only screen and (min-width: 761px) and (max-width: 950px) {
.alextras--settings-body {
grid-template-columns: 1fr;
}
}
@media only screen and (max-width: 650px) {
.alextras--settings-body {
grid-template-columns: 1fr;
}
}
.alextras--module {
display: flex;
flex-direction: column;
Expand Down
53 changes: 43 additions & 10 deletions src/utils/Settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
/* eslint-disable @typescript-eslint/no-dynamic-delete */
import Storage from './Storage';
import { getModule } from './ModuleLoader';
import { anilistModules, malModules, getModule } from './ModuleLoader';

export const purgeUnusedSettings = () => {
const moduleIds = [...anilistModules, ...malModules].map(m => m.id);
const settings = Storage.getAll();

for (const key of ['settings', 'moduleStates']) {
if (!settings[key]) continue;

let changed = false;

for (const moduleId in settings[key]) {
if (!moduleIds.includes(moduleId)) {
delete settings[key][moduleId];
changed = true;
}
}

if (changed) {
Storage.set(key, settings[key]);
}
}
};

/**
* Settings manager for registered modules.
Expand All @@ -26,28 +49,38 @@ export default class SettingsManager {
* Set a setting value.
*/
public set(key: string, value: any) {
const moduleSettings = Storage.get(this.moduleId, {});
const settings = Storage.get('settings', {});
const moduleSettings = settings[this.moduleId] ?? {};
moduleSettings[key] = value;
Storage.set(this.moduleId, moduleSettings);
settings[this.moduleId] = moduleSettings;
Storage.set('settings', settings);
}

/**
* Get a setting value.
* If the setting is not found, it will return the default value from the module settings.
* If the setting is not specified in the module settings, it will return the default value passed as the second argument.
* If the setting is not found, it will return the default value passed as the second argument.
* If a default value is not provided, it will return the default value from the module settings, if any.
*/
public get(key: string, defaultValue?: any) {
const moduleSettings = Storage.get(this.moduleId, {});
return moduleSettings[key] ?? this.module.settingsPage?.[key].default ?? defaultValue;
const settings = Storage.get('settings', {});
const moduleSettings = settings[this.moduleId] ?? {};
return moduleSettings[key] ?? defaultValue ?? this.module.settingsPage?.[key].default;
}

/**
* Delete a setting.
*/
public remove(key: string) {
const moduleSettings = Storage.get(this.moduleId, {});
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
const settings = Storage.get('settings', {});
const moduleSettings = settings[this.moduleId] ?? {};
delete moduleSettings[key];
Storage.set(this.moduleId, moduleSettings);
settings[this.moduleId] = moduleSettings;
Storage.set('settings', settings);
}

public clear() {
const settings = Storage.get('settings', {});
delete settings[this.moduleId];
Storage.set('settings', settings);
}
}

0 comments on commit dbebd18

Please sign in to comment.