This commit is contained in:
samunohito 2024-01-22 10:37:44 +09:00
parent 457a0a19ec
commit 9494c30c9f
8 changed files with 231 additions and 135 deletions

View file

@ -53,9 +53,9 @@
"json5": "2.2.3",
"matter-js": "0.19.0",
"mfm-js": "0.24.0",
"misskey-bubble-game": "workspace:*",
"misskey-js": "workspace:*",
"misskey-reversi": "workspace:*",
"misskey-bubble-game": "workspace:*",
"photoswipe": "5.4.3",
"punycode": "2.3.1",
"rollup": "4.9.1",
@ -115,8 +115,6 @@
"@vitest/coverage-v8": "0.34.6",
"@vue/runtime-core": "3.4.3",
"acorn": "8.11.2",
"ag-grid-community": "^31.0.2",
"ag-grid-vue3": "^31.0.2",
"blueimp-load-image": "^5.16.0",
"cross-env": "7.0.3",
"cypress": "13.6.1",

View file

@ -0,0 +1,26 @@
<template>
<div
class="cell"
:style="{ width: width + 'px' }"
>
<span>{{ cell.value }}</span>
</div>
</template>
<script setup lang="ts">
import { toRefs } from 'vue';
import { GridCell } from '@/components/grid/types.js';
const props = defineProps<{ cell: GridCell }>();
const { cell } = toRefs(props);
const width = cell.value.columnSetting.width;
</script>
<style scoped lang="scss">
.cell {
display: inline-block;
padding: 4px 8px;
overflow: hidden;
white-space: nowrap;
}
</style>

View file

