diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2022-12-27 14:36:33 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2022-12-27 14:36:33 +0900 |
| commit | 9384f5399da39e53855beb8e7f8ded1aa56bf72e (patch) | |
| tree | ce5959571a981b9c4047da3c7b3fd080aa44222c /packages/client/src/ui/deck | |
| parent | wip: retention for dashboard (diff) | |
| download | misskey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.gz misskey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.bz2 misskey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.zip | |
rename: client -> frontend
Diffstat (limited to 'packages/client/src/ui/deck')
| -rw-r--r-- | packages/client/src/ui/deck/antenna-column.vue | 70 | ||||
| -rw-r--r-- | packages/client/src/ui/deck/column-core.vue | 34 | ||||
| -rw-r--r-- | packages/client/src/ui/deck/column.vue | 398 | ||||
| -rw-r--r-- | packages/client/src/ui/deck/deck-store.ts | 296 | ||||
| -rw-r--r-- | packages/client/src/ui/deck/direct-column.vue | 31 | ||||
| -rw-r--r-- | packages/client/src/ui/deck/list-column.vue | 58 | ||||
| -rw-r--r-- | packages/client/src/ui/deck/main-column.vue | 68 | ||||
| -rw-r--r-- | packages/client/src/ui/deck/mentions-column.vue | 28 | ||||
| -rw-r--r-- | packages/client/src/ui/deck/notifications-column.vue | 44 | ||||
| -rw-r--r-- | packages/client/src/ui/deck/tl-column.vue | 119 | ||||
| -rw-r--r-- | packages/client/src/ui/deck/widgets-column.vue | 69 |
11 files changed, 0 insertions, 1215 deletions
diff --git a/packages/client/src/ui/deck/antenna-column.vue b/packages/client/src/ui/deck/antenna-column.vue deleted file mode 100644 index ba14530662..0000000000 --- a/packages/client/src/ui/deck/antenna-column.vue +++ /dev/null @@ -1,70 +0,0 @@ -<template> -<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> - <template #header> - <i class="ti ti-antenna"></i><span style="margin-left: 8px;">{{ column.name }}</span> - </template> - - <XTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @after="() => emit('loaded')"/> -</XColumn> -</template> - -<script lang="ts" setup> -import { onMounted } from 'vue'; -import XColumn from './column.vue'; -import { updateColumn, Column } from './deck-store'; -import XTimeline from '@/components/MkTimeline.vue'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; - -const props = defineProps<{ - column: Column; - isStacked: boolean; -}>(); - -const emit = defineEmits<{ - (ev: 'loaded'): void; - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - -let timeline = $ref<InstanceType<typeof XTimeline>>(); - -onMounted(() => { - if (props.column.antennaId == null) { - setAntenna(); - } -}); - -async function setAntenna() { - const antennas = await os.api('antennas/list'); - const { canceled, result: antenna } = await os.select({ - title: i18n.ts.selectAntenna, - items: antennas.map(x => ({ - value: x, text: x.name, - })), - default: props.column.antennaId, - }); - if (canceled) return; - updateColumn(props.column.id, { - antennaId: antenna.id, - }); -} - -const menu = [{ - icon: 'ti ti-pencil', - text: i18n.ts.selectAntenna, - action: setAntenna, -}]; - -/* -function focus() { - timeline.focus(); -} - -defineExpose({ - focus, -}); -*/ -</script> - -<style lang="scss" scoped> -</style> diff --git a/packages/client/src/ui/deck/column-core.vue b/packages/client/src/ui/deck/column-core.vue deleted file mode 100644 index 30c0dc5e1c..0000000000 --- a/packages/client/src/ui/deck/column-core.vue +++ /dev/null @@ -1,34 +0,0 @@ -<template> -<!-- TODO: リファクタの余地がありそう --> -<div v-if="!column">たぶん見えちゃいけないやつ</div> -<XMainColumn v-else-if="column.type === 'main'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XWidgetsColumn v-else-if="column.type === 'widgets'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XNotificationsColumn v-else-if="column.type === 'notifications'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XTlColumn v-else-if="column.type === 'tl'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XListColumn v-else-if="column.type === 'list'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XAntennaColumn v-else-if="column.type === 'antenna'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XMentionsColumn v-else-if="column.type === 'mentions'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XDirectColumn v-else-if="column.type === 'direct'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import XMainColumn from './main-column.vue'; -import XTlColumn from './tl-column.vue'; -import XAntennaColumn from './antenna-column.vue'; -import XListColumn from './list-column.vue'; -import XNotificationsColumn from './notifications-column.vue'; -import XWidgetsColumn from './widgets-column.vue'; -import XMentionsColumn from './mentions-column.vue'; -import XDirectColumn from './direct-column.vue'; -import { Column } from './deck-store'; - -defineProps<{ - column?: Column; - isStacked: boolean; -}>(); - -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); -</script> diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue deleted file mode 100644 index 2a99b621e6..0000000000 --- a/packages/client/src/ui/deck/column.vue +++ /dev/null @@ -1,398 +0,0 @@ -<template> -<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため --> -<section - v-hotkey="keymap" class="dnpfarvg _narrow_" - :class="{ paged: isMainColumn, naked, active, isStacked, draghover, dragging, dropready }" - @dragover.prevent.stop="onDragover" - @dragleave="onDragleave" - @drop.prevent.stop="onDrop" -> - <header - :class="{ indicated }" - draggable="true" - @click="goTop" - @dragstart="onDragstart" - @dragend="onDragend" - @contextmenu.prevent.stop="onContextmenu" - > - <button v-if="isStacked && !isMainColumn" class="toggleActive _button" @click="toggleActive"> - <template v-if="active"><i class="ti ti-chevron-up"></i></template> - <template v-else><i class="ti ti-chevron-down"></i></template> - </button> - <div class="action"> - <slot name="action"></slot> - </div> - <span class="header"><slot name="header"></slot></span> - <button v-tooltip="i18n.ts.settings" class="menu _button" @click.stop="showSettingsMenu"><i class="ti ti-dots"></i></button> - </header> - <div v-show="active" ref="body"> - <slot></slot> - </div> -</section> -</template> - -<script lang="ts" setup> -import { onBeforeUnmount, onMounted, provide, Ref, watch } from 'vue'; -import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column, deckStore } from './deck-store'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; -import { MenuItem } from '@/types/menu'; - -provide('shouldHeaderThin', true); -provide('shouldOmitHeaderTitle', true); -provide('shouldSpacerMin', true); - -const props = withDefaults(defineProps<{ - column: Column; - isStacked?: boolean; - naked?: boolean; - indicated?: boolean; - menu?: MenuItem[]; -}>(), { - isStacked: false, - naked: false, - indicated: false, -}); - -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; - (ev: 'change-active-state', v: boolean): void; -}>(); - -let body = $ref<HTMLDivElement>(); - -let dragging = $ref(false); -watch($$(dragging), v => os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd')); - -let draghover = $ref(false); -let dropready = $ref(false); - -const isMainColumn = $computed(() => props.column.type === 'main'); -const active = $computed(() => props.column.active !== false); -watch($$(active), v => emit('change-active-state', v)); - -const keymap = $computed(() => ({ - 'shift+up': () => emit('parent-focus', 'up'), - 'shift+down': () => emit('parent-focus', 'down'), - 'shift+left': () => emit('parent-focus', 'left'), - 'shift+right': () => emit('parent-focus', 'right'), -})); - -onMounted(() => { - os.deckGlobalEvents.on('column.dragStart', onOtherDragStart); - os.deckGlobalEvents.on('column.dragEnd', onOtherDragEnd); -}); - -onBeforeUnmount(() => { - os.deckGlobalEvents.off('column.dragStart', onOtherDragStart); - os.deckGlobalEvents.off('column.dragEnd', onOtherDragEnd); -}); - -function onOtherDragStart() { - dropready = true; -} - -function onOtherDragEnd() { - dropready = false; -} - -function toggleActive() { - if (!props.isStacked) return; - updateColumn(props.column.id, { - active: !props.column.active, - }); -} - -function getMenu() { - let items = [{ - icon: 'ti ti-settings', - text: i18n.ts._deck.configureColumn, - action: async () => { - const { canceled, result } = await os.form(props.column.name, { - name: { - type: 'string', - label: i18n.ts.name, - default: props.column.name, - }, - width: { - type: 'number', - label: i18n.ts.width, - default: props.column.width, - }, - flexible: { - type: 'boolean', - label: i18n.ts.flexible, - default: props.column.flexible, - }, - }); - if (canceled) return; - updateColumn(props.column.id, result); - }, - }, { - type: 'parent', - text: i18n.ts.move + '...', - icon: 'ti ti-arrows-move', - children: [{ - icon: 'ti ti-arrow-left', - text: i18n.ts._deck.swapLeft, - action: () => { - swapLeftColumn(props.column.id); - }, - }, { - icon: 'ti ti-arrow-right', - text: i18n.ts._deck.swapRight, - action: () => { - swapRightColumn(props.column.id); - }, - }, props.isStacked ? { - icon: 'ti ti-arrow-up', - text: i18n.ts._deck.swapUp, - action: () => { - swapUpColumn(props.column.id); - }, - } : undefined, props.isStacked ? { - icon: 'ti ti-arrow-down', - text: i18n.ts._deck.swapDown, - action: () => { - swapDownColumn(props.column.id); - }, - } : undefined], - }, { - icon: 'ti ti-stack-2', - text: i18n.ts._deck.stackLeft, - action: () => { - stackLeftColumn(props.column.id); - }, - }, props.isStacked ? { - icon: 'ti ti-window-maximize', - text: i18n.ts._deck.popRight, - action: () => { - popRightColumn(props.column.id); - }, - } : undefined, null, { - icon: 'ti ti-trash', - text: i18n.ts.remove, - danger: true, - action: () => { - removeColumn(props.column.id); - }, - }]; - - if (props.menu) { - items.unshift(null); - items = props.menu.concat(items); - } - - return items; -} - -function showSettingsMenu(ev: MouseEvent) { - os.popupMenu(getMenu(), ev.currentTarget ?? ev.target); -} - -function onContextmenu(ev: MouseEvent) { - os.contextMenu(getMenu(), ev); -} - -function goTop() { - body.scrollTo({ - top: 0, - behavior: 'smooth', - }); -} - -function onDragstart(ev) { - ev.dataTransfer.effectAllowed = 'move'; - ev.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, props.column.id); - - // Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう - // SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately - window.setTimeout(() => { - dragging = true; - }, 10); -} - -function onDragend(ev) { - dragging = false; -} - -function onDragover(ev) { - // 自分自身がドラッグされている場合 - if (dragging) { - // 自分自身にはドロップさせない - ev.dataTransfer.dropEffect = 'none'; - } else { - const isDeckColumn = ev.dataTransfer.types[0] === _DATA_TRANSFER_DECK_COLUMN_; - - ev.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; - - if (isDeckColumn) draghover = true; - } -} - -function onDragleave() { - draghover = false; -} - -function onDrop(ev) { - draghover = false; - os.deckGlobalEvents.emit('column.dragEnd'); - - const id = ev.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_); - if (id != null && id !== '') { - swapColumn(props.column.id, id); - } -} -</script> - -<style lang="scss" scoped> -.dnpfarvg { - --root-margin: 10px; - --deckColumnHeaderHeight: 40px; - - height: 100%; - overflow: hidden; - contain: strict; - - &.draghover { - &:after { - content: ""; - display: block; - position: absolute; - z-index: 1000; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: var(--focus); - } - } - - &.dragging { - &:after { - content: ""; - display: block; - position: absolute; - z-index: 1000; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: var(--focus); - opacity: 0.5; - } - } - - &.dropready { - * { - pointer-events: none; - } - } - - &:not(.active) { - flex-basis: var(--deckColumnHeaderHeight); - min-height: var(--deckColumnHeaderHeight); - - > header.indicated { - box-shadow: 4px 0px var(--accent) inset; - } - } - - &.naked { - background: var(--acrylicBg) !important; - -webkit-backdrop-filter: var(--blur, blur(10px)); - backdrop-filter: var(--blur, blur(10px)); - - > header { - background: transparent; - box-shadow: none; - - > button { - color: var(--fg); - } - } - } - - &.paged { - background: var(--bg) !important; - } - - > header { - position: relative; - display: flex; - z-index: 2; - line-height: var(--deckColumnHeaderHeight); - height: var(--deckColumnHeaderHeight); - padding: 0 16px; - font-size: 0.9em; - color: var(--panelHeaderFg); - background: var(--panelHeaderBg); - box-shadow: 0 1px 0 0 var(--panelHeaderDivider); - cursor: pointer; - - &, * { - user-select: none; - } - - &.indicated { - box-shadow: 0 3px 0 0 var(--accent); - } - - > .header { - display: inline-block; - align-items: center; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - > span:only-of-type { - width: 100%; - } - - > .toggleActive, - > .action > ::v-deep(*), - > .menu { - z-index: 1; - width: var(--deckColumnHeaderHeight); - line-height: var(--deckColumnHeaderHeight); - color: var(--faceTextButton); - - &:hover { - color: var(--faceTextButtonHover); - } - - &:active { - color: var(--faceTextButtonActive); - } - } - - > .toggleActive, > .action { - margin-left: -16px; - } - - > .action { - z-index: 1; - } - - > .action:empty { - display: none; - } - - > .menu { - margin-left: auto; - margin-right: -16px; - } - } - - > div { - height: calc(100% - var(--deckColumnHeaderHeight)); - overflow-y: auto; - overflow-x: hidden; // Safari does not supports clip - overflow-x: clip; - -webkit-overflow-scrolling: touch; - box-sizing: border-box; - background-color: var(--bg); - } -} -</style> diff --git a/packages/client/src/ui/deck/deck-store.ts b/packages/client/src/ui/deck/deck-store.ts deleted file mode 100644 index 56db7398e5..0000000000 --- a/packages/client/src/ui/deck/deck-store.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { throttle } from 'throttle-debounce'; -import { markRaw } from 'vue'; -import { notificationTypes } from 'misskey-js'; -import { Storage } from '../../pizzax'; -import { i18n } from '@/i18n'; -import { api } from '@/os'; -import { deepClone } from '@/scripts/clone'; - -type ColumnWidget = { - name: string; - id: string; - data: Record<string, any>; -}; - -export type Column = { - id: string; - type: 'main' | 'widgets' | 'notifications' | 'tl' | 'antenna' | 'list' | 'mentions' | 'direct'; - name: string | null; - width: number; - widgets?: ColumnWidget[]; - active?: boolean; - flexible?: boolean; - antennaId?: string; - listId?: string; - includingTypes?: typeof notificationTypes[number][]; - tl?: 'home' | 'local' | 'social' | 'global'; -}; - -export const deckStore = markRaw(new Storage('deck', { - profile: { - where: 'deviceAccount', - default: 'default', - }, - columns: { - where: 'deviceAccount', - default: [] as Column[], - }, - layout: { - where: 'deviceAccount', - default: [] as Column['id'][][], - }, - columnAlign: { - where: 'deviceAccount', - default: 'left' as 'left' | 'right' | 'center', - }, - alwaysShowMainColumn: { - where: 'deviceAccount', - default: true, - }, - navWindow: { - where: 'deviceAccount', - default: true, - }, -})); - -export const loadDeck = async () => { - let deck; - - try { - deck = await api('i/registry/get', { - scope: ['client', 'deck', 'profiles'], - key: deckStore.state.profile, - }); - } catch (err) { - if (err.code === 'NO_SUCH_KEY') { - // 後方互換性のため - if (deckStore.state.profile === 'default') { - saveDeck(); - return; - } - - deckStore.set('columns', []); - deckStore.set('layout', []); - return; - } - throw err; - } - - 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 async function getProfiles(): Promise<string[]> { - return await api('i/registry/keys', { - scope: ['client', 'deck', 'profiles'], - }); -} - -export async function deleteProfile(key: string): Promise<void> { - return await api('i/registry/remove', { - scope: ['client', 'deck', 'profiles'], - key: key, - }); -} - -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']) { - deckStore.set('columns', deckStore.state.columns.filter(c => c.id !== 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']) { - const aX = deckStore.state.layout.findIndex(ids => ids.indexOf(a) !== -1); - const aY = deckStore.state.layout[aX].findIndex(id => id === a); - const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) !== -1); - const bY = deckStore.state.layout[bX].findIndex(id => id === b); - const layout = deepClone(deckStore.state.layout); - layout[aX][aY] = b; - layout[bX][bY] = a; - deckStore.set('layout', layout); - saveDeck(); -} - -export function swapLeftColumn(id: Column['id']) { - const layout = deepClone(deckStore.state.layout); - deckStore.state.layout.some((ids, i) => { - if (ids.includes(id)) { - const left = deckStore.state.layout[i - 1]; - if (left) { - layout[i - 1] = deckStore.state.layout[i]; - layout[i] = left; - deckStore.set('layout', layout); - } - return true; - } - }); - saveDeck(); -} - -export function swapRightColumn(id: Column['id']) { - const layout = deepClone(deckStore.state.layout); - deckStore.state.layout.some((ids, i) => { - if (ids.includes(id)) { - const right = deckStore.state.layout[i + 1]; - if (right) { - layout[i + 1] = deckStore.state.layout[i]; - layout[i] = right; - deckStore.set('layout', layout); - } - return true; - } - }); - saveDeck(); -} - -export function swapUpColumn(id: Column['id']) { - const layout = deepClone(deckStore.state.layout); - const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id)); - const ids = deepClone(deckStore.state.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; - deckStore.set('layout', layout); - } - return true; - } - }); - saveDeck(); -} - -export function swapDownColumn(id: Column['id']) { - const layout = deepClone(deckStore.state.layout); - const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id)); - const ids = deepClone(deckStore.state.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; - deckStore.set('layout', layout); - } - return true; - } - }); - saveDeck(); -} - -export function stackLeftColumn(id: Column['id']) { - let layout = deepClone(deckStore.state.layout); - const i = deckStore.state.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); - deckStore.set('layout', layout); - saveDeck(); -} - -export function popRightColumn(id: Column['id']) { - let layout = deepClone(deckStore.state.layout); - const i = deckStore.state.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); - deckStore.set('layout', layout); - - const columns = deepClone(deckStore.state.columns); - for (const column of columns) { - if (affected.includes(column.id)) { - column.active = true; - } - } - deckStore.set('columns', columns); - - saveDeck(); -} - -export function addColumnWidget(id: Column['id'], widget: ColumnWidget) { - const columns = deepClone(deckStore.state.columns); - const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); - const column = deepClone(deckStore.state.columns[columnIndex]); - if (column == null) return; - if (column.widgets == null) column.widgets = []; - column.widgets.unshift(widget); - columns[columnIndex] = column; - deckStore.set('columns', columns); - saveDeck(); -} - -export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) { - const columns = deepClone(deckStore.state.columns); - const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); - const column = deepClone(deckStore.state.columns[columnIndex]); - if (column == null) return; - 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[]) { - const columns = deepClone(deckStore.state.columns); - const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); - const column = deepClone(deckStore.state.columns[columnIndex]); - if (column == null) return; - column.widgets = widgets; - columns[columnIndex] = column; - deckStore.set('columns', columns); - saveDeck(); -} - -export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) { - const columns = deepClone(deckStore.state.columns); - const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); - const column = deepClone(deckStore.state.columns[columnIndex]); - if (column == null) return; - column.widgets = column.widgets.map(w => w.id === widgetId ? { - ...w, - data: widgetData, - } : w); - columns[columnIndex] = column; - deckStore.set('columns', columns); - saveDeck(); -} - -export function updateColumn(id: Column['id'], column: Partial<Column>) { - const columns = deepClone(deckStore.state.columns); - const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); - const currentColumn = deepClone(deckStore.state.columns[columnIndex]); - if (currentColumn == null) return; - for (const [k, v] of Object.entries(column)) { - currentColumn[k] = v; - } - columns[columnIndex] = currentColumn; - deckStore.set('columns', columns); - saveDeck(); -} diff --git a/packages/client/src/ui/deck/direct-column.vue b/packages/client/src/ui/deck/direct-column.vue deleted file mode 100644 index 75b018cacd..0000000000 --- a/packages/client/src/ui/deck/direct-column.vue +++ /dev/null @@ -1,31 +0,0 @@ -<template> -<XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> - <template #header><i class="ti ti-mail" style="margin-right: 8px;"></i>{{ column.name }}</template> - - <XNotes :pagination="pagination"/> -</XColumn> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import XColumn from './column.vue'; -import XNotes from '@/components/MkNotes.vue'; -import { Column } from './deck-store'; - -defineProps<{ - column: Column; - isStacked: boolean; -}>(); - -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - -const pagination = { - endpoint: 'notes/mentions' as const, - limit: 10, - params: { - visibility: 'specified', - }, -}; -</script> diff --git a/packages/client/src/ui/deck/list-column.vue b/packages/client/src/ui/deck/list-column.vue deleted file mode 100644 index d9f3f7b4e7..0000000000 --- a/packages/client/src/ui/deck/list-column.vue +++ /dev/null @@ -1,58 +0,0 @@ -<template> -<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> - <template #header> - <i class="ti ti-list"></i><span style="margin-left: 8px;">{{ column.name }}</span> - </template> - - <XTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" @after="() => emit('loaded')"/> -</XColumn> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import XColumn from './column.vue'; -import { updateColumn, Column } from './deck-store'; -import XTimeline from '@/components/MkTimeline.vue'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; - -const props = defineProps<{ - column: Column; - isStacked: boolean; -}>(); - -const emit = defineEmits<{ - (ev: 'loaded'): void; - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - -let timeline = $ref<InstanceType<typeof XTimeline>>(); - -if (props.column.listId == null) { - setList(); -} - -async function setList() { - const lists = await os.api('users/lists/list'); - const { canceled, result: list } = await os.select({ - title: i18n.ts.selectList, - items: lists.map(x => ({ - value: x, text: x.name, - })), - default: props.column.listId, - }); - if (canceled) return; - updateColumn(props.column.id, { - listId: list.id, - }); -} - -const menu = [{ - icon: 'ti ti-pencil', - text: i18n.ts.selectList, - action: setList, -}]; -</script> - -<style lang="scss" scoped> -</style> diff --git a/packages/client/src/ui/deck/main-column.vue b/packages/client/src/ui/deck/main-column.vue deleted file mode 100644 index 0c66172397..0000000000 --- a/packages/client/src/ui/deck/main-column.vue +++ /dev/null @@ -1,68 +0,0 @@ -<template> -<XColumn v-if="deckStore.state.alwaysShowMainColumn || mainRouter.currentRoute.value.name !== 'index'" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> - <template #header> - <template v-if="pageMetadata?.value"> - <i :class="pageMetadata?.value.icon"></i> - {{ pageMetadata?.value.title }} - </template> - </template> - - <RouterView @contextmenu.stop="onContextmenu"/> -</XColumn> -</template> - -<script lang="ts" setup> -import { ComputedRef, provide } from 'vue'; -import XColumn from './column.vue'; -import { deckStore, Column } from '@/ui/deck/deck-store'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; -import { mainRouter } from '@/router'; -import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; - -defineProps<{ - column: Column; - isStacked: boolean; -}>(); - -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - -let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); - -provide('router', mainRouter); -provideMetadataReceiver((info) => { - pageMetadata = info; -}); - -/* -function back() { - history.back(); -} -*/ -function onContextmenu(ev: MouseEvent) { - if (!ev.target) return; - - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(ev.target as HTMLElement)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes((ev.target as HTMLElement).tagName) || (ev.target as HTMLElement).attributes['contenteditable']) return; - if (window.getSelection()?.toString() !== '') return; - const path = mainRouter.currentRoute.value.path; - os.contextMenu([{ - type: 'label', - text: path, - }, { - icon: 'ti ti-window-maximize', - text: i18n.ts.openInWindow, - action: () => { - os.pageWindow(path); - }, - }], ev); -} -</script> diff --git a/packages/client/src/ui/deck/mentions-column.vue b/packages/client/src/ui/deck/mentions-column.vue deleted file mode 100644 index 16962956a0..0000000000 --- a/packages/client/src/ui/deck/mentions-column.vue +++ /dev/null @@ -1,28 +0,0 @@ -<template> -<XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> - <template #header><i class="ti ti-at" style="margin-right: 8px;"></i>{{ column.name }}</template> - - <XNotes :pagination="pagination"/> -</XColumn> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import XColumn from './column.vue'; -import XNotes from '@/components/MkNotes.vue'; -import { Column } from './deck-store'; - -defineProps<{ - column: Column; - isStacked: boolean; -}>(); - -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - -const pagination = { - endpoint: 'notes/mentions' as const, - limit: 10, -}; -</script> diff --git a/packages/client/src/ui/deck/notifications-column.vue b/packages/client/src/ui/deck/notifications-column.vue deleted file mode 100644 index 9d133035fe..0000000000 --- a/packages/client/src/ui/deck/notifications-column.vue +++ /dev/null @@ -1,44 +0,0 @@ -<template> -<XColumn :column="column" :is-stacked="isStacked" :menu="menu" @parent-focus="$event => emit('parent-focus', $event)"> - <template #header><i class="ti ti-bell" style="margin-right: 8px;"></i>{{ column.name }}</template> - - <XNotifications :include-types="column.includingTypes"/> -</XColumn> -</template> - -<script lang="ts" setup> -import { defineAsyncComponent } from 'vue'; -import XColumn from './column.vue'; -import { updateColumn, Column } from './deck-store'; -import XNotifications from '@/components/MkNotifications.vue'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; - -const props = defineProps<{ - column: Column; - isStacked: boolean; -}>(); - -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - -function func() { - os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSettingWindow.vue')), { - includingTypes: props.column.includingTypes, - }, { - done: async (res) => { - const { includingTypes } = res; - updateColumn(props.column.id, { - includingTypes: includingTypes, - }); - }, - }, 'closed'); -} - -const menu = [{ - icon: 'ti ti-pencil', - text: i18n.ts.notificationSetting, - action: func, -}]; -</script> diff --git a/packages/client/src/ui/deck/tl-column.vue b/packages/client/src/ui/deck/tl-column.vue deleted file mode 100644 index 49b29145ff..0000000000 --- a/packages/client/src/ui/deck/tl-column.vue +++ /dev/null @@ -1,119 +0,0 @@ -<template> -<XColumn :menu="menu" :column="column" :is-stacked="isStacked" :indicated="indicated" @change-active-state="onChangeActiveState" @parent-focus="$event => emit('parent-focus', $event)"> - <template #header> - <i v-if="column.tl === 'home'" class="ti ti-home"></i> - <i v-else-if="column.tl === 'local'" class="ti ti-messages"></i> - <i v-else-if="column.tl === 'social'" class="ti ti-share"></i> - <i v-else-if="column.tl === 'global'" class="ti ti-world"></i> - <span style="margin-left: 8px;">{{ column.name }}</span> - </template> - - <div v-if="disabled" class="iwaalbte"> - <p> - <i class="ti ti-minus-circle"></i> - {{ $t('disabled-timeline.title') }} - </p> - <p class="desc">{{ $t('disabled-timeline.description') }}</p> - </div> - <XTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => emit('loaded')" @queue="queueUpdated" @note="onNote"/> -</XColumn> -</template> - -<script lang="ts" setup> -import { onMounted } from 'vue'; -import XColumn from './column.vue'; -import { removeColumn, updateColumn, Column } from './deck-store'; -import XTimeline from '@/components/MkTimeline.vue'; -import * as os from '@/os'; -import { $i } from '@/account'; -import { instance } from '@/instance'; -import { i18n } from '@/i18n'; - -const props = defineProps<{ - column: Column; - isStacked: boolean; -}>(); - -const emit = defineEmits<{ - (ev: 'loaded'): void; - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - -let disabled = $ref(false); -let indicated = $ref(false); -let columnActive = $ref(true); - -onMounted(() => { - if (props.column.tl == null) { - setType(); - } else if ($i) { - disabled = !$i.isModerator && !$i.isAdmin && ( - instance.disableLocalTimeline && ['local', 'social'].includes(props.column.tl) || - instance.disableGlobalTimeline && ['global'].includes(props.column.tl)); - } -}); - -async function setType() { - const { canceled, result: src } = await os.select({ - title: i18n.ts.timeline, - items: [{ - value: 'home' as const, text: i18n.ts._timelines.home, - }, { - value: 'local' as const, text: i18n.ts._timelines.local, - }, { - value: 'social' as const, text: i18n.ts._timelines.social, - }, { - value: 'global' as const, text: i18n.ts._timelines.global, - }], - }); - if (canceled) { - if (props.column.tl == null) { - removeColumn(props.column.id); - } - return; - } - updateColumn(props.column.id, { - tl: src, - }); -} - -function queueUpdated(q) { - if (columnActive) { - indicated = q !== 0; - } -} - -function onNote() { - if (!columnActive) { - indicated = true; - } -} - -function onChangeActiveState(state) { - columnActive = state; - - if (columnActive) { - indicated = false; - } -} - -const menu = [{ - icon: 'ti ti-pencil', - text: i18n.ts.timeline, - action: setType, -}]; -</script> - -<style lang="scss" scoped> -.iwaalbte { - text-align: center; - - > p { - margin: 16px; - - &.desc { - font-size: 14px; - } - } -} -</style> diff --git a/packages/client/src/ui/deck/widgets-column.vue b/packages/client/src/ui/deck/widgets-column.vue deleted file mode 100644 index fc61d18ff6..0000000000 --- a/packages/client/src/ui/deck/widgets-column.vue +++ /dev/null @@ -1,69 +0,0 @@ -<template> -<XColumn :menu="menu" :naked="true" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> - <template #header><i class="ti ti-apps" style="margin-right: 8px;"></i>{{ column.name }}</template> - - <div class="wtdtxvec"> - <div v-if="!(column.widgets && column.widgets.length > 0) && !edit" class="intro">{{ i18n.ts._deck.widgetsIntroduction }}</div> - <XWidgets :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> - </div> -</XColumn> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import XColumn from './column.vue'; -import { addColumnWidget, Column, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store'; -import XWidgets from '@/components/MkWidgets.vue'; -import { i18n } from '@/i18n'; - -const props = defineProps<{ - column: Column; - isStacked: boolean; -}>(); - -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - -let edit = $ref(false); - -function addWidget(widget) { - addColumnWidget(props.column.id, widget); -} - -function removeWidget(widget) { - removeColumnWidget(props.column.id, widget); -} - -function updateWidget({ id, data }) { - updateColumnWidget(props.column.id, id, data); -} - -function updateWidgets(widgets) { - setColumnWidgets(props.column.id, widgets); -} - -function func() { - edit = !edit; -} - -const menu = [{ - icon: 'ti ti-pencil', - text: i18n.ts.editWidgets, - action: func, -}]; -</script> - -<style lang="scss" scoped> -.wtdtxvec { - --margin: 8px; - --panelBorder: none; - - padding: 0 var(--margin); - - > .intro { - padding: 16px; - text-align: center; - } -} -</style> |