diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-03-09 12:34:08 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-09 12:34:08 +0900 |
| commit | d30ddd4c2ebcacc0d0b49c74e8dfe05b5422ba2e (patch) | |
| tree | c0c87a30037d3ffc11784627e67a1965b262c336 /packages/frontend/src/deck.ts | |
| parent | [skip ci] Update CHANGELOG.md (prepend template) (diff) | |
| download | sharkey-d30ddd4c2ebcacc0d0b49c74e8dfe05b5422ba2e.tar.gz sharkey-d30ddd4c2ebcacc0d0b49c74e8dfe05b5422ba2e.tar.bz2 sharkey-d30ddd4c2ebcacc0d0b49c74e8dfe05b5422ba2e.zip | |
Refine preferences (#15597)
* wip
* wip
* wip
* test
* wip rollup pluginでsearchIndexの情報生成
* wip
* SPDX
* wip: markerIdを自動付与
* rollupでビルド時・devモード時に毎回uuidを生成するように
* 開発サーバーでだけ必要な挙動は開発サーバーのみで
* 条件が逆
* wip: childrenの生成
* update comment
* update comment
* rename auto generated file
* hashをパスと行数から決定
* Update privacy.vue
* Update privacy.vue
* wip
* Update general.vue
* Update general.vue
* wip
* wip
* Update SearchMarker.vue
* wip
* Update profile.vue
* Update mute-block.vue
* Update mute-block.vue
* Update general.vue
* Update general.vue
* childrenがduplicate key errorを吐く問題をいったん解決
* マーカーの形を成形
* loggerを置きかえ
* とりあえず省略記法に対応
* Refactor and Format codes
* wip
* Update settings-search-index.ts
* wip
* wip
* とりあえず不確定要因の仮置きidを削除
* hashの生成を正規化(絶対パスになっていたのを緩和)
* pathの入力を省略可能に
* adminでもパス生成できるように
* Update settings-search-index.ts
* Update privacy.vue
* wip
* build searchIndex
* wip
* build
* Update general.vue
* build
* Update sounds.vue
* build
* build
* Update sounds.vue
* 🎨
* 🎨
* Update privacy.vue
* Update privacy.vue
* Update security.vue
* create-search-indexを多少改善
* build
* Update 2fa.vue
* wip
* 必ずtransformCodeCacheを利用するように, キャッシュの明確な受け渡しを定義
* キャッシュはdevServerでなくても更新
* Revert "wip"
This reverts commit 41bffd3a13f55618bf939dc1c9acb2a77ead4054.
* inlining
* wip
* Update theme.vue
* 🎨
* wip normalize
* Update theme.vue
* キャッシュのパス変換
* build
* wip
* wip
* Update SearchMarker.vue
* i18n.ts['key'] の形式が取り出せない問題のFix
* build
* 仮でpath入れ
* 必ず絶対パスが使われるように
* wip
* 🎨
* storybookビルド時はcreateSearchIndexをしない
* inliningの構造化
* format code
* Update index.vue
* wip
* wip
* 🎨
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* clean up
* wip
* wip
* wip
* Update rollup-plugin-unwind-css-module-class-name.test.ts
* Update navbar.vue
* clean up
* wip
* wip
* wip
* wip
* wip
* Update preferences-backups.vue
* Update common.ts
* Update preferences.ts
* wip
* wip
* wip
* wip
* Update MkPreferenceContainer.vue
* Update MkPreferenceContainer.vue
* Update MkPreferenceContainer.vue
* enhance: 検索で上下矢印を使用することで検索結果を移動できるように
* Update main-boot.ts
* refactor
* wip
* Update sounds.vue
* fix(frontend): PageWindowでSearchMarkerが動作するように
* enhance(frontend): SearchMarkerの点滅を一定時間で止める
* wip
* lint fix
* fix: 子要素監視が抜けていたのを修正
* アニメーションの回数はCSSで制御するように
* refactor
* enhance(frontend): 検索インデックス作成時のログを削減
* revert
* fix
* fix
* Update preferences.ts
* Update preferences.ts
* wip
* Update preferences.ts
* wip
* 🎨
* wip
* Update MkPreferenceContainer.vue
* wip
* Update preferences.ts
* wip
* Update preferences.ts
* Update preferences.ts
* wip
* wip
* Update preferences.ts
* wip
* wip
* Update preferences.ts
* Update CHANGELOG.md
* Update preferences.ts
* Update deck-store.ts
* deckStoreをdefaultStoreに統合
* wip
* defaultStore -> store
* Update profile.ts
* wip
* refactor
* wip: plugin
* plugin
* plugin
* plugin
* Update plugin.ts
* wip
* Update plugin.vue
* Update preferences.ts
* Update main-boot.ts
* wip
* fix test
* Update plugin.vue
* Update plugin.vue
* Update utility.ts
* wip
* wip
* Update utility.ts
* wip
* wip
* clean up
* Update utility.ts
---------
Co-authored-by: tai-cha <dev@taichan.site>
Co-authored-by: taichan <40626578+tai-cha@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
Diffstat (limited to 'packages/frontend/src/deck.ts')
| -rw-r--r-- | packages/frontend/src/deck.ts | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/packages/frontend/src/deck.ts b/packages/frontend/src/deck.ts new file mode 100644 index 0000000000..dee9a3db78 --- /dev/null +++ b/packages/frontend/src/deck.ts @@ -0,0 +1,306 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { throttle } from 'throttle-debounce'; +import { notificationTypes } from 'misskey-js'; +import type { BasicTimelineType } from '@/timelines.js'; +import type { SoundStore } from '@/preferences/def.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; +import { deepClone } from '@/scripts/clone.js'; +import { store } from '@/store.js'; + +type ColumnWidget = { + name: string; + id: string; + data: Record<string, any>; +}; + +export const columnTypes = [ + 'main', + 'widgets', + 'notifications', + 'tl', + 'antenna', + 'list', + 'channel', + 'mentions', + 'direct', + 'roleTimeline', +] as const; + +export type ColumnType = typeof columnTypes[number]; + +export type Column = { + id: string; + type: ColumnType; + name: string | null; + width: number; + widgets?: ColumnWidget[]; + active?: boolean; + flexible?: boolean; + antennaId?: string; + listId?: string; + channelId?: string; + roleId?: string; + excludeTypes?: typeof notificationTypes[number][]; + tl?: BasicTimelineType; + withRenotes?: boolean; + withReplies?: boolean; + withSensitive?: boolean; + onlyFiles?: boolean; + soundSetting?: SoundStore; +}; + +export const loadDeck = async () => { + let deck; + + try { + deck = await misskeyApi('i/registry/get', { + scope: ['client', 'deck', 'profiles'], + key: store.state['deck.profile'], + }); + } catch (err) { + if (typeof err === 'object' && err != null && 'code' in err && err.code === 'NO_SUCH_KEY') { + // 後方互換性のため + if (store.state['deck.profile'] === 'default') { + saveDeck(); + return; + } + + store.set('deck.columns', []); + store.set('deck.layout', []); + return; + } + throw err; + } + + store.set('deck.columns', deck.columns); + store.set('deck.layout', deck.layout); +}; + +export async function forceSaveDeck() { + await misskeyApi('i/registry/set', { + scope: ['client', 'deck', 'profiles'], + key: store.state['deck.profile'], + value: { + columns: store.reactiveState['deck.columns'].value, + layout: store.reactiveState['deck.layout'].value, + }, + }); +} + +// TODO: deckがloadされていない状態でsaveすると意図せず上書きが発生するので対策する +export const saveDeck = throttle(1000, () => { + forceSaveDeck(); +}); + +export async function getProfiles(): Promise<string[]> { + return await misskeyApi('i/registry/keys', { + scope: ['client', 'deck', 'profiles'], + }); +} + +export async function deleteProfile(key: string): Promise<void> { + return await misskeyApi('i/registry/remove', { + scope: ['client', 'deck', 'profiles'], + key: key, + }); +} + +export function addColumn(column: Column) { + if (column.name === undefined) column.name = null; + store.push('deck.columns', column); + store.push('deck.layout', [column.id]); + saveDeck(); +} + +export function removeColumn(id: Column['id']) { + store.set('deck.columns', store.state['deck.columns'].filter(c => c.id !== id)); + store.set('deck.layout', store.state['deck.layout'] + .map(ids => ids.filter(_id => _id !== id)) + .filter(ids => ids.length > 0)); + saveDeck(); +} + +export function swapColumn(a: Column['id'], b: Column['id']) { + const aX = store.state['deck.layout'].findIndex(ids => ids.indexOf(a) !== -1); + const aY = store.state['deck.layout'][aX].findIndex(id => id === a); + const bX = store.state['deck.layout'].findIndex(ids => ids.indexOf(b) !== -1); + const bY = store.state['deck.layout'][bX].findIndex(id => id === b); + const layout = deepClone(store.state['deck.layout']); + layout[aX][aY] = b; + layout[bX][bY] = a; + store.set('deck.layout', layout); + saveDeck(); +} + +export function swapLeftColumn(id: Column['id']) { + const layout = deepClone(store.state['deck.layout']); + store.state['deck.layout'].some((ids, i) => { + if (ids.includes(id)) { + const left = store.state['deck.layout'][i - 1]; + if (left) { + layout[i - 1] = store.state['deck.layout'][i]; + layout[i] = left; + store.set('deck.layout', layout); + } + return true; + } + return false; + }); + saveDeck(); +} + +export function swapRightColumn(id: Column['id']) { + const layout = deepClone(store.state['deck.layout']); + store.state['deck.layout'].some((ids, i) => { + if (ids.includes(id)) { + const right = store.state['deck.layout'][i + 1]; + if (right) { + layout[i + 1] = store.state['deck.layout'][i]; + layout[i] = right; + store.set('deck.layout', layout); + } + return true; + } + return false; + }); + saveDeck(); +} + +export function swapUpColumn(id: Column['id']) { + const layout = deepClone(store.state['deck.layout']); + const idsIndex = store.state['deck.layout'].findIndex(ids => ids.includes(id)); + const ids = deepClone(store.state['deck.layout'][idsIndex]); + ids.some((x, i) => { + if (x === id) { + const up = ids[i - 1]; + if (up) { + ids[i - 1] = id; + ids[i] = up; + + layout[idsIndex] = ids; + store.set('deck.layout', layout); + } + return true; + } + return false; + }); + saveDeck(); +} + +export function swapDownColumn(id: Column['id']) { + const layout = deepClone(store.state['deck.layout']); + const idsIndex = store.state['deck.layout'].findIndex(ids => ids.includes(id)); + const ids = deepClone(store.state['deck.layout'][idsIndex]); + ids.some((x, i) => { + if (x === id) { + const down = ids[i + 1]; + if (down) { + ids[i + 1] = id; + ids[i] = down; + + layout[idsIndex] = ids; + store.set('deck.layout', layout); + } + return true; + } + return false; + }); + saveDeck(); +} + +export function stackLeftColumn(id: Column['id']) { + let layout = deepClone(store.state['deck.layout']); + const i = store.state['deck.layout'].findIndex(ids => ids.includes(id)); + layout = layout.map(ids => ids.filter(_id => _id !== id)); + layout[i - 1].push(id); + layout = layout.filter(ids => ids.length > 0); + store.set('deck.layout', layout); + saveDeck(); +} + +export function popRightColumn(id: Column['id']) { + let layout = deepClone(store.state['deck.layout']); + const i = store.state['deck.layout'].findIndex(ids => ids.includes(id)); + const affected = layout[i]; + layout = layout.map(ids => ids.filter(_id => _id !== id)); + layout.splice(i + 1, 0, [id]); + layout = layout.filter(ids => ids.length > 0); + store.set('deck.layout', layout); + + const columns = deepClone(store.state['deck.columns']); + for (const column of columns) { + if (affected.includes(column.id)) { + column.active = true; + } + } + store.set('deck.columns', columns); + + saveDeck(); +} + +export function addColumnWidget(id: Column['id'], widget: ColumnWidget) { + const columns = deepClone(store.state['deck.columns']); + const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id); + const column = deepClone(store.state['deck.columns'][columnIndex]); + if (column == null) return; + if (column.widgets == null) column.widgets = []; + column.widgets.unshift(widget); + columns[columnIndex] = column; + store.set('deck.columns', columns); + saveDeck(); +} + +export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) { + const columns = deepClone(store.state['deck.columns']); + const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id); + const column = deepClone(store.state['deck.columns'][columnIndex]); + if (column == null) return; + if (column.widgets == null) column.widgets = []; + column.widgets = column.widgets.filter(w => w.id !== widget.id); + columns[columnIndex] = column; + store.set('deck.columns', columns); + saveDeck(); +} + +export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) { + const columns = deepClone(store.state['deck.columns']); + const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id); + const column = deepClone(store.state['deck.columns'][columnIndex]); + if (column == null) return; + column.widgets = widgets; + columns[columnIndex] = column; + store.set('deck.columns', columns); + saveDeck(); +} + +export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) { + const columns = deepClone(store.state['deck.columns']); + const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id); + const column = deepClone(store.state['deck.columns'][columnIndex]); + if (column == null) return; + if (column.widgets == null) column.widgets = []; + column.widgets = column.widgets.map(w => w.id === widgetId ? { + ...w, + data: widgetData, + } : w); + columns[columnIndex] = column; + store.set('deck.columns', columns); + saveDeck(); +} + +export function updateColumn(id: Column['id'], column: Partial<Column>) { + const columns = deepClone(store.state['deck.columns']); + const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id); + const currentColumn = deepClone(store.state['deck.columns'][columnIndex]); + if (currentColumn == null) return; + for (const [k, v] of Object.entries(column)) { + currentColumn[k] = v; + } + columns[columnIndex] = currentColumn; + store.set('deck.columns', columns); + saveDeck(); +} |