diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-12-29 13:42:15 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-12-29 13:42:15 +0900 |
| commit | 621fc5a715e372064bb178a24f07c8aa960f7f50 (patch) | |
| tree | 4efab3afa32c533fc36bdb72c622619614125f5a /packages/client/src/components | |
| parent | Merge branch 'develop' (diff) | |
| parent | 12.101.0 (diff) | |
| download | misskey-621fc5a715e372064bb178a24f07c8aa960f7f50.tar.gz misskey-621fc5a715e372064bb178a24f07c8aa960f7f50.tar.bz2 misskey-621fc5a715e372064bb178a24f07c8aa960f7f50.zip | |
Merge branch 'develop'
Diffstat (limited to 'packages/client/src/components')
21 files changed, 300 insertions, 352 deletions
diff --git a/packages/client/src/components/emoji-picker.vue b/packages/client/src/components/emoji-picker.vue index ff450246f9..a8eed1ca21 100644 --- a/packages/client/src/components/emoji-picker.vue +++ b/packages/client/src/components/emoji-picker.vue @@ -77,7 +77,7 @@ import { defineComponent, markRaw } from 'vue'; import { emojilist } from '@/scripts/emojilist'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; -import Particle from '@/components/particle.vue'; +import Ripple from '@/components/ripple.vue'; import * as os from '@/os'; import { isTouchUsing } from '@/scripts/touch'; import { isMobile } from '@/scripts/is-mobile'; @@ -296,9 +296,9 @@ export default defineComponent({ if (ev) { const el = ev.currentTarget || ev.target; const rect = el.getBoundingClientRect(); - const x = rect.left + (el.clientWidth / 2); - const y = rect.top + (el.clientHeight / 2); - os.popup(Particle, { x, y }, {}, 'end'); + const x = rect.left + (el.offsetWidth / 2); + const y = rect.top + (el.offsetHeight / 2); + os.popup(Ripple, { x, y }, {}, 'end'); } const key = this.getKey(emoji); diff --git a/packages/client/src/components/form/input.vue b/packages/client/src/components/form/input.vue index c990b693f1..3533f4f27b 100644 --- a/packages/client/src/components/form/input.vue +++ b/packages/client/src/components/form/input.vue @@ -5,7 +5,7 @@ <div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div> <input ref="inputEl" v-model="v" - v-panel + v-adaptive-border :type="type" :disabled="disabled" :required="required" @@ -243,7 +243,8 @@ export default defineComponent({ font-weight: normal; font-size: 1em; color: var(--fg); - border: solid 0.5px var(--panel); + background: var(--panel); + border: solid 1px var(--panel); border-radius: 6px; outline: none; box-shadow: none; @@ -251,7 +252,7 @@ export default defineComponent({ transition: border-color 0.1s ease-out; &:hover { - border-color: var(--inputBorderHover); + border-color: var(--inputBorderHover) !important; } } @@ -298,7 +299,7 @@ export default defineComponent({ &.focused { > input { - border-color: var(--accent); + border-color: var(--accent) !important; //box-shadow: 0 0 0 4px var(--focus); } } diff --git a/packages/client/src/components/form/select.vue b/packages/client/src/components/form/select.vue index 9ecff1aa6f..afc53ca9c8 100644 --- a/packages/client/src/components/form/select.vue +++ b/packages/client/src/components/form/select.vue @@ -3,7 +3,9 @@ <div class="label" @click="focus"><slot name="label"></slot></div> <div ref="container" class="input" :class="{ inline, disabled, focused }" @click.prevent="onClick"> <div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div> - <select ref="inputEl" v-model="v" v-panel + <select ref="inputEl" + v-model="v" + v-adaptive-border class="select" :disabled="disabled" :required="required" @@ -226,7 +228,7 @@ export default defineComponent({ &:hover { > .select { - border-color: var(--inputBorderHover); + border-color: var(--inputBorderHover) !important; } } @@ -242,6 +244,7 @@ export default defineComponent({ font-weight: normal; font-size: 1em; color: var(--fg); + background: var(--panel); border: solid 1px var(--panel); border-radius: 6px; outline: none; @@ -295,7 +298,7 @@ export default defineComponent({ &.focused { > select { - border-color: var(--accent); + border-color: var(--accent) !important; } } diff --git a/packages/client/src/components/form/switch.vue b/packages/client/src/components/form/switch.vue index 239303a55a..aa9b09215e 100644 --- a/packages/client/src/components/form/switch.vue +++ b/packages/client/src/components/form/switch.vue @@ -2,10 +2,6 @@ <div class="ziffeoms" :class="{ disabled, checked }" - role="switch" - :aria-checked="checked" - :aria-disabled="disabled" - @click.prevent="toggle" > <input ref="input" @@ -13,18 +9,20 @@ :disabled="disabled" @keydown.enter="toggle" > - <span v-tooltip="checked ? $ts.itsOn : $ts.itsOff" class="button"> - <span class="handle"></span> + <span ref="button" v-adaptive-border v-tooltip="checked ? $ts.itsOn : $ts.itsOff" class="button" @click.prevent="toggle"> + <i class="check fas fa-check"></i> </span> <span class="label"> - <span><slot></slot></span> + <span @click="toggle"><slot></slot></span> <p class="caption"><slot name="caption"></slot></p> </span> </div> </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineComponent, ref, toRefs } from 'vue'; +import * as os from '@/os'; +import Ripple from '@/components/ripple.vue'; export default defineComponent({ props: { @@ -37,17 +35,28 @@ export default defineComponent({ default: false } }, - computed: { - checked(): boolean { - return this.modelValue; - } + + setup(props, context) { + const button = ref<HTMLElement>(); + const checked = toRefs(props).modelValue; + const toggle = () => { + if (props.disabled) return; + context.emit('update:modelValue', !checked.value); + + if (!checked.value) { + const rect = button.value.getBoundingClientRect(); + const x = rect.left + (button.value.offsetWidth / 2); + const y = rect.top + (button.value.offsetHeight / 2); + os.popup(Ripple, { x, y, particle: false }, {}, 'end'); + } + }; + + return { + button, + checked, + toggle, + }; }, - methods: { - toggle() { - if (this.disabled) return; - this.$emit('update:modelValue', !this.checked); - } - } }); </script> @@ -55,16 +64,7 @@ export default defineComponent({ .ziffeoms { position: relative; display: flex; - cursor: pointer; - transition: all 0.3s; - - &:first-child { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - } + transition: all 0.2s ease; > * { user-select: none; @@ -80,27 +80,32 @@ export default defineComponent({ > .button { position: relative; - display: inline-block; + display: inline-flex; flex-shrink: 0; margin: 0; - width: 36px; - height: 26px; - background: var(--switchBg); + box-sizing: border-box; + width: 23px; + height: 23px; outline: none; - border-radius: 999px; + background: var(--panel); + border: solid 1px var(--panel); + border-radius: 4px; + cursor: pointer; transition: inherit; - > .handle { - position: absolute; - top: 0; - bottom: 0; - left: 5px; - margin: auto 0; - border-radius: 100%; - transition: background-color 0.3s, transform 0.3s; - width: 16px; - height: 16px; - background-color: #fff; + > .check { + margin: auto; + opacity: 0; + color: var(--fgOnAccent); + font-size: 13px; + transform: scale(0.5); + transition: all 0.2s ease; + } + } + + &:hover { + > .button { + border-color: var(--inputBorderHover) !important; } } @@ -108,13 +113,13 @@ export default defineComponent({ margin-left: 16px; margin-top: 2px; display: block; - cursor: pointer; transition: inherit; color: var(--fg); > span { display: block; line-height: 20px; + cursor: pointer; transition: inherit; } @@ -129,12 +134,6 @@ export default defineComponent({ } } - &:hover { - > .button { - background-color: var(--accentedBg); - } - } - &.disabled { opacity: 0.6; cursor: not-allowed; @@ -142,11 +141,12 @@ export default defineComponent({ &.checked { > .button { - background-color: var(--accent); - border-color: var(--accent); + background-color: var(--accent) !important; + border-color: var(--accent) !important; - > .handle { - transform: translateX(10px); + > .check { + opacity: 1; + transform: scale(1); } } } diff --git a/packages/client/src/components/form/textarea.vue b/packages/client/src/components/form/textarea.vue index 98fd0da94b..c9ba9b97a2 100644 --- a/packages/client/src/components/form/textarea.vue +++ b/packages/client/src/components/form/textarea.vue @@ -4,7 +4,7 @@ <div class="input" :class="{ disabled, focused, tall, pre }"> <textarea ref="inputEl" v-model="v" - v-panel + v-adaptive-border :class="{ code, _monospace: code }" :disabled="disabled" :required="required" @@ -210,7 +210,8 @@ export default defineComponent({ font-weight: normal; font-size: 1em; color: var(--fg); - border: solid 0.5px var(--panel); + background: var(--panel); + border: solid 1px var(--panel); border-radius: 6px; outline: none; box-shadow: none; @@ -218,13 +219,13 @@ export default defineComponent({ transition: border-color 0.1s ease-out; &:hover { - border-color: var(--inputBorderHover); + border-color: var(--inputBorderHover) !important; } } &.focused { > textarea { - border-color: var(--accent); + border-color: var(--accent) !important; } } diff --git a/packages/client/src/components/global/a.vue b/packages/client/src/components/global/a.vue index 5db61203c6..77ee7525a4 100644 --- a/packages/client/src/components/global/a.vue +++ b/packages/client/src/components/global/a.vue @@ -106,11 +106,6 @@ export default defineComponent({ return; } - if (this.to.startsWith('/my/messaging')) { - if (ColdDeviceStorage.get('chatOpenBehavior') === 'window') return this.window(); - if (ColdDeviceStorage.get('chatOpenBehavior') === 'popout') return this.popout(); - } - if (this.behavior) { if (this.behavior === 'window') { return this.window(); diff --git a/packages/client/src/components/global/header.vue b/packages/client/src/components/global/header.vue index 2e03d783af..a241ece407 100644 --- a/packages/client/src/components/global/header.vue +++ b/packages/client/src/components/global/header.vue @@ -6,7 +6,7 @@ <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"/> + <MkUserName v-if="info.userName" :user="info.userName" :nowrap="true" class="title"/> <div v-else-if="info.title" class="title">{{ info.title }}</div> <div v-if="!narrow && info.subtitle" class="subtitle"> {{ info.subtitle }} @@ -268,6 +268,7 @@ export default defineComponent({ > .titleContainer { display: flex; align-items: center; + max-width: 400px; overflow: auto; white-space: nowrap; text-align: left; diff --git a/packages/client/src/components/global/url.vue b/packages/client/src/components/global/url.vue index 097fcddef6..56a8c3453a 100644 --- a/packages/client/src/components/global/url.vue +++ b/packages/client/src/components/global/url.vue @@ -1,7 +1,5 @@ <template> -<component :is="self ? 'MkA' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" - @mouseover="onMouseover" - @mouseleave="onMouseleave" +<component :is="self ? 'MkA' : 'a'" ref="el" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" @contextmenu.stop="() => {}" > <template v-if="!self"> @@ -20,11 +18,11 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineComponent, ref } from 'vue'; import { toUnicode as decodePunycode } from 'punycode/'; import { url as local } from '@/config'; -import { isTouchUsing } from '@/scripts/touch'; import * as os from '@/os'; +import { useTooltip } from '@/scripts/use-tooltip'; export default defineComponent({ props: { @@ -35,74 +33,36 @@ export default defineComponent({ rel: { type: String, required: false, + default: null, } }, - data() { - const self = this.url.startsWith(local); + setup(props) { + const self = props.url.startsWith(local); + const url = new URL(props.url); + const el = ref(); + + useTooltip(el, (showing) => { + os.popup(import('@/components/url-preview-popup.vue'), { + showing, + url: props.url, + source: el.value, + }, {}, 'closed'); + }); + return { local, - schema: null as string | null, - hostname: null as string | null, - port: null as string | null, - pathname: null as string | null, - query: null as string | null, - hash: null as string | null, + schema: url.protocol, + hostname: decodePunycode(url.hostname), + port: url.port, + pathname: decodeURIComponent(url.pathname), + query: decodeURIComponent(url.search), + hash: decodeURIComponent(url.hash), self: self, attr: self ? 'to' : 'href', target: self ? null : '_blank', - showTimer: null, - hideTimer: null, - checkTimer: null, - close: null, + el, }; }, - created() { - const url = new URL(this.url); - this.schema = url.protocol; - this.hostname = decodePunycode(url.hostname); - this.port = url.port; - this.pathname = decodeURIComponent(url.pathname); - this.query = decodeURIComponent(url.search); - this.hash = decodeURIComponent(url.hash); - }, - methods: { - async showPreview() { - if (!document.body.contains(this.$el)) return; - if (this.close) return; - - const { dispose } = await os.popup(import('@/components/url-preview-popup.vue'), { - url: this.url, - source: this.$el - }); - - this.close = () => { - dispose(); - }; - - this.checkTimer = setInterval(() => { - if (!document.body.contains(this.$el)) this.closePreview(); - }, 1000); - }, - closePreview() { - if (this.close) { - clearInterval(this.checkTimer); - this.close(); - this.close = null; - } - }, - onMouseover() { - if (isTouchUsing) return; - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.showTimer = setTimeout(this.showPreview, 500); - }, - onMouseleave() { - if (isTouchUsing) return; - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.hideTimer = setTimeout(this.closePreview, 500); - } - } }); </script> diff --git a/packages/client/src/components/media-list.vue b/packages/client/src/components/media-list.vue index c987ff5ff1..2970d06c97 100644 --- a/packages/client/src/components/media-list.vue +++ b/packages/client/src/components/media-list.vue @@ -105,6 +105,7 @@ export default defineComponent({ return { previewable, gallery, + pswpZIndex: os.claimZIndex('middle'), }; }, }); @@ -188,3 +189,11 @@ export default defineComponent({ } } </style> + +<style lang="scss"> +.pswp { + // なぜか機能しない + //z-index: v-bind(pswpZIndex); + z-index: 2000000; +} +</style> diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index d1da365d9a..2e6d26476a 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -175,14 +175,7 @@ export default defineComponent({ if (!this.$store.state.animatedMfm) { return genEl(token.children); } - let count = token.props.args.count ? parseInt(token.props.args.count) : 10; - if (count > 100) { - count = 100; - } - const speed = token.props.args.speed ? parseFloat(token.props.args.speed) : 1; - return h(MkSparkle, { - count, speed, - }, genEl(token.children)); + return h(MkSparkle, {}, genEl(token.children)); } case 'rotate': { const degrees = parseInt(token.props.args.deg) || '90'; diff --git a/packages/client/src/components/note-preview.vue b/packages/client/src/components/note-preview.vue index 6e3eba9306..bdcb8d5eed 100644 --- a/packages/client/src/components/note-preview.vue +++ b/packages/client/src/components/note-preview.vue @@ -7,7 +7,7 @@ </div> <div class="body"> <div class="content"> - <Mfm :text="text" :author="$i" :i="$i"/> + <Mfm :text="text.trim()" :author="$i" :i="$i"/> </div> </div> </div> @@ -61,6 +61,7 @@ export default defineComponent({ width: 40px; height: 40px; border-radius: 8px; + pointer-events: none; } > .main { @@ -69,6 +70,7 @@ export default defineComponent({ > .header { margin-bottom: 2px; + font-weight: bold; } > .body { diff --git a/packages/client/src/components/page-window.vue b/packages/client/src/components/page-window.vue index 39c185b3e0..ec7451d5aa 100644 --- a/packages/client/src/components/page-window.vue +++ b/packages/client/src/components/page-window.vue @@ -16,7 +16,13 @@ <template #headerLeft> <button v-if="history.length > 0" v-tooltip="$ts.goBack" class="_button" @click="back()"><i class="fas fa-arrow-left"></i></button> </template> - <div class="yrolvcoq"> + <template #headerRight> + <button v-tooltip="$ts.showInPage" class="_button" @click="expand()"><i class="fas fa-expand-alt"></i></button> + <button v-tooltip="$ts.popout" class="_button" @click="popout()"><i class="fas fa-external-link-alt"></i></button> + <button class="_button" @click="menu"><i class="fas fa-ellipsis-h"></i></button> + </template> + + <div class="yrolvcoq" :style="{ background: pageInfo?.bg }"> <MkStickyContainer> <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <component :is="component" v-bind="props" :ref="changePage"/> @@ -33,6 +39,7 @@ import copyToClipboard from '@/scripts/copy-to-clipboard'; import { resolve } from '@/router'; import { url } from '@/config'; import * as symbols from '@/symbols'; +import * as os from '@/os'; export default defineComponent({ components: { @@ -139,6 +146,23 @@ export default defineComponent({ this.props = props; }, + menu(ev) { + os.popupMenu([{ + icon: 'fas fa-external-link-alt', + text: this.$ts.openInNewTab, + action: () => { + window.open(this.url, '_blank'); + this.$refs.window.close(); + } + }, { + icon: 'fas fa-link', + text: this.$ts.copyLink, + action: () => { + copyToClipboard(this.url); + } + }], ev.currentTarget || ev.target); + }, + back() { this.navigate(this.history.pop(), false); }, diff --git a/packages/client/src/components/reactions-viewer.reaction.vue b/packages/client/src/components/reactions-viewer.reaction.vue index a1de99f018..bbf518549c 100644 --- a/packages/client/src/components/reactions-viewer.reaction.vue +++ b/packages/client/src/components/reactions-viewer.reaction.vue @@ -2,7 +2,7 @@ <button v-if="count > 0" ref="buttonRef" - v-particle="canToggle" + v-ripple="canToggle" class="hkzvhatu _button" :class="{ reacted: note.myReaction == reaction, canToggle }" @click="toggleReaction()" diff --git a/packages/client/src/components/particle.vue b/packages/client/src/components/ripple.vue index d82705c1e8..272eacbc6e 100644 --- a/packages/client/src/components/particle.vue +++ b/packages/client/src/components/ripple.vue @@ -1,5 +1,5 @@ <template> -<div class="vswabwbm" :style="{ top: `${y - 64}px`, left: `${x - 64}px` }" :class="{ active }"> +<div class="vswabwbm" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }" :class="{ active }"> <svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"> <circle fill="none" cx="64" cy="64"> <animate attributeName="r" @@ -52,7 +52,8 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineComponent, onMounted } from 'vue'; +import * as os from '@/os'; export default defineComponent({ props: { @@ -63,37 +64,46 @@ export default defineComponent({ y: { type: Number, required: true + }, + particle: { + type: Boolean, + required: false, + default: true, } }, emits: ['end'], - data() { + setup(props, context) { const particles = []; const origin = 64; const colors = ['#FF1493', '#00FFFF', '#FFE202']; - for (let i = 0; i < 12; i++) { - const angle = Math.random() * (Math.PI * 2); - const pos = Math.random() * 16; - const velocity = 16 + (Math.random() * 48); - particles.push({ - size: 4 + (Math.random() * 8), - xA: origin + (Math.sin(angle) * pos), - yA: origin + (Math.cos(angle) * pos), - xB: origin + (Math.sin(angle) * (pos + velocity)), - yB: origin + (Math.cos(angle) * (pos + velocity)), - color: colors[Math.floor(Math.random() * colors.length)] - }); + if (props.particle) { + for (let i = 0; i < 12; i++) { + const angle = Math.random() * (Math.PI * 2); + const pos = Math.random() * 16; + const velocity = 16 + (Math.random() * 48); + particles.push({ + size: 4 + (Math.random() * 8), + xA: origin + (Math.sin(angle) * pos), + yA: origin + (Math.cos(angle) * pos), + xB: origin + (Math.sin(angle) * (pos + velocity)), + yB: origin + (Math.cos(angle) * (pos + velocity)), + color: colors[Math.floor(Math.random() * colors.length)] + }); + } } + onMounted(() => { + setTimeout(() => { + context.emit('end'); + }, 1100); + }); + return { - particles + particles, + zIndex: os.claimZIndex('high'), }; }, - mounted() { - setTimeout(() => { - this.$emit('end'); - }, 1100); - } }); </script> @@ -101,7 +111,6 @@ export default defineComponent({ .vswabwbm { pointer-events: none; position: fixed; - z-index: 1000000; width: 128px; height: 128px; diff --git a/packages/client/src/components/signup.vue b/packages/client/src/components/signup.vue index 8668d1d076..38a9fd55f1 100644 --- a/packages/client/src/components/signup.vue +++ b/packages/client/src/components/signup.vue @@ -51,14 +51,13 @@ <span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</span> </template> </MkInput> - <label v-if="meta.tosUrl" class="_formBlock tou"> - <input v-model="ToSAgreement" type="checkbox"> + <MkSwitch v-if="meta.tosUrl" v-model="ToSAgreement" class="_formBlock tou"> <I18n :src="$ts.agreeTo"> <template #0> <a :href="meta.tosUrl" class="_link" target="_blank">{{ $ts.tos }}</a> </template> </I18n> - </label> + </MkSwitch> <captcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/> <captcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/> <MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ $ts.start }}</MkButton> @@ -258,11 +257,5 @@ export default defineComponent({ .captcha { margin: 16px 0; } - - > .tou { - display: block; - margin: 16px 0; - cursor: pointer; - } } </style> diff --git a/packages/client/src/components/sparkle.vue b/packages/client/src/components/sparkle.vue index 21b57f1580..f52e5a3f9b 100644 --- a/packages/client/src/components/sparkle.vue +++ b/packages/client/src/components/sparkle.vue @@ -1,161 +1,121 @@ <template> <span class="mk-sparkle"> - <span ref="content"> + <span ref="el"> <slot></slot> </span> - <canvas ref="canvas"></canvas> + <!-- なぜか path に対する key が機能しないため + <svg :width="width" :height="height" :viewBox="`0 0 ${width} ${height}`" xmlns="http://www.w3.org/2000/svg"> + <path v-for="particle in particles" :key="particle.id" style="transform-origin: center; transform-box: fill-box;" + :transform="`translate(${particle.x} ${particle.y})`" + :fill="particle.color" + d="M29.427,2.011C29.721,0.83 30.782,0 32,0C33.218,0 34.279,0.83 34.573,2.011L39.455,21.646C39.629,22.347 39.991,22.987 40.502,23.498C41.013,24.009 41.653,24.371 42.354,24.545L61.989,29.427C63.17,29.721 64,30.782 64,32C64,33.218 63.17,34.279 61.989,34.573L42.354,39.455C41.653,39.629 41.013,39.991 40.502,40.502C39.991,41.013 39.629,41.653 39.455,42.354L34.573,61.989C34.279,63.17 33.218,64 32,64C30.782,64 29.721,63.17 29.427,61.989L24.545,42.354C24.371,41.653 24.009,41.013 23.498,40.502C22.987,39.991 22.347,39.629 21.646,39.455L2.011,34.573C0.83,34.279 0,33.218 0,32C0,30.782 0.83,29.721 2.011,29.427L21.646,24.545C22.347,24.371 22.987,24.009 23.498,23.498C24.009,22.987 24.371,22.347 24.545,21.646L29.427,2.011Z" + > + <animateTransform + attributeName="transform" + attributeType="XML" + type="rotate" + from="0 0 0" + to="360 0 0" + :dur="`${particle.dur}ms`" + repeatCount="indefinite" + additive="sum" + /> + <animateTransform + attributeName="transform" + attributeType="XML" + type="scale" + :values="`0; ${particle.size}; 0`" + :dur="`${particle.dur}ms`" + repeatCount="indefinite" + additive="sum" + /> + </path> + </svg> + --> + <svg v-for="particle in particles" :key="particle.id" :width="width" :height="height" :viewBox="`0 0 ${width} ${height}`" xmlns="http://www.w3.org/2000/svg"> + <path style="transform-origin: center; transform-box: fill-box;" + :transform="`translate(${particle.x} ${particle.y})`" + :fill="particle.color" + d="M29.427,2.011C29.721,0.83 30.782,0 32,0C33.218,0 34.279,0.83 34.573,2.011L39.455,21.646C39.629,22.347 39.991,22.987 40.502,23.498C41.013,24.009 41.653,24.371 42.354,24.545L61.989,29.427C63.17,29.721 64,30.782 64,32C64,33.218 63.17,34.279 61.989,34.573L42.354,39.455C41.653,39.629 41.013,39.991 40.502,40.502C39.991,41.013 39.629,41.653 39.455,42.354L34.573,61.989C34.279,63.17 33.218,64 32,64C30.782,64 29.721,63.17 29.427,61.989L24.545,42.354C24.371,41.653 24.009,41.013 23.498,40.502C22.987,39.991 22.347,39.629 21.646,39.455L2.011,34.573C0.83,34.279 0,33.218 0,32C0,30.782 0.83,29.721 2.011,29.427L21.646,24.545C22.347,24.371 22.987,24.009 23.498,23.498C24.009,22.987 24.371,22.347 24.545,21.646L29.427,2.011Z" + > + <animateTransform + attributeName="transform" + attributeType="XML" + type="rotate" + from="0 0 0" + to="360 0 0" + :dur="`${particle.dur}ms`" + repeatCount="1" + additive="sum" + /> + <animateTransform + attributeName="transform" + attributeType="XML" + type="scale" + :values="`0; ${particle.size}; 0`" + :dur="`${particle.dur}ms`" + repeatCount="1" + additive="sum" + /> + </path> + </svg> </span> </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineComponent, onMounted, onUnmounted, ref } from 'vue'; import * as os from '@/os'; -const sprite = new Image(); -sprite.src = '/client-assets/sparkle-spritesheet.png'; - export default defineComponent({ - props: { - count: { - type: Number, - required: true, - }, - speed: { - type: Number, - required: true, - }, - }, - data() { - return { - sprites: [0,6,13,20], - particles: [], - anim: null, - ctx: null, - }; - }, - unmounted() { - window.cancelAnimationFrame(this.anim); - }, - mounted() { - this.ctx = this.$refs.canvas.getContext('2d'); - - new ResizeObserver(this.resize).observe(this.$refs.content); - - this.resize(); - this.tick(); - }, - updated() { - this.resize(); - }, - methods: { - createSparkles(w, h, count) { - const holder = []; - - for (let i = 0; i < count; i++) { - - const color = '#' + ('000000' + Math.floor(Math.random() * 16777215).toString(16)).slice(-6); - - holder[i] = { - position: { - x: Math.floor(Math.random() * w), - y: Math.floor(Math.random() * h) - }, - style: this.sprites[ Math.floor(Math.random() * 4) ], - delta: { - x: Math.floor(Math.random() * 1000) - 500, - y: Math.floor(Math.random() * 1000) - 500 - }, - color: color, - opacity: Math.random(), - }; - - } - - return holder; - }, - draw(time) { - this.ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height); - this.ctx.beginPath(); + setup() { + const particles = ref([]); + const el = ref<HTMLElement>(); + const width = ref(0); + const height = ref(0); + const colors = ['#FF1493', '#00FFFF', '#FFE202', '#FFE202', '#FFE202']; - const particleSize = Math.floor(this.fontSize / 2); - this.particles.forEach((particle) => { - var modulus = Math.floor(Math.random()*7); - - if (Math.floor(time) % modulus === 0) { - particle.style = this.sprites[ Math.floor(Math.random()*4) ]; - } - - this.ctx.save(); - this.ctx.globalAlpha = particle.opacity; - this.ctx.drawImage(sprite, particle.style, 0, 7, 7, particle.position.x, particle.position.y, particleSize, particleSize); - - this.ctx.globalCompositeOperation = "source-atop"; - this.ctx.globalAlpha = 0.5; - this.ctx.fillStyle = particle.color; - this.ctx.fillRect(particle.position.x, particle.position.y, particleSize, particleSize); - - this.ctx.restore(); + onMounted(() => { + const ro = new ResizeObserver((entries, observer) => { + width.value = el.value?.offsetWidth + 64; + height.value = el.value?.offsetHeight + 64; }); - this.ctx.stroke(); - }, - tick() { - this.anim = window.requestAnimationFrame((time) => { - if (!this.$refs.canvas) { - return; - } - this.particles.forEach((particle) => { - if (!particle) { - return; - } - var randX = Math.random() > Math.random() * 2; - var randY = Math.random() > Math.random() * 3; - - if (randX) { - particle.position.x += (particle.delta.x * this.speed) / 1500; - } - - if (!randY) { - particle.position.y -= (particle.delta.y * this.speed) / 800; - } - - if( particle.position.x > this.$refs.canvas.width ) { - particle.position.x = -7; - } else if (particle.position.x < -7) { - particle.position.x = this.$refs.canvas.width; - } - - if (particle.position.y > this.$refs.canvas.height) { - particle.position.y = -7; - particle.position.x = Math.floor(Math.random() * this.$refs.canvas.width); - } else if (particle.position.y < -7) { - particle.position.y = this.$refs.canvas.height; - particle.position.x = Math.floor(Math.random() * this.$refs.canvas.width); - } - - particle.opacity -= 0.005; - - if (particle.opacity <= 0) { - particle.opacity = 1; - } - }); - - this.draw(time); + ro.observe(el.value); + let stop = false; + const add = () => { + if (stop) return; + const x = (Math.random() * (width.value - 64)); + const y = (Math.random() * (height.value - 64)); + const sizeFactor = Math.random(); + const particle = { + id: Math.random().toString(), + x, + y, + size: 0.2 + ((sizeFactor / 10) * 3), + dur: 1000 + (sizeFactor * 1000), + color: colors[Math.floor(Math.random() * colors.length)], + }; + particles.value.push(particle); + window.setTimeout(() => { + particles.value = particles.value.filter(x => x.id !== particle.id); + }, particle.dur - 100); - this.tick(); + window.setTimeout(() => { + add(); + }, 500 + (Math.random() * 500)); + }; + add(); + onUnmounted(() => { + ro.disconnect(); + stop = true; }); - }, - resize() { - if (this.$refs.content) { - const contentRect = this.$refs.content.getBoundingClientRect(); - this.fontSize = parseFloat(getComputedStyle(this.$refs.content).fontSize); - const padding = this.fontSize * 0.2; + }); - this.$refs.canvas.width = parseInt(contentRect.width + padding); - this.$refs.canvas.height = parseInt(contentRect.height + padding); - - this.particles = this.createSparkles(this.$refs.canvas.width, this.$refs.canvas.height, this.count); - } - }, + return { + el, + width, + height, + particles, + }; }, }); </script> @@ -169,10 +129,10 @@ export default defineComponent({ display: inline-block; } - > canvas { + > svg { position: absolute; - top: -0.1em; - left: -0.1em; + top: -32px; + left: -32px; pointer-events: none; } } diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue index 869709cf21..6f3f277b11 100644 --- a/packages/client/src/components/ui/menu.vue +++ b/packages/client/src/components/ui/menu.vue @@ -284,7 +284,7 @@ export default defineComponent({ } &.asDrawer { - padding: 12px 0; + padding: 12px 0 calc(env(safe-area-inset-bottom, 0px) + 12px) 0; width: 100%; > .item { diff --git a/packages/client/src/components/ui/modal.vue b/packages/client/src/components/ui/modal.vue index b09d04c450..3e2e59b27c 100644 --- a/packages/client/src/components/ui/modal.vue +++ b/packages/client/src/components/ui/modal.vue @@ -13,6 +13,7 @@ import { defineComponent, nextTick, onMounted, computed, PropType, ref, watch } from 'vue'; import * as os from '@/os'; import { isTouchUsing } from '@/scripts/touch'; +import { defaultStore } from '@/store'; function getFixedContainer(el: Element | null): Element | null { if (el == null || el.tagName === 'BODY') return null; @@ -77,7 +78,7 @@ export default defineComponent({ const zIndex = os.claimZIndex(props.zPriority); const type = computed(() => { if (props.preferType === 'auto') { - if (isTouchUsing && window.innerWidth < 500 && window.innerHeight < 1000) { + if (!defaultStore.state.disableDrawer && isTouchUsing && window.innerWidth < 500 && window.innerHeight < 1000) { return 'drawer'; } else { return props.src != null ? 'popup' : 'dialog'; diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue index 00200efd3c..64af4a54f7 100644 --- a/packages/client/src/components/ui/pagination.vue +++ b/packages/client/src/components/ui/pagination.vue @@ -5,7 +5,12 @@ <MkError v-else-if="error" @retry="init()"/> <div v-else-if="empty" key="_empty_" class="empty"> - <slot name="empty"></slot> + <slot name="empty"> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.nothing }}</div> + </div> + </slot> </div> <div v-else class="cxiknjgy"> diff --git a/packages/client/src/components/ui/window.vue b/packages/client/src/components/ui/window.vue index d01498d8df..bd33289ccc 100644 --- a/packages/client/src/components/ui/window.vue +++ b/packages/client/src/components/ui/window.vue @@ -414,6 +414,10 @@ export default defineComponent({ } } + > .left { + min-width: 16px; + } + > .title { flex: 1; position: relative; @@ -421,7 +425,6 @@ export default defineComponent({ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - text-align: center; cursor: move; } } diff --git a/packages/client/src/components/updated.vue b/packages/client/src/components/updated.vue index 74f54524be..375ac0dbbb 100644 --- a/packages/client/src/components/updated.vue +++ b/packages/client/src/components/updated.vue @@ -1,7 +1,7 @@ <template> <MkModal ref="modal" :z-priority="'middle'" @click="$refs.modal.close()" @closed="$emit('closed')"> <div class="ewlycnyt"> - <div class="title">{{ $ts.misskeyUpdated }}</div> + <div class="title"><MkSparkle>{{ $ts.misskeyUpdated }}</MkSparkle></div> <div class="version">✨{{ version }}🚀</div> <MkButton full @click="whatIsNew">{{ $ts.whatIsNew }}</MkButton> <MkButton class="gotIt" primary full @click="$refs.modal.close()">{{ $ts.gotIt }}</MkButton> @@ -9,31 +9,19 @@ </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; import MkModal from '@/components/ui/modal.vue'; import MkButton from '@/components/ui/button.vue'; +import MkSparkle from '@/components/sparkle.vue'; import { version } from '@/config'; -export default defineComponent({ - components: { - MkModal, - MkButton, - }, +const modal = ref(); - data() { - return { - version: version, - }; - }, - - methods: { - whatIsNew() { - this.$refs.modal.close(); - window.open(`https://misskey-hub.net/docs/releases.html#_${version.replace(/\./g, '-')}`, '_blank'); - } - } -}); +const whatIsNew = () => { + modal.value.close(); + window.open(`https://misskey-hub.net/docs/releases.html#_${version.replace(/\./g, '-')}`, '_blank'); +}; </script> <style lang="scss" scoped> |