Skip to content

Commit

Permalink
enhance(frontend): デッキのアンテナ・リスト選択画面からそれぞれを新規作成できるように (#14104)
Browse files Browse the repository at this point in the history
* enhance(frontend): デッキのアンテナ・リスト選択画面からそれぞれを新規作成できるように

* Update Changelog

* fix

* fix

* lint

* add story

* typo

ねぼけていた

* Update antenna-column.vue

---------

Co-authored-by: syuilo <[email protected]>
  • Loading branch information
kakkokari-gtyih and syuilo authored Jul 30, 2024
1 parent de3ddb5 commit 738b3ea
Show file tree
Hide file tree
Showing 22 changed files with 409 additions and 113 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- Enhance: AiScriptを0.19.0にアップデート
- Enhance: Allow negative delay for MFM animation elements (`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)
- Enhance: センシティブなメディアを開く際に確認ダイアログを出せるように
- Enhance: デッキのアンテナ・リスト選択画面からそれぞれを新規作成できるように
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
Expand Down
12 changes: 12 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,10 @@ export interface Locale extends ILocale {
* アンテナを編集
*/
"editAntenna": string;
/**
* アンテナを作成
*/
"createAntenna": string;
/**
* ウィジェットを選択
*/
Expand Down Expand Up @@ -5024,6 +5028,14 @@ export interface Locale extends ILocale {
* センシティブなメディアです。表示しますか?
*/
"sensitiveMediaRevealConfirm": string;
/**
* 作成したリスト
*/
"createdLists": string;
/**
* 作成したアンテナ
*/
"createdAntennas": string;
"_delivery": {
/**
* 配信状態
Expand Down
3 changes: 3 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ editList: "リストを編集"
selectChannel: "チャンネルを選択"
selectAntenna: "アンテナを選択"
editAntenna: "アンテナを編集"
createAntenna: "アンテナを作成"
selectWidget: "ウィジェットを選択"
editWidgets: "ウィジェットを編集"
editWidgetsExit: "編集を終了"
Expand Down Expand Up @@ -1252,6 +1253,8 @@ inquiry: "お問い合わせ"
tryAgain: "もう一度お試しください。"
confirmWhenRevealingSensitiveMedia: "センシティブなメディアを表示するとき確認する"
sensitiveMediaRevealConfirm: "センシティブなメディアです。表示しますか?"
createdLists: "作成したリスト"
createdAntennas: "作成したアンテナ"

_delivery:
status: "配信状態"
Expand Down
62 changes: 62 additions & 0 deletions packages/frontend/src/components/MkAntennaEditor.stories.impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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 { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAntennaEditor from './MkAntennaEditor.vue';
export const Default = {
render(args) {
return {
components: {
MkAntennaEditor,
},
setup() {
return {
args,
};
},
computed: {
props() {
return {
...this.args,
};
},
events() {
return {
created: action('created'),
updated: action('updated'),
deleted: action('deleted'),
};
},
},
template: '<MkAntennaEditor v-bind="props" v-on="events" />',
};
},
args: {
},
parameters: {
layout: 'fullscreen',
msw: {
handlers: [
...commonHandlers,
http.post('/api/antennas/create', async ({ request }) => {
action('POST /api/antennas/create')(await request.json());
return HttpResponse.json({});
}),
http.post('/api/antennas/update', async ({ request }) => {
action('POST /api/antennas/update')(await request.json());
return HttpResponse.json({});
}),
http.post('/api/antennas/delete', async ({ request }) => {
action('POST /api/antennas/delete')(await request.json());
return HttpResponse.json();
}),
],
},
},
} satisfies StoryObj<typeof MkAntennaEditor>;
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.actions">
<div class="_buttons">
<MkButton inline primary @click="saveAntenna()"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
<MkButton v-if="antenna.id != null" inline danger @click="deleteAntenna()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
<MkButton v-if="initialAntenna.id != null" inline danger @click="deleteAntenna()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
</div>
</div>
</div>
Expand All @@ -61,28 +61,53 @@ import MkSwitch from '@/components/MkSwitch.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import { deepMerge } from '@/scripts/merge.js';
import type { DeepPartial } from '@/scripts/merge.js';

type PartialAllowedAntenna = Omit<Misskey.entities.Antenna, 'id' | 'createdAt' | 'updatedAt'> & {
id?: string;
createdAt?: string;
updatedAt?: string;
};

const props = defineProps<{
antenna: Misskey.entities.Antenna
antenna?: DeepPartial<PartialAllowedAntenna>;
}>();

const initialAntenna = deepMerge<PartialAllowedAntenna>(props.antenna ?? {}, {
name: '',
src: 'all',
userListId: null,
users: [],
keywords: [],
excludeKeywords: [],
excludeBots: false,
withReplies: false,
caseSensitive: false,
localOnly: false,
withFile: false,
isActive: true,
hasUnreadNote: false,
notify: false,
});

const emit = defineEmits<{
(ev: 'created'): void,
(ev: 'updated'): void,
(ev: 'created', newAntenna: Misskey.entities.Antenna): void,
(ev: 'updated', editedAntenna: Misskey.entities.Antenna): void,
(ev: 'deleted'): void,
}>();

const name = ref<string>(props.antenna.name);
const src = ref<Misskey.entities.AntennasCreateRequest['src']>(props.antenna.src);
const userListId = ref<string | null>(props.antenna.userListId);
const users = ref<string>(props.antenna.users.join('\n'));
const keywords = ref<string>(props.antenna.keywords.map(x => x.join(' ')).join('\n'));
const excludeKeywords = ref<string>(props.antenna.excludeKeywords.map(x => x.join(' ')).join('\n'));
const caseSensitive = ref<boolean>(props.antenna.caseSensitive);
const localOnly = ref<boolean>(props.antenna.localOnly);
const excludeBots = ref<boolean>(props.antenna.excludeBots);
const withReplies = ref<boolean>(props.antenna.withReplies);
const withFile = ref<boolean>(props.antenna.withFile);
const name = ref<string>(initialAntenna.name);
const src = ref<Misskey.entities.AntennasCreateRequest['src']>(initialAntenna.src);
const userListId = ref<string | null>(initialAntenna.userListId);
const users = ref<string>(initialAntenna.users.join('\n'));
const keywords = ref<string>(initialAntenna.keywords.map(x => x.join(' ')).join('\n'));
const excludeKeywords = ref<string>(initialAntenna.excludeKeywords.map(x => x.join(' ')).join('\n'));
const caseSensitive = ref<boolean>(initialAntenna.caseSensitive);
const localOnly = ref<boolean>(initialAntenna.localOnly);
const excludeBots = ref<boolean>(initialAntenna.excludeBots);
const withReplies = ref<boolean>(initialAntenna.withReplies);
const withFile = ref<boolean>(initialAntenna.withFile);
const userLists = ref<Misskey.entities.UserList[] | null>(null);

watch(() => src.value, async () => {
Expand All @@ -106,24 +131,26 @@ async function saveAntenna() {
excludeKeywords: excludeKeywords.value.trim().split('\n').map(x => x.trim().split(' ')),
};

if (props.antenna.id == null) {
await os.apiWithDialog('antennas/create', antennaData);
emit('created');
if (initialAntenna.id == null) {
const res = await os.apiWithDialog('antennas/create', antennaData);
emit('created', res);
} else {
await os.apiWithDialog('antennas/update', { ...antennaData, antennaId: props.antenna.id });
emit('updated');
const res = await os.apiWithDialog('antennas/update', { ...antennaData, antennaId: initialAntenna.id });
emit('updated', res);
}
}

async function deleteAntenna() {
if (initialAntenna.id == null) return;

const { canceled } = await os.confirm({
type: 'warning',
text: i18n.tsx.removeAreYouSure({ x: props.antenna.name }),
text: i18n.tsx.removeAreYouSure({ x: initialAntenna.name }),
});
if (canceled) return;

await misskeyApi('antennas/delete', {
antennaId: props.antenna.id,
antennaId: initialAntenna.id,
});

os.success();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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 { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAntennaEditorDialog from './MkAntennaEditorDialog.vue';
export const Default = {
render(args) {
return {
components: {
MkAntennaEditorDialog,
},
setup() {
return {
args,
};
},
computed: {
props() {
return {
...this.args,
};
},
events() {
return {
created: action('created'),
updated: action('updated'),
deleted: action('deleted'),
closed: action('closed'),
};
},
},
template: '<MkAntennaEditorDialog v-bind="props" v-on="events" />',
};
},
args: {
},
parameters: {
layout: 'centered',
msw: {
handlers: [
...commonHandlers,
http.post('/api/antennas/create', async ({ request }) => {
action('POST /api/antennas/create')(await request.json());
return HttpResponse.json({});
}),
http.post('/api/antennas/update', async ({ request }) => {
action('POST /api/antennas/update')(await request.json());
return HttpResponse.json({});
}),
http.post('/api/antennas/delete', async ({ request }) => {
action('POST /api/antennas/delete')(await request.json());
return HttpResponse.json();
}),
],
},
},
} satisfies StoryObj<typeof MkAntennaEditorDialog>;
63 changes: 63 additions & 0 deletions packages/frontend/src/components/MkAntennaEditorDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->

<template>
<MkModalWindow
ref="dialog"
:withOkButton="false"
:width="500"
:height="550"
@close="close()"
@closed="emit('closed')"
>
<template #header>{{ antenna == null ? i18n.ts.createAntenna : i18n.ts.editAntenna }}</template>
<XAntennaEditor
:antenna="antenna"
@created="onAntennaCreated"
@updated="onAntennaUpdated"
@deleted="onAntennaDeleted"
/>
</MkModalWindow>
</template>

<script lang="ts" setup>
import { shallowRef } from 'vue';
import * as Misskey from 'misskey-js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import XAntennaEditor from '@/components/MkAntennaEditor.vue';
import { i18n } from '@/i18n.js';

defineProps<{
antenna?: Misskey.entities.Antenna;
}>();

const emit = defineEmits<{
(ev: 'created', newAntenna: Misskey.entities.Antenna): void,
(ev: 'updated', editedAntenna: Misskey.entities.Antenna): void,
(ev: 'deleted'): void,
(ev: 'closed'): void,
}>();

const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();

function onAntennaCreated(newAntenna: Misskey.entities.Antenna) {
emit('created', newAntenna);
dialog.value?.close();
}

function onAntennaUpdated(editedAntenna: Misskey.entities.Antenna) {
emit('updated', editedAntenna);
dialog.value?.close();
}

function onAntennaDeleted() {
emit('deleted');
dialog.value?.close();
}

function close() {
dialog.value?.close();
}
</script>
20 changes: 15 additions & 5 deletions packages/frontend/src/components/MkDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkInput>
<MkSelect v-if="select" v-model="selectedValue" autofocus>
<template v-if="select.items">
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
<template v-for="item in select.items">
<optgroup v-if="'sectionTitle' in item" :label="item.sectionTitle">
<option v-for="subItem in item.items" :value="subItem.value">{{ subItem.text }}</option>
</optgroup>
<option v-else :value="item.value">{{ item.text }}</option>
</template>
</template>
</MkSelect>
<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
Expand Down Expand Up @@ -67,11 +72,16 @@ type Input = {
maxLength?: number;
};

type SelectItem = {
value: any;
text: string;
};

type Select = {
items: {
value: any;
text: string;
}[];
items: (SelectItem | {
sectionTitle: string;
items: SelectItem[];
})[];
default: string | null;
};

Expand Down
Loading

0 comments on commit 738b3ea

Please sign in to comment.