diff --git a/misskey-assets b/misskey-assets index cf3ce27b2eb8417233072e3d6d2fb7c5356c2364..0179793ec891856d6f37a3be16ba4c22f67a81b5 160000 --- a/misskey-assets +++ b/misskey-assets @@ -1 +1 @@ -Subproject commit cf3ce27b2eb8417233072e3d6d2fb7c5356c2364 +Subproject commit 0179793ec891856d6f37a3be16ba4c22f67a81b5 diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue index fff89117ce3a29b16b21ed904405dd40caea0f85..3502e090e75025b4ff48a203d03d500c1edee30e 100644 --- a/packages/frontend/src/components/MkWidgets.vue +++ b/packages/frontend/src/components/MkWidgets.vue @@ -1,7 +1,7 @@ <template> -<div class="vjoppmmu"> +<div :class="$style.root"> <template v-if="edit"> - <header> + <header :class="$style['edit-header']"> <MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" class="mk-widget-select"> <template #label>{{ i18n.ts.selectWidget }}</template> <option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.t(`_widgets.${widget}`) }}</option> @@ -14,23 +14,34 @@ item-key="id" handle=".handle" :animation="150" + :group="{ name: 'SortableMkWidgets' }" @update:model-value="v => emit('updateWidgets', v)" + :class="$style['edit-editing']" > <template #item="{element}"> - <div class="customize-container"> - <button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="ti ti-settings"></i></button> - <button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="ti ti-x"></i></button> + <div :class="[$style.widget, $style['customize-container']]"> + <button :class="$style['customize-container-config']" class="_button" @click.prevent.stop="configWidget(element.id)"><i class="ti ti-settings"></i></button> + <button :class="$style['customize-container-remove']" class="_button" @click.prevent.stop="removeWidget(element)"><i class="ti ti-x"></i></button> <div class="handle"> - <component :is="`mkw-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :widget="element" @update-props="updateWidget(element.id, $event)"/> + <component :is="`mkw-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :class="$style['customize-container-handle-widget']" :widget="element" @update-props="updateWidget(element.id, $event)"/> </div> </div> </template> </Sortable> </template> - <component :is="`mkw-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" class="widget" :widget="widget" @update-props="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/> + <component :is="`mkw-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @update-props="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/> </div> </template> - +<script lang="ts"> +export type Widget = { + name: string; + id: string; + data: Record<string, any>; +}; +export type DefaultStoredWidget = { + place: string | null; +} & Widget; +</script> <script lang="ts" setup> import { defineAsyncComponent, reactive, ref, computed } from 'vue'; import { v4 as uuid } from 'uuid'; @@ -43,12 +54,6 @@ import { deepClone } from '@/scripts/clone'; const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); -type Widget = { - name: string; - id: string; - data: Record<string, any>; -}; - const props = defineProps<{ widgets: Widget[]; edit: boolean; @@ -109,11 +114,22 @@ function onContextmenu(widget: Widget, ev: MouseEvent) { } </script> -<style lang="scss" scoped> -.vjoppmmu { +<style lang="scss" module> +.root { container-type: inline-size; +} + +.widget { + contain: content; + margin: var(--margin) 0; - > header { + &:first-of-type { + margin-top: 0; + } +} + +.edit { + &-header { margin: 16px 0; > * { @@ -122,44 +138,42 @@ function onContextmenu(widget: Widget, ev: MouseEvent) { } } - > .widget, .customize-container { - contain: content; - margin: var(--margin) 0; + &-editing { + min-height: 100px; + } +} - &:first-of-type { - margin-top: 0; - } +.customize-container { + position: relative; + cursor: move; + + &-config, + &-remove { + position: absolute; + z-index: 10000; + top: 8px; + width: 32px; + height: 32px; + color: #fff; + background: rgba(#000, 0.7); + border-radius: 4px; } - .customize-container { - position: relative; - cursor: move; - - > .config, - > .remove { - position: absolute; - z-index: 10000; - top: 8px; - width: 32px; - height: 32px; - color: #fff; - background: rgba(#000, 0.7); - border-radius: 4px; - } + &-config { + right: 8px + 8px + 32px; + } - > .config { - right: 8px + 8px + 32px; - } + &-remove { + right: 8px; + } - > .remove { - right: 8px; - } + &-handle { - > .handle { - > .widget { - pointer-events: none; - } - } + &-widget { + pointer-events: none; + } } + } + </style> diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 1bedab5fadccbc5044796b20cd44d680999e644c..8e3e6b36da6f15972202252cbf3cfdb846d131ad 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -122,7 +122,7 @@ export const defaultStore = markRaw(new Storage('base', { }[], }, widgets: { - where: 'deviceAccount', + where: 'account', default: [] as { name: string; id: string; diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue index 0e726c11ed96874bcea7e9cc2103e11317530619..44b017ea965b3e293e506e52ba01b572f1a4f01a 100644 --- a/packages/frontend/src/ui/classic.vue +++ b/packages/frontend/src/ui/classic.vue @@ -7,7 +7,7 @@ <XSidebar/> </div> <div v-else ref="widgetsLeft" class="widgets left"> - <XWidgets :place="'left'" @mounted="attachSticky(widgetsLeft)"/> + <XWidgets place="left" @mounted="attachSticky(widgetsLeft)"/> </div> <main class="main" :style="{ background: pageMetadata?.value?.bg }" @contextmenu.stop="onContextmenu"> @@ -17,7 +17,7 @@ </main> <div v-if="isDesktop" ref="widgetsRight" class="widgets right"> - <XWidgets :place="null" @mounted="attachSticky(widgetsRight)"/> + <XWidgets :place="showMenuOnTop ? 'right' : null" @mounted="attachSticky(widgetsRight)"/> </div> </div> @@ -52,7 +52,7 @@ import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/script import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; const XHeaderMenu = defineAsyncComponent(() => import('./classic.header.vue')); -const XWidgets = defineAsyncComponent(() => import('./classic.widgets.vue')); +const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue')); const DESKTOP_THRESHOLD = 1100; diff --git a/packages/frontend/src/ui/classic.widgets.vue b/packages/frontend/src/ui/classic.widgets.vue deleted file mode 100644 index 163ec982cec83bf8a336147d6b95628a6da1b034..0000000000000000000000000000000000000000 --- a/packages/frontend/src/ui/classic.widgets.vue +++ /dev/null @@ -1,84 +0,0 @@ -<template> -<div class="ddiqwdnk"> - <XWidgets class="widgets" :edit="editMode" :widgets="$store.reactiveState.widgets.value.filter(w => w.place === place)" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> - <MkAd class="a" :prefer="['square']"/> - - <button v-if="editMode" class="_textButton edit" style="font-size: 0.9em;" @click="editMode = false"><i class="ti ti-check"></i> {{ $ts.editWidgetsExit }}</button> - <button v-else class="_textButton edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ $ts.editWidgets }}</button> -</div> -</template> - -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; -import XWidgets from '@/components/MkWidgets.vue'; - -export default defineComponent({ - components: { - XWidgets, - }, - - props: { - place: { - type: String, - }, - }, - - emits: ['mounted'], - - data() { - return { - editMode: false, - }; - }, - - mounted() { - this.$emit('mounted', this.$el); - }, - - methods: { - addWidget(widget) { - this.$store.set('widgets', [{ - ...widget, - place: this.place, - }, ...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, - } : w)); - }, - - updateWidgets(widgets) { - this.$store.set('widgets', [ - ...this.$store.state.widgets.filter(w => w.place !== this.place), - ...widgets, - ]); - }, - }, -}); -</script> - -<style lang="scss" scoped> -.ddiqwdnk { - position: sticky; - height: min-content; - box-sizing: border-box; - padding-bottom: 8px; - - > .widgets, - > .a { - width: 300px; - } - - > .edit { - display: block; - margin: 16px auto; - } -} -</style> diff --git a/packages/frontend/src/ui/deck/widgets-column.vue b/packages/frontend/src/ui/deck/widgets-column.vue index fc61d18ff6d0f73d392d8f59ff1b40dcc553f7d6..edc54c1c2b7971ac40941b9688d7d52b421612c4 100644 --- a/packages/frontend/src/ui/deck/widgets-column.vue +++ b/packages/frontend/src/ui/deck/widgets-column.vue @@ -4,7 +4,7 @@ <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"/> + <XWidgets :edit="edit" :widgets="column.widgets ?? []" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> </div> </XColumn> </template> diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index b91bf476e82f92be1013b65786d8b083f3c94784..2cb8a9d6a6db250c2dcf0acd4bdfa0346cc88056 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -273,7 +273,7 @@ const wallpaper = localStorage.getItem('wallpaper') != null; right: 0; z-index: 1001; height: 100dvh; - padding: var(--margin); + padding: var(--margin) !important; box-sizing: border-box; overflow: auto; overscroll-behavior: contain; diff --git a/packages/frontend/src/ui/universal.widgets.vue b/packages/frontend/src/ui/universal.widgets.vue index 33fb49283693af69f937fc71b69d2c5c4907fa66..002aab10901f7d60a0303ded6df92818c1f2b041 100644 --- a/packages/frontend/src/ui/universal.widgets.vue +++ b/packages/frontend/src/ui/universal.widgets.vue @@ -1,25 +1,44 @@ <template> -<div class="efzpzdvf"> - <XWidgets :edit="editMode" :widgets="defaultStore.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> +<div class="efzpzdvf" :class="{ universal: !classic, classic }"> + <XWidgets :edit="editMode" :widgets="widgets" @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="ti ti-check"></i> {{ i18n.ts.editWidgetsExit }}</button> <button v-else class="_textButton mk-widget-edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button> </div> </template> +<script lang="ts"> +let editMode = $ref(false); +</script> <script lang="ts" setup> import { onMounted } from 'vue'; import XWidgets from '@/components/MkWidgets.vue'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; +const props = withDefaults(defineProps<{ + // null = å…¨ã¦ã®ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆã‚’表示 + // left = place: leftã ã‘を表示 + // right = rightã¨nullを表示 + place?: 'left' | null | 'right'; + classic?: boolean; +}>(), { + place: null, + classic: false, +}); + const emit = defineEmits<{ - (ev: 'mounted', el: Element): void; + (ev: 'mounted', el?: Element): void; }>(); -let editMode = $ref(false); let rootEl = $ref<HTMLDivElement>(); +const widgets = $computed(() => { + if (props.place === null) return defaultStore.reactiveState.widgets.value; + if (props.place === 'left') return defaultStore.reactiveState.widgets.value.filter(w => w.place === 'left'); + return defaultStore.reactiveState.widgets.value.filter(w => w.place !== 'left'); +}); + onMounted(() => { emit('mounted', rootEl); }); @@ -27,7 +46,7 @@ onMounted(() => { function addWidget(widget) { defaultStore.set('widgets', [{ ...widget, - place: null, + place: props.place, }, ...defaultStore.state.widgets]); } @@ -39,11 +58,26 @@ function updateWidget({ id, data }) { defaultStore.set('widgets', defaultStore.state.widgets.map(w => w.id === id ? { ...w, data, + place: props.place, } : w)); } -function updateWidgets(widgets) { - defaultStore.set('widgets', widgets); +function updateWidgets(thisWidgets) { + if (props.place === null) { + defaultStore.set('widgets', thisWidgets); + return; + } + if (props.place === 'left') { + defaultStore.set('widgets', [ + ...thisWidgets.map(w => ({ ...w, place: 'left' })), + ...defaultStore.state.widgets.filter(w => w.place !== 'left' && !thisWidgets.some(t => w.id === t.id)), + ]); + return; + } + defaultStore.set('widgets', [ + ...defaultStore.state.widgets.filter(w => w.place === 'left' && !thisWidgets.some(t => w.id === t.id)), + ...thisWidgets.map(w => ({ ...w, place: 'right' })), + ]); } </script> @@ -52,11 +86,17 @@ function updateWidgets(widgets) { position: sticky; height: min-content; min-height: 100vh; - padding: var(--margin) 0; box-sizing: border-box; + &.universal { + padding: var(--margin) 0; + + > * { + margin: var(--margin) 0; + } + } + > * { - margin: var(--margin) 0; width: 300px; &:first-child {