Compare commits

...

23 Commits

Author SHA1 Message Date
Jozen Blue Martinez 435dfa7884 fix(filepicker_ls): use case insensitive matching for file exts (#585) 2024-02-10 11:34:16 -08:00
Party Wumpus 2500b748ce Revert "Call plugin unload function after stopping event loop (#539)" (#584)
This reverts commit 39f4f2870b , because functions (seemingly) don't run after the event loop closes, so the unload function is never actually run.
2024-02-09 20:33:47 +00:00
Party Wumpus fd4ed811be Refactor plugin store and add sorting by downloads and release date (#547)
* untested first commit

* fix types & names

* comment out built in sorting for now

* rerun search when sort changes

* fix ts complaints

* use prettier

* stop switch-case fall through

* move spinner

* use locale instead of hardcoded string

* fix typo

* add sorting by downloads & try using the data field in the dropdown for data

* fix typing error

* fix asc/desc in dropdown

* fix asc/desc again. asc = smaller one go first aaaaa

* I don't think i know what ascending means maybe

* use props instead of children, like a normal component
2024-02-07 17:38:08 +00:00
Party Wumpus 3e4c255c5b Specify catthehacker/ubuntu:act-22.04 as container for act
Fixes an issue where act wouldn't use the correct container and so couldn't find a compatible python version, so it would fail to build.
2024-02-06 19:49:57 +00:00
AAGaming 62e3128d64 fix: bump dfl to fix error on latest steam beta 2024-02-03 00:33:39 -05:00
AAGaming 7f2caa3ea9 fix: use findInReactTree to find correct errorboundary for toaster
fixes toaster error on latest beta
2024-02-03 00:33:00 -05:00
AAGaming 6b4a56c7dc fix the tasks 2024-02-03 00:32:32 -05:00
WerWolvTranslationBot 647f3fe8ed Translations update from Weblate (#580)
* Added translation using Weblate (Japanese)

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Translated using Weblate (Japanese)

Currently translated at 82.7% (115 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Japanese)

Currently translated at 89.2% (124 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/pt_PT/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/pt_BR/

---------

Co-authored-by: Tak-attack <tak.bts@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Fábio Oliveira <fabio.an.oliveira@gmail.com>
2024-01-25 16:03:02 -08:00
Marco Rodolfi 3146ebf85f [Bugfix] Toaster changed name again (#581)
Add another name placeholder for getting the toaster out of the HTML tree. Thanks to @eXhumer for the fix.
2024-01-25 17:21:11 +01:00
dependabot[bot] 9295e4b038 Bump aiohttp from 3.8.5 to 3.9.0 in /backend (#577) 2024-01-23 19:49:13 +00:00
Beebles f9a07da3cc fix: Fix on Chromium 109 beta (#576)
* Add new user agent to do not close tabs list

* fix: bump DFL to fix chromium 109 beta

---------

Co-authored-by: Sims <38142618+suchmememanyskill@users.noreply.github.com>
2024-01-19 18:54:56 -08:00
dependabot[bot] 12a99b8b06 Bump tj-actions/changed-files to 41.0.0 in /.github/workflows (#575)
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 35.6.3 to 41.0.0.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/v35.6.3...v41.0.0)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 16:42:39 -08:00
WerWolvTranslationBot e3d72b6082 Translations update from Weblate (#553)
* Added translation using Weblate (Japanese)

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Translated using Weblate (Japanese)

Currently translated at 82.7% (115 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Japanese)

Currently translated at 89.2% (124 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/pt_PT/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/pt_BR/

---------

Co-authored-by: Tak-attack <tak.bts@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Fábio Oliveira <fabio.an.oliveira@gmail.com>
2023-12-15 18:12:57 -08:00
Jan 39f4f2870b Call plugin unload function after stopping event loop (#539)
This can prevent race conditions where unload is clearing data but main is still working with it
2023-12-15 18:07:54 -08:00
AAGaming 3489fd7d69 fix(developer): add back valve internal on beta
look i was tired when writing yesterday's fix okay
2023-12-13 22:06:22 -05:00
AAGaming e21a5d5890 fix: idiotic formatting error i should have noticed 2023-12-12 22:23:07 -05:00
AAGaming 80a00a0d35 fix: Adjust tabs and toaster hooks to work on react 18, also half-fix Valve Internal 2023-12-12 22:21:25 -05:00
Party Wumpus 91186da979 bump dfl 2023-11-24 15:08:29 -08:00
Jan 7c3ae9b62b replace chmod implementation with os.chmod (#541) 2023-11-11 20:56:32 +00:00
Jan 75ad98a7b2 Check if Linux service is running before trying to start or stop it (#540)
this prevents needless prompts opening up
2023-11-11 20:50:23 +00:00
dependabot[bot] 479a16c655 Bump aiohttp from 3.8.4 to 3.8.5 in /backend (#558) 2023-11-10 21:01:48 +00:00
Party Wumpus 8f26fdec2d Count the number of installs for each plugin (#557) 2023-11-10 17:19:01 +00:00
AAGaming 29d651bed6 fix: get rid of title view jank on latest beta 2023-11-09 15:35:32 -05:00
22 changed files with 513 additions and 134 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- name: Get changed files - name: Get changed files
id: changed-files id: changed-files
uses: tj-actions/changed-files@v35.6.3 uses: tj-actions/changed-files@v41.0.0
with: with:
separator: "," separator: ","
files: | files: |
+2 -2
View File
@@ -38,7 +38,7 @@
"type": "shell", "type": "shell",
"group": "none", "group": "none",
"detail": "Check for local runs, create a plugins folder", "detail": "Check for local runs, create a plugins folder",
"command": "rsync -azp --rsh='ssh -p ${config:deckport} ${config:deckkey}' requirements.txt deck@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader/requirements.txt && ssh deck@${config:deckip} -p ${config:deckport} ${config:deckkey} 'python -m ensurepip && python -m pip install --upgrade pip && python -m pip install --upgrade setuptools && python -m pip install -r ${config:deckdir}/homebrew/dev/pluginloader/requirements.txt'", "command": "rsync -azp --rsh='ssh -p ${config:deckport} ${config:deckkey}' backend/requirements.txt deck@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader/backend/requirements.txt && ssh deck@${config:deckip} -p ${config:deckport} ${config:deckkey} 'python -m ensurepip && python -m pip install --upgrade --break-system-packages pip && python -m pip install --break-system-packages --upgrade setuptools && python -m pip install --break-system-packages -r ${config:deckdir}/homebrew/dev/pluginloader/backend/requirements.txt'",
"problemMatcher": [] "problemMatcher": []
}, },
{ {
@@ -105,7 +105,7 @@
"detail": "Deploy dev PluginLoader to deck", "detail": "Deploy dev PluginLoader to deck",
"type": "shell", "type": "shell",
"group": "none", "group": "none",
"command": "rsync -azp --delete --rsh='ssh -p ${config:deckport} ${config:deckkey}' --exclude='.git/' --exclude='.github/' --exclude='.vscode/' --exclude='frontend/' --exclude='dist/' --exclude='contrib/' --exclude='*.log' --exclude='requirements.txt' --exclude='backend/__pycache__/' --exclude='.gitignore' . deck@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader", "command": "rsync -azp --delete --rsh='ssh -p ${config:deckport} ${config:deckkey}' --exclude='.git/' --exclude='.github/' --exclude='.vscode/' --exclude='frontend/' --exclude='dist/' --exclude='contrib/' --exclude='*.log' --exclude='requirements.txt' --exclude='**/__pycache__/' --exclude='.gitignore' . deck@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader",
"problemMatcher": [] "problemMatcher": []
}, },
// RUN // RUN
+2 -2
View File
@@ -26,10 +26,10 @@ cd ..
if [[ "$type" == "release" ]]; then if [[ "$type" == "release" ]]; then
printf "release!\n" printf "release!\n"
act workflow_dispatch -e act/release.json --artifact-server-path act/artifacts --container-architecture linux/amd64 act workflow_dispatch -e act/release.json --artifact-server-path act/artifacts --container-architecture linux/amd64 --platform ubuntu-22.04=catthehacker/ubuntu:act-22.04
elif [[ "$type" == "prerelease" ]]; then elif [[ "$type" == "prerelease" ]]; then
printf "prerelease!\n" printf "prerelease!\n"
act workflow_dispatch -e act/prerelease.json --artifact-server-path act/artifacts --container-architecture linux/amd64 act workflow_dispatch -e act/prerelease.json --artifact-server-path act/artifacts --container-architecture linux/amd64 --platform ubuntu-22.04=catthehacker/ubuntu:act-22.04
else else
printf "Release type unspecified/badly specified.\n" printf "Release type unspecified/badly specified.\n"
printf "Options: 'release' or 'prerelease'\n" printf "Options: 'release' or 'prerelease'\n"
+4
View File
@@ -218,6 +218,10 @@
"about": "About", "about": "About",
"alph_asce": "Alphabetical (Z to A)", "alph_asce": "Alphabetical (Z to A)",
"alph_desc": "Alphabetical (A to Z)", "alph_desc": "Alphabetical (A to Z)",
"date_asce": "Oldest First",
"date_desc": "Newest First",
"downloads_asce": "Least Downloaded First",
"downloads_desc": "Most Downloaded First",
"title": "Browse" "title": "Browse"
}, },
"store_testing_cta": "Please consider testing new plugins to help the Decky Loader team!", "store_testing_cta": "Please consider testing new plugins to help the Decky Loader team!",
+253
View File
@@ -0,0 +1,253 @@
{
"BranchSelect": {
"update_channel": {
"stable": "安定",
"testing": "テスト",
"label": "アップデートチャンネル",
"prerelease": "プレリリース"
}
},
"DropdownMultiselect": {
"button": {
"back": "戻る"
}
},
"FilePickerIndex": {
"file": {
"select": "ファイルを選択"
},
"files": {
"all_files": "すべてのファイル",
"file_type": "ファイルタイプ",
"show_hidden": "非表示ファイルを表示する"
},
"filter": {
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "サイズ(小さい順)",
"size_desc": "サイズ(大きい順)",
"created_asce": "作成日(古い順)",
"created_desc": "作成日(新しい順)",
"modified_asce": "更新日(古い順)",
"modified_desc": "更新日(新しい順)"
},
"folder": {
"label": "フォルダ",
"select": "このフォルダを使用",
"show_more": "その他のファイルを表示"
}
},
"MultiplePluginsInstallModal": {
"description": {
"install": "インストール {{name}} {{version}}",
"reinstall": "再インストール {{name}} {{version}}",
"update": "アップデート {{name}} {{version}}"
},
"ok_button": {
"idle": "確認",
"loading": "作業中"
},
"title": {
"install_other": "{{count}} 個のプラグインをインストール",
"mixed_other": "{{count}} 個のプラグインを修正",
"update_other": "{{count}} 個のプラグインをアップデート",
"reinstall_other": "{{count}} 個のプラグインを再インストール"
},
"confirm": "以下の変更を加えてもよろしいですか?"
},
"Developer": {
"enabling": "React DevToolsを有効",
"disabling": "React DevToolsを無効",
"5secreload": "5秒以内に再読み込みされます"
},
"PluginInstallModal": {
"install": {
"button_idle": "インストール",
"title": "{{artifact}} をインストール",
"button_processing": "インストール中",
"desc": "{{artifact}} {{version}} をインストールしてもよろしいですか?"
},
"no_hash": "このプラグインにはハッシュがありません。ご自身の責任でインストールしてください。",
"reinstall": {
"button_idle": "再インストール",
"button_processing": "再インストール中",
"desc": "{{artifact}} {{version}} を再インストールしてもよろしいですか?",
"title": "{{artifact}} を再インストール"
},
"update": {
"button_idle": "アップデート",
"title": "{{artifact}} をアップデート",
"desc": "{{artifact}} {{version}} をアップデートしてもよろしいですか?",
"button_processing": "アップデート中"
}
},
"PluginListIndex": {
"hide": "クイックアクセス: 非表示",
"no_plugin": "プラグインがインストールされていません!",
"reinstall": "再インストール",
"reload": "再読み込み",
"uninstall": "アンインストール",
"plugin_actions": "プラグインアクション",
"update_all_other": "{{count}} 個のプラグインをアップデート",
"show": "クイックアクセス: 表示",
"update_to": "{{name}} を更新"
},
"PluginListLabel": {
"hidden": "クイックアクセスメニューから非表示にします"
},
"PluginLoader": {
"error": "エラー",
"plugin_load_error": {
"message": "プラグイン {{name}} の読み込みエラー",
"toast": "{{name}} の読み込みエラー"
},
"plugin_uninstall": {
"button": "アンインストール",
"desc": "{{name}} をアンインストールしてもよろしいですか?",
"title": "{{name}} をアンインストール"
},
"decky_title": "Decky",
"decky_update_available": "{{tag_name}} のアップデートが利用可能です!",
"plugin_update_other": "{{count}} 個のプラグインのアップデートが利用可能です!",
"plugin_error_uninstall": "{{name}} プラグインを読み込む際に上記のような例外が発生しました。 これは通常、SteamUIの最新バージョンに合ったプラグインのアップデートが必要な場合に発生します。Decky設定のプラグインセクションでアップデートがあるかどうかを確認するか、アンインストールをお試しください。"
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "コンソールを開く",
"label": "CEFコンソール",
"desc": "CEFコンソールを開きます。デバッグ目的でのみ使用してください。これらの項目は危険な可能性があるので、プラグイン開発者であるか、開発者のガイドに従う場合のみ使用する必要があります。"
},
"react_devtools": {
"ip_label": "IP",
"label": "React DevTools を有効化",
"desc": "React DevToolsを実行しているコンピューターへの接続を有効にします。この設定を変更すると、Steam が再ロードされます。有効にする前にIPアドレスを設定してください。"
},
"third_party_plugins": {
"button_install": "インストール",
"button_zip": "ブラウズ",
"header": "サードパーティプラグイン",
"label_desc": "URL",
"label_url": "URLからプラグインをインストール",
"label_zip": "ZIPファイルからプラグインをインストール"
},
"valve_internal": {
"desc1": "Valveの内部開発者メニューを有効にします。",
"desc2": "このメニューの機能が分からない場合、このメニューには触れないでください。",
"label": "Valve Internalを有効"
},
"header": "その他"
},
"PluginView": {
"hidden_other": "{{count}} 個のプラグインがこのリストから非表示になります"
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Deckyバージョン",
"header": "情報"
},
"developer_mode": {
"label": "開発者モード"
},
"notifications": {
"header": "通知",
"plugin_updates_label": "プラグインのアップデートが利用可能な場合に通知",
"decky_updates_label": "Deckyのアップデートが利用可能な場合に通知"
},
"beta": {
"header": "ベータ版への参加"
},
"other": {
"header": "その他"
},
"updates": {
"header": "アップデート"
}
},
"SettingsIndex": {
"developer_title": "開発者",
"plugins_title": "プラグイン",
"general_title": "一般"
},
"Store": {
"store_filter": {
"label": "フィルター",
"label_def": "すべて"
},
"store_search": {
"label": "検索"
},
"store_sort": {
"label": "並べ替え",
"label_def": "直近のアップデート(新しい順)"
},
"store_source": {
"desc": "すべてのプラグインのソース コードは、GitHubのSteamDeckHomebrew/decky-plugin-databaseリポジトリで入手できます。",
"label": "ソースコード"
},
"store_tabs": {
"alph_asce": "アルファベット(Z to A)",
"alph_desc": "アルファベット(A to Z)",
"title": "閲覧",
"about": "概要"
},
"store_testing_warning": {
"label": "テストストア チャンネルへようこそ",
"desc": "このストアチャンネルを使用して、最先端のプラグイン バージョンをテストできます。 すべてのユーザーがプラグインを更新できるように、必ずGitHubにフィードバックを残してください。"
},
"store_contrib": {
"desc": "Decky Plugin Storeに貢献したい場合は、GitHubのSteamDeckHomebrew/decky-plugin-templateリポジトリを確認してください。 開発と配布に関する情報は README で入手できます。",
"label": "貢献"
},
"store_testing_cta": "Decky Loaderチームを支援するために、新しいプラグインのテストを検討してください!"
},
"StoreSelect": {
"custom_store": {
"label": "カスタムストア",
"url_label": "URL"
},
"store_channel": {
"custom": "カスタム",
"default": "デフォルト",
"label": "ストアチャンネル",
"testing": "テスト"
}
},
"TitleView": {
"decky_store_desc": "Deckyストアを開く",
"settings_desc": "Decky設定を開く"
},
"Updater": {
"decky_updates": "Deckyアップデート",
"no_patch_notes_desc": "このバージョンのパッチノートはありません",
"patch_notes_desc": "パッチノート",
"updates": {
"check_button": "アップデートを確認",
"checking": "確認中",
"cur_version": "現在のバージョン: {{ver}}",
"install_button": "アップデートをインストール",
"label": "アップデート",
"lat_version": "アップデート: {{ver}} を実行中",
"reloading": "再読み込み中",
"updating": "アップデート中"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "指定されたパスは無効です。 内容をご確認の上、正しく入力し直してください。",
"unknown": "不明なエラーが発生しました。 エラー内容は次のとおりです: {{raw_error}}",
"perm_denied": "選択したパスへのアクセス権がありません。選択したフォルダ/ファイルのアクセス権がユーザー(Steam Deckのdeckユーザー)に合わせて正しく設定されていることを確認してください。"
}
},
"PluginCard": {
"plugin_version_label": "プラグインバージョン",
"plugin_no_desc": "説明はありません。",
"plugin_full_access": "このプラグインはSteam Deckの全てのアクセス権を持ちます。",
"plugin_install": "インストール"
},
"RemoteDebugging": {
"remote_cef": {
"label": "リモート CEF デバッグを許可する",
"desc": "ネットワーク上のすべてのユーザーにCEFデバッガへの非認証アクセスを許可します"
}
}
}
+9 -1
View File
@@ -210,7 +210,11 @@
"title": "Navegar", "title": "Navegar",
"alph_asce": "Alfabética (Z - A)" "alph_asce": "Alfabética (Z - A)"
}, },
"store_testing_cta": "Por favor, considere testar os novos plugins para ajudar o time do Decky Loader!" "store_testing_cta": "Por favor, considere testar os novos plugins para ajudar o time do Decky Loader!",
"store_testing_warning": {
"desc": "Você pode usar este canal da loja para testar versões avançadas do plugin. Certifique-se de deixar feedback no GitHub para que o plugin possa ser atualizado para todos os usuários.",
"label": "Bem-vindo ao Canal de Testes da Loja"
}
}, },
"StoreSelect": { "StoreSelect": {
"custom_store": { "custom_store": {
@@ -255,5 +259,9 @@
"unknown": "Ocorreu um erro desconhecido. O erro completo é: {{raw_error}}", "unknown": "Ocorreu um erro desconhecido. O erro completo é: {{raw_error}}",
"perm_denied": "Você não tem acesso à este diretório. Por favor, verifiquei se seu usuário (deck no Steam Deck) tem as permissões necessárias para acessar este arquivo/pasta." "perm_denied": "Você não tem acesso à este diretório. Por favor, verifiquei se seu usuário (deck no Steam Deck) tem as permissões necessárias para acessar este arquivo/pasta."
} }
},
"TitleView": {
"decky_store_desc": "Abrir Loja Decky",
"settings_desc": "Abrir Definições Decky"
} }
} }
+47 -2
View File
@@ -1,7 +1,27 @@
{ {
"FilePickerIndex": { "FilePickerIndex": {
"folder": { "folder": {
"select": "Usar esta pasta" "select": "Usar esta pasta",
"label": "Pasta",
"show_more": "Mostrar mais ficheiros"
},
"file": {
"select": "Selecionar este ficheiro"
},
"filter": {
"size_desc": "Tamanho (maior)",
"created_asce": "Criado (mais antigo)",
"created_desc": "Criado (mais recente)",
"modified_asce": "Modificado (mais antigo)",
"modified_desc": "Modificado (mais recente)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Tamanho (mais pequeno)"
},
"files": {
"file_type": "Tipo de ficheiro",
"show_hidden": "Mostrar ficheiros ocultos",
"all_files": "Todos os ficheiros"
} }
}, },
"PluginView": { "PluginView": {
@@ -157,6 +177,11 @@
}, },
"updates": { "updates": {
"header": "Actualizações" "header": "Actualizações"
},
"notifications": {
"decky_updates_label": "Atualização Decky disponível",
"header": "Notificações",
"plugin_updates_label": "Atualizações de plugins disponíveis"
} }
}, },
"SettingsIndex": { "SettingsIndex": {
@@ -190,7 +215,11 @@
"alph_desc": "Alfabeticamente (A-Z)", "alph_desc": "Alfabeticamente (A-Z)",
"title": "Navegar" "title": "Navegar"
}, },
"store_testing_cta": "Testa novos plugins e ajuda a equipa do Decky Loader!" "store_testing_cta": "Testa novos plugins e ajuda a equipa do Decky Loader!",
"store_testing_warning": {
"desc": "Pode usar esta versão da loja para testar versões experimentais de plugins. Certifique-se de deixar feedback no GitHub para que o plugin possa ser atualizado para todos os utilizadores.",
"label": "Bem-vindo ao Canal de Testes da Loja"
}
}, },
"StoreSelect": { "StoreSelect": {
"custom_store": { "custom_store": {
@@ -218,5 +247,21 @@
"reloading": "Recarregar", "reloading": "Recarregar",
"install_button": "Instalar actualização" "install_button": "Instalar actualização"
} }
},
"FilePickerError": {
"errors": {
"perm_denied": "Não tem acesso ao diretório especificado. Por favor, verifique se o seu utilizador (deck na Steam Deck) possui as permissões correspondentes para aceder à pasta/ficheiro especificado.",
"unknown": "Ocorreu um erro desconhecido. O erro é: {{raw_error}}",
"file_not_found": "O caminho especificado não é válido. Por favor, verifique e insira-o corretamente."
}
},
"TitleView": {
"decky_store_desc": "Abrir a Loja Decky",
"settings_desc": "Abrir as Definições Decky"
},
"DropdownMultiselect": {
"button": {
"back": "Voltar"
}
} }
} }
+1 -1
View File
@@ -1,4 +1,4 @@
aiohttp==3.8.4 aiohttp==3.9.0
aiohttp-jinja2==1.5.1 aiohttp-jinja2==1.5.1
aiohttp_cors==0.7.0 aiohttp_cors==0.7.0
watchdog==2.1.7 watchdog==2.1.7
+12
View File
@@ -186,6 +186,18 @@ class PluginBrowser:
else: else:
logger.fatal(f"Could not fetch from URL. {await res.text()}") logger.fatal(f"Could not fetch from URL. {await res.text()}")
storeUrl = ""
match self.settings.getSetting("store", 0):
case 0: storeUrl = "https://plugins.deckbrew.xyz/plugins" # default
case 1: storeUrl = "https://testing.deckbrew.xyz/plugins" # testing
case 2: storeUrl = self.settings.getSetting("store-url", "https://plugins.deckbrew.xyz/plugins") # custom
case _: storeUrl = "https://plugins.deckbrew.xyz/plugins"
logger.info(f"Incrementing installs for {name} from URL {storeUrl} (version {version})")
async with ClientSession() as client:
res = await client.post(storeUrl+f"/{name}/versions/{version}/increment?isUpdate={isInstalled}", ssl=get_ssl_context())
if res.status != 200:
logger.error(f"Server did not accept install count increment request. code: {res.status}")
# Check to make sure we got the file # Check to make sure we got the file
if res_zip is None: if res_zip is None:
logger.fatal(f"Could not fetch {artifact}") logger.fatal(f"Could not fetch {artifact}")
+2 -2
View File
@@ -412,7 +412,7 @@ async def get_tab_lambda(test: Callable[[Tab], bool]) -> Tab:
SHARED_CTX_NAMES = ["SharedJSContext", "Steam Shared Context presented by Valve™", "Steam", "SP"] SHARED_CTX_NAMES = ["SharedJSContext", "Steam Shared Context presented by Valve™", "Steam", "SP"]
CLOSEABLE_URLS = ["about:blank", "data:text/html,%3Cbody%3E%3C%2Fbody%3E"] # Closing anything other than these *really* likes to crash Steam CLOSEABLE_URLS = ["about:blank", "data:text/html,%3Cbody%3E%3C%2Fbody%3E"] # Closing anything other than these *really* likes to crash Steam
DO_NOT_CLOSE_URL = "Valve Steam Gamepad/default" # Steam Big Picture Mode tab DO_NOT_CLOSE_URLS = ["Valve Steam Gamepad/default", "Valve%20Steam%20Gamepad/default"] # Steam Big Picture Mode tab
def tab_is_gamepadui(t: Tab) -> bool: def tab_is_gamepadui(t: Tab) -> bool:
return "https://steamloopback.host/routes/" in t.url and t.title in SHARED_CTX_NAMES return "https://steamloopback.host/routes/" in t.url and t.title in SHARED_CTX_NAMES
@@ -432,7 +432,7 @@ async def inject_to_tab(tab_name: str, js: str, run_async: bool = False):
async def close_old_tabs(): async def close_old_tabs():
tabs = await get_tabs() tabs = await get_tabs()
for t in tabs: for t in tabs:
if not t.title or (t.title not in SHARED_CTX_NAMES and any(url in t.url for url in CLOSEABLE_URLS) and DO_NOT_CLOSE_URL not in t.url): if not t.title or (t.title not in SHARED_CTX_NAMES and any(url in t.url for url in CLOSEABLE_URLS) and not any(url in t.url for url in DO_NOT_CLOSE_URLS)):
logger.debug("Closing tab: " + getattr(t, "title", "Untitled")) logger.debug("Closing tab: " + getattr(t, "title", "Untitled"))
await t.close() await t.close()
await sleep(0.5) await sleep(0.5)
+24 -2
View File
@@ -58,8 +58,22 @@ def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool =
def chmod(path : str, permissions : int, recursive : bool = True) -> bool: def chmod(path : str, permissions : int, recursive : bool = True) -> bool:
if _get_effective_user_id() != 0: if _get_effective_user_id() != 0:
return True return True
result = call(["chmod", "-R", str(permissions), path] if recursive else ["chmod", str(permissions), path])
return result == 0 try:
octal_permissions = int(str(permissions), 8)
if recursive:
for root, dirs, files in os.walk(path):
for d in dirs:
os.chmod(os.path.join(root, d), octal_permissions)
for d in files:
os.chmod(os.path.join(root, d), octal_permissions)
os.chmod(path, octal_permissions)
except:
return False
return True
def folder_owner(path : str) -> UserType|None: def folder_owner(path : str) -> UserType|None:
user_owner = _get_user_owner(path) user_owner = _get_user_owner(path)
@@ -125,11 +139,19 @@ async def service_restart(service_name : str) -> bool:
return res.returncode == 0 return res.returncode == 0
async def service_stop(service_name : str) -> bool: async def service_stop(service_name : str) -> bool:
if not await service_active(service_name):
# Service isn't running. pretend we stopped it
return True
cmd = ["systemctl", "stop", service_name] cmd = ["systemctl", "stop", service_name]
res = run(cmd, stdout=PIPE, stderr=STDOUT) res = run(cmd, stdout=PIPE, stderr=STDOUT)
return res.returncode == 0 return res.returncode == 0
async def service_start(service_name : str) -> bool: async def service_start(service_name : str) -> bool:
if await service_active(service_name):
# Service is running. pretend we started it
return True
cmd = ["systemctl", "start", service_name] cmd = ["systemctl", "start", service_name]
res = run(cmd, stdout=PIPE, stderr=STDOUT) res = run(cmd, stdout=PIPE, stderr=STDOUT)
return res.returncode == 0 return res.returncode == 0
+1 -1
View File
@@ -238,7 +238,7 @@ class Utilities:
elif include_files: elif include_files:
# Handle requested extensions if present # Handle requested extensions if present
if len(include_ext) == 0 or 'all_files' in include_ext \ if len(include_ext) == 0 or 'all_files' in include_ext \
or splitext(file.name)[1].lstrip('.') in include_ext: or splitext(file.name)[1].lstrip('.').upper() in (ext.upper() for ext in include_ext):
if (is_hidden and include_hidden) or not is_hidden: if (is_hidden and include_hidden) or not is_hidden:
files.append({"file": file, "filest": filest, "is_dir": False}) files.append({"file": file, "filest": filest, "is_dir": False})
# Filter logic # Filter logic
+1 -1
View File
@@ -44,7 +44,7 @@
} }
}, },
"dependencies": { "dependencies": {
"decky-frontend-lib": "3.21.1", "decky-frontend-lib": "3.24.5",
"filesize": "^10.0.7", "filesize": "^10.0.7",
"i18next": "^23.2.1", "i18next": "^23.2.1",
"i18next-http-backend": "^2.2.1", "i18next-http-backend": "^2.2.1",
+9 -7
View File
@@ -6,8 +6,8 @@ settings:
dependencies: dependencies:
decky-frontend-lib: decky-frontend-lib:
specifier: 3.21.1 specifier: 3.24.5
version: 3.21.1 version: 3.24.5
filesize: filesize:
specifier: ^10.0.7 specifier: ^10.0.7
version: 10.0.7 version: 10.0.7
@@ -1482,8 +1482,8 @@ packages:
dependencies: dependencies:
ms: 2.1.2 ms: 2.1.2
/decky-frontend-lib@3.21.1: /decky-frontend-lib@3.24.5:
resolution: {integrity: sha512-30605ET9qqZ6St6I9WmMmLGgSrTIdMwo7xy85+lRaF1miUd2icOGEJjwnbVcZDdkal+1fJ3tNEDXlchVfG4TrA==} resolution: {integrity: sha512-eYlbKDOOcIBPI0b76Rqvlryq2ym/QNiry4xf2pFrXmBa1f95dflqbQAb2gTq9uHEa5gFmeV4lUcMPGJ3M14Xqw==}
dev: false dev: false
/decode-named-character-reference@1.0.2: /decode-named-character-reference@1.0.2:
@@ -1852,8 +1852,8 @@ packages:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true dev: true
/fsevents@2.3.2: /fsevents@2.3.3:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin] os: [darwin]
requiresBuild: true requiresBuild: true
@@ -3200,6 +3200,7 @@ packages:
prop-types: 15.8.1 prop-types: 15.8.1
react: 16.14.0 react: 16.14.0
scheduler: 0.19.1 scheduler: 0.19.1
bundledDependencies: false
/react-file-icon@1.3.0(react-dom@16.14.0)(react@16.14.0): /react-file-icon@1.3.0(react-dom@16.14.0)(react@16.14.0):
resolution: {integrity: sha512-wxl/WwSX5twQKVXloPHbS71iZQUKO84KgZ44Kh7vYZGu1qH2kagx+RSTNfk/+IHtXfjPWPNIHPGi2Y8S94N1CQ==} resolution: {integrity: sha512-wxl/WwSX5twQKVXloPHbS71iZQUKO84KgZ44Kh7vYZGu1qH2kagx+RSTNfk/+IHtXfjPWPNIHPGi2Y8S94N1CQ==}
@@ -3283,6 +3284,7 @@ packages:
loose-envify: 1.4.0 loose-envify: 1.4.0
object-assign: 4.1.1 object-assign: 4.1.1
prop-types: 15.8.1 prop-types: 15.8.1
bundledDependencies: false
/readable-stream@2.3.8: /readable-stream@2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
@@ -3477,7 +3479,7 @@ packages:
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
hasBin: true hasBin: true
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.3
dev: true dev: true
/rsvp@3.2.1: /rsvp@3.2.1:
+7 -14
View File
@@ -1,12 +1,4 @@
import { import { ButtonItem, Focusable, PanelSection, PanelSectionRow } from 'decky-frontend-lib';
ButtonItem,
Focusable,
PanelSection,
PanelSectionRow,
joinClassNames,
scrollClasses,
staticClasses,
} from 'decky-frontend-lib';
import { VFC, useEffect, useState } from 'react'; import { VFC, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaEyeSlash } from 'react-icons/fa'; import { FaEyeSlash } from 'react-icons/fa';
@@ -36,10 +28,7 @@ const PluginView: VFC = () => {
return ( return (
<Focusable onCancelButton={closeActivePlugin}> <Focusable onCancelButton={closeActivePlugin}>
<TitleView /> <TitleView />
<div <div style={{ height: '100%', paddingTop: '16px' }}>
className={joinClassNames(staticClasses.TabGroupPanel, scrollClasses.ScrollPanel, scrollClasses.ScrollY)}
style={{ height: '100%' }}
>
{(visible || activePlugin.alwaysRender) && activePlugin.content} {(visible || activePlugin.alwaysRender) && activePlugin.content}
</div> </div>
</Focusable> </Focusable>
@@ -48,7 +37,11 @@ const PluginView: VFC = () => {
return ( return (
<> <>
<TitleView /> <TitleView />
<div className={joinClassNames(staticClasses.TabGroupPanel, scrollClasses.ScrollPanel, scrollClasses.ScrollY)}> <div
style={{
paddingTop: '16px',
}}
>
<PanelSection> <PanelSection>
{pluginList {pluginList
.filter((p) => p.content) .filter((p) => p.content)
+2
View File
@@ -10,6 +10,8 @@ const titleStyles: CSSProperties = {
display: 'flex', display: 'flex',
paddingTop: '3px', paddingTop: '3px',
paddingRight: '16px', paddingRight: '16px',
position: 'sticky',
top: '0px',
}; };
const TitleView: VFC = () => { const TitleView: VFC = () => {
+72 -65
View File
@@ -8,20 +8,19 @@ import {
TextField, TextField,
findModule, findModule,
} from 'decky-frontend-lib'; } from 'decky-frontend-lib';
import { FC, useEffect, useMemo, useState } from 'react'; import { Dispatch, FC, SetStateAction, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import logo from '../../../assets/plugin_store.png'; import logo from '../../../assets/plugin_store.png';
import Logger from '../../logger'; import Logger from '../../logger';
import { Store, StorePlugin, getPluginList, getStore } from '../../store'; import { SortDirections, SortOptions, Store, StorePlugin, getPluginList, getStore } from '../../store';
import PluginCard from './PluginCard'; import PluginCard from './PluginCard';
const logger = new Logger('Store'); const logger = new Logger('Store');
const StorePage: FC<{}> = () => { const StorePage: FC<{}> = () => {
const [currentTabRoute, setCurrentTabRoute] = useState<string>('browse'); const [currentTabRoute, setCurrentTabRoute] = useState<string>('browse');
const [data, setData] = useState<StorePlugin[] | null>(null); const [pluginCount, setPluginCount] = useState<number | null>(null);
const [isTesting, setIsTesting] = useState<boolean>(false);
const { TabCount } = findModule((m) => { const { TabCount } = findModule((m) => {
if (m?.TabCount && m?.TabTitle) return true; if (m?.TabCount && m?.TabTitle) return true;
return false; return false;
@@ -29,17 +28,6 @@ const StorePage: FC<{}> = () => {
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => {
(async () => {
const res = await getPluginList();
logger.log('got data!', res);
setData(res);
const storeRes = await getStore();
logger.log(`store is ${storeRes}, isTesting is ${storeRes === Store.Testing}`);
setIsTesting(storeRes === Store.Testing);
})();
}, []);
return ( return (
<> <>
<div <div
@@ -49,52 +37,71 @@ const StorePage: FC<{}> = () => {
background: '#0005', background: '#0005',
}} }}
> >
{!data ? ( <Tabs
<div style={{ height: '100%' }}> activeTab={currentTabRoute}
<SteamSpinner /> onShowTab={(tabId: string) => {
</div> setCurrentTabRoute(tabId);
) : ( }}
<Tabs tabs={[
activeTab={currentTabRoute} {
onShowTab={(tabId: string) => { title: t('Store.store_tabs.title'),
setCurrentTabRoute(tabId); content: <BrowseTab setPluginCount={setPluginCount} />,
}} id: 'browse',
tabs={[ renderTabAddon: () => <span className={TabCount}>{pluginCount}</span>,
{ },
title: t('Store.store_tabs.title'), {
content: <BrowseTab children={{ data: data, isTesting: isTesting }} />, title: t('Store.store_tabs.about'),
id: 'browse', content: <AboutTab />,
renderTabAddon: () => <span className={TabCount}>{data.length}</span>, id: 'about',
}, },
{ ]}
title: t('Store.store_tabs.about'), />
content: <AboutTab />,
id: 'about',
},
]}
/>
)}
</div> </div>
</> </>
); );
}; };
const BrowseTab: FC<{ children: { data: StorePlugin[]; isTesting: boolean } }> = (data) => { const BrowseTab: FC<{ setPluginCount: Dispatch<SetStateAction<number | null>> }> = ({ setPluginCount }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const sortOptions = useMemo( const dropdownSortOptions = useMemo(
(): DropdownOption[] => [ (): DropdownOption[] => [
{ data: 1, label: t('Store.store_tabs.alph_desc') }, // ascending and descending order are the wrong way around for the alphabetical sort
{ data: 2, label: t('Store.store_tabs.alph_asce') }, // this is because it was initially done incorrectly for i18n and 'fixing' it would
// make all the translations incorrect
{ data: [SortOptions.name, SortDirections.ascending], label: t('Store.store_tabs.alph_desc') },
{ data: [SortOptions.name, SortDirections.descending], label: t('Store.store_tabs.alph_asce') },
{ data: [SortOptions.date, SortDirections.ascending], label: t('Store.store_tabs.date_asce') },
{ data: [SortOptions.date, SortDirections.descending], label: t('Store.store_tabs.date_desc') },
{ data: [SortOptions.downloads, SortDirections.descending], label: t('Store.store_tabs.downloads_desc') },
{ data: [SortOptions.downloads, SortDirections.ascending], label: t('Store.store_tabs.downloads_asce') },
], ],
[], [],
); );
// const filterOptions = useMemo((): DropdownOption[] => [{ data: 1, label: 'All' }], []); // const filterOptions = useMemo((): DropdownOption[] => [{ data: 1, label: 'All' }], []);
const [selectedSort, setSort] = useState<[SortOptions, SortDirections]>(dropdownSortOptions[0].data);
const [selectedSort, setSort] = useState<number>(sortOptions[0].data);
// const [selectedFilter, setFilter] = useState<number>(filterOptions[0].data); // const [selectedFilter, setFilter] = useState<number>(filterOptions[0].data);
const [searchFieldValue, setSearchValue] = useState<string>(''); const [searchFieldValue, setSearchValue] = useState<string>('');
const [pluginList, setPluginList] = useState<StorePlugin[] | null>(null);
const [isTesting, setIsTesting] = useState<boolean>(false);
useEffect(() => {
(async () => {
const res = await getPluginList(selectedSort[0], selectedSort[1]);
logger.log('got data!', res);
setPluginList(res);
setPluginCount(res.length);
})();
}, [selectedSort]);
useEffect(() => {
(async () => {
const storeRes = await getStore();
logger.log(`store is ${storeRes}, isTesting is ${storeRes === Store.Testing}`);
setIsTesting(storeRes === Store.Testing);
})();
}, []);
return ( return (
<> <>
@@ -117,7 +124,7 @@ const BrowseTab: FC<{ children: { data: StorePlugin[]; isTesting: boolean } }> =
<span className="DialogLabel">{t("Store.store_sort.label")}</span> <span className="DialogLabel">{t("Store.store_sort.label")}</span>
<Dropdown <Dropdown
menuLabel={t("Store.store_sort.label") as string} menuLabel={t("Store.store_sort.label") as string}
rgOptions={sortOptions} rgOptions={dropdownSortOptions}
strDefaultLabel={t("Store.store_sort.label_def") as string} strDefaultLabel={t("Store.store_sort.label_def") as string}
selectedOption={selectedSort} selectedOption={selectedSort}
onChange={(e) => setSort(e.data)} onChange={(e) => setSort(e.data)}
@@ -163,7 +170,7 @@ const BrowseTab: FC<{ children: { data: StorePlugin[]; isTesting: boolean } }> =
<span className="DialogLabel">{t('Store.store_sort.label')}</span> <span className="DialogLabel">{t('Store.store_sort.label')}</span>
<Dropdown <Dropdown
menuLabel={t('Store.store_sort.label') as string} menuLabel={t('Store.store_sort.label') as string}
rgOptions={sortOptions} rgOptions={dropdownSortOptions}
strDefaultLabel={t('Store.store_sort.label_def') as string} strDefaultLabel={t('Store.store_sort.label_def') as string}
selectedOption={selectedSort} selectedOption={selectedSort}
onChange={(e) => setSort(e.data)} onChange={(e) => setSort(e.data)}
@@ -182,7 +189,7 @@ const BrowseTab: FC<{ children: { data: StorePlugin[]; isTesting: boolean } }> =
</div> </div>
</Focusable> </Focusable>
</div> </div>
{data.children.isTesting && ( {isTesting && (
<div <div
style={{ style={{
alignItems: 'center', alignItems: 'center',
@@ -213,22 +220,22 @@ const BrowseTab: FC<{ children: { data: StorePlugin[]; isTesting: boolean } }> =
</div> </div>
)} )}
<div> <div>
{data.children.data {!pluginList ? (
.filter((plugin: StorePlugin) => { <div style={{ height: '100%' }}>
return ( <SteamSpinner />
plugin.name.toLowerCase().includes(searchFieldValue.toLowerCase()) || </div>
plugin.description.toLowerCase().includes(searchFieldValue.toLowerCase()) || ) : (
plugin.author.toLowerCase().includes(searchFieldValue.toLowerCase()) || pluginList
plugin.tags.some((tag: string) => tag.toLowerCase().includes(searchFieldValue.toLowerCase())) .filter((plugin: StorePlugin) => {
); return (
}) plugin.name.toLowerCase().includes(searchFieldValue.toLowerCase()) ||
.sort((a, b) => { plugin.description.toLowerCase().includes(searchFieldValue.toLowerCase()) ||
if (selectedSort % 2 === 1) return a.name.localeCompare(b.name); plugin.author.toLowerCase().includes(searchFieldValue.toLowerCase()) ||
else return b.name.localeCompare(a.name); plugin.tags.some((tag: string) => tag.toLowerCase().includes(searchFieldValue.toLowerCase()))
}) );
.map((plugin: StorePlugin) => ( })
<PluginCard plugin={plugin} /> .map((plugin: StorePlugin) => <PluginCard plugin={plugin} />)
))} )}
</div> </div>
</> </>
); );
+17 -19
View File
@@ -1,4 +1,4 @@
import { findModuleChild, sleep } from 'decky-frontend-lib'; import { sleep } from 'decky-frontend-lib';
import { FaReact } from 'react-icons/fa'; import { FaReact } from 'react-icons/fa';
import Logger from './logger'; import Logger from './logger';
@@ -9,32 +9,30 @@ const logger = new Logger('DeveloperMode');
let removeSettingsObserver: () => void = () => {}; let removeSettingsObserver: () => void = () => {};
export async function setShowValveInternal(show: boolean) { declare global {
let settingsMod: any; interface Window {
while (!settingsMod) { settingsStore: any;
settingsMod = findModuleChild((m) => {
if (typeof m !== 'object') return undefined;
for (let prop in m) {
if (typeof m[prop]?.settings?.bIsValveEmail !== 'undefined') return m[prop];
}
});
if (!settingsMod) {
logger.debug('[ValveInternal] waiting for settingsMod');
await sleep(1000);
}
} }
}
export async function setShowValveInternal(show: boolean) {
if (show) { if (show) {
removeSettingsObserver = settingsMod[ const mobx =
Object.getOwnPropertySymbols(settingsMod).find((x) => x.toString() == 'Symbol(mobx administration)') as any window.settingsStore[
].observe((e: any) => { Object.getOwnPropertySymbols(window.settingsStore).find(
(x) => x.toString() == 'Symbol(mobx administration)',
) as any
];
removeSettingsObserver = (mobx.observe_ || mobx.observe).call(mobx, (e: any) => {
e.newValue.bIsValveEmail = true; e.newValue.bIsValveEmail = true;
}); });
settingsMod.m_Settings.bIsValveEmail = true;
window.settingsStore.m_Settings.bIsValveEmail = true;
logger.log('Enabled Valve Internal menu'); logger.log('Enabled Valve Internal menu');
} else { } else {
removeSettingsObserver(); removeSettingsObserver();
settingsMod.m_Settings.bIsValveEmail = false; window.settingsStore.m_Settings.bIsValveEmail = false;
logger.log('Disabled Valve Internal menu'); logger.log('Disabled Valve Internal menu');
} }
} }
+22 -2
View File
@@ -7,6 +7,17 @@ export enum Store {
Custom, Custom,
} }
export enum SortOptions {
name = 'name',
date = 'date',
downloads = 'downloads',
}
export enum SortDirections {
ascending = 'asc',
descending = 'desc',
}
export interface StorePluginVersion { export interface StorePluginVersion {
name: string; name: string;
hash: string; hash: string;
@@ -36,11 +47,20 @@ export async function getStore(): Promise<Store> {
return await getSetting<Store>('store', Store.Default); return await getSetting<Store>('store', Store.Default);
} }
export async function getPluginList(): Promise<StorePlugin[]> { export async function getPluginList(
sort_by: SortOptions | null = null,
sort_direction: SortDirections | null = null,
): Promise<StorePlugin[]> {
let version = await window.DeckyPluginLoader.updateVersion(); let version = await window.DeckyPluginLoader.updateVersion();
let store = await getSetting<Store | null>('store', null); let store = await getSetting<Store | null>('store', null);
let customURL = await getSetting<string>('store-url', 'https://plugins.deckbrew.xyz/plugins'); let customURL = await getSetting<string>('store-url', 'https://plugins.deckbrew.xyz/plugins');
let query: URLSearchParams | string = new URLSearchParams();
sort_by && query.set('sort_by', sort_by);
sort_direction && query.set('sort_direction', sort_direction);
query = '?' + String(query);
let storeURL; let storeURL;
if (store === null) { if (store === null) {
console.log('Could not get store, using Default.'); console.log('Could not get store, using Default.');
@@ -62,7 +82,7 @@ export async function getPluginList(): Promise<StorePlugin[]> {
storeURL = 'https://plugins.deckbrew.xyz/plugins'; storeURL = 'https://plugins.deckbrew.xyz/plugins';
break; break;
} }
return fetch(storeURL, { return fetch(storeURL + query, {
method: 'GET', method: 'GET',
headers: { headers: {
'X-Decky-Version': version.current, 'X-Decky-Version': version.current,
+2 -2
View File
@@ -1,5 +1,5 @@
// TabsHook for versions before the Desktop merge // TabsHook for versions before the Desktop merge
import { Patch, afterPatch, sleep } from 'decky-frontend-lib'; import { Patch, afterPatch, getReactRoot, sleep } from 'decky-frontend-lib';
import { memo } from 'react'; import { memo } from 'react';
import NewTabsHook from './tabs-hook'; import NewTabsHook from './tabs-hook';
@@ -35,7 +35,7 @@ class TabsHook extends NewTabsHook {
init() { init() {
const self = this; const self = this;
const tree = (document.getElementById('root') as any)._reactRootContainer._internalRoot.current; const tree = getReactRoot(document.getElementById('root') as any);
let scrollRoot: any; let scrollRoot: any;
async function findScrollRoot(currentNode: any, iters: number): Promise<any> { async function findScrollRoot(currentNode: any, iters: number): Promise<any> {
if (iters >= 30) { if (iters >= 30) {
+4 -4
View File
@@ -1,5 +1,5 @@
// TabsHook for versions after the Desktop merge // TabsHook for versions after the Desktop merge
import { Patch, QuickAccessTab, afterPatch, findInReactTree, sleep } from 'decky-frontend-lib'; import { Patch, QuickAccessTab, afterPatch, findInReactTree, getReactRoot, sleep } from 'decky-frontend-lib';
import { QuickAccessVisibleStateProvider } from './components/QuickAccessVisibleState'; import { QuickAccessVisibleStateProvider } from './components/QuickAccessVisibleState';
import Logger from './logger'; import Logger from './logger';
@@ -32,11 +32,11 @@ class TabsHook extends Logger {
} }
init() { init() {
const tree = (document.getElementById('root') as any)._reactRootContainer._internalRoot.current; const tree = getReactRoot(document.getElementById('root') as any);
let qAMRoot: any; let qAMRoot: any;
const findQAMRoot = (currentNode: any, iters: number): any => { const findQAMRoot = (currentNode: any, iters: number): any => {
if (iters >= 65) { if (iters >= 80) {
// currently 45 // currently 67
return null; return null;
} }
if ( if (
+19 -6
View File
@@ -1,4 +1,13 @@
import { Module, Patch, ToastData, afterPatch, findInReactTree, findModuleChild, sleep } from 'decky-frontend-lib'; import {
Module,
Patch,
ToastData,
afterPatch,
findInReactTree,
findModuleChild,
getReactRoot,
sleep,
} from 'decky-frontend-lib';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import Toast from './components/Toast'; import Toast from './components/Toast';
@@ -38,16 +47,17 @@ class Toaster extends Logger {
// </DeckyToasterStateContextProvider> // </DeckyToasterStateContextProvider>
// )); // ));
let instance: any; let instance: any;
const tree = (document.getElementById('root') as any)._reactRootContainer._internalRoot.current; const tree = getReactRoot(document.getElementById('root') as any);
const findToasterRoot = (currentNode: any, iters: number): any => { const findToasterRoot = (currentNode: any, iters: number): any => {
if (iters >= 65) { if (iters >= 80) {
// currently 65 // currently 66
return null; return null;
} }
if ( if (
currentNode?.memoizedProps?.className?.startsWith?.('gamepadtoasts_GamepadToastPlaceholder') || currentNode?.memoizedProps?.className?.startsWith?.('gamepadtoasts_GamepadToastPlaceholder') ||
currentNode?.memoizedProps?.className?.startsWith?.('toastmanager_ToastPlaceholder') || currentNode?.memoizedProps?.className?.startsWith?.('toastmanager_ToastPlaceholder') ||
currentNode?.memoizedProps?.className?.startsWith?.('toastmanager_ToastPopup') currentNode?.memoizedProps?.className?.startsWith?.('toastmanager_ToastPopup') ||
currentNode?.memoizedProps?.className?.startsWith?.('gamepadtoasts_GamepadToastPopup')
) { ) {
this.log(`Toaster root was found in ${iters} recursion cycles`); this.log(`Toaster root was found in ${iters} recursion cycles`);
return currentNode; return currentNode;
@@ -71,7 +81,10 @@ class Toaster extends Logger {
instance = findToasterRoot(tree, 0); instance = findToasterRoot(tree, 0);
} }
this.node = instance.return; this.node = instance.return;
this.rNode = this.node.return; this.rNode = findInReactTree(
this.node.return.return,
(node) => node?.stateNode && node.type?.InstallErrorReportingStore,
);
let toast: any; let toast: any;
let renderedToast: ReactNode = null; let renderedToast: ReactNode = null;
let innerPatched: any; let innerPatched: any;