デッキまわりをCompositon API / Setup Sugarに (#8410)

* universal.widgets.vue

* column.vue, antenna-column.vue

* direct-column.vue, list-column.vue

* main-column.vue

* wip

* ✌️

* fix

* ✌️

* ✌️
This commit is contained in:
tamaina 2022-03-21 03:11:14 +09:00 committed by GitHub
parent eb9e6d230f
commit 78736c70f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 639 additions and 756 deletions

View file

@ -17,7 +17,7 @@
</template> </template>
<script lang="ts" setup> <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 { notificationTypes } from 'misskey-js';
import MkPagination from '@/components/ui/pagination.vue'; import MkPagination from '@/components/ui/pagination.vue';
import { Paging } from '@/components/ui/pagination.vue'; import { Paging } from '@/components/ui/pagination.vue';
@ -29,7 +29,7 @@ import { stream } from '@/stream';
import { $i } from '@/account'; import { $i } from '@/account';
const props = defineProps<{ const props = defineProps<{
includeTypes?: PropType<typeof notificationTypes[number][]>; includeTypes?: typeof notificationTypes[number][];
unreadOnly?: boolean; unreadOnly?: boolean;
}>(); }>();

View file

@ -293,23 +293,25 @@ export function inputDate(props: {
}); });
} }
export function select(props: { export function select<C extends any = any>(props: {
title?: string | null; title?: string | null;
text?: string | null; text?: string | null;
default?: string | null; default?: string | null;
items?: { } & ({
value: string; items: {
value: C;
text: string; text: string;
}[]; }[];
groupedItems?: { } | {
groupedItems: {
label: string; label: string;
items: { items: {
value: string; value: C;
text: string; text: string;
}[]; }[];
}[]; }[];
}): Promise<{ canceled: true; result: undefined; } | { })): Promise<{ canceled: true; result: undefined; } | {
canceled: false; result: string; canceled: false; result: C;
}> { }> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup(import('@/components/dialog.vue'), { popup(import('@/components/dialog.vue'), {

View file

@ -17,7 +17,8 @@
:key="ids[0]" :key="ids[0]"
class="column" class="column"
:column="columns.find(c => c.id === ids[0])" :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)" @parent-focus="moveFocus(ids[0], $event)"
/> />
</template> </template>
@ -25,8 +26,8 @@
<div v-if="isMobile" class="buttons"> <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 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 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 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 post _button" @click="os.post()"><i class="fas fa-pencil-alt"></i></button>
</div> </div>
<transition :name="$store.state.animation ? 'menu-back' : ''"> <transition :name="$store.state.animation ? 'menu-back' : ''">
@ -45,8 +46,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, provide, ref, watch } from 'vue'; import { computed, provide, ref, watch } from 'vue';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import DeckColumnCore from '@/ui/deck/column-core.vue'; import DeckColumnCore from '@/ui/deck/column-core.vue';
import XSidebar from '@/ui/_common_/sidebar.vue'; import XSidebar from '@/ui/_common_/sidebar.vue';
@ -60,15 +61,6 @@ import { useRoute } from 'vue-router';
import { $i } from '@/account'; import { $i } from '@/account';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
export default defineComponent({
components: {
XCommon,
XSidebar,
XDrawerMenu,
DeckColumnCore,
},
setup() {
const isMobile = ref(window.innerWidth <= 500); const isMobile = ref(window.innerWidth <= 500);
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
isMobile.value = window.innerWidth <= 500; isMobile.value = window.innerWidth <= 500;
@ -122,8 +114,7 @@ export default defineComponent({
const onContextmenu = (ev) => { const onContextmenu = (ev) => {
os.contextMenu([{ os.contextMenu([{
text: i18n.ts._deck.addColumn, text: i18n.ts._deck.addColumn,
icon: null, action: addColumn,
action: addColumn
}], ev); }], ev);
}; };
@ -137,25 +128,15 @@ export default defineComponent({
document.documentElement.style.overflowY = 'hidden'; document.documentElement.style.overflowY = 'hidden';
document.documentElement.style.scrollBehavior = 'auto'; document.documentElement.style.scrollBehavior = 'auto';
window.addEventListener('wheel', (ev) => { window.addEventListener('wheel', (ev) => {
if (getScrollContainer(ev.target) == null) { if (getScrollContainer(ev.target as HTMLElement) == null) {
document.documentElement.scrollLeft += ev.deltaY > 0 ? 96 : -96; document.documentElement.scrollLeft += ev.deltaY > 0 ? 96 : -96;
} }
}); });
loadDeck(); loadDeck();
return { function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') {
isMobile, // TODO??
deckStore, }
drawerMenuShowing,
columns,
layout,
menuIndicated,
onContextmenu,
wallpaper: localStorage.getItem('wallpaper') != null,
post: os.post,
};
},
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -1,75 +1,62 @@
<template> <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> <template #header>
<i class="fas fa-satellite"></i><span style="margin-left: 8px;">{{ column.name }}</span> <i class="fas fa-satellite"></i><span style="margin-left: 8px;">{{ column.name }}</span>
</template> </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> </XColumn>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { onMounted } from 'vue';
import XColumn from './column.vue'; import XColumn from './column.vue';
import XTimeline from '@/components/timeline.vue'; import XTimeline from '@/components/timeline.vue';
import * as os from '@/os'; import * as os from '@/os';
import { updateColumn } from './deck-store'; import { updateColumn, Column } from './deck-store';
import { i18n } from '@/i18n';
export default defineComponent({ const props = defineProps<{
components: { column: Column;
XColumn, isStacked: boolean;
XTimeline, }>();
},
props: { const emit = defineEmits<{
column: { (e: 'loaded'): void;
type: Object, (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
required: true }>();
},
isStacked: { let timeline = $ref<InstanceType<typeof XTimeline>>();
type: Boolean,
required: true onMounted(() => {
if (props.column.antennaId == null) {
setAntenna();
} }
}, });
data() { async function setAntenna() {
return {
};
},
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 antennas = await os.api('antennas/list');
const { canceled, result: antenna } = await os.select({ const { canceled, result: antenna } = await os.select({
title: this.$ts.selectAntenna, title: i18n.ts.selectAntenna,
items: antennas.map(x => ({ items: antennas.map(x => ({
value: x, text: x.name value: x, text: x.name
})), })),
default: this.column.antennaId default: props.column.antennaId
}); });
if (canceled) return; if (canceled) return;
updateColumn(this.column.id, { updateColumn(props.column.id, {
antennaId: antenna.id antennaId: antenna.id
}); });
}, }
/*
function focus() {
timeline.focus();
}
focus() { defineExpose({
(this.$refs.timeline as any).focus(); focus,
}
}
}); });
*/
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -1,17 +1,18 @@
<template> <template>
<!-- TODO: リファクタの余地がありそう --> <!-- TODO: リファクタの余地がありそう -->
<XMainColumn v-if="column.type === 'main'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> <div v-if="!column">たぶん見えちゃいけないやつ</div>
<XWidgetsColumn v-else-if="column.type === 'widgets'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> <XMainColumn v-else-if="column.type === 'main'" :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)"/> <XWidgetsColumn v-else-if="column.type === 'widgets'" :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)"/> <XNotificationsColumn v-else-if="column.type === 'notifications'" :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)"/> <XTlColumn v-else-if="column.type === 'tl'" :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)"/> <XListColumn v-else-if="column.type === 'list'" :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)"/> <XAntennaColumn v-else-if="column.type === 'antenna'" :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)"/> <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> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { } from 'vue';
import XMainColumn from './main-column.vue'; import XMainColumn from './main-column.vue';
import XTlColumn from './tl-column.vue'; import XTlColumn from './tl-column.vue';
import XAntennaColumn from './antenna-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 XWidgetsColumn from './widgets-column.vue';
import XMentionsColumn from './mentions-column.vue'; import XMentionsColumn from './mentions-column.vue';
import XDirectColumn from './direct-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({ 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: { methods: {
focus() { focus() {
this.$children[0].focus(); this.$children[0].focus();
} }
} }
}); });
*/
</script> </script>

View file

@ -31,211 +31,186 @@
</template> </template>
<script lang="ts"> <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 * 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 { deckStore } from './deck-store';
import { i18n } from '@/i18n';
export default defineComponent({ provide('shouldHeaderThin', true);
provide: { provide('shouldOmitHeaderTitle', true);
shouldHeaderThin: true,
shouldOmitHeaderTitle: true,
},
props: { const props = withDefaults(defineProps<{
column: { column: Column;
type: Object, isStacked?: boolean;
required: false, func?: DeckFunc | null;
default: null naked?: boolean;
}, indicated?: boolean;
isStacked: { }>(), {
type: Boolean, isStacked: false,
required: false, func: null,
default: false naked: false,
}, indicated: 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 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 = [{ const items = [{
icon: 'fas fa-pencil-alt', icon: 'fas fa-pencil-alt',
text: this.$ts.edit, text: i18n.ts.edit,
action: async () => { action: async () => {
const { canceled, result } = await os.form(this.column.name, { const { canceled, result } = await os.form(props.column.name, {
name: { name: {
type: 'string', type: 'string',
label: this.$ts.name, label: i18n.ts.name,
default: this.column.name default: props.column.name
}, },
width: { width: {
type: 'number', type: 'number',
label: this.$ts.width, label: i18n.ts.width,
default: this.column.width default: props.column.width
}, },
flexible: { flexible: {
type: 'boolean', type: 'boolean',
label: this.$ts.flexible, label: i18n.ts.flexible,
default: this.column.flexible default: props.column.flexible
} }
}); });
if (canceled) return; if (canceled) return;
updateColumn(this.column.id, result); updateColumn(props.column.id, result);
} }
}, null, { }, null, {
icon: 'fas fa-arrow-left', icon: 'fas fa-arrow-left',
text: this.$ts._deck.swapLeft, text: i18n.ts._deck.swapLeft,
action: () => { action: () => {
swapLeftColumn(this.column.id); swapLeftColumn(props.column.id);
} }
}, { }, {
icon: 'fas fa-arrow-right', icon: 'fas fa-arrow-right',
text: this.$ts._deck.swapRight, text: i18n.ts._deck.swapRight,
action: () => { action: () => {
swapRightColumn(this.column.id); swapRightColumn(props.column.id);
} }
}, this.isStacked ? { }, props.isStacked ? {
icon: 'fas fa-arrow-up', icon: 'fas fa-arrow-up',
text: this.$ts._deck.swapUp, text: i18n.ts._deck.swapUp,
action: () => { action: () => {
swapUpColumn(this.column.id); swapUpColumn(props.column.id);
} }
} : undefined, this.isStacked ? { } : undefined, props.isStacked ? {
icon: 'fas fa-arrow-down', icon: 'fas fa-arrow-down',
text: this.$ts._deck.swapDown, text: i18n.ts._deck.swapDown,
action: () => { action: () => {
swapDownColumn(this.column.id); swapDownColumn(props.column.id);
} }
} : undefined, null, { } : undefined, null, {
icon: 'fas fa-window-restore', icon: 'fas fa-window-restore',
text: this.$ts._deck.stackLeft, text: i18n.ts._deck.stackLeft,
action: () => { action: () => {
stackLeftColumn(this.column.id); stackLeftColumn(props.column.id);
} }
}, this.isStacked ? { }, props.isStacked ? {
icon: 'fas fa-window-maximize', icon: 'fas fa-window-maximize',
text: this.$ts._deck.popRight, text: i18n.ts._deck.popRight,
action: () => { action: () => {
popRightColumn(this.column.id); popRightColumn(props.column.id);
} }
} : undefined, null, { } : undefined, null, {
icon: 'fas fa-trash-alt', icon: 'fas fa-trash-alt',
text: this.$ts.remove, text: i18n.ts.remove,
danger: true, danger: true,
action: () => { action: () => {
removeColumn(this.column.id); removeColumn(props.column.id);
} }
}]; }];
return items; return items;
}, }
onContextmenu(ev: MouseEvent) { function onContextmenu(ev: MouseEvent) {
os.contextMenu(this.getMenu(), ev); os.contextMenu(getMenu(), ev);
}, }
goTop() { function goTop() {
this.$refs.body.scrollTo({ body.scrollTo({
top: 0, top: 0,
behavior: 'smooth' behavior: 'smooth'
}); });
}, }
onDragstart(e) { function onDragstart(e) {
e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, this.column.id); e.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, props.column.id);
// ChromeDragstartDOM(=)Drag // ChromeDragstartDOM(=)Drag
// SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately // SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately
window.setTimeout(() => { window.setTimeout(() => {
this.dragging = true; dragging = true;
}, 10); }, 10);
}, }
onDragend(e) { function onDragend(e) {
this.dragging = false; dragging = false;
}, }
onDragover(e) { function onDragover(e) {
// //
if (this.dragging) { if (dragging) {
// //
e.dataTransfer.dropEffect = 'none'; e.dataTransfer.dropEffect = 'none';
return; return;
@ -245,24 +220,22 @@ export default defineComponent({
e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none';
if (!this.dragging && isDeckColumn) this.draghover = true; if (!dragging && isDeckColumn) draghover = true;
}, }
onDragleave() { function onDragleave() {
this.draghover = false; draghover = false;
}, }
onDrop(e) { function onDrop(e) {
this.draghover = false; draghover = false;
os.deckGlobalEvents.emit('column.dragEnd'); os.deckGlobalEvents.emit('column.dragEnd');
const id = e.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_); const id = e.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_);
if (id != null && id != '') { if (id != null && id != '') {
swapColumn(this.column.id, id); swapColumn(props.column.id, id);
} }
} }
}
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -1,8 +1,9 @@
import { throttle } from 'throttle-debounce'; import { throttle } from 'throttle-debounce';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { api } from '@/os'; import { api } from '@/os';
import { markRaw, watch } from 'vue'; import { markRaw } from 'vue';
import { Storage } from '../../pizzax'; import { Storage } from '../../pizzax';
import { notificationTypes } from 'misskey-js';
type ColumnWidget = { type ColumnWidget = {
name: string; name: string;
@ -10,13 +11,18 @@ type ColumnWidget = {
data: Record<string, any>; data: Record<string, any>;
}; };
type Column = { export type Column = {
id: string; id: string;
type: string; type: string;
name: string | null; name: string | null;
width: number; width: number;
widgets?: ColumnWidget[]; widgets?: ColumnWidget[];
active?: boolean; active?: boolean;
flexible?: boolean;
antennaId?: string;
listId?: string;
includingTypes?: typeof notificationTypes[number][];
tl?: 'home' | 'local' | 'social' | 'global';
}; };
function copy<T>(x: T): T { function copy<T>(x: T): T {

View file

@ -1,5 +1,5 @@
<template> <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> <template #header><i class="fas fa-envelope" style="margin-right: 8px;"></i>{{ column.name }}</template>
<XNotes :pagination="pagination"/> <XNotes :pagination="pagination"/>
@ -7,21 +7,25 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { } from 'vue';
import XColumn from './column.vue'; import XColumn from './column.vue';
import XNotes from '@/components/notes.vue'; import XNotes from '@/components/notes.vue';
import * as os from '@/os'; import { Column } from './deck-store';
const props = defineProps<{ defineProps<{
column: Record<string, unknown>; // TODO column: Column;
isStacked: boolean; isStacked: boolean;
}>(); }>();
const emit = defineEmits<{
(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
}>();
const pagination = { const pagination = {
point: 'notes/mentions' as const, endpoint: 'notes/mentions' as const,
limit: 10, limit: 10,
params: computed(() => ({ params: {
visibility: 'specified' as const, visibility: 'specified'
})), },
}; };
</script> </script>

View file

@ -1,75 +1,65 @@
<template> <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> <template #header>
<i class="fas fa-list-ul"></i><span style="margin-left: 8px;">{{ column.name }}</span> <i class="fas fa-list-ul"></i><span style="margin-left: 8px;">{{ column.name }}</span>
</template> </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> </XColumn>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { } from 'vue';
import XColumn from './column.vue'; import XColumn from './column.vue';
import XTimeline from '@/components/timeline.vue'; import XTimeline from '@/components/timeline.vue';
import * as os from '@/os'; 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({ export default defineComponent({
components: {
XColumn,
XTimeline,
},
props: {
column: {
type: Object,
required: true
},
isStacked: {
type: Boolean,
required: true
}
},
data() {
return {
};
},
watch: { watch: {
mediaOnly() { mediaOnly() {
(this.$refs.timeline as any).reload(); (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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -1,5 +1,5 @@
<template> <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 #header>
<template v-if="pageInfo"> <template v-if="pageInfo">
<i :class="pageInfo.icon"></i> <i :class="pageInfo.icon"></i>
@ -20,72 +20,59 @@
</XColumn> </XColumn>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { } from 'vue';
import XColumn from './column.vue'; import XColumn from './column.vue';
import XNotes from '@/components/notes.vue'; import { deckStore, Column } from '@/ui/deck/deck-store';
import { deckStore } from '@/ui/deck/deck-store';
import * as os from '@/os'; import * as os from '@/os';
import * as symbols from '@/symbols'; import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
import { router } from '@/router';
export default defineComponent({ defineProps<{
components: { column: Column;
XColumn, isStacked: boolean;
XNotes }>();
},
props: { const emit = defineEmits<{
column: { (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
type: Object, }>();
required: true
},
isStacked: {
type: Boolean,
required: true
}
},
data() { let pageInfo = $ref<Record<string, any> | null>(null);
return {
deckStore,
pageInfo: null,
}
},
methods: { function changePage(page) {
changePage(page) {
if (page == null) return; if (page == null) return;
if (page[symbols.PAGE_INFO]) { if (page[symbols.PAGE_INFO]) {
this.pageInfo = page[symbols.PAGE_INFO]; pageInfo = page[symbols.PAGE_INFO];
} }
}, }
/*
back() { function back() {
history.back(); history.back();
}, }
*/
function onContextmenu(ev: MouseEvent) {
if (!ev.target) return;
onContextmenu(ev: MouseEvent) {
const isLink = (el: HTMLElement) => { const isLink = (el: HTMLElement) => {
if (el.tagName === 'A') return true; if (el.tagName === 'A') return true;
if (el.parentElement) { if (el.parentElement) {
return isLink(el.parentElement); return isLink(el.parentElement);
} }
}; };
if (isLink(ev.target)) return; if (isLink(ev.target as HTMLElement)) return;
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) 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; if (window.getSelection()?.toString() !== '') return;
const path = this.$route.path; const path = router.currentRoute.value.path;
os.contextMenu([{ os.contextMenu([{
type: 'label', type: 'label',
text: path, text: path,
}, { }, {
icon: 'fas fa-window-maximize', icon: 'fas fa-window-maximize',
text: this.$ts.openInWindow, text: i18n.ts.openInWindow,
action: () => { action: () => {
os.pageWindow(path); os.pageWindow(path);
} }
}], ev); }], ev);
},
} }
});
</script> </script>

View file

@ -1,5 +1,5 @@
<template> <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> <template #header><i class="fas fa-at" style="margin-right: 8px;"></i>{{ column.name }}</template>
<XNotes :pagination="pagination"/> <XNotes :pagination="pagination"/>
@ -10,13 +10,17 @@
import { } from 'vue'; import { } from 'vue';
import XColumn from './column.vue'; import XColumn from './column.vue';
import XNotes from '@/components/notes.vue'; import XNotes from '@/components/notes.vue';
import * as os from '@/os'; import { Column } from './deck-store';
const props = defineProps<{ defineProps<{
column: Record<string, unknown>; // TODO column: Column;
isStacked: boolean; isStacked: boolean;
}>(); }>();
const emit = defineEmits<{
(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
}>();
const pagination = { const pagination = {
endpoint: 'notes/mentions' as const, endpoint: 'notes/mentions' as const,
limit: 10, limit: 10,

View file

@ -1,53 +1,38 @@
<template> <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> <template #header><i class="fas fa-bell" style="margin-right: 8px;"></i>{{ column.name }}</template>
<XNotifications :include-types="column.includingTypes"/> <XNotifications :include-types="column.includingTypes"/>
</XColumn> </XColumn>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { } from 'vue';
import XColumn from './column.vue'; import XColumn from './column.vue';
import XNotifications from '@/components/notifications.vue'; import XNotifications from '@/components/notifications.vue';
import * as os from '@/os'; import * as os from '@/os';
import { updateColumn } from './deck-store'; import { updateColumn } from './deck-store';
import { Column } from './deck-store';
export default defineComponent({ const props = defineProps<{
components: { column: Column;
XColumn, isStacked: boolean;
XNotifications }>();
},
props: { const emit = defineEmits<{
column: { (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
type: Object, }>();
required: true
},
isStacked: {
type: Boolean,
required: true
}
},
data() { function func() {
return {
}
},
methods: {
func() {
os.popup(import('@/components/notification-setting-window.vue'), { os.popup(import('@/components/notification-setting-window.vue'), {
includingTypes: this.column.includingTypes, includingTypes: props.column.includingTypes,
}, { }, {
done: async (res) => { done: async (res) => {
const { includingTypes } = res; const { includingTypes } = res;
updateColumn(this.column.id, { updateColumn(props.column.id, {
includingTypes: includingTypes includingTypes: includingTypes
}); });
}, },
}, 'closed'); }, 'closed');
} }
}
});
</script> </script>

View file

@ -1,5 +1,5 @@
<template> <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> <template #header>
<i v-if="column.tl === 'home'" class="fas fa-home"></i> <i v-if="column.tl === 'home'" class="fas fa-home"></i>
<i v-else-if="column.tl === 'local'" class="fas fa-comments"></i> <i v-else-if="column.tl === 'local'" class="fas fa-comments"></i>
@ -15,108 +15,103 @@
</p> </p>
<p class="desc">{{ $t('disabled-timeline.description') }}</p> <p class="desc">{{ $t('disabled-timeline.description') }}</p>
</div> </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> </XColumn>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { onMounted } from 'vue';
import XColumn from './column.vue'; import XColumn from './column.vue';
import XTimeline from '@/components/timeline.vue'; import XTimeline from '@/components/timeline.vue';
import * as os from '@/os'; 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({ const props = defineProps<{
components: { column: Column;
XColumn, isStacked: boolean;
XTimeline, }>();
},
props: { const emit = defineEmits<{
column: { (e: 'loaded'): void;
type: Object, (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
required: true }>();
},
isStacked: { let disabled = $ref(false);
type: Boolean, let indicated = $ref(false);
required: true 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));
} }
}, });
data() { async function setType() {
return { const { canceled, result: src } = await os.select({
disabled: false, title: i18n.ts.timeline,
indicated: false, items: [{
columnActive: true, 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
});
}
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: { watch: {
mediaOnly() { mediaOnly() {
(this.$refs.timeline as any).reload(); (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: { 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() { focus() {
(this.$refs.timeline as any).focus(); (this.$refs.timeline as any).focus();
} }
} }
}); });
*/
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -1,64 +1,49 @@
<template> <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> <template #header><i class="fas fa-window-maximize" style="margin-right: 8px;"></i>{{ column.name }}</template>
<div class="wtdtxvec"> <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> </div>
</XColumn> </XColumn>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, defineAsyncComponent } from 'vue'; import { } from 'vue';
import XWidgets from '@/components/widgets.vue'; import XWidgets from '@/components/widgets.vue';
import XColumn from './column.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({ const props = defineProps<{
components: { column: Column;
XColumn, isStacked: boolean;
XWidgets, }>();
},
props: { const emit = defineEmits<{
column: { (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
type: Object, }>();
required: true,
},
isStacked: {
type: Boolean,
required: true,
},
},
data() { let edit = $ref(false);
return {
edit: false,
};
},
methods: { function addWidget(widget) {
addWidget(widget) { addColumnWidget(props.column.id, widget);
addColumnWidget(this.column.id, widget);
},
removeWidget(widget) {
removeColumnWidget(this.column.id, widget);
},
updateWidget({ id, data }) {
updateColumnWidget(this.column.id, id, data);
},
updateWidgets(widgets) {
setColumnWidgets(this.column.id, widgets);
},
func() {
this.edit = !this.edit;
} }
function removeWidget(widget) {
removeColumnWidget(props.column.id, widget);
}
function updateWidget({ id, data }) {
updateColumnWidget(props.column.id, id, data);
}
function updateWidgets(widgets) {
setColumnWidgets(props.column.id, widgets);
}
function func() {
edit = !edit;
} }
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -1,58 +1,50 @@
<template> <template>
<div class="efzpzdvf"> <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-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> {{ $ts.editWidgets }}</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> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, defineAsyncComponent } from 'vue'; import { onMounted } from 'vue';
import XWidgets from '@/components/widgets.vue'; import XWidgets from '@/components/widgets.vue';
import * as os from '@/os'; import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
export default defineComponent({ const emit = defineEmits<{
components: { (e: 'mounted', el: Element): void;
XWidgets }>();
},
emits: ['mounted'], let editMode = $ref(false);
let rootEl = $ref<HTMLDivElement>();
data() { onMounted(() => {
return { emit('mounted', rootEl);
editMode: false, });
};
},
mounted() { function addWidget(widget) {
this.$emit('mounted', this.$el); defaultStore.set('widgets', [{
},
methods: {
addWidget(widget) {
this.$store.set('widgets', [{
...widget, ...widget,
place: null, place: null,
}, ...this.$store.state.widgets]); }, ...defaultStore.state.widgets]);
}, }
removeWidget(widget) { function removeWidget(widget) {
this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id != widget.id)); defaultStore.set('widgets', defaultStore.state.widgets.filter(w => w.id != widget.id));
}, }
updateWidget({ id, data }) { function updateWidget({ id, data }) {
this.$store.set('widgets', this.$store.state.widgets.map(w => w.id === id ? { defaultStore.set('widgets', defaultStore.state.widgets.map(w => w.id === id ? {
...w, ...w,
data: data data: data
} : w)); } : w));
}, }
updateWidgets(widgets) { function updateWidgets(widgets) {
this.$store.set('widgets', widgets); defaultStore.set('widgets', widgets);
} }
}
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>