implement optional confetti on announcements

This commit is contained in:
bunnybeam 2025-07-07 20:03:28 +01:00
parent 69f3c8a58e
commit 45bf8262aa
No known key found for this signature in database
12 changed files with 60 additions and 1 deletions

View file

@ -1501,6 +1501,8 @@ _announcement:
dialogAnnouncementUxWarn: "Having two or more dialog-style notifications simultaneously can significantly impact the user experience, so please use them carefully."
silence: "No notification"
silenceDescription: "Turning this on will skip the notification of this announcement and the user won't need to read it."
confetti: "Throw confetti"
confettiDescription: "If enabled, the announcement will display a confetti effect when viewed."
_initialAccountSetting:
accountCreated: "Your account was successfully created!"
letsStartAccountSetup: "For starters, let's set up your profile."

4
locales/index.d.ts vendored
View file

@ -13346,6 +13346,10 @@ export interface Locale extends ILocale {
* Don't delete files used as avatars&c
*/
"keepFilesInUse": string;
/**
* this option requires more complicated database queries, you may need to increase the value of db.extra.statement_timeout in the configuration file
*/
"keepFilesInUseDescription": string;
};
}
declare const locales: {

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: bunnybeam and other Sharkey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class AnnouncementConfetti1751912435779 {
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "announcement" ADD "confetti" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`CREATE INDEX "IDX_94aabe9f742bc9808264a1c97c" ON "announcement" ("confetti") `);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_94aabe9f742bc9808264a1c97c"`);
await queryRunner.query(`ALTER TABLE "announcement" DROP COLUMN "confetti"`);
}
}

View file

@ -78,6 +78,7 @@ export class AnnouncementService {
forExistingUsers: values.forExistingUsers,
silence: values.silence,
needConfirmationToRead: values.needConfirmationToRead,
confetti: values.confetti,
userId: values.userId,
});
@ -130,6 +131,7 @@ export class AnnouncementService {
forExistingUsers: values.forExistingUsers,
silence: values.silence,
needConfirmationToRead: values.needConfirmationToRead,
confetti: values.confetti,
isActive: values.isActive,
});

View file

@ -55,6 +55,7 @@ export class AnnouncementEntityService {
forYou: announcement.userId === me?.id,
needConfirmationToRead: announcement.needConfirmationToRead,
silence: announcement.silence,
confetti: announcement.confetti,
isRead: announcement.isRead !== null ? announcement.isRead : undefined,
};
}

View file

@ -72,6 +72,12 @@ export class MiAnnouncement {
})
public silence: boolean;
@Index()
@Column('boolean', {
default: false,
})
public confetti: boolean;
@Index()
@Column({
...id(),

View file

@ -52,6 +52,10 @@ export const packedAnnouncementSchema = {
type: 'boolean',
optional: false, nullable: false,
},
confetti: {
type: 'boolean',
optional: false, nullable: false,
},
forYou: {
type: 'boolean',
optional: false, nullable: false,

View file

@ -61,6 +61,7 @@ export const paramDef = {
forExistingUsers: { type: 'boolean', default: false },
silence: { type: 'boolean', default: false },
needConfirmationToRead: { type: 'boolean', default: false },
confetti: { type: 'boolean', default: false },
userId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
},
required: ['title', 'text', 'imageUrl'],
@ -83,6 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
forExistingUsers: ps.forExistingUsers,
silence: ps.silence,
needConfirmationToRead: ps.needConfirmationToRead,
confetti: ps.confetti,
userId: ps.userId,
}, me);

View file

@ -124,6 +124,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
forExistingUsers: announcement.forExistingUsers,
silence: announcement.silence,
needConfirmationToRead: announcement.needConfirmationToRead,
confetti: announcement.confetti,
userId: announcement.userId,
reads: reads.get(announcement)!,
}));

View file

@ -38,6 +38,7 @@ export const paramDef = {
forExistingUsers: { type: 'boolean' },
silence: { type: 'boolean' },
needConfirmationToRead: { type: 'boolean' },
confetti: { type: 'boolean' },
isActive: { type: 'boolean' },
},
required: ['id'],
@ -67,6 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
forExistingUsers: ps.forExistingUsers,
silence: ps.silence,
needConfirmationToRead: ps.needConfirmationToRead,
confetti: ps.confetti,
isActive: ps.isActive,
}, me);
});

View file

@ -70,6 +70,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="announcement.needConfirmationToRead" :helpText="i18n.ts._announcement.needConfirmationToReadDescription">
{{ i18n.ts._announcement.needConfirmationToRead }}
</MkSwitch>
<MkSwitch v-model="announcement.confetti" :helpText="i18n.ts._announcement.confettiDescription">
{{ i18n.ts._announcement.confetti }}
</MkSwitch>
<p v-if="announcement.reads">{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}</p>
</div>
</MkFolder>
@ -127,6 +130,7 @@ function add() {
forExistingUsers: false,
silence: false,
needConfirmationToRead: false,
confetti: false,
});
}

View file

@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, computed, watch } from 'vue';
import { ref, computed, watch, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
@ -57,6 +57,7 @@ import { definePage } from '@/page.js';
import { $i } from '@/i.js';
import { prefer } from '@/preferences.js';
import { updateCurrentAccountPartial } from '@/accounts.js';
import { confetti } from '@/utility/confetti.js';
const props = defineProps<{
announcementId: string;
@ -67,11 +68,22 @@ const error = ref<any>(null);
const path = computed(() => props.announcementId);
function fetch() {
console.log("aaa");
announcement.value = null;
misskeyApi('announcements/show', {
announcementId: props.announcementId,
}).then(async _announcement => {
console.log("bbbb");
announcement.value = _announcement;
console.log("cccc");
console.log(announcement.value.confetti);
console.log(announcement.value.isRead);
if (announcement.value.confetti && !announcement.value.isRead) {
console.log("dddd");
confetti({
duration: 1000 * 3,
});
}
}).catch(err => {
error.value = err;
});
@ -106,6 +118,9 @@ definePage(() => ({
title: announcement.value ? announcement.value.title : i18n.ts.announcements,
icon: 'ti ti-speakerphone',
}));
onMounted(() => {
});
</script>
<style lang="scss" module>