From 78736c70f7e9b2564b74137e126595a683ae33a9 Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Mon, 21 Mar 2022 03:11:14 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=87=E3=83=83=E3=82=AD=E3=81=BE=E3=82=8F?= =?UTF-8?q?=E3=82=8A=E3=82=92Compositon=20API=20/=20Setup=20Sugar=E3=81=AB?= =?UTF-8?q?=20(#8410)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * universal.widgets.vue * column.vue, antenna-column.vue * direct-column.vue, list-column.vue * main-column.vue * wip * :v: * fix * :v: * :v: --- .../client/src/components/notifications.vue | 4 +- packages/client/src/os.ts | 16 +- packages/client/src/ui/deck.vue | 181 ++++---- .../client/src/ui/deck/antenna-column.vue | 97 ++-- packages/client/src/ui/deck/column-core.vue | 54 +-- packages/client/src/ui/deck/column.vue | 427 ++++++++---------- packages/client/src/ui/deck/deck-store.ts | 10 +- packages/client/src/ui/deck/direct-column.vue | 22 +- packages/client/src/ui/deck/list-column.vue | 96 ++-- packages/client/src/ui/deck/main-column.vue | 109 ++--- .../client/src/ui/deck/mentions-column.vue | 12 +- .../src/ui/deck/notifications-column.vue | 61 +-- packages/client/src/ui/deck/tl-column.vue | 155 +++---- .../client/src/ui/deck/widgets-column.vue | 71 ++- packages/client/src/ui/universal.widgets.vue | 80 ++-- 15 files changed, 639 insertions(+), 756 deletions(-) diff --git a/packages/client/src/components/notifications.vue b/packages/client/src/components/notifications.vue index c3753076ac..d522503a14 100644 --- a/packages/client/src/components/notifications.vue +++ b/packages/client/src/components/notifications.vue @@ -17,7 +17,7 @@ </template> <script lang="ts" setup> -import { defineComponent, PropType, markRaw, onUnmounted, onMounted, computed, ref } from 'vue'; +import { defineComponent, markRaw, onUnmounted, onMounted, computed, ref } from 'vue'; import { notificationTypes } from 'misskey-js'; import MkPagination from '@/components/ui/pagination.vue'; import { Paging } from '@/components/ui/pagination.vue'; @@ -29,7 +29,7 @@ import { stream } from '@/stream'; import { $i } from '@/account'; const props = defineProps<{ - includeTypes?: PropType<typeof notificationTypes[number][]>; + includeTypes?: typeof notificationTypes[number][]; unreadOnly?: boolean; }>(); diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index 5f1fb1ef96..43c110555f 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -293,23 +293,25 @@ export function inputDate(props: { }); } -export function select(props: { +export function select<C extends any = any>(props: { title?: string | null; text?: string | null; default?: string | null; - items?: { - value: string; +} & ({ + items: { + value: C; text: string; }[]; - groupedItems?: { +} | { + groupedItems: { label: string; items: { - value: string; + value: C; text: string; }[]; }[]; -}): Promise<{ canceled: true; result: undefined; } | { - canceled: false; result: string; +})): Promise<{ canceled: true; result: undefined; } | { + canceled: false; result: C; }> { return new Promise((resolve, reject) => { popup(import('@/components/dialog.vue'), { diff --git a/packages/client/src/ui/deck.vue b/packages/client/src/ui/deck.vue index e4571d4091..7e845feef4 100644 --- a/packages/client/src/ui/deck.vue +++ b/packages/client/src/ui/deck.vue @@ -17,7 +17,8 @@ :key="ids[0]" class="column" :column="columns.find(c => c.id === ids[0])" - :style="columns.find(c => c.id === ids[0]).flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0]).width + 'px' }" + :is-stacked="false" + :style="columns.find(c => c.id === ids[0])!.flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0])!.width + 'px' }" @parent-focus="moveFocus(ids[0], $event)" /> </template> @@ -25,8 +26,8 @@ <div v-if="isMobile" class="buttons"> <button class="button nav _button" @click="drawerMenuShowing = true"><i class="fas fa-bars"></i><span v-if="menuIndicated" class="indicator"><i class="fas fa-circle"></i></span></button> <button class="button home _button" @click="$router.push('/')"><i class="fas fa-home"></i></button> - <button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button> - <button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button> + <button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i?.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button> + <button class="button post _button" @click="os.post()"><i class="fas fa-pencil-alt"></i></button> </div> <transition :name="$store.state.animation ? 'menu-back' : ''"> @@ -45,8 +46,8 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent, provide, ref, watch } from 'vue'; +<script lang="ts" setup> +import { computed, provide, ref, watch } from 'vue'; import { v4 as uuid } from 'uuid'; import DeckColumnCore from '@/ui/deck/column-core.vue'; import XSidebar from '@/ui/_common_/sidebar.vue'; @@ -60,102 +61,82 @@ import { useRoute } from 'vue-router'; import { $i } from '@/account'; import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XCommon, - XSidebar, - XDrawerMenu, - DeckColumnCore, - }, - - setup() { - const isMobile = ref(window.innerWidth <= 500); - window.addEventListener('resize', () => { - isMobile.value = window.innerWidth <= 500; - }); - - const drawerMenuShowing = ref(false); - - const route = useRoute(); - watch(route, () => { - drawerMenuShowing.value = false; - }); - - const columns = deckStore.reactiveState.columns; - const layout = deckStore.reactiveState.layout; - const menuIndicated = computed(() => { - if ($i == null) return false; - for (const def in menuDef) { - if (menuDef[def].indicated) return true; - } - return false; - }); - - const addColumn = async (ev) => { - const columns = [ - 'main', - 'widgets', - 'notifications', - 'tl', - 'antenna', - 'list', - 'mentions', - 'direct', - ]; - - const { canceled, result: column } = await os.select({ - title: i18n.ts._deck.addColumn, - items: columns.map(column => ({ - value: column, text: i18n.t('_deck._columns.' + column) - })) - }); - if (canceled) return; - - addColumnToStore({ - type: column, - id: uuid(), - name: i18n.t('_deck._columns.' + column), - width: 330, - }); - }; - - const onContextmenu = (ev) => { - os.contextMenu([{ - text: i18n.ts._deck.addColumn, - icon: null, - action: addColumn - }], ev); - }; - - provide('shouldSpacerMin', true); - if (deckStore.state.navWindow) { - provide('navHook', (url) => { - os.pageWindow(url); - }); - } - - document.documentElement.style.overflowY = 'hidden'; - document.documentElement.style.scrollBehavior = 'auto'; - window.addEventListener('wheel', (ev) => { - if (getScrollContainer(ev.target) == null) { - document.documentElement.scrollLeft += ev.deltaY > 0 ? 96 : -96; - } - }); - loadDeck(); - - return { - isMobile, - deckStore, - drawerMenuShowing, - columns, - layout, - menuIndicated, - onContextmenu, - wallpaper: localStorage.getItem('wallpaper') != null, - post: os.post, - }; - }, +const isMobile = ref(window.innerWidth <= 500); +window.addEventListener('resize', () => { + isMobile.value = window.innerWidth <= 500; }); + +const drawerMenuShowing = ref(false); + +const route = useRoute(); +watch(route, () => { + drawerMenuShowing.value = false; +}); + +const columns = deckStore.reactiveState.columns; +const layout = deckStore.reactiveState.layout; +const menuIndicated = computed(() => { + if ($i == null) return false; + for (const def in menuDef) { + if (menuDef[def].indicated) return true; + } + return false; +}); + +const addColumn = async (ev) => { + const columns = [ + 'main', + 'widgets', + 'notifications', + 'tl', + 'antenna', + 'list', + 'mentions', + 'direct', + ]; + + const { canceled, result: column } = await os.select({ + title: i18n.ts._deck.addColumn, + items: columns.map(column => ({ + value: column, text: i18n.t('_deck._columns.' + column) + })) + }); + if (canceled) return; + + addColumnToStore({ + type: column, + id: uuid(), + name: i18n.t('_deck._columns.' + column), + width: 330, + }); +}; + +const onContextmenu = (ev) => { + os.contextMenu([{ + text: i18n.ts._deck.addColumn, + action: addColumn, + }], ev); +}; + +provide('shouldSpacerMin', true); +if (deckStore.state.navWindow) { + provide('navHook', (url) => { + os.pageWindow(url); + }); +} + +document.documentElement.style.overflowY = 'hidden'; +document.documentElement.style.scrollBehavior = 'auto'; +window.addEventListener('wheel', (ev) => { + if (getScrollContainer(ev.target as HTMLElement) == null) { + document.documentElement.scrollLeft += ev.deltaY > 0 ? 96 : -96; + } +}); +loadDeck(); + +function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') { + // TODO?? +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/ui/deck/antenna-column.vue b/packages/client/src/ui/deck/antenna-column.vue index 198ebbbefa..e0f56c2800 100644 --- a/packages/client/src/ui/deck/antenna-column.vue +++ b/packages/client/src/ui/deck/antenna-column.vue @@ -1,75 +1,62 @@ <template> -<XColumn :func="{ handler: setAntenna, title: $ts.selectAntenna }" :column="column" :is-stacked="isStacked"> +<XColumn :func="{ handler: setAntenna, title: $ts.selectAntenna }" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header> <i class="fas fa-satellite"></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')"/> + <XTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @after="() => emit('loaded')"/> </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; import XColumn from './column.vue'; import XTimeline from '@/components/timeline.vue'; import * as os from '@/os'; -import { updateColumn } from './deck-store'; +import { updateColumn, Column } from './deck-store'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XColumn, - XTimeline, - }, +const props = defineProps<{ + column: Column; + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, +const emit = defineEmits<{ + (e: 'loaded'): void; + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); - data() { - return { - }; - }, +let timeline = $ref<InstanceType<typeof XTimeline>>(); - watch: { - mediaOnly() { - (this.$refs.timeline as any).reload(); - } - }, - - mounted() { - if (this.column.antennaId == null) { - this.setAntenna(); - } - }, - - methods: { - async setAntenna() { - const antennas = await os.api('antennas/list'); - const { canceled, result: antenna } = await os.select({ - title: this.$ts.selectAntenna, - items: antennas.map(x => ({ - value: x, text: x.name - })), - default: this.column.antennaId - }); - if (canceled) return; - updateColumn(this.column.id, { - antennaId: antenna.id - }); - }, - - focus() { - (this.$refs.timeline as any).focus(); - } +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 + }); +} +/* +function focus() { + timeline.focus(); +} + +defineExpose({ + focus, +}); +*/ </script> <style lang="scss" scoped> diff --git a/packages/client/src/ui/deck/column-core.vue b/packages/client/src/ui/deck/column-core.vue index 5393bac736..485e89a062 100644 --- a/packages/client/src/ui/deck/column-core.vue +++ b/packages/client/src/ui/deck/column-core.vue @@ -1,17 +1,18 @@ <template> <!-- TODO: リファクタの余地がありそう --> -<XMainColumn v-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)"/> +<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"> -import { defineComponent } from 'vue'; +<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'; @@ -20,33 +21,24 @@ 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<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); + +/* export default defineComponent({ - components: { - XMainColumn, - XTlColumn, - XAntennaColumn, - XListColumn, - XNotificationsColumn, - XWidgetsColumn, - XMentionsColumn, - XDirectColumn - }, - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: false, - default: false - } - }, methods: { focus() { this.$children[0].focus(); } } }); +*/ </script> diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue index f1ce3ca838..4f427b7624 100644 --- a/packages/client/src/ui/deck/column.vue +++ b/packages/client/src/ui/deck/column.vue @@ -31,238 +31,211 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +export type DeckFunc = { + title: string; + handler: (payload: MouseEvent) => void; + icon?: string; +}; +</script> +<script lang="ts" setup> +import { onBeforeUnmount, onMounted, provide, watch } from 'vue'; import * as os from '@/os'; -import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn } from './deck-store'; +import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column } from './deck-store'; import { deckStore } from './deck-store'; +import { i18n } from '@/i18n'; -export default defineComponent({ - provide: { - shouldHeaderThin: true, - shouldOmitHeaderTitle: true, - }, +provide('shouldHeaderThin', true); +provide('shouldOmitHeaderTitle', true); - props: { - column: { - type: Object, - required: false, - default: null - }, - isStacked: { - type: Boolean, - required: false, - default: false - }, - func: { - type: Object, - required: false, - default: null - }, - naked: { - type: Boolean, - required: false, - default: false - }, - indicated: { - type: Boolean, - required: false, - default: false - }, - }, - - data() { - return { - deckStore, - dragging: false, - draghover: false, - dropready: false, - }; - }, - - computed: { - isMainColumn(): boolean { - return this.column.type === 'main'; - }, - - active(): boolean { - return this.column.active !== false; - }, - - keymap(): any { - return { - 'shift+up': () => this.$parent.$emit('parent-focus', 'up'), - 'shift+down': () => this.$parent.$emit('parent-focus', 'down'), - 'shift+left': () => this.$parent.$emit('parent-focus', 'left'), - 'shift+right': () => this.$parent.$emit('parent-focus', 'right'), - }; - } - }, - - watch: { - active(v) { - this.$emit('change-active-state', v); - }, - - dragging(v) { - os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd'); - } - }, - - mounted() { - os.deckGlobalEvents.on('column.dragStart', this.onOtherDragStart); - os.deckGlobalEvents.on('column.dragEnd', this.onOtherDragEnd); - }, - - beforeUnmount() { - os.deckGlobalEvents.off('column.dragStart', this.onOtherDragStart); - os.deckGlobalEvents.off('column.dragEnd', this.onOtherDragEnd); - }, - - methods: { - onOtherDragStart() { - this.dropready = true; - }, - - onOtherDragEnd() { - this.dropready = false; - }, - - toggleActive() { - if (!this.isStacked) return; - updateColumn(this.column.id, { - active: !this.column.active - }); - }, - - getMenu() { - const items = [{ - icon: 'fas fa-pencil-alt', - text: this.$ts.edit, - action: async () => { - const { canceled, result } = await os.form(this.column.name, { - name: { - type: 'string', - label: this.$ts.name, - default: this.column.name - }, - width: { - type: 'number', - label: this.$ts.width, - default: this.column.width - }, - flexible: { - type: 'boolean', - label: this.$ts.flexible, - default: this.column.flexible - } - }); - if (canceled) return; - updateColumn(this.column.id, result); - } - }, null, { - icon: 'fas fa-arrow-left', - text: this.$ts._deck.swapLeft, - action: () => { - swapLeftColumn(this.column.id); - } - }, { - icon: 'fas fa-arrow-right', - text: this.$ts._deck.swapRight, - action: () => { - swapRightColumn(this.column.id); - } - }, this.isStacked ? { - icon: 'fas fa-arrow-up', - text: this.$ts._deck.swapUp, - action: () => { - swapUpColumn(this.column.id); - } - } : undefined, this.isStacked ? { - icon: 'fas fa-arrow-down', - text: this.$ts._deck.swapDown, - action: () => { - swapDownColumn(this.column.id); - } - } : undefined, null, { - icon: 'fas fa-window-restore', - text: this.$ts._deck.stackLeft, - action: () => { - stackLeftColumn(this.column.id); - } - }, this.isStacked ? { - icon: 'fas fa-window-maximize', - text: this.$ts._deck.popRight, - action: () => { - popRightColumn(this.column.id); - } - } : undefined, null, { - icon: 'fas fa-trash-alt', - text: this.$ts.remove, - danger: true, - action: () => { - removeColumn(this.column.id); - } - }]; - - return items; - }, - - onContextmenu(ev: MouseEvent) { - os.contextMenu(this.getMenu(), ev); - }, - - goTop() { - this.$refs.body.scrollTo({ - top: 0, - behavior: 'smooth' - }); - }, - - onDragstart(e) { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, this.column.id); - - // Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう - // SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately - window.setTimeout(() => { - this.dragging = true; - }, 10); - }, - - onDragend(e) { - this.dragging = false; - }, - - onDragover(e) { - // 自分自身がドラッグされている場合 - if (this.dragging) { - // 自分自身にはドロップさせない - e.dataTransfer.dropEffect = 'none'; - return; - } - - const isDeckColumn = e.dataTransfer.types[0] == _DATA_TRANSFER_DECK_COLUMN_; - - e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; - - if (!this.dragging && isDeckColumn) this.draghover = true; - }, - - onDragleave() { - this.draghover = false; - }, - - onDrop(e) { - this.draghover = false; - os.deckGlobalEvents.emit('column.dragEnd'); - - const id = e.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_); - if (id != null && id != '') { - swapColumn(this.column.id, id); - } - } - } +const props = withDefaults(defineProps<{ + column: Column; + isStacked?: boolean; + func?: DeckFunc | null; + naked?: boolean; + indicated?: boolean; +}>(), { + isStacked: false, + func: null, + naked: false, + indicated: false, }); + +const emit = defineEmits<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (e: '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() { + const items = [{ + icon: 'fas fa-pencil-alt', + text: i18n.ts.edit, + 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); + } + }, null, { + icon: 'fas fa-arrow-left', + text: i18n.ts._deck.swapLeft, + action: () => { + swapLeftColumn(props.column.id); + } + }, { + icon: 'fas fa-arrow-right', + text: i18n.ts._deck.swapRight, + action: () => { + swapRightColumn(props.column.id); + } + }, props.isStacked ? { + icon: 'fas fa-arrow-up', + text: i18n.ts._deck.swapUp, + action: () => { + swapUpColumn(props.column.id); + } + } : undefined, props.isStacked ? { + icon: 'fas fa-arrow-down', + text: i18n.ts._deck.swapDown, + action: () => { + swapDownColumn(props.column.id); + } + } : undefined, null, { + icon: 'fas fa-window-restore', + text: i18n.ts._deck.stackLeft, + action: () => { + stackLeftColumn(props.column.id); + } + }, props.isStacked ? { + icon: 'fas fa-window-maximize', + text: i18n.ts._deck.popRight, + action: () => { + popRightColumn(props.column.id); + } + } : undefined, null, { + icon: 'fas fa-trash-alt', + text: i18n.ts.remove, + danger: true, + action: () => { + removeColumn(props.column.id); + } + }]; + return items; +} + +function onContextmenu(ev: MouseEvent) { + os.contextMenu(getMenu(), ev); +} + +function goTop() { + body.scrollTo({ + top: 0, + behavior: 'smooth' + }); +} + +function onDragstart(e) { + e.dataTransfer.effectAllowed = 'move'; + e.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(e) { + dragging = false; +} + +function onDragover(e) { + // 自分自身がドラッグされている場合 + if (dragging) { + // 自分自身にはドロップさせない + e.dataTransfer.dropEffect = 'none'; + return; + } + + const isDeckColumn = e.dataTransfer.types[0] == _DATA_TRANSFER_DECK_COLUMN_; + + e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; + + if (!dragging && isDeckColumn) draghover = true; +} + +function onDragleave() { + draghover = false; +} + +function onDrop(e) { + draghover = false; + os.deckGlobalEvents.emit('column.dragEnd'); + + const id = e.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_); + if (id != null && id != '') { + swapColumn(props.column.id, id); + } +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/ui/deck/deck-store.ts b/packages/client/src/ui/deck/deck-store.ts index 66db5e83ed..f7c39ad8fd 100644 --- a/packages/client/src/ui/deck/deck-store.ts +++ b/packages/client/src/ui/deck/deck-store.ts @@ -1,8 +1,9 @@ import { throttle } from 'throttle-debounce'; import { i18n } from '@/i18n'; import { api } from '@/os'; -import { markRaw, watch } from 'vue'; +import { markRaw } from 'vue'; import { Storage } from '../../pizzax'; +import { notificationTypes } from 'misskey-js'; type ColumnWidget = { name: string; @@ -10,13 +11,18 @@ type ColumnWidget = { data: Record<string, any>; }; -type Column = { +export type Column = { id: string; type: string; name: string | null; width: number; widgets?: ColumnWidget[]; active?: boolean; + flexible?: boolean; + antennaId?: string; + listId?: string; + includingTypes?: typeof notificationTypes[number][]; + tl?: 'home' | 'local' | 'social' | 'global'; }; function copy<T>(x: T): T { diff --git a/packages/client/src/ui/deck/direct-column.vue b/packages/client/src/ui/deck/direct-column.vue index ca70f693c3..ebaba574f4 100644 --- a/packages/client/src/ui/deck/direct-column.vue +++ b/packages/client/src/ui/deck/direct-column.vue @@ -1,5 +1,5 @@ <template> -<XColumn :column="column" :is-stacked="isStacked"> +<XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header><i class="fas fa-envelope" style="margin-right: 8px;"></i>{{ column.name }}</template> <XNotes :pagination="pagination"/> @@ -7,21 +7,25 @@ </template> <script lang="ts" setup> -import { computed } from 'vue'; +import { } from 'vue'; import XColumn from './column.vue'; import XNotes from '@/components/notes.vue'; -import * as os from '@/os'; +import { Column } from './deck-store'; -const props = defineProps<{ - column: Record<string, unknown>; // TODO +defineProps<{ + column: Column; isStacked: boolean; }>(); +const emit = defineEmits<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); + const pagination = { - point: 'notes/mentions' as const, + endpoint: 'notes/mentions' as const, limit: 10, - params: computed(() => ({ - visibility: 'specified' as const, - })), + params: { + visibility: 'specified' + }, }; </script> diff --git a/packages/client/src/ui/deck/list-column.vue b/packages/client/src/ui/deck/list-column.vue index ab04aee4e7..b990516d05 100644 --- a/packages/client/src/ui/deck/list-column.vue +++ b/packages/client/src/ui/deck/list-column.vue @@ -1,75 +1,65 @@ <template> -<XColumn :func="{ handler: setList, title: $ts.selectList }" :column="column" :is-stacked="isStacked"> +<XColumn :func="{ handler: setList, title: $ts.selectList }" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header> <i class="fas fa-list-ul"></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')"/> + <XTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" @after="() => emit('loaded')"/> </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XColumn from './column.vue'; import XTimeline from '@/components/timeline.vue'; import * as os from '@/os'; -import { updateColumn } from './deck-store'; +import { updateColumn, Column } from './deck-store'; +import { i18n } from '@/i18n'; + +const props = defineProps<{ + column: Column; + isStacked: boolean; +}>(); + +const emit = defineEmits<{ + (e: 'loaded'): void; + (e: '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 + }); +} + +/* +function focus() { + timeline.focus(); +} export default defineComponent({ - components: { - XColumn, - XTimeline, - }, - - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, - - data() { - return { - }; - }, - watch: { mediaOnly() { (this.$refs.timeline as any).reload(); } - }, - - mounted() { - if (this.column.listId == null) { - this.setList(); - } - }, - - methods: { - async setList() { - const lists = await os.api('users/lists/list'); - const { canceled, result: list } = await os.select({ - title: this.$ts.selectList, - items: lists.map(x => ({ - value: x, text: x.name - })), - default: this.column.listId - }); - if (canceled) return; - updateColumn(this.column.id, { - listId: list.id - }); - }, - - focus() { - (this.$refs.timeline as any).focus(); - } } }); +*/ </script> <style lang="scss" scoped> diff --git a/packages/client/src/ui/deck/main-column.vue b/packages/client/src/ui/deck/main-column.vue index cb045e9a46..57caab44cb 100644 --- a/packages/client/src/ui/deck/main-column.vue +++ b/packages/client/src/ui/deck/main-column.vue @@ -1,5 +1,5 @@ <template> -<XColumn v-if="deckStore.state.alwaysShowMainColumn || $route.name !== 'index'" :column="column" :is-stacked="isStacked"> +<XColumn v-if="deckStore.state.alwaysShowMainColumn || $route.name !== 'index'" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header> <template v-if="pageInfo"> <i :class="pageInfo.icon"></i> @@ -20,72 +20,59 @@ </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XColumn from './column.vue'; -import XNotes from '@/components/notes.vue'; -import { deckStore } from '@/ui/deck/deck-store'; +import { deckStore, Column } from '@/ui/deck/deck-store'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { router } from '@/router'; -export default defineComponent({ - components: { - XColumn, - XNotes - }, +defineProps<{ + column: Column; + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, +const emit = defineEmits<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); - data() { - return { - deckStore, - pageInfo: null, - } - }, +let pageInfo = $ref<Record<string, any> | null>(null); - methods: { - changePage(page) { - if (page == null) return; - if (page[symbols.PAGE_INFO]) { - this.pageInfo = page[symbols.PAGE_INFO]; - } - }, - - back() { - history.back(); - }, - - onContextmenu(ev: MouseEvent) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(ev.target)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; - if (window.getSelection().toString() !== '') return; - const path = this.$route.path; - os.contextMenu([{ - type: 'label', - text: path, - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(path); - } - }], ev); - }, +function changePage(page) { + if (page == null) return; + if (page[symbols.PAGE_INFO]) { + pageInfo = page[symbols.PAGE_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 = router.currentRoute.value.path; + os.contextMenu([{ + type: 'label', + text: path, + }, { + icon: 'fas fa-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 index 6822e7ef06..a7a012a7fb 100644 --- a/packages/client/src/ui/deck/mentions-column.vue +++ b/packages/client/src/ui/deck/mentions-column.vue @@ -1,5 +1,5 @@ <template> -<XColumn :column="column" :is-stacked="isStacked"> +<XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header><i class="fas fa-at" style="margin-right: 8px;"></i>{{ column.name }}</template> <XNotes :pagination="pagination"/> @@ -10,13 +10,17 @@ import { } from 'vue'; import XColumn from './column.vue'; import XNotes from '@/components/notes.vue'; -import * as os from '@/os'; +import { Column } from './deck-store'; -const props = defineProps<{ - column: Record<string, unknown>; // TODO +defineProps<{ + column: Column; isStacked: boolean; }>(); +const emit = defineEmits<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); + const pagination = { endpoint: 'notes/mentions' as const, limit: 10, diff --git a/packages/client/src/ui/deck/notifications-column.vue b/packages/client/src/ui/deck/notifications-column.vue index f8f406cdd1..50ee12a275 100644 --- a/packages/client/src/ui/deck/notifications-column.vue +++ b/packages/client/src/ui/deck/notifications-column.vue @@ -1,53 +1,38 @@ <template> -<XColumn :column="column" :is-stacked="isStacked" :func="{ handler: func, title: $ts.notificationSetting }"> +<XColumn :column="column" :is-stacked="isStacked" :func="{ handler: func, title: $ts.notificationSetting }" @parent-focus="$event => emit('parent-focus', $event)"> <template #header><i class="fas fa-bell" style="margin-right: 8px;"></i>{{ column.name }}</template> <XNotifications :include-types="column.includingTypes"/> </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XColumn from './column.vue'; import XNotifications from '@/components/notifications.vue'; import * as os from '@/os'; import { updateColumn } from './deck-store'; +import { Column } from './deck-store'; -export default defineComponent({ - components: { - XColumn, - XNotifications - }, +const props = defineProps<{ + column: Column; + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true +const emit = defineEmits<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); + +function func() { + os.popup(import('@/components/notification-setting-window.vue'), { + includingTypes: props.column.includingTypes, + }, { + done: async (res) => { + const { includingTypes } = res; + updateColumn(props.column.id, { + includingTypes: includingTypes + }); }, - isStacked: { - type: Boolean, - required: true - } - }, - - data() { - return { - } - }, - - methods: { - func() { - os.popup(import('@/components/notification-setting-window.vue'), { - includingTypes: this.column.includingTypes, - }, { - done: async (res) => { - const { includingTypes } = res; - updateColumn(this.column.id, { - includingTypes: includingTypes - }); - }, - }, 'closed'); - } - } -}); + }, 'closed'); +} </script> diff --git a/packages/client/src/ui/deck/tl-column.vue b/packages/client/src/ui/deck/tl-column.vue index 8b22d7efb9..02b9ef83a1 100644 --- a/packages/client/src/ui/deck/tl-column.vue +++ b/packages/client/src/ui/deck/tl-column.vue @@ -1,5 +1,5 @@ <template> -<XColumn :func="{ handler: setType, title: $ts.timeline }" :column="column" :is-stacked="isStacked" :indicated="indicated" @change-active-state="onChangeActiveState"> +<XColumn :func="{ handler: setType, title: $ts.timeline }" :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="fas fa-home"></i> <i v-else-if="column.tl === 'local'" class="fas fa-comments"></i> @@ -15,108 +15,103 @@ </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"/> + <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"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; import XColumn from './column.vue'; import XTimeline from '@/components/timeline.vue'; import * as os from '@/os'; -import { removeColumn, updateColumn } from './deck-store'; +import { removeColumn, updateColumn, Column } from './deck-store'; +import { $i } from '@/account'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XColumn, - XTimeline, - }, +const props = defineProps<{ + column: Column; + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true +const emit = defineEmits<{ + (e: 'loaded'): void; + (e: '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 + }); +} - data() { - return { - disabled: false, - indicated: false, - columnActive: true, - }; - }, +function queueUpdated(q) { + if (columnActive) { + indicated = q !== 0; + } +} +function onNote() { + if (!columnActive) { + indicated = true; + } +} + +function onChangeActiveState(state) { + columnActive = state; + + if (columnActive) { + indicated = false; + } +} + +/* +export default defineComponent({ watch: { mediaOnly() { (this.$refs.timeline as any).reload(); } }, - mounted() { - if (this.column.tl == null) { - this.setType(); - } else { - this.disabled = !this.$i.isModerator && !this.$i.isAdmin && ( - this.$instance.disableLocalTimeline && ['local', 'social'].includes(this.column.tl) || - this.$instance.disableGlobalTimeline && ['global'].includes(this.column.tl)); - } - }, - methods: { - async setType() { - const { canceled, result: src } = await os.select({ - title: this.$ts.timeline, - items: [{ - value: 'home', text: this.$ts._timelines.home - }, { - value: 'local', text: this.$ts._timelines.local - }, { - value: 'social', text: this.$ts._timelines.social - }, { - value: 'global', text: this.$ts._timelines.global - }] - }); - if (canceled) { - if (this.column.tl == null) { - removeColumn(this.column.id); - } - return; - } - updateColumn(this.column.id, { - tl: src - }); - }, - - queueUpdated(q) { - if (this.columnActive) { - this.indicated = q !== 0; - } - }, - - onNote() { - if (!this.columnActive) { - this.indicated = true; - } - }, - - onChangeActiveState(state) { - this.columnActive = state; - - if (this.columnActive) { - this.indicated = false; - } - }, - focus() { (this.$refs.timeline as any).focus(); } } }); +*/ </script> <style lang="scss" scoped> diff --git a/packages/client/src/ui/deck/widgets-column.vue b/packages/client/src/ui/deck/widgets-column.vue index 8c3a95ac2b..a2edc38357 100644 --- a/packages/client/src/ui/deck/widgets-column.vue +++ b/packages/client/src/ui/deck/widgets-column.vue @@ -1,64 +1,49 @@ <template> -<XColumn :func="{ handler: func, title: $ts.editWidgets }" :naked="true" :column="column" :is-stacked="isStacked"> +<XColumn :func="{ handler: func, title: $ts.editWidgets }" :naked="true" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header><i class="fas fa-window-maximize" style="margin-right: 8px;"></i>{{ column.name }}</template> <div class="wtdtxvec"> - <XWidgets :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> + <XWidgets v-if="column.widgets" :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"> -import { defineComponent, defineAsyncComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XWidgets from '@/components/widgets.vue'; import XColumn from './column.vue'; -import { addColumnWidget, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store'; +import { addColumnWidget, Column, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store'; -export default defineComponent({ - components: { - XColumn, - XWidgets, - }, +const props = defineProps<{ + column: Column; + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true, - }, - isStacked: { - type: Boolean, - required: true, - }, - }, +const emit = defineEmits<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); - data() { - return { - edit: false, - }; - }, +let edit = $ref(false); - methods: { - addWidget(widget) { - addColumnWidget(this.column.id, widget); - }, +function addWidget(widget) { + addColumnWidget(props.column.id, widget); +} - removeWidget(widget) { - removeColumnWidget(this.column.id, widget); - }, +function removeWidget(widget) { + removeColumnWidget(props.column.id, widget); +} - updateWidget({ id, data }) { - updateColumnWidget(this.column.id, id, data); - }, +function updateWidget({ id, data }) { + updateColumnWidget(props.column.id, id, data); +} - updateWidgets(widgets) { - setColumnWidgets(this.column.id, widgets); - }, +function updateWidgets(widgets) { + setColumnWidgets(props.column.id, widgets); +} - func() { - this.edit = !this.edit; - } - } -}); +function func() { + edit = !edit; +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/ui/universal.widgets.vue b/packages/client/src/ui/universal.widgets.vue index fbfafd10ee..2660e80368 100644 --- a/packages/client/src/ui/universal.widgets.vue +++ b/packages/client/src/ui/universal.widgets.vue @@ -1,58 +1,50 @@ <template> <div class="efzpzdvf"> - <XWidgets :edit="editMode" :widgets="$store.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> + <XWidgets :edit="editMode" :widgets="defaultStore.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> - <button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="fas fa-check"></i> {{ $ts.editWidgetsExit }}</button> - <button v-else class="_textButton" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ $ts.editWidgets }}</button> + <button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="fas fa-check"></i> {{ i18n.ts.editWidgetsExit }}</button> + <button v-else class="_textButton" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ i18n.ts.editWidgets }}</button> </div> </template> -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; import XWidgets from '@/components/widgets.vue'; -import * as os from '@/os'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - XWidgets - }, +const emit = defineEmits<{ + (e: 'mounted', el: Element): void; +}>(); - emits: ['mounted'], +let editMode = $ref(false); +let rootEl = $ref<HTMLDivElement>(); - data() { - return { - editMode: false, - }; - }, - - mounted() { - this.$emit('mounted', this.$el); - }, - - methods: { - addWidget(widget) { - this.$store.set('widgets', [{ - ...widget, - place: null, - }, ...this.$store.state.widgets]); - }, - - removeWidget(widget) { - this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id != widget.id)); - }, - - updateWidget({ id, data }) { - this.$store.set('widgets', this.$store.state.widgets.map(w => w.id === id ? { - ...w, - data: data - } : w)); - }, - - updateWidgets(widgets) { - this.$store.set('widgets', widgets); - } - } +onMounted(() => { + emit('mounted', rootEl); }); + +function addWidget(widget) { + defaultStore.set('widgets', [{ + ...widget, + place: null, + }, ...defaultStore.state.widgets]); +} + +function removeWidget(widget) { + defaultStore.set('widgets', defaultStore.state.widgets.filter(w => w.id != widget.id)); +} + +function updateWidget({ id, data }) { + defaultStore.set('widgets', defaultStore.state.widgets.map(w => w.id === id ? { + ...w, + data: data + } : w)); +} + +function updateWidgets(widgets) { + defaultStore.set('widgets', widgets); +} </script> <style lang="scss" scoped>