This commit is contained in:
samunohito 2024-01-29 09:35:26 +09:00
parent 53bad559a0
commit ff14249507
7 changed files with 83 additions and 34 deletions

View File

@ -23,7 +23,7 @@
</div> </div>
<div v-else-if="cellType === 'boolean'"> <div v-else-if="cellType === 'boolean'">
<span v-if="cell.value === true" class="ti ti-check"/> <span v-if="cell.value === true" class="ti ti-check"/>
<span v-else class="ti ti-x"/> <span v-else class="ti"/>
</div> </div>
<div v-else-if="cellType === 'image'"> <div v-else-if="cellType === 'image'">
<img <img

View File

@ -8,7 +8,8 @@
<div :class="$style.left"/> <div :class="$style.left"/>
<div :class="$style.wrapper"> <div :class="$style.wrapper">
<div ref="contentEl" :class="$style.contentArea"> <div ref="contentEl" :class="$style.contentArea">
{{ text }} <span v-if="column.setting.icon" class="ti" :class="column.setting.icon"/>
<span v-else>{{ text }}</span>
</div> </div>
</div> </div>
<div <div

View File

@ -18,6 +18,7 @@ export type ColumnType = 'text' | 'number' | 'date' | 'boolean' | 'image';
export type ColumnSetting = { export type ColumnSetting = {
bindTo: string; bindTo: string;
title?: string; title?: string;
icon?: string;
type: ColumnType; type: ColumnType;
width: SizeStyle; width: SizeStyle;
editable?: boolean; editable?: boolean;

View File

@ -19,6 +19,7 @@ export class GridItem implements IGridItem {
readonly fileId?: string; readonly fileId?: string;
readonly url: string; readonly url: string;
public checked: boolean;
public name: string; public name: string;
public category: string; public category: string;
public aliases: string; public aliases: string;
@ -45,6 +46,7 @@ export class GridItem implements IGridItem {
this.fileId = fileId; this.fileId = fileId;
this.url = url; this.url = url;
this.checked = true;
this.aliases = aliases; this.aliases = aliases;
this.name = name; this.name = name;
this.category = category; this.category = category;

View File

@ -1,9 +1,13 @@
<template> <template>
<div class="_gaps"> <div class="_gaps">
<MkInput :modelValue="query" :debounce="true" type="search" autocapitalize="off" @change="(v) => query = v"> <div :class="$style.searchArea">
<template #prefix><i class="ti ti-search"></i></template> <MkInput v-model="query" :debounce="true" type="search" autocapitalize="off" style="flex: 1">
<template #label>{{ i18n.ts.search }}</template> <template #prefix><i class="ti ti-search"></i></template>
</MkInput> </MkInput>
<MkButton primary style="margin-left: auto;" @click="onSearchButtonClicked">
{{ i18n.ts.search }}
</MkButton>
</div>
<div <div
style="overflow-y: scroll; padding-top: 8px; padding-bottom: 8px;" style="overflow-y: scroll; padding-top: 8px; padding-bottom: 8px;"
@ -11,36 +15,35 @@
<MkGrid :data="convertedGridItems" :columnSettings="columnSettings"/> <MkGrid :data="convertedGridItems" :columnSettings="columnSettings"/>
</div> </div>
<div :class="$style.pages"> <div class="_gaps">
<button>&lt;&lt;</button> <div :class="$style.pages">
<button>&lt;</button> <button>&lt;</button>
<button>&gt;</button>
</div>
<button>1</button> <div :class="$style.buttons">
<button>2</button> <MkButton primary>{{ i18n.ts.update }}</MkButton>
<button>3</button> <MkButton>リセット</MkButton>
<button>4</button> </div>
<button>5</button>
<span>...</span>
<button>10</button>
<button>&gt;</button>
<button>&gt;&gt;</button>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onActivated, onMounted, ref, toRefs, watch } from 'vue'; import { computed, onMounted, ref, toRefs, watch } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { GridItem } from '@/pages/admin/custom-emojis-grid.impl.js'; import { GridItem } from '@/pages/admin/custom-emojis-grid.impl.js';
import MkGrid from '@/components/grid/MkGrid.vue'; import MkGrid from '@/components/grid/MkGrid.vue';
import { ColumnSetting } from '@/components/grid/types.js'; import { ColumnSetting } from '@/components/grid/grid.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import { required } from '@/components/grid/cell-validators.js';
import MkButton from '@/components/MkButton.vue';
const columnSettings: ColumnSetting[] = [ const columnSettings: ColumnSetting[] = [
{ bindTo: 'url', title: '🎨', type: 'image', editable: false, width: 50 }, { bindTo: 'selected', icon: 'ti-trash', type: 'boolean', editable: true, width: 34 },
{ bindTo: 'name', title: 'name', type: 'text', editable: true, width: 140 }, { bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 50, validators: [required] },
{ bindTo: 'name', title: 'name', type: 'text', editable: true, width: 140, validators: [required] },
{ bindTo: 'category', title: 'category', type: 'text', editable: true, width: 140 }, { bindTo: 'category', title: 'category', type: 'text', editable: true, width: 140 },
{ bindTo: 'aliases', title: 'aliases', type: 'text', editable: true, width: 140 }, { bindTo: 'aliases', title: 'aliases', type: 'text', editable: true, width: 140 },
{ bindTo: 'license', title: 'license', type: 'text', editable: true, width: 140 }, { bindTo: 'license', title: 'license', type: 'text', editable: true, width: 140 },
@ -49,6 +52,10 @@ const columnSettings: ColumnSetting[] = [
{ bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 140 }, { bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 140 },
]; ];
const emit = defineEmits<{
(ev: 'operation:search', query: string, sinceId?: string, untilId?: string): void;
}>();
const props = defineProps<{ const props = defineProps<{
customEmojis: Misskey.entities.EmojiDetailed[]; customEmojis: Misskey.entities.EmojiDetailed[];
}>(); }>();
@ -62,21 +69,33 @@ const convertedGridItems = computed(() => gridItems.value.map(it => it.asRecord(
watch(customEmojis, refreshGridItems); watch(customEmojis, refreshGridItems);
function onSearchButtonClicked() {
emit('operation:search', query.value, undefined, undefined);
}
function refreshGridItems() { function refreshGridItems() {
gridItems.value = customEmojis.value.map(it => GridItem.fromEmojiDetailed(it)); gridItems.value = customEmojis.value.map(it => GridItem.fromEmojiDetailed(it));
} }
refreshGridItems(); onMounted(() => {
refreshGridItems();
});
</script> </script>
<style module lang="scss"> <style module lang="scss">
.searchArea {
display: flex;
flex-direction: row;
align-items: center;
justify-content: stretch;
gap: 8px;
}
.pages { .pages {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
margin-top: 8px;
button { button {
background-color: var(--buttonBg); background-color: var(--buttonBg);
@ -86,4 +105,12 @@ refreshGridItems();
padding: 8px; padding: 8px;
} }
} }
.buttons {
display: inline-flex;
margin-left: auto;
gap: 8px;
flex-wrap: wrap;
}
</style> </style>

View File

@ -75,10 +75,6 @@ type FolderItem = {
type UploadResult = { key: string, item: IGridItem, success: boolean, err: any }; type UploadResult = { key: string, item: IGridItem, success: boolean, err: any };
const emit = defineEmits<{
(ev: 'operation:registered'): void;
}>();
const columnSettings: ColumnSetting[] = [ const columnSettings: ColumnSetting[] = [
{ bindTo: 'url', title: '🎨', type: 'image', editable: false, width: 50, validators: [required] }, { bindTo: 'url', title: '🎨', type: 'image', editable: false, width: 50, validators: [required] },
{ bindTo: 'name', title: 'name', type: 'text', editable: true, width: 140, validators: [required] }, { bindTo: 'name', title: 'name', type: 'text', editable: true, width: 140, validators: [required] },
@ -90,6 +86,10 @@ const columnSettings: ColumnSetting[] = [
{ bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 100 }, { bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 100 },
]; ];
const emit = defineEmits<{
(ev: 'operation:registered'): void;
}>();
const uploadFolders = ref<FolderItem[]>([]); const uploadFolders = ref<FolderItem[]>([]);
const gridItems = ref<IGridItem[]>([]); const gridItems = ref<IGridItem[]>([]);
const selectedFolderId = ref(defaultStore.state.uploadFolder); const selectedFolderId = ref(defaultStore.state.uploadFolder);

View File

@ -11,8 +11,15 @@
</MkTab> </MkTab>
<div> <div>
<XListComponent v-if="modeTab === 'list'" :customEmojis="customEmojis"/> <XListComponent
<XRegisterComponent v-else @operation:registered="onOperationRegistered"/> v-if="modeTab === 'list'"
:customEmojis="customEmojis"
@operation:search="onOperationSearch"
/>
<XRegisterComponent
v-else
@operation:registered="onOperationRegistered"
/>
</div> </div>
</div> </div>
</MkStickyContainer> </MkStickyContainer>
@ -34,13 +41,24 @@ type PageMode = 'list' | 'register';
const customEmojis = ref<Misskey.entities.EmojiDetailed[]>([]); const customEmojis = ref<Misskey.entities.EmojiDetailed[]>([]);
const headerTab = ref('local'); const headerTab = ref('local');
const modeTab = ref<PageMode>('list'); const modeTab = ref<PageMode>('list');
const query = ref<string>();
async function refreshCustomEmojis() { async function refreshCustomEmojis(query?: string, sinceId?: string, untilId?: string) {
customEmojis.value = await misskeyApi('admin/emoji/list', { limit: 100 }); customEmojis.value = await misskeyApi('admin/emoji/list', {
limit: 100,
query,
sinceId,
untilId,
});
}
async function onOperationSearch(q: string, sinceId?: string, untilId?: string) {
query.value = q;
await refreshCustomEmojis(q, sinceId, untilId);
} }
async function onOperationRegistered() { async function onOperationRegistered() {
await refreshCustomEmojis(); await refreshCustomEmojis(query.value);
} }
onMounted(async () => { onMounted(async () => {