mirror of
https://github.com/misskey-dev/misskey.git
synced 2025-01-06 01:46:10 +01:00
wip
This commit is contained in:
parent
457a0a19ec
commit
9494c30c9f
8 changed files with 231 additions and 135 deletions
|
@ -53,9 +53,9 @@
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"matter-js": "0.19.0",
|
"matter-js": "0.19.0",
|
||||||
"mfm-js": "0.24.0",
|
"mfm-js": "0.24.0",
|
||||||
|
"misskey-bubble-game": "workspace:*",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"misskey-reversi": "workspace:*",
|
"misskey-reversi": "workspace:*",
|
||||||
"misskey-bubble-game": "workspace:*",
|
|
||||||
"photoswipe": "5.4.3",
|
"photoswipe": "5.4.3",
|
||||||
"punycode": "2.3.1",
|
"punycode": "2.3.1",
|
||||||
"rollup": "4.9.1",
|
"rollup": "4.9.1",
|
||||||
|
@ -115,8 +115,6 @@
|
||||||
"@vitest/coverage-v8": "0.34.6",
|
"@vitest/coverage-v8": "0.34.6",
|
||||||
"@vue/runtime-core": "3.4.3",
|
"@vue/runtime-core": "3.4.3",
|
||||||
"acorn": "8.11.2",
|
"acorn": "8.11.2",
|
||||||
"ag-grid-community": "^31.0.2",
|
|
||||||
"ag-grid-vue3": "^31.0.2",
|
|
||||||
"blueimp-load-image": "^5.16.0",
|
"blueimp-load-image": "^5.16.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.6.1",
|
"cypress": "13.6.1",
|
||||||
|
|
26
packages/frontend/src/components/grid/MkCell.vue
Normal file
26
packages/frontend/src/components/grid/MkCell.vue
Normal 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>
|
98
packages/frontend/src/components/grid/MkGrid.vue
Normal file
98
packages/frontend/src/components/grid/MkGrid.vue
Normal 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>
|
23
packages/frontend/src/components/grid/MkRow.vue
Normal file
23
packages/frontend/src/components/grid/MkRow.vue
Normal 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>
|
35
packages/frontend/src/components/grid/types.ts
Normal file
35
packages/frontend/src/components/grid/types.ts
Normal 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[];
|
||||||
|
}
|
|
@ -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>
|
|
|
@ -5,9 +5,9 @@ export class GridItem {
|
||||||
readonly url?: string;
|
readonly url?: string;
|
||||||
readonly blob?: Blob;
|
readonly blob?: Blob;
|
||||||
|
|
||||||
public aliases: string;
|
|
||||||
public name: string;
|
public name: string;
|
||||||
public category: string;
|
public category: string;
|
||||||
|
public aliases: string;
|
||||||
public license: string;
|
public license: string;
|
||||||
public isSensitive: boolean;
|
public isSensitive: boolean;
|
||||||
public localOnly: boolean;
|
public localOnly: boolean;
|
||||||
|
@ -15,13 +15,13 @@ export class GridItem {
|
||||||
|
|
||||||
private readonly origin: string;
|
private readonly origin: string;
|
||||||
|
|
||||||
private constructor(
|
constructor(
|
||||||
id: string | undefined,
|
id: string | undefined,
|
||||||
url: string | undefined = undefined,
|
url: string | undefined = undefined,
|
||||||
blob: Blob | undefined = undefined,
|
blob: Blob | undefined = undefined,
|
||||||
aliases: string,
|
|
||||||
name: string,
|
name: string,
|
||||||
category: string,
|
category: string,
|
||||||
|
aliases: string,
|
||||||
license: string,
|
license: string,
|
||||||
isSensitive: boolean,
|
isSensitive: boolean,
|
||||||
localOnly: boolean,
|
localOnly: boolean,
|
||||||
|
@ -43,22 +43,15 @@ export class GridItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
static ofEmojiDetailed(it: Misskey.entities.EmojiDetailed): GridItem {
|
static ofEmojiDetailed(it: Misskey.entities.EmojiDetailed): GridItem {
|
||||||
return new GridItem(
|
return new GridItem(it.id, it.url, undefined, it.name, it.category ?? '', it.aliases.join(', '), it.license ?? '', it.isSensitive, it.localOnly, it.roleIdsThatCanBeUsedThisEmojiAsReaction.join(', '));
|
||||||
it.id,
|
|
||||||
it.url,
|
|
||||||
undefined,
|
|
||||||
it.aliases.join(', '),
|
|
||||||
it.name,
|
|
||||||
it.category ?? '',
|
|
||||||
it.license ?? '',
|
|
||||||
it.isSensitive,
|
|
||||||
it.localOnly,
|
|
||||||
it.roleIdsThatCanBeUsedThisEmojiAsReaction.join(', '),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get edited(): boolean {
|
public get edited(): boolean {
|
||||||
const { origin, ..._this } = this;
|
const { origin, ..._this } = this;
|
||||||
return JSON.stringify(_this) !== origin;
|
return JSON.stringify(_this) !== origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public asRecord(): Record<string, unknown> {
|
||||||
|
return this as Record<string, unknown>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,104 +5,67 @@
|
||||||
<MkPageHeader/>
|
<MkPageHeader/>
|
||||||
</template>
|
</template>
|
||||||
<div class="_gaps" :class="$style.root">
|
<div class="_gaps" :class="$style.root">
|
||||||
<AgGridVue
|
<MkGrid :data="gridItems" :columnSettings="columnSettings"/>
|
||||||
:rowData="gridItems"
|
|
||||||
:columnDefs="colDefs"
|
|
||||||
:rowSelection="'multiple'"
|
|
||||||
:rowClassRules="rowClassRules"
|
|
||||||
style="height: 500px"
|
|
||||||
class="ag-theme-quartz-auto-dark"
|
|
||||||
@gridReady="onGridReady"
|
|
||||||
>
|
|
||||||
</AgGridVue>
|
|
||||||
</div>
|
</div>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { markRaw, onMounted, ref, watch } from 'vue';
|
import { onMounted, ref, watch } from 'vue';
|
||||||
import 'ag-grid-community/styles/ag-grid.css';
|
|
||||||
import 'ag-grid-community/styles/ag-theme-quartz.css';
|
|
||||||
import * as Misskey from 'misskey-js';
|
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 { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { GridItem } from '@/pages/admin/custom-emojis-grid.impl.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
|
const columnSettings: ColumnSetting[] = [
|
||||||
export default {
|
{ bindTo: 'url', title: ' ', type: 'image', width: 50 },
|
||||||
components: {
|
{ bindTo: 'name', title: 'name', type: 'text', width: 140 },
|
||||||
AgGridVue,
|
{ bindTo: 'category', title: 'category', type: 'text', width: 140 },
|
||||||
// eslint-disable-next-line vue/no-unused-components
|
{ bindTo: 'aliases', title: 'aliases', type: 'text', width: 140 },
|
||||||
customEmojisGridEmoji: CustomEmojisGridEmoji,
|
{ bindTo: 'license', title: 'license', type: 'text', width: 140 },
|
||||||
},
|
{ bindTo: 'isSensitive', title: 'sensitive', type: 'text', width: 90 },
|
||||||
setup() {
|
{ bindTo: 'localOnly', title: 'localOnly', type: 'text', width: 90 },
|
||||||
const colDefs = markRaw<ColDef[]>([
|
{ bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', width: 140 },
|
||||||
{
|
];
|
||||||
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 rowClassRules = markRaw<RowClassRules<GridItem>>({
|
const customEmojis = ref<Misskey.entities.EmojiDetailed[]>([]);
|
||||||
'emoji-grid-row-edited': params => params.data?.edited ?? false,
|
const gridItems = ref<GridItem[]>([]);
|
||||||
});
|
// const convertedGridItems = computed<DataSource[]>(() => gridItems.value.map(it => it.asRecord()));
|
||||||
|
|
||||||
const gridApi = ref<GridApi>();
|
const refreshCustomEmojis = async () => {
|
||||||
const customEmojis = ref<Misskey.entities.EmojiDetailed[]>([]);
|
|
||||||
const gridItems = ref<GridItem[]>([]);
|
|
||||||
|
|
||||||
const refreshCustomEmojis = async () => {
|
|
||||||
customEmojis.value = await misskeyApi('emojis', { detail: true }).then(it => it.emojis);
|
customEmojis.value = await misskeyApi('emojis', { detail: true }).then(it => it.emojis);
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshGridItems = () => {
|
const refreshGridItems = () => {
|
||||||
console.log(customEmojis.value);
|
|
||||||
gridItems.value = customEmojis.value.map(it => GridItem.ofEmojiDetailed(it));
|
gridItems.value = customEmojis.value.map(it => GridItem.ofEmojiDetailed(it));
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(customEmojis, refreshGridItems);
|
watch(customEmojis, refreshGridItems);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await refreshCustomEmojis();
|
await refreshCustomEmojis();
|
||||||
refreshGridItems();
|
refreshGridItems();
|
||||||
});
|
});
|
||||||
|
|
||||||
function onGridReady(params: GridReadyEvent) {
|
|
||||||
gridApi.value = params.api;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
colDefs,
|
|
||||||
rowClassRules,
|
|
||||||
customEmojis,
|
|
||||||
gridItems,
|
|
||||||
onGridReady,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.emoji-grid-row-edited {
|
.emoji-grid-row-edited {
|
||||||
background-color: var(--ag-advanced-filter-column-pill-color);
|
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>
|
||||||
|
|
||||||
<style module lang="scss">
|
<style module lang="scss">
|
||||||
.root {
|
.root {
|
||||||
padding: 16px
|
padding: 16px;
|
||||||
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue