add MkGrid.stories.impl.ts

This commit is contained in:
samunohito 2024-02-22 19:24:47 +09:00
parent 5a2b11ec17
commit 098cf397b9
5 changed files with 348 additions and 1 deletions

View File

@ -0,0 +1,95 @@
/**
* AIで生成した無作為なファーストネーム
*/
export const firstNameDict: string = [
'Ethan', 'Olivia', 'Jackson', 'Emma', 'Liam', 'Ava', 'Aiden', 'Sophia', 'Mason', 'Isabella',
'Noah', 'Mia', 'Lucas', 'Harper', 'Caleb', 'Abigail', 'Samuel', 'Emily', 'Logan',
'Madison', 'Benjamin', 'Chloe', 'Elijah', 'Grace', 'Alexander', 'Scarlett', 'William', 'Zoey', 'James', 'Lily',
]
/**
* AIで生成した無作為なラストネーム
*/
export const lastNameDict: string = [
'Anderson', 'Johnson', 'Thompson', 'Davis', 'Rodriguez', 'Smith', 'Patel', 'Williams', 'Lee', 'Brown',
'Garcia', 'Jackson', 'Martinez', 'Taylor', 'Harris', 'Nguyen', 'Miller', 'Jones', 'Wilson',
'White', 'Thomas', 'Garcia', 'Martinez', 'Robinson', 'Turner', 'Lewis', 'Hall', 'King', 'Baker', 'Cooper',
]
/**
* AIで生成した無作為な国名
*/
export const countryDict: string = [
'Japan', 'Canada', 'Brazil', 'Australia', 'Italy', 'SouthAfrica', 'Mexico', 'Sweden', 'Russia', 'India',
'Germany', 'Argentina', 'South Korea', 'France', 'Nigeria', 'Turkey', 'Spain', 'Egypt', 'Thailand',
'Vietnam', 'Kenya', 'Saudi Arabia', 'Netherlands', 'Colombia', 'Poland', 'Chile', 'Malaysia', 'Ukraine', 'New Zealand', 'Peru',
]
export function text(length: number = 10): string {
let result = "";
while (result.length < length) {
result += Math.random().toString(36).substring(2);
}
return result.substring(0, length);
}
export function integer(min: number = 0, max: number = 9999): number {
return Math.floor(Math.random() * (max - min)) + min;
}
export function date(params?: {
yearMin?: number,
yearMax?: number,
monthMin?: number,
monthMax?: number,
dayMin?: number,
dayMax?: number,
hourMin?: number,
hourMax?: number,
minuteMin?: number,
minuteMax?: number,
secondMin?: number,
secondMax?: number,
millisecondMin?: number,
millisecondMax?: number,
}) {
const year = integer(params?.yearMin ?? 1970, params?.yearMax ?? (new Date()).getFullYear());
const month = integer(params?.monthMin ?? 1, params?.monthMax ?? 12);
let day = integer(params?.dayMin ?? 1, params?.dayMax ?? 31);
if (month === 2) {
day = Math.min(day, 28);
} else if ([4, 6, 9, 11].includes(month)) {
day = Math.min(day, 30);
} else {
day = Math.min(day, 31);
}
const hour = integer(params?.hourMin ?? 0, params?.hourMax ?? 23);
const minute = integer(params?.minuteMin ?? 0, params?.minuteMax ?? 59);
const second = integer(params?.secondMin ?? 0, params?.secondMax ?? 59);
const millisecond = integer(params?.millisecondMin ?? 0, params?.millisecondMax ?? 999);
return new Date(year, month - 1, day, hour, minute, second, millisecond);
}
export function boolean(): boolean {
return Math.random() < 0.5;
}
export function choose<T>(array: T[]): T {
return array[Math.floor(Math.random() * array.length)];
}
export function firstName(): string {
return firstNameDict[Math.floor(Math.random() * firstNameDict.length)];
}
export function lastName(): string {
return lastNameDict[Math.floor(Math.random() * lastNameDict.length)];
}
export function country(): string {
return countryDict[Math.floor(Math.random() * countryDict.length)];
}

View File

@ -411,6 +411,7 @@ function toStories(component: string): Promise<string> {
glob('src/components/MkInviteCode.vue'),
glob('src/components/MkTagItem.vue'),
glob('src/components/MkRoleSelectDialog.vue'),
glob('src/components/grid/MkGrid.vue'),
glob('src/pages/user/home.vue'),
]);
const components = globs.flat();

View File

