MisskeyDeck: カラムをスタックできるように

This commit is contained in:
syuilo
2018-06-08 04:21:06 +09:00
parent 44ef60c8a2
commit 79d592b431
9 changed files with 275 additions and 143 deletions

View File

@@ -0,0 +1,48 @@
<template>
<x-widgets-column v-if="column.type == 'widgets'"/>
<x-notifications-column v-else-if="column.type == 'notifications'"/>
<x-tl-column v-else-if="column.type == 'home'"/>
<x-tl-column v-else-if="column.type == 'local'"/>
<x-tl-column v-else-if="column.type == 'global'"/>
<x-tl-column v-else-if="column.type == 'list'"/>
</template>
<script lang="ts">
import Vue from 'vue';
import XTlColumn from './deck.tl-column.vue';
import XNotificationsColumn from './deck.notifications-column.vue';
import XWidgetsColumn from './deck.widgets-column.vue';
export default Vue.extend({
components: {
XTlColumn,
XNotificationsColumn,
XWidgetsColumn
},
props: {
column: {
type: Object,
required: true
},
isStacked: {
type: Boolean,
required: false,
default: false
},
isActive: {
type: Boolean,
required: false,
default: true
}
},
provide() {
return {
column: this.column,
isStacked: this.isStacked,
isActive: this.isActive
};
}
});
</script>

View File

