diff options
Diffstat (limited to 'packages/frontend/src/ui/deck')
| -rw-r--r-- | packages/frontend/src/ui/deck/antenna-column.vue | 38 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/channel-column.vue | 3 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/column.vue | 4 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/deck-store.ts | 24 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/direct-column.vue | 2 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/list-column.vue | 41 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/mentions-column.vue | 2 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/notifications-column.vue | 7 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/role-timeline-column.vue | 4 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/tl-column.vue | 38 |
10 files changed, 108 insertions, 55 deletions
diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue index c3dc1e4fce..987bd4db55 100644 --- a/packages/frontend/src/ui/deck/antenna-column.vue +++ b/packages/frontend/src/ui/deck/antenna-column.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="() => timeline.reloadTimeline()"> +<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }"> <template #header> <i class="ti ti-antenna"></i><span style="margin-left: 8px;">{{ column.name }}</span> </template> @@ -14,7 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { onMounted, ref, shallowRef, watch } from 'vue'; +import { onMounted, ref, shallowRef, watch, defineAsyncComponent } from 'vue'; +import type { entities as MisskeyEntities } from 'misskey-js'; import XColumn from './column.vue'; import { updateColumn, Column } from './deck-store.js'; import MkTimeline from '@/components/MkTimeline.vue'; @@ -22,6 +23,7 @@ import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { MenuItem } from '@/types/menu.js'; +import { antennasCache } from '@/cache.js'; import { SoundStore } from '@/store.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; import * as sound from '@/scripts/sound.js'; @@ -46,14 +48,36 @@ watch(soundSetting, v => { async function setAntenna() { const antennas = await misskeyApi('antennas/list'); - const { canceled, result: antenna } = await os.select({ + const { canceled, result: antenna } = await os.select<MisskeyEntities.Antenna | '_CREATE_'>({ title: i18n.ts.selectAntenna, - items: antennas.map(x => ({ - value: x, text: x.name, - })), + items: [ + { value: '_CREATE_', text: i18n.ts.createNew }, + (antennas.length > 0 ? { + sectionTitle: i18n.ts.createdAntennas, + items: antennas.map(x => ({ + value: x, text: x.name, + })), + } : undefined), + ], default: props.column.antennaId, }); - if (canceled) return; + if (canceled || antenna == null) return; + + if (antenna === '_CREATE_') { + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkAntennaEditorDialog.vue')), {}, { + created: (newAntenna: MisskeyEntities.Antenna) => { + antennasCache.delete(); + updateColumn(props.column.id, { + antennaId: newAntenna.id, + }); + }, + closed: () => { + dispose(); + }, + }); + return; + } + updateColumn(props.column.id, { antennaId: antenna.id, }); diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue index 2d2c1b4332..518df7a6fc 100644 --- a/packages/frontend/src/ui/deck/channel-column.vue +++ b/packages/frontend/src/ui/deck/channel-column.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="() => timeline.reloadTimeline()"> +<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }"> <template #header> <i class="ti ti-device-tv"></i><span style="margin-left: 8px;">{{ column.name }}</span> </template> @@ -83,6 +83,7 @@ async function setChannel() { } async function post() { + if (props.column.channelId == null) return; if (!channel.value || channel.value.id !== props.column.channelId) { channel.value = await misskeyApi('channels/show', { channelId: props.column.channelId, diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue index 6f5d4dc9b5..5fed70fc90 100644 --- a/packages/frontend/src/ui/deck/column.vue +++ b/packages/frontend/src/ui/deck/column.vue @@ -271,7 +271,7 @@ function onDrop(ev) { border-radius: var(--radius); &.draghover { - &:after { + &::after { content: ""; display: block; position: absolute; @@ -285,7 +285,7 @@ function onDrop(ev) { } &.dragging { - &:after { + &::after { content: ""; display: block; position: absolute; diff --git a/packages/frontend/src/ui/deck/deck-store.ts b/packages/frontend/src/ui/deck/deck-store.ts index 1a4f7c5e17..eb587554b9 100644 --- a/packages/frontend/src/ui/deck/deck-store.ts +++ b/packages/frontend/src/ui/deck/deck-store.ts @@ -6,6 +6,7 @@ import { throttle } from 'throttle-debounce'; import { markRaw } from 'vue'; import { notificationTypes } from 'misskey-js'; +import type { BasicTimelineType } from '@/timelines.js'; import { Storage } from '@/pizzax.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { deepClone } from '@/scripts/clone.js'; @@ -17,9 +18,24 @@ type ColumnWidget = { 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: 'main' | 'widgets' | 'notifications' | 'tl' | 'antenna' | 'channel' | 'list' | 'mentions' | 'direct'; + type: ColumnType; name: string | null; width: number; widgets?: ColumnWidget[]; @@ -30,7 +46,7 @@ export type Column = { channelId?: string; roleId?: string; excludeTypes?: typeof notificationTypes[number][]; - tl?: 'home' | 'local' | 'social' | 'global' | 'bubble'; + tl?: BasicTimelineType; withRenotes?: boolean; withReplies?: boolean; onlyFiles?: boolean; @@ -265,7 +281,7 @@ 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; + if (column == null || column.widgets == null) return; column.widgets = column.widgets.filter(w => w.id !== widget.id); columns[columnIndex] = column; deckStore.set('columns', columns); @@ -287,7 +303,7 @@ export function updateColumnWidget(id: Column['id'], widgetId: string, widgetDat 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 == null || column.widgets == null) return; column.widgets = column.widgets.map(w => w.id === widgetId ? { ...w, data: widgetData, diff --git a/packages/frontend/src/ui/deck/direct-column.vue b/packages/frontend/src/ui/deck/direct-column.vue index e011de0e3b..d12a18f760 100644 --- a/packages/frontend/src/ui/deck/direct-column.vue +++ b/packages/frontend/src/ui/deck/direct-column.vue @@ -34,7 +34,7 @@ const tlComponent = ref<InstanceType<typeof MkNotes>>(); function reloadTimeline() { return new Promise<void>((res) => { - tlComponent.value.pagingComponent?.reload().then(() => { + tlComponent.value?.pagingComponent?.reload().then(() => { res(); }); }); diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue index eaa5997b35..a0e318f7eb 100644 --- a/packages/frontend/src/ui/deck/list-column.vue +++ b/packages/frontend/src/ui/deck/list-column.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="() => timeline.reloadTimeline()"> +<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }"> <template #header> <i class="ti ti-list"></i><span style="margin-left: 8px;">{{ column.name }}</span> </template> @@ -15,6 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { watch, shallowRef, ref } from 'vue'; +import type { entities as MisskeyEntities } from 'misskey-js'; import XColumn from './column.vue'; import { updateColumn, Column } from './deck-store.js'; import MkTimeline from '@/components/MkTimeline.vue'; @@ -23,6 +24,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { MenuItem } from '@/types/menu.js'; import { SoundStore } from '@/store.js'; +import { userListsCache } from '@/cache.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; import * as sound from '@/scripts/sound.js'; @@ -58,17 +60,38 @@ watch(soundSetting, v => { async function setList() { const lists = await misskeyApi('users/lists/list'); - const { canceled, result: list } = await os.select({ + const { canceled, result: list } = await os.select<MisskeyEntities.UserList | '_CREATE_'>({ title: i18n.ts.selectList, - items: lists.map(x => ({ - value: x, text: x.name, - })), + items: [ + { value: '_CREATE_', text: i18n.ts.createNew }, + (lists.length > 0 ? { + sectionTitle: i18n.ts.createdLists, + items: lists.map(x => ({ + value: x, text: x.name, + })), + } : undefined), + ], default: props.column.listId, }); - if (canceled) return; - updateColumn(props.column.id, { - listId: list.id, - }); + if (canceled || list == null) return; + + if (list === '_CREATE_') { + const { canceled, result: name } = await os.inputText({ + title: i18n.ts.enterListName, + }); + if (canceled || name == null || name === '') return; + + const res = await os.apiWithDialog('users/lists/create', { name: name }); + userListsCache.delete(); + + updateColumn(props.column.id, { + listId: res.id, + }); + } else { + updateColumn(props.column.id, { + listId: list.id, + }); + } } function editList() { diff --git a/packages/frontend/src/ui/deck/mentions-column.vue b/packages/frontend/src/ui/deck/mentions-column.vue index 81926dd7cb..7b25a55ec3 100644 --- a/packages/frontend/src/ui/deck/mentions-column.vue +++ b/packages/frontend/src/ui/deck/mentions-column.vue @@ -26,7 +26,7 @@ const tlComponent = ref<InstanceType<typeof MkNotes>>(); function reloadTimeline() { return new Promise<void>((res) => { - tlComponent.value.pagingComponent?.reload().then(() => { + tlComponent.value?.pagingComponent?.reload().then(() => { res(); }); }); diff --git a/packages/frontend/src/ui/deck/notifications-column.vue b/packages/frontend/src/ui/deck/notifications-column.vue index 451cc58791..19ccfc1f7c 100644 --- a/packages/frontend/src/ui/deck/notifications-column.vue +++ b/packages/frontend/src/ui/deck/notifications-column.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<XColumn :column="column" :isStacked="isStacked" :menu="menu" :refresher="() => notificationsComponent.reload()"> +<XColumn :column="column" :isStacked="isStacked" :menu="menu" :refresher="async () => { await notificationsComponent?.reload() }"> <template #header><i class="ti ti-bell" style="margin-right: 8px;"></i>{{ column.name }}</template> <XNotifications ref="notificationsComponent" :excludeTypes="props.column.excludeTypes"/> @@ -27,7 +27,7 @@ const props = defineProps<{ const notificationsComponent = shallowRef<InstanceType<typeof XNotifications>>(); function func() { - os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSelectWindow.vue')), { + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSelectWindow.vue')), { excludeTypes: props.column.excludeTypes, }, { done: async (res) => { @@ -36,7 +36,8 @@ function func() { excludeTypes: excludeTypes, }); }, - }, 'closed'); + closed: () => dispose(), + }); } const menu = [{ diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue index 32ab7527b4..a375e9c574 100644 --- a/packages/frontend/src/ui/deck/role-timeline-column.vue +++ b/packages/frontend/src/ui/deck/role-timeline-column.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="() => timeline.reloadTimeline()"> +<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }"> <template #header> <i class="ti ti-badge"></i><span style="margin-left: 8px;">{{ column.name }}</span> </template> @@ -53,7 +53,7 @@ async function setRole() { })), default: props.column.roleId, }); - if (canceled) return; + if (canceled || role == null) return; updateColumn(props.column.id, { roleId: role.id, }); diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue index 18eaaadf9f..17afa12551 100644 --- a/packages/frontend/src/ui/deck/tl-column.vue +++ b/packages/frontend/src/ui/deck/tl-column.vue @@ -4,17 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="() => timeline.reloadTimeline()"> +<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }"> <template #header> - <i v-if="column.tl === 'home'" class="ti ti-home"></i> - <i v-else-if="column.tl === 'local'" class="ti ti-planet"></i> - <i v-else-if="column.tl === 'social'" class="ti ti-universe"></i> - <i v-else-if="column.tl === 'bubble'" class="ph-thumb-up ph-bold ph-lg"></i> - <i v-else-if="column.tl === 'global'" class="ti ti-whirl"></i> + <i v-if="column.tl != null" :class="basicTimelineIconClass(column.tl)"/> <span style="margin-left: 8px;">{{ column.name }}</span> </template> - <div v-if="(((column.tl === 'local' || column.tl === 'social') && !isLocalTimelineAvailable) || (column.tl === 'bubble' && !isBubbleTimelineAvailable) || (column.tl === 'global' && !isGlobalTimelineAvailable))" :class="$style.disabled"> + <div v-if="!isAvailableBasicTimeline(column.tl)" :class="$style.disabled"> <p :class="$style.disabledTitle"> <i class="ti ti-circle-minus"></i> {{ i18n.ts._disabledTimeline.title }} @@ -35,15 +31,15 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { onMounted, watch, ref, shallowRef } from 'vue'; +import { onMounted, watch, ref, shallowRef, computed } from 'vue'; import XColumn from './column.vue'; import { removeColumn, updateColumn, Column } from './deck-store.js'; +import type { MenuItem } from '@/types/menu.js'; import MkTimeline from '@/components/MkTimeline.vue'; import * as os from '@/os.js'; -import { $i } from '@/account.js'; import { i18n } from '@/i18n.js'; +import { hasWithReplies, isAvailableBasicTimeline, basicTimelineIconClass } from '@/timelines.js'; import { instance } from '@/instance.js'; -import { MenuItem } from '@/types/menu.js'; import { SoundStore } from '@/store.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; import * as sound from '@/scripts/sound.js'; @@ -53,12 +49,8 @@ const props = defineProps<{ isStacked: boolean; }>(); -const disabled = ref(false); const timeline = shallowRef<InstanceType<typeof MkTimeline>>(); -const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable)); -const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable)); -const isBubbleTimelineAvailable = ($i == null && instance.policies.btlAvailable) || ($i != null && $i.policies.btlAvailable); const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 }); const withRenotes = ref(props.column.withRenotes ?? true); const withReplies = ref(props.column.withReplies ?? false); @@ -89,11 +81,6 @@ watch(soundSetting, v => { onMounted(() => { if (props.column.tl == null) { setType(); - } else if ($i) { - disabled.value = ( - (!((instance.policies.ltlAvailable) || ($i.policies.ltlAvailable)) && ['local', 'social'].includes(props.column.tl)) || - (!((instance.policies.gtlAvailable) || ($i.policies.gtlAvailable)) && ['global'].includes(props.column.tl)) || - (!((instance.policies.btlAvailable) || ($i.policies.btlAvailable)) && ['bubble'].includes(props.column.tl))); } }); @@ -107,7 +94,7 @@ async function setType() { }, { value: 'social' as const, text: i18n.ts._timelines.social, }, { - value: 'bubble' as const, text: 'Bubble', + value: 'bubble' as const, text: i18n.ts._timelines.bubble, }, { value: 'global' as const, text: i18n.ts._timelines.global, }], @@ -118,8 +105,9 @@ async function setType() { } return; } + if (src == null) return; updateColumn(props.column.id, { - tl: src, + tl: src ?? undefined, }); } @@ -127,7 +115,7 @@ function onNote() { sound.playMisskeySfxFile(soundSetting.value); } -const menu: MenuItem[] = [{ +const menu = computed<MenuItem[]>(() => [{ icon: 'ti ti-pencil', text: i18n.ts.timeline, action: setType, @@ -139,7 +127,7 @@ const menu: MenuItem[] = [{ type: 'switch', text: i18n.ts.showRenotes, ref: withRenotes, -}, props.column.tl === 'local' || props.column.tl === 'social' ? { +}, hasWithReplies(props.column.tl) ? { type: 'switch', text: i18n.ts.showRepliesToOthersInTimeline, ref: withReplies, @@ -148,8 +136,8 @@ const menu: MenuItem[] = [{ type: 'switch', text: i18n.ts.fileAttachedOnly, ref: onlyFiles, - disabled: props.column.tl === 'local' || props.column.tl === 'social' ? withReplies : false, -}]; + disabled: hasWithReplies(props.column.tl) ? withReplies : false, +}]); </script> <style lang="scss" module> |