@ -22,6 +22,12 @@
<div v-if="cellType === 'text'">
{{ cell.value }}
</div>
<div v-if="cellType === 'number'">
{{ cell.value }}
</div>
<div v-if="cellType === 'date'">
{{ cell.value }}
</div>
<div v-else-if="cellType === 'boolean'">
<span v-if="cell.value === true" class="ti ti-check"/>
<span v-else class="ti"/>
@ -46,6 +52,24 @@
@mousedown.stop
@contextmenu.stop
/>
<input
v-if="cellType === 'number'"
type="number"
:class="$style.editingInput"
:value="editingValue"
@input="onInputText"
@mousedown.stop
@contextmenu.stop
/>
<input
v-if="cellType === 'date'"
type="date"
:class="$style.editingInput"
:value="editingValue"
@input="onInputText"
@mousedown.stop
@contextmenu.stop
/>
</div>
</div>
</td>
@ -185,6 +209,8 @@ async function beginEditing(target: HTMLElement) {
rootEl.value?.focus();
} else {
switch (cellType.value) {
case 'number':
case 'date':
case 'text': {
editingValue.value = cell.value.value;
editing.value = true;

View File

@ -0,0 +1,225 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
import { StoryObj } from '@storybook/vue3';
import { ref } from 'vue';
import { commonHandlers } from '../../../.storybook/mocks.js';
import { boolean, choose, country, date, firstName, integer, lastName, text } from '../../../.storybook/fake-utils.js';
import MkGrid from './MkGrid.vue';
import { GridContext, GridEvent } from '@/components/grid/grid-event.js';
import { DataSource, GridSetting } from '@/components/grid/grid.js';
function d(p: {
check?: boolean,
name?: string,
email?: string,
age?: number,
birthday?: string,
gender?: string,
country?: string,
reportCount?: number,
createdAt?: string,
}) {
const prefix = text(10);
return {
check: p.check ?? boolean(),
name: p.name ?? `${firstName()} ${lastName()}`,
email: p.email ?? `${prefix}@example.com`,
age: p.age ?? integer(20, 80),
birthday: date().toISOString(),
gender: p.gender ?? choose(['male', 'female', 'other', 'unknown']),
country: p.country ?? country(),
reportCount: p.reportCount ?? integer(),
createdAt: p.createdAt ?? date().toISOString(),
};
}
const defaultCols = [
{ bindTo: 'check', icon: 'ti-check', type: 'boolean', width: 50 },
{ bindTo: 'name', title: 'Name', type: 'text', width: 'auto' },
{ bindTo: 'email', title: 'Email', type: 'text', width: 'auto' },
{ bindTo: 'age', title: 'Age', type: 'number', width: 50 },
{ bindTo: 'birthday', title: 'Birthday', type: 'date', width: 'auto' },
{ bindTo: 'gender', title: 'Gender', type: 'text', width: 80 },
{ bindTo: 'country', title: 'Country', type: 'text', width: 120 },
{ bindTo: 'reportCount', title: 'ReportCount', type: 'number', width: 'auto' },
{ bindTo: 'createdAt', title: 'CreatedAt', type: 'date', width: 'auto' },
];
function createArgs(overrides?: { settings?: Partial<GridSetting>, data?: DataSource[] }) {
const refData = ref([
d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}),
d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}),
d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}),
d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}),
d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}), d({}),
]);
return {
settings: {
row: overrides?.settings?.row,
cols: [
...defaultCols.filter(col => overrides?.settings?.cols?.every(c => c.bindTo !== col.bindTo) ?? true),
...overrides?.settings?.cols ?? [],
],
cells: overrides?.settings?.cells,
},
data: refData.value,
};
}
function createRender(params: { settings: Partial<GridSetting>, data: DataSource[] }) {
return {
render(args) {
return {
components: {
MkGrid,
},
setup() {
return {
args,
};
},
data() {
return {
data: args.data,
};
},
computed: {
props() {
return {
...args,
};
},
events() {
return {
event: (event: GridEvent, context: GridContext) => {
switch (event.type) {
case 'cell-value-change': {
args.data[event.row.index][event.column.setting.bindTo] = event.newValue;
}
}
},
};
},
},
template: '<div style="padding:20px"><MkGrid v-bind="props" v-on="events" /></div>',
};
},
args: {
...params,
},
parameters: {
layout: 'fullscreen',
msw: {
handlers: [
...commonHandlers,
],
},
},
} satisfies StoryObj<typeof MkGrid>;
}
export const Default = createRender(createArgs());
export const NoNumber = createRender(createArgs({
settings: {
row: {
showNumber: false,
},
},
}));
export const NoSelectable = createRender(createArgs({
settings: {
row: {
selectable: false,
},
},
}));
export const Editable = createRender(createArgs({
settings: {
cols: defaultCols.map(col => ({ ...col, editable: true })),
},
}));
export const AdditionalRowStyle = createRender(createArgs({
settings: {
cols: defaultCols.map(col => ({ ...col, editable: true })),
row: {
styleRules: [
{
condition: ({ row }) => AdditionalRowStyle.args.data[row.index].check,
applyStyle: {
style: {
backgroundColor: 'lightgray',
},
},
},
],
},
},
}));
export const ContextMenu = createRender(createArgs({
settings: {
cols: [
{
bindTo: 'check', icon: 'ti-check', type: 'boolean', width: 50, contextMenuFactory: (col, context) => [
{
type: 'button',
text: 'Check All',
action: () => {
for (const d of ContextMenu.args.data) {
d.check = true;
}
},
},
{
type: 'button',
text: 'Uncheck All',
action: () => {
for (const d of ContextMenu.args.data) {
d.check = false;
}
},
},
],
},
],
row: {
contextMenuFactory: (row, context) => [
{
type: 'button',
text: 'Delete',
action: () => {
const idxes = context.rangedRows.map(r => r.index);
const newData = ContextMenu.args.data.filter((d, i) => !idxes.includes(i));
ContextMenu.args.data.splice(0);
ContextMenu.args.data.push(...newData);
},
},
],
},
cells: {
contextMenuFactory: (col, row, value, context) => [
{
type: 'button',
text: 'Delete',
action: () => {
for (const cell of context.rangedCells) {
ContextMenu.args.data[cell.row.index][cell.column.setting.bindTo] = undefined;
}
},
},
],
},
},
}));

View File

@ -67,7 +67,7 @@ type RowHolder = {
}
const emit = defineEmits<{
(ev: 'event', event: GridEvent, current: GridContext): void;
(ev: 'event', event: GridEvent, context: GridContext): void;
}>();
const props = defineProps<{