This commit is contained in:
syuilo 2023-03-01 14:22:53 +09:00
parent 9e5278d276
commit d0bbeeee52
3 changed files with 90 additions and 72 deletions

View file

@ -69,6 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
return await Promise.all(assigns.map(async assign => ({ return await Promise.all(assigns.map(async assign => ({
id: assign.id, id: assign.id,
createdAt: assign.createdAt,
user: await this.userEntityService.pack(assign.user!, me, { detail: true }), user: await this.userEntityService.pack(assign.user!, me, { detail: true }),
expiresAt: assign.expiresAt, expiresAt: assign.expiresAt,
}))); })));

View file

@ -30,13 +30,20 @@
<template #default="{ items }"> <template #default="{ items }">
<div class="_gaps_s"> <div class="_gaps_s">
<div v-for="item in items" :key="item.user.id" :class="$style.userItem"> <div v-for="item in items" :key="item.user.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedItems.includes(item.id) }]">
<MkA :class="$style.user" :to="`/user-info/${item.user.id}`"> <div :class="$style.userItemMain">
<MkA :class="$style.userItemMainBody" :to="`/user-info/${item.user.id}`">
<MkUserCardMini :user="item.user"/> <MkUserCardMini :user="item.user"/>
</MkA> </MkA>
<button v-if="item.expiresAt != null" class="_button" :class="$style.expiresAt" @click="showExpireInfo(item, $event)"><i class="ti ti-clock-hour-3"></i></button> <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> <button class="_button" :class="$style.unassign" @click="unassign(item.user, $event)"><i class="ti ti-x"></i></button>
</div> </div>
<div v-if="expandedItems.includes(item.id)" :class="$style.userItemSub">
<div>Assigned: <MkTime :time="item.createdAt" mode="detail"/></div>
<div v-if="item.expiresAt">Period: {{ item.expiresAt.toLocaleString() }}</div>
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
</div>
</div>
</div> </div>
</template> </template>
</MkPagination> </MkPagination>
@ -77,6 +84,8 @@ const usersPagination = {
})), })),
}; };
let expandedItems = $ref([]);
const role = reactive(await os.api('admin/roles/show', { const role = reactive(await os.api('admin/roles/show', {
roleId: props.id, roleId: props.id,
})); }));
@ -129,7 +138,7 @@ async function assign() {
: null; : null;
await os.apiWithDialog('admin/roles/assign', { roleId: role.id, userId: user.id, expiresAt }); await os.apiWithDialog('admin/roles/assign', { roleId: role.id, userId: user.id, expiresAt });
role.users.push(user); //role.users.push(user);
} }
async function unassign(user, ev) { async function unassign(user, ev) {
@ -139,16 +148,17 @@ async function unassign(user, ev) {
danger: true, danger: true,
action: async () => { action: async () => {
await os.apiWithDialog('admin/roles/unassign', { roleId: role.id, userId: user.id }); await os.apiWithDialog('admin/roles/unassign', { roleId: role.id, userId: user.id });
role.users = role.users.filter(u => u.id !== user.id); //role.users = role.users.filter(u => u.id !== user.id);
}, },
}], ev.currentTarget ?? ev.target); }], ev.currentTarget ?? ev.target);
} }
async function showExpireInfo(assignment) { async function toggleItem(item) {
os.alert({ if (expandedItems.includes(item.id)) {
type: 'info', expandedItems = expandedItems.filter(x => x !== item.id);
text: assignment.expiresAt.toLocaleString(), } else {
}); expandedItems.push(item.id);
}
} }
const headerActions = $computed(() => []); const headerActions = $computed(() => []);
@ -162,24 +172,41 @@ definePageMetadata(computed(() => ({
</script> </script>
<style lang="scss" module> <style lang="scss" module>
.userItem { .userItemMain {
display: flex; display: flex;
} }
.user { .userItemSub {
flex: 1; padding: 6px 12px;
min-width: 0; font-size: 85%;
color: var(--fgTransparentWeak);
} }
.expiresAt, .userItemMainBody {
flex: 1;
min-width: 0;
margin-right: 8px;
&:hover {
text-decoration: none;
}
}
.userToggle,
.unassign { .unassign {
width: 32px; width: 32px;
height: 32px; height: 32px;
margin-left: 8px;
align-self: center; align-self: center;
} }
.expiresAt + .unassign { .chevron {
margin-left: 0; display: block;
transition: transform 0.1s ease-out;
}
.userItem.userItemOpend {
.chevron {
transform: rotateX(180deg);
}
} }
</style> </style>

View file

@ -2,13 +2,13 @@
<MkStickyContainer> <MkStickyContainer>
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :content-max="700"> <MkSpacer :content-max="700">
<div class="mk-list-page"> <div>
<Transition :name="$store.state.animation ? '_transition_zoom' : ''" mode="out-in"> <Transition :name="$store.state.animation ? '_transition_zoom' : ''" mode="out-in">
<div v-if="list" class=""> <div v-if="list" class="">
<div class=""> <div class="_buttons">
<MkButton inline @click="addUser()">{{ i18n.ts.addUser }}</MkButton> <MkButton inline @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
<MkButton inline @click="renameList()">{{ i18n.ts.rename }}</MkButton> <MkButton inline @click="renameList()">{{ i18n.ts.rename }}</MkButton>
<MkButton inline @click="deleteList()">{{ i18n.ts.delete }}</MkButton> <MkButton inline danger @click="deleteList()">{{ i18n.ts.delete }}</MkButton>
</div> </div>
</div> </div>
</Transition> </Transition>
@ -16,18 +16,12 @@
<Transition :name="$store.state.animation ? '_transition_zoom' : ''" mode="out-in"> <Transition :name="$store.state.animation ? '_transition_zoom' : ''" mode="out-in">
<div v-if="list" class="members _margin"> <div v-if="list" class="members _margin">
<div class="">{{ i18n.ts.members }}</div> <div class="">{{ i18n.ts.members }}</div>
<div class=""> <div class="_gaps_s">
<div class="users"> <div v-for="user in users" :key="user.id" :class="$style.userItem">
<div v-for="user in users" :key="user.id" class="user _panel"> <MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
<MkAvatar :user="user" class="avatar" indicator link preview/> <MkUserCardMini :user="user"/>
<div class="body"> </MkA>
<MkUserName :user="user" class="name"/> <button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button>
<MkAcct :user="user" class="acct"/>
</div>
<div class="action">
<button class="_button" @click="removeUser(user)"><i class="ti ti-x"></i></button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -44,6 +38,8 @@ import * as os from '@/os';
import { mainRouter } from '@/router'; import { mainRouter } from '@/router';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { userPage } from '@/filters/user';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
const props = defineProps<{ const props = defineProps<{
listId: string; listId: string;
@ -76,13 +72,20 @@ function addUser() {
}); });
} }
function removeUser(user) { async function removeUser(user, ev) {
os.popupMenu([{
text: i18n.ts.remove,
icon: 'ti ti-x',
danger: true,
action: async () => {
os.api('users/lists/pull', { os.api('users/lists/pull', {
listId: list.id, listId: list.id,
userId: user.id, userId: user.id,
}).then(() => { }).then(() => {
users = users.filter(x => x.id !== user.id); users = users.filter(x => x.id !== user.id);
}); });
},
}], ev.currentTarget ?? ev.target);
} }
async function renameList() { async function renameList() {
@ -126,37 +129,24 @@ definePageMetadata(computed(() => list ? {
} : null)); } : null));
</script> </script>
<style lang="scss" scoped> <style lang="scss" module>
.mk-list-page { .userItem {
> .members {
> ._content {
> .users {
> .user {
display: flex; display: flex;
align-items: center; }
padding: 16px;
> .avatar { .userItemBody {
width: 50px;
height: 50px;
}
> .body {
flex: 1; flex: 1;
padding: 8px; min-width: 0;
margin-right: 8px;
> .name { &:hover {
display: block; text-decoration: none;
font-weight: bold;
}
> .acct {
opacity: 0.5;
}
}
}
}
}
} }
} }
.remove {
width: 32px;
height: 32px;
align-self: center;
}
</style> </style>