diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 394577f378..680e70f61e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -97,6 +97,7 @@ cantRenote: "この投稿はRenoteできません。" cantReRenote: "RenoteをRenoteすることはできません。" quote: "引用" pinnedNote: "ピン留めされたノート" +pinned: "ピン留め" you: "あなた" clickToShow: "クリックして表示" sensitive: "閲覧注意" diff --git a/src/client/components/ui/tooltip.vue b/src/client/components/ui/tooltip.vue index 6ea344c54d..b220fe5d8c 100644 --- a/src/client/components/ui/tooltip.vue +++ b/src/client/components/ui/tooltip.vue @@ -1,6 +1,6 @@ <template> -<transition name="zoom-in-top" appear @after-leave="$emit('closed')"> - <div class="buebdbiu _acrylic _shadow" v-if="showing"> +<transition name="tooltip" appear @after-leave="$emit('closed')"> + <div class="buebdbiu _acrylic _shadow" v-show="showing" ref="content"> <slot>{{ text }}</slot> </div> </transition> @@ -35,19 +35,43 @@ export default defineComponent({ const rect = this.source.getBoundingClientRect(); - let x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2); - let y = rect.top + window.pageYOffset + this.source.offsetHeight; + const contentWidth = this.$refs.content.offsetWidth; + const contentHeight = this.$refs.content.offsetHeight; - x -= (this.$el.offsetWidth / 2); + let left = rect.left + window.pageXOffset + (this.source.offsetWidth / 2); + let top = rect.top + window.pageYOffset + this.source.offsetHeight; - this.$el.style.left = x + 'px'; - this.$el.style.top = y + 'px'; + left -= (this.$el.offsetWidth / 2); + + if (left + contentWidth - window.pageXOffset > window.innerWidth) { + left = window.innerWidth - contentWidth + window.pageXOffset - 1; + } + + if (top + contentHeight - window.pageYOffset > window.innerHeight) { + top = rect.top + window.pageYOffset - contentHeight; + this.$refs.content.style.transformOrigin = 'center bottom'; + } + + this.$el.style.left = left + 'px'; + this.$el.style.top = top + 'px'; }); }, }) </script> <style lang="scss" scoped> +.tooltip-enter-active, +.tooltip-leave-active { + opacity: 1; + transform: scale(1); + transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 200ms cubic-bezier(0.23, 1, 0.32, 1); +} +.tooltip-enter-from, +.tooltip-leave-active { + opacity: 0; + transform: scale(0.75); +} + .buebdbiu { position: absolute; z-index: 11000; @@ -57,6 +81,6 @@ export default defineComponent({ text-align: center; border-radius: 4px; pointer-events: none; - transform-origin: center -16px; + transform-origin: center top; } </style> diff --git a/src/client/components/widgets.vue b/src/client/components/widgets.vue new file mode 100644 index 0000000000..23fce7d714 --- /dev/null +++ b/src/client/components/widgets.vue @@ -0,0 +1,153 @@ +<template> +<div class="vjoppmmu"> + <template v-if="edit"> + <header> + <MkSelect v-model:value="widgetAdderSelected" style="margin-bottom: var(--margin)"> + <template #label>{{ $ts.selectWidget }}</template> + <option v-for="widget in widgetDefs" :value="widget" :key="widget">{{ $t(`_widgets.${widget}`) }}</option> + </MkSelect> + <MkButton inline @click="addWidget" primary><Fa :icon="faPlus"/> {{ $ts.add }}</MkButton> + <MkButton inline @click="$emit('exit')">{{ $ts.close }}</MkButton> + </header> + <XDraggable + v-model="_widgets" + item-key="id" + animation="150" + > + <template #item="{element}"> + <div class="customize-container"> + <button class="config _button" @click.prevent.stop="configWidget(element.id)"><Fa :icon="faCog"/></button> + <button class="remove _button" @click.prevent.stop="removeWidget(element)"><Fa :icon="faTimes"/></button> + <component :is="`mkw-${element.name}`" :widget="element" :setting-callback="setting => settings[element.id] = setting" :column="column" @updateProps="updateWidget(element.id, $event)"/> + </div> + </template> + </XDraggable> + </template> + <component v-else class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" :column="column" @updateProps="updateWidget(widget.id, $event)"/> +</div> +</template> + +<script lang="ts"> +import { defineComponent, defineAsyncComponent } from 'vue'; +import { v4 as uuid } from 'uuid'; +import { faTimes, faCog, faPlus } from '@fortawesome/free-solid-svg-icons'; +import MkSelect from '@/components/ui/select.vue'; +import MkButton from '@/components/ui/button.vue'; +import { widgets as widgetDefs } from '@/widgets'; + +export default defineComponent({ + components: { + XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), + MkSelect, + MkButton, + }, + + props: { + widgets: { + required: true, + }, + edit: { + type: Boolean, + required: true, + }, + }, + + emits: ['updateWidgets', 'addWidget', 'removeWidget', 'updateWidget', 'exit'], + + data() { + return { + widgetAdderSelected: null, + widgetDefs, + settings: {}, + faTimes, faPlus, faCog + }; + }, + + computed: { + _widgets: { + get() { + return this.widgets; + }, + set(value) { + this.$emit('updateWidgets', value); + } + } + }, + + methods: { + configWidget(id) { + this.settings[id](); + }, + + addWidget() { + if (this.widgetAdderSelected == null) return; + + this.$emit('addWidget', { + name: this.widgetAdderSelected, + id: uuid(), + data: {} + }); + + this.widgetAdderSelected = null; + }, + + removeWidget(widget) { + this.$emit('removeWidget', widget); + }, + + updateWidget(id, data) { + this.$emit('updateWidget', { id, data }); + }, + } +}); +</script> + +<style lang="scss" scoped> +.vjoppmmu { + > header { + margin: 16px 0; + + > * { + width: 100%; + padding: 4px; + } + } + + > .widget, .customize-container { + margin: var(--margin) 0; + + &:first-of-type { + margin-top: 0; + } + } + + .customize-container { + position: relative; + cursor: move; + + > *:not(.remove):not(.config) { + pointer-events: none; + } + + > .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; + } + + > .remove { + right: 8px; + } + } +} +</style> diff --git a/src/client/directives/tooltip.ts b/src/client/directives/tooltip.ts index faeeef79a7..2a0a13663c 100644 --- a/src/client/directives/tooltip.ts +++ b/src/client/directives/tooltip.ts @@ -4,6 +4,7 @@ import { popup } from '@/os'; const start = isDeviceTouch ? 'touchstart' : 'mouseover'; const end = isDeviceTouch ? 'touchend' : 'mouseleave'; +const delay = 100; export default { mounted(el: HTMLElement, binding, vn) { @@ -47,13 +48,13 @@ export default { el.addEventListener(start, () => { clearTimeout(self.showTimer); clearTimeout(self.hideTimer); - self.showTimer = setTimeout(show, 300); + self.showTimer = setTimeout(show, delay); }, { passive: true }); el.addEventListener(end, () => { clearTimeout(self.showTimer); clearTimeout(self.hideTimer); - self.hideTimer = setTimeout(self.close, 300); + self.hideTimer = setTimeout(self.close, delay); }, { passive: true }); el.addEventListener('click', () => { diff --git a/src/client/style.scss b/src/client/style.scss index 14e8c87314..de548cc9c9 100644 --- a/src/client/style.scss +++ b/src/client/style.scss @@ -488,19 +488,6 @@ hr { transform: scale(0.9); } -.zoom-in-top-enter-active, -.zoom-in-top-leave-active { - opacity: 1; - transform: scaleY(1); - transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1); - transform-origin: center top; -} -.zoom-in-top-enter-from, -.zoom-in-top-leave-active { - opacity: 0; - transform: scaleY(0); -} - @keyframes blink { 0% { opacity: 1; transform: scale(1); } 30% { opacity: 1; transform: scale(1); } diff --git a/src/client/ui/chat/index.vue b/src/client/ui/chat/index.vue index a54e5f97ff..d1579038c7 100644 --- a/src/client/ui/chat/index.vue +++ b/src/client/ui/chat/index.vue @@ -10,10 +10,10 @@ </button> </div> <div class="right"> - <MkA class="item" to="/my/messaging"><Fa class="icon" :icon="faComments"/><i v-if="$i.hasUnreadMessagingMessage"><Fa :icon="faCircle"/></i></MkA> - <MkA class="item" to="/my/messages"><Fa class="icon" :icon="faEnvelope"/><i v-if="$i.hasUnreadSpecifiedNotes"><Fa :icon="faCircle"/></i></MkA> - <MkA class="item" to="/my/mentions"><Fa class="icon" :icon="faAt"/><i v-if="$i.hasUnreadMentions"><Fa :icon="faCircle"/></i></MkA> - <MkA class="item" to="/my/notifications"><Fa class="icon" :icon="faBell"/><i v-if="$i.hasUnreadNotification"><Fa :icon="faCircle"/></i></MkA> + <MkA class="item" to="/my/messaging" v-tooltip="$ts.messaging"><Fa class="icon" :icon="faComments"/><i v-if="$i.hasUnreadMessagingMessage"><Fa :icon="faCircle"/></i></MkA> + <MkA class="item" to="/my/messages" v-tooltip="$ts.directNotes"><Fa class="icon" :icon="faEnvelope"/><i v-if="$i.hasUnreadSpecifiedNotes"><Fa :icon="faCircle"/></i></MkA> + <MkA class="item" to="/my/mentions" v-tooltip="$ts.mentions"><Fa class="icon" :icon="faAt"/><i v-if="$i.hasUnreadMentions"><Fa :icon="faCircle"/></i></MkA> + <MkA class="item" to="/my/notifications" v-tooltip="$ts.notifications"><Fa class="icon" :icon="faBell"/><i v-if="$i.hasUnreadNotification"><Fa :icon="faCircle"/></i></MkA> </div> </header> <div class="body"> @@ -63,10 +63,10 @@ </button> </div> <div class="right"> - <button class="_button item search" @click="search"> + <button class="_button item search" @click="search" v-tooltip="$ts.search"> <Fa :icon="faSearch"/> </button> - <MkA class="item" to="/settings"><Fa class="icon" :icon="faCog"/></MkA> + <MkA class="item" to="/settings" v-tooltip="$ts.settings"><Fa class="icon" :icon="faCog"/></MkA> </div> </footer> </div> @@ -97,11 +97,12 @@ </div> <div class="right"> + <div class="instance">{{ instanceName }}</div> <XHeaderClock class="clock"/> - <button class="_button button search" @click="search"> + <button class="_button button search" @click="search" v-tooltip="$ts.search"> <Fa :icon="faSearch"/> </button> - <button class="_button button follow" v-if="tl.startsWith('channel:') && currentChannel" :class="{ followed: currentChannel.isFollowing }" @click="toggleChannelFollow"> + <button class="_button button follow" v-if="tl.startsWith('channel:') && currentChannel" :class="{ followed: currentChannel.isFollowing }" @click="toggleChannelFollow" v-tooltip="currentChannel.isFollowing ? $ts.unfollow : $ts.follow"> <Fa v-if="currentChannel.isFollowing" :icon="faStar"/> <Fa v-else :icon="farStar"/> </button> @@ -121,6 +122,9 @@ </main> <XSide class="side" ref="side"/> + <div class="side"> + <XWidgets/> + </div> <XCommon/> </div> @@ -132,6 +136,7 @@ import { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, fa import { faBell, faStar as farStar, faEnvelope, faComments } from '@fortawesome/free-regular-svg-icons'; import { instanceName, url } from '@/config'; import XSidebar from '@/components/sidebar.vue'; +import XWidgets from './widgets.vue'; import XCommon from '../_common_/common.vue'; import XSide from './side.vue'; import XTimeline from './timeline.vue'; @@ -147,6 +152,7 @@ export default defineComponent({ components: { XCommon, XSidebar, + XWidgets, XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる XTimeline, XPostForm, @@ -187,6 +193,7 @@ export default defineComponent({ featuredChannels: null, currentChannel: null, menuDef: sidebarDef, + instanceName, faLayerGroup, faBars, faBell, faHome, faCircle, faPencilAlt, faShareAlt, faSatelliteDish, faListUl, faSatellite, faCog, faSearch, faPlus, faStar, farStar, faAt, faLink, faEllipsisH, faGlobe, faComments, faEnvelope, }; }, @@ -310,8 +317,7 @@ export default defineComponent({ $ui-font-size: 1em; // TODO: どこかに集約したい // ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ - min-height: calc(var(--vh, 1vh) * 100); - box-sizing: border-box; + height: calc(var(--vh, 1vh) * 100); display: flex; > .nav { @@ -518,6 +524,10 @@ export default defineComponent({ margin-left: auto; padding-left: 8px; + > .instance { + margin-right: 16px; + } + > .clock { margin-right: 16px; } @@ -552,6 +562,7 @@ export default defineComponent({ } > .side { + width: 350px; border-left: solid 1px var(--divider); } } diff --git a/src/client/ui/chat/side.vue b/src/client/ui/chat/side.vue index 188123deb9..9b15c72841 100644 --- a/src/client/ui/chat/side.vue +++ b/src/client/ui/chat/side.vue @@ -120,12 +120,8 @@ export default defineComponent({ --section-padding: 16px; --margin: var(--marginHalf); - width: 390px; - > .container { - position: fixed; - width: 390px; - height: 100vh; + height: 100%; overflow: auto; box-sizing: border-box; diff --git a/src/client/ui/chat/store.ts b/src/client/ui/chat/store.ts new file mode 100644 index 0000000000..a869debd61 --- /dev/null +++ b/src/client/ui/chat/store.ts @@ -0,0 +1,13 @@ +import { markRaw } from 'vue'; +import { Storage } from '../../pizzax'; + +export const store = markRaw(new Storage('chatUi', { + widgets: { + where: 'account', + default: [] as { + name: string; + id: string; + data: Record<string, any>; + }[] + }, +})); diff --git a/src/client/ui/chat/widgets.vue b/src/client/ui/chat/widgets.vue new file mode 100644 index 0000000000..6becaa22e3 --- /dev/null +++ b/src/client/ui/chat/widgets.vue @@ -0,0 +1,61 @@ +<template> +<div class="qydbhufi"> + <XWidgets :edit="edit" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> + + <button v-if="edit" @click="edit = false" class="_textButton" style="font-size: 0.9em;">{{ $ts.editWidgetsExit }}</button> + <button v-else @click="edit = true" class="_textButton" style="font-size: 0.9em;">{{ $ts.editWidgets }}</button> +</div> +</template> + +<script lang="ts"> +import { defineComponent, defineAsyncComponent } from 'vue'; +import XWidgets from '@/components/widgets.vue'; +import { store } from './store.ts'; + +export default defineComponent({ + components: { + XWidgets, + }, + + data() { + return { + edit: false, + widgets: store.reactiveState.widgets + }; + }, + + methods: { + addWidget(widget) { + store.set('widgets', [widget, ...store.state.widgets]); + }, + + removeWidget(widget) { + store.set('widgets', store.state.widgets.filter(w => w.id != widget.id)); + }, + + updateWidget({ id, data }) { + store.set('widgets', store.state.widgets.map(w => w.id === id ? { + ...w, + data: data + } : w)); + }, + + updateWidgets(widgets) { + store.set('widgets', widgets); + } + } +}); +</script> + +<style lang="scss" scoped> +.qydbhufi { + height: 100%; + box-sizing: border-box; + overflow: auto; + padding: var(--margin); + + ::v-deep(._panel) { + box-shadow: none; + } +} +</style> diff --git a/src/client/ui/deck/widgets-column.vue b/src/client/ui/deck/widgets-column.vue index 5cf7dde26f..b7740c270d 100644 --- a/src/client/ui/deck/widgets-column.vue +++ b/src/client/ui/deck/widgets-column.vue @@ -3,49 +3,22 @@ <template #header><Fa :icon="faWindowMaximize" style="margin-right: 8px;"/>{{ column.name }}</template> <div class="wtdtxvec"> - <template v-if="edit"> - <header> - <MkSelect v-model:value="widgetAdderSelected" style="margin-bottom: var(--margin)"> - <template #label>{{ $ts.selectWidget }}</template> - <option v-for="widget in widgets" :value="widget" :key="widget">{{ $t(`_widgets.${widget}`) }}</option> - </MkSelect> - <MkButton inline @click="addWidget" primary><Fa :icon="faPlus"/> {{ $ts.add }}</MkButton> - <MkButton inline @click="edit = false">{{ $ts.close }}</MkButton> - </header> - <XDraggable - v-model="_widgets" - item-key="id" - animation="150" - > - <template #item="{element}"> - <div class="customize-container" @click="widgetFunc(element.id)"> - <button class="remove _button" @click.prevent.stop="removeWidget(element)"><Fa :icon="faTimes"/></button> - <component :is="`mkw-${element.name}`" :widget="element" :setting-callback="setting => settings[element.id] = setting" :column="column" @updateProps="saveWidget(element.id, $event)"/> - </div> - </template> - </XDraggable> - </template> - <component v-else class="widget" v-for="widget in column.widgets" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" :column="column" @updateProps="saveWidget(widget.id, $event)"/> + <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"> import { defineComponent, defineAsyncComponent } from 'vue'; -import { v4 as uuid } from 'uuid'; import { faWindowMaximize, faTimes, faCog, faPlus } from '@fortawesome/free-solid-svg-icons'; -import MkSelect from '@/components/ui/select.vue'; -import MkButton from '@/components/ui/button.vue'; +import XWidgets from '@/components/widgets.vue'; import XColumn from './column.vue'; -import { widgets } from '../../widgets'; import { addColumnWidget, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store'; export default defineComponent({ components: { XColumn, - XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), - MkSelect, - MkButton, + XWidgets, }, props: { @@ -62,49 +35,27 @@ export default defineComponent({ data() { return { edit: false, - widgetAdderSelected: null, - widgets, - settings: {}, faWindowMaximize, faTimes, faPlus }; }, - computed: { - _widgets: { - get() { - return this.column.widgets; - }, - set(value) { - setColumnWidgets(this.column.id, value); - } - } - }, - methods: { - widgetFunc(id) { - this.settings[id](); - }, - - addWidget() { - if (this.widgetAdderSelected == null) return; - - addColumnWidget(this.column.id, { - name: this.widgetAdderSelected, - id: uuid(), - data: {} - }); - - this.widgetAdderSelected = null; + addWidget(widget) { + addColumnWidget(this.column.id, widget); }, removeWidget(widget) { removeColumnWidget(this.column.id, widget); }, - saveWidget(id, data) { + updateWidget({ id, data }) { updateColumnWidget(this.column.id, id, data); }, + updateWidgets(widgets) { + setColumnWidgets(this.column.id, widgets); + }, + func() { this.edit = !this.edit; } @@ -114,46 +65,12 @@ export default defineComponent({ <style lang="scss" scoped> .wtdtxvec { - ._panel { + --margin: 8px; + + padding: 0 var(--margin); + + ::v-deep(._panel) { box-shadow: none; } - - > header { - padding: 16px; - - > * { - width: 100%; - padding: 4px; - } - } - - > .widget, .customize-container { - margin: 8px; - - &:first-of-type { - margin-top: 0; - } - } - - .customize-container { - position: relative; - cursor: move; - - > *:not(.remove) { - pointer-events: none; - } - - > .remove { - position: absolute; - z-index: 2; - top: 8px; - right: 8px; - width: 32px; - height: 32px; - color: #fff; - background: rgba(#000, 0.7); - border-radius: 4px; - } - } } </style> diff --git a/src/client/ui/default.widgets.vue b/src/client/ui/default.widgets.vue index ec73c42777..ff7cdf1140 100644 --- a/src/client/ui/default.widgets.vue +++ b/src/client/ui/default.widgets.vue @@ -1,46 +1,21 @@ <template> <div class="efzpzdvf"> - <template v-if="editMode"> - <MkButton primary @click="addWidget" class="add"><Fa :icon="faPlus"/></MkButton> - <XDraggable - v-model="widgets" - item-key="id" - handle=".handle" - animation="150" - class="sortable" - > - <template #item="{element}"> - <div class="customize-container _panel"> - <header> - <span class="handle"><Fa :icon="faBars"/></span>{{ $t('_widgets.' + element.name) }}<button class="remove _button" @click="removeWidget(element)"><Fa :icon="faTimes"/></button> - </header> - <div @click="widgetFunc(element.id)"> - <component class="_inContainer_ _forceContainerFull_" :is="`mkw-${element.name}`" :widget="element" :ref="element.id" :setting-callback="setting => settings[element.id] = setting" @updateProps="saveWidget(element.id, $event)"/> - </div> - </div> - </template> - </XDraggable> - <button @click="editMode = false" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faCheck"/> {{ $ts.editWidgetsExit }}</button> - </template> - <template v-else> - <component v-for="widget in widgets" class="_inContainer_ _forceContainerFull_" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" @updateProps="saveWidget(widget.id, $event)"/> - <button @click="editMode = true" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faPencilAlt"/> {{ $ts.editWidgets }}</button> - </template> + <XWidgets :edit="editMode" :widgets="$store.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> + + <button v-if="editMode" @click="editMode = false" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faCheck"/> {{ $ts.editWidgetsExit }}</button> + <button v-else @click="editMode = true" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faPencilAlt"/> {{ $ts.editWidgets }}</button> </div> </template> <script lang="ts"> import { defineComponent, defineAsyncComponent } from 'vue'; -import { v4 as uuid } from 'uuid'; import { faPencilAlt, faPlus, faBars, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons'; -import { widgets } from '@/widgets'; +import XWidgets from '@/components/widgets.vue'; import * as os from '@/os'; -import MkButton from '@/components/ui/button.vue'; export default defineComponent({ components: { - MkButton, - XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), + XWidgets }, emits: ['mounted'], @@ -48,62 +23,35 @@ export default defineComponent({ data() { return { editMode: false, - settings: {}, faPencilAlt, faPlus, faBars, faTimes, faCheck, }; }, - computed: { - widgets: { - get() { - return this.$store.reactiveState.widgets.value; - }, - set(value) { - this.$store.set('widgets', value); - } - }, - }, - mounted() { this.$emit('mounted', this.$el); }, methods: { - widgetFunc(id) { - this.settings[id](); - }, - - async addWidget() { - const { canceled, result: widget } = await os.dialog({ - type: null, - title: this.$ts.chooseWidget, - select: { - items: widgets.map(widget => ({ - value: widget, - text: this.$t('_widgets.' + widget), - })) - }, - showCancelButton: true - }); - if (canceled) return; - - this.$store.set('widgets', [...this.$store.state.widgets, { - name: widget, - id: uuid(), + addWidget(widget) { + this.$store.set('widgets', [{ + ...widget, place: null, - data: {} - }]); + }, ...this.$store.state.widgets]); }, removeWidget(widget) { this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id != widget.id)); }, - saveWidget(id, data) { + 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); } } }); @@ -129,35 +77,5 @@ export default defineComponent({ > .add { margin: 0 auto; } - - .customize-container { - margin: 8px 0; - - > header { - position: relative; - line-height: 32px; - - > .handle { - padding: 0 8px; - cursor: move; - } - - > .remove { - position: absolute; - top: 0; - right: 0; - padding: 0 8px; - line-height: 32px; - } - } - - > div { - padding: 8px; - - > * { - pointer-events: none; - } - } - } } </style>