@@ -1,10 +1,10 @@
<template>
<div class="dnpfarvgbnfmyzbdquhhzyxcmstpdqzs" :class="{ naked, narrow }">
<header :class="{ indicate }">
<div class="dnpfarvgbnfmyzbdquhhzyxcmstpdqzs" :class="{ naked, narrow, isActive, isStacked }">
<header :class="{ indicate }" @click="toggleActive">
<slot name="header"></slot>
<button ref="menu" @click="showMenu">%fa:caret-down%</button>
<button ref="menu" @click.stop="showMenu">%fa:caret-down%</button>
</header>
<div ref="body">
<div ref="body" v-show="isActive">
<slot></slot>
</div>
</div>
@@ -16,17 +16,14 @@ import Menu from '../../../../common/views/components/menu.vue';
export default Vue.extend({
props: {
id: {
type: String,
required: false
},
name: {
type: String,
required: false
},
menu: {
type: Array,
required: false
required: false,
default: null
},
naked: {
type: Boolean,
@@ -40,9 +37,17 @@ export default Vue.extend({
}
},
inject: {
column: { from: 'column' },
_isActive: { from: 'isActive' },
isStacked: { from: 'isStacked' },
getColumnVm: { from: 'getColumnVm' }
},
data() {
return {
indicate: false
indicate: false,
isActive: this._isActive
};
},
@@ -62,6 +67,13 @@ export default Vue.extend({
},
methods: {
toggleActive() {
if (!this.isStacked) return;
const vms = this.$store.state.settings.deck.layout.find(ids => ids.indexOf(this.column.id) != -1).map(id => this.getColumnVm(id));
if (this.isActive && vms.filter(vm => vm.$el.classList.contains('isActive')).length == 1) return;
this.isActive = !this.isActive;
},
isScrollTop() {
return this.$refs.body.scrollTop == 0;
},
@@ -86,23 +98,33 @@ export default Vue.extend({
default: this.name,
allowEmpty: false
}).then(name => {
this.$store.dispatch('settings/renameDeckColumn', { id: this.id, name });
this.$store.dispatch('settings/renameDeckColumn', { id: this.column.id, name });
});
}
}, null, {
content: '%fa:arrow-left% %i18n:common.deck.swap-left%',
onClick: () => {
this.$store.dispatch('settings/swapLeftDeckColumn', this.id);
this.$store.dispatch('settings/swapLeftDeckColumn', this.column.id);
}
}, {
content: '%fa:arrow-right% %i18n:common.deck.swap-right%',
onClick: () => {
this.$store.dispatch('settings/swapRightDeckColumn', this.id);
this.$store.dispatch('settings/swapRightDeckColumn', this.column.id);
}
}, null, {
content: '%fa:window-restore R% %i18n:common.deck.stack-left%',
onClick: () => {
this.$store.dispatch('settings/stackLeftDeckColumn', this.column.id);
}
}, {
content: '%fa:window-restore R% %i18n:common.deck.pop-right%',
onClick: () => {
this.$store.dispatch('settings/popRightDeckColumn', this.column.id);
}
}, null, {
content: '%fa:trash-alt R% %i18n:common.deck.remove%',
onClick: () => {
this.$store.dispatch('settings/removeDeckColumn', this.id);
this.$store.dispatch('settings/removeDeckColumn', this.column.id);
}
}];
@@ -128,14 +150,20 @@ root(isDark)
$header-height = 42px
width 330px
min-width 330px
height 100%
background isDark ? #282C37 : #fff
border-radius 6px
box-shadow 0 2px 16px rgba(#000, 0.1)
overflow hidden
&.narrow
&:not(.isActive)
flex-basis $header-height
min-height $header-height
&:not(.isStacked).narrow
width 285px
min-width 285px
&.naked
background rgba(#000, isDark ? 0.25 : 0.1)
@@ -157,6 +185,9 @@ root(isDark)
background isDark ? #313543 : #fff
box-shadow 0 1px rgba(#000, 0.15)
&, *
user-select none
&.indicate
box-shadow 0 3px 0 0 $theme-color

View File

@@ -1,11 +1,9 @@
<template>
<div>
<x-column :id="column.id" :name="name">
<span slot="header">%fa:bell R%{{ name }}</span>
<x-column :name="name">
<span slot="header">%fa:bell R%{{ name }}</span>
<x-notifications/>
</x-column>
</div>
<x-notifications/>
</x-column>
</template>
<script lang="ts">
@@ -19,12 +17,7 @@ export default Vue.extend({
XNotifications
},
props: {
column: {
type: Object,
required: true
}
},
inject: ['column'],
computed: {
name(): string {

View File

@@ -1,22 +1,20 @@
<template>
<div>
<x-column :id="column.id" :menu="menu" :name="name">
<span slot="header">
<template v-if="column.type == 'home'">%fa:home%</template>
<template v-if="column.type == 'local'">%fa:R comments%</template>
<template v-if="column.type == 'global'">%fa:globe%</template>
<template v-if="column.type == 'list'">%fa:list%</template>
<span>{{ name }}</span>
</span>
<x-column :menu="menu" :name="name">
<span slot="header">
<template v-if="column.type == 'home'">%fa:home%</template>
<template v-if="column.type == 'local'">%fa:R comments%</template>
<template v-if="column.type == 'global'">%fa:globe%</template>
<template v-if="column.type == 'list'">%fa:list%</template>
<span>{{ name }}</span>
</span>
<div class="editor" v-if="edit">
<mk-switch v-model="column.isMediaOnly" @change="onChangeSettings" text="%i18n:@is-media-only%"/>
<mk-switch v-model="column.isMediaView" @change="onChangeSettings" text="%i18n:@is-media-view%"/>
</div>
<x-list-tl v-if="column.type == 'list'" :list="column.list" :media-only="column.isMediaOnly"/>
<x-tl v-else :src="column.type" :media-only="column.isMediaOnly"/>
</x-column>
</div>
<div class="editor" v-if="edit">
<mk-switch v-model="column.isMediaOnly" @change="onChangeSettings" text="%i18n:@is-media-only%"/>
<mk-switch v-model="column.isMediaView" @change="onChangeSettings" text="%i18n:@is-media-view%"/>
</div>
<x-list-tl v-if="column.type == 'list'" :list="column.list" :media-only="column.isMediaOnly"/>
<x-tl v-else :src="column.type" :media-only="column.isMediaOnly"/>
</x-column>
</template>
<script lang="ts">
@@ -32,12 +30,7 @@ export default Vue.extend({
XListTl
},
props: {
column: {
type: Object,
required: true
}
},
inject: ['column'],
data() {
return {

View File

@@ -1,13 +1,13 @@
<template>
<mk-ui :class="$style.root">
<div class="qlvquzbjribqcaozciifydkngcwtyzje" :data-darkmode="$store.state.device.darkmode">
<template v-for="column in columns">
<x-widgets-column v-if="column.type == 'widgets'" :key="column.id" :column="column"/>
<x-notifications-column v-if="column.type == 'notifications'" :key="column.id" :column="column"/>
<x-tl-column v-if="column.type == 'home'" :key="column.id" :column="column"/>
<x-tl-column v-if="column.type == 'local'" :key="column.id" :column="column"/>
<x-tl-column v-if="column.type == 'global'" :key="column.id" :column="column"/>
<x-tl-column v-if="column.type == 'list'" :key="column.id" :column="column"/>
<template v-for="ids in layout">
<div v-if="ids.length > 1" class="folder">
<template v-for="id, i in ids">
<x-column-core :ref="id" :key="id" :column="columns.find(c => c.id == id)" :is-stacked="true" :is-active="i == 0"/>
</template>
</div>
<x-column-core v-else :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id == ids[0])"/>
</template>
<button ref="add" @click="add" title="%i18n:common.deck.add-column%">%fa:plus%</button>
</div>
@@ -16,27 +16,34 @@
<script lang="ts">
import Vue from 'vue';
import XTlColumn from './deck.tl-column.vue';
import XNotificationsColumn from './deck.notifications-column.vue';
import XWidgetsColumn from './deck.widgets-column.vue';
import XColumnCore from './deck.column-core.vue';
import Menu from '../../../../common/views/components/menu.vue';
import MkUserListsWindow from '../../components/user-lists-window.vue';
import * as uuid from 'uuid';
export default Vue.extend({
components: {
XTlColumn,
XNotificationsColumn,
XWidgetsColumn
XColumnCore
},
computed: {
columns() {
columns(): any[] {
if (this.$store.state.settings.deck == null) return [];
return this.$store.state.settings.deck.columns;
},
layout(): any[] {
if (this.$store.state.settings.deck == null) return [];
if (this.$store.state.settings.deck.layout == null) return this.$store.state.settings.deck.columns.map(c => [c.id]);
return this.$store.state.settings.deck.layout;
}
},
provide() {
return {
getColumnVm: this.getColumnVm
};
},
created() {
if (this.$store.state.settings.deck == null) {
const deck = {
@@ -58,11 +65,23 @@ export default Vue.extend({
}]
};
deck.layout = deck.columns.map(c => [c.id]);
this.$store.dispatch('settings/set', {
key: 'deck',
value: deck
});
}
// 互換性のため
if (this.$store.state.settings.deck != null && this.$store.state.settings.deck.layout == null) {
this.$store.dispatch('settings/set', {
key: 'deck',
value: Object.assign({}, this.$store.state.settings.deck, {
layout: this.$store.state.settings.deck.columns.map(c => [c.id])
})
});
}
},
mounted() {
@@ -74,6 +93,10 @@ export default Vue.extend({
},
methods: {
getColumnVm(id) {
return this.$refs[id][0];
},
add() {
this.os.new(Menu, {
source: this.$refs.add,
@@ -159,6 +182,13 @@ root(isDark)
&:last-of-type
margin-right 0
&.folder
display flex
flex-direction column
> *:not(:last-child)
margin-bottom 8px
> *
&:first-child
margin-left auto

View File

@@ -1,57 +1,55 @@
<template>
<div class="wtdtxvecapixsepjtcupubtsmometobz">
<x-column :id="column.id" :menu="menu" :naked="true" :narrow="true" :name="name">
<span slot="header">%fa:calculator%{{ name }}</span>
<x-column :menu="menu" :naked="true" :narrow="true" :name="name" class="wtdtxvecapixsepjtcupubtsmometobz">
<span slot="header">%fa:calculator%{{ name }}</span>
<div class="gqpwvtwtprsbmnssnbicggtwqhmylhnq">
<template v-if="edit">
<header>
<select v-model="widgetAdderSelected">
<option value="profile">%i18n:common.widgets.profile%</option>
<option value="analog-clock">%i18n:common.widgets.analog-clock%</option>
<option value="calendar">%i18n:common.widgets.calendar%</option>
<option value="timemachine">%i18n:common.widgets.timemachine%</option>
<option value="activity">%i18n:common.widgets.activity%</option>
<option value="rss">%i18n:common.widgets.rss%</option>
<option value="trends">%i18n:common.widgets.trends%</option>
<option value="photo-stream">%i18n:common.widgets.photo-stream%</option>
<option value="slideshow">%i18n:common.widgets.slideshow%</option>
<option value="version">%i18n:common.widgets.version%</option>
<option value="broadcast">%i18n:common.widgets.broadcast%</option>
<option value="notifications">%i18n:common.widgets.notifications%</option>
<option value="users">%i18n:common.widgets.users%</option>
<option value="polls">%i18n:common.widgets.polls%</option>
<option value="post-form">%i18n:common.widgets.post-form%</option>
<option value="messaging">%i18n:common.widgets.messaging%</option>
<option value="memo">%i18n:common.widgets.memo%</option>
<option value="server">%i18n:common.widgets.server%</option>
<option value="donation">%i18n:common.widgets.donation%</option>
<option value="nav">%i18n:common.widgets.nav%</option>
<option value="tips">%i18n:common.widgets.tips%</option>
</select>
<button @click="addWidget">%i18n:@add%</button>
</header>
<x-draggable
:list="column.widgets"
:options="{ handle: '.handle', animation: 150 }"
@sort="onWidgetSort"
>
<div v-for="widget in column.widgets" class="customize-container" :key="widget.id">
<header>
<span class="handle">%fa:bars%</span>{{ widget.name }}<button class="remove" @click="removeWidget(widget)">%fa:times%</button>
</header>
<div @click="widgetFunc(widget.id)">
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="deck"/>
</div>
<div class="gqpwvtwtprsbmnssnbicggtwqhmylhnq">
<template v-if="edit">
<header>
<select v-model="widgetAdderSelected">
<option value="profile">%i18n:common.widgets.profile%</option>
<option value="analog-clock">%i18n:common.widgets.analog-clock%</option>
<option value="calendar">%i18n:common.widgets.calendar%</option>
<option value="timemachine">%i18n:common.widgets.timemachine%</option>
<option value="activity">%i18n:common.widgets.activity%</option>
<option value="rss">%i18n:common.widgets.rss%</option>
<option value="trends">%i18n:common.widgets.trends%</option>
<option value="photo-stream">%i18n:common.widgets.photo-stream%</option>
<option value="slideshow">%i18n:common.widgets.slideshow%</option>
<option value="version">%i18n:common.widgets.version%</option>
<option value="broadcast">%i18n:common.widgets.broadcast%</option>
<option value="notifications">%i18n:common.widgets.notifications%</option>
<option value="users">%i18n:common.widgets.users%</option>
<option value="polls">%i18n:common.widgets.polls%</option>
<option value="post-form">%i18n:common.widgets.post-form%</option>
<option value="messaging">%i18n:common.widgets.messaging%</option>
<option value="memo">%i18n:common.widgets.memo%</option>
<option value="server">%i18n:common.widgets.server%</option>
<option value="donation">%i18n:common.widgets.donation%</option>
<option value="nav">%i18n:common.widgets.nav%</option>
<option value="tips">%i18n:common.widgets.tips%</option>
</select>
<button @click="addWidget">%i18n:@add%</button>
</header>
<x-draggable
:list="column.widgets"
:options="{ handle: '.handle', animation: 150 }"
@sort="onWidgetSort"
>
<div v-for="widget in column.widgets" class="customize-container" :key="widget.id">
<header>
<span class="handle">%fa:bars%</span>{{ widget.name }}<button class="remove" @click="removeWidget(widget)">%fa:times%</button>
</header>
<div @click="widgetFunc(widget.id)">
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="deck"/>
</div>
</x-draggable>
</template>
<template v-else>
<component class="widget" v-for="widget in column.widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" platform="deck"/>
</template>
</div>
</x-column>
</div>
</div>
</x-draggable>
</template>
<template v-else>
<component class="widget" v-for="widget in column.widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" platform="deck"/>
</template>
</div>
</x-column>
</template>
<script lang="ts">
@@ -66,12 +64,7 @@ export default Vue.extend({
XDraggable
},
props: {
column: {
type: Object,
required: true
}
},
inject: ['column'],
data() {
return {