diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2022-12-27 14:36:33 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2022-12-27 14:36:33 +0900 |
| commit | 9384f5399da39e53855beb8e7f8ded1aa56bf72e (patch) | |
| tree | ce5959571a981b9c4047da3c7b3fd080aa44222c /packages/frontend/src/components/MkWidgets.vue | |
| parent | wip: retention for dashboard (diff) | |
| download | misskey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.gz misskey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.bz2 misskey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.zip | |
rename: client -> frontend
Diffstat (limited to 'packages/frontend/src/components/MkWidgets.vue')
| -rw-r--r-- | packages/frontend/src/components/MkWidgets.vue | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue new file mode 100644 index 0000000000..fff89117ce --- /dev/null +++ b/packages/frontend/src/components/MkWidgets.vue @@ -0,0 +1,165 @@ +<template> +<div class="vjoppmmu"> + <template v-if="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> + </MkSelect> + <MkButton inline primary class="mk-widget-add" @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> + <MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton> + </header> + <Sortable + :model-value="props.widgets" + item-key="id" + handle=".handle" + :animation="150" + @update:model-value="v => emit('updateWidgets', v)" + > + <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="handle"> + <component :is="`mkw-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="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)"/> +</div> +</template> + +<script lang="ts" setup> +import { defineAsyncComponent, reactive, ref, computed } from 'vue'; +import { v4 as uuid } from 'uuid'; +import MkSelect from '@/components/form/select.vue'; +import MkButton from '@/components/MkButton.vue'; +import { widgets as widgetDefs } from '@/widgets'; +import * as os from '@/os'; +import { i18n } from '@/i18n'; +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; +}>(); + +const emit = defineEmits<{ + (ev: 'updateWidgets', widgets: Widget[]): void; + (ev: 'addWidget', widget: Widget): void; + (ev: 'removeWidget', widget: Widget): void; + (ev: 'updateWidget', widget: Partial<Widget>): void; + (ev: 'exit'): void; +}>(); + +const widgetRefs = {}; +const configWidget = (id: string) => { + widgetRefs[id].configure(); +}; +const widgetAdderSelected = ref(null); +const addWidget = () => { + if (widgetAdderSelected.value == null) return; + + emit('addWidget', { + name: widgetAdderSelected.value, + id: uuid(), + data: {}, + }); + + widgetAdderSelected.value = null; +}; +const removeWidget = (widget) => { + emit('removeWidget', widget); +}; +const updateWidget = (id, data) => { + emit('updateWidget', { id, data }); +}; + +function onContextmenu(widget: Widget, 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; + + os.contextMenu([{ + type: 'label', + text: i18n.t(`_widgets.${widget.name}`), + }, { + icon: 'ti ti-settings', + text: i18n.ts.settings, + action: () => { + configWidget(widget.id); + }, + }], ev); +} +</script> + +<style lang="scss" scoped> +.vjoppmmu { + container-type: inline-size; + + > header { + margin: 16px 0; + + > * { + width: 100%; + padding: 4px; + } + } + + > .widget, .customize-container { + contain: content; + margin: var(--margin) 0; + + &: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; + } + + > .config { + right: 8px + 8px + 32px; + } + + > .remove { + right: 8px; + } + + > .handle { + > .widget { + pointer-events: none; + } + } + } +} +</style> |