summaryrefslogtreecommitdiff
path: root/packages/frontend/src/ui/deck
diff options
context:
space:
mode:
authordakkar <dakkar@thenautilus.net>2024-05-31 12:26:07 +0100
committerdakkar <dakkar@thenautilus.net>2024-05-31 12:26:07 +0100
commit3372e0ffe153d996bdf04ae3d834633d0ba18fca (patch)
tree7a46452030b2a2ca24d1ea9158fa5f0f75000245 /packages/frontend/src/ui/deck
parentMerge branch 'develop' into future (diff)
parentBump version to 2024.5.0-rc.9 (diff)
downloadsharkey-3372e0ffe153d996bdf04ae3d834633d0ba18fca.tar.gz
sharkey-3372e0ffe153d996bdf04ae3d834633d0ba18fca.tar.bz2
sharkey-3372e0ffe153d996bdf04ae3d834633d0ba18fca.zip
Merge remote-tracking branch 'misskey/release/2024.5.0' into future
Diffstat (limited to 'packages/frontend/src/ui/deck')
-rw-r--r--packages/frontend/src/ui/deck/antenna-column.vue24
-rw-r--r--packages/frontend/src/ui/deck/channel-column.vue37
-rw-r--r--packages/frontend/src/ui/deck/deck-store.ts2
-rw-r--r--packages/frontend/src/ui/deck/list-column.vue22
-rw-r--r--packages/frontend/src/ui/deck/role-timeline-column.vue23
-rw-r--r--packages/frontend/src/ui/deck/tl-column.vue20
-rw-r--r--packages/frontend/src/ui/deck/tl-note-notification.ts107
7 files changed, 216 insertions, 19 deletions
diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue
index 79c7c48073..2df8b9ead7 100644
--- a/packages/frontend/src/ui/deck/antenna-column.vue
+++ b/packages/frontend/src/ui/deck/antenna-column.vue
@@ -9,18 +9,22 @@ SPDX-License-Identifier: AGPL-3.0-only
<i class="ph-flying-saucer ph-bold ph-lg"></i><span style="margin-left: 8px;">{{ column.name }}</span>
</template>
- <MkTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId"/>
+ <MkTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @note="onNote"/>
</XColumn>
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, ref, shallowRef, watch } from 'vue';
import XColumn from './column.vue';
import { updateColumn, Column } from './deck-store.js';
import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
+import { MenuItem } from '@/types/menu.js';
+import { SoundStore } from '@/store.js';
+import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
+import * as sound from '@/scripts/sound.js';
const props = defineProps<{
column: Column;
@@ -28,6 +32,7 @@ const props = defineProps<{
}>();
const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
onMounted(() => {
if (props.column.antennaId == null) {
@@ -35,6 +40,10 @@ onMounted(() => {
}
});
+watch(soundSetting, v => {
+ updateColumn(props.column.id, { soundSetting: v });
+});
+
async function setAntenna() {
const antennas = await misskeyApi('antennas/list');
const { canceled, result: antenna } = await os.select({
@@ -54,7 +63,11 @@ function editAntenna() {
os.pageWindow('my/antennas/' + props.column.antennaId);
}
-const menu = [
+function onNote() {
+ sound.playMisskeySfxFile(soundSetting.value);
+}
+
+const menu: MenuItem[] = [
{
icon: 'ph-pencil-simple ph-bold ph-lg',
text: i18n.ts.selectAntenna,
@@ -65,6 +78,11 @@ const menu = [
text: i18n.ts.editAntenna,
action: editAntenna,
},
+ {
+ icon: 'ti ti-bell',
+ text: i18n.ts._deck.newNoteNotificationSettings,
+ action: () => soundSettingsButton(soundSetting),
+ },
];
/*
diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue
index 993be46910..8f326c6689 100644
--- a/packages/frontend/src/ui/deck/channel-column.vue
+++ b/packages/frontend/src/ui/deck/channel-column.vue
@@ -13,21 +13,26 @@ SPDX-License-Identifier: AGPL-3.0-only
<div style="padding: 8px; text-align: center;">
<MkButton primary gradate rounded inline small @click="post"><i class="ph-pencil-simple ph-bold ph-lg"></i></MkButton>
</div>
- <MkTimeline ref="timeline" src="channel" :channel="column.channelId" :key="column.channelId + column.withRenotes + column.onlyFiles" :withRenotes="withRenotes" :onlyFiles="onlyFiles"/>
+ <MkTimeline ref="timeline" src="channel" :channel="column.channelId" :key="column.channelId + column.withRenotes + column.onlyFiles" :withRenotes="withRenotes" :onlyFiles="onlyFiles" @note="onNote"/>
</template>
</XColumn>
</template>
<script lang="ts" setup>
-import { watch, ref, shallowRef } from 'vue';
+import { ref, shallowRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import XColumn from './column.vue';
import { updateColumn, Column } from './deck-store.js';
import MkTimeline from '@/components/MkTimeline.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
+import { favoritedChannelsCache } from '@/cache.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
+import { MenuItem } from '@/types/menu.js';
+import { SoundStore } from '@/store.js';
+import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
+import * as sound from '@/scripts/sound.js';
const props = defineProps<{
column: Column;
@@ -51,25 +56,29 @@ watch(onlyFiles, v => {
});
});
+const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
+
if (props.column.channelId == null) {
setChannel();
}
+watch(soundSetting, v => {
+ updateColumn(props.column.id, { soundSetting: v });
+});
+
async function setChannel() {
- const channels = await misskeyApi('channels/my-favorites', {
- limit: 100,
- });
- const { canceled, result: channel } = await os.select({
+ const channels = await favoritedChannelsCache.fetch();
+ const { canceled, result: chosenChannel } = await os.select({
title: i18n.ts.selectChannel,
items: channels.map(x => ({
value: x, text: x.name,
})),
default: props.column.channelId,
});
- if (canceled) return;
+ if (canceled || chosenChannel == null) return;
updateColumn(props.column.id, {
- channelId: channel.id,
- name: channel.name,
+ channelId: chosenChannel.id,
+ name: chosenChannel.name,
});
}
@@ -85,7 +94,11 @@ async function post() {
});
}
-const menu = [{
+function onNote() {
+ sound.playMisskeySfxFile(soundSetting.value);
+}
+
+const menu: MenuItem[] = [{
icon: 'ph-pencil-simple ph-bold ph-lg',
text: i18n.ts.selectChannel,
action: setChannel,
@@ -97,5 +110,9 @@ const menu = [{
type: 'switch',
text: i18n.ts.fileAttachedOnly,
ref: onlyFiles,
+}, {
+ icon: 'ti ti-bell',
+ text: i18n.ts._deck.newNoteNotificationSettings,
+ action: () => soundSettingsButton(soundSetting),
}];
</script>
diff --git a/packages/frontend/src/ui/deck/deck-store.ts b/packages/frontend/src/ui/deck/deck-store.ts
index 6c4e2fd52b..1a4f7c5e17 100644
--- a/packages/frontend/src/ui/deck/deck-store.ts
+++ b/packages/frontend/src/ui/deck/deck-store.ts
@@ -9,6 +9,7 @@ import { notificationTypes } from 'misskey-js';
import { Storage } from '@/pizzax.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { deepClone } from '@/scripts/clone.js';
+import { SoundStore } from '@/store.js';
type ColumnWidget = {
name: string;
@@ -33,6 +34,7 @@ export type Column = {
withRenotes?: boolean;
withReplies?: boolean;
onlyFiles?: boolean;
+ soundSetting: SoundStore;
};
export const deckStore = markRaw(new Storage('deck', {
diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue
index f7988ed1b7..14f3e5fcd9 100644
--- a/packages/frontend/src/ui/deck/list-column.vue
+++ b/packages/frontend/src/ui/deck/list-column.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<i class="ph-list ph-bold ph-lg"></i><span style="margin-left: 8px;">{{ column.name }}</span>
</template>
- <MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :key="column.listId + column.withRenotes + column.onlyFiles" :withRenotes="withRenotes" :onlyFiles="onlyFiles"/>
+ <MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :key="column.listId + column.withRenotes + column.onlyFiles" :withRenotes="withRenotes" :onlyFiles="onlyFiles" @note="onNote"/>
</XColumn>
</template>
@@ -21,6 +21,10 @@ import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
+import { MenuItem } from '@/types/menu.js';
+import { SoundStore } from '@/store.js';
+import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
+import * as sound from '@/scripts/sound.js';
const props = defineProps<{
column: Column;
@@ -30,6 +34,7 @@ const props = defineProps<{
const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
const withRenotes = ref(props.column.withRenotes ?? true);
const onlyFiles = ref(props.column.onlyFiles ?? false);
+const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
if (props.column.listId == null) {
setList();
@@ -47,6 +52,10 @@ watch(onlyFiles, v => {
});
});
+watch(soundSetting, v => {
+ updateColumn(props.column.id, { soundSetting: v });
+});
+
async function setList() {
const lists = await misskeyApi('users/lists/list');
const { canceled, result: list } = await os.select({
@@ -66,7 +75,11 @@ function editList() {
os.pageWindow('my/lists/' + props.column.listId);
}
-const menu = [
+function onNote() {
+ sound.playMisskeySfxFile(soundSetting.value);
+}
+
+const menu: MenuItem[] = [
{
icon: 'ph-pencil-simple ph-bold ph-lg',
text: i18n.ts.selectList,
@@ -87,5 +100,10 @@ const menu = [
text: i18n.ts.fileAttachedOnly,
ref: onlyFiles,
},
+ {
+ icon: 'ti ti-bell',
+ text: i18n.ts._deck.newNoteNotificationSettings,
+ action: () => soundSettingsButton(soundSetting),
+ },
];
</script>
diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue
index 1a673a1753..2fe53918ff 100644
--- a/packages/frontend/src/ui/deck/role-timeline-column.vue
+++ b/packages/frontend/src/ui/deck/role-timeline-column.vue
@@ -9,18 +9,22 @@ SPDX-License-Identifier: AGPL-3.0-only
<i class="ph-seal-check ph-bold ph-lg"></i><span style="margin-left: 8px;">{{ column.name }}</span>
</template>
- <MkTimeline v-if="column.roleId" ref="timeline" src="role" :role="column.roleId"/>
+ <MkTimeline v-if="column.roleId" ref="timeline" src="role" :role="column.roleId" @note="onNote"/>
</XColumn>
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, ref, shallowRef, watch } from 'vue';
import XColumn from './column.vue';
import { updateColumn, Column } from './deck-store.js';
import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
+import { MenuItem } from '@/types/menu.js';
+import { SoundStore } from '@/store.js';
+import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
+import * as sound from '@/scripts/sound.js';
const props = defineProps<{
column: Column;
@@ -28,6 +32,7 @@ const props = defineProps<{
}>();
const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
onMounted(() => {
if (props.column.roleId == null) {
@@ -35,6 +40,10 @@ onMounted(() => {
}
});
+watch(soundSetting, v => {
+ updateColumn(props.column.id, { soundSetting: v });
+});
+
async function setRole() {
const roles = (await misskeyApi('roles/list')).filter(x => x.isExplorable);
const { canceled, result: role } = await os.select({
@@ -50,10 +59,18 @@ async function setRole() {
});
}
-const menu = [{
+function onNote() {
+ sound.playMisskeySfxFile(soundSetting.value);
+}
+
+const menu: MenuItem[] = [{
icon: 'ph-pencil-simple ph-bold ph-lg',
text: i18n.ts.role,
action: setRole,
+}, {
+ icon: 'ti ti-bell',
+ text: i18n.ts._deck.newNoteNotificationSettings,
+ action: () => soundSettingsButton(soundSetting),
}];
/*
diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue
index 3745d026e8..d9474bad8c 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -29,6 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:withRenotes="withRenotes"
:withReplies="withReplies"
:onlyFiles="onlyFiles"
+ @note="onNote"
/>
</XColumn>
</template>
@@ -42,6 +43,10 @@ import * as os from '@/os.js';
import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
+import { MenuItem } from '@/types/menu.js';
+import { SoundStore } from '@/store.js';
+import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
+import * as sound from '@/scripts/sound.js';
const props = defineProps<{
column: Column;
@@ -54,6 +59,7 @@ const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable));
const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable));
const isBubbleTimelineAvailable = ($i == null && instance.policies.btlAvailable) || ($i != null && $i.policies.btlAvailable);
+const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
const withRenotes = ref(props.column.withRenotes ?? true);
const withReplies = ref(props.column.withReplies ?? false);
const onlyFiles = ref(props.column.onlyFiles ?? false);
@@ -76,6 +82,10 @@ watch(onlyFiles, v => {
});
});
+watch(soundSetting, v => {
+ updateColumn(props.column.id, { soundSetting: v });
+});
+
onMounted(() => {
if (props.column.tl == null) {
setType();
@@ -113,11 +123,19 @@ async function setType() {
});
}
-const menu = [{
+function onNote() {
+ sound.playMisskeySfxFile(soundSetting.value);
+}
+
+const menu: MenuItem[] = [{
icon: 'ph-pencil-simple ph-bold ph-lg',
text: i18n.ts.timeline,
action: setType,
}, {
+ icon: 'ti ti-bell',
+ text: i18n.ts._deck.newNoteNotificationSettings,
+ action: () => soundSettingsButton(soundSetting),
+}, {
type: 'switch',
text: i18n.ts.showRenotes,
ref: withRenotes,
diff --git a/packages/frontend/src/ui/deck/tl-note-notification.ts b/packages/frontend/src/ui/deck/tl-note-notification.ts
new file mode 100644
index 0000000000..275ea56ba0
--- /dev/null
+++ b/packages/frontend/src/ui/deck/tl-note-notification.ts
@@ -0,0 +1,107 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as Misskey from 'misskey-js';
+import { Ref } from 'vue';
+import { SoundStore } from '@/store.js';
+import { getSoundDuration, playMisskeySfxFile, soundsTypes, SoundType } from '@/scripts/sound.js';
+import { i18n } from '@/i18n.js';
+import * as os from '@/os.js';
+
+export async function soundSettingsButton(soundSetting: Ref<SoundStore>): Promise<void> {
+ function getSoundTypeName(f: SoundType): string {
+ switch (f) {
+ case null:
+ return i18n.ts.none;
+ case '_driveFile_':
+ return i18n.ts._soundSettings.driveFile;
+ default:
+ return f;
+ }
+ }
+
+ const { canceled, result } = await os.form(i18n.ts.sound, {
+ type: {
+ type: 'enum',
+ label: i18n.ts.sound,
+ default: soundSetting.value.type ?? 'none',
+ enum: soundsTypes.map(f => ({
+ value: f ?? 'none', label: getSoundTypeName(f),
+ })),
+ },
+ soundFile: {
+ type: 'drive-file',
+ label: i18n.ts.file,
+ defaultFileId: soundSetting.value.type === '_driveFile_' ? soundSetting.value.fileId : null,
+ hidden: v => v.type !== '_driveFile_',
+ validate: async (file: Misskey.entities.DriveFile) => {
+ if (!file.type.startsWith('audio')) {
+ os.alert({
+ type: 'warning',
+ title: i18n.ts._soundSettings.driveFileTypeWarn,
+ text: i18n.ts._soundSettings.driveFileTypeWarnDescription,
+ });
+ return false;
+ }
+
+ const duration = await getSoundDuration(file.url);
+ if (duration >= 2000) {
+ const { canceled } = await os.confirm({
+ type: 'warning',
+ title: i18n.ts._soundSettings.driveFileDurationWarn,
+ text: i18n.ts._soundSettings.driveFileDurationWarnDescription,
+ okText: i18n.ts.continue,
+ cancelText: i18n.ts.cancel,
+ });
+ if (canceled) return false;
+ }
+
+ return true;
+ },
+ },
+ volume: {
+ type: 'range',
+ label: i18n.ts.volume,
+ default: soundSetting.value.volume ?? 1,
+ textConverter: (v) => `${Math.floor(v * 100)}%`,
+ min: 0,
+ max: 1,
+ step: 0.05,
+ },
+ listen: {
+ type: 'button',
+ content: i18n.ts.listen,
+ action: (_, v) => {
+ const sound = buildSoundStore(v);
+ if (!sound) return;
+ playMisskeySfxFile(sound);
+ },
+ },
+ });
+ if (canceled) return;
+
+ const res = buildSoundStore(result);
+ if (res) soundSetting.value = res;
+
+ function buildSoundStore(result: any): SoundStore | null {
+ const type = (result.type === 'none' ? null : result.type) as SoundType;
+ const volume = result.volume as number;
+ const fileId = result.soundFile?.id ?? (soundSetting.value.type === '_driveFile_' ? soundSetting.value.fileId : undefined);
+ const fileUrl = result.soundFile?.url ?? (soundSetting.value.type === '_driveFile_' ? soundSetting.value.fileUrl : undefined);
+
+ if (type === '_driveFile_') {
+ if (!fileUrl || !fileId) {
+ os.alert({
+ type: 'warning',
+ text: i18n.ts._soundSettings.driveFileWarn,
+ });
+ return null;
+ }
+ return { type, volume, fileId, fileUrl };
+ } else {
+ return { type, volume };
+ }
+ }
+}