enh(frontend): 斜線デザインの調整, Mk.*CardMiniの改修 (taiyme#289)

This commit is contained in:
taiyme 2024-11-02 20:39:51 +09:00 committed by kakkokari-gtyih
parent 224bbd486f
commit f90ed6d3ba
14 changed files with 205 additions and 126 deletions

View file

@ -4,114 +4,158 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="[$style.root, { yellow: instance.isNotResponding, red: instance.isBlocked, gray: instance.isSuspended, blue: instance.isSilenced }]">
<img class="icon" :src="getInstanceIcon(instance)" alt="" loading="lazy"/>
<div class="body">
<span class="host">{{ instance.name ?? instance.host }}</span>
<span class="sub _monospace"><b>{{ instance.host }}</b> / {{ instance.softwareName || '?' }} {{ instance.softwareVersion }}</span>
<div
:class="[$style.root, {
[$style.isNotResponding]: instance.isNotResponding,
[$style.isSilenced]: instance.isSilenced,
[$style.isSuspended]: instance.isSuspended,
[$style.isBlocked]: instance.isBlocked,
}]"
>
<img :class="$style.icon" :src="getInstanceIcon(instance)" alt="" loading="lazy"/>
<div :class="$style.body">
<span :class="$style.host">{{ instance.name || instance.host }}</span>
<span :class="$style.sub">
<span class="_monospace">
<span style="font-weight: 700;">{{ instance.host }}</span> / {{ instance.softwareName || '?' }} {{ instance.softwareVersion }}
</span>
</span>
</div>
<MkMiniChart v-if="chartValues" class="chart" :src="chartValues"/>
<MkMiniChart v-if="chartValues" :class="$style.chart" :src="chartValues"/>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { shallowRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import MkMiniChart from '@/components/MkMiniChart.vue';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
import MkMiniChart from '@/components/MkMiniChart.vue';
const props = defineProps<{
instance: Misskey.entities.FederationInstance;
}>();
const chartValues = ref<number[] | null>(null);
const chartValues = shallowRef<number[] | null>(null);
misskeyApiGet('charts/instance', { host: props.instance.host, limit: 16 + 1, span: 'day' }).then(res => {
//
res.requests.received.splice(0, 1);
chartValues.value = res.requests.received;
watch(() => props.instance.host, (host) => {
misskeyApiGet('charts/instance', {
host,
limit: 16 + 1,
span: 'day',
}).then(res => {
//
res.requests.received.shift();
chartValues.value = res.requests.received;
}).catch(() => {
chartValues.value = null;
});
}, {
immediate: true,
});
function getInstanceIcon(instance): string {
return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png';
}
const getInstanceIcon = (instance: Misskey.entities.FederationInstance) => (
getProxiedImageUrlNullable(instance.iconUrl, 'preview')
?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview')
?? '/client-assets/dummy.png'
);
</script>
<style lang="scss" module>
.root {
$bodyTitleHieght: 18px;
$bodyInfoHieght: 16px;
$bodyTitleHieght: 18px;
$bodyInfoHieght: 16px;
.root {
display: flex;
align-items: center;
padding: 16px;
background: var(--MI_THEME-panel);
border-radius: 8px;
background-color: var(--MI_THEME-panel);
background-image: repeating-linear-gradient(
135deg,
transparent,
transparent 10px,
var(--c) 6px,
var(--c) 16px
);
--c: transparent;
> :global(.icon) {
display: block;
width: ($bodyTitleHieght + $bodyInfoHieght);
height: ($bodyTitleHieght + $bodyInfoHieght);
object-fit: cover;
border-radius: 4px;
margin-right: 10px;
}
> :global(.body) {
flex: 1;
overflow: hidden;
font-size: 0.9em;
color: var(--MI_THEME-fg);
padding-right: 8px;
> :global(.host) {
display: block;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: $bodyTitleHieght;
&,
html[data-color-scheme=light] & {
&.isNotResponding {
--c: color(from color-mix(in srgb, var(--MI_THEME-panel), orange 50%) srgb r g b / 0.25);
}
> :global(.sub) {
display: block;
width: 100%;
font-size: 80%;
opacity: 0.7;
line-height: $bodyInfoHieght;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.isSilenced {
--c: color(from color-mix(in srgb, var(--MI_THEME-panel), blue 50%) srgb r g b / 0.25);
}
&.isSuspended {
--c: color(from color-mix(in srgb, var(--MI_THEME-panel), black 15%) srgb r g b / 0.25);
}
&.isBlocked {
--c: color(from color-mix(in srgb, var(--MI_THEME-panel), red 50%) srgb r g b / 0.25);
}
}
> :global(.chart) {
height: 30px;
}
html[data-color-scheme=dark] & {
&.isNotResponding {
--c: color(from color-mix(in srgb, var(--MI_THEME-panel), orange 50%) srgb r g b / 0.5);
}
&:global(.blue) {
--c: rgba(0, 42, 255, 0.15);
background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
background-size: 16px 16px;
}
&.isSilenced {
--c: color(from color-mix(in srgb, var(--MI_THEME-panel), blue 50%) srgb r g b / 0.5);
}
&:global(.yellow) {
--c: rgb(255 196 0 / 15%);
background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
background-size: 16px 16px;
}
&.isSuspended {
--c: color(from color-mix(in srgb, var(--MI_THEME-panel), white 15%) srgb r g b / 0.5);
}
&:global(.red) {
--c: rgb(255 0 0 / 15%);
background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
background-size: 16px 16px;
}
&:global(.gray) {
--c: var(--MI_THEME-bg);
background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
background-size: 16px 16px;
&.isBlocked {
--c: color(from color-mix(in srgb, var(--MI_THEME-panel), red 50%) srgb r g b / 0.5);
}
}
}
.icon {
display: block;
width: ($bodyTitleHieght + $bodyInfoHieght);
height: ($bodyTitleHieght + $bodyInfoHieght);
object-fit: cover;
border-radius: 4px;
margin-right: 12px;
}
.body {
flex: 1;
overflow: hidden;
font-size: 0.9em;
color: var(--fg);
padding-right: 8px;
}
.host {
display: block;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: $bodyTitleHieght;
}
.sub {
display: block;
width: 100%;
font-size: 80%;
opacity: 0.7;
line-height: $bodyInfoHieght;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chart {
height: 30px;
}
</style>

View file

@ -166,7 +166,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #default="{ items }">
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;">
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
<MkUserCardMini :user="item.user" :withChart="false"/>
<MkUserCardMini :user="item.user"/>
</MkA>
</div>
</template>
@ -183,7 +183,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #default="{ items }">
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;">
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
<MkUserCardMini :user="item.user" :withChart="false"/>
<MkUserCardMini :user="item.user"/>
</MkA>
</div>
</template>

View file

@ -4,7 +4,12 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div v-adaptive-bg :class="[$style.root]">
<div
:class="[$style.root, {
[$style.isSilenced]: 'isSilenced' in user && user.isSilenced,
[$style.isSuspended]: 'isSuspended' in user && user.isSuspended,
}]"
>
<MkAvatar :class="$style.avatar" :user="user" indicator/>
<div :class="$style.body">
<span :class="$style.name"><MkUserName :user="user"/></span>
@ -15,29 +20,42 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { shallowRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { onMounted, ref } from 'vue';
import MkMiniChart from '@/components/MkMiniChart.vue';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { acct } from '@/filters/user.js';
import MkMiniChart from '@/components/MkMiniChart.vue';
const props = withDefaults(defineProps<{
user: Misskey.entities.User;
withChart?: boolean;
}>(), {
withChart: true,
withChart: false,
});
const chartValues = ref<number[] | null>(null);
const chartValues = shallowRef<number[] | null>(null);
onMounted(() => {
if (props.withChart) {
misskeyApiGet('charts/user/notes', { userId: props.user.id, limit: 16 + 1, span: 'day' }).then(res => {
watch([
() => props.user.id,
() => props.withChart,
], ([userId, withChart]) => {
if (withChart) {
misskeyApiGet('charts/user/notes', {
userId,
limit: 16 + 1,
span: 'day',
}).then(res => {
//
res.inc.splice(0, 1);
res.inc.shift();
chartValues.value = res.inc;
}).catch(() => {
chartValues.value = null;
});
} else {
chartValues.value = null;
}
}, {
immediate: true,
});
</script>
@ -49,8 +67,37 @@ $bodyInfoHieght: 16px;
display: flex;
align-items: center;
padding: 16px;
background: var(--MI_THEME-panel);
border-radius: 8px;
background-color: var(--MI-THEME-panel);
background-image: repeating-linear-gradient(
135deg,
transparent,
transparent 10px,
var(--c) 6px,
var(--c) 16px
);
--c: transparent;
&,
html[data-color-scheme=light] & {
&.isSilenced {
--c: color(from color-mix(in srgb, var(--MI_THEME-panel), blue 50%) srgb r g b / 0.25);
}
&.isSuspended {
--c: color(from color-mix(in srgb, var(--MI_THEME-panel), black 15%) srgb r g b / 0.25);
}
}
html[data-color-scheme=dark] & {
&.isSilenced {
--c: color(from color-mix(in srgb, var(--MI_THEME-panel), blue 50%) srgb r g b / 0.5);
}
&.isSuspended {
--c: color(from color-mix(in srgb, var(--MI_THEME-panel), white 15%) srgb r g b / 0.5);
}
}
}
.avatar {

View file

@ -4,13 +4,9 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="[$style.spacer, defaultStore.reactiveState.darkMode.value ? $style.dark : $style.light]"></div>
<div :class="$style.spacer"></div>
</template>
<script lang="ts" setup>
import { defaultStore } from '@/store.js';
</script>
<style lang="scss" module>
.spacer {
box-sizing: border-box;
@ -18,15 +14,21 @@ import { defaultStore } from '@/store.js';
margin: 0 auto;
height: 300px;
background-clip: content-box;
background-size: auto auto;
background-color: rgba(255, 255, 255, 0);
background-image: repeating-linear-gradient(
135deg,
transparent,
transparent 10px,
var(--c) 6px,
var(--c) 16px
);
&.light {
background-image: repeating-linear-gradient(135deg, transparent, transparent 16px, #00000010 16px, #00000010 20px );
&,
html[data-color-scheme=light] & {
--c: rgb(0 0 0 / 0.02);
}
&.dark {
background-image: repeating-linear-gradient(135deg, transparent, transparent 16px, #FFFFFF16 16px, #FFFFFF16 20px );
html[data-color-scheme=dark] & {
--c: rgb(255 255 255 / 0.02);
}
}
</style>

View file

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div v-if="show" ref="el" :class="[$style.root]" :style="{ background: bg }">
<div v-if="show" ref="el" :class="[$style.root]">
<div :class="[$style.upper, { [$style.slim]: narrow, [$style.thin]: thin_ }]">
<div v-if="!thin_ && narrow && props.displayMyAvatar && $i" class="_button" :class="$style.buttonsLeft" @click="openAccountMenu">
<MkAvatar :class="$style.avatar" :user="$i"/>
@ -42,13 +42,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, inject, shallowRef, computed } from 'vue';
import tinycolor from 'tinycolor2';
import XTabs, { Tab } from './MkPageHeader.tabs.vue';
import { scrollToTop } from '@@/js/scroll.js';
import { globalEvents } from '@/events.js';
import XTabs, { Tab } from './MkPageHeader.tabs.vue';
import type { PageHeaderItem } from '@/types/page-header.js';
import { injectReactiveMetadata } from '@/scripts/page-metadata.js';
import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
import { PageHeaderItem } from '@/types/page-header.js';
const props = withDefaults(defineProps<{
tabs?: Tab[];
@ -70,7 +68,6 @@ const hideTitle = inject('shouldOmitHeaderTitle', false);
const thin_ = props.thin || inject('shouldHeaderThin', false);
const el = shallowRef<HTMLElement | undefined>(undefined);
const bg = ref<string | undefined>(undefined);
const narrow = ref(false);
const hasTabs = computed(() => props.tabs.length > 0);
const hasActions = computed(() => props.actions && props.actions.length > 0);
@ -98,19 +95,9 @@ function onTabClick(): void {
top();
}
const calcBg = () => {
const rawBg = 'var(--MI_THEME-bg)';
const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
tinyBg.setAlpha(0.85);
bg.value = tinyBg.toRgbString();
};
let ro: ResizeObserver | null;
onMounted(() => {
calcBg();
globalEvents.on('themeChanged', calcBg);
if (el.value && el.value.parentElement) {
narrow.value = el.value.parentElement.offsetWidth < 500;
ro = new ResizeObserver((entries, observer) => {
@ -123,7 +110,6 @@ onMounted(() => {
});
onUnmounted(() => {
globalEvents.off('themeChanged', calcBg);
if (ro) ro.disconnect();
});
</script>
@ -134,6 +120,7 @@ onUnmounted(() => {
backdrop-filter: var(--MI-blur, blur(15px));
border-bottom: solid 0.5px var(--MI_THEME-divider);
width: 100%;
background: color(from var(--MI_THEME-bg) srgb r g b / 0.85);
}
.upper,

View file

@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkKeyValue>
</div>
<MkA v-if="file.user" class="user" :to="`/admin/user/${file.user.id}`">
<MkUserCardMini :user="file.user"/>
<MkUserCardMini :user="file.user" withChart/>
</MkA>
<div>
<MkSwitch v-model="isSensitive" @update:modelValue="toggleIsSensitive">{{ i18n.ts.sensitive }}</MkSwitch>

View file

@ -8,8 +8,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
<MkLoading v-if="fetching"/>
<div v-else class="users">
<MkA v-for="(user, i) in newUsers" :key="user.id" :to="`/admin/user/${user.id}`" class="user">
<MkUserCardMini :user="user"/>
<MkA v-for="user in newUsers" :key="user.id" :to="`/admin/user/${user.id}`" class="user">
<MkUserCardMini :user="user" withChart/>
</MkA>
</div>
</Transition>

View file

@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-for="item in items" :key="item.user.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedItems.includes(item.id) }]">
<div :class="$style.userItemMain">
<MkA :class="$style.userItemMainBody" :to="`/admin/user/${item.user.id}`">
<MkUserCardMini :user="item.user"/>
<MkUserCardMini :user="item.user" withChart/>
</MkA>
<button class="_button" :class="$style.userToggle" @click="toggleItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
<button class="_button" :class="$style.unassign" @click="unassign(item.user, $event)"><i class="ti ti-x"></i></button>
@ -49,7 +49,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
</div>
</div>
</div>
</template>
</MkPagination>
</div>

View file

@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination">
<div :class="$style.users">
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" :class="$style.user" :to="`/admin/user/${user.id}`">
<MkUserCardMini :user="user"/>
<MkUserCardMini :user="user" withChart/>
</MkA>
</div>
</MkPagination>

View file

@ -119,7 +119,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-else-if="tab === 'users'" key="users" class="_gaps_m">
<MkPagination v-slot="{items}" :pagination="usersPagination" style="display: grid; grid-template-columns: repeat(auto-fill,minmax(270px,1fr)); grid-gap: 12px;">
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/admin/user/${user.id}`">
<MkUserCardMini :user="user"/>
<MkUserCardMini :user="user" withChart/>
</MkA>
</MkPagination>
</div>

View file

@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_s">
<div v-for="user in users" :key="user.id" :class="$style.userItem">
<MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
<MkUserCardMini :user="user"/>
<MkUserCardMini :user="user" withChart/>
</MkA>
</div>
</div>

View file

@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-for="item in items" :key="item.id">
<div :class="$style.userItem">
<MkA :class="$style.userItemBody" :to="`${userPage(item.user)}`">
<MkUserCardMini :user="item.user"/>
<MkUserCardMini :user="item.user" withChart/>
</MkA>
<button class="_button" :class="$style.menu" @click="showMembershipMenu(item, $event)"><i class="ti ti-dots"></i></button>
<button class="_button" :class="$style.remove" @click="removeUser(item, $event)"><i class="ti ti-x"></i></button>

View file

@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps">
<div :class="$style.userItem">
<MkUserCardMini v-if="user" :class="$style.userCard" :user="user" :withChart="false"/>
<MkUserCardMini v-if="user" :class="$style.userCard" :user="user"/>
<MkButton v-if="user == null && $i != null" transparent :class="$style.addMeButton" @click="selectSelf"><div :class="$style.addUserButtonInner"><span><i class="ti ti-plus"></i><i class="ti ti-user"></i></span><span>{{ i18n.ts.selectSelf }}</span></div></MkButton>
<MkButton v-if="user == null" transparent :class="$style.addUserButton" @click="selectUser"><div :class="$style.addUserButtonInner"><i class="ti ti-plus"></i><span>{{ i18n.ts.selectUser }}</span></div></MkButton>
<button class="_button" :class="$style.remove" :disabled="user == null" @click="removeUser"><i class="ti ti-x"></i></button>

View file

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton @click="init"><i class="ti ti-refresh"></i> {{ i18n.ts.reloadAccountsList }}</MkButton>
</div>
<MkUserCardMini v-for="user in accounts" :key="user.id" :user="user" :class="$style.user" @click.prevent="menu(user, $event)"/>
<MkUserCardMini v-for="user in accounts" :key="user.id" :user="user" withChart :class="$style.user" @click.prevent="menu(user, $event)"/>
</div>
</FormSuspense>
</div>