Compare commits

..

10 Commits

Author SHA1 Message Date
Beebles 2384f6c40e ensure nulls arent passed to sort 2024-09-13 18:46:49 -06:00
Beebles ab3b69ffed move welcome announcement to default 2024-09-13 18:34:09 -06:00
Beebles 91e4d6ca6d remove duplicates when adding announcements to array 2024-09-13 18:32:22 -06:00
Beebles 5b9613c8df change announcements to be stack 2024-09-13 18:12:56 -06:00
Beebles af9c2ecee9 change to use array of hidden announcements 2024-09-13 17:22:14 -06:00
Beebles 985dc669a1 rename motd to announcements and implement new API 2024-09-13 17:22:14 -06:00
Beebles 3193f71c20 move motd into div with padding 2024-09-13 17:22:14 -06:00
Beebles 29cfe6c66e only set motd if value returned 2024-09-13 17:22:14 -06:00
Beebles 6f8c414859 fix(motd): run prettier 2024-09-13 17:22:14 -06:00
Beebles 1f9359988b feat(motd): add motd component (untested) 2024-09-13 17:22:14 -06:00
10 changed files with 212 additions and 107 deletions
+2 -33
View File
@@ -99,14 +99,12 @@
}
},
"PluginListIndex": {
"freeze": "Замразяване на актуализациите",
"hide": "Бърз достъп: Скриване",
"no_plugin": "Няма инсталирани добавки!",
"plugin_actions": "Действия с добавката",
"reinstall": "Преинсталиране",
"reload": "Презареждане",
"show": "Бърз достъп: Показване",
"unfreeze": "Разрешаване на актуализациите",
"uninstall": "Деинсталиране",
"update_all_one": "Обновяване на 1 добавка",
"update_all_other": "Обновяване на {{count}} добавки",
@@ -194,19 +192,9 @@
"SettingsIndex": {
"developer_title": "Разработчик",
"general_title": "Общи",
"plugins_title": "Добавки",
"testing_title": "Тестване"
"plugins_title": "Добавки"
},
"Store": {
"download_progress_info": {
"download_zip": "Изтегляне на плъгина",
"increment_count": "Увеличаване на броя изтегляния",
"installing_plugin": "Инсталиране на плъгина",
"open_zip": "Отваряне на zip файла",
"parse_zip": "Разглеждане на zip файла",
"start": "Иницииране",
"uninstalling_previous": "Деинсталиране на предишното копие"
},
"store_contrib": {
"desc": "Ако искате да допринесете към магазина за добавки на Decky, разгледайте хранилището SteamDeckHomebrew/decky-plugin-template в GitHub. Може да намерите информация относно разработката и разпространението във файла README.",
"label": "Допринасяне"
@@ -230,17 +218,9 @@
"about": "Относно",
"alph_asce": "По азбучен ред (Я -> А)",
"alph_desc": "По азбучен ред (А -> Я)",
"date_asce": "Най-старият първи",
"date_desc": "Най-новият първи",
"downloads_asce": "Най-малко изтеглени първи",
"downloads_desc": "Най-изтеглени първи",
"title": "Разглеждане"
},
"store_testing_cta": "Помислете дали искате да тествате новите добавки, за да помогнете на екипа на Decky Loader!",
"store_testing_warning": {
"desc": "Можете да използвате този канал на магазина, за да тествате най-новите версии на плъгините. Не забравяйте да оставите обратна връзка в GitHub, за да може плъгинът да бъде актуализиран за всички потребители.",
"label": "Добре дошли в канала на магазина за тестване"
}
"store_testing_cta": "Помислете дали искате да тествате новите добавки, за да помогнете на екипа на Decky Loader!"
},
"StoreSelect": {
"custom_store": {
@@ -254,17 +234,6 @@
"testing": "Тестване"
}
},
"Testing": {
"download": "Изтегляне",
"error": "Грешка при инсталирането на PR",
"header": "Следните версии на Decky Loader са създадени от отворени заявки за изтегляне от трети страни. Екипът на Decky Loader не е проверявал тяхната функционалност или сигурност и е възможно те да са остарели.",
"loading": "Зареждане на отворени заявки за изтегляне...",
"start_download_toast": "Изтегляне на PR #{{id}}"
},
"TitleView": {
"decky_store_desc": "Отворете Decky Store",
"settings_desc": "Отворете настройките на Decky"
},
"Updater": {
"decky_updates": "Обновления на Decky",
"no_patch_notes_desc": "няма бележки за промените в тази версия",
+1 -14
View File
@@ -198,15 +198,6 @@
"testing_title": "Testen"
},
"Store": {
"download_progress_info": {
"download_zip": "Plugin herunterladen",
"increment_count": "Erhöhen der Downloadanzahl",
"installing_plugin": "Plugin installieren",
"open_zip": "Öffnen der Zip-Datei",
"parse_zip": "Parsen der Zip-Datei",
"start": "Initialisieren",
"uninstalling_previous": "Vorherige Kopie deinstallieren"
},
"store_contrib": {
"desc": "Wenn du Erweiterungen im Decky Store veröffentlichen willst, besuche die SteamDeckHomebrew/decky-plugin-template Repository auf GitHub. Informationen rund um Entwicklung und Veröffentlichung findest du in der README.",
"label": "Mitwirken"
@@ -255,11 +246,7 @@
}
},
"Testing": {
"download": "Download",
"error": "Fehler beim Installieren von PR",
"header": "Die folgenden Versionen von Decky Loader wurden aus offenen Pull Requests von Dritten erstellt. Das Decky Loader-Team hat ihre Funktionalität oder Sicherheit nicht überprüft, und sie können veraltet sein.",
"loading": "Offene Pull Requests laden...",
"start_download_toast": "Herunterladen von PR #{{id}}"
"download": "Download"
},
"TitleView": {
"decky_store_desc": "Decky Store Öffnen",
+6 -19
View File
@@ -2,9 +2,9 @@
"BranchSelect": {
"update_channel": {
"label": "Updatekanaal",
"prerelease": "Vooruitgave",
"prerelease": "Prerelease",
"stable": "Stabiel",
"testing": "Testen"
"testing": "Testing"
}
},
"Developer": {
@@ -188,7 +188,7 @@
"header": "Overige"
},
"updates": {
"header": "Bijwerkingen"
"header": "Updates"
}
},
"SettingsIndex": {
@@ -198,15 +198,6 @@
"testing_title": "Testen"
},
"Store": {
"download_progress_info": {
"download_zip": "Plugin downloaden",
"increment_count": "Aantal downloads verhogen",
"installing_plugin": "Plugin installeren",
"open_zip": "Zip-bestand openen",
"parse_zip": "Zip-bestand parseren",
"start": "Initialiseren",
"uninstalling_previous": "Vorige kopie verwijderen"
},
"store_contrib": {
"desc": "Als je wilt bijdragen aan de Decky Plugin Store, kijk dan in de SteamDeckHomebrew/decky-plugin-template repository op GitHub. Informatie over ontwikkeling en distributie is beschikbaar in de README.",
"label": "Bijdragen"
@@ -251,15 +242,11 @@
"custom": "Aangepast",
"default": "Standaard",
"label": "Winkelkanaal",
"testing": "Testen"
"testing": "Testing"
}
},
"Testing": {
"download": "Downloaden",
"error": "Fout bij installatie van PR",
"header": "De volgende versies van Decky Loader zijn gebouwd op basis van open Pull Requests van derden. Het Decky Loader-team heeft hun functionaliteit of veiligheid niet gecontroleerd en ze kunnen verouderd zijn.",
"loading": "Openstaande Pull Requests laden...",
"start_download_toast": "PR #{{id}} downloaden"
"download": "Downloaden"
},
"TitleView": {
"decky_store_desc": "Decky Store openen",
@@ -274,7 +261,7 @@
"checking": "Bezig met controleren op updates",
"cur_version": "Huidige versie: {{ver}}",
"install_button": "Bijwerken",
"label": "Bijwerkingen",
"label": "Updates",
"lat_version": "Bijwerkt: versie {{ver}}",
"reloading": "Bezig met herstarten",
"updating": "Bezig met bijwerken"
@@ -11,7 +11,7 @@ from setproctitle import setproctitle, setthreadtitle
from .messages import SocketResponseDict, SocketMessageType
from ..localplatform.localsocket import LocalSocket
from ..localplatform.localplatform import setgid, setuid, get_username, get_home_path, ON_LINUX
from ..localplatform.localplatform import setgid, setuid, get_username, get_home_path
from ..enums import UserType
from .. import helpers, settings, injector # pyright: ignore [reportUnusedImport]
@@ -54,13 +54,10 @@ class SandboxedPlugin:
loop = new_event_loop()
set_event_loop(loop)
# When running Decky manually in a terminal, ctrl-c will trigger this, so we have to handle it properly
if ON_LINUX:
loop.add_signal_handler(SIGINT, lambda: ensure_future(self.shutdown()))
loop.add_signal_handler(SIGTERM, lambda: ensure_future(self.shutdown()))
loop.add_signal_handler(SIGINT, lambda: ensure_future(self.shutdown()))
loop.add_signal_handler(SIGTERM, lambda: ensure_future(self.shutdown()))
if self.passive:
return
setgid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
setuid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
# export a bunch of environment variables to help plugin developers
+2 -3
View File
@@ -47,7 +47,7 @@
}
},
"dependencies": {
"@decky/ui": "^4.7.2",
"@decky/ui": "^4.7.1",
"filesize": "^10.1.2",
"i18next": "^23.11.5",
"i18next-http-backend": "^2.5.2",
@@ -55,7 +55,6 @@
"react-i18next": "^14.1.2",
"react-icons": "^5.2.1",
"react-markdown": "^9.0.1",
"remark-gfm": "^4.0.0",
"compare-versions": "^6.1.1"
"remark-gfm": "^4.0.0"
}
}
+5 -13
View File
@@ -9,11 +9,8 @@ importers:
.:
dependencies:
'@decky/ui':
specifier: ^4.7.2
version: 4.7.2
compare-versions:
specifier: ^6.1.1
version: 6.1.1
specifier: ^4.7.1
version: 4.7.1
filesize:
specifier: ^10.1.2
version: 10.1.2
@@ -218,8 +215,8 @@ packages:
'@decky/api@1.1.1':
resolution: {integrity: sha512-R5fkBRHBt5QIQY7Q0AlbVIhlIZ/nTzwBOoi8Rt4Go2fjFnoMKPInCJl6cPjXzimGwl2pyqKJgY6VnH6ar0XrHQ==}
'@decky/ui@4.7.2':
resolution: {integrity: sha512-jYXVhbyyupXAcCuFqr7G2qjYVjp8hlMGF8zl8ALv67y0YhikAtfhA2rGUjCuaV3kdo9YrpBh8djRUJXdFPg/Eg==}
'@decky/ui@4.7.1':
resolution: {integrity: sha512-yJwBgW+J2cMDfMkmcDFtzsubhUjekFZAtCnP55QEJ/1UKGR7sLNOvDLFYi1h5PI0K4L1XYcAMKHwbYFFTzcDTA==}
'@esbuild/aix-ppc64@0.20.2':
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
@@ -851,9 +848,6 @@ packages:
commondir@1.0.1:
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
compare-versions@6.1.1:
resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -2295,7 +2289,7 @@ snapshots:
'@decky/api@1.1.1': {}
'@decky/ui@4.7.2': {}
'@decky/ui@4.7.1': {}
'@esbuild/aix-ppc64@0.20.2':
optional: true
@@ -2822,8 +2816,6 @@ snapshots:
commondir@1.0.1: {}
compare-versions@6.1.1: {}
concat-map@0.0.1: {}
convert-source-map@2.0.0: {}
@@ -0,0 +1,123 @@
import { DialogButton, Focusable, PanelSection } from '@decky/ui';
import { useEffect, useMemo, useState } from 'react';
import { FaTimes } from 'react-icons/fa';
import { Announcement, getAnnouncements } from '../store';
import { useSetting } from '../utils/hooks/useSetting';
const SEVERITIES = {
High: {
color: '#bb1414',
text: '#fff',
},
Medium: {
color: '#bbbb14',
text: '#fff',
},
Low: {
color: '#1488bb',
text: '#fff',
},
};
const welcomeAnnouncement: Announcement = {
id: 'welcomeAnnouncement',
title: 'Welcome to Decky!',
text: 'We hope you enjoy using Decky! If you have any questions or feedback, please let us know.',
created: Date.now().toString(),
updated: Date.now().toString(),
};
export function AnnouncementsDisplay() {
const [announcements, setAnnouncements] = useState<Announcement[]>([welcomeAnnouncement]);
// showWelcome will display a welcome motd, the welcome motd has an id of "welcome" and once that is saved to hiddenMotdId, it will not show again
const [hiddenAnnouncementIds, setHiddenAnnouncementIds] = useSetting<string[]>('hiddenAnnouncementIds', []);
function addAnnouncements(newAnnouncements: Announcement[]) {
// Removes any duplicates and sorts by created date
setAnnouncements((oldAnnouncements) => {
const newArr = [...oldAnnouncements, ...newAnnouncements];
const setOfIds = new Set(newArr.map((a) => a.id));
return (
(
Array.from(setOfIds)
.map((id) => newArr.find((a) => a.id === id))
// Typescript doesn't type filter(Boolean) correctly, so I have to assert this
.filter(Boolean) as Announcement[]
).sort((a, b) => {
return new Date(b.created).getTime() - new Date(a.created).getTime();
})
);
});
}
async function fetchAnnouncement() {
const announcements = await getAnnouncements();
announcements && addAnnouncements(announcements);
}
useEffect(() => {
void fetchAnnouncement();
}, []);
const currentlyDisplayingAnnouncement: Announcement | null = useMemo(() => {
return announcements.find((announcement) => !hiddenAnnouncementIds.includes(announcement.id)) || null;
}, [announcements, hiddenAnnouncementIds]);
function hideAnnouncement(id: string) {
setHiddenAnnouncementIds([...hiddenAnnouncementIds, id]);
void fetchAnnouncement();
}
if (!currentlyDisplayingAnnouncement) {
return null;
}
// Severity is not implemented in the API currently
const severity = SEVERITIES['Low'];
return (
<PanelSection>
<Focusable
style={{
// Transparency is 20% of the color
backgroundColor: `${severity.color}33`,
color: severity.text,
borderColor: severity.color,
borderWidth: '2px',
borderStyle: 'solid',
padding: '0.7rem',
display: 'flex',
flexDirection: 'column',
position: 'relative',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ fontWeight: 'bold' }}>{currentlyDisplayingAnnouncement.title}</span>
<DialogButton
style={{
width: '1rem',
minWidth: '1rem',
height: '1rem',
padding: '0',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
top: '.75rem',
right: '.75rem',
}}
onClick={() => hideAnnouncement(currentlyDisplayingAnnouncement.id)}
>
<FaTimes
style={{
height: '.75rem',
}}
/>
</DialogButton>
</div>
<span style={{ fontSize: '0.75rem', whiteSpace: 'pre-line' }}>{currentlyDisplayingAnnouncement.text}</span>
</Focusable>
</PanelSection>
);
}
+2
View File
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { FaEyeSlash } from 'react-icons/fa';
import { Plugin } from '../plugin';
import { AnnouncementsDisplay } from './AnnouncementsDisplay';
import { useDeckyState } from './DeckyState';
import NotificationBadge from './NotificationBadge';
import { useQuickAccessVisible } from './QuickAccessVisibleState';
@@ -42,6 +43,7 @@ const PluginView: FC = () => {
paddingTop: '16px',
}}
>
<AnnouncementsDisplay />
<PanelSection>
{pluginList
.filter((p) => p.content)
+18 -8
View File
@@ -5,19 +5,29 @@ interface Window {
}
(async () => {
console.debug('[Decky:Boot] Frontend init');
console.time('[Decky:Boot] Waiting for SteamApp init stage 1 to finish...');
// @ts-expect-error TODO type BFinishedInitStageOne in @decky/ui
while (!window.App?.BFinishedInitStageOne()) {
await new Promise((r) => setTimeout(r, 0)); // Can't use DFL sleep here.
// Wait for main webpack chunks to definitely be loaded
console.time('[Decky:Boot] Waiting for main Webpack chunks...');
while (!window.webpackChunksteamui || window.webpackChunksteamui.length < 5) {
await new Promise((r) => setTimeout(r, 10)); // Can't use DFL sleep here.
}
console.timeEnd('[Decky:Boot] Waiting for main Webpack chunks...');
console.timeEnd('[Decky:Boot] Waiting for SteamApp init stage 1 to finish...');
// Wait for the React root to be mounted
console.time('[Decky:Boot] Waiting for React root mount...');
let root;
while (
// Does React root node exist?
!(root = document.getElementById('root')) ||
// Does it have a child element?
!(root as any)[Object.keys(root).find((k) => k.startsWith('__reactContainer$')) as string].child
) {
await new Promise((r) => setTimeout(r, 10)); // Can't use DFL sleep here.
}
console.timeEnd('[Decky:Boot] Waiting for React root mount...');
if (!window.SP_REACT) {
console.debug('[Decky:Boot] Setting up Webpack & React globals...');
await new Promise((r) => setTimeout(r, 500)); // Can't use DFL sleep here.
// deliberate partial import
const DFLWebpack = await import('@decky/ui/dist/webpack');
window.SP_REACT = DFLWebpack.findModule((m) => m.Component && m.PureComponent && m.useLayoutEffect);
@@ -1,5 +1,3 @@
import { compare } from 'compare-versions';
import { InstallType, Plugin, installPlugin, installPlugins } from './plugin';
import { getSetting, setSetting } from './utils/settings';
@@ -42,6 +40,14 @@ export interface PluginInstallRequest {
installType: InstallType;
}
export interface Announcement {
id: string;
title: string;
text: string;
created: string;
updated: string;
}
// name: version
export type PluginUpdateMapping = Map<string, StorePluginVersion>;
@@ -49,6 +55,47 @@ export async function getStore(): Promise<Store> {
return await getSetting<Store>('store', Store.Default);
}
export async function getAnnouncements(): Promise<Announcement[]> {
let version = await window.DeckyPluginLoader.updateVersion();
let store = await getSetting<Store | null>('store', null);
let customURL = await getSetting<string>(
'announcements-url',
'https://plugins.deckbrew.xyz/v1/announcements/-/current',
);
if (store === null) {
console.log('Could not get store, using Default.');
await setSetting('store', Store.Default);
store = Store.Default;
}
let resolvedURL;
switch (store) {
case Store.Default:
resolvedURL = 'https://plugins.deckbrew.xyz/v1/announcements/-/current';
break;
case Store.Testing:
resolvedURL = 'https://testing.deckbrew.xyz/v1/announcements/-/current';
break;
case Store.Custom:
resolvedURL = customURL;
break;
default:
console.error('Somehow you ended up without a standard URL, using the default URL.');
resolvedURL = 'https://plugins.deckbrew.xyz/v1/announcements/-/current';
break;
}
const res = await fetch(resolvedURL, {
method: 'GET',
headers: {
'X-Decky-Version': version.current,
},
});
if (res.status !== 200) return [];
const json = await res.json();
return json ?? [];
}
export async function getPluginList(
sort_by: SortOptions | null = null,
sort_direction: SortDirections | null = null,
@@ -139,15 +186,7 @@ export async function checkForPluginUpdates(plugins: Plugin[]): Promise<PluginUp
const updateMap = new Map<string, StorePluginVersion>();
for (let plugin of plugins) {
const remotePlugin = serverData?.find((x) => x.name == plugin.name);
//FIXME: Ugly hack since plugin.version might be null during evaluation,
//so this will set the older version possible
const curVer = plugin.version ? plugin.version : '0.0';
if (
remotePlugin &&
remotePlugin.versions?.length > 0 &&
plugin.version != remotePlugin?.versions?.[0]?.name &&
compare(remotePlugin?.versions?.[0]?.name, curVer, '>')
) {
if (remotePlugin && remotePlugin.versions?.length > 0 && plugin.version != remotePlugin?.versions?.[0]?.name) {
updateMap.set(plugin.name, remotePlugin.versions[0]);
}
}