parent
af6dd4194f
commit
1163c85db6
9 changed files with 132 additions and 96 deletions
|
@ -1721,8 +1721,6 @@ _notification:
|
|||
_deck:
|
||||
alwaysShowMainColumn: "常にメインカラムを表示"
|
||||
columnAlign: "カラムの寄せ"
|
||||
columnMargin: "カラム間のマージン"
|
||||
columnHeaderHeight: "カラムのヘッダー幅"
|
||||
addColumn: "カラムを追加"
|
||||
swapLeft: "左に移動"
|
||||
swapRight: "右に移動"
|
||||
|
|
|
@ -10,18 +10,6 @@
|
|||
<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>
|
||||
|
@ -41,8 +29,6 @@ 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 () => {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<option value="small">{{ i18n.ts.small }}</option>
|
||||
<option value="medium">{{ i18n.ts.medium }}</option>
|
||||
<option value="large">{{ i18n.ts.large }}</option>
|
||||
<option value="veryLarge">{{ i18n.ts.large }}+</option>
|
||||
</FormRadios>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
codeString: '#ffb675',
|
||||
codeNumber: '#cfff9e',
|
||||
codeBoolean: '#c59eff',
|
||||
deckDivider: '#000',
|
||||
htmlThemeColor: '@bg',
|
||||
X2: ':darken<2<@panel',
|
||||
X3: 'rgba(255, 255, 255, 0.05)',
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
codeString: '#b98710',
|
||||
codeNumber: '#0fbbbb',
|
||||
codeBoolean: '#62b70c',
|
||||
deckDivider: ':darken<3<@bg',
|
||||
htmlThemeColor: '@bg',
|
||||
X2: ':darken<2<@panel',
|
||||
X3: 'rgba(0, 0, 0, 0.05)',
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
verySmall: defaultStore.reactiveState.statusbarSize.value === 'verySmall',
|
||||
small: defaultStore.reactiveState.statusbarSize.value === 'small',
|
||||
medium: defaultStore.reactiveState.statusbarSize.value === 'medium',
|
||||
large: defaultStore.reactiveState.statusbarSize.value === 'large'
|
||||
large: defaultStore.reactiveState.statusbarSize.value === 'large',
|
||||
veryLarge: defaultStore.reactiveState.statusbarSize.value === 'veryLarge',
|
||||
}"
|
||||
>
|
||||
<div v-for="x in defaultStore.reactiveState.statusbars.value" :key="x.id" class="item" :class="{ black: x.black }">
|
||||
|
@ -46,6 +47,11 @@ const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue')
|
|||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
&.veryLarge {
|
||||
--height: 30px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
> .item {
|
||||
display: inline-flex;
|
||||
vertical-align: bottom;
|
||||
|
|
|
@ -1,32 +1,37 @@
|
|||
<template>
|
||||
<div
|
||||
class="mk-deck" :class="[{ isMobile }, `${deckStore.reactiveState.columnAlign.value}`]" :style="{ '--deckMargin': deckStore.reactiveState.columnMargin.value + 'px' }"
|
||||
class="mk-deck" :class="[{ isMobile }]"
|
||||
>
|
||||
<XSidebar v-if="!isMobile"/>
|
||||
|
||||
<div class="main">
|
||||
<XStatusBars class="statusbars"/>
|
||||
<div ref="columnsEl" class="columns" @contextmenu.self.prevent="onContextmenu">
|
||||
<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 class="columnsWrapper">
|
||||
<div ref="columnsEl" class="columns" :class="deckStore.reactiveState.columnAlign.value" @contextmenu.self.prevent="onContextmenu">
|
||||
<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>
|
||||
<div class="sideMenu">
|
||||
<button class="_button button" @click="addColumn"><i class="fas fa-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -183,22 +188,14 @@ function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') {
|
|||
// TODO: ここではなくて、各カラムで自身の幅に応じて上書きするようにしたい
|
||||
--margin: var(--marginHalf);
|
||||
|
||||
--deckDividerThickness: 5px;
|
||||
|
||||
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;
|
||||
|
||||
&.center {
|
||||
> .column:first-of-type {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
> .column:last-of-type {
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.isMobile {
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
@ -209,24 +206,55 @@ function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .columns {
|
||||
display: flex;
|
||||
> .columnsWrapper {
|
||||
flex: 1;
|
||||
padding: var(--deckMargin);
|
||||
overflow-x: auto;
|
||||
overflow-y: clip;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
> .column {
|
||||
flex-shrink: 0;
|
||||
margin-right: var(--deckMargin);
|
||||
> .columns {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
overflow-y: clip;
|
||||
|
||||
&.folder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-bottom: var(--deckMargin);
|
||||
&.center {
|
||||
> .column:first-of-type {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
> .column:last-of-type {
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
> .column {
|
||||
flex-shrink: 0;
|
||||
border-right: solid var(--deckDividerThickness) var(--deckDivider);
|
||||
|
||||
&:first-child {
|
||||
border-left: solid var(--deckDividerThickness) var(--deckDivider);
|
||||
}
|
||||
|
||||
&.folder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> *:not(:last-child) {
|
||||
border-bottom: solid var(--deckDividerThickness) var(--deckDivider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .sideMenu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
|
||||
> .button {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<template>
|
||||
<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
|
||||
<section v-hotkey="keymap" class="dnpfarvg _panel _narrow_"
|
||||
<section
|
||||
v-hotkey="keymap" class="dnpfarvg _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 }"
|
||||
<header
|
||||
:class="{ indicated }"
|
||||
draggable="true"
|
||||
@click="goTop"
|
||||
@dragstart="onDragstart"
|
||||
|
@ -22,7 +23,7 @@
|
|||
<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>
|
||||
<button v-tooltip="i18n.ts.settings" class="menu _button" @click.stop="showSettingsMenu"><i class="fas fa-cog"></i></button>
|
||||
</header>
|
||||
<div v-show="active" ref="body">
|
||||
<slot></slot>
|
||||
|
@ -39,9 +40,8 @@ export type DeckFunc = {
|
|||
</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 { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column } from './deck-store';
|
||||
import { deckStore } from './deck-store';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
provide('shouldHeaderThin', true);
|
||||
|
@ -105,7 +105,7 @@ function onOtherDragEnd() {
|
|||
function toggleActive() {
|
||||
if (!props.isStacked) return;
|
||||
updateColumn(props.column.id, {
|
||||
active: !props.column.active
|
||||
active: !props.column.active,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -118,69 +118,83 @@ function getMenu() {
|
|||
name: {
|
||||
type: 'string',
|
||||
label: i18n.ts.name,
|
||||
default: props.column.name
|
||||
default: props.column.name,
|
||||
},
|
||||
width: {
|
||||
type: 'number',
|
||||
label: i18n.ts.width,
|
||||
default: props.column.width
|
||||
default: props.column.width,
|
||||
},
|
||||
flexible: {
|
||||
type: 'boolean',
|
||||
label: i18n.ts.flexible,
|
||||
default: props.column.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);
|
||||
}
|
||||
},
|
||||
}];
|
||||
|
||||
if (props.func) {
|
||||
items.unshift(null);
|
||||
items.unshift({
|
||||
icon: props.func.icon,
|
||||
text: props.func.title,
|
||||
action: props.func.handler,
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function showSettingsMenu(ev: MouseEvent) {
|
||||
os.popupMenu(getMenu(), ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
function onContextmenu(ev: MouseEvent) {
|
||||
os.contextMenu(getMenu(), ev);
|
||||
}
|
||||
|
@ -188,7 +202,7 @@ function onContextmenu(ev: MouseEvent) {
|
|||
function goTop() {
|
||||
body.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -239,15 +253,13 @@ function onDrop(ev) {
|
|||
<style lang="scss" scoped>
|
||||
.dnpfarvg {
|
||||
--root-margin: 10px;
|
||||
--deckColumnHeaderHeight: 42px;
|
||||
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
contain: content;
|
||||
box-shadow: 0 0 8px 0 var(--shadow);
|
||||
contain: strict;
|
||||
|
||||
&.draghover {
|
||||
box-shadow: 0 0 0 2px var(--focus);
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
|
@ -262,7 +274,18 @@ function onDrop(ev) {
|
|||
}
|
||||
|
||||
&.dragging {
|
||||
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);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&.dropready {
|
||||
|
|
|
@ -54,14 +54,6 @@ export const deckStore = markRaw(new Storage('deck', {
|
|||
where: 'deviceAccount',
|
||||
default: true,
|
||||
},
|
||||
columnMargin: {
|
||||
where: 'deviceAccount',
|
||||
default: 16,
|
||||
},
|
||||
columnHeaderHeight: {
|
||||
where: 'deviceAccount',
|
||||
default: 42,
|
||||
},
|
||||
}));
|
||||
|
||||
export const loadDeck = async () => {
|
||||
|
|
Loading…
Reference in a new issue