refine remote page
This commit is contained in:
parent
1cdf1bf4c9
commit
ac943195cf
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="false" style="text-align: center">
|
<div v-if="gridItems.value === 0" style="text-align: center">
|
||||||
登録された絵文字はありません。
|
登録された絵文字はありません。
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="_gaps">
|
<div v-else class="_gaps">
|
||||||
|
@ -474,7 +474,6 @@ async function onQueryRolesEditClicked() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onToggleSortOrderButtonClicked(order: GridSortOrder) {
|
function onToggleSortOrderButtonClicked(order: GridSortOrder) {
|
||||||
console.log(order);
|
|
||||||
switch (order.direction) {
|
switch (order.direction) {
|
||||||
case 'ASC':
|
case 'ASC':
|
||||||
order.direction = 'DESC';
|
order.direction = 'DESC';
|
||||||
|
|
|
@ -23,19 +23,6 @@ type PageMode = 'list' | 'register';
|
||||||
const modeTab = ref<PageMode>('list');
|
const modeTab = ref<PageMode>('list');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.emoji-grid-row-edited {
|
|
||||||
background-color: var(--ag-advanced-filter-column-pill-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-grid-item-image {
|
|
||||||
width: auto;
|
|
||||||
height: 26px;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style module lang="scss">
|
<style module lang="scss">
|
||||||
.root {
|
.root {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|
|
@ -1,18 +1,60 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="_gaps" :class="$style.root">
|
<div :class="$style.root">
|
||||||
<div :class="$style.searchArea">
|
<div class="_gaps">
|
||||||
<MkInput v-model="query" :debounce="true" type="search" autocapitalize="off" style="flex: 1">
|
<MkFolder>
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
<template #icon><i class="ti ti-search"></i></template>
|
||||||
|
<template #label>検索設定</template>
|
||||||
|
<template #caption>
|
||||||
|
検索条件を詳細に設定します。
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="_gaps">
|
||||||
|
<div :class="[[spMode ? $style.searchAreaSp : $style.searchArea]]">
|
||||||
|
<MkInput v-model="queryName" :debounce="true" type="search" autocapitalize="off" class="col1 row1">
|
||||||
<template #label>name</template>
|
<template #label>name</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="host" :debounce="true" type="search" autocapitalize="off" style="flex: 1">
|
<MkInput v-model="queryHost" :debounce="true" type="search" autocapitalize="off" class="col2 row1">
|
||||||
<template #prefix><i class="ti ti-cloud-network"></i></template>
|
|
||||||
<template #label>host</template>
|
<template #label>host</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkButton primary style="margin-left: auto;" @click="onSearchButtonClicked">
|
<MkInput v-model="queryUri" :debounce="true" type="search" autocapitalize="off" class="col1 row2">
|
||||||
{{ i18n.ts.search }}
|
<template #label>uri</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkInput v-model="queryPublicUrl" :debounce="true" type="search" autocapitalize="off" class="col2 row2">
|
||||||
|
<template #label>publicUrl</template>
|
||||||
|
</MkInput>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MkFolder :spacerMax="8" :spacerMin="8">
|
||||||
|
<template #icon><i class="ti ti-arrows-sort"></i></template>
|
||||||
|
<template #label>ソート順</template>
|
||||||
|
<div :class="$style.sortOrderArea">
|
||||||
|
<div :class="$style.sortOrderAreaTags">
|
||||||
|
<MkTagItem
|
||||||
|
v-for="order in sortOrders"
|
||||||
|
:key="order.key"
|
||||||
|
:iconClass="order.direction === 'ASC' ? 'ti ti-arrow-up' : 'ti ti-arrow-down'"
|
||||||
|
:exButtonIconClass="'ti ti-x'"
|
||||||
|
:content="order.key"
|
||||||
|
@click="onToggleSortOrderButtonClicked(order)"
|
||||||
|
@exButtonClick="onRemoveSortOrderButtonClicked(order.key)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<MkButton :class="$style.sortOrderAddButton" @click="onAddSortOrderButtonClicked">
|
||||||
|
<span class="ti ti-plus"/>
|
||||||
</MkButton>
|
</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<div :class="[[spMode ? $style.searchButtonsSp : $style.searchButtons]]">
|
||||||
|
<MkButton primary @click="onSearchButtonClicked">
|
||||||
|
{{ i18n.ts.search }}
|
||||||
|
</MkButton>
|
||||||
|
<MkButton @click="onQueryResetButtonClicked">
|
||||||
|
リセット
|
||||||
|
</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder>
|
<MkFolder>
|
||||||
<template #icon><i class="ti ti-notes"></i></template>
|
<template #icon><i class="ti ti-notes"></i></template>
|
||||||
|
@ -24,23 +66,17 @@
|
||||||
<XRegisterLogs :logs="requestLogs"/>
|
<XRegisterLogs :logs="requestLogs"/>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
<div v-if="gridItems.length > 0">
|
<div v-if="gridItems.length > 0" :class="$style.gridArea">
|
||||||
<div :class="$style.gridArea">
|
|
||||||
<MkGrid :data="gridItems" :settings="setupGrid()" @event="onGridEvent"/>
|
<MkGrid :data="gridItems" :settings="setupGrid()" @event="onGridEvent"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="_gaps">
|
<MkPagingButtons :current="currentPage" :max="allPages" :buttonCount="5" @pageChanged="onPageChanged"/>
|
||||||
<div :class="$style.pages">
|
|
||||||
<button @click="onLatestButtonClicked"><</button>
|
|
||||||
<button @click="onOldestButtonClicked">></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :class="$style.buttons">
|
<div v-if="gridItems.length > 0" class="_gaps" :class="$style.buttons">
|
||||||
<MkButton primary @click="onImportClicked">チェックがついた絵文字をインポート</MkButton>
|
<MkButton primary @click="onImportClicked">チェックがついた絵文字をインポート</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -51,13 +87,18 @@ import { i18n } from '@/i18n.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkGrid from '@/components/grid/MkGrid.vue';
|
import MkGrid from '@/components/grid/MkGrid.vue';
|
||||||
import { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
|
import { emptyStrToUndefined, RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
|
||||||
import { GridCellValueChangeEvent, GridContext, GridEvent, GridKeyDownEvent } from '@/components/grid/grid-event.js';
|
import { GridCellValueChangeEvent, GridContext, GridEvent, GridKeyDownEvent } from '@/components/grid/grid-event.js';
|
||||||
import { optInGridUtils } from '@/components/grid/optin-utils.js';
|
import { optInGridUtils } from '@/components/grid/optin-utils.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.local.logs.vue';
|
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.local.logs.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { GridSetting } from '@/components/grid/grid.js';
|
import { GridSetting } from '@/components/grid/grid.js';
|
||||||
|
import MkTagItem from '@/components/MkTagItem.vue';
|
||||||
|
import { deviceKind } from '@/scripts/device-kind.js';
|
||||||
|
import { MenuItem } from '@/types/menu.js';
|
||||||
|
import MkTab from '@/components/MkTab.vue';
|
||||||
|
import MkPagingButtons from '@/components/MkPagingButtons.vue';
|
||||||
|
|
||||||
type GridItem = {
|
type GridItem = {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
|
@ -67,6 +108,19 @@ type GridItem = {
|
||||||
host: string;
|
host: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const gridSortOrderKeys = [
|
||||||
|
'name',
|
||||||
|
'host',
|
||||||
|
'uri',
|
||||||
|
'publicUrl',
|
||||||
|
];
|
||||||
|
type GridSortOrderKey = typeof gridSortOrderKeys[number];
|
||||||
|
|
||||||
|
type GridSortOrder = {
|
||||||
|
key: GridSortOrderKey;
|
||||||
|
direction: 'ASC' | 'DESC';
|
||||||
|
}
|
||||||
|
|
||||||
function setupGrid(): GridSetting {
|
function setupGrid(): GridSetting {
|
||||||
return {
|
return {
|
||||||
row: {
|
row: {
|
||||||
|
@ -89,6 +143,8 @@ function setupGrid(): GridSetting {
|
||||||
{ bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 'auto' },
|
{ bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 'auto' },
|
||||||
{ bindTo: 'name', title: 'name', type: 'text', editable: false, width: 'auto' },
|
{ bindTo: 'name', title: 'name', type: 'text', editable: false, width: 'auto' },
|
||||||
{ bindTo: 'host', title: 'host', type: 'text', editable: false, width: 'auto' },
|
{ bindTo: 'host', title: 'host', type: 'text', editable: false, width: 'auto' },
|
||||||
|
{ bindTo: 'uri', title: 'uri', type: 'text', editable: false, width: 'auto' },
|
||||||
|
{ bindTo: 'publicUrl', title: 'publicUrl', type: 'text', editable: false, width: 'auto' },
|
||||||
],
|
],
|
||||||
cells: {
|
cells: {
|
||||||
contextMenuFactory: (col, row, value, context) => {
|
contextMenuFactory: (col, row, value, context) => {
|
||||||
|
@ -108,26 +164,68 @@ function setupGrid(): GridSetting {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const customEmojis = ref<Misskey.entities.EmojiDetailedAdmin[]>([]);
|
||||||
|
const allPages = ref<number>(0);
|
||||||
|
const currentPage = ref<number>(0);
|
||||||
|
|
||||||
|
const queryName = ref<string | null>(null);
|
||||||
|
const queryHost = ref<string | null>(null);
|
||||||
|
const queryUri = ref<string | null>(null);
|
||||||
|
const queryPublicUrl = ref<string | null>(null);
|
||||||
|
const previousQuery = ref<string | undefined>(undefined);
|
||||||
|
const sortOrders = ref<GridSortOrder[]>([]);
|
||||||
const requestLogs = ref<RequestLogItem[]>([]);
|
const requestLogs = ref<RequestLogItem[]>([]);
|
||||||
|
|
||||||
const customEmojis = ref<Misskey.entities.EmojiDetailedAdmin[]>([]);
|
|
||||||
const gridItems = ref<GridItem[]>([]);
|
const gridItems = ref<GridItem[]>([]);
|
||||||
const query = ref<string>('');
|
|
||||||
const host = ref<string>('');
|
|
||||||
|
|
||||||
const latest = computed(() => customEmojis.value.length > 0 ? customEmojis.value[0]?.id : undefined);
|
const spMode = computed(() => ['smartphone', 'tablet'].includes(deviceKind));
|
||||||
const oldest = computed(() => customEmojis.value.length > 0 ? customEmojis.value[customEmojis.value.length - 1]?.id : undefined);
|
|
||||||
|
function onToggleSortOrderButtonClicked(order: GridSortOrder) {
|
||||||
|
switch (order.direction) {
|
||||||
|
case 'ASC':
|
||||||
|
order.direction = 'DESC';
|
||||||
|
break;
|
||||||
|
case 'DESC':
|
||||||
|
order.direction = 'ASC';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRemoveSortOrderButtonClicked(key: GridSortOrderKey) {
|
||||||
|
sortOrders.value = sortOrders.value.filter(it => it.key !== key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAddSortOrderButtonClicked(ev: MouseEvent) {
|
||||||
|
const menuItems: MenuItem[] = gridSortOrderKeys
|
||||||
|
.filter(key => !sortOrders.value.map(it => it.key).includes(key))
|
||||||
|
.map(it => {
|
||||||
|
return {
|
||||||
|
text: it,
|
||||||
|
action: () => {
|
||||||
|
sortOrders.value.push({ key: it, direction: 'ASC' });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
os.contextMenu(menuItems, ev);
|
||||||
|
}
|
||||||
|
|
||||||
async function onSearchButtonClicked() {
|
async function onSearchButtonClicked() {
|
||||||
await refreshCustomEmojis(query.value, host.value);
|
await refreshCustomEmojis();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onQueryResetButtonClicked() {
|
||||||
|
queryName.value = null;
|
||||||
|
queryHost.value = null;
|
||||||
|
queryUri.value = null;
|
||||||
|
queryPublicUrl.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onLatestButtonClicked() {
|
async function onLatestButtonClicked() {
|
||||||
await refreshCustomEmojis(query.value, host.value, undefined, latest.value);
|
await refreshCustomEmojis();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onOldestButtonClicked() {
|
async function onOldestButtonClicked() {
|
||||||
await refreshCustomEmojis(query.value, host.value, oldest.value, undefined);
|
await refreshCustomEmojis();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onImportClicked() {
|
async function onImportClicked() {
|
||||||
|
@ -201,19 +299,29 @@ async function importEmojis(targets: GridItem[]) {
|
||||||
await refreshCustomEmojis();
|
await refreshCustomEmojis();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshCustomEmojis(query?: string, host?: string, sinceId?: string, untilId?: string) {
|
async function refreshCustomEmojis() {
|
||||||
const emojis = await misskeyApi('admin/emoji/v2/list', {
|
const query: Misskey.entities.AdminEmojiV2ListRequest['query'] = {
|
||||||
limit: 100,
|
name: emptyStrToUndefined(queryName.value),
|
||||||
query: {
|
host: emptyStrToUndefined(queryHost.value),
|
||||||
name: query,
|
uri: emptyStrToUndefined(queryUri.value),
|
||||||
host: host,
|
publicUrl: emptyStrToUndefined(queryPublicUrl.value),
|
||||||
sinceId: sinceId,
|
|
||||||
untilId: untilId,
|
|
||||||
hostType: 'remote',
|
hostType: 'remote',
|
||||||
},
|
};
|
||||||
|
|
||||||
|
if (JSON.stringify(query) !== previousQuery.value) {
|
||||||
|
currentPage.value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await misskeyApi('admin/emoji/v2/list', {
|
||||||
|
limit: 100,
|
||||||
|
query: query,
|
||||||
|
page: currentPage.value,
|
||||||
|
sort: sortOrders.value.map(({ key, direction }) => ({ key: key as any, direction })),
|
||||||
});
|
});
|
||||||
|
|
||||||
customEmojis.value = emojis.emojis;
|
customEmojis.value = result.emojis;
|
||||||
|
allPages.value = result.allPages;
|
||||||
|
previousQuery.value = JSON.stringify(query);
|
||||||
gridItems.value = customEmojis.value.map(it => ({
|
gridItems.value = customEmojis.value.map(it => ({
|
||||||
checked: false,
|
checked: false,
|
||||||
id: it.id,
|
id: it.id,
|
||||||
|
@ -229,32 +337,90 @@ onMounted(async () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.emoji-grid-row-edited {
|
.row1 {
|
||||||
background-color: var(--ag-advanced-filter-column-pill-color);
|
grid-row: 1 / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-grid-item-image {
|
.row2 {
|
||||||
width: auto;
|
grid-row: 2 / 3;
|
||||||
height: 26px;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.col1 {
|
||||||
|
grid-column: 1 / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col2 {
|
||||||
|
grid-column: 2 / 3;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style module lang="scss">
|
<style module lang="scss">
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
|
--stickyTop: 0px;
|
||||||
|
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchArea {
|
.searchArea {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchButtons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchButtonsSp {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchAreaSp {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sortOrderArea {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: flex-end;
|
align-items: flex-start;
|
||||||
justify-content: stretch;
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sortOrderAreaTags {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sortOrderAddButton {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 2.0em;
|
||||||
|
min-height: 2.0em;
|
||||||
|
max-width: 2.0em;
|
||||||
|
max-height: 2.0em;
|
||||||
|
padding: 8px;
|
||||||
|
margin-left: auto;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background-color: var(--buttonBg);
|
||||||
|
}
|
||||||
|
|
||||||
.gridArea {
|
.gridArea {
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
|
|
Loading…
Reference in New Issue