forked from FoundKeyGang/FoundKey
Remove Deck UI
I'm not sure this even makes sense, I was just trying to remove more code from the client.
This commit is contained in:
parent
f53ff2089b
commit
045c9bf088
18 changed files with 0 additions and 1551 deletions
|
@ -171,7 +171,6 @@ import { getAccountFromId } from '@/scripts/get-account-from-id';
|
||||||
const app = createApp(
|
const app = createApp(
|
||||||
window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) :
|
window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) :
|
||||||
!$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) :
|
!$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) :
|
||||||
ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) :
|
|
||||||
ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) :
|
ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) :
|
||||||
defineAsyncComponent(() => import('@/ui/universal.vue')),
|
defineAsyncComponent(() => import('@/ui/universal.vue')),
|
||||||
);
|
);
|
||||||
|
|
|
@ -169,13 +169,6 @@ export const menuDef = reactive({
|
||||||
localStorage.setItem('ui', 'default');
|
localStorage.setItem('ui', 'default');
|
||||||
unisonReload();
|
unisonReload();
|
||||||
},
|
},
|
||||||
}, {
|
|
||||||
text: i18n.ts.deck,
|
|
||||||
active: ui === 'deck',
|
|
||||||
action: () => {
|
|
||||||
localStorage.setItem('ui', 'deck');
|
|
||||||
unisonReload();
|
|
||||||
},
|
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts.classic,
|
text: i18n.ts.classic,
|
||||||
active: ui === 'classic',
|
active: ui === 'classic',
|
||||||
|
|
|
@ -546,8 +546,6 @@ export function post(props: Record<string, any> = {}) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deckGlobalEvents = new EventEmitter();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
export function checkExistence(fileData: ArrayBuffer): Promise<any> {
|
export function checkExistence(fileData: ArrayBuffer): Promise<any> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="_formRoot">
|
|
||||||
<FormSwitch v-model="navWindow">{{ i18n.ts.defaultNavigationBehaviour }}: {{ i18n.ts.openInWindow }}</FormSwitch>
|
|
||||||
|
|
||||||
<FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ i18n.ts._deck.alwaysShowMainColumn }}</FormSwitch>
|
|
||||||
|
|
||||||
<FormRadios v-model="columnAlign" class="_formBlock">
|
|
||||||
<template #label>{{ i18n.ts._deck.columnAlign }}</template>
|
|
||||||
<option value="left">{{ i18n.ts.left }}</option>
|
|
||||||
<option value="center">{{ i18n.ts.center }}</option>
|
|
||||||
</FormRadios>
|
|
||||||
|
|
||||||
<FormRadios v-model="columnHeaderHeight" class="_formBlock">
|
|
||||||
<template #label>{{ i18n.ts._deck.columnHeaderHeight }}</template>
|
|
||||||
<option :value="42">{{ i18n.ts.narrow }}</option>
|
|
||||||
<option :value="45">{{ i18n.ts.medium }}</option>
|
|
||||||
<option :value="48">{{ i18n.ts.wide }}</option>
|
|
||||||
</FormRadios>
|
|
||||||
|
|
||||||
<FormInput v-model="columnMargin" type="number" class="_formBlock">
|
|
||||||
<template #label>{{ i18n.ts._deck.columnMargin }}</template>
|
|
||||||
<template #suffix>px</template>
|
|
||||||
</FormInput>
|
|
||||||
|
|
||||||
<FormLink class="_formBlock" @click="setProfile">{{ i18n.ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, watch } from 'vue';
|
|
||||||
import FormSwitch from '@/components/form/switch.vue';
|
|
||||||
import FormLink from '@/components/form/link.vue';
|
|
||||||
import FormRadios from '@/components/form/radios.vue';
|
|
||||||
import FormInput from '@/components/form/input.vue';
|
|
||||||
import { deckStore } from '@/ui/deck/deck-store';
|
|
||||||
import * as os from '@/os';
|
|
||||||
import { unisonReload } from '@/scripts/unison-reload';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
|
||||||
|
|
||||||
const navWindow = computed(deckStore.makeGetterSetter('navWindow'));
|
|
||||||
const alwaysShowMainColumn = computed(deckStore.makeGetterSetter('alwaysShowMainColumn'));
|
|
||||||
const columnAlign = computed(deckStore.makeGetterSetter('columnAlign'));
|
|
||||||
const columnMargin = computed(deckStore.makeGetterSetter('columnMargin'));
|
|
||||||
const columnHeaderHeight = computed(deckStore.makeGetterSetter('columnHeaderHeight'));
|
|
||||||
const profile = computed(deckStore.makeGetterSetter('profile'));
|
|
||||||
|
|
||||||
watch(navWindow, async () => {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'info',
|
|
||||||
text: i18n.ts.reloadToApplySetting,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
unisonReload();
|
|
||||||
});
|
|
||||||
|
|
||||||
async function setProfile() {
|
|
||||||
const { canceled, result: name } = await os.inputText({
|
|
||||||
title: i18n.ts._deck.profile,
|
|
||||||
allowEmpty: false,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
profile.value = name;
|
|
||||||
unisonReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
definePageMetadata({
|
|
||||||
title: i18n.ts.deck,
|
|
||||||
icon: 'fas fa-columns',
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -87,8 +87,6 @@
|
||||||
<template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template>
|
<template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template>
|
||||||
</FormRange>
|
</FormRange>
|
||||||
|
|
||||||
<FormLink to="/settings/deck" class="_formBlock">{{ i18n.ts.deck }}</FormLink>
|
|
||||||
|
|
||||||
<FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ i18n.ts.customCss }}</FormLink>
|
<FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ i18n.ts.customCss }}</FormLink>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -217,7 +217,6 @@ const component = computed(() => {
|
||||||
case 'menu': return defineAsyncComponent(() => import('./menu.vue'));
|
case 'menu': return defineAsyncComponent(() => import('./menu.vue'));
|
||||||
case 'sounds': return defineAsyncComponent(() => import('./sounds.vue'));
|
case 'sounds': return defineAsyncComponent(() => import('./sounds.vue'));
|
||||||
case 'custom-css': return defineAsyncComponent(() => import('./custom-css.vue'));
|
case 'custom-css': return defineAsyncComponent(() => import('./custom-css.vue'));
|
||||||
case 'deck': return defineAsyncComponent(() => import('./deck.vue'));
|
|
||||||
case 'plugin': return defineAsyncComponent(() => import('./plugin.vue'));
|
case 'plugin': return defineAsyncComponent(() => import('./plugin.vue'));
|
||||||
case 'plugin/install': return defineAsyncComponent(() => import('./plugin.install.vue'));
|
case 'plugin/install': return defineAsyncComponent(() => import('./plugin.install.vue'));
|
||||||
case 'import-export': return defineAsyncComponent(() => import('./import-export.vue'));
|
case 'import-export': return defineAsyncComponent(() => import('./import-export.vue'));
|
||||||
|
|
|
@ -1,289 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="mk-deck" :class="[{ isMobile }, `${deckStore.reactiveState.columnAlign.value}`]" :style="{ '--deckMargin': deckStore.reactiveState.columnMargin.value + 'px' }"
|
|
||||||
>
|
|
||||||
<XSidebar v-if="!isMobile"/>
|
|
||||||
|
|
||||||
<template v-for="ids in layout">
|
|
||||||
<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
|
|
||||||
<section
|
|
||||||
v-if="ids.length > 1"
|
|
||||||
class="folder column"
|
|
||||||
:style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }"
|
|
||||||
>
|
|
||||||
<DeckColumnCore v-for="id in ids" :ref="id" :key="id" :column="columns.find(c => c.id === id)" :is-stacked="true" @parent-focus="moveFocus(id, $event)"/>
|
|
||||||
</section>
|
|
||||||
<DeckColumnCore
|
|
||||||
v-else
|
|
||||||
:ref="ids[0]"
|
|
||||||
:key="ids[0]"
|
|
||||||
class="column"
|
|
||||||
:column="columns.find(c => c.id === ids[0])"
|
|
||||||
: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)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<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 home _button" @click="mainRouter.push('/')"><i class="fas fa-home"></i></button>
|
|
||||||
<button class="button notifications _button" @click="mainRouter.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="os.post()"><i class="fas fa-pencil-alt"></i></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<transition :name="$store.state.animation ? 'menu-back' : ''">
|
|
||||||
<div
|
|
||||||
v-if="drawerMenuShowing"
|
|
||||||
class="menu-back _modalBg"
|
|
||||||
@click="drawerMenuShowing = false"
|
|
||||||
@touchstart.passive="drawerMenuShowing = false"
|
|
||||||
></div>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<transition :name="$store.state.animation ? 'menu' : ''">
|
|
||||||
<XDrawerMenu v-if="drawerMenuShowing" class="menu"/>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<XCommon/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, provide, ref, watch } from 'vue';
|
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import XCommon from './_common_/common.vue';
|
|
||||||
import { deckStore, addColumn as addColumnToStore, loadDeck } from './deck/deck-store';
|
|
||||||
import DeckColumnCore from '@/ui/deck/column-core.vue';
|
|
||||||
import XSidebar from '@/ui/_common_/sidebar.vue';
|
|
||||||
import XDrawerMenu from '@/ui/_common_/sidebar-for-mobile.vue';
|
|
||||||
import { getScrollContainer } from '@/scripts/scroll';
|
|
||||||
import * as os from '@/os';
|
|
||||||
import { menuDef } from '@/menu';
|
|
||||||
import { $i } from '@/account';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
import { mainRouter } from '@/router';
|
|
||||||
|
|
||||||
if (deckStore.state.navWindow) {
|
|
||||||
mainRouter.navHook = (path) => {
|
|
||||||
os.pageWindow(path);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMobile = ref(window.innerWidth <= 500);
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
isMobile.value = window.innerWidth <= 500;
|
|
||||||
});
|
|
||||||
|
|
||||||
const drawerMenuShowing = ref(false);
|
|
||||||
|
|
||||||
const route = 'TODO';
|
|
||||||
watch(route, () => {
|
|
||||||
drawerMenuShowing.value = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns = deckStore.reactiveState.columns;
|
|
||||||
const layout = deckStore.reactiveState.layout;
|
|
||||||
const menuIndicated = computed(() => {
|
|
||||||
if ($i == null) return false;
|
|
||||||
for (const def in menuDef) {
|
|
||||||
if (menuDef[def].indicated) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const addColumn = async (ev) => {
|
|
||||||
const columns = [
|
|
||||||
'main',
|
|
||||||
'widgets',
|
|
||||||
'notifications',
|
|
||||||
'tl',
|
|
||||||
'antenna',
|
|
||||||
'list',
|
|
||||||
'mentions',
|
|
||||||
'direct',
|
|
||||||
];
|
|
||||||
|
|
||||||
const { canceled, result: column } = await os.select({
|
|
||||||
title: i18n.ts._deck.addColumn,
|
|
||||||
items: columns.map(column => ({
|
|
||||||
value: column, text: i18n.t('_deck._columns.' + column),
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
addColumnToStore({
|
|
||||||
type: column,
|
|
||||||
id: uuid(),
|
|
||||||
name: i18n.t('_deck._columns.' + column),
|
|
||||||
width: 330,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
provide('shouldSpacerMin', true);
|
|
||||||
|
|
||||||
document.documentElement.style.overflowY = 'hidden';
|
|
||||||
document.documentElement.style.scrollBehavior = 'auto';
|
|
||||||
window.addEventListener('wheel', (ev) => {
|
|
||||||
if (getScrollContainer(ev.target as HTMLElement) == null && ev.deltaX === 0) {
|
|
||||||
document.documentElement.scrollLeft += ev.deltaY;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
loadDeck();
|
|
||||||
|
|
||||||
function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') {
|
|
||||||
// TODO??
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.menu-enter-active,
|
|
||||||
.menu-leave-active {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(0);
|
|
||||||
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
||||||
}
|
|
||||||
.menu-enter-from,
|
|
||||||
.menu-leave-active {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(-240px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-back-enter-active,
|
|
||||||
.menu-back-leave-active {
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
||||||
}
|
|
||||||
.menu-back-enter-from,
|
|
||||||
.menu-back-leave-active {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mk-deck {
|
|
||||||
$nav-hide-threshold: 650px; // TODO: どこかに集約したい
|
|
||||||
|
|
||||||
// TODO: ここではなくて、各カラムで自身の幅に応じて上書きするようにしたい
|
|
||||||
--margin: var(--marginHalf);
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
|
|
||||||
height: calc(var(--vh, 1vh) * 100);
|
|
||||||
box-sizing: border-box;
|
|
||||||
flex: 1;
|
|
||||||
padding: var(--deckMargin);
|
|
||||||
|
|
||||||
&.center {
|
|
||||||
> .column:first-of-type {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .column:last-of-type {
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.isMobile {
|
|
||||||
padding-bottom: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .column {
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-right: var(--deckMargin);
|
|
||||||
|
|
||||||
&.folder {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
> *:not(:last-child) {
|
|
||||||
margin-bottom: var(--deckMargin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .buttons {
|
|
||||||
position: fixed;
|
|
||||||
z-index: 1000;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
padding: 16px;
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
> .button {
|
|
||||||
position: relative;
|
|
||||||
flex: 1;
|
|
||||||
padding: 0;
|
|
||||||
margin: auto;
|
|
||||||
height: 64px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--panel);
|
|
||||||
color: var(--fg);
|
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 400px) {
|
|
||||||
height: 60px;
|
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--X2);
|
|
||||||
}
|
|
||||||
|
|
||||||
> .indicator {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
color: var(--indicator);
|
|
||||||
font-size: 16px;
|
|
||||||
animation: blink 1s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> * {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
cursor: default;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .menu-back {
|
|
||||||
z-index: 1001;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .menu {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1001;
|
|
||||||
// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
|
|
||||||
height: calc(var(--vh, 1vh) * 100);
|
|
||||||
width: 240px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: auto;
|
|
||||||
overscroll-behavior: contain;
|
|
||||||
background: var(--bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,63 +0,0 @@
|
||||||
<template>
|
|
||||||
<XColumn :func="{ handler: setAntenna, title: i18n.ts.selectAntenna }" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
|
|
||||||
<template #header>
|
|
||||||
<i class="fas fa-satellite"></i><span style="margin-left: 8px;">{{ column.name }}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<XTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @after="() => emit('loaded')"/>
|
|
||||||
</XColumn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted } from 'vue';
|
|
||||||
import XColumn from './column.vue';
|
|
||||||
import { updateColumn, Column } from './deck-store';
|
|
||||||
import XTimeline from '@/components/timeline.vue';
|
|
||||||
import * as os from '@/os';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
column: Column;
|
|
||||||
isStacked: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'loaded'): void;
|
|
||||||
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let timeline = $ref<InstanceType<typeof XTimeline>>();
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (props.column.antennaId == null) {
|
|
||||||
setAntenna();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function setAntenna() {
|
|
||||||
const antennas = await os.api('antennas/list');
|
|
||||||
const { canceled, result: antenna } = await os.select({
|
|
||||||
title: i18n.ts.selectAntenna,
|
|
||||||
items: antennas.map(x => ({
|
|
||||||
value: x, text: x.name,
|
|
||||||
})),
|
|
||||||
default: props.column.antennaId,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
updateColumn(props.column.id, {
|
|
||||||
antennaId: antenna.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
function focus() {
|
|
||||||
timeline.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
focus,
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
</style>
|
|
|
@ -1,43 +0,0 @@
|
||||||
<template>
|
|
||||||
<!-- TODO: リファクタの余地がありそう -->
|
|
||||||
<div v-if="!column">たぶん見えちゃいけないやつ</div>
|
|
||||||
<XMainColumn v-else-if="column.type === 'main'" :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)"/>
|
|
||||||
<XNotificationsColumn v-else-if="column.type === 'notifications'" :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)"/>
|
|
||||||
<XListColumn v-else-if="column.type === 'list'" :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)"/>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import XMainColumn from './main-column.vue';
|
|
||||||
import XTlColumn from './tl-column.vue';
|
|
||||||
import XAntennaColumn from './antenna-column.vue';
|
|
||||||
import XListColumn from './list-column.vue';
|
|
||||||
import XNotificationsColumn from './notifications-column.vue';
|
|
||||||
import XWidgetsColumn from './widgets-column.vue';
|
|
||||||
import XMentionsColumn from './mentions-column.vue';
|
|
||||||
import XDirectColumn from './direct-column.vue';
|
|
||||||
import { Column } from './deck-store';
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
column?: Column;
|
|
||||||
isStacked: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
/*
|
|
||||||
export default defineComponent({
|
|
||||||
methods: {
|
|
||||||
focus() {
|
|
||||||
this.$children[0].focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
</script>
|
|
|
@ -1,377 +0,0 @@
|
||||||
<template>
|
|
||||||
<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
|
|
||||||
<section
|
|
||||||
v-hotkey="keymap" class="dnpfarvg _panel _narrow_"
|
|
||||||
:class="{ paged: isMainColumn, naked, active, isStacked, draghover, dragging, dropready }"
|
|
||||||
:style="{ '--deckColumnHeaderHeight': deckStore.reactiveState.columnHeaderHeight.value + 'px' }"
|
|
||||||
@dragover.prevent.stop="onDragover"
|
|
||||||
@dragleave="onDragleave"
|
|
||||||
@drop.prevent.stop="onDrop"
|
|
||||||
>
|
|
||||||
<header
|
|
||||||
:class="{ indicated }"
|
|
||||||
draggable="true"
|
|
||||||
@click="goTop"
|
|
||||||
@dragstart="onDragstart"
|
|
||||||
@dragend="onDragend"
|
|
||||||
>
|
|
||||||
<button v-if="isStacked && !isMainColumn" class="toggleActive _button" @click="toggleActive">
|
|
||||||
<template v-if="active"><i class="fas fa-angle-up"></i></template>
|
|
||||||
<template v-else><i class="fas fa-angle-down"></i></template>
|
|
||||||
</button>
|
|
||||||
<div class="action">
|
|
||||||
<slot name="action"></slot>
|
|
||||||
</div>
|
|
||||||
<span class="header"><slot name="header"></slot></span>
|
|
||||||
<button v-if="func" v-tooltip="func.title" class="menu _button" @click.stop="func.handler"><i :class="func.icon || 'fas fa-cog'"></i></button>
|
|
||||||
</header>
|
|
||||||
<div v-show="active" ref="body">
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export type DeckFunc = {
|
|
||||||
title: string;
|
|
||||||
handler: (payload: MouseEvent) => void;
|
|
||||||
icon?: string;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onBeforeUnmount, onMounted, provide, watch } from 'vue';
|
|
||||||
import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column , deckStore } from './deck-store';
|
|
||||||
|
|
||||||
import * as os from '@/os';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
|
|
||||||
provide('shouldHeaderThin', true);
|
|
||||||
provide('shouldOmitHeaderTitle', true);
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
|
||||||
column: Column;
|
|
||||||
isStacked?: boolean;
|
|
||||||
func?: DeckFunc | null;
|
|
||||||
naked?: boolean;
|
|
||||||
indicated?: boolean;
|
|
||||||
}>(), {
|
|
||||||
isStacked: false,
|
|
||||||
func: null,
|
|
||||||
naked: false,
|
|
||||||
indicated: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
|
|
||||||
(ev: '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 = [{
|
|
||||||
icon: 'fas fa-pencil-alt',
|
|
||||||
text: i18n.ts.edit,
|
|
||||||
action: async () => {
|
|
||||||
const { canceled, result } = await os.form(props.column.name, {
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
label: i18n.ts.name,
|
|
||||||
default: props.column.name,
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: 'number',
|
|
||||||
label: i18n.ts.width,
|
|
||||||
default: props.column.width,
|
|
||||||
},
|
|
||||||
flexible: {
|
|
||||||
type: 'boolean',
|
|
||||||
label: i18n.ts.flexible,
|
|
||||||
default: props.column.flexible,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
updateColumn(props.column.id, result);
|
|
||||||
},
|
|
||||||
}, null, {
|
|
||||||
icon: 'fas fa-arrow-left',
|
|
||||||
text: i18n.ts._deck.swapLeft,
|
|
||||||
action: () => {
|
|
||||||
swapLeftColumn(props.column.id);
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
icon: 'fas fa-arrow-right',
|
|
||||||
text: i18n.ts._deck.swapRight,
|
|
||||||
action: () => {
|
|
||||||
swapRightColumn(props.column.id);
|
|
||||||
},
|
|
||||||
}, props.isStacked ? {
|
|
||||||
icon: 'fas fa-arrow-up',
|
|
||||||
text: i18n.ts._deck.swapUp,
|
|
||||||
action: () => {
|
|
||||||
swapUpColumn(props.column.id);
|
|
||||||
},
|
|
||||||
} : undefined, props.isStacked ? {
|
|
||||||
icon: 'fas fa-arrow-down',
|
|
||||||
text: i18n.ts._deck.swapDown,
|
|
||||||
action: () => {
|
|
||||||
swapDownColumn(props.column.id);
|
|
||||||
},
|
|
||||||
} : undefined, null, {
|
|
||||||
icon: 'fas fa-window-restore',
|
|
||||||
text: i18n.ts._deck.stackLeft,
|
|
||||||
action: () => {
|
|
||||||
stackLeftColumn(props.column.id);
|
|
||||||
},
|
|
||||||
}, props.isStacked ? {
|
|
||||||
icon: 'fas fa-window-maximize',
|
|
||||||
text: i18n.ts._deck.popRight,
|
|
||||||
action: () => {
|
|
||||||
popRightColumn(props.column.id);
|
|
||||||
},
|
|
||||||
} : undefined, null, {
|
|
||||||
icon: 'fas fa-trash-alt',
|
|
||||||
text: i18n.ts.remove,
|
|
||||||
danger: true,
|
|
||||||
action: () => {
|
|
||||||
removeColumn(props.column.id);
|
|
||||||
},
|
|
||||||
}];
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
function goTop() {
|
|
||||||
body.scrollTo({
|
|
||||||
top: 0,
|
|
||||||
behavior: 'smooth',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDragstart(ev) {
|
|
||||||
ev.dataTransfer.effectAllowed = 'move';
|
|
||||||
ev.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, props.column.id);
|
|
||||||
|
|
||||||
// Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう
|
|
||||||
// SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately
|
|
||||||
window.setTimeout(() => {
|
|
||||||
dragging = true;
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDragend(ev) {
|
|
||||||
dragging = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDragover(ev) {
|
|
||||||
// 自分自身がドラッグされている場合
|
|
||||||
if (dragging) {
|
|
||||||
// 自分自身にはドロップさせない
|
|
||||||
ev.dataTransfer.dropEffect = 'none';
|
|
||||||
} else {
|
|
||||||
const isDeckColumn = ev.dataTransfer.types[0] === _DATA_TRANSFER_DECK_COLUMN_;
|
|
||||||
|
|
||||||
ev.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none';
|
|
||||||
|
|
||||||
if (isDeckColumn) draghover = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDragleave() {
|
|
||||||
draghover = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDrop(ev) {
|
|
||||||
draghover = false;
|
|
||||||
os.deckGlobalEvents.emit('column.dragEnd');
|
|
||||||
|
|
||||||
const id = ev.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_);
|
|
||||||
if (id != null && id !== '') {
|
|
||||||
swapColumn(props.column.id, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.dnpfarvg {
|
|
||||||
--root-margin: 10px;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
contain: content;
|
|
||||||
box-shadow: 0 0 8px 0 var(--shadow);
|
|
||||||
|
|
||||||
&.draghover {
|
|
||||||
box-shadow: 0 0 0 2px var(--focus);
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1000;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: var(--focus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.dragging {
|
|
||||||
box-shadow: 0 0 0 2px var(--focus);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.dropready {
|
|
||||||
* {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.active) {
|
|
||||||
flex-basis: var(--deckColumnHeaderHeight);
|
|
||||||
min-height: var(--deckColumnHeaderHeight);
|
|
||||||
|
|
||||||
> header.indicated {
|
|
||||||
box-shadow: 4px 0px var(--accent) inset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.naked {
|
|
||||||
background: var(--bg) !important;
|
|
||||||
-webkit-backdrop-filter: var(--blur, blur(10px));
|
|
||||||
backdrop-filter: var(--blur, blur(10px));
|
|
||||||
|
|
||||||
> header {
|
|
||||||
background: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
|
|
||||||
> button {
|
|
||||||
color: var(--fg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.paged {
|
|
||||||
background: var(--bg) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
> header {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
z-index: 2;
|
|
||||||
line-height: var(--deckColumnHeaderHeight);
|
|
||||||
height: var(--deckColumnHeaderHeight);
|
|
||||||
padding: 0 16px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
color: var(--panelHeaderFg);
|
|
||||||
background: var(--panelHeaderBg);
|
|
||||||
box-shadow: 0 1px 0 0 var(--panelHeaderDivider);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&, * {
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.indicated {
|
|
||||||
box-shadow: 0 3px 0 0 var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
> .header {
|
|
||||||
display: inline-block;
|
|
||||||
align-items: center;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
> span:only-of-type {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .toggleActive,
|
|
||||||
> .action > ::v-deep(*),
|
|
||||||
> .menu {
|
|
||||||
z-index: 1;
|
|
||||||
width: var(--deckColumnHeaderHeight);
|
|
||||||
line-height: var(--deckColumnHeaderHeight);
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--faceTextButton);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--faceTextButtonHover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
color: var(--faceTextButtonActive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .toggleActive, > .action {
|
|
||||||
margin-left: -16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .action {
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .action:empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .menu {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: -16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> div {
|
|
||||||
height: calc(100% - var(--deckColumnHeaderHeight));
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden; // Safari does not supports clip
|
|
||||||
overflow-x: clip;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,304 +0,0 @@
|
||||||
import { throttle } from 'throttle-debounce';
|
|
||||||
import { markRaw } from 'vue';
|
|
||||||
import { notificationTypes } from 'foundkey-js';
|
|
||||||
import { Storage } from '@/pizzax';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
import { api } from '@/os';
|
|
||||||
|
|
||||||
type ColumnWidget = {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
data: Record<string, any>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Column = {
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
name: string | null;
|
|
||||||
width: number;
|
|
||||||
widgets?: ColumnWidget[];
|
|
||||||
active?: boolean;
|
|
||||||
flexible?: boolean;
|
|
||||||
antennaId?: string;
|
|
||||||
listId?: string;
|
|
||||||
includingTypes?: typeof notificationTypes[number][];
|
|
||||||
tl?: 'home' | 'local' | 'social' | 'global';
|
|
||||||
};
|
|
||||||
|
|
||||||
function copy<T>(x: T): T {
|
|
||||||
return JSON.parse(JSON.stringify(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
export const deckStore = markRaw(new Storage('deck', {
|
|
||||||
profile: {
|
|
||||||
where: 'deviceAccount',
|
|
||||||
default: 'default',
|
|
||||||
},
|
|
||||||
columns: {
|
|
||||||
where: 'deviceAccount',
|
|
||||||
default: [] as Column[],
|
|
||||||
},
|
|
||||||
layout: {
|
|
||||||
where: 'deviceAccount',
|
|
||||||
default: [] as Column['id'][][],
|
|
||||||
},
|
|
||||||
columnAlign: {
|
|
||||||
where: 'deviceAccount',
|
|
||||||
default: 'left' as 'left' | 'right' | 'center',
|
|
||||||
},
|
|
||||||
alwaysShowMainColumn: {
|
|
||||||
where: 'deviceAccount',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
navWindow: {
|
|
||||||
where: 'deviceAccount',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
columnMargin: {
|
|
||||||
where: 'deviceAccount',
|
|
||||||
default: 16,
|
|
||||||
},
|
|
||||||
columnHeaderHeight: {
|
|
||||||
where: 'deviceAccount',
|
|
||||||
default: 42,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const loadDeck = async () => {
|
|
||||||
let deck;
|
|
||||||
|
|
||||||
try {
|
|
||||||
deck = await api('i/registry/get', {
|
|
||||||
scope: ['client', 'deck', 'profiles'],
|
|
||||||
key: deckStore.state.profile,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code === 'NO_SUCH_KEY') {
|
|
||||||
// 後方互換性のため
|
|
||||||
if (deckStore.state.profile === 'default') {
|
|
||||||
saveDeck();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deckStore.set('columns', [{
|
|
||||||
id: 'a',
|
|
||||||
type: 'main',
|
|
||||||
name: i18n.ts._deck._columns.main,
|
|
||||||
width: 350,
|
|
||||||
}, {
|
|
||||||
id: 'b',
|
|
||||||
type: 'notifications',
|
|
||||||
name: i18n.ts._deck._columns.notifications,
|
|
||||||
width: 330,
|
|
||||||
}]);
|
|
||||||
deckStore.set('layout', [['a'], ['b']]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
deckStore.set('columns', deck.columns);
|
|
||||||
deckStore.set('layout', deck.layout);
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: deckがloadされていない状態でsaveすると意図せず上書きが発生するので対策する
|
|
||||||
export const saveDeck = throttle(1000, () => {
|
|
||||||
api('i/registry/set', {
|
|
||||||
scope: ['client', 'deck', 'profiles'],
|
|
||||||
key: deckStore.state.profile,
|
|
||||||
value: {
|
|
||||||
columns: deckStore.reactiveState.columns.value,
|
|
||||||
layout: deckStore.reactiveState.layout.value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export function addColumn(column: Column) {
|
|
||||||
if (column.name === undefined) column.name = null;
|
|
||||||
deckStore.push('columns', column);
|
|
||||||
deckStore.push('layout', [column.id]);
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeColumn(id: Column['id']) {
|
|
||||||
deckStore.set('columns', deckStore.state.columns.filter(c => c.id !== id));
|
|
||||||
deckStore.set('layout', deckStore.state.layout
|
|
||||||
.map(ids => ids.filter(_id => _id !== id))
|
|
||||||
.filter(ids => ids.length > 0));
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function swapColumn(a: Column['id'], b: Column['id']) {
|
|
||||||
const aX = deckStore.state.layout.findIndex(ids => ids.indexOf(a) !== -1);
|
|
||||||
const aY = deckStore.state.layout[aX].findIndex(id => id === a);
|
|
||||||
const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) !== -1);
|
|
||||||
const bY = deckStore.state.layout[bX].findIndex(id => id === b);
|
|
||||||
const layout = copy(deckStore.state.layout);
|
|
||||||
layout[aX][aY] = b;
|
|
||||||
layout[bX][bY] = a;
|
|
||||||
deckStore.set('layout', layout);
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function swapLeftColumn(id: Column['id']) {
|
|
||||||
const layout = copy(deckStore.state.layout);
|
|
||||||
deckStore.state.layout.some((ids, i) => {
|
|
||||||
if (ids.includes(id)) {
|
|
||||||
const left = deckStore.state.layout[i - 1];
|
|
||||||
if (left) {
|
|
||||||
layout[i - 1] = deckStore.state.layout[i];
|
|
||||||
layout[i] = left;
|
|
||||||
deckStore.set('layout', layout);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function swapRightColumn(id: Column['id']) {
|
|
||||||
const layout = copy(deckStore.state.layout);
|
|
||||||
deckStore.state.layout.some((ids, i) => {
|
|
||||||
if (ids.includes(id)) {
|
|
||||||
const right = deckStore.state.layout[i + 1];
|
|
||||||
if (right) {
|
|
||||||
layout[i + 1] = deckStore.state.layout[i];
|
|
||||||
layout[i] = right;
|
|
||||||
deckStore.set('layout', layout);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function swapUpColumn(id: Column['id']) {
|
|
||||||
const layout = copy(deckStore.state.layout);
|
|
||||||
const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id));
|
|
||||||
const ids = copy(deckStore.state.layout[idsIndex]);
|
|
||||||
ids.some((x, i) => {
|
|
||||||
if (x === id) {
|
|
||||||
const up = ids[i - 1];
|
|
||||||
if (up) {
|
|
||||||
ids[i - 1] = id;
|
|
||||||
ids[i] = up;
|
|
||||||
|
|
||||||
layout[idsIndex] = ids;
|
|
||||||
deckStore.set('layout', layout);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function swapDownColumn(id: Column['id']) {
|
|
||||||
const layout = copy(deckStore.state.layout);
|
|
||||||
const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id));
|
|
||||||
const ids = copy(deckStore.state.layout[idsIndex]);
|
|
||||||
ids.some((x, i) => {
|
|
||||||
if (x === id) {
|
|
||||||
const down = ids[i + 1];
|
|
||||||
if (down) {
|
|
||||||
ids[i + 1] = id;
|
|
||||||
ids[i] = down;
|
|
||||||
|
|
||||||
layout[idsIndex] = ids;
|
|
||||||
deckStore.set('layout', layout);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stackLeftColumn(id: Column['id']) {
|
|
||||||
let layout = copy(deckStore.state.layout);
|
|
||||||
const i = deckStore.state.layout.findIndex(ids => ids.includes(id));
|
|
||||||
layout = layout.map(ids => ids.filter(_id => _id !== id));
|
|
||||||
layout[i - 1].push(id);
|
|
||||||
layout = layout.filter(ids => ids.length > 0);
|
|
||||||
deckStore.set('layout', layout);
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function popRightColumn(id: Column['id']) {
|
|
||||||
let layout = copy(deckStore.state.layout);
|
|
||||||
const i = deckStore.state.layout.findIndex(ids => ids.includes(id));
|
|
||||||
const affected = layout[i];
|
|
||||||
layout = layout.map(ids => ids.filter(_id => _id !== id));
|
|
||||||
layout.splice(i + 1, 0, [id]);
|
|
||||||
layout = layout.filter(ids => ids.length > 0);
|
|
||||||
deckStore.set('layout', layout);
|
|
||||||
|
|
||||||
const columns = copy(deckStore.state.columns);
|
|
||||||
for (const column of columns) {
|
|
||||||
if (affected.includes(column.id)) {
|
|
||||||
column.active = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deckStore.set('columns', columns);
|
|
||||||
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
|
|
||||||
const columns = copy(deckStore.state.columns);
|
|
||||||
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
|
|
||||||
const column = copy(deckStore.state.columns[columnIndex]);
|
|
||||||
if (column == null) return;
|
|
||||||
if (column.widgets == null) column.widgets = [];
|
|
||||||
column.widgets.unshift(widget);
|
|
||||||
columns[columnIndex] = column;
|
|
||||||
deckStore.set('columns', columns);
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
|
|
||||||
const columns = copy(deckStore.state.columns);
|
|
||||||
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
|
|
||||||
const column = copy(deckStore.state.columns[columnIndex]);
|
|
||||||
if (column == null) return;
|
|
||||||
column.widgets = column.widgets.filter(w => w.id !== widget.id);
|
|
||||||
columns[columnIndex] = column;
|
|
||||||
deckStore.set('columns', columns);
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
|
|
||||||
const columns = copy(deckStore.state.columns);
|
|
||||||
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
|
|
||||||
const column = copy(deckStore.state.columns[columnIndex]);
|
|
||||||
if (column == null) return;
|
|
||||||
column.widgets = widgets;
|
|
||||||
columns[columnIndex] = column;
|
|
||||||
deckStore.set('columns', columns);
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) {
|
|
||||||
const columns = copy(deckStore.state.columns);
|
|
||||||
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
|
|
||||||
const column = copy(deckStore.state.columns[columnIndex]);
|
|
||||||
if (column == null) return;
|
|
||||||
column.widgets = column.widgets.map(w => w.id === widgetId ? {
|
|
||||||
...w,
|
|
||||||
data: widgetData,
|
|
||||||
} : w);
|
|
||||||
columns[columnIndex] = column;
|
|
||||||
deckStore.set('columns', columns);
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateColumn(id: Column['id'], column: Partial<Column>) {
|
|
||||||
const columns = copy(deckStore.state.columns);
|
|
||||||
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
|
|
||||||
const currentColumn = copy(deckStore.state.columns[columnIndex]);
|
|
||||||
if (currentColumn == null) return;
|
|
||||||
for (const [k, v] of Object.entries(column)) {
|
|
||||||
currentColumn[k] = v;
|
|
||||||
}
|
|
||||||
columns[columnIndex] = currentColumn;
|
|
||||||
deckStore.set('columns', columns);
|
|
||||||
saveDeck();
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
<template>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<XNotes :pagination="pagination"/>
|
|
||||||
</XColumn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import XColumn from './column.vue';
|
|
||||||
import { Column } from './deck-store';
|
|
||||||
import XNotes from '@/components/notes.vue';
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
column: Column;
|
|
||||||
isStacked: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const pagination = {
|
|
||||||
endpoint: 'notes/mentions' as const,
|
|
||||||
limit: 10,
|
|
||||||
params: {
|
|
||||||
visibility: 'specified',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -1,65 +0,0 @@
|
||||||
<template>
|
|
||||||
<XColumn :func="{ handler: setList, title: i18n.ts.selectList }" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
|
|
||||||
<template #header>
|
|
||||||
<i class="fas fa-list-ul"></i><span style="margin-left: 8px;">{{ column.name }}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<XTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" @after="() => emit('loaded')"/>
|
|
||||||
</XColumn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import XColumn from './column.vue';
|
|
||||||
import { updateColumn, Column } from './deck-store';
|
|
||||||
import XTimeline from '@/components/timeline.vue';
|
|
||||||
import * as os from '@/os';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
column: Column;
|
|
||||||
isStacked: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'loaded'): void;
|
|
||||||
(ev: '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({
|
|
||||||
watch: {
|
|
||||||
mediaOnly() {
|
|
||||||
(this.$refs.timeline as any).reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
</style>
|
|
|
@ -1,44 +0,0 @@
|
||||||
<template>
|
|
||||||
<XColumn v-if="deckStore.state.alwaysShowMainColumn || mainRouter.currentRoute.value.name !== 'index'" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
|
|
||||||
<template #header>
|
|
||||||
<template v-if="pageMetadata?.value">
|
|
||||||
<i :class="pageMetadata?.value.icon"></i>
|
|
||||||
{{ pageMetadata?.value.title }}
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<RouterView />
|
|
||||||
</XColumn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ComputedRef, provide } from 'vue';
|
|
||||||
import XColumn from './column.vue';
|
|
||||||
import { deckStore, Column } from '@/ui/deck/deck-store';
|
|
||||||
import * as os from '@/os';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
import { mainRouter } from '@/router';
|
|
||||||
import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
column: Column;
|
|
||||||
isStacked: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
|
|
||||||
|
|
||||||
provide('router', mainRouter);
|
|
||||||
provideMetadataReceiver((info) => {
|
|
||||||
pageMetadata = info;
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
function back() {
|
|
||||||
history.back();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
</script>
|
|
|
@ -1,27 +0,0 @@
|
||||||
<template>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<XNotes :pagination="pagination"/>
|
|
||||||
</XColumn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import XColumn from './column.vue';
|
|
||||||
import { Column } from './deck-store';
|
|
||||||
import XNotes from '@/components/notes.vue';
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
column: Column;
|
|
||||||
isStacked: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const pagination = {
|
|
||||||
endpoint: 'notes/mentions' as const,
|
|
||||||
limit: 10,
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -1,38 +0,0 @@
|
||||||
<template>
|
|
||||||
<XColumn :column="column" :is-stacked="isStacked" :func="{ handler: func, title: i18n.ts.notificationSetting }" @parent-focus="$event => emit('parent-focus', $event)">
|
|
||||||
<template #header><i class="fas fa-bell" style="margin-right: 8px;"></i>{{ column.name }}</template>
|
|
||||||
|
|
||||||
<XNotifications :include-types="column.includingTypes"/>
|
|
||||||
</XColumn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { defineAsyncComponent } from 'vue';
|
|
||||||
import XColumn from './column.vue';
|
|
||||||
import { updateColumn , Column } from './deck-store';
|
|
||||||
import XNotifications from '@/components/notifications.vue';
|
|
||||||
import * as os from '@/os';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
column: Column;
|
|
||||||
isStacked: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
function func() {
|
|
||||||
os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), {
|
|
||||||
includingTypes: props.column.includingTypes,
|
|
||||||
}, {
|
|
||||||
done: async (res) => {
|
|
||||||
const { includingTypes } = res;
|
|
||||||
updateColumn(props.column.id, {
|
|
||||||
includingTypes,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}, 'closed');
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,129 +0,0 @@
|
||||||
<template>
|
|
||||||
<XColumn :func="{ handler: setType, title: i18n.ts.timeline }" :column="column" :is-stacked="isStacked" :indicated="indicated" @change-active-state="onChangeActiveState" @parent-focus="$event => emit('parent-focus', $event)">
|
|
||||||
<template #header>
|
|
||||||
<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 === 'social'" class="fas fa-share-alt"></i>
|
|
||||||
<i v-else-if="column.tl === 'global'" class="fas fa-globe"></i>
|
|
||||||
<span style="margin-left: 8px;">{{ column.name }}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div v-if="disabled" class="iwaalbte">
|
|
||||||
<p>
|
|
||||||
<i class="fas fa-minus-circle"></i>
|
|
||||||
{{ i18n.t('disabled-timeline.title') }}
|
|
||||||
</p>
|
|
||||||
<p class="desc">{{ i18n.t('disabled-timeline.description') }}</p>
|
|
||||||
</div>
|
|
||||||
<XTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => emit('loaded')" @queue="queueUpdated" @note="onNote"/>
|
|
||||||
</XColumn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted } from 'vue';
|
|
||||||
import XColumn from './column.vue';
|
|
||||||
import { removeColumn, updateColumn, Column } from './deck-store';
|
|
||||||
import XTimeline from '@/components/timeline.vue';
|
|
||||||
import * as os from '@/os';
|
|
||||||
import { $i } from '@/account';
|
|
||||||
import { instance } from '@/instance';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
column: Column;
|
|
||||||
isStacked: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'loaded'): void;
|
|
||||||
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let disabled = $ref(false);
|
|
||||||
let indicated = $ref(false);
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function setType() {
|
|
||||||
const { canceled, result: src } = await os.select({
|
|
||||||
title: i18n.ts.timeline,
|
|
||||||
items: [{
|
|
||||||
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: {
|
|
||||||
mediaOnly() {
|
|
||||||
(this.$refs.timeline as any).reload();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
focus() {
|
|
||||||
(this.$refs.timeline as any).focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.iwaalbte {
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
> p {
|
|
||||||
margin: 16px;
|
|
||||||
|
|
||||||
&.desc {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,56 +0,0 @@
|
||||||
<template>
|
|
||||||
<XColumn :func="{ handler: func, title: i18n.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>
|
|
||||||
|
|
||||||
<div class="wtdtxvec">
|
|
||||||
<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" setup>
|
|
||||||
import XColumn from './column.vue';
|
|
||||||
import { addColumnWidget, Column, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store';
|
|
||||||
import XWidgets from '@/components/widgets.vue';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
column: Column;
|
|
||||||
isStacked: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let edit = $ref(false);
|
|
||||||
|
|
||||||
function addWidget(widget) {
|
|
||||||
addColumnWidget(props.column.id, widget);
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.wtdtxvec {
|
|
||||||
--margin: 8px;
|
|
||||||
--panelBorder: none;
|
|
||||||
|
|
||||||
padding: 0 var(--margin);
|
|
||||||
}
|
|
||||||
</style>
|
|
Loading…
Reference in a new issue