forked from FoundKeyGang/FoundKey
wip
This commit is contained in:
parent
6a9187c1ba
commit
911dedf3d7
11 changed files with 315 additions and 233 deletions
|
@ -97,6 +97,7 @@ cantRenote: "この投稿はRenoteできません。"
|
||||||
cantReRenote: "RenoteをRenoteすることはできません。"
|
cantReRenote: "RenoteをRenoteすることはできません。"
|
||||||
quote: "引用"
|
quote: "引用"
|
||||||
pinnedNote: "ピン留めされたノート"
|
pinnedNote: "ピン留めされたノート"
|
||||||
|
pinned: "ピン留め"
|
||||||
you: "あなた"
|
you: "あなた"
|
||||||
clickToShow: "クリックして表示"
|
clickToShow: "クリックして表示"
|
||||||
sensitive: "閲覧注意"
|
sensitive: "閲覧注意"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<transition name="zoom-in-top" appear @after-leave="$emit('closed')">
|
<transition name="tooltip" appear @after-leave="$emit('closed')">
|
||||||
<div class="buebdbiu _acrylic _shadow" v-if="showing">
|
<div class="buebdbiu _acrylic _shadow" v-show="showing" ref="content">
|
||||||
<slot>{{ text }}</slot>
|
<slot>{{ text }}</slot>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
@ -35,19 +35,43 @@ export default defineComponent({
|
||||||
|
|
||||||
const rect = this.source.getBoundingClientRect();
|
const rect = this.source.getBoundingClientRect();
|
||||||
|
|
||||||
let x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
|
const contentWidth = this.$refs.content.offsetWidth;
|
||||||
let y = rect.top + window.pageYOffset + this.source.offsetHeight;
|
const contentHeight = this.$refs.content.offsetHeight;
|
||||||
|
|
||||||
x -= (this.$el.offsetWidth / 2);
|
let left = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
|
||||||
|
let top = rect.top + window.pageYOffset + this.source.offsetHeight;
|
||||||
|
|
||||||
this.$el.style.left = x + 'px';
|
left -= (this.$el.offsetWidth / 2);
|
||||||
this.$el.style.top = y + 'px';
|
|
||||||
|
if (left + contentWidth - window.pageXOffset > window.innerWidth) {
|
||||||
|
left = window.innerWidth - contentWidth + window.pageXOffset - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top + contentHeight - window.pageYOffset > window.innerHeight) {
|
||||||
|
top = rect.top + window.pageYOffset - contentHeight;
|
||||||
|
this.$refs.content.style.transformOrigin = 'center bottom';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$el.style.left = left + 'px';
|
||||||
|
this.$el.style.top = top + 'px';
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.tooltip-enter-active,
|
||||||
|
.tooltip-leave-active {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 200ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
.tooltip-enter-from,
|
||||||
|
.tooltip-leave-active {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.75);
|
||||||
|
}
|
||||||
|
|
||||||
.buebdbiu {
|
.buebdbiu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 11000;
|
z-index: 11000;
|
||||||
|
@ -57,6 +81,6 @@ export default defineComponent({
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transform-origin: center -16px;
|
transform-origin: center top;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
153
src/client/components/widgets.vue
Normal file
153
src/client/components/widgets.vue
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
<template>
|
||||||
|
<div class="vjoppmmu">
|
||||||
|
<template v-if="edit">
|
||||||
|
<header>
|
||||||
|
<MkSelect v-model:value="widgetAdderSelected" style="margin-bottom: var(--margin)">
|
||||||
|
<template #label>{{ $ts.selectWidget }}</template>
|
||||||
|
<option v-for="widget in widgetDefs" :value="widget" :key="widget">{{ $t(`_widgets.${widget}`) }}</option>
|
||||||
|
</MkSelect>
|
||||||
|
<MkButton inline @click="addWidget" primary><Fa :icon="faPlus"/> {{ $ts.add }}</MkButton>
|
||||||
|
<MkButton inline @click="$emit('exit')">{{ $ts.close }}</MkButton>
|
||||||
|
</header>
|
||||||
|
<XDraggable
|
||||||
|
v-model="_widgets"
|
||||||
|
item-key="id"
|
||||||
|
animation="150"
|
||||||
|
>
|
||||||
|
<template #item="{element}">
|
||||||
|
<div class="customize-container">
|
||||||
|
<button class="config _button" @click.prevent.stop="configWidget(element.id)"><Fa :icon="faCog"/></button>
|
||||||
|
<button class="remove _button" @click.prevent.stop="removeWidget(element)"><Fa :icon="faTimes"/></button>
|
||||||
|
<component :is="`mkw-${element.name}`" :widget="element" :setting-callback="setting => settings[element.id] = setting" :column="column" @updateProps="updateWidget(element.id, $event)"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</XDraggable>
|
||||||
|
</template>
|
||||||
|
<component v-else class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" :column="column" @updateProps="updateWidget(widget.id, $event)"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, defineAsyncComponent } from 'vue';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { faTimes, faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import MkSelect from '@/components/ui/select.vue';
|
||||||
|
import MkButton from '@/components/ui/button.vue';
|
||||||
|
import { widgets as widgetDefs } from '@/widgets';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
|
||||||
|
MkSelect,
|
||||||
|
MkButton,
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
widgets: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ['updateWidgets', 'addWidget', 'removeWidget', 'updateWidget', 'exit'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
widgetAdderSelected: null,
|
||||||
|
widgetDefs,
|
||||||
|
settings: {},
|
||||||
|
faTimes, faPlus, faCog
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
_widgets: {
|
||||||
|
get() {
|
||||||
|
return this.widgets;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$emit('updateWidgets', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
configWidget(id) {
|
||||||
|
this.settings[id]();
|
||||||
|
},
|
||||||
|
|
||||||
|
addWidget() {
|
||||||
|
if (this.widgetAdderSelected == null) return;
|
||||||
|
|
||||||
|
this.$emit('addWidget', {
|
||||||
|
name: this.widgetAdderSelected,
|
||||||
|
id: uuid(),
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.widgetAdderSelected = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
removeWidget(widget) {
|
||||||
|
this.$emit('removeWidget', widget);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateWidget(id, data) {
|
||||||
|
this.$emit('updateWidget', { id, data });
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.vjoppmmu {
|
||||||
|
> header {
|
||||||
|
margin: 16px 0;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
width: 100%;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .widget, .customize-container {
|
||||||
|
margin: var(--margin) 0;
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.customize-container {
|
||||||
|
position: relative;
|
||||||
|
cursor: move;
|
||||||
|
|
||||||
|
> *:not(.remove):not(.config) {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .config,
|
||||||
|
> .remove {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10000;
|
||||||
|
top: 8px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(#000, 0.7);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .config {
|
||||||
|
right: 8px + 8px + 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .remove {
|
||||||
|
right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -4,6 +4,7 @@ import { popup } from '@/os';
|
||||||
|
|
||||||
const start = isDeviceTouch ? 'touchstart' : 'mouseover';
|
const start = isDeviceTouch ? 'touchstart' : 'mouseover';
|
||||||
const end = isDeviceTouch ? 'touchend' : 'mouseleave';
|
const end = isDeviceTouch ? 'touchend' : 'mouseleave';
|
||||||
|
const delay = 100;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mounted(el: HTMLElement, binding, vn) {
|
mounted(el: HTMLElement, binding, vn) {
|
||||||
|
@ -47,13 +48,13 @@ export default {
|
||||||
el.addEventListener(start, () => {
|
el.addEventListener(start, () => {
|
||||||
clearTimeout(self.showTimer);
|
clearTimeout(self.showTimer);
|
||||||
clearTimeout(self.hideTimer);
|
clearTimeout(self.hideTimer);
|
||||||
self.showTimer = setTimeout(show, 300);
|
self.showTimer = setTimeout(show, delay);
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
|
|
||||||
el.addEventListener(end, () => {
|
el.addEventListener(end, () => {
|
||||||
clearTimeout(self.showTimer);
|
clearTimeout(self.showTimer);
|
||||||
clearTimeout(self.hideTimer);
|
clearTimeout(self.hideTimer);
|
||||||
self.hideTimer = setTimeout(self.close, 300);
|
self.hideTimer = setTimeout(self.close, delay);
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
|
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
|
|
|
@ -488,19 +488,6 @@ hr {
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom-in-top-enter-active,
|
|
||||||
.zoom-in-top-leave-active {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scaleY(1);
|
|
||||||
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
||||||
transform-origin: center top;
|
|
||||||
}
|
|
||||||
.zoom-in-top-enter-from,
|
|
||||||
.zoom-in-top-leave-active {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scaleY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes blink {
|
@keyframes blink {
|
||||||
0% { opacity: 1; transform: scale(1); }
|
0% { opacity: 1; transform: scale(1); }
|
||||||
30% { opacity: 1; transform: scale(1); }
|
30% { opacity: 1; transform: scale(1); }
|
||||||
|
|
|
@ -10,10 +10,10 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<MkA class="item" to="/my/messaging"><Fa class="icon" :icon="faComments"/><i v-if="$i.hasUnreadMessagingMessage"><Fa :icon="faCircle"/></i></MkA>
|
<MkA class="item" to="/my/messaging" v-tooltip="$ts.messaging"><Fa class="icon" :icon="faComments"/><i v-if="$i.hasUnreadMessagingMessage"><Fa :icon="faCircle"/></i></MkA>
|
||||||
<MkA class="item" to="/my/messages"><Fa class="icon" :icon="faEnvelope"/><i v-if="$i.hasUnreadSpecifiedNotes"><Fa :icon="faCircle"/></i></MkA>
|
<MkA class="item" to="/my/messages" v-tooltip="$ts.directNotes"><Fa class="icon" :icon="faEnvelope"/><i v-if="$i.hasUnreadSpecifiedNotes"><Fa :icon="faCircle"/></i></MkA>
|
||||||
<MkA class="item" to="/my/mentions"><Fa class="icon" :icon="faAt"/><i v-if="$i.hasUnreadMentions"><Fa :icon="faCircle"/></i></MkA>
|
<MkA class="item" to="/my/mentions" v-tooltip="$ts.mentions"><Fa class="icon" :icon="faAt"/><i v-if="$i.hasUnreadMentions"><Fa :icon="faCircle"/></i></MkA>
|
||||||
<MkA class="item" to="/my/notifications"><Fa class="icon" :icon="faBell"/><i v-if="$i.hasUnreadNotification"><Fa :icon="faCircle"/></i></MkA>
|
<MkA class="item" to="/my/notifications" v-tooltip="$ts.notifications"><Fa class="icon" :icon="faBell"/><i v-if="$i.hasUnreadNotification"><Fa :icon="faCircle"/></i></MkA>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
@ -63,10 +63,10 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<button class="_button item search" @click="search">
|
<button class="_button item search" @click="search" v-tooltip="$ts.search">
|
||||||
<Fa :icon="faSearch"/>
|
<Fa :icon="faSearch"/>
|
||||||
</button>
|
</button>
|
||||||
<MkA class="item" to="/settings"><Fa class="icon" :icon="faCog"/></MkA>
|
<MkA class="item" to="/settings" v-tooltip="$ts.settings"><Fa class="icon" :icon="faCog"/></MkA>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -97,11 +97,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right">
|
<div class="right">
|
||||||
|
<div class="instance">{{ instanceName }}</div>
|
||||||
<XHeaderClock class="clock"/>
|
<XHeaderClock class="clock"/>
|
||||||
<button class="_button button search" @click="search">
|
<button class="_button button search" @click="search" v-tooltip="$ts.search">
|
||||||
<Fa :icon="faSearch"/>
|
<Fa :icon="faSearch"/>
|
||||||
</button>
|
</button>
|
||||||
<button class="_button button follow" v-if="tl.startsWith('channel:') && currentChannel" :class="{ followed: currentChannel.isFollowing }" @click="toggleChannelFollow">
|
<button class="_button button follow" v-if="tl.startsWith('channel:') && currentChannel" :class="{ followed: currentChannel.isFollowing }" @click="toggleChannelFollow" v-tooltip="currentChannel.isFollowing ? $ts.unfollow : $ts.follow">
|
||||||
<Fa v-if="currentChannel.isFollowing" :icon="faStar"/>
|
<Fa v-if="currentChannel.isFollowing" :icon="faStar"/>
|
||||||
<Fa v-else :icon="farStar"/>
|
<Fa v-else :icon="farStar"/>
|
||||||
</button>
|
</button>
|
||||||
|
@ -121,6 +122,9 @@
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<XSide class="side" ref="side"/>
|
<XSide class="side" ref="side"/>
|
||||||
|
<div class="side">
|
||||||
|
<XWidgets/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<XCommon/>
|
<XCommon/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -132,6 +136,7 @@ import { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, fa
|
||||||
import { faBell, faStar as farStar, faEnvelope, faComments } from '@fortawesome/free-regular-svg-icons';
|
import { faBell, faStar as farStar, faEnvelope, faComments } from '@fortawesome/free-regular-svg-icons';
|
||||||
import { instanceName, url } from '@/config';
|
import { instanceName, url } from '@/config';
|
||||||
import XSidebar from '@/components/sidebar.vue';
|
import XSidebar from '@/components/sidebar.vue';
|
||||||
|
import XWidgets from './widgets.vue';
|
||||||
import XCommon from '../_common_/common.vue';
|
import XCommon from '../_common_/common.vue';
|
||||||
import XSide from './side.vue';
|
import XSide from './side.vue';
|
||||||
import XTimeline from './timeline.vue';
|
import XTimeline from './timeline.vue';
|
||||||
|
@ -147,6 +152,7 @@ export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
XCommon,
|
XCommon,
|
||||||
XSidebar,
|
XSidebar,
|
||||||
|
XWidgets,
|
||||||
XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる
|
XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる
|
||||||
XTimeline,
|
XTimeline,
|
||||||
XPostForm,
|
XPostForm,
|
||||||
|
@ -187,6 +193,7 @@ export default defineComponent({
|
||||||
featuredChannels: null,
|
featuredChannels: null,
|
||||||
currentChannel: null,
|
currentChannel: null,
|
||||||
menuDef: sidebarDef,
|
menuDef: sidebarDef,
|
||||||
|
instanceName,
|
||||||
faLayerGroup, faBars, faBell, faHome, faCircle, faPencilAlt, faShareAlt, faSatelliteDish, faListUl, faSatellite, faCog, faSearch, faPlus, faStar, farStar, faAt, faLink, faEllipsisH, faGlobe, faComments, faEnvelope,
|
faLayerGroup, faBars, faBell, faHome, faCircle, faPencilAlt, faShareAlt, faSatelliteDish, faListUl, faSatellite, faCog, faSearch, faPlus, faStar, farStar, faAt, faLink, faEllipsisH, faGlobe, faComments, faEnvelope,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -310,8 +317,7 @@ export default defineComponent({
|
||||||
$ui-font-size: 1em; // TODO: どこかに集約したい
|
$ui-font-size: 1em; // TODO: どこかに集約したい
|
||||||
|
|
||||||
// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
|
// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
|
||||||
min-height: calc(var(--vh, 1vh) * 100);
|
height: calc(var(--vh, 1vh) * 100);
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
> .nav {
|
> .nav {
|
||||||
|
@ -518,6 +524,10 @@ export default defineComponent({
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
|
|
||||||
|
> .instance {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
> .clock {
|
> .clock {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
@ -552,6 +562,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
> .side {
|
> .side {
|
||||||
|
width: 350px;
|
||||||
border-left: solid 1px var(--divider);
|
border-left: solid 1px var(--divider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,12 +120,8 @@ export default defineComponent({
|
||||||
--section-padding: 16px;
|
--section-padding: 16px;
|
||||||
--margin: var(--marginHalf);
|
--margin: var(--marginHalf);
|
||||||
|
|
||||||
width: 390px;
|
|
||||||
|
|
||||||
> .container {
|
> .container {
|
||||||
position: fixed;
|
height: 100%;
|
||||||
width: 390px;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
|
13
src/client/ui/chat/store.ts
Normal file
13
src/client/ui/chat/store.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
import { Storage } from '../../pizzax';
|
||||||
|
|
||||||
|
export const store = markRaw(new Storage('chatUi', {
|
||||||
|
widgets: {
|
||||||
|
where: 'account',
|
||||||
|
default: [] as {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
data: Record<string, any>;
|
||||||
|
}[]
|
||||||
|
},
|
||||||
|
}));
|
61
src/client/ui/chat/widgets.vue
Normal file
61
src/client/ui/chat/widgets.vue
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<div class="qydbhufi">
|
||||||
|
<XWidgets :edit="edit" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/>
|
||||||
|
|
||||||
|
<button v-if="edit" @click="edit = false" class="_textButton" style="font-size: 0.9em;">{{ $ts.editWidgetsExit }}</button>
|
||||||
|
<button v-else @click="edit = true" class="_textButton" style="font-size: 0.9em;">{{ $ts.editWidgets }}</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, defineAsyncComponent } from 'vue';
|
||||||
|
import XWidgets from '@/components/widgets.vue';
|
||||||
|
import { store } from './store.ts';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
XWidgets,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
edit: false,
|
||||||
|
widgets: store.reactiveState.widgets
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
addWidget(widget) {
|
||||||
|
store.set('widgets', [widget, ...store.state.widgets]);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeWidget(widget) {
|
||||||
|
store.set('widgets', store.state.widgets.filter(w => w.id != widget.id));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateWidget({ id, data }) {
|
||||||
|
store.set('widgets', store.state.widgets.map(w => w.id === id ? {
|
||||||
|
...w,
|
||||||
|
data: data
|
||||||
|
} : w));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateWidgets(widgets) {
|
||||||
|
store.set('widgets', widgets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.qydbhufi {
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: auto;
|
||||||
|
padding: var(--margin);
|
||||||
|
|
||||||
|
::v-deep(._panel) {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -3,49 +3,22 @@
|
||||||
<template #header><Fa :icon="faWindowMaximize" style="margin-right: 8px;"/>{{ column.name }}</template>
|
<template #header><Fa :icon="faWindowMaximize" style="margin-right: 8px;"/>{{ column.name }}</template>
|
||||||
|
|
||||||
<div class="wtdtxvec">
|
<div class="wtdtxvec">
|
||||||
<template v-if="edit">
|
<XWidgets :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/>
|
||||||
<header>
|
|
||||||
<MkSelect v-model:value="widgetAdderSelected" style="margin-bottom: var(--margin)">
|
|
||||||
<template #label>{{ $ts.selectWidget }}</template>
|
|
||||||
<option v-for="widget in widgets" :value="widget" :key="widget">{{ $t(`_widgets.${widget}`) }}</option>
|
|
||||||
</MkSelect>
|
|
||||||
<MkButton inline @click="addWidget" primary><Fa :icon="faPlus"/> {{ $ts.add }}</MkButton>
|
|
||||||
<MkButton inline @click="edit = false">{{ $ts.close }}</MkButton>
|
|
||||||
</header>
|
|
||||||
<XDraggable
|
|
||||||
v-model="_widgets"
|
|
||||||
item-key="id"
|
|
||||||
animation="150"
|
|
||||||
>
|
|
||||||
<template #item="{element}">
|
|
||||||
<div class="customize-container" @click="widgetFunc(element.id)">
|
|
||||||
<button class="remove _button" @click.prevent.stop="removeWidget(element)"><Fa :icon="faTimes"/></button>
|
|
||||||
<component :is="`mkw-${element.name}`" :widget="element" :setting-callback="setting => settings[element.id] = setting" :column="column" @updateProps="saveWidget(element.id, $event)"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</XDraggable>
|
|
||||||
</template>
|
|
||||||
<component v-else class="widget" v-for="widget in column.widgets" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" :column="column" @updateProps="saveWidget(widget.id, $event)"/>
|
|
||||||
</div>
|
</div>
|
||||||
</XColumn>
|
</XColumn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, defineAsyncComponent } from 'vue';
|
import { defineComponent, defineAsyncComponent } from 'vue';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { faWindowMaximize, faTimes, faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
|
import { faWindowMaximize, faTimes, faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||||
import MkSelect from '@/components/ui/select.vue';
|
import XWidgets from '@/components/widgets.vue';
|
||||||
import MkButton from '@/components/ui/button.vue';
|
|
||||||
import XColumn from './column.vue';
|
import XColumn from './column.vue';
|
||||||
import { widgets } from '../../widgets';
|
|
||||||
import { addColumnWidget, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store';
|
import { addColumnWidget, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
XColumn,
|
XColumn,
|
||||||
XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
|
XWidgets,
|
||||||
MkSelect,
|
|
||||||
MkButton,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
@ -62,49 +35,27 @@ export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
edit: false,
|
edit: false,
|
||||||
widgetAdderSelected: null,
|
|
||||||
widgets,
|
|
||||||
settings: {},
|
|
||||||
faWindowMaximize, faTimes, faPlus
|
faWindowMaximize, faTimes, faPlus
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
_widgets: {
|
|
||||||
get() {
|
|
||||||
return this.column.widgets;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
setColumnWidgets(this.column.id, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
widgetFunc(id) {
|
addWidget(widget) {
|
||||||
this.settings[id]();
|
addColumnWidget(this.column.id, widget);
|
||||||
},
|
|
||||||
|
|
||||||
addWidget() {
|
|
||||||
if (this.widgetAdderSelected == null) return;
|
|
||||||
|
|
||||||
addColumnWidget(this.column.id, {
|
|
||||||
name: this.widgetAdderSelected,
|
|
||||||
id: uuid(),
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.widgetAdderSelected = null;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
removeWidget(widget) {
|
removeWidget(widget) {
|
||||||
removeColumnWidget(this.column.id, widget);
|
removeColumnWidget(this.column.id, widget);
|
||||||
},
|
},
|
||||||
|
|
||||||
saveWidget(id, data) {
|
updateWidget({ id, data }) {
|
||||||
updateColumnWidget(this.column.id, id, data);
|
updateColumnWidget(this.column.id, id, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateWidgets(widgets) {
|
||||||
|
setColumnWidgets(this.column.id, widgets);
|
||||||
|
},
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
this.edit = !this.edit;
|
this.edit = !this.edit;
|
||||||
}
|
}
|
||||||
|
@ -114,46 +65,12 @@ export default defineComponent({
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.wtdtxvec {
|
.wtdtxvec {
|
||||||
._panel {
|
--margin: 8px;
|
||||||
|
|
||||||
|
padding: 0 var(--margin);
|
||||||
|
|
||||||
|
::v-deep(._panel) {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
> header {
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
width: 100%;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .widget, .customize-container {
|
|
||||||
margin: 8px;
|
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.customize-container {
|
|
||||||
position: relative;
|
|
||||||
cursor: move;
|
|
||||||
|
|
||||||
> *:not(.remove) {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .remove {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
top: 8px;
|
|
||||||
right: 8px;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
color: #fff;
|
|
||||||
background: rgba(#000, 0.7);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,46 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="efzpzdvf">
|
<div class="efzpzdvf">
|
||||||
<template v-if="editMode">
|
<XWidgets :edit="editMode" :widgets="$store.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/>
|
||||||
<MkButton primary @click="addWidget" class="add"><Fa :icon="faPlus"/></MkButton>
|
|
||||||
<XDraggable
|
<button v-if="editMode" @click="editMode = false" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faCheck"/> {{ $ts.editWidgetsExit }}</button>
|
||||||
v-model="widgets"
|
<button v-else @click="editMode = true" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faPencilAlt"/> {{ $ts.editWidgets }}</button>
|
||||||
item-key="id"
|
|
||||||
handle=".handle"
|
|
||||||
animation="150"
|
|
||||||
class="sortable"
|
|
||||||
>
|
|
||||||
<template #item="{element}">
|
|
||||||
<div class="customize-container _panel">
|
|
||||||
<header>
|
|
||||||
<span class="handle"><Fa :icon="faBars"/></span>{{ $t('_widgets.' + element.name) }}<button class="remove _button" @click="removeWidget(element)"><Fa :icon="faTimes"/></button>
|
|
||||||
</header>
|
|
||||||
<div @click="widgetFunc(element.id)">
|
|
||||||
<component class="_inContainer_ _forceContainerFull_" :is="`mkw-${element.name}`" :widget="element" :ref="element.id" :setting-callback="setting => settings[element.id] = setting" @updateProps="saveWidget(element.id, $event)"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</XDraggable>
|
|
||||||
<button @click="editMode = false" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faCheck"/> {{ $ts.editWidgetsExit }}</button>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<component v-for="widget in widgets" class="_inContainer_ _forceContainerFull_" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" @updateProps="saveWidget(widget.id, $event)"/>
|
|
||||||
<button @click="editMode = true" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faPencilAlt"/> {{ $ts.editWidgets }}</button>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, defineAsyncComponent } from 'vue';
|
import { defineComponent, defineAsyncComponent } from 'vue';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { faPencilAlt, faPlus, faBars, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons';
|
import { faPencilAlt, faPlus, faBars, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { widgets } from '@/widgets';
|
import XWidgets from '@/components/widgets.vue';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import MkButton from '@/components/ui/button.vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
MkButton,
|
XWidgets
|
||||||
XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
emits: ['mounted'],
|
emits: ['mounted'],
|
||||||
|
@ -48,62 +23,35 @@ export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
editMode: false,
|
editMode: false,
|
||||||
settings: {},
|
|
||||||
faPencilAlt, faPlus, faBars, faTimes, faCheck,
|
faPencilAlt, faPlus, faBars, faTimes, faCheck,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
widgets: {
|
|
||||||
get() {
|
|
||||||
return this.$store.reactiveState.widgets.value;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.$store.set('widgets', value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$emit('mounted', this.$el);
|
this.$emit('mounted', this.$el);
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
widgetFunc(id) {
|
addWidget(widget) {
|
||||||
this.settings[id]();
|
this.$store.set('widgets', [{
|
||||||
},
|
...widget,
|
||||||
|
|
||||||
async addWidget() {
|
|
||||||
const { canceled, result: widget } = await os.dialog({
|
|
||||||
type: null,
|
|
||||||
title: this.$ts.chooseWidget,
|
|
||||||
select: {
|
|
||||||
items: widgets.map(widget => ({
|
|
||||||
value: widget,
|
|
||||||
text: this.$t('_widgets.' + widget),
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
showCancelButton: true
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
this.$store.set('widgets', [...this.$store.state.widgets, {
|
|
||||||
name: widget,
|
|
||||||
id: uuid(),
|
|
||||||
place: null,
|
place: null,
|
||||||
data: {}
|
}, ...this.$store.state.widgets]);
|
||||||
}]);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
removeWidget(widget) {
|
removeWidget(widget) {
|
||||||
this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id != widget.id));
|
this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id != widget.id));
|
||||||
},
|
},
|
||||||
|
|
||||||
saveWidget(id, data) {
|
updateWidget({ id, data }) {
|
||||||
this.$store.set('widgets', this.$store.state.widgets.map(w => w.id === id ? {
|
this.$store.set('widgets', this.$store.state.widgets.map(w => w.id === id ? {
|
||||||
...w,
|
...w,
|
||||||
data: data
|
data: data
|
||||||
} : w));
|
} : w));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateWidgets(widgets) {
|
||||||
|
this.$store.set('widgets', widgets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -129,35 +77,5 @@ export default defineComponent({
|
||||||
> .add {
|
> .add {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customize-container {
|
|
||||||
margin: 8px 0;
|
|
||||||
|
|
||||||
> header {
|
|
||||||
position: relative;
|
|
||||||
line-height: 32px;
|
|
||||||
|
|
||||||
> .handle {
|
|
||||||
padding: 0 8px;
|
|
||||||
cursor: move;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .remove {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 0 8px;
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> div {
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue