summaryrefslogtreecommitdiff
path: root/packages/client/src/components/ui
diff options
context:
space:
mode:
authortamaina <tamaina@hotmail.co.jp>2022-01-30 14:11:52 +0900
committerGitHub <noreply@github.com>2022-01-30 14:11:52 +0900
commit55b3ae22ee81774b5641f3a42216327b9277f6e5 (patch)
tree0467accc21547ddd0122c4520ac3aa38ceeb29c9 /packages/client/src/components/ui
parentupdate misskey-js (diff)
downloadmisskey-55b3ae22ee81774b5641f3a42216327b9277f6e5.tar.gz
misskey-55b3ae22ee81774b5641f3a42216327b9277f6e5.tar.bz2
misskey-55b3ae22ee81774b5641f3a42216327b9277f6e5.zip
enhance: メニュー関連をComposition API化、switchアイテム追加 (#8215)
* メニューをComposition API化、switchアイテム追加 クライアントサイド画像圧縮の準備 * メニュー型定義を分離 (TypeScriptの型支援が効かないので) * disabled * make keepOriginal to follow setting value * fix * fix * Fix * clean up
Diffstat (limited to 'packages/client/src/components/ui')
-rw-r--r--packages/client/src/components/ui/context-menu.vue113
-rw-r--r--packages/client/src/components/ui/menu.vue167
-rw-r--r--packages/client/src/components/ui/popup-menu.vue48
3 files changed, 131 insertions, 197 deletions
diff --git a/packages/client/src/components/ui/context-menu.vue b/packages/client/src/components/ui/context-menu.vue
index 85606bf6d5..f491b43b46 100644
--- a/packages/client/src/components/ui/context-menu.vue
+++ b/packages/client/src/components/ui/context-menu.vue
@@ -1,88 +1,71 @@
<template>
<transition :name="$store.state.animation ? 'fade' : ''" appear>
- <div class="nvlagfpb" :style="{ zIndex }" @contextmenu.prevent.stop="() => {}">
+ <div ref="rootEl" class="nvlagfpb" :style="{ zIndex }" @contextmenu.prevent.stop="() => {}">
<MkMenu :items="items" class="_popup _shadow" :align="'left'" @close="$emit('closed')"/>
</div>
</transition>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { onMounted, onBeforeUnmount } from 'vue';
import contains from '@/scripts/contains';
import MkMenu from './menu.vue';
+import { MenuItem } from './types/menu.vue';
import * as os from '@/os';
-export default defineComponent({
- components: {
- MkMenu,
- },
- props: {
- items: {
- type: Array,
- required: true
- },
- ev: {
- required: true
- },
- viaKeyboard: {
- type: Boolean,
- required: false
- },
- },
- emits: ['closed'],
- data() {
- return {
- zIndex: os.claimZIndex('high'),
- };
- },
- computed: {
- keymap(): any {
- return {
- 'esc': () => this.$emit('closed'),
- };
- },
- },
- mounted() {
- let left = this.ev.pageX + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
- let top = this.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
+const props = defineProps<{
+ items: MenuItem[];
+ ev: MouseEvent;
+}>();
- const width = this.$el.offsetWidth;
- const height = this.$el.offsetHeight;
+const emit = defineEmits<{
+ (e: 'closed'): void;
+}>();
- if (left + width - window.pageXOffset > window.innerWidth) {
- left = window.innerWidth - width + window.pageXOffset;
- }
+let rootEl = $ref<HTMLDivElement>();
- if (top + height - window.pageYOffset > window.innerHeight) {
- top = window.innerHeight - height + window.pageYOffset;
- }
+let zIndex = $ref<number>(os.claimZIndex('high'));
- if (top < 0) {
- top = 0;
- }
+onMounted(() => {
+ let left = props.ev.pageX + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
+ let top = props.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
- if (left < 0) {
- left = 0;
- }
+ const width = rootEl.offsetWidth;
+ const height = rootEl.offsetHeight;
- this.$el.style.top = top + 'px';
- this.$el.style.left = left + 'px';
+ if (left + width - window.pageXOffset > window.innerWidth) {
+ left = window.innerWidth - width + window.pageXOffset;
+ }
+
+ if (top + height - window.pageYOffset > window.innerHeight) {
+ top = window.innerHeight - height + window.pageYOffset;
+ }
- for (const el of Array.from(document.querySelectorAll('body *'))) {
- el.addEventListener('mousedown', this.onMousedown);
- }
- },
- beforeUnmount() {
- for (const el of Array.from(document.querySelectorAll('body *'))) {
- el.removeEventListener('mousedown', this.onMousedown);
- }
- },
- methods: {
- onMousedown(e) {
- if (!contains(this.$el, e.target) && (this.$el != e.target)) this.$emit('closed');
- },
+ if (top < 0) {
+ top = 0;
+ }
+
+ if (left < 0) {
+ left = 0;
+ }
+
+ rootEl.style.top = `${top}px`;
+ rootEl.style.left = `${left}px`;
+
+ for (const el of Array.from(document.querySelectorAll('body *'))) {
+ el.addEventListener('mousedown', onMousedown);
+ }
+});
+
+onBeforeUnmount(() => {
+ for (const el of Array.from(document.querySelectorAll('body *'))) {
+ el.removeEventListener('mousedown', onMousedown);
}
});
+
+function onMousedown(e: Event) {
+ if (!contains(rootEl, e.target) && (rootEl != e.target)) emit('closed');
+}
</script>
<style lang="scss" scoped>
diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue
index 41165c8d33..a93cc8cda8 100644
--- a/packages/client/src/components/ui/menu.vue
+++ b/packages/client/src/components/ui/menu.vue
@@ -1,8 +1,8 @@
<template>
-<div ref="items" v-hotkey="keymap"
+<div ref="itemsEl" v-hotkey="keymap"
class="rrevdjwt"
:class="{ center: align === 'center', asDrawer }"
- :style="{ width: (width && !asDrawer) ? width + 'px' : null, maxHeight: maxHeight ? maxHeight + 'px' : null }"
+ :style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }"
@contextmenu.self="e => e.preventDefault()"
>
<template v-for="(item, i) in items2">
@@ -28,6 +28,9 @@
<MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/>
<span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span>
</button>
+ <span v-else-if="item.type === 'switch'" :tabindex="i" class="item">
+ <FormSwitch v-model="item.ref" :disabled="item.disabled" class="form-switch">{{ item.text }}</FormSwitch>
+ </span>
<button v-else :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)">
<i v-if="item.icon" class="fa-fw" :class="item.icon"></i>
<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
@@ -41,114 +44,78 @@
</div>
</template>
-<script lang="ts">
-import { defineComponent, ref, unref } from 'vue';
+<script lang="ts" setup>
+import { nextTick, onMounted, watch } from 'vue';
import { focusPrev, focusNext } from '@/scripts/focus';
-import contains from '@/scripts/contains';
+import FormSwitch from '@/components/form/switch.vue';
+import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from '@/types/menu';
-export default defineComponent({
- props: {
- items: {
- type: Array,
- required: true
- },
- viaKeyboard: {
- type: Boolean,
- required: false
- },
- asDrawer: {
- type: Boolean,
- required: false
- },
- align: {
- type: String,
- requried: false
- },
- width: {
- type: Number,
- required: false
- },
- maxHeight: {
- type: Number,
- required: false
- },
- },
- emits: ['close'],
- data() {
- return {
- items2: [],
- };
- },
- computed: {
- keymap(): any {
- return {
- 'up|k|shift+tab': this.focusUp,
- 'down|j|tab': this.focusDown,
- 'esc': this.close,
- };
- },
- },
- watch: {
- items: {
- handler() {
- const items = ref(unref(this.items).filter(item => item !== undefined));
+const props = defineProps<{
+ items: MenuItem[];
+ viaKeyboard?: boolean;
+ asDrawer?: boolean;
+ align?: 'center' | string;
+ width?: number;
+ maxHeight?: number;
+}>();
- for (let i = 0; i < items.value.length; i++) {
- const item = items.value[i];
-
- if (item && item.then) { // if item is Promise
- items.value[i] = { type: 'pending' };
- item.then(actualItem => {
- items.value[i] = actualItem;
- });
- }
- }
+const emit = defineEmits<{
+ (e: 'close'): void;
+}>();
- this.items2 = items;
- },
- immediate: true
- }
- },
- mounted() {
- if (this.viaKeyboard) {
- this.$nextTick(() => {
- focusNext(this.$refs.items.children[0], true, false);
+let itemsEl = $ref<HTMLDivElement>();
+
+let items2: InnerMenuItem[] = $ref([]);
+
+let keymap = $computed(() => ({
+ 'up|k|shift+tab': focusUp,
+ 'down|j|tab': focusDown,
+ 'esc': close,
+}));
+
+watch(() => props.items, () => {
+ const items: (MenuItem | MenuPending)[] = [...props.items].filter(item => item !== undefined);
+
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+
+ if (item && 'then' in item) { // if item is Promise
+ items[i] = { type: 'pending' };
+ item.then(actualItem => {
+ items2[i] = actualItem;
});
}
+ }
- if (this.contextmenuEvent) {
- this.$el.style.top = this.contextmenuEvent.pageY + 'px';
- this.$el.style.left = this.contextmenuEvent.pageX + 'px';
+ items2 = items as InnerMenuItem[];
+}, {
+ immediate: true,
+});
- for (const el of Array.from(document.querySelectorAll('body *'))) {
- el.addEventListener('mousedown', this.onMousedown);
- }
- }
- },
- beforeUnmount() {
- for (const el of Array.from(document.querySelectorAll('body *'))) {
- el.removeEventListener('mousedown', this.onMousedown);
- }
- },
- methods: {
- clicked(fn, ev) {
- fn(ev);
- this.close();
- },
- close() {
- this.$emit('close');
- },
- focusUp() {
- focusPrev(document.activeElement);
- },
- focusDown() {
- focusNext(document.activeElement);
- },
- onMousedown(e) {
- if (!contains(this.$el, e.target) && (this.$el != e.target)) this.close();
- },
+onMounted(() => {
+ if (props.viaKeyboard) {
+ nextTick(() => {
+ focusNext(itemsEl.children[0], true, false);
+ });
}
});
+
+function clicked(fn: MenuAction, ev: MouseEvent) {
+ fn(ev);
+ close();
+}
+
+function close() {
+ emit('close');
+}
+
+function focusUp() {
+ focusPrev(document.activeElement);
+}
+
+function focusDown() {
+ focusNext(document.activeElement);
+}
</script>
<style lang="scss" scoped>
diff --git a/packages/client/src/components/ui/popup-menu.vue b/packages/client/src/components/ui/popup-menu.vue
index 8ffc4ad195..8d6c1b5695 100644
--- a/packages/client/src/components/ui/popup-menu.vue
+++ b/packages/client/src/components/ui/popup-menu.vue
@@ -1,44 +1,28 @@
<template>
-<MkModal ref="modal" v-slot="{ type, maxHeight }" :z-priority="'high'" :src="src" :transparent-bg="true" @click="$refs.modal.close()" @closed="$emit('closed')">
- <MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :as-drawer="type === 'drawer'" class="sfhdhdhq _popup _shadow" :class="{ drawer: type === 'drawer' }" @close="$refs.modal.close()"/>
+<MkModal ref="modal" v-slot="{ type, maxHeight }" :z-priority="'high'" :src="src" :transparent-bg="true" @click="modal.close()" @closed="emit('closed')">
+ <MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :as-drawer="type === 'drawer'" class="sfhdhdhq _popup _shadow" :class="{ drawer: type === 'drawer' }" @close="modal.close()"/>
</MkModal>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
import MkModal from './modal.vue';
import MkMenu from './menu.vue';
+import { MenuItem } from '@/types/menu';
-export default defineComponent({
- components: {
- MkModal,
- MkMenu,
- },
+defineProps<{
+ items: MenuItem[];
+ align?: 'center' | string;
+ width?: number;
+ viaKeyboard?: boolean;
+ src?: any;
+}>();
- props: {
- items: {
- type: Array,
- required: true
- },
- align: {
- type: String,
- required: false
- },
- width: {
- type: Number,
- required: false
- },
- viaKeyboard: {
- type: Boolean,
- required: false
- },
- src: {
- required: false
- },
- },
+const emit = defineEmits<{
+ (e: 'closed'): void;
+}>();
- emits: ['close', 'closed'],
-});
+let modal = $ref<InstanceType<typeof MkModal>>();
</script>
<style lang="scss" scoped>