@ -0,0 +1,98 @@
<template>
<div :style="$style.grid">
<div :class="$style.header">
<div
v-for="column in columns"
:key="column.index"
:class="$style.cell"
:style="{ width: column.setting.width + 'px'}"
>
{{ column.setting.title ?? column.setting.bindTo }}
</div>
</div>
<MkRow v-for="row in rows" :key="row.index" :row="row"/>
</div>
</template>
<script setup lang="ts">
import { ref, toRefs, watch } from 'vue';
import MkRow from '@/components/grid/MkRow.vue';
import { ColumnSetting, DataSource, GridCell, GridColumn, GridRow } from '@/components/grid/types.js';
const props = defineProps<{
columnSettings: ColumnSetting[],
data: DataSource[]
}>();
const { columnSettings, data } = toRefs(props);
const columns = ref<GridColumn[]>();
const rows = ref<GridRow[]>();
watch(columnSettings, refreshColumnsSetting);
watch(data, refreshData);
function refreshColumnsSetting() {
const bindToList = columnSettings.value.map(it => it.bindTo);
if (new Set(bindToList).size !== columnSettings.value.length) {
throw new Error(`Duplicate bindTo setting : [${bindToList.join(',')}]}]`);
}
refreshData();
}
function refreshData() {
const _settings = columnSettings.value;
const _data = data.value;
const _rows = _data.map((_, index) => ({ index, cells: Array.of<GridCell>() }));
const _columns = columnSettings.value.map((setting, index) => ({ index, setting, cells: Array.of<GridCell>() }));
for (const [rowIndex, row] of _rows.entries()) {
for (const [colIndex, column] of _columns.entries()) {
if (!(column.setting.bindTo in _data[rowIndex])) {
continue;
}
const value = _data[rowIndex][column.setting.bindTo];
const cell = {
address: { col: colIndex, row: rowIndex },
value,
columnSetting: _settings[colIndex],
};
row.cells.push(cell);
column.cells.push(cell);
}
}
rows.value = _rows;
columns.value = _columns;
}
refreshColumnsSetting();
refreshData();
</script>
<style module lang="scss">
.grid {
display: flex;
flex-wrap: nowrap;
flex-direction: row;
overflow: scroll;
}
.header {
display: flex;
flex-wrap: nowrap;
width: fit-content;
> .cell {
display: block;
padding: 4px 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
}
</style>

View file

@ -0,0 +1,23 @@
<template>
<div :class="$style.row">
<div v-for="cell in row.cells" :key="JSON.stringify(cell.address)">
<MkCell :cell="cell"/>
</div>
</div>
</template>
<script setup lang="ts">
import MkCell from '@/components/grid/MkCell.vue';
import { GridRow } from '@/components/grid/types.js';
defineProps<{ row: GridRow }>();
</script>
<style module lang="scss">
.row {
display: flex;
flex-wrap: nowrap;
flex-direction: row;
}
</style>

View file

@ -0,0 +1,35 @@
export type CellValue = string | boolean | number | undefined | null
export type CellAddress = {
row: number;
col: number;
}
export type GridCell = {
address: CellAddress;
value: CellValue;
columnSetting: ColumnSetting;
}
export type ColumnType = 'text' | 'number' | 'date' | 'boolean' | 'image';
export type ColumnSetting = {
bindTo: string;
title?: string;
type: ColumnType;
width?: number | 'auto';
editable?: boolean;
};
export type DataSource = Record<string, CellValue>;
export type GridColumn = {
index: number;
setting: ColumnSetting;
cells: GridCell[];
}
export type GridRow = {
index: number;
cells: GridCell[];
}

View file

@ -1,40 +0,0 @@
<!--
SPDX-FileCopyrightText: syuilo and other misskey contributors
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div class="root">
<img class="thumbnail" :src="emojiUrl" :alt="emojiName"/>
</div>
</template>
<script setup lang="ts">
import { CellClassParams } from 'ag-grid-community';
import { computed } from 'vue';
import { GridItem } from '@/pages/admin/custom-emojis-grid.impl.js';
const props = defineProps<{
params: CellClassParams<GridItem, string>
}>();
const emojiUrl = computed(() => props.params.data?.url);
const emojiName = computed(() => props.params.data?.name);
</script>
<style lang="scss">
.root {
display: flex;
align-items: center;
}
.thumbnail {
object-fit: cover;
width: 26px;
height: auto;
max-width: 100%;
max-height: 100%;
}
</style>

View file

@ -5,9 +5,9 @@ export class GridItem {
readonly url?: string;
readonly blob?: Blob;
public aliases: string;
public name: string;
public category: string;
public aliases: string;
public license: string;
public isSensitive: boolean;
public localOnly: boolean;
@ -15,13 +15,13 @@ export class GridItem {
private readonly origin: string;
private constructor(
constructor(
id: string | undefined,
url: string | undefined = undefined,
blob: Blob | undefined = undefined,
aliases: string,
name: string,
category: string,
aliases: string,
license: string,
isSensitive: boolean,
localOnly: boolean,
@ -43,22 +43,15 @@ export class GridItem {
}
static ofEmojiDetailed(it: Misskey.entities.EmojiDetailed): GridItem {
return new GridItem(
it.id,
it.url,
undefined,
it.aliases.join(', '),
it.name,
it.category ?? '',
it.license ?? '',
it.isSensitive,
it.localOnly,
it.roleIdsThatCanBeUsedThisEmojiAsReaction.join(', '),
);
return new GridItem(it.id, it.url, undefined, it.name, it.category ?? '', it.aliases.join(', '), it.license ?? '', it.isSensitive, it.localOnly, it.roleIdsThatCanBeUsedThisEmojiAsReaction.join(', '));
}
public get edited(): boolean {
const { origin, ..._this } = this;
return JSON.stringify(_this) !== origin;
}
public asRecord(): Record<string, unknown> {
return this as Record<string, unknown>;
}
}

View file

@ -5,104 +5,67 @@
<MkPageHeader/>
</template>
<div class="_gaps" :class="$style.root">
<AgGridVue
:rowData="gridItems"
:columnDefs="colDefs"
:rowSelection="'multiple'"
:rowClassRules="rowClassRules"
style="height: 500px"
class="ag-theme-quartz-auto-dark"
@gridReady="onGridReady"
>
</AgGridVue>
<MkGrid :data="gridItems" :columnSettings="columnSettings"/>
</div>
</MkStickyContainer>
</div>
</template>
<script lang="ts">
import { markRaw, onMounted, ref, watch } from 'vue';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-quartz.css';
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { AgGridVue } from 'ag-grid-vue3';
import { ColDef, GridApi, GridReadyEvent, RowClassRules } from 'ag-grid-community';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { GridItem } from '@/pages/admin/custom-emojis-grid.impl.js';
import CustomEmojisGridEmoji from '@/pages/admin/custom-emojis-grid-emoji.vue';
import MkGrid from '@/components/grid/MkGrid.vue';
import { ColumnSetting } from '@/components/grid/types.js';
// eslint-disable-next-line import/no-default-export
export default {
components: {
AgGridVue,
// eslint-disable-next-line vue/no-unused-components
customEmojisGridEmoji: CustomEmojisGridEmoji,
},
setup() {
const colDefs = markRaw<ColDef[]>([
{
field: 'img',
headerName: '',
initialWidth: 90,
cellRenderer: 'customEmojisGridEmoji',
checkboxSelection: true,
},
{ field: 'name', headerName: 'name', initialWidth: 140, editable: true },
{ field: 'category', headerName: 'category', initialWidth: 140, editable: true },
{ field: 'aliases', headerName: 'aliases', initialWidth: 140, editable: true },
{ field: 'license', headerName: 'license', initialWidth: 140, editable: true },
{ field: 'isSensitive', headerName: 'sensitive', initialWidth: 90, editable: true },
{ field: 'localOnly', headerName: 'localOnly', initialWidth: 90, editable: true },
{ field: 'roleIdsThatCanBeUsedThisEmojiAsReaction', headerName: 'role', initialWidth: 140, editable: true },
]);
const columnSettings: ColumnSetting[] = [
{ bindTo: 'url', title: ' ', type: 'image', width: 50 },
{ bindTo: 'name', title: 'name', type: 'text', width: 140 },
{ bindTo: 'category', title: 'category', type: 'text', width: 140 },
{ bindTo: 'aliases', title: 'aliases', type: 'text', width: 140 },
{ bindTo: 'license', title: 'license', type: 'text', width: 140 },
{ bindTo: 'isSensitive', title: 'sensitive', type: 'text', width: 90 },
{ bindTo: 'localOnly', title: 'localOnly', type: 'text', width: 90 },
{ bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', width: 140 },
];
const rowClassRules = markRaw<RowClassRules<GridItem>>({
'emoji-grid-row-edited': params => params.data?.edited ?? false,
});
const customEmojis = ref<Misskey.entities.EmojiDetailed[]>([]);
const gridItems = ref<GridItem[]>([]);
// const convertedGridItems = computed<DataSource[]>(() => gridItems.value.map(it => it.asRecord()));
const gridApi = ref<GridApi>();
const customEmojis = ref<Misskey.entities.EmojiDetailed[]>([]);
const gridItems = ref<GridItem[]>([]);
const refreshCustomEmojis = async () => {
customEmojis.value = await misskeyApi('emojis', { detail: true }).then(it => it.emojis);
};
const refreshGridItems = () => {
console.log(customEmojis.value);
gridItems.value = customEmojis.value.map(it => GridItem.ofEmojiDetailed(it));
};
watch(customEmojis, refreshGridItems);
onMounted(async () => {
await refreshCustomEmojis();
refreshGridItems();
});
function onGridReady(params: GridReadyEvent) {
gridApi.value = params.api;
}
return {
colDefs,
rowClassRules,
customEmojis,
gridItems,
onGridReady,
};
},
const refreshCustomEmojis = async () => {
customEmojis.value = await misskeyApi('emojis', { detail: true }).then(it => it.emojis);
};
const refreshGridItems = () => {
gridItems.value = customEmojis.value.map(it => GridItem.ofEmojiDetailed(it));
};
watch(customEmojis, refreshGridItems);
onMounted(async () => {
await refreshCustomEmojis();
refreshGridItems();
});
</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">
.root {
padding: 16px
padding: 16px;
overflow: scroll;
}
</style>