summaryrefslogtreecommitdiff
path: root/src/client/app/desktop/views/deck/deck.notes.vue
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2019-02-15 05:08:59 +0900
committerGitHub <noreply@github.com>2019-02-15 05:08:59 +0900
commit53422ffcb296be404c0f3ef7e175bedecca4fb4d (patch)
tree39cb47b43edd8b8265bb9ad48becdea2666f8881 /src/client/app/desktop/views/deck/deck.notes.vue
parentUpdate README.md [AUTOGEN] (#4253) (diff)
downloadsharkey-53422ffcb296be404c0f3ef7e175bedecca4fb4d.tar.gz
sharkey-53422ffcb296be404c0f3ef7e175bedecca4fb4d.tar.bz2
sharkey-53422ffcb296be404c0f3ef7e175bedecca4fb4d.zip
Improve desktop UX (#4262)
* wip * wip * wip * wip * wip * wip * Merge * wip * wip * wip * wip * wip * wip
Diffstat (limited to 'src/client/app/desktop/views/deck/deck.notes.vue')
-rw-r--r--src/client/app/desktop/views/deck/deck.notes.vue245
1 files changed, 245 insertions, 0 deletions
diff --git a/src/client/app/desktop/views/deck/deck.notes.vue b/src/client/app/desktop/views/deck/deck.notes.vue
new file mode 100644
index 0000000000..260d75a884
--- /dev/null
+++ b/src/client/app/desktop/views/deck/deck.notes.vue
@@ -0,0 +1,245 @@
+<template>
+<div class="eamppglmnmimdhrlzhplwpvyeaqmmhxu">
+ <slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
+
+ <div class="placeholder" v-if="fetching">
+ <template v-for="i in 10">
+ <mk-note-skeleton :key="i"/>
+ </template>
+ </div>
+
+ <mk-error v-if="!fetching && requestInitPromise != null" @retry="resolveInitPromise"/>
+
+ <!-- トランジションを有効にするとなぜかメモリリークする -->
+ <component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="transition notes" ref="notes" tag="div">
+ <template v-for="(note, i) in _notes">
+ <x-note
+ :note="note"
+ :key="note.id"
+ @update:note="onNoteUpdated(i, $event)"
+ :media-view="mediaView"
+ :compact="true"
+ :mini="true"/>
+ <p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
+ <span><fa icon="angle-up"/>{{ note._datetext }}</span>
+ <span><fa icon="angle-down"/>{{ _notes[i + 1]._datetext }}</span>
+ </p>
+ </template>
+ </component>
+
+ <footer v-if="more">
+ <button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
+ <template v-if="!moreFetching">{{ $t('@.load-more') }}</template>
+ <template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>
+ </button>
+ </footer>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import i18n from '../../../i18n';
+import shouldMuteNote from '../../../common/scripts/should-mute-note';
+
+import XNote from '../components/note.vue';
+
+const displayLimit = 20;
+
+export default Vue.extend({
+ i18n: i18n(),
+ components: {
+ XNote
+ },
+
+ inject: ['column', 'isScrollTop', 'count'],
+
+ props: {
+ more: {
+ type: Function,
+ required: false
+ },
+ mediaView: {
+ type: Boolean,
+ required: false,
+ default: false
+ }
+ },
+
+ data() {
+ return {
+ rootEl: null,
+ requestInitPromise: null as () => Promise<any[]>,
+ notes: [],
+ queue: [],
+ fetching: true,
+ moreFetching: false
+ };
+ },
+
+ computed: {
+ _notes(): any[] {
+ return (this.notes as any).map(note => {
+ const date = new Date(note.createdAt).getDate();
+ const month = new Date(note.createdAt).getMonth() + 1;
+ note._date = date;
+ note._datetext = this.$t('@.month-and-day').replace('{month}', month.toString()).replace('{day}', date.toString());
+ return note;
+ });
+ }
+ },
+
+ watch: {
+ queue(q) {
+ this.count(q.length);
+ }
+ },
+
+ created() {
+ this.column.$on('top', this.onTop);
+ this.column.$on('bottom', this.onBottom);
+ },
+
+ beforeDestroy() {
+ this.column.$off('top', this.onTop);
+ this.column.$off('bottom', this.onBottom);
+ },
+
+ methods: {
+ focus() {
+ (this.$refs.notes as any).children[0].focus ? (this.$refs.notes as any).children[0].focus() : (this.$refs.notes as any).$el.children[0].focus();
+ },
+
+ onNoteUpdated(i, note) {
+ Vue.set((this as any).notes, i, note);
+ },
+
+ init(promiseGenerator: () => Promise<any[]>) {
+ this.requestInitPromise = promiseGenerator;
+ this.resolveInitPromise();
+ },
+
+ resolveInitPromise() {
+ this.queue = [];
+ this.notes = [];
+ this.fetching = true;
+
+ const promise = this.requestInitPromise();
+
+ promise.then(notes => {
+ this.notes = notes;
+ this.requestInitPromise = null;
+ this.fetching = false;
+ }, e => {
+ this.fetching = false;
+ });
+ },
+
+ prepend(note, silent = false) {
+ // 弾く
+ if (shouldMuteNote(this.$store.state.i, this.$store.state.settings, note)) return;
+
+ // タブが非表示ならタイトルで通知
+ if (document.hidden) {
+ this.$store.commit('pushBehindNote', note);
+ }
+
+ if (this.isScrollTop()) {
+ // Prepend the note
+ this.notes.unshift(note);
+
+ // オーバーフローしたら古い投稿は捨てる
+ if (this.notes.length >= displayLimit) {
+ this.notes = this.notes.slice(0, displayLimit);
+ }
+ } else {
+ this.queue.push(note);
+ }
+ },
+
+ append(note) {
+ this.notes.push(note);
+ },
+
+ tail() {
+ return this.notes[this.notes.length - 1];
+ },
+
+ releaseQueue() {
+ for (const n of this.queue) {
+ this.prepend(n, true);
+ }
+ this.queue = [];
+ },
+
+ async loadMore() {
+ if (this.more == null) return;
+ if (this.moreFetching) return;
+
+ this.moreFetching = true;
+ await this.more();
+ this.moreFetching = false;
+ },
+
+ onTop() {
+ this.releaseQueue();
+ },
+
+ onBottom() {
+ this.loadMore();
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+.eamppglmnmimdhrlzhplwpvyeaqmmhxu
+ .transition
+ .mk-notes-enter
+ .mk-notes-leave-to
+ opacity 0
+ transform translateY(-30px)
+
+ > *
+ transition transform .3s ease, opacity .3s ease
+
+ > .placeholder
+ padding 16px
+ opacity 0.3
+
+ > .notes
+ > .date
+ display block
+ margin 0
+ line-height 28px
+ font-size 12px
+ text-align center
+ color var(--dateDividerFg)
+ background var(--dateDividerBg)
+ border-bottom solid var(--lineWidth) var(--faceDivider)
+
+ span
+ margin 0 16px
+
+ [data-icon]
+ margin-right 8px
+
+ > footer
+ > button
+ display block
+ margin 0
+ padding 16px
+ width 100%
+ text-align center
+ color #ccc
+ background var(--face)
+ border-top solid var(--lineWidth) var(--faceDivider)
+ border-bottom-left-radius 6px
+ border-bottom-right-radius 6px
+
+ &:hover
+ box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.05)
+
+ &:active
+ box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.1)
+
+</style>