diff --git a/.github/workflows/changelog-comment.yml b/.github/workflows/changelog-comment.yml index 33dd202..fcba466 100644 --- a/.github/workflows/changelog-comment.yml +++ b/.github/workflows/changelog-comment.yml @@ -12,7 +12,7 @@ on: permissions: issues: write - pull-requests: read + pull-requests: write jobs: comment: @@ -21,7 +21,7 @@ jobs: permissions: issues: write - pull-requests: read + pull-requests: write contents: read steps: diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 521a733..d0418bf 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -95,6 +95,7 @@ import NavRail from '@/components/ui/NavRail.vue' import OnboardingOverlay from '@/components/ui/onboarding/OnboardingOverlay.vue' import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue' import SkinSiteSessionFrame from '@/components/ui/SkinSiteSessionFrame.vue' +import RemoteAnnouncements from '@/components/ui/RemoteAnnouncements.vue' import StartupAnnouncementModal from '@/components/ui/StartupAnnouncementModal.vue' import SplashScreen from '@/components/ui/SplashScreen.vue' import WindowControls from '@/components/ui/WindowControls.vue' @@ -2227,6 +2228,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload) +
+ + + import { BellIcon, + NewspaperIcon, DownloadIcon, DropdownIcon, OnlineIndicatorIcon, @@ -211,7 +229,7 @@ import { } from '@modrinth/ui' import { convertFileSrc } from '@tauri-apps/api/core' import { Dropdown } from 'floating-vue' -import { computed, onBeforeUnmount, ref, watch } from 'vue' +import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' import AppUpdateButton from '@/components/ui/app-update-button/index.vue' @@ -222,6 +240,10 @@ import { get_many as getInstances } from '@/helpers/instance' import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process' import type { LoadingBar } from '@/helpers/state' import { progress_bars_list } from '@/helpers/state' +import { + OPEN_REMOTE_ANNOUNCEMENT_CENTER_EVENT, + REMOTE_ANNOUNCEMENTS_UPDATED_EVENT, +} from '@/helpers/remote-announcements' import type { GameInstance } from '@/helpers/types' import { downloadBarTypes, injectDownloadManager } from '@/providers/download-manager' @@ -231,6 +253,25 @@ const popupNotificationManager = injectPopupNotificationManager() const downloadManager = injectDownloadManager() const { formatMessage } = useVIntl() +const announcementUnreadCount = ref(0) + +function openAnnouncementCenter() { + window.dispatchEvent(new CustomEvent(OPEN_REMOTE_ANNOUNCEMENT_CENTER_EVENT)) +} + +function updateAnnouncementCount(event: Event) { + const detail = (event as CustomEvent<{ unreadKeys?: string[] }>).detail + announcementUnreadCount.value = detail?.unreadKeys?.length ?? 0 +} + +onMounted(() => { + window.addEventListener(REMOTE_ANNOUNCEMENTS_UPDATED_EVENT, updateAnnouncementCount) +}) + +onBeforeUnmount(() => { + window.removeEventListener(REMOTE_ANNOUNCEMENTS_UPDATED_EVENT, updateAnnouncementCount) +}) + type NotificationHistoryItem = { key: string createdAt?: number @@ -370,6 +411,10 @@ const messages = defineMessages({ id: 'app.action-bar.notifications', defaultMessage: 'Notifications', }, + announcements: { + id: 'app.action-bar.announcements', + defaultMessage: 'Announcements', + }, clearNotifications: { id: 'app.action-bar.notifications.clear', defaultMessage: 'Clear all', diff --git a/apps/app-frontend/src/components/ui/RemoteAnnouncements.vue b/apps/app-frontend/src/components/ui/RemoteAnnouncements.vue new file mode 100644 index 0000000..4e23f3c --- /dev/null +++ b/apps/app-frontend/src/components/ui/RemoteAnnouncements.vue @@ -0,0 +1,388 @@ + + + diff --git a/apps/app-frontend/src/helpers/remote-announcements.ts b/apps/app-frontend/src/helpers/remote-announcements.ts index a8b2a24..92f7bfe 100644 --- a/apps/app-frontend/src/helpers/remote-announcements.ts +++ b/apps/app-frontend/src/helpers/remote-announcements.ts @@ -64,3 +64,6 @@ export function isAnnouncementActive(item: RemoteAnnouncement, now = Date.now()) export function announcementKey(item: RemoteAnnouncement) { return item.id + ':' + item.published_at } + +export const REMOTE_ANNOUNCEMENTS_UPDATED_EVENT = 'starlight:remote-announcements-updated' +export const OPEN_REMOTE_ANNOUNCEMENT_CENTER_EVENT = 'starlight:open-remote-announcement-center' diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index 399d63b..d588a0e 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -11455,5 +11455,35 @@ }, "app.settings.defaults.window-title.default-placeholder": { "message": "Enter the default window title..." + }, + "app.action-bar.announcements": { + "message": "Announcements" + }, + "app.remote-announcements.center-title": { + "message": "Announcements" + }, + "app.remote-announcements.empty": { + "message": "No announcements" + }, + "app.remote-announcements.view": { + "message": "View announcement" + }, + "app.remote-announcements.unread": { + "message": "Unread" + }, + "app.remote-announcements.read-all": { + "message": "Mark all announcements as read" + }, + "app.remote-announcements.preview-title": { + "message": "Announcement style preview" + }, + "app.remote-announcements.preview-summary": { + "message": "This is a local preview notification. Select View announcement to see the full Markdown content." + }, + "app.remote-announcements.preview-content": { + "message": "## Announcement preview\n\nThis is **sample content**, not a published announcement.\n\n- Supports headings, lists, and links\n- Close buttons are always available\n\n> Previewing does not change real announcement read status.\n\n| Type | Display |\n| --- | --- |\n| Modal | Full Markdown content |\n| Popup | Summary, then full content |\n\n[Visit the website](https://skin.starlight.cool/)" + }, + "app.remote-announcements.preview-action": { + "message": "Visit website" } -} +} \ No newline at end of file diff --git a/apps/app-frontend/src/locales/zh-CN/index.json b/apps/app-frontend/src/locales/zh-CN/index.json index edb61ed..eeedf40 100644 --- a/apps/app-frontend/src/locales/zh-CN/index.json +++ b/apps/app-frontend/src/locales/zh-CN/index.json @@ -11359,5 +11359,8 @@ "app.settings.defaults.window-title.enabled-description": { "message": "允许实例自定义 Minecraft 窗口标题。" }, "app.settings.defaults.window-title.default": { "message": "默认窗口标题" }, "app.settings.defaults.window-title.default-description": { "message": "当实例未提供自己的标题时使用。" }, - "app.settings.defaults.window-title.default-placeholder": { "message": "输入默认窗口标题..." } -} + "app.settings.defaults.window-title.default-placeholder": { "message": "输入默认窗口标题..." }, + "app.action-bar.announcements": { "message": "公告" }, + "app.remote-announcements.center-title": { "message": "公告" }, + "app.remote-announcements.empty": { "message": "暂无公告" } +} \ No newline at end of file diff --git a/packages/ui/src/composables/index.ts b/packages/ui/src/composables/index.ts index 8a74269..f66970b 100644 --- a/packages/ui/src/composables/index.ts +++ b/packages/ui/src/composables/index.ts @@ -8,6 +8,7 @@ export * from './format-number' export * from './how-ago' export * from './i18n' export * from './i18n-debug' +export * from './modal-stack' export * from './page-leave-safety' export * from './scroll-indicator' export type { ArmorPreviewConfig } from './skin-rendering/armor-preview-types' diff --git a/packages/ui/src/locales/en-US/index.json b/packages/ui/src/locales/en-US/index.json index 948c00a..6634624 100644 --- a/packages/ui/src/locales/en-US/index.json +++ b/packages/ui/src/locales/en-US/index.json @@ -4723,5 +4723,143 @@ }, "version.supplementary-resources.type": { "defaultMessage": "Type" - } + }, + "skin.preview.armor.equip-material": { + "defaultMessage": "{material} {slot}" + }, + "skin.preview.armor.material": { + "defaultMessage": "Armor material" + }, + "skin.preview.armor.material.chainmail": { + "defaultMessage": "Chainmail" + }, + "skin.preview.armor.material.copper": { + "defaultMessage": "Copper" + }, + "skin.preview.armor.material.diamond": { + "defaultMessage": "Diamond" + }, + "skin.preview.armor.material.gold": { + "defaultMessage": "Gold" + }, + "skin.preview.armor.material.iron": { + "defaultMessage": "Iron" + }, + "skin.preview.armor.material.leather": { + "defaultMessage": "Leather" + }, + "skin.preview.armor.material.netherite": { + "defaultMessage": "Netherite" + }, + "skin.preview.armor.material.turtle": { + "defaultMessage": "Turtle Shell" + }, + "skin.preview.armor.open": { + "defaultMessage": "Armor and trims" + }, + "skin.preview.armor.piece": { + "defaultMessage": "Armor piece" + }, + "skin.preview.armor.remove-piece": { + "defaultMessage": "Remove {slot}" + }, + "skin.preview.armor.remove-trim": { + "defaultMessage": "No armor trim" + }, + "skin.preview.armor.slot-state": { + "defaultMessage": "{slot}: {state}" + }, + "skin.preview.armor.slot.boots": { + "defaultMessage": "Boots" + }, + "skin.preview.armor.slot.chestplate": { + "defaultMessage": "Chestplate" + }, + "skin.preview.armor.slot.helmet": { + "defaultMessage": "Helmet" + }, + "skin.preview.armor.slot.leggings": { + "defaultMessage": "Leggings" + }, + "skin.preview.armor.trim-material": { + "defaultMessage": "Trim material" + }, + "skin.preview.armor.trim-material.amethyst": { + "defaultMessage": "Amethyst" + }, + "skin.preview.armor.trim-material.emerald": { + "defaultMessage": "Emerald" + }, + "skin.preview.armor.trim-material.lapis": { + "defaultMessage": "Lapis Lazuli" + }, + "skin.preview.armor.trim-material.quartz": { + "defaultMessage": "Quartz" + }, + "skin.preview.armor.trim-material.redstone": { + "defaultMessage": "Redstone" + }, + "skin.preview.armor.trim-material.resin": { + "defaultMessage": "Resin Brick" + }, + "skin.preview.armor.trim-pattern": { + "defaultMessage": "Trim pattern" + }, + "skin.preview.armor.trim.bolt": { + "defaultMessage": "Bolt Armor Trim" + }, + "skin.preview.armor.trim.coast": { + "defaultMessage": "Coast Armor Trim" + }, + "skin.preview.armor.trim.dune": { + "defaultMessage": "Dune Armor Trim" + }, + "skin.preview.armor.trim.eye": { + "defaultMessage": "Eye Armor Trim" + }, + "skin.preview.armor.trim.flow": { + "defaultMessage": "Flow Armor Trim" + }, + "skin.preview.armor.trim.host": { + "defaultMessage": "Host Armor Trim" + }, + "skin.preview.armor.trim.raiser": { + "defaultMessage": "Raiser Armor Trim" + }, + "skin.preview.armor.trim.rib": { + "defaultMessage": "Rib Armor Trim" + }, + "skin.preview.armor.trim.sentry": { + "defaultMessage": "Sentry Armor Trim" + }, + "skin.preview.armor.trim.shaper": { + "defaultMessage": "Shaper Armor Trim" + }, + "skin.preview.armor.trim.silence": { + "defaultMessage": "Silence Armor Trim" + }, + "skin.preview.armor.trim.snout": { + "defaultMessage": "Snout Armor Trim" + }, + "skin.preview.armor.trim.spire": { + "defaultMessage": "Spire Armor Trim" + }, + "skin.preview.armor.trim.tide": { + "defaultMessage": "Tide Armor Trim" + }, + "skin.preview.armor.trim.vex": { + "defaultMessage": "Vex Armor Trim" + }, + "skin.preview.armor.trim.ward": { + "defaultMessage": "Ward Armor Trim" + }, + "skin.preview.armor.trim.wayfinder": { + "defaultMessage": "Wayfinder Armor Trim" + }, + "skin.preview.armor.trim.wild": { + "defaultMessage": "Wild Armor Trim" + }, + "skin.preview.armor.unequipped": { + "defaultMessage": "Unequipped" + } } diff --git a/packages/ui/src/locales/zh-CN/index.json b/packages/ui/src/locales/zh-CN/index.json index cada412..80ae032 100644 --- a/packages/ui/src/locales/zh-CN/index.json +++ b/packages/ui/src/locales/zh-CN/index.json @@ -4744,5 +4744,143 @@ }, "image-viewer.previous": { "defaultMessage": "上一张图像" - } + }, + "skin.preview.armor.equip-material": { + "message": "为{slot}装备{material}" + }, + "skin.preview.armor.material": { + "message": "盔甲材质" + }, + "skin.preview.armor.material.chainmail": { + "message": "锁链" + }, + "skin.preview.armor.material.copper": { + "message": "铜" + }, + "skin.preview.armor.material.diamond": { + "message": "钻石" + }, + "skin.preview.armor.material.gold": { + "message": "金" + }, + "skin.preview.armor.material.iron": { + "message": "铁" + }, + "skin.preview.armor.material.leather": { + "message": "皮革" + }, + "skin.preview.armor.material.netherite": { + "message": "下界合金" + }, + "skin.preview.armor.material.turtle": { + "message": "海龟壳" + }, + "skin.preview.armor.open": { + "message": "盔甲与纹饰" + }, + "skin.preview.armor.piece": { + "message": "盔甲部位" + }, + "skin.preview.armor.remove-piece": { + "message": "脱下{slot}" + }, + "skin.preview.armor.remove-trim": { + "message": "无盔甲纹饰" + }, + "skin.preview.armor.slot-state": { + "message": "{slot}:{state}" + }, + "skin.preview.armor.slot.boots": { + "message": "靴子" + }, + "skin.preview.armor.slot.chestplate": { + "message": "胸甲" + }, + "skin.preview.armor.slot.helmet": { + "message": "头盔" + }, + "skin.preview.armor.slot.leggings": { + "message": "护腿" + }, + "skin.preview.armor.trim-material": { + "message": "纹饰材料" + }, + "skin.preview.armor.trim-material.amethyst": { + "message": "紫水晶" + }, + "skin.preview.armor.trim-material.emerald": { + "message": "绿宝石" + }, + "skin.preview.armor.trim-material.lapis": { + "message": "青金石" + }, + "skin.preview.armor.trim-material.quartz": { + "message": "下界石英" + }, + "skin.preview.armor.trim-material.redstone": { + "message": "红石" + }, + "skin.preview.armor.trim-material.resin": { + "message": "树脂砖" + }, + "skin.preview.armor.trim-pattern": { + "message": "纹饰图案" + }, + "skin.preview.armor.trim.bolt": { + "message": "镶铆盔甲纹饰" + }, + "skin.preview.armor.trim.coast": { + "message": "海岸盔甲纹饰" + }, + "skin.preview.armor.trim.dune": { + "message": "沙丘盔甲纹饰" + }, + "skin.preview.armor.trim.eye": { + "message": "眼眸盔甲纹饰" + }, + "skin.preview.armor.trim.flow": { + "message": "涡流盔甲纹饰" + }, + "skin.preview.armor.trim.host": { + "message": "雇主盔甲纹饰" + }, + "skin.preview.armor.trim.raiser": { + "message": "牧民盔甲纹饰" + }, + "skin.preview.armor.trim.rib": { + "message": "肋骨盔甲纹饰" + }, + "skin.preview.armor.trim.sentry": { + "message": "哨兵盔甲纹饰" + }, + "skin.preview.armor.trim.shaper": { + "message": "塑造盔甲纹饰" + }, + "skin.preview.armor.trim.silence": { + "message": "幽静盔甲纹饰" + }, + "skin.preview.armor.trim.snout": { + "message": "猪鼻盔甲纹饰" + }, + "skin.preview.armor.trim.spire": { + "message": "尖塔盔甲纹饰" + }, + "skin.preview.armor.trim.tide": { + "message": "潮汐盔甲纹饰" + }, + "skin.preview.armor.trim.vex": { + "message": "恼鬼盔甲纹饰" + }, + "skin.preview.armor.trim.ward": { + "message": "监守盔甲纹饰" + }, + "skin.preview.armor.trim.wayfinder": { + "message": "向导盔甲纹饰" + }, + "skin.preview.armor.trim.wild": { + "message": "荒野盔甲纹饰" + }, + "skin.preview.armor.unequipped": { + "message": "未装备" + } }