diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-10-25 02:34:58 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-10-25 02:34:58 +0900 |
| commit | 6d557269c13f023d80aeeb28ddceb7337aca134c (patch) | |
| tree | f69595ca45dd0d05dc3af4404dd8205dc828ddc1 /src/client/components | |
| parent | Merge branch 'develop' (diff) | |
| parent | 12.94.0 (diff) | |
| download | misskey-6d557269c13f023d80aeeb28ddceb7337aca134c.tar.gz misskey-6d557269c13f023d80aeeb28ddceb7337aca134c.tar.bz2 misskey-6d557269c13f023d80aeeb28ddceb7337aca134c.zip | |
Merge branch 'develop'
Diffstat (limited to 'src/client/components')
| -rw-r--r-- | src/client/components/chart.vue | 41 | ||||
| -rw-r--r-- | src/client/components/global/header.vue | 9 | ||||
| -rw-r--r-- | src/client/components/global/sticky-container.vue | 74 | ||||
| -rw-r--r-- | src/client/components/index.ts | 2 | ||||
| -rw-r--r-- | src/client/components/media-image.vue | 12 | ||||
| -rw-r--r-- | src/client/components/media-list.vue | 97 | ||||
| -rw-r--r-- | src/client/components/mention.vue | 8 | ||||
| -rw-r--r-- | src/client/components/modal-page-window.vue | 11 | ||||
| -rw-r--r-- | src/client/components/page-window.vue | 7 | ||||
| -rw-r--r-- | src/client/components/queue-chart.vue | 22 | ||||
| -rw-r--r-- | src/client/components/reactions-viewer.details.vue | 54 | ||||
| -rw-r--r-- | src/client/components/reactions-viewer.reaction.vue | 1 | ||||
| -rw-r--r-- | src/client/components/ui/folder.vue | 6 | ||||
| -rw-r--r-- | src/client/components/ui/info.vue | 4 | ||||
| -rw-r--r-- | src/client/components/ui/super-menu.vue | 5 | ||||
| -rw-r--r-- | src/client/components/ui/tooltip.vue | 12 |
16 files changed, 256 insertions, 109 deletions
diff --git a/src/client/components/chart.vue b/src/client/components/chart.vue index 2b94bd679d..ae9a5e79b1 100644 --- a/src/client/components/chart.vue +++ b/src/client/components/chart.vue @@ -89,6 +89,16 @@ export default defineComponent({ required: false, default: false }, + stacked: { + type: Boolean, + required: false, + default: false + }, + aspectRatio: { + type: Number, + required: false, + default: null + }, }, setup(props) { @@ -157,7 +167,7 @@ export default defineComponent({ })), }, options: { - aspectRatio: 2.5, + aspectRatio: props.aspectRatio || 2.5, layout: { padding: { left: 16, @@ -174,7 +184,6 @@ export default defineComponent({ unit: props.span === 'day' ? 'month' : 'day', }, grid: { - display: props.detailed, color: gridColor, borderColor: 'rgb(0, 0, 0, 0)', }, @@ -190,6 +199,7 @@ export default defineComponent({ }, y: { position: 'left', + stacked: props.stacked, grid: { color: gridColor, borderColor: 'rgb(0, 0, 0, 0)', @@ -204,6 +214,7 @@ export default defineComponent({ }, plugins: { legend: { + display: props.detailed, position: 'bottom', labels: { boxWidth: 16, @@ -583,6 +594,30 @@ export default defineComponent({ }; }; + const fetchPerUserNotesChart = async (): Promise<typeof data> => { + const raw = await os.api('charts/user/notes', { userId: props.args.user.id, limit: props.limit, span: props.span }); + return { + series: [...(props.args.withoutAll ? [] : [{ + name: 'All', + type: 'line', + borderDash: [5, 5], + data: format(sum(raw.inc, negate(raw.dec))), + }]), { + name: 'Renotes', + type: 'area', + data: format(raw.diffs.renote), + }, { + name: 'Replies', + type: 'area', + data: format(raw.diffs.reply), + }, { + name: 'Normal', + type: 'area', + data: format(raw.diffs.normal), + }], + }; + }; + const fetchAndRender = async () => { const fetchData = () => { switch (props.src) { @@ -611,6 +646,8 @@ export default defineComponent({ case 'instance-drive-usage-total': return fetchInstanceDriveUsageChart(true); case 'instance-drive-files': return fetchInstanceDriveFilesChart(false); case 'instance-drive-files-total': return fetchInstanceDriveFilesChart(true); + + case 'per-user-notes': return fetchPerUserNotesChart(); } }; fetching.value = true; diff --git a/src/client/components/global/header.vue b/src/client/components/global/header.vue index 2bf490c98a..526db07fd3 100644 --- a/src/client/components/global/header.vue +++ b/src/client/components/global/header.vue @@ -2,8 +2,8 @@ <div class="fdidabkb" :class="{ slim: narrow, thin: thin_ }" :style="{ background: bg }" @click="onClick" ref="el"> <template v-if="info"> <div class="titleContainer" @click="showTabsPopup" v-if="!hideTitle"> - <i v-if="info.icon" class="icon" :class="info.icon"></i> - <MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true" :show-indicator="true"/> + <MkAvatar v-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true" :show-indicator="true"/> + <i v-else-if="info.icon" class="icon" :class="info.icon"></i> <div class="title"> <MkUserName v-if="info.userName" :user="info.userName" :nowrap="false" class="title"/> @@ -162,11 +162,6 @@ export default defineComponent({ onUnmounted(() => { ro.disconnect(); }); - setTimeout(() => { - const currentStickyTop = getComputedStyle(el.value.parentElement).getPropertyValue('--stickyTop') || '0px'; - el.value.style.setProperty('--stickyTop', currentStickyTop); - el.value.parentElement.style.setProperty('--stickyTop', `calc(${currentStickyTop} + ${el.value.offsetHeight}px)`); - }, 100); // レンダリング順序の関係で親のstickyTopの設定が少し遅れることがあるため } }); diff --git a/src/client/components/global/sticky-container.vue b/src/client/components/global/sticky-container.vue new file mode 100644 index 0000000000..859b2c1d73 --- /dev/null +++ b/src/client/components/global/sticky-container.vue @@ -0,0 +1,74 @@ +<template> +<div ref="rootEl"> + <slot name="header"></slot> + <div ref="bodyEl"> + <slot></slot> + </div> +</div> +</template> + +<script lang="ts"> +import { defineComponent, onMounted, onUnmounted, ref } from 'vue'; + +export default defineComponent({ + props: { + autoSticky: { + type: Boolean, + required: false, + default: false, + }, + }, + + setup(props, context) { + const rootEl = ref<HTMLElement>(null); + const bodyEl = ref<HTMLElement>(null); + + const calc = () => { + const currentStickyTop = getComputedStyle(rootEl.value).getPropertyValue('--stickyTop') || '0px'; + + const header = rootEl.value.children[0]; + if (header === bodyEl.value) { + bodyEl.value.style.setProperty('--stickyTop', currentStickyTop); + } else { + bodyEl.value.style.setProperty('--stickyTop', `calc(${currentStickyTop} + ${header.offsetHeight}px)`); + + if (props.autoSticky) { + header.style.setProperty('--stickyTop', currentStickyTop); + header.style.position = 'sticky'; + header.style.top = 'var(--stickyTop)'; + header.style.zIndex = '1'; + } + } + }; + + onMounted(() => { + calc(); + + const observer = new MutationObserver(() => { + setTimeout(() => { + calc(); + }, 100); + }); + + observer.observe(rootEl.value, { + attributes: false, + childList: true, + subtree: false, + }); + + onUnmounted(() => { + observer.disconnect(); + }); + }); + + return { + rootEl, + bodyEl, + }; + }, +}); +</script> + +<style lang="scss" module> + +</style> diff --git a/src/client/components/index.ts b/src/client/components/index.ts index ecf66ea0e8..2340b228f8 100644 --- a/src/client/components/index.ts +++ b/src/client/components/index.ts @@ -15,6 +15,7 @@ import error from './global/error.vue'; import ad from './global/ad.vue'; import header from './global/header.vue'; import spacer from './global/spacer.vue'; +import stickyContainer from './global/sticky-container.vue'; export default function(app: App) { app.component('I18n', i18n); @@ -32,4 +33,5 @@ export default function(app: App) { app.component('MkAd', ad); app.component('MkHeader', header); app.component('MkSpacer', spacer); + app.component('MkStickyContainer', stickyContainer); } diff --git a/src/client/components/media-image.vue b/src/client/components/media-image.vue index 863eb10272..fd5e0b5f9b 100644 --- a/src/client/components/media-image.vue +++ b/src/client/components/media-image.vue @@ -12,7 +12,6 @@ <a :href="image.url" :title="image.name" - @click.prevent="onClick" > <ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment" :title="image.comment" :cover="false"/> <div class="gif" v-if="image.type === 'image/gif'">GIF</div> @@ -73,17 +72,6 @@ export default defineComponent({ immediate: true, }); }, - methods: { - onClick() { - if (this.$store.state.imageNewTab) { - window.open(this.image.url, '_blank'); - } else { - os.popup(ImageViewer, { - image: this.image - }, {}, 'closed'); - } - } - } }); </script> diff --git a/src/client/components/media-list.vue b/src/client/components/media-list.vue index 71767a0f9f..c499525d84 100644 --- a/src/client/components/media-list.vue +++ b/src/client/components/media-list.vue @@ -1,11 +1,11 @@ <template> -<div class="mk-media-list"> +<div class="hoawjimk"> <XBanner v-for="media in mediaList.filter(media => !previewable(media))" :media="media" :key="media.id"/> - <div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container" ref="gridOuter"> - <div :data-count="mediaList.filter(media => previewable(media)).length" :style="gridInnerStyle"> + <div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container"> + <div :data-count="mediaList.filter(media => previewable(media)).length" ref="gallery"> <template v-for="media in mediaList"> <XVideo :video="media" :key="media.id" v-if="media.type.startsWith('video')"/> - <XImage :image="media" :key="media.id" v-else-if="media.type.startsWith('image')" :raw="raw"/> + <XImage class="image" :data-id="media.id" :image="media" :key="media.id" v-else-if="media.type.startsWith('image')" :raw="raw"/> </template> </div> </div> @@ -13,11 +13,16 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineComponent, onMounted, PropType, ref } from 'vue'; +import * as misskey from 'misskey-js'; +import PhotoSwipeLightbox from 'photoswipe/dist/photoswipe-lightbox.esm.js'; +import PhotoSwipe from 'photoswipe/dist/photoswipe.esm.js'; +import 'photoswipe/dist/photoswipe.css'; import XBanner from './media-banner.vue'; import XImage from './media-image.vue'; import XVideo from './media-video.vue'; import * as os from '@client/os'; +import { defaultStore } from '@client/store'; export default defineComponent({ components: { @@ -27,63 +32,63 @@ export default defineComponent({ }, props: { mediaList: { - required: true + type: Array as PropType<misskey.entities.DriveFile[]>, + required: true, }, raw: { default: false }, }, - data() { - return { - gridInnerStyle: {}, - sizeWaiting: false - } - }, - mounted() { - this.size(); - window.addEventListener('resize', this.size); - }, - beforeUnmount() { - window.removeEventListener('resize', this.size); - }, - activated() { - this.size(); - }, - methods: { - previewable(file) { - return file.type.startsWith('video') || file.type.startsWith('image'); - }, - size() { - // for Safari bug - if (this.sizeWaiting) return; + setup(props) { + const gallery = ref(null); - this.sizeWaiting = true; + onMounted(() => { + const lightbox = new PhotoSwipeLightbox({ + dataSource: props.mediaList.filter(media => media.type.startsWith('image')).map(media => ({ + src: media.url, + w: media.properties.width, + h: media.properties.height, + alt: media.name, + })), + gallery: gallery.value, + children: '.image', + thumbSelector: '.image', + pswpModule: PhotoSwipe + }); - window.requestAnimationFrame(() => { - this.sizeWaiting = false; + lightbox.on('itemData', (e) => { + const { itemData } = e; - if (this.$refs.gridOuter) { - let height = 287; - const parent = this.$parent.$el; + // element is children + const { element } = itemData; - if (this.$refs.gridOuter.clientHeight) { - height = this.$refs.gridOuter.clientHeight; - } else if (parent) { - height = parent.getBoundingClientRect().width * 9 / 16; - } + const id = element.dataset.id; + const file = props.mediaList.find(media => media.id === id); - this.gridInnerStyle = { height: `${height}px` }; - } else { - this.gridInnerStyle = {}; - } + itemData.src = file.url; + itemData.w = Number(file.properties.width); + itemData.h = Number(file.properties.height); + itemData.msrc = file.thumbnailUrl; + itemData.thumbCropped = true; }); - } + + lightbox.init(); + }); + + const previewable = (file: misskey.entities.DriveFile): boolean => { + return file.type.startsWith('video') || file.type.startsWith('image'); + }; + + return { + previewable, + gallery, + }; }, }); </script> <style lang="scss" scoped> -.mk-media-list { +.hoawjimk { > .gird-container { position: relative; width: 100%; diff --git a/src/client/components/mention.vue b/src/client/components/mention.vue index b9bd6b320b..101a9020ee 100644 --- a/src/client/components/mention.vue +++ b/src/client/components/mention.vue @@ -1,6 +1,7 @@ <template> <MkA class="ldlomzub" :class="{ isMe }" :to="url" v-user-preview="canonical" v-if="url.startsWith('/')"> <span class="me" v-if="isMe">{{ $ts.you }}</span> + <img class="icon" :src="`/avatar/@${username}@${host}`" alt=""> <span class="main"> <span class="username">@{{ username }}</span> <span class="host" v-if="(host != localHost) || $store.state.showFullAcct">@{{ toUnicode(host) }}</span> @@ -76,6 +77,13 @@ export default defineComponent({ vertical-align: top; } + > .icon { + width: 1.5em; + margin: 0 0.2em; + vertical-align: bottom; + border-radius: 100%; + } + > .main { > .host { opacity: 0.5; diff --git a/src/client/components/modal-page-window.vue b/src/client/components/modal-page-window.vue index cb81a974f5..e47d3dc62c 100644 --- a/src/client/components/modal-page-window.vue +++ b/src/client/components/modal-page-window.vue @@ -10,10 +10,13 @@ </span> <button class="_button" @click="$refs.modal.close()"><i class="fas fa-times"></i></button> </div> - <div class="body _fitSide_"> - <keep-alive> - <component :is="component" v-bind="props" :ref="changePage"/> - </keep-alive> + <div class="body"> + <MkStickyContainer> + <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> + <keep-alive> + <component :is="component" v-bind="props" :ref="changePage"/> + </keep-alive> + </MkStickyContainer> </div> </div> </MkModal> diff --git a/src/client/components/page-window.vue b/src/client/components/page-window.vue index 7d15c75d62..bc7c5b7a19 100644 --- a/src/client/components/page-window.vue +++ b/src/client/components/page-window.vue @@ -16,8 +16,11 @@ <template #headerLeft> <button v-if="history.length > 0" class="_button" @click="back()" v-tooltip="$ts.goBack"><i class="fas fa-arrow-left"></i></button> </template> - <div class="yrolvcoq _fitSide_"> - <component :is="component" v-bind="props" :ref="changePage"/> + <div class="yrolvcoq"> + <MkStickyContainer> + <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> + <component :is="component" v-bind="props" :ref="changePage"/> + </MkStickyContainer> </div> </XWindow> </template> diff --git a/src/client/components/queue-chart.vue b/src/client/components/queue-chart.vue index 59c9723f89..f9c3eccfb5 100644 --- a/src/client/components/queue-chart.vue +++ b/src/client/components/queue-chart.vue @@ -79,6 +79,7 @@ export default defineComponent({ pointRadius: 0, tension: 0, borderWidth: 2, + borderJoinStyle: 'round', borderColor: '#00E396', backgroundColor: alpha('#00E396', 0.1), data: [] @@ -87,6 +88,7 @@ export default defineComponent({ pointRadius: 0, tension: 0, borderWidth: 2, + borderJoinStyle: 'round', borderColor: '#00BCD4', backgroundColor: alpha('#00BCD4', 0.1), data: [] @@ -95,17 +97,21 @@ export default defineComponent({ pointRadius: 0, tension: 0, borderWidth: 2, + borderJoinStyle: 'round', borderColor: '#FFB300', backgroundColor: alpha('#FFB300', 0.1), + yAxisID: 'y2', data: [] }, { label: 'Delayed', pointRadius: 0, tension: 0, borderWidth: 2, + borderJoinStyle: 'round', borderColor: '#E53935', borderDash: [5, 5], fill: false, + yAxisID: 'y2', data: [] }], }, @@ -122,15 +128,29 @@ export default defineComponent({ scales: { x: { grid: { - display: false, + display: true, color: gridColor, borderColor: 'rgb(0, 0, 0, 0)', }, ticks: { display: false, + maxTicksLimit: 10 }, }, y: { + min: 0, + stack: 'queue', + stackWeight: 2, + grid: { + color: gridColor, + borderColor: 'rgb(0, 0, 0, 0)', + }, + }, + y2: { + min: 0, + offset: true, + stack: 'queue', + stackWeight: 1, grid: { color: gridColor, borderColor: 'rgb(0, 0, 0, 0)', diff --git a/src/client/components/reactions-viewer.details.vue b/src/client/components/reactions-viewer.details.vue index 6e7da58e20..7c49bd1d9c 100644 --- a/src/client/components/reactions-viewer.details.vue +++ b/src/client/components/reactions-viewer.details.vue @@ -1,23 +1,25 @@ <template> -<MkTooltip :source="source" ref="tooltip" @closed="$emit('closed')"> +<MkTooltip :source="source" ref="tooltip" @closed="$emit('closed')" :max-width="340"> <div class="bqxuuuey"> - <div class="info"> - <div>{{ reaction.replace('@.', '') }}</div> + <div class="reaction"> <XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/> + <div class="name">{{ reaction.replace('@.', '') }}</div> + </div> + <div class="users"> + <template v-if="users.length <= 10"> + <b v-for="u in users" :key="u.id" style="margin-right: 12px;"> + <MkAvatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/> + <MkUserName :user="u" :nowrap="false" style="line-height: 24px;"/> + </b> + </template> + <template v-if="10 < users.length"> + <b v-for="u in users" :key="u.id" style="margin-right: 12px;"> + <MkAvatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/> + <MkUserName :user="u" :nowrap="false" style="line-height: 24px;"/> + </b> + <span slot="omitted">+{{ count - 10 }}</span> + </template> </div> - <template v-if="users.length <= 10"> - <b v-for="u in users" :key="u.id" style="margin-right: 12px;"> - <MkAvatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/> - <MkUserName :user="u" :nowrap="false" style="line-height: 24px;"/> - </b> - </template> - <template v-if="10 < users.length"> - <b v-for="u in users" :key="u.id" style="margin-right: 12px;"> - <MkAvatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/> - <MkUserName :user="u" :nowrap="false" style="line-height: 24px;"/> - </b> - <span slot="omitted">+{{ count - 10 }}</span> - </template> </div> </MkTooltip> </template> @@ -59,8 +61,11 @@ export default defineComponent({ <style lang="scss" scoped> .bqxuuuey { - > .info { - padding: 0 0 8px 0; + display: flex; + + > .reaction { + flex: 1; + max-width: 100px; text-align: center; > .icon { @@ -68,6 +73,19 @@ export default defineComponent({ width: 60px; margin: 0 auto; } + + > .name { + font-size: 0.9em; + } + } + + > .users { + flex: 1; + min-width: 0; + font-size: 0.9em; + border-left: solid 0.5px var(--divider); + padding-left: 10px; + margin-left: 10px; } } </style> diff --git a/src/client/components/reactions-viewer.reaction.vue b/src/client/components/reactions-viewer.reaction.vue index 6754d13815..f47ba83f61 100644 --- a/src/client/components/reactions-viewer.reaction.vue +++ b/src/client/components/reactions-viewer.reaction.vue @@ -177,6 +177,7 @@ export default defineComponent({ > span { font-size: 0.9em; line-height: 32px; + margin: 0 0 0 4px; } } </style> diff --git a/src/client/components/ui/folder.vue b/src/client/components/ui/folder.vue index d0616a57c1..3997421d08 100644 --- a/src/client/components/ui/folder.vue +++ b/src/client/components/ui/folder.vue @@ -153,10 +153,4 @@ export default defineComponent({ } } } - -._fitSide_ .ssazuxis { - > header { - padding: 0 16px; - } -} </style> diff --git a/src/client/components/ui/info.vue b/src/client/components/ui/info.vue index e16f2736f1..f6b2edf267 100644 --- a/src/client/components/ui/info.vue +++ b/src/client/components/ui/info.vue @@ -42,8 +42,4 @@ export default defineComponent({ margin-right: 4px; } } - -._fitSide_ .fpezltsf { - border-radius: 0; -} </style> diff --git a/src/client/components/ui/super-menu.vue b/src/client/components/ui/super-menu.vue index 6ab94d744d..195cc57326 100644 --- a/src/client/components/ui/super-menu.vue +++ b/src/client/components/ui/super-menu.vue @@ -50,9 +50,6 @@ export default defineComponent({ border-top: solid 0.5px var(--divider); } - margin-left: 16px; - margin-right: 16px; - > .title { font-size: 0.9em; opacity: 0.7; @@ -120,7 +117,7 @@ export default defineComponent({ > .items { display: grid; - grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); grid-gap: 8px; padding: 0 16px; diff --git a/src/client/components/ui/tooltip.vue b/src/client/components/ui/tooltip.vue index de8c02ad4a..c003895c14 100644 --- a/src/client/components/ui/tooltip.vue +++ b/src/client/components/ui/tooltip.vue @@ -1,6 +1,6 @@ <template> <transition name="tooltip" appear @after-leave="$emit('closed')"> - <div class="buebdbiu _acrylic _shadow" v-show="showing" ref="content"> + <div class="buebdbiu _acrylic _shadow" v-show="showing" ref="content" :style="{ maxWidth: maxWidth + 'px' }"> <slot>{{ text }}</slot> </div> </transition> @@ -21,7 +21,12 @@ export default defineComponent({ text: { type: String, required: false - } + }, + maxWidth: { + type: Number, + required: false, + default: 250, + }, }, emits: ['closed'], @@ -75,11 +80,12 @@ export default defineComponent({ .buebdbiu { position: absolute; z-index: 11000; - max-width: 240px; font-size: 0.8em; padding: 8px 12px; + box-sizing: border-box; text-align: center; border-radius: 4px; + border: solid 0.5px var(--divider); pointer-events: none; transform-origin: center bottom; } |