From 6c975275f82c79eed2c7757d55283c95d23ca5b8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 11 Jan 2021 20:38:34 +0900 Subject: Registry (#7073) * wip * wip * wip * wip * wip * Update registry.value.vue * wip * wip * wip * wip * typo --- src/client/account.ts | 1 - src/client/components/post-form.vue | 2 +- src/client/components/ui/info.vue | 2 +- src/client/init.ts | 8 -- src/client/pages/settings/deck.vue | 21 +++- src/client/pages/settings/index.vue | 30 +++++- src/client/pages/settings/other.vue | 8 +- src/client/pages/settings/registry.keys.vue | 115 +++++++++++++++++++++ src/client/pages/settings/registry.value.vue | 149 +++++++++++++++++++++++++++ src/client/pages/settings/registry.vue | 91 ++++++++++++++++ src/client/pizzax.ts | 56 ++++++---- src/client/router.ts | 1 - src/client/ui/deck.vue | 3 +- src/client/ui/deck/deck-store.ts | 85 ++++++++++++--- 14 files changed, 517 insertions(+), 55 deletions(-) create mode 100644 src/client/pages/settings/registry.keys.vue create mode 100644 src/client/pages/settings/registry.value.vue create mode 100644 src/client/pages/settings/registry.vue (limited to 'src/client') diff --git a/src/client/account.ts b/src/client/account.ts index fdf49ee213..e6ee8613d2 100644 --- a/src/client/account.ts +++ b/src/client/account.ts @@ -7,7 +7,6 @@ import { waiting } from '@/os'; type Account = { id: string; token: string; - clientData: Record; }; const data = localStorage.getItem('account'); diff --git a/src/client/components/post-form.vue b/src/client/components/post-form.vue index 19773b3b6c..bf300eebd8 100644 --- a/src/client/components/post-form.vue +++ b/src/client/components/post-form.vue @@ -262,7 +262,7 @@ export default defineComponent({ } // keep cw when reply - if (this.$store.keepCw && this.reply && this.reply.cw) { + if (this.$store.state.keepCw && this.reply && this.reply.cw) { this.useCw = true; this.cw = this.reply.cw; } diff --git a/src/client/components/ui/info.vue b/src/client/components/ui/info.vue index 3bdb69b3d1..5c71b14a0a 100644 --- a/src/client/components/ui/info.vue +++ b/src/client/components/ui/info.vue @@ -34,7 +34,7 @@ export default defineComponent({ font-size: 90%; background: var(--infoBg); color: var(--infoFg); - border-radius: 5px; + border-radius: var(--radius); &.warn { background: var(--infoWarnBg); diff --git a/src/client/init.ts b/src/client/init.ts index f39f50eea6..f09097fe31 100644 --- a/src/client/init.ts +++ b/src/client/init.ts @@ -347,14 +347,6 @@ if ($i) { updateAccount({ hasUnreadAnnouncement: false }); }); - main.on('clientSettingUpdated', x => { - updateAccount({ - clientData: { - [x.key]: x.value - } - }); - }); - // トークンが再生成されたとき // このままではMisskeyが利用できないので強制的にサインアウトさせる main.on('myTokenRegenerated', () => { diff --git a/src/client/pages/settings/deck.vue b/src/client/pages/settings/deck.vue index 0d9f1ab0aa..30d36d4a06 100644 --- a/src/client/pages/settings/deck.vue +++ b/src/client/pages/settings/deck.vue @@ -24,6 +24,8 @@ {{ $ts._deck.columnMargin }} + + {{ $ts._deck.profile }} @@ -31,7 +33,7 @@ import { defineComponent } from 'vue'; import { faImage, faCog, faColumns } from '@fortawesome/free-solid-svg-icons'; import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; +import FormLink from '@/components/form/link.vue'; import FormRadios from '@/components/form/radios.vue'; import FormInput from '@/components/form/input.vue'; import FormBase from '@/components/form/base.vue'; @@ -42,7 +44,7 @@ import * as os from '@/os'; export default defineComponent({ components: { FormSwitch, - FormSelect, + FormLink, FormInput, FormRadios, FormBase, @@ -67,6 +69,7 @@ export default defineComponent({ columnAlign: deckStore.makeGetterSetter('columnAlign'), columnMargin: deckStore.makeGetterSetter('columnMargin'), columnHeaderHeight: deckStore.makeGetterSetter('columnHeaderHeight'), + profile: deckStore.makeGetterSetter('profile'), }, watch: { @@ -85,5 +88,19 @@ export default defineComponent({ mounted() { this.$emit('info', this.INFO); }, + + methods: { + async setProfile() { + const { canceled, result: name } = await os.dialog({ + title: this.$ts._deck.profile, + input: { + allowEmpty: false + } + }); + if (canceled) return; + this.profile = name; + location.reload(); + } + } }); diff --git a/src/client/pages/settings/index.vue b/src/client/pages/settings/index.vue index aa9fe27164..0f95a76f11 100644 --- a/src/client/pages/settings/index.vue +++ b/src/client/pages/settings/index.vue @@ -35,13 +35,13 @@
- +
diff --git a/src/client/pages/settings/registry.value.vue b/src/client/pages/settings/registry.value.vue new file mode 100644 index 0000000000..943ededd21 --- /dev/null +++ b/src/client/pages/settings/registry.value.vue @@ -0,0 +1,149 @@ + + + diff --git a/src/client/pages/settings/registry.vue b/src/client/pages/settings/registry.vue new file mode 100644 index 0000000000..a43c98e730 --- /dev/null +++ b/src/client/pages/settings/registry.vue @@ -0,0 +1,91 @@ + + + diff --git a/src/client/pizzax.ts b/src/client/pizzax.ts index fdaf2bebb6..794738edd4 100644 --- a/src/client/pizzax.ts +++ b/src/client/pizzax.ts @@ -11,6 +11,7 @@ type ArrayElement = A extends readonly (infer T)[] ? T : never; export class Storage { public readonly key: string; + public readonly keyForLocalStorage: string; public readonly def: T; @@ -19,20 +20,22 @@ export class Storage { public readonly reactiveState: { [K in keyof T]: Ref }; constructor(key: string, def: T) { - this.key = 'pizzax::' + key; + this.key = key; + this.keyForLocalStorage = 'pizzax::' + key; this.def = def; // TODO: indexedDBにする - const deviceState = JSON.parse(localStorage.getItem(this.key) || '{}'); - const deviceAccountState = $i ? JSON.parse(localStorage.getItem(this.key + '::' + $i.id) || '{}') : {}; + const deviceState = JSON.parse(localStorage.getItem(this.keyForLocalStorage) || '{}'); + const deviceAccountState = $i ? JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::' + $i.id) || '{}') : {}; + const registryCache = $i ? JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::cache::' + $i.id) || '{}') : {}; const state = {}; const reactiveState = {}; for (const [k, v] of Object.entries(def)) { if (v.where === 'device' && Object.prototype.hasOwnProperty.call(deviceState, k)) { state[k] = deviceState[k]; - } else if (v.where === 'account' && $i && Object.prototype.hasOwnProperty.call($i.clientData, k)) { - state[k] = $i.clientData[k]; + } else if (v.where === 'account' && $i && Object.prototype.hasOwnProperty.call(registryCache, k)) { + state[k] = registryCache[k]; } else if (v.where === 'deviceAccount' && Object.prototype.hasOwnProperty.call(deviceAccountState, k)) { state[k] = deviceAccountState[k]; } else { @@ -47,16 +50,24 @@ export class Storage { this.reactiveState = reactiveState as any; if ($i) { - watch($i, () => { - if (_DEV_) console.log('$i updated'); - - for (const [k, v] of Object.entries(def)) { - if (v.where === 'account' && Object.prototype.hasOwnProperty.call($i!.clientData, k)) { - state[k] = $i!.clientData[k]; - reactiveState[k].value = $i!.clientData[k]; + // なぜかsetTimeoutしないとapi関数内でエラーになる(おそらく循環参照してることに原因がありそう) + setTimeout(() => { + api('i/registry/get-all', { scope: ['client', this.key] }).then(kvs => { + for (const [k, v] of Object.entries(def)) { + if (v.where === 'account') { + if (Object.prototype.hasOwnProperty.call(kvs, k)) { + state[k] = kvs[k]; + reactiveState[k].value = kvs[k]; + } else { + state[k] = v.default; + reactiveState[k].value = v.default; + } + } } - } - }); + }); + }, 1); + + // TODO: streamingのuser storage updateイベントを監視して更新 } } @@ -68,21 +79,26 @@ export class Storage { switch (this.def[key].where) { case 'device': { - const deviceState = JSON.parse(localStorage.getItem(this.key) || '{}'); + const deviceState = JSON.parse(localStorage.getItem(this.keyForLocalStorage) || '{}'); deviceState[key] = value; - localStorage.setItem(this.key, JSON.stringify(deviceState)); + localStorage.setItem(this.keyForLocalStorage, JSON.stringify(deviceState)); break; } case 'deviceAccount': { if ($i == null) break; - const deviceAccountState = JSON.parse(localStorage.getItem(this.key + '::' + $i.id) || '{}'); + const deviceAccountState = JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::' + $i.id) || '{}'); deviceAccountState[key] = value; - localStorage.setItem(this.key + '::' + $i.id, JSON.stringify(deviceAccountState)); + localStorage.setItem(this.keyForLocalStorage + '::' + $i.id, JSON.stringify(deviceAccountState)); break; } case 'account': { - api('i/update-client-setting', { - name: key, + if ($i == null) break; + const cache = JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::cache::' + $i.id) || '{}'); + cache[key] = value; + localStorage.setItem(this.keyForLocalStorage + '::cache::' + $i.id, JSON.stringify(cache)); + api('i/registry/set', { + scope: ['client', this.key], + key: key, value: value }); break; diff --git a/src/client/router.ts b/src/client/router.ts index 5753a47024..6f79426b23 100644 --- a/src/client/router.ts +++ b/src/client/router.ts @@ -81,7 +81,6 @@ export const router = createRouter({ { path: '/miauth/:session', component: page('miauth') }, { path: '/authorize-follow', component: page('follow') }, { path: '/share', component: page('share') }, - { path: '/test', component: page('test') }, { path: '/:catchAll(.*)', component: page('not-found') } ], // なんかHacky diff --git a/src/client/ui/deck.vue b/src/client/ui/deck.vue index 099a6f60c6..a074629ddd 100644 --- a/src/client/ui/deck.vue +++ b/src/client/ui/deck.vue @@ -41,7 +41,7 @@ import { getScrollContainer } from '@/scripts/scroll'; import * as os from '@/os'; import { sidebarDef } from '@/sidebar'; import XCommon from './_common_/common.vue'; -import { deckStore, addColumn } from './deck/deck-store'; +import { deckStore, addColumn, loadDeck } from './deck/deck-store'; export default defineComponent({ components: { @@ -88,6 +88,7 @@ export default defineComponent({ document.documentElement.style.overflowY = 'hidden'; document.documentElement.style.scrollBehavior = 'auto'; window.addEventListener('wheel', this.onWheel); + loadDeck(); }, mounted() { diff --git a/src/client/ui/deck/deck-store.ts b/src/client/ui/deck/deck-store.ts index 3d2e1873d3..93ea0a3228 100644 --- a/src/client/ui/deck/deck-store.ts +++ b/src/client/ui/deck/deck-store.ts @@ -1,5 +1,7 @@ +import { throttle } from 'throttle-debounce'; import { i18n } from '@/i18n'; -import { markRaw } from 'vue'; +import { api } from '@/os'; +import { markRaw, watch } from 'vue'; import { Storage } from '../../pizzax'; type ColumnWidget = { @@ -21,23 +23,17 @@ function copy(x: T): T { } export const deckStore = markRaw(new Storage('deck', { + profile: { + where: 'deviceAccount', + default: 'default' + }, columns: { where: 'deviceAccount', - default: [{ - id: 'a', - type: 'main', - name: i18n.locale._deck._columns.main, - width: 350, - }, { - id: 'b', - type: 'notifications', - name: i18n.locale._deck._columns.notifications, - width: 330, - }] as Column[] + default: [] as Column[] }, layout: { where: 'deviceAccount', - default: [['a'], ['b']] as Column['id'][][] + default: [] as Column['id'][][] }, columnAlign: { where: 'deviceAccount', @@ -61,10 +57,60 @@ export const deckStore = markRaw(new Storage('deck', { }, })); +export const loadDeck = async () => { + let deck; + + try { + deck = await api('i/registry/get', { + scope: ['client', 'deck', 'profiles'], + key: deckStore.state.profile, + }); + } catch (e) { + if (e.code === 'NO_SUCH_KEY') { + // 後方互換性のため + if (deckStore.state.profile === 'default') { + saveDeck(); + return; + } + + deckStore.set('columns', [{ + id: 'a', + type: 'main', + name: i18n.locale._deck._columns.main, + width: 350, + }, { + id: 'b', + type: 'notifications', + name: i18n.locale._deck._columns.notifications, + width: 330, + }]); + deckStore.set('layout', [['a'], ['b']]); + return; + } + throw e; + } + + deckStore.set('columns', deck.columns); + deckStore.set('layout', deck.layout); +}; + +// TODO: deckがloadされていない状態でsaveすると意図せず上書きが発生するので対策する +export const saveDeck = throttle(1000, () => { + api('i/registry/set', { + scope: ['client', 'deck', 'profiles'], + key: deckStore.state.profile, + value: { + columns: deckStore.reactiveState.columns.value, + layout: deckStore.reactiveState.layout.value, + } + }); +}); + export function addColumn(column: Column) { if (column.name == undefined) column.name = null; deckStore.push('columns', column); deckStore.push('layout', [column.id]); + saveDeck(); } export function removeColumn(id: Column['id']) { @@ -72,6 +118,7 @@ export function removeColumn(id: Column['id']) { deckStore.set('layout', deckStore.state.layout .map(ids => ids.filter(_id => _id !== id)) .filter(ids => ids.length > 0)); + saveDeck(); } export function swapColumn(a: Column['id'], b: Column['id']) { @@ -83,6 +130,7 @@ export function swapColumn(a: Column['id'], b: Column['id']) { layout[aX][aY] = b; layout[bX][bY] = a; deckStore.set('layout', layout); + saveDeck(); } export function swapLeftColumn(id: Column['id']) { @@ -98,6 +146,7 @@ export function swapLeftColumn(id: Column['id']) { return true; } }); + saveDeck(); } export function swapRightColumn(id: Column['id']) { @@ -113,6 +162,7 @@ export function swapRightColumn(id: Column['id']) { return true; } }); + saveDeck(); } export function swapUpColumn(id: Column['id']) { @@ -132,6 +182,7 @@ export function swapUpColumn(id: Column['id']) { return true; } }); + saveDeck(); } export function swapDownColumn(id: Column['id']) { @@ -151,6 +202,7 @@ export function swapDownColumn(id: Column['id']) { return true; } }); + saveDeck(); } export function stackLeftColumn(id: Column['id']) { @@ -160,6 +212,7 @@ export function stackLeftColumn(id: Column['id']) { layout[i - 1].push(id); layout = layout.filter(ids => ids.length > 0); deckStore.set('layout', layout); + saveDeck(); } export function popRightColumn(id: Column['id']) { @@ -169,6 +222,7 @@ export function popRightColumn(id: Column['id']) { layout.splice(i + 1, 0, [id]); layout = layout.filter(ids => ids.length > 0); deckStore.set('layout', layout); + saveDeck(); } export function addColumnWidget(id: Column['id'], widget: ColumnWidget) { @@ -180,6 +234,7 @@ export function addColumnWidget(id: Column['id'], widget: ColumnWidget) { column.widgets.unshift(widget); columns[columnIndex] = column; deckStore.set('columns', columns); + saveDeck(); } export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) { @@ -190,6 +245,7 @@ export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) { column.widgets = column.widgets.filter(w => w.id != widget.id); columns[columnIndex] = column; deckStore.set('columns', columns); + saveDeck(); } export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) { @@ -200,6 +256,7 @@ export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) { column.widgets = widgets; columns[columnIndex] = column; deckStore.set('columns', columns); + saveDeck(); } export function updateColumnWidget(id: Column['id'], widgetId: string, data: any) { @@ -213,6 +270,7 @@ export function updateColumnWidget(id: Column['id'], widgetId: string, data: any } : w); columns[columnIndex] = column; deckStore.set('columns', columns); + saveDeck(); } export function updateColumn(id: Column['id'], column: Partial) { @@ -225,4 +283,5 @@ export function updateColumn(id: Column['id'], column: Partial) { } columns[columnIndex] = currentColumn; deckStore.set('columns', columns); + saveDeck(); } -- cgit v1.2.3-freya