refactor: 💥 v2.0.0 Beta.

BREAKING CHANGE: ⚠ Please avoid using v1 and v2 simultaneously.

This version is stable enough to be released but is still under heavy development,
**You may encounter bugs, if you encounter any, you can uninstall this version and go back to v1.**

For users:
- Users can now share presets and vote for them.
- New installer/uninstaller with customizable installation path
- #66 Add multi-language: English, Français, 简体中文, Deutsch, Türkçe.
- Presets triggers are now in preset tabs with more options
- Added splash screen
- Better night mode
- Release tab now open browser
- Notifications are now handled within the app/system accordingly to the app's minimized state

For developers:
- Now using React and TypeScript
- Switched from npm to yarn: https://github.com/electron-userland/electron-builder/issues/1147#issuecomment-276284477
- Updated Continuous Delivery

Contributors: @rikoopa @Jamiexhz @Silvaburn#3669
This commit is contained in:
Quentin “Storm1er” Decaunes 2020-03-14 15:26:51 +00:00
parent 04b556934d
commit 7a829f184f
98 changed files with 17186 additions and 5324 deletions

3
.env Normal file
View File

@ -0,0 +1,3 @@
BROWSER=none
REACT_APP_VERSION=${npm_package_version}-dev
REACT_APP_SERVER_ENDPOINT=https://api.ryzencontroller.localhost

2
.env.production Normal file
View File

@ -0,0 +1,2 @@
REACT_APP_VERSION=$npm_package_version
REACT_APP_SERVER_ENDPOINT=https://api.ryzencontroller.com

27
.gitignore vendored
View File

@ -1,3 +1,24 @@
node_modules # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
release-builds
installer-builds # dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
/dist
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -1,11 +1,12 @@
include: include:
- template: Dependency-Scanning.gitlab-ci.yml - template: Dependency-Scanning.gitlab-ci.yml
- template: SAST.gitlab-ci.yml - template: SAST.gitlab-ci.yml
- template: Code-Quality.gitlab-ci.yml
stages: stages:
- test - test
- install - install
- package - build
- installer - installer
- release-note - release-note
- release - release
@ -16,31 +17,10 @@ stages:
code_quality: code_quality:
stage: test stage: test
image: docker:stable
allow_failure: true
services:
- docker:stable-dind
variables:
DOCKER_DRIVER: overlay2
script:
- |
if ! docker info &>/dev/null; then
if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
export DOCKER_HOST='tcp://localhost:2375'
fi
fi
- docker run
--env CODECLIMATE_CODE="$PWD"
--volume "$PWD":/code
--volume /var/run/docker.sock:/var/run/docker.sock
--volume /tmp/cc:/tmp/cc
--entrypoint "/bin/sh" codeclimate/codeclimate -c "/usr/src/app/bin/codeclimate engines:install && /usr/src/app/bin/codeclimate analyze"
only: only:
- branches - branches
- schedules@ryzen-controller-team/ryzen-controller - schedules@ryzen-controller-team/ryzen-controller
except: except: []
variables:
- $CODE_QUALITY_DISABLED
dependency_scanning: dependency_scanning:
stage: test stage: test
@ -56,21 +36,6 @@ sast:
- schedules@ryzen-controller-team/ryzen-controller - schedules@ryzen-controller-team/ryzen-controller
except: [] except: []
js_syntax:
stage: test
only:
- branches
- schedules@ryzen-controller-team/ryzen-controller
image: node:carbon
tags: [ docker ]
cache: {}
dependencies: []
before_script:
- npm install -g acorn
- rm -rf node_modules
script:
- find . -iname "*.js" -print0 | xargs -0 -I % sh -c 'echo "%"; acorn --ecma6 --silent "%"'
release-check: release-check:
tags: [ docker ] tags: [ docker ]
image: registry.gitlab.com/juhani/go-semrel-gitlab:v0.20.4 image: registry.gitlab.com/juhani/go-semrel-gitlab:v0.20.4
@ -78,9 +43,9 @@ release-check:
- branches - branches
- schedules@ryzen-controller-team/ryzen-controller - schedules@ryzen-controller-team/ryzen-controller
stage: test stage: test
dependencies: []
script: script:
- release next-version > .next_version - release next-version > .next_version
- cat .next_version
artifacts: artifacts:
paths: paths:
- .next_version - .next_version
@ -95,15 +60,14 @@ node:
- schedules@ryzen-controller-team/ryzen-controller - schedules@ryzen-controller-team/ryzen-controller
stage: install stage: install
tags: [ docker ] tags: [ docker ]
image: electronuserland/builder:wine-mono image: storm1er/electron-builder-wine-dubnium:1.0.2
dependencies: needs:
- release-check - release-check
script: script:
- VERSION=`cat .next_version` - VERSION=`cat .next_version`
- npm version --no-git-tag-version ${VERSION} - yarn config set version-git-tag false
- npm install - yarn version --new-version "${VERSION}"
- cp vendor/7z/7z.exe node_modules/electron-winstaller/vendor/7z.exe - yarn install --frozen-lockfile
- cp vendor/7z/7z.dll node_modules/electron-winstaller/vendor/7z.dll
cache: cache:
paths: paths:
- node_modules/ - node_modules/
@ -111,128 +75,66 @@ node:
paths: paths:
- node_modules/ - node_modules/
- package.json - package.json
- package-lock.json
############################################################################### ###############################################################################
## PACKAGE ## BUILD
############################################################################### ###############################################################################
win32: build:
only: only:
- branches - branches
- schedules@ryzen-controller-team/ryzen-controller - schedules@ryzen-controller-team/ryzen-controller
stage: package stage: build
tags: [ docker ] tags: [ docker ]
image: electronuserland/builder:wine-mono image: storm1er/electron-builder-wine-dubnium:1.0.2
script: script:
- rm -rf vendor/* - yarn build
- npm run-script package-win32
artifacts: artifacts:
paths: paths:
- release-builds/ - build/
dependencies: needs:
- node
linux:
only:
- branches
- schedules@ryzen-controller-team/ryzen-controller
stage: package
tags: [ docker ]
image: electronuserland/builder:wine-mono
script:
- rm -rf bin/*
- npm run-script package-linux
artifacts:
paths:
- release-builds/
dependencies:
- node - node
############################################################################### ###############################################################################
## INSTALLER ## INSTALLER
############################################################################### ###############################################################################
exe-installer: installers:
only: only:
- branches - branches
- schedules@ryzen-controller-team/ryzen-controller - schedules@ryzen-controller-team/ryzen-controller
stage: installer stage: installer
tags: [ docker ] tags: [ docker ]
image: docker:stable image: storm1er/electron-builder-wine-dubnium:1.0.2
services:
- docker:stable-dind
variables:
DOCKER_DRIVER: overlay2
script: script:
- | - export ELECTRON_CACHE=`pwd`/.cache/electron
if ! docker info &>/dev/null; then - export ELECTRON_BUILDER_CACHE=`pwd`/.cache/builder
if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then - mkdir -p `pwd`/.cache/electron
export DOCKER_HOST='tcp://localhost:2375' - mkdir -p `pwd`/.cache/builder
fi - yarn dist-pack:all
fi - mkdir dist/win
- mkdir installer-builds - mkdir dist/rpm
- chmod 777 installer-builds - mkdir dist/deb
- docker run - mv dist/*.exe dist/win/
--env ELECTRON_CACHE="/root/.cache/electron" - mv dist/*.rpm dist/rpm/
--env ELECTRON_BUILDER_CACHE="/root/.cache/electron-builder" - mv dist/*.deb dist/deb/
-v ${PWD}:/project - echo "https://gitlab.com/ryzen-controller-team/ryzen-controller/-/jobs/${CI_JOB_ID}/artifacts/browse/dist/win/" > .exe_link
-v ~/.cache/electron:/root/.cache/electron - echo "https://gitlab.com/ryzen-controller-team/ryzen-controller/-/jobs/${CI_JOB_ID}/artifacts/browse/dist/rpm/" > .rpm_link
-v ~/.cache/electron-builder:/root/.cache/electron-builder - echo "https://gitlab.com/ryzen-controller-team/ryzen-controller/-/jobs/${CI_JOB_ID}/artifacts/browse/dist/deb/" > .deb_link
electronuserland/builder:wine-mono /bin/bash -c "npm run-script build-exe" cache:
- ls installer-builds/RyzenControllerInstaller.exe paths:
- echo "https://gitlab.com/ryzen-controller-team/ryzen-controller/-/jobs/${CI_JOB_ID}/artifacts/browse/installer-builds/" > .exe_link - .cache
artifacts: artifacts:
paths: paths:
- installer-builds/ - dist/win
- dist/rpm
- dist/deb
- .exe_link - .exe_link
dependencies:
- node
- win32
deb-installer:
only:
- branches
- schedules@ryzen-controller-team/ryzen-controller
stage: installer
tags: [ docker ]
image: debian:stretch
before_script:
- apt update -y
- apt install fakeroot curl -y
- curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash
- export NVM_DIR="$HOME/.nvm"
- '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"'
- nvm install 10.15.1
- nvm use 10.15.1
- echo "https://gitlab.com/ryzen-controller-team/ryzen-controller/-/jobs/${CI_JOB_ID}/artifacts/browse/installer-builds/installers/" > .deb_link
script:
- npm run-script build-deb
artifacts:
paths:
- installer-builds/
- .deb_link
dependencies:
- node
- linux
rpm-installer:
only:
- branches
- schedules@ryzen-controller-team/ryzen-controller
stage: installer
tags: [ docker ]
image: electronuserland/builder:wine-mono
script:
- npm run-script build-rpm
- echo "https://gitlab.com/ryzen-controller-team/ryzen-controller/-/jobs/${CI_JOB_ID}/artifacts/browse/installer-builds/installers/" > .rpm_link
artifacts:
paths:
- installer-builds/
- .rpm_link - .rpm_link
dependencies: - .deb_link
needs:
- node - node
- linux - build
############################################################################### ###############################################################################
## RELEASE-NOTE ## RELEASE-NOTE
@ -242,8 +144,8 @@ create-rpm-link:
tags: [ docker ] tags: [ docker ]
image: alpine image: alpine
stage: release-note stage: release-note
dependencies: needs:
- rpm-installer - installers
- release-check - release-check
only: only:
- schedules@ryzen-controller-team/ryzen-controller - schedules@ryzen-controller-team/ryzen-controller
@ -256,10 +158,12 @@ create-rpm-link:
- curl -H "Content-Type:application/json" - curl -H "Content-Type:application/json"
-X POST -X POST
-H "Authorization:Bearer ${BL_TOKEN}" -H "Authorization:Bearer ${BL_TOKEN}"
-d "{\"title\":\"${VERSION} rpm\",\"tags\":[\"RC_rpm\",\"RC_${VERSION_ESCAPED}\",\"RC\"],\"long_url\":\"${URL}\",\"group_guid\":\"Bj4p9bnMV93\"}" -d "{\"title\":\"${VERSION} rpm\",\"tags\":[\"RC_rpm\",\"RC_${VERSION_ESCAPED}\",\"RC\",\"tmp\"],\"long_url\":\"${URL}\",\"group_guid\":\"Bj4p9bnMV93\"}"
"https://api-ssl.bitly.com/v4/bitlinks" > .rpm_res.json "https://api-ssl.bitly.com/v4/bitlinks" > .rpm_res.json
- cat .rpm_res.json | jq -e -r .link > .rpm_short_link - cat .rpm_res.json | jq -e -r .link > .rpm_short_link
- cat .rpm_short_link
artifacts: artifacts:
when: always
paths: paths:
- .rpm_short_link - .rpm_short_link
- .rpm_res.json - .rpm_res.json
@ -268,8 +172,8 @@ create-deb-link:
tags: [ docker ] tags: [ docker ]
image: alpine image: alpine
stage: release-note stage: release-note
dependencies: needs:
- deb-installer - installers
- release-check - release-check
only: only:
- schedules@ryzen-controller-team/ryzen-controller - schedules@ryzen-controller-team/ryzen-controller
@ -282,10 +186,12 @@ create-deb-link:
- curl -H "Content-Type:application/json" - curl -H "Content-Type:application/json"
-X POST -X POST
-H "Authorization:Bearer ${BL_TOKEN}" -H "Authorization:Bearer ${BL_TOKEN}"
-d "{\"title\":\"${VERSION} deb\",\"tags\":[\"RC_deb\",\"RC_${VERSION_ESCAPED}\",\"RC\"],\"long_url\":\"${URL}\",\"group_guid\":\"Bj4p9bnMV93\"}" -d "{\"title\":\"${VERSION} deb\",\"tags\":[\"RC_deb\",\"RC_${VERSION_ESCAPED}\",\"RC\",\"tmp\"],\"long_url\":\"${URL}\",\"group_guid\":\"Bj4p9bnMV93\"}"
"https://api-ssl.bitly.com/v4/bitlinks" > .deb_res.json "https://api-ssl.bitly.com/v4/bitlinks" > .deb_res.json
- cat .deb_res.json | jq -e -r .link > .deb_short_link - cat .deb_res.json | jq -e -r .link > .deb_short_link
- cat .deb_short_link
artifacts: artifacts:
when: always
paths: paths:
- .deb_short_link - .deb_short_link
- .deb_res.json - .deb_res.json
@ -294,8 +200,8 @@ create-exe-link:
tags: [ docker ] tags: [ docker ]
image: alpine image: alpine
stage: release-note stage: release-note
dependencies: needs:
- exe-installer - installers
- release-check - release-check
only: only:
- schedules@ryzen-controller-team/ryzen-controller - schedules@ryzen-controller-team/ryzen-controller
@ -308,10 +214,12 @@ create-exe-link:
- curl -H "Content-Type:application/json" - curl -H "Content-Type:application/json"
-X POST -X POST
-H "Authorization:Bearer ${BL_TOKEN}" -H "Authorization:Bearer ${BL_TOKEN}"
-d "{\"title\":\"${VERSION} exe\",\"tags\":[\"RC_exe\",\"RC_${VERSION_ESCAPED}\",\"RC\"],\"long_url\":\"${URL}\",\"group_guid\":\"Bj4p9bnMV93\"}" -d "{\"title\":\"${VERSION} exe\",\"tags\":[\"RC_exe\",\"RC_${VERSION_ESCAPED}\",\"RC\",\"tmp\"],\"long_url\":\"${URL}\",\"group_guid\":\"Bj4p9bnMV93\"}"
"https://api-ssl.bitly.com/v4/bitlinks" > .exe_res.json "https://api-ssl.bitly.com/v4/bitlinks" > .exe_res.json
- cat .exe_res.json | jq -e -r .link > .exe_short_link - cat .exe_res.json | jq -e -r .link > .exe_short_link
- cat .exe_short_link
artifacts: artifacts:
when: always
paths: paths:
- .exe_short_link - .exe_short_link
- .exe_res.json - .exe_res.json
@ -338,7 +246,7 @@ publish:
only: only:
- schedules@ryzen-controller-team/ryzen-controller - schedules@ryzen-controller-team/ryzen-controller
stage: release stage: release
dependencies: needs:
- release-check - release-check
- node - node
- create-rpm-link - create-rpm-link
@ -347,7 +255,18 @@ publish:
- update-changelog - update-changelog
script: script:
- VERSION=`cat .next_version` - VERSION=`cat .next_version`
- release --ci-commit-tag ${VERSION} commit-and-tag CHANGELOG.md package.json package-lock.json - release --ci-commit-tag ${VERSION} commit-and-tag CHANGELOG.md package.json
- release --ci-commit-tag ${VERSION} add-download-link -d "Debian installer (and variant)" -n "ryzencontroller_${VERSION}_amd64.deb" -u "`cat .deb_short_link`" - release --ci-commit-tag ${VERSION} add-download-link -d "Debian installer (and variant)" -n "ryzencontroller_${VERSION}_amd64.deb" -u "`cat .deb_short_link`"
- release --ci-commit-tag ${VERSION} add-download-link -d "Redhat installer (and variant)" -n "ryzencontroller_${VERSION}.amd64.rpm" -u "`cat .rpm_short_link`" - release --ci-commit-tag ${VERSION} add-download-link -d "Redhat installer (and variant)" -n "ryzencontroller_${VERSION}.amd64.rpm" -u "`cat .rpm_short_link`"
- release --ci-commit-tag ${VERSION} add-download-link -d "Windows installer" -n "RyzenControllerInstaller.exe" -u "`cat .exe_short_link`" - release --ci-commit-tag ${VERSION} add-download-link -d "Windows installer" -n "RyzenControllerInstaller.exe" -u "`cat .exe_short_link`"
no-publish:
tags: [ docker ]
image: registry.gitlab.com/juhani/go-semrel-gitlab:v0.20.4
only:
- schedules@ryzen-controller-team/ryzen-controller
stage: release
when: on_failure
dependencies: []
script:
- echo "Publish step not needed."

15
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/src",
"sourceMapPathOverrides": {
"webpack:///src/*": "${webRoot}/*"
}
}
]
}

10
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"eslint.validate": [
"javascript",
"javascriptreact",
{ "language": "typescript", "autoFix": true },
{ "language": "typescriptreact", "autoFix": true }
],
"prettier.packageManager": "yarn",
"editor.tabSize": 2
}

37
.vscode/tasks.json vendored
View File

@ -1,37 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"problemMatcher": []
},
{
"type": "npm",
"script": "package-win32",
"problemMatcher": []
},
{
"type": "npm",
"script": "package-linux",
"problemMatcher": []
},
{
"type": "npm",
"script": "build-exe",
"problemMatcher": []
},
{
"type": "npm",
"script": "build-deb",
"problemMatcher": []
},
{
"type": "npm",
"script": "build-rpm",
"problemMatcher": []
}
]
}

View File

@ -89,4 +89,4 @@
- Wider window as there is a new tab. (67fc8ae650218cc2b7a7305b4a9d5835f581041b) - Wider window as there is a new tab. (67fc8ae650218cc2b7a7305b4a9d5835f581041b)
- Avoid Sentry noise by creating a random user id. (1a1990b9ad4ed670525e0ffb6f337104c0558bff) - Avoid Sentry noise by creating a random user id. (1a1990b9ad4ed670525e0ffb6f337104c0558bff)
- Better build-exe alive message. (56c9d827bbbc58104ab931ad92b26226d4564aa6) - Better build-exe alive message. (56c9d827bbbc58104ab931ad92b26226d4564aa6)
- Release tab content is now appearing after opening windows from minized start. (b079a535dabe5667d586da0a537ed0c0a934339b) - Release tab content is now appearing after opening windows from minized start. (b079a535dabe5667d586da0a537ed0c0a934339b)

87
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,87 @@
# How to contribute
## 1. For everyone
You can help us in many ways:
Without beeing a developer
- By [opening an issue](https://gitlab.com/ryzen-controller-team/ryzen-controller/issues/new) when:
- you found a bug
- have a suggestion
- or just to tell us thanks
- By testing new features an give feedbacks
- on merge requests labeled `[to-test]`, take a look at the pipelines, you'll see some some green "bubbles". The one named _installers_ allow you to download artifacts.
If you have some developer/IT knowledge
- By doing code reviews:
- on merge requests labeled `[to-review]`, by commenting the code.
- on the [whole codebase](https://gitlab.com/ryzen-controller-team/ryzen-controller/tree/v2-react/src), by [opening an issue](https://gitlab.com/ryzen-controller-team/ryzen-controller/issues/new)
If you want to be a part of Ryzen Controller Team, ask to become a member by [opening an issue](https://gitlab.com/ryzen-controller-team/ryzen-controller/issues/new) ;)
## 2. To members or Ryzen Controller Team
### 2.1. Git workflow
This project is fast forward commit only. It means the [commit graph](https://gitlab.com/ryzen-controller-team/ryzen-controller/-/network/master) will stay on one line. It also means that every merge request must be up-to-date with the target branch before beeing merged.
This allow us to avoid any side effect due to merge commits.
A little article about this: [A git workflow using rebase](https://medium.com/singlestone/a-git-workflow-using-rebase-1b1210de83e5)
#### 2.1.1. Quick example
```bash
git clone git@gitlab.com:ryzen-controller-team/ryzen-controller.git ryzencontroller
cd ryzencontroller
git checkout -b my-bugfix
# Doing stuff with files
git add .
git commit -m "fix: Ensure people know what to do."
git push origin my-bugfix
```
Then you'll see a like to create a merge request
### 2.2. Automation
This project contains a [`.gitlab-ci.yml`](https://gitlab.com/ryzen-controller-team/ryzen-controller/blob/master/.gitlab-ci.yml) file.
This file allow us to auotmatically test, build, package and publish Ryzen Controller app.
It's executed for each merge request update and each week on the master branch.
Here a list of what's done by stage.
#### 2.2.1. Tests
Some jobs to enhance gitlab features like [code quality review](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html), [security dashboard](https://gitlab.com/ryzen-controller-team/ryzen-controller/security/dashboard/), [Static Application Security Testing](https://docs.gitlab.com/ee/user/application_security/sast/).
And a release-check ([sample job](https://gitlab.com/ryzen-controller-team/ryzen-controller/-/jobs/400164480)), this analyse commit list to detect if a new version must be released.
#### 2.2.2. Install
`yarn install --frozen-lockfile`: https://yarnpkg.com/lang/en/docs/cli/install/#toc-yarn-install-frozen-lockfile
Dont generate a yarn.lock lockfile and fail if an update is needed.
#### 2.2.3. Build
`yarn build`: Compile projects files into `build/` folder, production ready.
#### 2.2.4. Installers
`yarn dist-pack-all`: Create a electron package and the installers that goes with it.
#### 2.2.5. Release note
Only executed on scheduled pipelines
`create-rpm-link`, `create-deb-link`, `create-exe-link`: Create bitly links so we can count downloads =)
`update-changelog`: Update [CHANGELOG.md](https://gitlab.com/ryzen-controller-team/ryzen-controller/blob/master/CHANGELOG.md) files, will be used for the release description.
#### 2.2.6. Release
Only executed on scheduled pipelines
`publish`: Will create a new [release](https://gitlab.com/ryzen-controller-team/ryzen-controller/-/releases)
`no-publish`: Avoid the red flag when no release has to be published.

68
CRA-README.md Normal file
View File

@ -0,0 +1,68 @@
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br />
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.<br />
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.<br />
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br />
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
### Analyzing the Bundle Size
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
### Making a Progressive Web App
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
### Advanced Configuration
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
### Deployment
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
### `npm run build` fails to minify
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify

View File

@ -1,6 +1,6 @@
# Ryzen controller # Ryzen controller
*_I'm looking for maintainers and reviewers, if you know html/js and have some spare times, please open an issue or ping `@storm1er#0376` on [discord](https://discord.gg/EahayUv)._* *_I'm looking for maintainers and reviewers, if you know Electron/React and have some spare times, please open an issue or ping `@storm1er#0376` on [discord](https://discord.gg/EahayUv)._*
Thanks to https://github.com/FlyGoat/RyzenAdj and his author, ryzenadj.exe is included (windows only for now). Thanks to https://github.com/FlyGoat/RyzenAdj and his author, ryzenadj.exe is included (windows only for now).
@ -9,33 +9,25 @@ Thanks to https://github.com/FlyGoat/RyzenAdj and his author, ryzenadj.exe is in
- [Windows](#windows) - [Windows](#windows)
- [Debian like](#debian-like) - [Debian like](#debian-like)
- [Redhat like](#redhat-like) - [Redhat like](#redhat-like)
- [Troubleshoot, Q&A](#troubleshoot-qa)
- [Development](#development) - [Development](#development)
- [Pre-requisite](#pre-requisite) - [Pre-requisite](#pre-requisite)
- [Dev install](#dev-install) - [Dev install](#dev-install)
- [Building binaries](#building-binaries) - [Building binaries and installers](#building-binaries-and-installers)
- [Using Windows](#using-windows) - [Using Windows](#using-windows)
- [Using Linux](#using-linux) - [Using Linux](#using-linux)
- [Building installers](#building-installers)
- [For Windows](#for-windows)
- [For Debian like](#for-debian-like)
- [For Redhat like](#for-redhat-like)
## What's this? ## What's this?
![Ryzen Controller ScreenCast](vendor/screencast.webm)
- It's a little Ryzen Master for laptops. - It's a little Ryzen Master for laptops.
- Works best on 2xxx Ryzen series (3xxx series is experimental) - Works best on 2xxx Ryzen series (3xxx series is experimental)
- More videos:
- https://www.youtube.com/watch?v=VYWiKQkT-8o
- https://www.youtube.com/watch?v=fHnO_4k-Cs4
## Installation ## Installation
### Windows ### Windows
- Go to [release page](https://gitlab.com/le.storm1er/ryzen-controller/releases) - Go to [release page](https://gitlab.com/le.storm1er/ryzen-controller/releases)
- Download the latest `RyzenControllerInstaller.exe` - Download the latest `Ryzen Controller Setup X.X.X.exe`
- Enjoy! - Enjoy!
### Debian like ### Debian like
@ -43,8 +35,8 @@ Thanks to https://github.com/FlyGoat/RyzenAdj and his author, ryzenadj.exe is in
- Go to [RyzenAdj](https://github.com/FlyGoat/RyzenAdj) repo. - Go to [RyzenAdj](https://github.com/FlyGoat/RyzenAdj) repo.
- Download and build as explained in [Build requirements](https://github.com/FlyGoat/RyzenAdj#build-requirements). - Download and build as explained in [Build requirements](https://github.com/FlyGoat/RyzenAdj#build-requirements).
- Go to Ryzen Controller's [release page](https://gitlab.com/le.storm1er/ryzen-controller/releases) - Go to Ryzen Controller's [release page](https://gitlab.com/le.storm1er/ryzen-controller/releases)
- Download the latest `ryzencontroller_VERSION_ARCH.deb` file. - Download the latest `ryzen-controller_X.X.X_ARCH.deb` file.
- `sudo dpkg -i ryzencontroller_VERSION_ARCH.deb` - `sudo dpkg -i ryzen-controller_X.X.X_ARCH.deb`
- Launch with `sudo ryzencontroller` - Launch with `sudo ryzencontroller`
- Set the path to your freshly builded `ryzenadj` binary into the "settings" tab. - Set the path to your freshly builded `ryzenadj` binary into the "settings" tab.
- Enjoy! - Enjoy!
@ -54,19 +46,39 @@ Thanks to https://github.com/FlyGoat/RyzenAdj and his author, ryzenadj.exe is in
- Go to [RyzenAdj](https://github.com/FlyGoat/RyzenAdj) repo. - Go to [RyzenAdj](https://github.com/FlyGoat/RyzenAdj) repo.
- Download and build as explained in [Build requirements](https://github.com/FlyGoat/RyzenAdj#build-requirements). - Download and build as explained in [Build requirements](https://github.com/FlyGoat/RyzenAdj#build-requirements).
- Go to Ryzen Controller's [release page](https://gitlab.com/le.storm1er/ryzen-controller/releases) - Go to Ryzen Controller's [release page](https://gitlab.com/le.storm1er/ryzen-controller/releases)
- Download the latest `ryzencontroller_VERSION_ARCH.rpm` file. - Download the latest `ryzen-controller-X.X.X.ARCH.rpm` file.
- `sudo rpm -u ryzencontroller_VERSION_ARCH.rpm` - `sudo rpm -u ryzen-controller-X.X.X.ARCH.rpm`
- Launch with `sudo ryzencontroller` - Launch with `sudo ryzencontroller`
- Set the path to your freshly builded `ryzenadj` binary into the "settings" tab. - Set the path to your freshly builded `ryzenadj` binary into the "settings" tab.
- Enjoy! - Enjoy!
## Troubleshoot, Q&A
> I'm getting an error when installing ryzen controller on linux
_You may need to install `smartmontools` & `lm-sensors` packages to allow Ryzen Controller to work well._
```bash
# Install the app
sudo dpkg -i ryzen-controller_x.x.x_amd64.deb
# If you get error about missing dependencies
sudo apt-get -f install
# To ensure correct temperature and others sys-info
sudo apt-get -y smartmontools lm-sensors
```
> Why yarn?
_See https://github.com/electron-userland/electron-builder/issues/1147#issuecomment-276284477_
## Development ## Development
**THIS PART IS ONLY FOR DEVELOPMENT PURPOSE, IF YOU JUST WANT TO USE RYZEN CONTROLLER, SEE THE [INSTALLATION](#installation) PART.** **THIS PART IS ONLY FOR DEVELOPMENT PURPOSE, IF YOU JUST WANT TO USE RYZEN CONTROLLER, SEE THE [INSTALLATION](#installation) PART.**
### Pre-requisite ### Pre-requisite
- NodeJS v10.15.1 or newer. - NodeJS v10.18.0 or newer.
- About building dependencies: - About building dependencies:
- No dependencies for windows installer - No dependencies for windows installer
- See [electron-installer-debian requirements](https://github.com/electron-userland/electron-installer-debian#requirements) - See [electron-installer-debian requirements](https://github.com/electron-userland/electron-installer-debian#requirements)
@ -75,49 +87,27 @@ Thanks to https://github.com/FlyGoat/RyzenAdj and his author, ryzenadj.exe is in
### Dev install ### Dev install
```bash ```bash
$> cd project cd project
$> npm install yarn install --frozen-lockfile # Please commit any change in yarn.lock/package.json in separated merge request
$> npm start yarn start # You may want to look at "start:*" scripts in package.json
``` ```
### Building binaries ### Building binaries and installers
#### Using Windows #### Using Windows
```bash ```bash
$> cd project cd project
$> npm run-script package-win32 yarn docker # may not be needed, depends on your machine
yarn clean # You may want to look at "clean:*" scripts in package.json
yarn dist-pack-win # You may want to look at "dist-pack:*" scripts in package.json
``` ```
#### Using Linux #### Using Linux
```bash ```bash
$> cd project cd project
$> npm run-script package-linux yarn docker # may not be needed, depends on your machine
``` yarn clean # You may want to look at "clean:*" scripts in package.json
yarn dist-pack-linux # You may want to look at "dist-pack:*" scripts in package.json
### Building installers
#### For Windows
```bash
$> cd project
$> npm run-script package-win32
$> npm run-script build-exe
```
#### For Debian like
```bash
$> cd project
$> npm run-script package-linux
$> npm run-script build-deb
```
#### For Redhat like
```bash
$> cd project
$> npm run-script package-linux
$> npm run-script build-rpm
``` ```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

24
docker/Dockerfile Normal file
View File

@ -0,0 +1,24 @@
# storm1er/electron-builder-wine-dubnium:1.0.2
FROM electronuserland/builder:wine
RUN rm `which node`
RUN rm `which npm`
ENV NODE_VERSION=10.18.0
RUN wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash
RUN echo 'export NVM_DIR="$HOME/.nvm"' >> "$HOME/.bashrc"
RUN echo '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm' >> "$HOME/.bashrc"
RUN echo '[ -s "$NVM_DIR/bash_completion" ] && . "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >> "$HOME/.bashrc"
RUN bash -c 'source $HOME/.nvm/nvm.sh && \
nvm install $NODE_VERSION && \
nvm use $NODE_VERSION && \
nvm alias default $NODE_VERSION'
ENV NVM_DIR /root/.nvm
ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
RUN curl --compressed -o- -L https://yarnpkg.com/install.sh | bash

View File

@ -1,407 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title id="title">Ryzen Controller</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./node_modules/uikit/dist/css/uikit.min.css" />
<script src="https://browser.sentry-cdn.com/5.6.1/bundle.min.js" crossorigin="anonymous"></script>
<script src="./node_modules/uikit/dist/js/uikit.min.js"></script>
<script src="./node_modules/uikit/dist/js/uikit-icons.min.js"></script>
<script src="https://kit.fontawesome.com/c7184454f2.js" crossorigin="anonymous"></script>
<style>
* {
margin: 0;
padding: 0;
border: 0;
vertical-align: baseline;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
html,
body {
background-color: black;
color: white;
border: 1.25px solid #222;
min-width: 800px;
height: 100%;
-webkit-app-region: drag;
overflow-y: hidden;
}
#titlebar {
display: block;
position: fixed;
height: 32px;
width: calc(100% - 2px);
z-index: 9999999999;
/*Compensate for body 1px border*/
}
#main {
height: calc(100% - 29px);
margin-top: 29px;
overflow-y: scroll;
}
nav {
display: block;
top: 0;
left: 0;
width: 100%;
height: 32px;
background-color: #222;
position: absolute;
margin: auto;
-webkit-app-region: drag;
position: fixed;
z-index: 1000;
overflow: hidden;
}
nav #titleShown {
width: 30%;
height: 100%;
color: white;
font-size: 1em;
float: left;
padding: 0 0 0 1em;
line-height: 32px;
-webkit-app-region: drag;
}
nav #buttons {
float: right;
min-width: 150px;
height: 100%;
background-color: #333;
line-height: 32px;
}
#buttons #minimize,
#buttons #maximize,
#buttons #close {
float: left;
height: 100%;
width: 33%;
text-align: center;
color: #eee;
transition: all ease-in-out .2s;
cursor: default;
}
#buttons #minimize:hover,
#buttons #maximize:hover {
background-color: #222;
}
#buttons #close:hover {
background-color: #ff0000;
}
h1,
h2,
h3 {
color: white;
}
#release-tab.uk-active {
position: absolute;
display: inline-block;
width: 100%;
height: calc(100vh - 110px);
}
#main-container-selector {
background-color: #222;
color: white;
}
.uk-tab>.uk-active>a {
color: white;
border-color: #1e87f0;
}
.uk-input,
.uk-select,
#logs {
background-color: #333;
color: white;
}
.uk-textarea {
background-color: #555;
color: white;
}
#controller-tab h3 label,
#experimental-tab h3 label {
cursor: pointer;
}
#donation {
background: rgb(232, 91, 70);
}
#discord {
background: rgb(114, 137, 218);
}
#dark {
border: 2px solid;
border-color: #333;
}
#light {
background-color: black;
border: 2px solid;
border-color: #333;
}
#dark:hover {
color: #bbb;
}
#light:hover {
color: #bbb;
}
#rcon {
height: 110px;
width: auto;
margin-bottom: 0;
}
h1{
margin-bottom: 5px;
}
tr {
background-color: #333;
color: white;
}
tr span.uk-text-lead {
color: white;
}
tr:hover {
background-color: #333;
color: white;
}
.preset {
background-color: #555;
}
td {
background-color: #333;
}
#modal-import-preset div,
#modal-new-preset div,
#modal-export-preset div {
background-color: #333;
}
.uk-tab>* {
padding-left: 10px;
}
button,
ul,
li,
div {
-webkit-app-region: no-drag;
}
.delete {
margin-bottom: 0px;
}
@media only screen and (max-width: 1086px) {
.delete {
margin-bottom: 20px
}
}
</style>
</head>
<body class="uk-animation-fade">
<header id="titlebar">
<nav>
<div id="titleShown"></div>
<div id="buttons">
<div id="minimize">
<span>-</span>
</div>
<div id="maximize">
<span>&square;</span>
</div>
<div id="close">
<span>&times;</span>
</div>
</div>
</nav>
</header>
<div id="main">
<h1>
<img id="rcon" src="assets/icon.png">
Ryzen Controller
<span id="version" class="uk-badge"></span>
<a class="uk-badge" id="donation" title="Buy us some beers ❤️" href="#" onClick="require('electron').shell.openExternal('https://www.patreon.com/ryzencontrollerteam')">Patreon</a>
<a class="uk-badge" id="discord" title="Join us on discord" href="#" onClick="require('electron').shell.openExternal('https://discord.gg/EahayUv')">Discord</a>
<a class="uk-badge" id="light" title="Light Mode" href="index.html"><i class="fas fa-sun"></i></a>
<a class="uk-badge" id="dark" title="Dark Mode (BETA)" href="index-dark.html"><i class="fas fa-moon"></i></a>
</h1>
<ul uk-switcher="animation: uk-animation-fade" uk-tab uk-tab uk-sticky="top: 56; offset: 32px; animation: uk-animation-slide-top;" class="uk-background-default uk-margin-remove" id="main-container-selector">
<li><a href="#">Presets</a></li>
<li><a href="#">Settings</a></li>
<li><a href="#">Releases</a></li>
<li><a href="#">Logs</a></li>
<li><a href="#">System Info</a></li>
<li><a href="#">Tutorial</a></li>
</ul>
<ul class="uk-switcher uk-margin-remove" id="main-container" uk-height-viewport="expand: true" style="position:relative;">
<li class="uk-margin-top uk-margin-bottom uk-container">
<div id="presetTab"></div>
<p class="uk-margin">
<button class="uk-button uk-button-secondary" uk-toggle="target: #modal-export-preset" type="button" onClick="preset_export()">
Export
</button>
<button class="uk-button uk-button-secondary" uk-toggle="target: #modal-import-preset" type="button">
Import
</button>
</p>
</li>
<li class="uk-margin-top uk-margin-bottom uk-container">
<h3 class="windows-only">Auto start:</h3>
<label class="windows-only"><input class="uk-checkbox" type="checkbox" id="start_at_boot"> When checked, Ryzen Controller will start on session start.</label>
<h3>Auto apply on launch:</h3>
<label><input class="uk-checkbox" type="checkbox" id="apply_last_settings_on_launch"> When checked, Ryzen Controller will try to apply latest used settings on launch.</label>
<h3>Minimize to tray:</h3>
<label><input class="uk-checkbox" type="checkbox" id="minimize_to_tray"> When checked, Ryzen Controller will minimize to tray instead of taskbar.</label><br>
<label><input class="uk-checkbox" type="checkbox" id="start_minimized"> When checked, Ryzen Controller will start minimized when you launch it.</label>
<!--
<h3 class="windows-only">HPET:</h3>
<p class="uk-margin windows-only">
High Precision Event Timer: Allow application to get time with precision below microseconds, but is slower than most other other timer facilities.<br/>
Try running benchmark with and without to see if you get any difference.<br/>
If it doesn't make any difference, it's recommanded to let it enable.<br/>
<em>You must reboot to apply changes.</em>
</p>
<p class="uk-margin windows-only">
<button class="uk-button uk-button-primary" onClick="toggleHpet('true')">Enable</button>
<button class="uk-button uk-button-secondary" onClick="toggleHpet('false')">Disable</button>
</p>
-->
<h3>Re-apply ryzenadj periodically:</h3>
<p>Ryzen Controller will re-apply ryzenadj every X seconds. Set to 0 to disable.</p>
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-6">
<input class="uk-input" type="number" min="0" step="10" max="3600" value="0" id="reapply_periodically" repeat="reapply_periodically_range">
</div>
<div class="uk-width-expand">
<input class="uk-range" type="range" min="0" step="10" max="3600" value="0" repeat="reapply_periodically" id="reapply_periodically_range">
</div>
</div>
<h3>Ryzenadj path:</h3>
<div class="uk-grid-small" uk-grid>
<div class="uk-width-2-3@s">
<input class="uk-input" type="text" id="ryzen_adj_path">
</div>
<div class="uk-width-1-3@s">
<button class="uk-button uk-button-default uk-button-small" type="button" onClick="askingForRyzenAdjExecutablePath()">
Select path to ryzenadj.exe
</button>
</div>
</div>
</li>
<li id="release-tab">
<webview style="height:100%;" frameborder="0" src="https://gitlab.com/ryzen-controller-team/ryzen-controller/releases"></webview>
</li>
<li class="uk-margin-top uk-margin-bottom uk-container">
<textarea class="uk-textarea" rows="20" id="logs" readonly></textarea>
</li>
<li class="uk-margin-top uk-margin-bottom uk-container">
<div>
<canvas id="glcanvas" width="0" height="0"></canvas>
<h3>System Infomation</h3>
<div id="cpu"></div>
<div id="cores"></div>
<div id="cpuspeed"></div>
<div id="gpu1"></div>
<div id="memory"></div>
<div id="platform"></div>
</div>
</li>
<li class="uk-margin-top uk-margin-bottom uk-container">
<div>
<h2>Coming Soon</h2>
</div>
</li>
</ul>
<!-- Add a preset modal -->
<div id="modal-new-preset" uk-modal>
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Save current settings to preset</h2>
<p>The current settings will be saved to a new preset. Give it a name then you'll be able to reapply it fastly from the preset tab.</p>
<div class="uk-margin">
<input class="uk-input" type="text" id="new_preset_name" placeholder="Preset name: performance, low energy, unicorn power, ...">
</div>
<button class="uk-button uk-button-primary uk-modal-close" type="button" onClick="preset_createNewPreset()">Save</button>
<button class="uk-button uk-button-secondary uk-modal-close" type="button">Close</button>
</div>
</div>
<!-- Export preset modal -->
<div id="modal-export-preset" uk-modal>
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Export presets</h2>
<p>This is the data to be used by Ryzen Controller to import presets.</p>
<div class="uk-margin">
<textarea class="uk-textarea" rows="5" id="modal-export-preset-textarea" readonly></textarea>
</div>
<button class="uk-button uk-button-secondary uk-modal-close" type="button">Close</button>
</div>
</div>
<!-- Import preset modal -->
<div id="modal-import-preset" uk-modal>
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Import presets</h2>
<p>Paste here the presets and valid, your current presets will be merged with this.</p>
<div class="uk-margin">
<textarea class="uk-textarea" rows="5" id="modal-import-preset-textarea"></textarea>
</div>
<button class="uk-button uk-button-primary uk-modal-close" type="button" onClick="preset_import()">Import</button>
<button class="uk-button uk-button-secondary uk-modal-close" type="button">Close</button>
</div>
</div>
</div>
<script>
require('./renderer.js')
</script>
<script type="text/javascript" src="./js/preset.js"></script>
<script type="text/javascript" src="./js/methods.js"></script>
<script type="text/javascript" src="./js/app.js"></script>
<script type="text/javascript" src="./js/init.js"></script>
<script type="text/javascript" src="./js/menuHandler.js"></script>
</body>
</html>

View File

@ -1,366 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title id="title">Ryzen Controller</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./node_modules/uikit/dist/css/uikit.min.css" />
<script src="https://browser.sentry-cdn.com/5.6.1/bundle.min.js" crossorigin="anonymous"></script>
<script src="./node_modules/uikit/dist/js/uikit.min.js"></script>
<script src="./node_modules/uikit/dist/js/uikit-icons.min.js"></script>
<script src="https://kit.fontawesome.com/c7184454f2.js" crossorigin="anonymous"></script>
<style>
* {
margin: 0;
padding: 0;
border: 0;
vertical-align: baseline;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
html,
body {
border: 1.25px solid #ddd;
min-width: 800px;
height: 100%;
-webkit-app-region: drag;
overflow-y: hidden;
}
#titlebar {
display: block;
position: fixed;
height: 32px;
width: calc(100% - 2px);
z-index: 9999999999;
/*Compensate for body 1px border*/
}
#main {
height: calc(100% - 29px);
margin-top: 29px;
overflow-y: scroll;
}
nav {
display: block;
top: 0;
left: 0;
width: 100%;
height: 32px;
background-color: #ddd;
position: absolute;
margin: auto;
-webkit-app-region: drag;
position: fixed;
z-index: 1000;
overflow: hidden;
}
nav #titleShown {
width: 30%;
height: 100%;
color: black;
font-size: 1em;
float: left;
padding: 0 0 0 1em;
line-height: 32px;
-webkit-app-region: drag;
}
nav #buttons {
float: right;
min-width: 150px;
height: 100%;
background-color: #bbb;
line-height: 32px;
}
#buttons #minimize,
#buttons #maximize,
#buttons #close {
float: left;
height: 100%;
width: 33%;
text-align: center;
color: white;
transition: all ease-in-out .2s;
cursor: default;
}
#buttons #minimize:hover,
#buttons #maximize:hover {
background-color: #444;
}
#buttons #close:hover {
background-color: #ff0000;
}
#release-tab.uk-active {
position: absolute;
display: inline-block;
width: 100%;
height: calc(100vh - 110px);
}
#controller-tab h3 label,
#experimental-tab h3 label {
cursor: pointer;
}
#donation {
background: rgb(232, 91, 70);
-webkit-app-region: no-drag;
}
#discord {
background: rgb(114, 137, 218);
-webkit-app-region: no-drag;
}
#dark {
background-color: #333;
}
#rcon {
height: 110px;
width: auto;
}
h1 {
margin-bottom: 5px;
}
#dark {
background-color: white;
border: 2px solid;
border-color: #333;
color: black;
-webkit-app-region: no-drag;
margin-left: 0;
}
#light {
margin-right: 0;
border: 2px solid;
border-color: #333;
-webkit-app-region: no-drag;
margin: auto;
}
#dark:hover {
color: #bbb;
}
#light:hover {
color: #bbb;
}
.uk-tab>* {
padding-left: 10px;
-webkit-app-region: no-drag;
}
button,
ul,
li,
div {
-webkit-app-region: no-drag;
}
.delete {
margin-bottom: 0px;
}
@media only screen and (max-width: 1086px) {
.delete {
margin-bottom: 20px
}
}
</style>
</head>
<body class="uk-animation-fade">
<header id="titlebar">
<nav>
<div id="titleShown"></div>
<div id="buttons">
<div id="minimize">
<span>-</span>
</div>
<div id="maximize">
<span>&square;</span>
</div>
<div id="close">
<span>&times;</span>
</div>
</div>
</nav>
</header>
<div id="main">
<h1>
<img id="rcon" src="assets/icon.png">
Ryzen Controller
<span id="version" class="uk-badge"></span>
<a class="uk-badge" id="donation" title="Buy us some beers ❤️" href="#" onClick="require('electron').shell.openExternal('https://www.patreon.com/ryzencontrollerteam')">Patreon</a>
<a class="uk-badge" id="discord" title="Join us on discord" href="#" onClick="require('electron').shell.openExternal('https://discord.gg/EahayUv')">Discord</a>
<a class="uk-badge" id="light" title="Light Mode" href="index.html"><i class="fas fa-sun"></i></a>
<a class="uk-badge" id="dark" title="Dark Mode (BETA)" href="index-dark.html"><i class="fas fa-moon"></i></a>
</h1>
<ul uk-switcher="animation: uk-animation-fade" uk-tab uk-tab uk-sticky="top: 56; offset: 32px; animation: uk-animation-slide-top;" class="uk-background-default uk-margin-remove" id="main-container-selector">
<li><a href="#">Presets</a></li>
<li><a href="#">Settings</a></li>
<li><a href="#">Releases</a></li>
<li><a href="#">Logs</a></li>
<li><a href="#">System Info</a></li>
<li><a href="#">Tutorial</a></li>
</ul>
<ul class="uk-switcher uk-margin-remove" id="main-container" uk-height-viewport="expand: true" style="position:relative;">
<li class="uk-margin-top uk-margin-bottom uk-container">
<div id="presetTab"></div>
<p class="uk-margin">
<button class="uk-button uk-button-secondary" uk-toggle="target: #modal-export-preset" type="button" onClick="preset_export()">
Export
</button>
<button class="uk-button uk-button-secondary" uk-toggle="target: #modal-import-preset" type="button">
Import
</button>
</p>
</li>
<li class="uk-margin-top uk-margin-bottom uk-container">
<h3 class="windows-only">Auto start:</h3>
<label class="windows-only"><input class="uk-checkbox" type="checkbox" id="start_at_boot"> When checked, Ryzen Controller will start on session start.</label>
<h3>Auto apply on launch:</h3>
<label><input class="uk-checkbox" type="checkbox" id="apply_last_settings_on_launch"> When checked, Ryzen Controller will try to apply latest used settings on launch.</label>
<h3>Minimize to tray:</h3>
<label><input class="uk-checkbox" type="checkbox" id="minimize_to_tray"> When checked, Ryzen Controller will minimize to tray instead of taskbar.</label><br>
<label><input class="uk-checkbox" type="checkbox" id="start_minimized"> When checked, Ryzen Controller will start minimized when you launch it.</label>
<!--
<h3 class="windows-only">HPET:</h3>
<p class="uk-margin windows-only">
High Precision Event Timer: Allow application to get time with precision below microseconds, but is slower than most other other timer facilities.<br/>
Try running benchmark with and without to see if you get any difference.<br/>
If it doesn't make any difference, it's recommanded to let it enable.<br/>
<em>You must reboot to apply changes.</em>
</p>
<p class="uk-margin windows-only">
<button class="uk-button uk-button-primary" onClick="toggleHpet('true')">Enable</button>
<button class="uk-button uk-button-secondary" onClick="toggleHpet('false')">Disable</button>
</p>
-->
<h3>Re-apply ryzenadj periodically:</h3>
<p>Ryzen Controller will re-apply ryzenadj every X seconds. Set to 0 to disable.</p>
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-6">
<input class="uk-input" type="number" min="0" step="10" max="3600" value="0" id="reapply_periodically" repeat="reapply_periodically_range">
</div>
<div class="uk-width-expand">
<input class="uk-range" type="range" min="0" step="10" max="3600" value="0" repeat="reapply_periodically" id="reapply_periodically_range">
</div>
</div>
<h3>Ryzenadj path:</h3>
<div class="uk-grid-small" uk-grid>
<div class="uk-width-2-3@s">
<input class="uk-input" type="text" id="ryzen_adj_path">
</div>
<div class="uk-width-1-3@s">
<button class="uk-button uk-button-default uk-button-small" type="button" onClick="askingForRyzenAdjExecutablePath()">
Select path to ryzenadj.exe
</button>
</div>
</div>
</li>
<li id="release-tab">
<webview style="height:100%;" frameborder="0" src="https://gitlab.com/ryzen-controller-team/ryzen-controller/releases"></webview>
</li>
<li class="uk-margin-top uk-margin-bottom uk-container">
<textarea class="uk-textarea" rows="20" id="logs" readonly></textarea>
</li>
<li class="uk-margin-top uk-margin-bottom uk-container">
<div>
<canvas id="glcanvas" width="0" height="0"></canvas>
<h3>System Infomation</h3>
<div id="cpu"></div>
<div id="cores"></div>
<div id="cpuspeed"></div>
<div id="gpu1"></div>
<div id="memory"></div>
<div id="platform"></div>
</div>
</li>
<li class="uk-margin-top uk-margin-bottom uk-container">
<div>
<h2>Coming Soon</h2>
</div>
</li>
</ul>
<!-- Add a preset modal -->
<div id="modal-new-preset" uk-modal>
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Save current settings to preset</h2>
<p>The current settings will be saved to a new preset. Give it a name then you'll be able to reapply it fastly from the preset tab.</p>
<div class="uk-margin">
<input class="uk-input" type="text" id="new_preset_name" placeholder="Preset name: performance, low energy, unicorn power, ...">
</div>
<button class="uk-button uk-button-primary uk-modal-close" type="button" onClick="preset_createNewPreset()">Save</button>
<button class="uk-button uk-button-secondary uk-modal-close" type="button">Close</button>
</div>
</div>
<!-- Export preset modal -->
<div id="modal-export-preset" uk-modal>
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Export presets</h2>
<p>This is the data to be used by Ryzen Controller to import presets.</p>
<div class="uk-margin">
<textarea class="uk-textarea" rows="5" id="modal-export-preset-textarea" readonly></textarea>
</div>
<button class="uk-button uk-button-secondary uk-modal-close" type="button">Close</button>
</div>
</div>
<!-- Import preset modal -->
<div id="modal-import-preset" uk-modal>
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Import presets</h2>
<p>Paste here the presets and valid, your current presets will be merged with this.</p>
<div class="uk-margin">
<textarea class="uk-textarea" rows="5" id="modal-import-preset-textarea"></textarea>
</div>
<button class="uk-button uk-button-primary uk-modal-close" type="button" onClick="preset_import()">Import</button>
<button class="uk-button uk-button-secondary uk-modal-close" type="button">Close</button>
</div>
</div>
</div>
<script>
require('./renderer.js')
</script>
<script type="text/javascript" src="./js/preset.js"></script>
<script type="text/javascript" src="./js/methods.js"></script>
<script type="text/javascript" src="./js/app.js"></script>
<script type="text/javascript" src="./js/init.js"></script>
<script type="text/javascript" src="./js/menuHandler.js"></script>
</body>
</html>

View File

@ -1,62 +0,0 @@
const electron = require('electron')
const app = electron.app
module.exports = {
handleSquirrelEvent: function() {
if (process.argv.length === 1) {
return false;
}
const ChildProcess = require('child_process');
const path = require('path');
const appFolder = path.resolve(process.execPath, '..');
const rootAtomFolder = path.resolve(appFolder, '..');
const updateDotExe = path.resolve(path.join(rootAtomFolder, 'Update.exe'));
const exeName = path.basename(process.execPath);
const spawn = function(command, args) {
let spawnedProcess, error;
try {
spawnedProcess = ChildProcess.spawn(command, args, {detached: true});
} catch (error) {}
return spawnedProcess;
};
const squirrelEvent = process.argv[1];
switch (squirrelEvent) {
case '--squirrel-install':
case '--squirrel-updated':
// Optionally do things such as:
// - Add your .exe to the PATH
// - Write to the registry for things like file associations and
// explorer context menus
setTimeout(app.quit, 1000);
return true;
case '--squirrel-uninstall':
// Undo anything you did in the --squirrel-install and
// --squirrel-updated handlers
// Removing desktop shortcut
let fs = require('fs');
var shortcut_path = app.getPath('desktop') + "\\Ryzen Controller";
if (!fs.existsSync(shortcut_path)) {
fs.unlink(shortcut_path, console.log);
}
setTimeout(app.quit, 1000);
return true;
case '--squirrel-obsolete':
// This is called on the outgoing version of your app before
// we update to the new version - it's the opposite of
// --squirrel-updated
app.quit();
return true;
}
}
}

View File

@ -1,58 +0,0 @@
const createWindowsInstaller = require('electron-winstaller').createWindowsInstaller
const path = require('path')
const lyrics = [
"I'm awake",
"I'm alive",
"now",
"I know",
"what I",
"believe inside",
"NOW",
"It's my time",
"I'll do",
"what I want",
"'cause this",
"is my life",
"Here",
"right now",
"I will",
"stand my ground",
"and never back down",
"I know",
"what I",
"believe inside",
"I'm awake",
"and",
"I'm alive",
"--",
];
var lyrics_pos = -1;
var keepAlive = false;
getInstallerConfig()
.then(createWindowsInstaller)
.catch((error) => {
console.error(error.message || error)
process.exit(1)
})
.then(function(){
clearInterval(keepAlive);
console.log('Installer created.');
})
function getInstallerConfig () {
console.log('creating windows installer')
keepAlive = setInterval(function(){
lyrics_pos++;
console.log(`${lyrics[ lyrics_pos % lyrics.length ]}`);
}, 10000);
const rootPath = path.join('./')
return Promise.resolve({
appDirectory: path.join(rootPath, 'release-builds', 'RyzenController-win32-ia32/'),
authors: 'Decaunes Quentin',
outputDirectory: path.join(rootPath, 'installer-builds'),
setupExe: 'RyzenControllerInstaller.exe',
noMsi: true
})
}

111
js/app.js
View File

@ -1,111 +0,0 @@
ready(function(){
const settings = require('electron-settings');
const fixPath = require('fix-path');
document.isStarting = true;
if (isDevMode()) {
addSentry();
}
fixPath();
displayOptions();
preFillSettings();
isRyzenAdjPathValid();
loadLatestUsedSettings();
registerRepeaterForAllInput();
registerEventListenerForSettingsInput();
checkForAdminRights();
displayVersion();
reApplyPeriodically(require('electron-settings').get('settings.reapply_periodically'));
handleAcStatusChanges();
if (isWindows() && isDevMode()) {
recreateShortcut();
}
handlePlatformSpecificDisplay();
preset_updateList();
checkForNewRelease();
document.isStarting = false;
appendLog(`################## UserId: ${settings.get('userid')}`);
settings.set('settings',
Object.assign(
{},
settings.get('settings'),
{ first_launch: false }
)
);
});
/**
* Will create and handle ryzenadj.exe execution.
*/
function applyRyzenSettings() {
const settings = getCurrentSettings("ryzenadjArgs");
const appSettings = require('electron-settings');
const child = require('child_process').execFile;
const executablePath = getRyzenAdjExecutablePath();
if (!isRyzenAdjPathValid()) {
return;
}
const options_data = require('./js/options_data.json');
const ryzenAdjConvert = {
"toHex": function (value) { return '0x' + decimalToHexString(value * 1000); },
"toThousand": function (value) { return value * 1000; },
"roundTen": function (value) { return parseInt(value / 10) * 10; },
};
// Create a string to be used for CLI.
var parameters = [];
for (const option in settings) {
if (settings.hasOwnProperty(option)) {
let value = settings[option];
let option_name = false;
try {
option_name = Object.keys(options_data).filter(function(cur_option_name){
return options_data[cur_option_name].ryzenadj_arg === option;
})[0];
} catch (error) {
notification('danger', `Unknown option "${option}".`);
appendLog(`applyRyzenSettings(): ${error}`);
Sentry.captureException(new Error(`applyRyzenSettings(): ${error}`));
}
if (options_data[option_name].ryzenadj_value_convert) {
value = ryzenAdjConvert[
options_data[option_name].ryzenadj_value_convert
](value);
}
parameters.push('' + option + value);
}
}
if (parameters.length === 0) {
notification('primary', 'Please add some options before applying ryzenAdj.');
return;
}
if (!appSettings.get('retry')) {
appSettings.set('retry', 2);
notification('warning', 'Applying settings...');
} else {
let retry = appSettings.get('retry') - 1;
appSettings.set('retry', retry);
}
child(executablePath, parameters, function(err, data) {
var output = data.toString();
if (err) {
if (appSettings.get('retry')) {
return setTimeout(applyRyzenSettings, 500);
}
notification('danger', err + '<br/>' + output);
Sentry.captureException(new Error(err + ' - ' + output));
}
else if (output) {
notification('success', `RyzenAdj has been applied successfully.`);
appendLog('Ryzenadj output:<br/>' + output);
saveLatestUsedSettings();
}
appSettings.set('retry', false);
});
}

View File

@ -1,78 +0,0 @@
var os = require("os");
var title = document.getElementById('title').innerHTML;
var cpus = os.cpus();
var cpucount = cpus.length;
var cpucores;
var cpuman;
var cpuspeed;
var cpuspeedmhz;
var platform;
var mem;
var totalmem;
var gputest1;
cpus.forEach(function (cpu, i) {
cpuman = "<strong>Processor Model: </strong>" + cpu.model;
cpuspeed = cpu.speed;
cpuspeedmhz = "<strong>Processor Frequency: </strong>" + cpuspeed.toString() + "MHz";
platform = os.platform();
mem = os.totalmem();
mem = mem / 1000000000;
mem = Math.trunc(mem);
totalmem = "<strong>System Memory: </strong>" + mem.toString()+ " " + "GB";
cpucores = "<strong>Processor Cores: </strong>" + cpucount.toString();
if(platform == "win32"){
platform = "<strong>Platform: </strong>" + "Windows " + os.release();
} else if(platform == "linux"){
platform = "<strong>Platform: </strong>" + "Linux " + os.release();
}
});
var canvas;
canvas = document.getElementById("glcanvas");
var gl = canvas.getContext("experimental-webgl");
function getUnmaskedInfo(gl) {
var unMaskedInfo = {
renderer: '',
vendor: ''
};
var dbgRenderInfo = gl.getExtension("WEBGL_debug_renderer_info");
if (dbgRenderInfo != null) {
unMaskedInfo.renderer = gl.getParameter(dbgRenderInfo.UNMASKED_RENDERER_WEBGL);
unMaskedInfo.vendor = gl.getParameter(dbgRenderInfo.UNMASKED_VENDOR_WEBGL);
}
return unMaskedInfo;
}
gputest1 = getUnmaskedInfo(gl).renderer;
if(gputest1 =="ANGLE (AMD Radeon(TM) RX Vega 10 Graphics Direct3D11 vs_5_0 ps_5_0)"){
gputest1 = "AMD Radeon(TM) RX Vega 10 Graphics"
} else if (gputest1 =="ANGLE (AMD Radeon(TM) RX Vega 8 Graphics Direct3D11 vs_5_0 ps_5_0)"){
gputest1 = "AMD Radeon(TM) RX Vega 8 Graphics"
} else if(gputest1 =="ANGLE (AMD Radeon(TM) RX Vega 8 Mobile Graphics Direct3D11 vs_5_0 ps_5_0)"){
gputest1 = "AMD Radeon(TM) RX Vega 8 Graphics"
} else if(gputest1 =="ANGLE (AMD Radeon(TM) RX Vega 3 Graphics Direct3D11 vs_5_0 ps_5_0)"){
gputest1 = "AMD Radeon(TM) RX Vega 3 Graphics"
} else if(gputest1 =="ANGLE (AMD Radeon(TM) RX Vega 3 Mobile Graphics Direct3D11 vs_5_0 ps_5_0)"){
gputest1 = "AMD Radeon(TM) RX Vega 3 Graphics"
}
document.getElementById('titleShown').innerHTML = title;
document.getElementById('cpu').innerHTML = cpuman;
document.getElementById('cores').innerHTML = cpucores;
document.getElementById('cpuspeed').innerHTML = cpuspeedmhz;
document.getElementById('platform').innerHTML = platform;
document.getElementById('memory').innerHTML = totalmem;
document.getElementById('gpu1').innerHTML = "<strong>GPU: </strong>" + gputest1;

View File

@ -1,22 +0,0 @@
const {remote} = require('electron');
document.getElementById('close').addEventListener('click', closeWindow);
document.getElementById('minimize').addEventListener('click', minimizeWindow);
document.getElementById('maximize').addEventListener('click', maximizeWindow);
function closeWindow () {
var window = remote.getCurrentWindow();
window.close();
}
function minimizeWindow () {
var window = remote.getCurrentWindow();
window.minimize();
}
function maximizeWindow () {
var window = remote.getCurrentWindow()
window.isMaximized() ? window.unmaximize() : window.maximize();
}

View File

@ -1,709 +0,0 @@
/**
* Will enable Sentry.
*/
function addSentry() {
const uuidv4 = require('uuid/v4');
const settings = require('electron-settings');
var waitSentry = setInterval(() => {
if (!Sentry) {
console.log('waitSentry');
return;
}
clearInterval(waitSentry);
console.log('sentryOk');
Sentry.init({
dsn: 'https://f80fd3ea297141a8bdc04ce812762f39@sentry.io/1513427',
release: require('./package.json').version,
beforeSend: (event) => {
event.exception.values = event.exception.values.map((value) => {
if (value.stacktrace) {
value.stacktrace.frames = value.stacktrace.frames.map((frame) => {
frame.filename = frame.filename.replace(/^.*ryzen(|-)controller\//g, "");
return frame;
});
}
return value;
});
if (event.request.url) {
event.request.url = event.request.url.replace(/^.*ryzen(|-)controller\//g, "");
}
return event;
}
});
Sentry.configureScope((scope) => {
if (!settings.get('userid')) {
settings.set('userid', uuidv4());
}
const userid = settings.get('userid');
scope.setUser({
id: userid
});
});
}, 100);
}
/**
* Will load options_data.json and display them into index.html.
*/
function displayOptions(){
const options_data = require('./js/options_data.json');
var tabs = {};
for (const option_name in options_data) {
if (!options_data.hasOwnProperty(option_name)) {
appendLog(`Error while loading ${option_name} option.`);
continue;
}
let option_data = options_data[option_name];
if (!option_data.hasOwnProperty('tab')) {
appendLog(`Error while loading ${option_name} tab property.`);
continue;
}
let tab_name = option_data['tab'];
let tab_name_css = tab_name.toLowerCase().replace(/ /g, "-");
let tab = document.querySelector(`#${tab_name_css}-tab`);
if (!tab) {
let selectorContent = document.querySelector(`#main-container-selector`).innerHTML;
let tabContent = document.querySelector(`#main-container`).innerHTML;
document.querySelector('#main-container-selector').innerHTML = /*html*/`
<li><a href="#">${tab_name}</a></li>
${selectorContent}
`;
document.querySelector(`#main-container`).innerHTML = /*html*/`
<li class="uk-margin-top uk-margin-bottom uk-container" id="${tab_name_css}-tab"></li>
${tabContent}
`;
tab = document.querySelector(`#${tab_name_css}-tab`);
}
tabs[tab_name_css] = true;
tab.innerHTML += /*html*/`
<h3 id="${option_name}-label" uk-tooltip="${option_data.description}"><label>
<input class="uk-checkbox uk-margin-small-right" type="checkbox" id="apply_${option_name}"/>
<span class="option-label">${option_data.label}</span>
</label></h3>
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-6">
<input
class="uk-input"
type="number"
id="${option_name}"
repeat="${option_name}_range"
min="${option_data.min}"
max="${option_data.max}"
step="${option_data.step}"
value="${option_data.default}"
/>
</div>
<div class="uk-width-expand">
<input
class="uk-range"
type="range"
repeat="${option_name}"
id="${option_name}_range"
min="${option_data.min}"
max="${option_data.max}"
step="${option_data.step}"
value="${option_data.default}"
/>
</div>
</div>
`;
UIkit.tooltip(document.querySelector(`#${option_name}-label`));
}
for (const tab_name_css in tabs) {
if (tabs.hasOwnProperty(tab_name_css)) {
const tab = document.querySelector(`#${tab_name_css}-tab`);
tab.innerHTML += /*html*/`
<p class="uk-margin">
<button class="uk-button uk-button-primary" onClick="applyRyzenSettings()">Apply</button>
<button class="uk-button uk-button-secondary" uk-toggle="target: #modal-new-preset">Save to preset</button>
</p>
`;
}
}
}
/**
* Will create a nodes from an html string.
* @param {string} str An html string
*/
function parseHTML(str) {
var tmp = document.implementation.createHTMLDocument();
tmp.body.innerHTML = str;
return tmp.body.children;
};
/**
* Return the current working directory.
*/
function getCurrentWorkingDirectory() {
const cwd = require('electron').remote.app.getAppPath()
appendLog(`getCurrentWorkingDirectory(): ${cwd}`);
return cwd;
}
/**
* Conversion from int to hex.
* @param {int} number A number.
*/
function decimalToHexString(number) {
if (number < 0)
{
number = 0xFFFFFFFF + number + 1;
}
return number.toString(16).toUpperCase();
}
/**
* Will execute the given callback once document is ready.
* @param {function} fn A callback to be executed.
*/
function ready(fn) {
if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading"){
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
/**
* Will make sure inputs are repeated when needed.
*
* Use the "repeat" html attribute to define where the current value must be repeated.
*/
function registerRepeaterForAllInput() {
var ranges = document.querySelectorAll('[repeat]');
for (const range in ranges) {
if (ranges.hasOwnProperty(range)) {
const element = ranges[range];
element.addEventListener('change', function(event) {
var repeater = document.getElementById(event.target.attributes.repeat.value);
repeater.value = event.target.value;
});
}
}
}
/**
* Check that the app is running with admin right.
*
* Will display a warning if not.
*/
function checkForAdminRights() {
if (!isWindows()) {
const isRoot = process.getuid && process.getuid() === 0;
if (!isRoot) {
notification('danger',
`Warning: you must launch this app as administrator.<br/>`
+ `Use "sudo ryzencontroller" from terminal to fix this.`
);
}
} else {
const child = require('child_process').execFile;
child('NET SESSION', function(err,so,se) {
if (se.length !== 0) {
notification('warning',
`Warning: you should launch this app as administrator, `
+ `ryzenadj.exe doesn't seems to work correctly without administrator rights.`
);
}
});
}
}
/**
* Check that the app is running on Windows.
*/
function isWindows() {
return require('os').platform() === 'win32';
}
/**
* Will display a notification in ".notification-zone".
* @param {string} type "primary", "warning", "danger" or "success".
* @param {string} message The message to be displayed, new line will be replaced by <br/>.
*/
function notification(type, message) {
appendLog(`notification(): ${type}\n${message}`);
if (window.last_notification) {
window.last_notification.close();
window.last_notification = false;
}
window.last_notification = new Notification('Ryzen Controller', {
body: message,
requireInteraction: false,
silent: true,
});
if (!window.last_notification) {
window.last_notification = false;
UIkit.notification({
message: (''+message).replace(/(?:\r\n|\r|\n)/g, '<br/>'),
status: type,
pos: 'top-right',
timeout: 5000,
});
}
}
/**
* Will return the ryzenadj.exe path registered, or default one if not provided.
*/
function getRyzenAdjExecutablePath() {
const settings = require('electron-settings');
var ryzen_adj_path = settings.get('settings.ryzen_adj_path');
if (!ryzen_adj_path && isWindows()) {
ryzen_adj_path = getCurrentWorkingDirectory() + "\\bin\\ryzenadj.exe";
}
appendLog(`getRyzenAdjExecutablePath(): "${ryzen_adj_path}"`);
return ryzen_adj_path;
}
/**
* Will fill settings page on render with saved data.
*/
function preFillSettings() {
const ryzen_adj_path = document.getElementById('ryzen_adj_path');
const settings = require('electron-settings');
ryzen_adj_path.value = getRyzenAdjExecutablePath();
document.getElementById('start_at_boot').checked = !!settings.get('settings.start_at_boot');
document.getElementById('apply_last_settings_on_launch').checked = !!settings.get('settings.apply_last_settings_on_launch');
document.getElementById('minimize_to_tray').checked = !!settings.get('settings.minimize_to_tray');
document.getElementById('start_minimized').checked = !!settings.get('settings.start_minimized');
seconds = parseInt(settings.get('settings.reapply_periodically'));
seconds = seconds >= 0 ? seconds : 0;
document.getElementById('reapply_periodically').value = seconds;
document.getElementById('reapply_periodically_range').value = seconds;
}
/**
* Check if ryzenadj executable has been registered and is valid.
* Will display a notification if not.
*
* @return {Boolean} True if ryzenadj executable exists.
*/
function isRyzenAdjPathValid() {
var fs = require('fs');
if (!fs.existsSync(getRyzenAdjExecutablePath())) {
notification('danger', "Path to ryzenadj.exe is wrong, please fix it in settings tab.");
return false;
}
return true;
}
/**
* Will open a dialog to let user choose where is ryzenadj.exe.
*/
function askingForRyzenAdjExecutablePath() {
var remote = require('electron').remote;
var dialog = remote.require('electron').dialog;
dialog.showOpenDialog({
properties: ['openFile']
}, function (filePaths) {
const settings = require('electron-settings');
if (typeof filePaths === 'undefined') {
notification('warning', 'No path given, nothing changed.');
return;
}
if (typeof filePaths[0] === 'undefined') {
notification('warning', 'No path given, nothing changed.');
return;
}
settings.set("settings",
Object.assign(
{},
settings.get('settings'),
{ ryzen_adj_path: filePaths[0] }
)
);
notification('primary', 'Path to ryzenAdj executable has been saved.');
appendLog(`askingForRyzenAdjExecutablePath(): ${filePaths[0]}`);
preFillSettings();
});
}
/**
* Will append logs to the logs tab.
* @param {string} message The message to be logged.
*/
function appendLog(message) {
var log_area = document.getElementById('logs');
log_area.value += message + "\n";
console.log(message);
}
/**
* Will save the latest used settings.
*/
function saveLatestUsedSettings() {
var inputs = document.querySelectorAll('#main-container *[id$="-tab"] input');
var latest_controller_tabs_settings = {};
inputs.forEach(element => {
let id = element.id;
let value = element.value;
if (id.indexOf('apply_') === 0) {
value = element.checked;
}
latest_controller_tabs_settings[id] = value;
});
const settings = require('electron-settings');
let ret = settings.set("latest_controller_tabs_settings", latest_controller_tabs_settings);
appendLog(`saveLatestUsedSettings(): ${JSON.stringify(latest_controller_tabs_settings)}`);
appendLog(`saveLatestUsedSettings(): ${JSON.stringify(ret)}`);
}
/**
* Will load the latest settings and refresh the controller tab's values.
*
* Will also apply latest settings if settings.apply_last_settings_on_launch is true.
*/
function loadLatestUsedSettings() {
const settings = require('electron-settings');
var latest_controller_tabs_settings = settings.get("latest_controller_tabs_settings");
appendLog(`loadLatestUsedSettings(): ${JSON.stringify(latest_controller_tabs_settings)}`);
for (const id in latest_controller_tabs_settings) {
if (latest_controller_tabs_settings.hasOwnProperty(id)) {
const value = latest_controller_tabs_settings[id];
let input = document.getElementById(id);
if (input) {
if (id.indexOf('apply_') === 0) {
input.checked = value;
} else {
input.value = value;
}
}
}
}
if (document.isStarting && settings.get('settings.apply_last_settings_on_launch')) {
applyRyzenSettings();
}
}
/**
* Will show or display options based on apply checkboxes value.
*/
function toggleOptionDisplayBasedOnApplyCheckbox() {
var checkbox_toggle_options = document.querySelectorAll('#main-container *[id$="-tab"] input[id^=apply_]');
const hideOptionBasedOnInput = function (input) {
if (input.checked) {
input.parentElement.parentElement.nextElementSibling.removeAttribute('hidden');
} else {
input.parentElement.parentElement.nextElementSibling.setAttribute('hidden', '');
}
}
Array.from(checkbox_toggle_options).forEach(input => {
hideOptionBasedOnInput(input);
input.addEventListener('change', function(event) {
hideOptionBasedOnInput(input);
});
});
}
/**
* Listen settings tab inputs to save their values.
*/
function registerEventListenerForSettingsInput() {
const settings = require('electron-settings');
toggleOptionDisplayBasedOnApplyCheckbox();
var apply_last_settings_on_launch = document.getElementById('apply_last_settings_on_launch');
apply_last_settings_on_launch.addEventListener('change', function() {
settings.set(
"settings",
Object.assign(
{},
settings.get('settings'),
{ apply_last_settings_on_launch: !!apply_last_settings_on_launch.checked }
)
);
});
var minimize_to_tray = document.getElementById('minimize_to_tray');
minimize_to_tray.addEventListener('change', function() {
settings.set(
"settings",
Object.assign(
{},
settings.get('settings'),
{ minimize_to_tray: !!minimize_to_tray.checked }
)
);
});
var start_minimized = document.getElementById('start_minimized');
start_minimized.addEventListener('change', function() {
settings.set(
"settings",
Object.assign(
{},
settings.get('settings'),
{ start_minimized: !!start_minimized.checked }
)
);
});
var reapply_periodically = document.getElementById('reapply_periodically');
reapply_periodically.addEventListener('change', function() {
reApplyPeriodically(reapply_periodically.value);
settings.set(
"settings",
Object.assign(
{},
settings.get('settings'),
{ reapply_periodically: reapply_periodically.value }
)
);
});
var start_at_boot = document.getElementById('start_at_boot');
start_at_boot.addEventListener('change', function() {
settings.set(
"settings",
Object.assign(
{},
settings.get('settings'),
{ start_at_boot: !!start_at_boot.checked }
)
);
updateScheduledStartOnBoot(!!start_at_boot.checked);
});
}
/**
* Simply display version in appropriate zone.
*/
function displayVersion() {
const pjson = require('./package.json');
document.getElementById('version').innerHTML = `v${pjson.version}${isDevMode() ? '-dev' : ''}`;
}
/**
* Re-apply flow for "reapply_periodically" settings.
* @param {number} seconds Interval in seconds between each apply.
*/
function reApplyPeriodically(seconds) {
seconds = parseInt(seconds) >= 0 ? parseInt(seconds) : 0;
appendLog(`reApplyPeriodically(): seconds = ${seconds}`);
appendLog(`reApplyPeriodically(): document.reapplyLoop = ${document.reapplyLoop}`);
clearInterval(document.reapplyLoop);
document.reapplyLoop = false;
if (seconds <= 0) {
if (!document.isStarting) {
notification('primary', "Ryzen Controller will stop applying RyzenAdj periodically.");
}
return;
}
document.reapplyLoop = setInterval(applyRyzenSettings, seconds * 1000);
}
/**
* Will recreate shortcut on launch ... no other solution for now :(
*/
function recreateShortcut() {
const settings = require('electron-settings');
if (!!settings.get('settings.first_launch')) {
let app = require('electron').remote.app;
let fs = require('fs');
try {
var shortcut_path = app.getPath('desktop') + "\\Ryzen Controller";
fs.unlink(shortcut_path, console.log);
fs.symlink(app.getPath('exe'), shortcut_path, function (err) {
if (err) {
notification("danger", "Shortcut can't be created, please check log tabs for more info.");
appendLog(`recreateShortcut(): ${err}`);
}
else {
notification('primary', "A shortcut has been created on desktop.");
}
});
} catch (error) {
appendLog(`recreateShortcut() ${error}`);
Sentry.captureException(`recreateShortcut() ${error}`);
}
}
}
/**
* Will return an object completed with the current settings from inputs.
* @param {string} keyType "inputId" or "ryzenadjArgs"
*/
function getCurrentSettings(keyType) {
if (keyType === "ryzenadjArgs") {
const options_data = require('./js/options_data.json');
var settingsToBeUsed = {};
for (const elementId in options_data) {
if (options_data.hasOwnProperty(elementId)) {
const optionData = options_data[elementId];
const ryzenadjArg = optionData.ryzenadj_arg;
if (document.getElementById('apply_' + elementId).checked) {
settingsToBeUsed[ryzenadjArg] = document.getElementById(elementId).value;
}
}
}
appendLog('getCurrentSettings(): ' + JSON.stringify(settingsToBeUsed));
return settingsToBeUsed;
} else {
var inputs = document.querySelectorAll('#main-container *[id$="-tab"] input');
var currentSettings = {};
inputs.forEach(element => {
let id = element.id;
let value = element.value;
if (id.indexOf('apply_') === 0) {
value = element.checked;
}
currentSettings[id] = value;
});
return currentSettings;
}
}
/**
* Will check for new release.
*/
function checkForNewRelease() {
var request = new XMLHttpRequest();
const version = require('./package.json').version;
try {
request.open('GET', 'https://gitlab.com/api/v4/projects/11046417/releases', true);
request.onload = function() {
if (this.status >= 200 && this.status < 400) {
var resp = {};
try {
resp = JSON.parse(this.response);
} catch (error) {
console.log('WARNING: unable to check if a new version is available.', error);
return;
}
if (resp[0].tag_name !== version) {
notification('primary', "A new vesion is available, please check the release tab.");
}
}
};
request.send();
} catch (error) {
console.log('Unable to check if new release is available.', error);
}
}
/**
* Will change the BCD entry value for userplatformclock.
*
* @param {string} value BCD entry value for userplatformclock: "true" or "false".
*/
function toggleHpet(value) {
value = value === "true" ? "true" : "false";
const BcdeditExecutablePath = "C:\\Windows\\System32\\bcdedit.exe";
const parameters = [
"/set",
"useplatformclock",
value,
];
const child = require('child_process').execFile;
child(BcdeditExecutablePath, parameters, function(err, data) {
var output = data.toString();
if (err) {
notification('danger', err + '<br/>' + output);
}
else if (output) {
notification('success', 'Bcdedit output:<br/>' + output);
saveLatestUsedSettings();
}
});
}
/**
* Any element containing class "windows-only" will be hidden on other platform.
*/
function handlePlatformSpecificDisplay() {
var windows_only_elements = document.getElementsByClassName('windows-only');
if (!isWindows()) {
for (const item of windows_only_elements) {
item.setAttribute('hidden', 'true');
}
}
}
/**
* Will delete scheduled task to start ryzen controller on session start then recreate it if isEnable is true.
*
* @param {bool} toBeEnabled Is auto launch should be enabled?
*/
function updateScheduledStartOnBoot(toBeEnabled) {
const app = require('electron').remote.app;
window.app = app;
const AutoLaunch = require('auto-launch');
let autoLaunch = new AutoLaunch({
name: 'Ryzen Controller'
});
autoLaunch.isEnabled().then((isEnabled) => {
console.log(`toBeEnabled: ${toBeEnabled} isEnabled: ${isEnabled}`);
try {
if (isEnabled) {
autoLaunch.disable();
}
if (toBeEnabled) {
autoLaunch.enable();
}
} catch (error) {
console.log("WARNING: Unable to manage start on boot.", error);
}
});
}
/**
* Return true if dev mode.
*/
function isDevMode() {
return !require('electron').remote.app.isPackaged;
}
/**
* Will listen for system events and handle status for it.
*/
function handleAcStatusChanges() {
const powerMonitor = require('electron').remote.powerMonitor;
const settings = require('electron-settings');
let applyPresetOnAcStatusChange = function(presetName) {
appendLog(`applyPresetOnAcStatusChange(${presetName})`);
if (!presetName) {
return;
}
preset_apply(presetName);
};
powerMonitor.on('on-ac', () => {
applyPresetOnAcStatusChange(settings.get(`auto-apply.update-ac-plugged-in`));
});
powerMonitor.on('on-battery', () => {
applyPresetOnAcStatusChange(settings.get(`auto-apply.update-ac-plugged-out`));
});
}

View File

@ -1,134 +0,0 @@
{
"slow_ppt_constant_time": {
"description": "This define the period to be used out of boost period to deliver a constant power to be delivered to the socket.",
"label": "Package Power Tracking (PPT) - Slow period",
"tab": "Power Settings",
"min": "1",
"max": "3600",
"step": "1",
"default": "900",
"ryzenadj_arg": "--slow-time=",
"ryzenadj_value_convert": "toThousand"
},
"psi0_current_limit": {
"description": "The limit of current we let the motherboard deliver to the PSI0.",
"label": "PSI0 Current Limit (mA)",
"tab": "Power Settings",
"min": "20",
"max": "100",
"step": "1",
"default": "20",
"ryzenadj_arg": "--psi0-current=",
"ryzenadj_value_convert": "toHex"
},
"vrm_current_m_a": {
"description": "The limit of current we let the motherboard deliver to the CPU.",
"label": "VRM Current (A)",
"tab": "Power Settings",
"min": "20",
"max": "75",
"step": "1",
"default": "30",
"ryzenadj_arg": "--vrmmax-current=",
"ryzenadj_value_convert": "toHex"
},
"min_gfxclk_frequency": {
"description": "The minimum clock speed the integrated (Vega) GPU is allowed to run at.",
"label": "Minimum Vega iGPU Clock Frequency (Mhz)",
"tab": "GPU Settings",
"min": "400",
"max": "1300",
"step": "1",
"default": "400",
"ryzenadj_arg": "--min-gfxclk=",
"ryzenadj_value_convert": "roundTen"
},
"max_gfxclk_frequency": {
"description": "The maximum clock speed the integrated (Vega) GPU is allowed to run at.",
"label": "Maximum Vega iGPU Clock Frequency (Mhz)",
"tab": "GPU Settings",
"min": "400",
"max": "1300",
"step": "1",
"default": "1100",
"ryzenadj_arg": "--max-gfxclk=",
"ryzenadj_value_convert": "roundTen"
},
"min_fclk_frequency": {
"description": "Infinity Fabric is AMD's marketing term for the bus connection that connects processor dies (GPU/CPU). This define the bus's min. clock limit.",
"label": "Minimum Infinity Fabric frequency (Mhz)",
"tab": "GPU Settings",
"min": "800",
"max": "1600",
"step": "1",
"default": "800",
"ryzenadj_arg": "--min-fclk-frequency=",
"ryzenadj_value_convert": false
},
"max_fclk_frequency": {
"description": "Infinity Fabric is AMD's marketing term for the bus connection that connects processor dies (GPU/CPU). This define the bus's max. clock limit.",
"label": "Maximum Infinity Fabric frequency (Mhz)",
"tab": "GPU Settings",
"min": "800",
"max": "1600",
"step": "1",
"default": "1200",
"ryzenadj_arg": "--max-fclk-frequency=",
"ryzenadj_value_convert": false
},
"temperature_limit_c": {
"description": "The temperature the CPU can reach before boost levels off.",
"label": "Temperature Limit (°C)",
"tab": "CPU Settings",
"min": "50",
"max": "100",
"step": "1",
"default": "75",
"ryzenadj_arg": "--tctl-temp=",
"ryzenadj_value_convert": false
},
"stapm_limit_w": {
"description": "Skin Temperature Aware Power Management. This will define the socket power package limit which is used to manage the device boost period.",
"label": "CPU TDP (W)",
"tab": "CPU Settings",
"min": "5",
"max": "60",
"step": "1",
"default": "20",
"ryzenadj_arg": "--stapm-limit=",
"ryzenadj_value_convert": "toThousand"
},
"stapm_time_ms": {
"description": "Skin Temperature Aware Power Management. This will define the boost period to be used.",
"label": "CPU Boost Period",
"tab": "CPU Settings",
"min": "1",
"max": "3600",
"step": "1",
"default": "900",
"ryzenadj_arg": "--stapm-time=",
"ryzenadj_value_convert": "toThousand"
},
"ppt_fast_limit_w": {
"description": "The amount of power the CPU can draw while boost levels on.",
"label": "CPU Boost TDP (W)",
"tab": "CPU Settings",
"min": "5",
"max": "60",
"step": "1",
"default": "25",
"ryzenadj_arg": "--fast-limit=",
"ryzenadj_value_convert": "toThousand"
},
"ppt_slow_limit_w": {
"description": "The amount of power the CPU can draw while boost levels off.",
"label": "CPU Min TDP (W)",
"tab": "CPU Settings",
"min": "5",
"max": "60",
"step": "1",
"default": "10",
"ryzenadj_arg": "--slow-limit=",
"ryzenadj_value_convert": "toThousand"
}
}

View File

@ -1,278 +0,0 @@
/**
* Will fill the export preset modal textarea with the preset data.
*/
function preset_export() {
const modalTextArea = document.getElementById('modal-export-preset-textarea');
const settings = require('electron-settings');
var presets = settings.get('presets');
presets = JSON.stringify(presets);
modalTextArea.innerHTML = btoa(unescape(encodeURIComponent(presets)));
}
/**
* Will import the preset from the export preset modal textarea.
*/
function preset_import() {
const modalTextArea = document.getElementById('modal-import-preset-textarea');
const settings = require('electron-settings');
var currentPresets = settings.get('presets');
var presetsToBeImported;
try {
presetsToBeImported = decodeURIComponent(escape(atob(modalTextArea.value)));
presetsToBeImported = JSON.parse(presetsToBeImported);
} catch (e) {
notification('danger', 'Unable to import presets, malformed data.');
appendLog(`preset_import() ${e}`);
return;
}
var updatedPresets = Object.assign(
{},
currentPresets,
presetsToBeImported
);
settings.set('presets', updatedPresets);
preset_updateList();
modalTextArea.innerText = '';
}
/**
* Will save the current settings to a new preset.
*/
function preset_createNewPreset() {
const settingsToBeSaved = getCurrentSettings("inputId");
const currentPresets = require('electron-settings').get('presets') || {};
var newPresetName = document.getElementById('new_preset_name').value;
if (!newPresetName) {
notification('danger', 'You must provide a preset name.');
return;
}
if (typeof currentPresets[newPresetName] !== "undefined") {
newPresetName = preset_findUnusedPresetName(newPresetName);
notification('warning', `This preset name already exist, your preset has been saved with the name "${newPresetName}".`);
}
const newPresetList = Object.assign(
{},
currentPresets,
{ [newPresetName]: settingsToBeSaved }
);
require('electron-settings').set('presets', newPresetList);
appendLog(`preset_createNewPreset(): Saved preset ${newPresetName}, ${JSON.stringify(newPresetList)}`);
preset_updateList();
if (newPresetName === document.getElementById('new_preset_name').value) {
notification('success', `The preset ${newPresetName} has been saved.`);
}
}
/**
* This recursive function will return an available preset name to be used to save a preset.
*
* @param {string} newPresetName The preset name to be edited.
* @param {number} suffix The preset name suffix
*/
function preset_findUnusedPresetName(newPresetName, suffix = 1) {
const currentPresets = require('electron-settings').get('presets') || {};
if (typeof currentPresets[`${newPresetName}${suffix}`] !== "undefined") {
suffix++;
return preset_findUnusedPresetName(newPresetName, suffix);
}
return `${newPresetName}${suffix}`;
}
/**
* Will save the preset to be enabled on AC plugged out.
*/
function preset_enableAutoApplyOnAcStatusChange(statusName, presetName) {
const settings = require('electron-settings');
const status = {
"update-ac-plugged-in": `will be applied on AC plugged in.`,
"update-ac-plugged-out": `will be applied on AC plugged out.`,
};
if (typeof status[statusName] === "undefined") {
let message = `Error while updating auto apply on AC status change.`;
notification('danger', message);
console.log(`preset_enableAutoApplyOnAcStatusChange(statusName:"${statusName}", presetName:"${presetName}")`);
Sentry.captureException(new Error(message));
return;
}
settings.set(`auto-apply.${statusName}`, presetName);
if (presetName) {
notification('primary', `Preset "${presetName}" ${status[statusName]}`);
} else {
notification('primary', `No preset ${status[statusName]}`);
}
}
/**
* This will update the preset tab based on saved presets.
*/
function preset_updateList() {
var presetTab = document.getElementById('presetTab');
const currentPresets = require('electron-settings').get('presets') || {};
var content = '';
content += /*html*/`
<table class="uk-table uk-table-striped uk-table-hover uk-table-middle">
<thead>
<tr>
<th>Name</th>
<th colspan="2">Apply on</th>
<th>Action</th>
</tr>
</thead>
<tbody>
`;
if (Object.keys(currentPresets).length === 0) {
content += /*html*/`<li>No preset has been created yet, import them or use the "Save to preset" button on Controller tab to create one.</li>`;
}
for (const presetName in currentPresets) {
if (currentPresets.hasOwnProperty(presetName)) {
const preset = currentPresets[presetName];
let valueSummary = [];
for (const key in preset) {
if (preset.hasOwnProperty(key) && key.indexOf('_range') !== -1 && key.indexOf('apply_') != 0) {
const value = preset[key];
valueSummary.push(value);
}
}
valueSummary.join(', ');
content += /*html*/`
<tr class="uk-margin">
<td class="preset">
<span class="uk-text-lead">${presetName}</span>
<i class="uk-text-small">${valueSummary}</i>
</td>
<td class="uk-table-expand preset">
<label style="cursor: pointer;">
<input
value="${presetName}"
class="uk-radio onAcStatusChange"
type="radio"
name="update-ac-plugged-in"
${presetName === require('electron-settings').get('auto-apply.update-ac-plugged-in') ? 'checked' : ''}
/>
AC&nbsp;plugged&nbsp;in
</label>
</td>
<td class="uk-table-expand preset">
<label style="cursor: pointer;">
<input
value="${presetName}"
class="uk-radio onAcStatusChange"
type="radio"
name="update-ac-plugged-out"
${presetName === require('electron-settings').get('auto-apply.update-ac-plugged-out') ? 'checked' : ''}
/>
AC&nbsp;plugged&nbsp;out
</label>
</td>
<td class="preset">
<button class="uk-button uk-button-danger delete" type="button" onClick="preset_deletion('${presetName}')">
Delete
</button>
<button class="uk-button uk-button-primary" type="button" onClick="preset_apply('${presetName}')">
Apply
</button>
</td>
</tr>
`;
}
}
content += /*html*/`
<tr class="autoapply">
<td><span class="uk-align-right">Disable auto apply</span></td>
<td>
<label style="cursor: pointer;">
<input
value=""
class="uk-radio onAcStatusChange"
type="radio"
name="update-ac-plugged-in"
${!require('electron-settings').get('auto-apply.update-ac-plugged-in') ? 'checked' : ''}
/>
On&nbsp;charging
</label>
</td>
<td>
<label style="cursor: pointer;">
<input
value=""
class="uk-radio onAcStatusChange"
type="radio"
name="update-ac-plugged-out"
${!require('electron-settings').get('auto-apply.update-ac-plugged-out') ? 'checked' : ''}
/>
On&nbsp;discharging
</label>
</td>
<td></td>
</tr>
<tbody>
</table>
`;
presetTab.innerHTML = content;
var acStatusChangeRadios = document.querySelectorAll('.onAcStatusChange');
Array.from(acStatusChangeRadios).forEach(radio => {
radio.addEventListener('click', function(event) {
preset_enableAutoApplyOnAcStatusChange(this.name, this.value);
});
});
}
/**
* Will check if the preset exists.
*
* @param {String} name The preset name to look for.
*/
function preset_isExist(name) {
const preset = require('electron-settings').get(`presets`)[name];
return !!preset;
}
/**
* This will apply the preset you asked for.
* @param {string} presetName The preset name to be applied.
*/
function preset_apply(presetName) {
if (!preset_isExist(presetName)) {
notification('danger', `Unable to apply unexisting preset "${presetName}".`);
return;
}
const preset = require('electron-settings').get(`presets`)[presetName];
appendLog(`preset_apply(): preset ${presetName}: ${JSON.stringify(preset)}`);
var ret = require('electron-settings').set("latest_controller_tabs_settings", preset);
appendLog(`preset_apply(): saved preset: ${JSON.stringify(ret)}`);
loadLatestUsedSettings();
applyRyzenSettings();
toggleOptionDisplayBasedOnApplyCheckbox();
}
/**
* This will delete the preset you asked for.
* @param {string} presetName The preset name to be deleted.
*/
function preset_deletion(presetName) {
var presets = require('electron-settings').get(`presets`);
delete presets[presetName];
require('electron-settings').set(`presets`, presets);
notification('success', `The preset ${presetName} has been deleted.`);
preset_updateList();
}

249
main.js
View File

@ -1,249 +0,0 @@
// Handle setupevents as quickly as possible
const setupEvents = require('./installers/setupEvents')
// squirrel event handled and app will exit in 1000ms, so don't do anything else
if (!setupEvents.handleSquirrelEvent()) {
// Modules to control application life and create native browser window
const {app, BrowserWindow, Menu, Tray} = require('electron')
const settings = require('electron-settings');
// Check for latest used version and clear settings if needed.
const old_version = settings.get('settings.last_used_version');
const new_version = require('./package.json').version;
if (old_version && old_version !== new_version) {
var compareVersions = require('compare-versions');
/**
* Since 1.4.0, ryzenadj is included in the windows package.
* So we are removing ryzenadj path as it can be included.
*/
if (compareVersions(old_version, '1.4.0') <= 0) {
settings.delete('settings.ryzen_adj_path');
}
/**
* Since 1.11.0 we added new settings and apply checkbox,
* We need to add new settings to presets.
*/
if (compareVersions(old_version, '1.11.0') <= 0) {
const update_latest_settings_to_1_11_0 = function(settings) {
var updated_settings = {};
for (const setting_name in settings) {
if (settings.hasOwnProperty(setting_name)) {
const setting_value = settings[setting_name];
// Register current setting.
updated_settings[setting_name] = setting_value;
// Add apply checkbox to any non-range settings.
if (setting_name.indexOf('_range') <= 0) {
if (setting_name.indexOf('apply_') <= 0) {
updated_settings[`apply_${setting_name}`] = true;
continue;
}
}
}
}
// Adding missing options.
updated_settings['apply_stapm_time_ms'] = false;
updated_settings['apply_psi0_current_limit'] = false;
return updated_settings;
};
const update_presets_to_1_11_0 = function(preset_list) {
var updated_preset_list = {};
// For each preset.
for (const preset_name in preset_list) {
if (preset_list.hasOwnProperty(preset_name)) {
const preset_settings = preset_list[preset_name];
updated_preset_list[preset_name] = {};
// For each setting.
for (const setting_name in preset_settings) {
if (preset_settings.hasOwnProperty(setting_name)) {
const setting_value = preset_settings[setting_name];
// Register current setting.
updated_preset_list[preset_name][setting_name] = setting_value;
if (setting_name.indexOf('_range') <= 0) {
continue;
}
if (setting_name.indexOf('apply_') <= 0) {
// Add apply checkbox.
updated_preset_list[preset_name][`apply_${setting_name}`] = true;
continue;
}
}
}
// Adding missing options.
updated_preset_list[preset_name]['apply_stapm_time_ms'] = false;
updated_preset_list[preset_name]['apply_psi0_current_limit'] = false;
}
}
return updated_preset_list;
};
settings.set('presets', update_presets_to_1_11_0(settings.get('presets')));
settings.set('latest_controller_tabs_settings', update_latest_settings_to_1_11_0(settings.get('latest_controller_tabs_settings')));
}
/**
* Since 1.12.0, new option to ryzenadj.
*/
if (compareVersions(old_version, '1.12.0') <= 0) {
const update_preset_to_1_12_0 = function(settings) {
// Adding missing options.
settings['apply_max_gfxclk_frequency'] = false;
settings['apply_min_gfxclk_frequency'] = false;
settings['apply_min_socclk_frequency'] = false;
settings['apply_max_socclk_frequency'] = false;
return settings;
};
const update_presets_to_1_12_0 = function(preset_list) {
// For each preset.
for (const preset_name in preset_list) {
if (preset_list.hasOwnProperty(preset_name)) {
preset_list[preset_name] = update_preset_to_1_12_0(preset_list[preset_name]);
}
}
return preset_list;
};
settings.set('presets', update_presets_to_1_12_0(settings.get('presets')));
settings.set('latest_controller_tabs_settings', update_preset_to_1_12_0(settings.get('latest_controller_tabs_settings')));
}
/**
* Since 1.14.0, login managed through s=windows scheduler
*/
if (compareVersions(old_version, '1.14.0') <= 0) {
// Ensure login on start false.
app.setLoginItemSettings({ openAtLogin: false });
}
settings.set('settings',
Object.assign(
{},
settings.get('settings'),
{
last_used_version: require('./package.json').version,
first_launch: true,
}
)
);
}
if (!old_version) {
settings.set('settings',
Object.assign(
{},
settings.get('settings'),
{
last_used_version: require('./package.json').version,
first_launch: true,
}
)
);
}
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
let tray
function createWindow () {
let appIcon = '';
if (require('os').platform() === 'win32') {
appIcon = __dirname + '/assets/icon.ico';
} else {
appIcon = __dirname + '/assets/icon.png';
}
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1000,
height: 600,
minWidth: 950,
minHeight: 400,
frame: false,
webPreferences: {
nodeIntegration: true,
webviewTag: true,
},
icon: appIcon,
show: false,
});
mainWindow.setMenuBarVisibility(false);
mainWindow.setResizable(true);
if (!settings.get('settings.start_minimized')) {
mainWindow.show();
mainWindow.setMenuBarVisibility(false);
}
mainWindow.setOpacity(0.98);
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
mainWindow.on('minimize',function(event){
if (settings.get('settings.minimize_to_tray')) {
event.preventDefault();
mainWindow.hide();
}
});
var contextMenu = Menu.buildFromTemplate([
{
label: 'Show App',
click: function () {
mainWindow.show();
}
},
{
label: 'Quit',
click: function () {
app.isQuiting = true;
app.quit();
}
}
]);
tray = new Tray(appIcon);
tray.setContextMenu(contextMenu);
tray.setIgnoreDoubleClickEvents(true);
tray.on('click', function() {
mainWindow.show();
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
}

2521
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,42 +1,150 @@
{ {
"name": "ryzencontroller", "name": "ryzen-controller",
"productName": "RyzenController", "version": "2.0.0",
"version": "1.17.0",
"description": "A minimal Electron application to use ryzenAdj through a friendly interface.", "description": "A minimal Electron application to use ryzenAdj through a friendly interface.",
"main": "main.js",
"scripts": { "scripts": {
"start": "electron .", "all": "yarn clean && yarn dist-pack",
"package-win32": "electron-packager . --overwrite --platform=win32 --arch=ia32 --prune=true --out=release-builds --ignore=\".*-builds\" --icon=\"assets/icon.ico\" --win32metadata.requested-execution-level=requireAdministrator", "1______________________________________________________________": "echo Basic scripts",
"package-linux": "electron-packager . ryzencontroller --overwrite --platform=linux --arch=x64 --out release-builds --ignore=\".*-builds\" --icon=\"assets/icon.ico\"", "build": "react-scripts build",
"build-exe": "node installers/windows/createinstaller.js", "test": "react-scripts test",
"build-deb": "electron-installer-debian --src=release-builds/ryzencontroller-linux-x64/ --dest=installer-builds/installers/ --arch=amd64", "lint": "prettier --write src/**/*.{js,jsx,ts,tsx,json,css,scss,md}",
"build-rpm": "electron-installer-redhat --src=release-builds/ryzencontroller-linux-x64/ --dest=installer-builds/installers/ --arch=amd64" "2______________________________________________________________": "echo Cleaning scripts",
"clean": "yarn clean:dist && yarn clean:node && yarn clean:build && yarn clean:yarn",
"clean:dist": "rm -rf dist",
"clean:node": "rm -rf node_modules/* node_modules/.bin node_modules/.cache node_modules/.yarn-integrity",
"clean:build": "rm -rf build",
"clean:yarn": "yarn install --frozen-lockfile",
"3______________________________________________________________": "echo Start dev env",
"start": "run-p start:*",
"start:electron": "wait-on http://localhost:3000/ && electron --no-sandbox --enable-transparent-visuals .",
"start:react": "react-scripts start",
"4______________________________________________________________": "echo To create unpacked app",
"dist-unpack": "run-s dist-unpack:*",
"dist-unpack:build": "yarn build",
"dist-unpack:electron": "electron-builder --dir",
"5______________________________________________________________": "echo To create packed app",
"dist-pack": "run-s dist-pack:*",
"dist-pack:build": "yarn build",
"dist-pack:all": "electron-builder -wl",
"dist-pack-win": "electron-builder -w",
"dist-pack-linux": "electron-builder -l",
"6______________________________________________________________": "echo Yarn scripts into docker (for linux)",
"docker": "bash -c \"docker run --rm -ti -v node_modules:/project/node_modules -v /${PWD}:/project storm1er/electron-builder-wine-dubnium:1.0.2 bash\"",
"docker-permission-fix": "docker run --rm -ti -v ${PWD}:/project storm1er/electron-builder-wine-dubnium:1.0.2 bash -c \"chown `id -u`:`id -g` -R .\""
}, },
"repository": "https://gitlab.com/le.storm1er/ryzen-controller", "main": "public/electron.js",
"private": false,
"author": {
"name": "Ryzen Controller Team",
"email": "incoming+ryzen-controller-team-ryzen-controller-11046417-issue-@incoming.gitlab.com",
"url": "https://gitlab.com/ryzen-controller-team/ryzen-controller/"
},
"homepage": "./",
"email": "quentin.decaunes@gmail.com",
"keywords": [ "keywords": [
"Controller", "Controller",
"Mobile", "Mobile",
"Ryzen", "Ryzen",
"Overclock" "Overclock"
], ],
"author": "le.storm1er",
"license": "CC0-1.0", "license": "CC0-1.0",
"optionalDependencies": { "repository": "https://gitlab.com/ryzen-controller-team/ryzen-controller",
"electron-installer-debian": "^1.1.1", "dependencies": {
"electron-installer-redhat": "^1.0.1", "@testing-library/jest-dom": "^4.2.4",
"electron-winstaller": "^3.0.4" "@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^7.2.1",
"@trodi/electron-splashscreen": "^1.0.0",
"@types/jest": "^24.0.24",
"@types/node": "^13.1.0",
"@types/react": "^16.9.17",
"@types/react-dom": "^16.9.4",
"@types/react-router-dom": "^5.1.3",
"@types/webpack-env": "^1.14.1",
"auto-launch": "^5.0.5",
"compare-versions": "^3.5.1",
"electron-is-dev": "^1.1.0",
"electron-react-devtools": "^0.5.3",
"electron-settings": "^3.2.0",
"fibers": "^4.0.2",
"husky": "^3.1.0",
"lint-staged": "^9.5.0",
"node-sass": "^4.13.0",
"object-hash": "^2.0.1",
"prettier": "^1.19.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.0",
"sass": "^1.24.0",
"systeminformation": "^4.16.1",
"typescript": "^3.7.4",
"uikit": "^3.2.6",
"uuidv4": "^6.0.0",
"windows-scheduler": "https://github.com/Ryzen-Controller-Team/windows-scheduler.git#1e2cc67db8efb1474ba0fe780967f86ed68f36a3"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}, },
"devDependencies": { "devDependencies": {
"electron": "^5.0.2", "electron": "^7.1.7",
"electron-packager": "^13.1.0" "electron-builder": "20.*",
"npm-run-all": "^4.1.5",
"wait-on": "^3.3.0"
}, },
"dependencies": { "build": {
"auto-launch": "^5.0.5", "appId": "ryzen-team.app.ryzen-controller",
"compare-versions": "^3.4.0", "productName": "Ryzen Controller",
"electron-settings": "^3.2.0", "icon": "./build/icon.ico",
"fix-path": "^2.1.0", "asarUnpack": [
"jquery": "^3.4.1", "**/build/bin/*"
"uikit": "^3.1.5", ],
"uuid": "^3.3.2" "linux": {
"category": "Utility",
"icon": "./build/icon.png",
"target": [
{
"target": "deb"
},
{
"target": "rpm"
}
]
},
"nsis": {
"oneClick": false,
"perMachine": true,
"allowToChangeInstallationDirectory": true
},
"win": {
"requestedExecutionLevel": "requireAdministrator"
}
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
"prettier --write",
"git add"
]
},
"prettier": {
"trailingComma": "es5",
"tabWidth": 2,
"printWidth": 120
} }
} }

View File

@ -0,0 +1 @@
"%~dp0..\..\..\elevate.exe" "%~dp0..\..\..\..\Ryzen Controller.exe"

View File

@ -1,2 +1,2 @@
%~dp0\ryzenadj.exe --stapm-limit=40000 --fast-limit=45000 --slow-limit=45000 --tctl-temp=90 %~dp0\ryzenadj.exe --stapm-limit=40000 --fast-limit=45000 --slow-limit=45000 --tctl-temp=90
pause pause

View File

@ -1,60 +1,60 @@
/* SPDX-License-Identifier: LGPL */ /* SPDX-License-Identifier: LGPL */
/* Copyright (C) 2019 Jiaxun Yang <jiaxun.yang@flygoat.com> */ /* Copyright (C) 2019 Jiaxun Yang <jiaxun.yang@flygoat.com> */
/* RyzenAdj API */ /* RyzenAdj API */
#ifndef RYZENADJ_H #ifndef RYZENADJ_H
#define RYZENADJ_H #define RYZENADJ_H
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#include "nb_smu_ops.h" #include "nb_smu_ops.h"
#ifdef _WIN32 #ifdef _WIN32
#define EXP __declspec(dllexport) #define EXP __declspec(dllexport)
#define CALL __stdcall #define CALL __stdcall
#else #else
#define EXP #define EXP
#define CALL #define CALL
#endif #endif
#define RYZENADJ_VER 5 #define RYZENADJ_VER 5
typedef struct { typedef struct {
nb_t nb; nb_t nb;
pci_obj_t pci_obj; pci_obj_t pci_obj;
smu_t mp1_smu; smu_t mp1_smu;
smu_t psmu; smu_t psmu;
} *ryzen_access; } *ryzen_access;
EXP ryzen_access CALL init_ryzenadj(); EXP ryzen_access CALL init_ryzenadj();
EXP void CALL cleanup_ryzenadj(ryzen_access ry); EXP void CALL cleanup_ryzenadj(ryzen_access ry);
EXP int CALL set_stapm_limit(ryzen_access, uint32_t value); EXP int CALL set_stapm_limit(ryzen_access, uint32_t value);
EXP int CALL set_fast_limit(ryzen_access, uint32_t value); EXP int CALL set_fast_limit(ryzen_access, uint32_t value);
EXP int CALL set_slow_limit(ryzen_access, uint32_t value); EXP int CALL set_slow_limit(ryzen_access, uint32_t value);
EXP int CALL set_slow_time(ryzen_access, uint32_t value); EXP int CALL set_slow_time(ryzen_access, uint32_t value);
EXP int CALL set_stapm_time(ryzen_access, uint32_t value); EXP int CALL set_stapm_time(ryzen_access, uint32_t value);
EXP int CALL set_tctl_temp(ryzen_access, uint32_t value); EXP int CALL set_tctl_temp(ryzen_access, uint32_t value);
EXP int CALL set_vrm_current(ryzen_access, uint32_t value); EXP int CALL set_vrm_current(ryzen_access, uint32_t value);
EXP int CALL set_vrmsoc_current(ryzen_access, uint32_t value); EXP int CALL set_vrmsoc_current(ryzen_access, uint32_t value);
EXP int CALL set_vrmmax_current(ryzen_access, uint32_t value); EXP int CALL set_vrmmax_current(ryzen_access, uint32_t value);
EXP int CALL set_vrmsocmax_current(ryzen_access, uint32_t value); EXP int CALL set_vrmsocmax_current(ryzen_access, uint32_t value);
EXP int CALL set_psi0_current(ryzen_access, uint32_t value); EXP int CALL set_psi0_current(ryzen_access, uint32_t value);
EXP int CALL set_psi0soc_current(ryzen_access, uint32_t value); EXP int CALL set_psi0soc_current(ryzen_access, uint32_t value);
EXP int CALL set_max_gfxclk_freq(ryzen_access, uint32_t value); EXP int CALL set_max_gfxclk_freq(ryzen_access, uint32_t value);
EXP int CALL set_min_gfxclk_freq(ryzen_access, uint32_t value); EXP int CALL set_min_gfxclk_freq(ryzen_access, uint32_t value);
EXP int CALL set_max_socclk_freq(ryzen_access, uint32_t value); EXP int CALL set_max_socclk_freq(ryzen_access, uint32_t value);
EXP int CALL set_min_socclk_freq(ryzen_access, uint32_t value); EXP int CALL set_min_socclk_freq(ryzen_access, uint32_t value);
EXP int CALL set_max_fclk_freq(ryzen_access, uint32_t value); EXP int CALL set_max_fclk_freq(ryzen_access, uint32_t value);
EXP int CALL set_min_fclk_freq(ryzen_access, uint32_t value); EXP int CALL set_min_fclk_freq(ryzen_access, uint32_t value);
EXP int CALL set_max_vcn(ryzen_access, uint32_t value); EXP int CALL set_max_vcn(ryzen_access, uint32_t value);
EXP int CALL set_min_vcn(ryzen_access, uint32_t value); EXP int CALL set_min_vcn(ryzen_access, uint32_t value);
EXP int CALL set_max_lclk(ryzen_access, uint32_t value); EXP int CALL set_max_lclk(ryzen_access, uint32_t value);
EXP int CALL set_min_lclk(ryzen_access, uint32_t value); EXP int CALL set_min_lclk(ryzen_access, uint32_t value);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif #endif

135
public/electron.js Normal file
View File

@ -0,0 +1,135 @@
const Splashscreen = require("@trodi/electron-splashscreen");
const electron = require("electron");
const path = require("path");
const isDev = require("electron-is-dev");
const electronSettings = require("electron-settings");
const app = electron.app;
const Tray = electron.Tray;
const Menu = electron.Menu;
const app_version_as_string = app.getVersion().replace(/\./g, "_") + (isDev ? "-dev" : "");
let mainWindow;
let tray;
const currentSettings = () => {
const localStorage = electronSettings.get(app_version_as_string);
if (localStorage) {
return localStorage.settings;
}
return false;
};
const createWindow = () => {
let appIcon = "";
if (require("os").platform() === "win32") {
appIcon = __dirname + "/icon.ico";
} else {
appIcon = __dirname + "/icon.png";
}
const mainWindowOpt = {
width: 900,
height: 680,
webPreferences: {
nodeIntegration: true,
sandbox: false,
},
icon: appIcon,
show: false,
transparent: true,
};
let showOnStart = true;
if (currentSettings()) {
if (currentSettings().minimizeOnLaunch === true) {
showOnStart = false;
}
}
const splashTimeOut = 4000;
if (showOnStart) {
mainWindow = Splashscreen.initSplashScreen({
windowOpts: mainWindowOpt,
templateUrl: path.join(__dirname, "..", isDev ? "public" : "build", "splash.html"),
delay: 0,
minVisible: splashTimeOut,
splashScreenOpts: {
height: 900,
width: 900,
transparent: true,
},
});
} else {
mainWindow = new electron.BrowserWindow(mainWindowOpt);
mainWindow.hide();
}
mainWindow.setMenuBarVisibility(false);
mainWindow.loadURL(isDev ? "http://localhost:3000" : `file://${path.join(__dirname, "../build/index.html")}`);
var contextMenu = Menu.buildFromTemplate([
{
label: "Show App",
click: function() {
mainWindow.show();
},
},
{
label: "Quit",
click: function() {
app.isQuiting = true;
app.quit();
},
},
]);
tray = new Tray(appIcon);
tray.setContextMenu(contextMenu);
tray.setIgnoreDoubleClickEvents(true);
tray.on("click", function() {
mainWindow.show();
});
mainWindow.on("minimize", function(event) {
if (currentSettings()) {
if (currentSettings().minimizeToTray) {
event.preventDefault();
// Weird behavior on linux where the "minimize" event is triggered when re-opening the app from tray icon.
setTimeout(() => {
mainWindow.show();
mainWindow.hide();
}, 100);
}
}
});
mainWindow.on("closed", () => (mainWindow = null));
app.getWindow = function() {
return mainWindow;
};
};
let isPrimaryInstance = app.requestSingleInstanceLock();
if (!isPrimaryInstance) {
app.quit();
}
app.on("second-instance", () => {
mainWindow.show();
mainWindow.focus();
});
app.on("ready", () => setTimeout(createWindow, 400));
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (mainWindow === null) {
createWindow();
}
});

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
public/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

38
public/index.html Normal file
View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en" style="min-height: 100%;">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Ryzen Controller</title>
</head>
<body style="min-height:100vh;">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

25
public/manifest.json Normal file
View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

6
public/splash.html Normal file
View File

@ -0,0 +1,6 @@
<html style="margin:0 0 0 0;padding:0 0 0 0;">
<head></head>
<body style="margin:0 0 0 0;padding:0 0 0 0;">
<img style="margin:0 0 0 0;padding:0 0 0 0;" width="900px" height="900px" src="./splash.png"/>
</body>
</html>

BIN
public/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

9
src/App.test.tsx Normal file
View File

@ -0,0 +1,9 @@
import React from "react";
import { render } from "@testing-library/react";
import App from "./App";
test("renders learn react link", () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

130
src/App.tsx Normal file
View File

@ -0,0 +1,130 @@
import * as React from "react";
import TopBar from "./components/TopBar";
import SceneSelector from "./components/SceneSelector";
import Scene from "./scenes/Scene";
import { HashRouter as Router } from "react-router-dom";
import SysInfoContext, { createMachineSignature, SysInfoState } from "./contexts/SysInfoContext";
import LightModeContext from "./contexts/LightModeContext";
import { checkNewVersion } from "./contexts/RyzenControllerAppContext";
import LocaleContext, { getTranslation } from "./contexts/LocaleContext";
import LocaleSelectorModal from "./components/LocaleSelectorModal";
const si = window.require("systeminformation");
type AppState = {
sysinfo: SysInfoState;
lightMode: {
mode: "light" | "dark";
switch(): void;
};
locale: {
is: AvailableLanguages;
change(to: AvailableLanguages): void;
getTranslation(id: string, fallback?: string, variables?: Record<string, string>): string;
};
};
class App extends React.Component<{}, AppState> {
_isMounted = false;
_defaultSysinfo: SysInfoState = {
cpu: false,
graphics: false,
mem: false,
memLayout: false,
system: false,
bios: false,
signature: false,
};
state = {
sysinfo: this._defaultSysinfo,
lightMode: {
mode: this.getLatestLightMode(),
switch: this.switchLightMode.bind(this),
},
locale: {
is: this.getLatestLocale(),
change: this.changeLocale.bind(this),
getTranslation: getTranslation,
},
};
switchLightMode(): void {
let lightMode = window.require("electron-settings").get("lightMode") || "light";
lightMode = lightMode === "light" ? "dark" : "light";
window.require("electron-settings").set("lightMode", lightMode);
this.setState({
lightMode: {
mode: lightMode,
switch: this.switchLightMode.bind(this),
},
});
}
changeLocale(to: AvailableLanguages): void {
window.require("electron-settings").set("locale", to);
window.location.reload();
}
getLatestLightMode(): "light" | "dark" {
return window.require("electron-settings").get("lightMode") || "light";
}
getLatestLocale(): AvailableLanguages {
return window.require("electron-settings").get("locale") || "en";
}
componentDidMount() {
this._isMounted = true;
si.getAllData()
.then((data: SysInfoState) => {
data.signature = createMachineSignature(data);
this._isMounted && this.setState({ sysinfo: data });
})
.catch((error: string) => {
this._isMounted &&
this.setState({
sysinfo: {
...this.state.sysinfo,
error: error,
},
});
});
checkNewVersion();
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const classes =
this.state.lightMode.mode === "light" ? "uk-dark uk-background-default" : "uk-light uk-background-secondary";
const body = window.document.getElementsByTagName("body").item(0);
if (body) {
body.className = classes;
}
return (
<div className={classes}>
<LocaleContext.Provider value={this.state.locale}>
<SysInfoContext.Provider value={this.state.sysinfo}>
<LightModeContext.Provider value={this.state.lightMode}>
<Router>
<div className="uk-card uk-margin-bottom">
<TopBar />
<SceneSelector />
</div>
<Scene />
</Router>
</LightModeContext.Provider>
</SysInfoContext.Provider>
<LocaleSelectorModal />
</LocaleContext.Provider>
</div>
);
}
}
export default App;

BIN
src/assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

29
src/components/Badge.tsx Normal file
View File

@ -0,0 +1,29 @@
import * as React from "react";
const style: React.CSSProperties = {
cursor: "pointer",
color: "#FFFFFF",
};
type BadgeProps = {
value: string | React.ReactNode;
onClick?(e: React.MouseEvent<HTMLElement>): void;
className?: string;
background?: string | number;
color?: string;
};
const Badge: React.SFC<BadgeProps> = props => {
let _style = { ...style };
if (props.background) {
_style.background = props.background;
}
return (
<span style={_style} className={`uk-badge ${props.className}`} onClick={props.onClick}>
{props.value}
</span>
);
};
export default Badge;

31
src/components/Card.tsx Normal file
View File

@ -0,0 +1,31 @@
import * as React from "react";
import LightModeContext from "../contexts/LightModeContext";
type CardProps = {
title: string;
children: React.ReactNode;
type?: "small" | "normal";
};
function Card(props: CardProps) {
const type = props.type || "normal";
return (
<LightModeContext.Consumer>
{lm => {
const defaultClasses =
"uk-margin-top uk-margin-small-right uk-margin-small-left uk-card uk-card-default uk-card-body";
const lightClasses = lm.mode === "light" ? "uk-dark uk-background-default" : "uk-light uk-background-primary";
const typeClasses = type === "normal" ? "" : "uk-padding-small";
return (
<div className={`${defaultClasses} ${lightClasses} ${typeClasses}`}>
<h3 className="uk-text-nowrap uk-card-title">{props.title}</h3>
<div>{props.children}</div>
</div>
);
}}
</LightModeContext.Consumer>
);
}
export default Card;

View File

@ -0,0 +1,30 @@
import * as React from "react";
import LocaleContext, { getTranslation } from "../contexts/LocaleContext";
export default function LocaleSelectorModal() {
return (
<LocaleContext.Consumer>
{locale => (
<div id="locale-selector-modal" uk-modal="">
<div className="uk-modal-dialog uk-modal-body">
<h2 className="uk-modal-title">{getTranslation("app.localeSelectorModalTitle", "Change language")}</h2>
<select
defaultValue={locale.is}
className="uk-select"
onChange={event => {
locale.change(event.target.value as AvailableLanguages);
}}
>
<option value="en">English</option>
<option value="fr">Français</option>
<option value="ch"></option>
<option value="de">Deutsch</option>
<option value="tr">Türkçe</option>
{/* Add language here with the name of the language in his native name */}
</select>
</div>
</div>
)}
</LocaleContext.Consumer>
);
}

View File

@ -0,0 +1,127 @@
import * as React from "react";
import Card from "./Card";
import RyzenControllerAppContext from "../contexts/RyzenControllerAppContext";
import { getTranslation } from "../contexts/LocaleContext";
class PresetAutoApplyCards extends React.PureComponent {
updateOnLaptopPluggedIn(ryzenControllerAppContext: RyzenControllerAppContextType) {
return function(event: React.ChangeEvent<HTMLSelectElement>): void {
ryzenControllerAppContext.updateSettings({
onLaptopPluggedIn: event.target.value,
});
};
}
updateOnLaptopPluggedOut(ryzenControllerAppContext: RyzenControllerAppContextType) {
return function(event: React.ChangeEvent<HTMLSelectElement>): void {
ryzenControllerAppContext.updateSettings({
onLaptopPluggedOut: event.target.value,
});
};
}
updateOnRCStart(ryzenControllerAppContext: RyzenControllerAppContextType) {
return function(event: React.ChangeEvent<HTMLSelectElement>): void {
ryzenControllerAppContext.updateSettings({
onRCStart: event.target.value,
});
};
}
updateOnSessionResume(ryzenControllerAppContext: RyzenControllerAppContextType) {
return function(event: React.ChangeEvent<HTMLSelectElement>): void {
ryzenControllerAppContext.updateSettings({
onSessionResume: event.target.value,
});
};
}
render() {
return (
<RyzenControllerAppContext.Consumer>
{ryzenControllerAppContext => {
const presetNames = Object.keys(ryzenControllerAppContext.presets);
return (
<div className="uk-flex uk-flex-wrap">
{window.require("os").platform() === "win32" ? (
<React.Fragment>
<Card
title={getTranslation("presetAutoApply.whenLaptopPluggedIn", "When laptop plugged in")}
type="small"
>
<select
onChange={this.updateOnLaptopPluggedIn(ryzenControllerAppContext)}
className="uk-select"
value={ryzenControllerAppContext.settings.onLaptopPluggedIn || ""}
>
<option value="">{getTranslation("presetAutoApply.nonePreset", "None")}</option>
{presetNames.map(presetName => {
return (
<option key={`1_${presetName}`} value={presetName}>
{presetName}
</option>
);
})}
</select>
</Card>
<Card
title={getTranslation("presetAutoApply.whenLaptopPluggedOut", "When laptop plugged out")}
type="small"
>
<select
onChange={this.updateOnLaptopPluggedOut(ryzenControllerAppContext)}
className="uk-select"
value={ryzenControllerAppContext.settings.onLaptopPluggedOut || ""}
>
<option value="">{getTranslation("presetAutoApply.nonePreset", "None")}</option>
{presetNames.map(presetName => {
return (
<option key={`2_${presetName}`} value={presetName}>
{presetName}
</option>
);
})}
</select>
</Card>
</React.Fragment>
) : null}
<Card title={getTranslation("presetAutoApply.whenSessionResume", "When session resume")} type="small">
<select
onChange={this.updateOnSessionResume(ryzenControllerAppContext)}
className="uk-select"
value={ryzenControllerAppContext.settings.onSessionResume || ""}
>
<option value="">{getTranslation("presetAutoApply.nonePreset", "None")}</option>
{presetNames.map(presetName => {
return (
<option key={`2_${presetName}`} value={presetName}>
{presetName}
</option>
);
})}
</select>
</Card>
<Card title={getTranslation("presetAutoApply.whenRCStart", "When Ryzen Controller starts")} type="small">
<select
onChange={this.updateOnRCStart(ryzenControllerAppContext)}
className="uk-select"
value={ryzenControllerAppContext.settings.onRCStart || ""}
>
<option value="">{getTranslation("presetAutoApply.nonePreset", "None")}</option>
{presetNames.map(presetName => {
return (
<option key={`2_${presetName}`} value={presetName}>
{presetName}
</option>
);
})}
</select>
</Card>
</div>
);
}}
</RyzenControllerAppContext.Consumer>
);
}
}
export default PresetAutoApplyCards;

View File

@ -0,0 +1,158 @@
import * as React from "react";
import NotificationContext from "../contexts/NotificationContext";
import RyzenControllerAppContext, { executeRyzenAdjUsingPreset } from "../contexts/RyzenControllerAppContext";
import SysInfoContext from "../contexts/SysInfoContext";
import PresetsOnlineContext from "../contexts/PresetsOnline";
import { getTranslation } from "../contexts/LocaleContext";
type PresetButtonsProps = {
presetName: string;
preset: RyzenAdjOptionListType;
};
class PresetButtons extends React.Component<PresetButtonsProps, {}> {
render() {
return (
<RyzenControllerAppContext.Consumer>
{(ryzenControllerAppContext: RyzenControllerAppContextType) => (
<div className="uk-flex uk-flex-right uk-flex-middle uk-height-1-1 uk-flex-wrap">
<div className="uk-button-group uk-margin-right">
<button
className="uk-button uk-button-small uk-button-primary"
uk-tooltip={`title: ${getTranslation(
"presetButtons.applyPresetTooltip",
"The preset will be loaded in RyzenAdj's tabs and applied."
)}`}
onClick={this.applyPreset(ryzenControllerAppContext)}
>
{getTranslation("presetButtons.apply", "Apply")}
</button>
<button
className="uk-button uk-button-small uk-button-danger"
onClick={this.removePreset(ryzenControllerAppContext)}
>
{getTranslation("presetButtons.delete", "Delete")}
</button>
</div>
<div className="uk-button-group uk-margin-right">
<button
className="uk-button uk-button-small uk-button-default"
uk-tooltip={`title: ${getTranslation(
"presetButtons.loadPresetTooltip",
"The preset will be loaded in RyzenAdj's tabs but not applied."
)}`}
onClick={this.loadPreset(ryzenControllerAppContext)}
>
{getTranslation("presetButtons.load", "Load")}
</button>
<SysInfoContext.Consumer>
{sysinfo => (
<PresetsOnlineContext.Consumer>
{(presetsOnlineContext: PresetsOnlineContextType) => (
<button
className="uk-button uk-button-small uk-button-default"
onClick={this.uploadPreset(presetsOnlineContext, sysinfo.signature)}
>
{getTranslation("presetButtons.upload", "Upload")}
</button>
)}
</PresetsOnlineContext.Consumer>
)}
</SysInfoContext.Consumer>
</div>
</div>
)}
</RyzenControllerAppContext.Consumer>
);
}
applyPreset(
ryzenControllerAppContext: RyzenControllerAppContextType
): ((event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void) | undefined {
return () => {
ryzenControllerAppContext.updateCurrentSettings(this.props.preset);
executeRyzenAdjUsingPreset(this.props.presetName);
};
}
uploadPreset(
presetsOnlineContext: PresetsOnlineContextType,
signature: string | false
): (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void {
return () => {
if (!signature) {
NotificationContext.warning(
getTranslation("presetButtons.mustWaitForSignatureGen", "You must wait for laptop signature to be generated")
);
return;
}
let presetsWithSameName = presetsOnlineContext.list.filter(preset => {
return preset.name === this.props.presetName && preset.systemHash === signature;
});
if (presetsWithSameName.length > 0) {
NotificationContext.warning(
getTranslation(
"presetButtons.presetWithSameNameAlreadyExistOnline",
"A preset with the same name already exist online"
)
);
return;
}
window
.require("uikit")
.modal.confirm(
getTranslation("presetButtons.uploadPresetConfirmation", "Are you sure to upload the preset {preset}?", {
preset: this.props.presetName,
})
)
.then(() => {
presetsOnlineContext
.uploadPreset({
name: this.props.presetName,
systemHash: signature,
ryzenAdjArguments: this.props.preset,
})
.then(value => {
NotificationContext.success(
getTranslation("presetButtons.uploadSucceed", "Preset {preset} has been uploaded", {
preset: value.name,
})
);
presetsOnlineContext.update();
});
});
};
}
loadPreset(
ryzenControllerAppContext: RyzenControllerAppContextType
): (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void {
return () => {
ryzenControllerAppContext.updateCurrentSettings(this.props.preset);
NotificationContext.talk(
getTranslation("presetButtons.loadedPreset", "Preset {preset} has been loaded.", {
preset: this.props.presetName,
})
);
};
}
removePreset(
ryzenControllerAppContext: RyzenControllerAppContextType
): (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void {
return () => {
require("uikit")
.modal.confirm(
getTranslation("presetButtons.confirmDeletion", 'Are you sure to delete "{preset}"?', {
preset: this.props.presetName,
})
)
.then(() => {
ryzenControllerAppContext.removePreset(this.props.presetName);
})
.catch(() => {});
};
}
}
export default PresetButtons;

View File

@ -0,0 +1,28 @@
import * as React from "react";
import PresetSummary from "../components/PresetSummary";
import PresetButtons from "../components/PresetButtons";
type PresetLineProps = {
presetName: string;
preset: RyzenAdjOptionListType;
};
class PresetLine extends React.PureComponent<PresetLineProps, {}> {
render() {
return (
<li className="uk-position-relative">
<div className="uk-grid">
<div className="uk-width-1-1 uk-width-1-2@s uk-width-2-3@l uk-width-3-4@xl">
{this.props.presetName}
<PresetSummary preset={this.props.preset} />
</div>
<div className="uk-width-1-1 uk-width-1-2@s uk-width-1-3@l uk-width-1-4@xl">
<PresetButtons preset={this.props.preset} presetName={this.props.presetName} />
</div>
</div>
</li>
);
}
}
export default PresetLine;

View File

@ -0,0 +1,34 @@
import * as React from "react";
import LightModeContext from "../contexts/LightModeContext";
import { getTranslation } from "../contexts/LocaleContext";
function PresetListEmpty() {
return (
<LightModeContext.Consumer>
{lm => {
const classes = lm.mode === "light" ? "uk-dark uk-background-default" : "uk-light uk-background-primary";
return (
<div className={`uk-flex uk-flex-center uk-margin-left uk-margin-right`}>
<div className={`uk-card uk-card-default uk-card-body uk-width-1-2@m ${classes}`}>
<h3 className="uk-card-title">
{getTranslation("presetListEmpty.youDontHaveAny", "You don't have any preset yet")}
</h3>
<p>
{getTranslation("presetListEmpty.sentencePart1", "You can create one by using the")}
<button className="uk-button uk-button-default uk-margin-small-left uk-margin-small-right">
{getTranslation("presetListEmpty.createPresetBtn", "Create preset")}
</button>
{getTranslation(
"presetListEmpty.sentencePart2",
"button available on Ryzen Adj settings tabs (CPU, GPU, ...)."
)}
</p>
</div>
</div>
);
}}
</LightModeContext.Consumer>
);
}
export default PresetListEmpty;

View File

@ -0,0 +1,62 @@
import * as React from "react";
import PresetsOnlineContext from "../contexts/PresetsOnline";
import Card from "./Card";
import SysInfoContext, { SysInfoState } from "../contexts/SysInfoContext";
import PresetOnlineLine from "../components/PresetOnlineLine";
import { isPresetValid } from "../contexts/RyzenControllerAppContext";
import { getTranslation } from "../contexts/LocaleContext";
function PresetOnline() {
return (
<SysInfoContext.Consumer>
{(sysInfoContext: SysInfoState) => (
<PresetsOnlineContext.Consumer>
{(presetsOnlineContext: PresetsOnlineContextType) => {
return presetsOnlineContext.list
.filter(preset => isPresetValid(preset.ryzenAdjArguments))
.filter(preset => preset.systemHash === sysInfoContext.signature).length && sysInfoContext?.signature ? (
<ul className="uk-margin uk-list uk-list-large uk-list-striped">
{presetsOnlineContext.list
.filter(preset => isPresetValid(preset.ryzenAdjArguments))
.filter(preset => preset.systemHash === sysInfoContext.signature)
.map((preset: ApiPreset, index) => {
const presetName = preset.name;
return <PresetOnlineLine preset={preset} key={`online_${index}_${presetName}_btn`} />;
})}
</ul>
) : presetsOnlineContext.loading || !sysInfoContext?.signature ? (
<div className="uk-flex uk-flex-center">
<div uk-spinner="ratio: 2"></div>
</div>
) : (
<Card
title={getTranslation(
"PresetOnline.listNotLoadedYet",
"List hasn't been loaded or there is no online preset yet."
)}
>
<button
className="uk-margin-small-bottom uk-button uk-button-small uk-button-default"
onClick={() => presetsOnlineContext.update()}
>
{getTranslation("PresetOnline.loadPresetListBtn", "Load preset list")}
</button>
<br />
{getTranslation("PresetOnline.sentencePart1", "You can share your own preset by clicking on the")}
<button
className="uk-margin-small-right uk-margin-small-left uk-button uk-button-small uk-button-default"
onClick={() => false}
>
{getTranslation("PresetOnline.uploadBtn", "Upload")}
</button>
{getTranslation("PresetOnline.sentencePart2", "button available on your presets.")}
</Card>
);
}}
</PresetsOnlineContext.Consumer>
)}
</SysInfoContext.Consumer>
);
}
export default PresetOnline;

View File

@ -0,0 +1,111 @@
import * as React from "react";
import RyzenControllerAppContext, { isPresetValid } from "../contexts/RyzenControllerAppContext";
import NotificationContext from "../contexts/NotificationContext";
import PresetsOnlineContext from "../contexts/PresetsOnline";
import { getTranslation } from "../contexts/LocaleContext";
type PresetOnlineButtonsProps = {
presetName: string;
presetId: number;
preset: RyzenAdjOptionListType;
upvote: number;
downvote: number;
};
function PresetOnlineButtons(props: PresetOnlineButtonsProps) {
return (
<div className="uk-flex uk-flex-right uk-flex-middle uk-height-1-1 uk-flex-wrap">
<RyzenControllerAppContext.Consumer>
{(ryzenControllerAppContext: RyzenControllerAppContextType) => (
<div className="uk-button-group uk-margin-right">
<button
className="uk-button uk-button-small uk-button-primary"
uk-tooltip={`title: ${getTranslation(
"presetOnlineBtn.downloadTooltip",
"Will save the preset to your local preset."
)}`}
onClick={() => {
if (ryzenControllerAppContext.presets.hasOwnProperty(props.presetName)) {
NotificationContext.warning(
getTranslation(
"presetOnlineBtn.presetSameNameExist",
"You already have a preset with the same name"
)
);
return;
}
if (!isPresetValid(props.preset)) {
const presetInvalidOrObsoleteMessage = getTranslation(
"presetOnlineBtn.presetInvalidOrObsolete",
'Preset "{presetName}" is invalid or obsolete',
{ presetName: props.presetName }
);
NotificationContext.error(presetInvalidOrObsoleteMessage);
return;
}
ryzenControllerAppContext.addPreset(props.presetName, props.preset);
const presetDownloadedMessage = getTranslation(
"presetOnlineBtn.presetDownloaded",
'Preset "{presetName}" has been downloaded',
{ presetName: props.presetName }
);
NotificationContext.talk(presetDownloadedMessage);
return;
}}
>
{getTranslation("presetOnlineBtn.download", "Download")}
</button>
<button
className="uk-button uk-button-small uk-button-default"
uk-tooltip={`title: ${getTranslation(
"presetOnlineBtn.loadTooltip",
"Without saving the preset, it will be loaded in RyzenAdj's tabs but not applied."
)}`}
onClick={() => {
ryzenControllerAppContext.updateCurrentSettings(props.preset);
const presetloadedMessage = getTranslation(
"presetOnlineBtn.presetDownloaded",
'Preset "{presetName}" has been loaded',
{ presetName: props.presetName }
);
NotificationContext.talk(presetloadedMessage);
}}
>
{getTranslation("presetOnlineBtn.load", "Load")}
</button>
</div>
)}
</RyzenControllerAppContext.Consumer>
<PresetsOnlineContext.Consumer>
{(presetsOnlineContext: PresetsOnlineContextType) => (
<div className="uk-button-group uk-margin-right">
<button
className="uk-button uk-button-small uk-button-default"
onClick={() => {
presetsOnlineContext.upvote(props.presetId);
}}
>
<span className="uk-margin-small-right" role="img" aria-label="upvote">
👍
</span>
(+{props.upvote})
</button>
<button
className="uk-button uk-button-small uk-button-default"
onClick={() => {
presetsOnlineContext.downvote(props.presetId);
}}
>
<span className="uk-margin-small-right" role="img" aria-label="downvote">
👎
</span>
({props.downvote})
</button>
</div>
)}
</PresetsOnlineContext.Consumer>
</div>
);
}
export default PresetOnlineButtons;

View File

@ -0,0 +1,33 @@
import * as React from "react";
import PresetSummary from "../components/PresetSummary";
import PresetOnlineButtons from "../components/PresetOnlineButtons";
type PresetOnlineLineProps = {
preset: ApiPreset;
};
class PresetOnlineLine extends React.PureComponent<PresetOnlineLineProps, {}> {
render() {
return (
<li className="uk-position-relative">
<div className="uk-grid">
<div className="uk-width-1-1 uk-width-1-2@s uk-width-2-3@l uk-width-3-4@xl">
{this.props.preset.name}
<PresetSummary preset={this.props.preset.ryzenAdjArguments} />
</div>
<div className="uk-width-1-1 uk-width-1-2@s uk-width-1-3@l uk-width-1-4@xl">
<PresetOnlineButtons
presetId={this.props.preset.id}
presetName={this.props.preset.name}
preset={this.props.preset.ryzenAdjArguments}
upvote={this.props.preset.upvote}
downvote={this.props.preset.downvote}
/>
</div>
</div>
</li>
);
}
}
export default PresetOnlineLine;

View File

@ -0,0 +1,15 @@
import * as React from "react";
import { createRyzenAdjCommandLine } from "../contexts/RyzenAdjContext";
function PresetSummary(props: { preset: RyzenAdjOptionListType }) {
return (
<p
className="uk-text-small uk-text-truncate uk-text-italic"
uk-tooltip={`title: ${createRyzenAdjCommandLine(props.preset).join("<br/>")}`}
>
{createRyzenAdjCommandLine(props.preset).join(" ")}
</p>
);
}
export default PresetSummary;

View File

@ -0,0 +1,102 @@
import * as React from "react";
import RyzenControllerAppContext, { defaultPreset, isPresetValid } from "../contexts/RyzenControllerAppContext";
import Notification from "../contexts/NotificationContext";
import { createRyzenAdjCommandLine, executeRyzenAdj } from "../contexts/RyzenAdjContext";
import LightModeContext from "../contexts/LightModeContext";
import NotificationContext from "../contexts/NotificationContext";
import { getTranslation } from "../contexts/LocaleContext";
const UIkit = require("uikit");
class RyzenAdjBottomBar extends React.PureComponent {
render() {
return (
<LightModeContext.Consumer>
{lightModeContext => {
return (
<RyzenControllerAppContext.Consumer>
{ryzenControllerAppContext => {
const classes =
lightModeContext.mode === "light"
? "uk-dark uk-background-default uk-card uk-card-default"
: "uk-light uk-background-primary";
return (
<div className={`uk-padding-small uk-position-fixed uk-position-bottom-right ${classes}`}>
<button className="uk-button uk-button-primary" onClick={this.apply(ryzenControllerAppContext)}>
{getTranslation("ryzenAdjBottomBar.apply", "Apply")}
</button>
<button
className="uk-button uk-button-default uk-margin-left"
onClick={this.createNewPreset(ryzenControllerAppContext)}
>
{getTranslation("ryzenAdjBottomBar.createPreset", "Create preset")}
</button>
<button
className="uk-button uk-button-default uk-margin-left"
onClick={this.reset(ryzenControllerAppContext)}
>
{getTranslation("ryzenAdjBottomBar.reset", "Reset")}
</button>
</div>
);
}}
</RyzenControllerAppContext.Consumer>
);
}}
</LightModeContext.Consumer>
);
}
createNewPreset(
ryzenControllerAppContext: RyzenControllerAppContextType
): (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void {
return function() {
const promptMessage = getTranslation("ryzenAdjBottomBar.prompt", "New preset name");
const mustProvideNameMessage = getTranslation("ryzenAdjBottomBar.mustProvideName", "You must provide a name");
UIkit.modal.prompt(promptMessage, "").then((newPresetName: string | null) => {
if (newPresetName === null) {
return;
} else if (newPresetName.length <= 0) {
Notification.warning(mustProvideNameMessage);
} else if (ryzenControllerAppContext.presets.hasOwnProperty(newPresetName)) {
const presetWithSameNameExistMessage = getTranslation(
"ryzenAdjBottomBar.presetWithSameNameExist",
'A preset with the name "{newPresetName}" already exist',
{ newPresetName: newPresetName }
);
Notification.warning(presetWithSameNameExistMessage);
} else {
ryzenControllerAppContext.addPreset(newPresetName, ryzenControllerAppContext.currentSettings);
const presetCreatedMessage = getTranslation(
"ryzenAdjBottomBar.presetCreated",
'Preset "{newPresetName}" created',
{ newPresetName: newPresetName }
);
Notification.success(presetCreatedMessage);
}
});
};
}
apply(ryzenControllerAppContext: RyzenControllerAppContextType) {
return function() {
if (!isPresetValid(ryzenControllerAppContext.currentSettings)) {
NotificationContext.warning(
getTranslation("ryzenAdjBottomBar.invalidPreset", "Unable to apply invalid preset")
);
return;
}
ryzenControllerAppContext.updateLatestSettings();
executeRyzenAdj(createRyzenAdjCommandLine(ryzenControllerAppContext.currentSettings));
};
}
reset(ryzenControllerAppContext: RyzenControllerAppContextType) {
return function() {
ryzenControllerAppContext.updateCurrentSettings(defaultPreset);
};
}
}
export default RyzenAdjBottomBar;

View File

@ -0,0 +1,84 @@
import * as React from "react";
import RyzenControllerAppContext from "../contexts/RyzenControllerAppContext";
type RyzenAdjOptionFormState = {
value: number;
};
type RyzenAdjOptionFormProps = {
option: RyzenAdjOptionDefinition;
enabled: boolean;
};
class RyzenAdjOptionForm extends React.PureComponent<RyzenAdjOptionFormProps, RyzenAdjOptionFormState> {
state = {
value: this.props.option.default,
};
render() {
const onChange = this.onChange.bind(this);
if (!this.props.enabled) return null;
return (
<RyzenControllerAppContext.Consumer>
{ryzenControllerAppContext => {
const isEnabled = ryzenControllerAppContext.currentSettings[this.props.option.ryzenadj_arg]?.enabled;
const value =
ryzenControllerAppContext.currentSettings[this.props.option.ryzenadj_arg].value || this.state.value;
if (!isEnabled) {
return null;
}
return (
<div className="uk-grid-small" uk-grid="">
<div className="uk-width-1-6">
<input
onChange={onChange(ryzenControllerAppContext).bind(this)}
className="uk-input"
type="number"
value={value}
min={this.props.option.min}
max={this.props.option.max}
step={this.props.option.step}
/>
</div>
<div className="uk-width-5-6">
<input
onChange={onChange(ryzenControllerAppContext).bind(this)}
className="uk-range"
type="range"
value={value}
min={this.props.option.min}
max={this.props.option.max}
step={this.props.option.step}
/>
</div>
</div>
);
}}
</RyzenControllerAppContext.Consumer>
);
}
onChange(ryzenControllerAppContext: RyzenControllerAppContextType) {
const name: RyzenAdjArguments = this.props.option.ryzenadj_arg;
const _this = this;
return function(event: React.ChangeEvent<HTMLInputElement>): void {
let value = parseInt(event.target.value);
if (value < _this.props.option.min) {
value = _this.props.option.min;
}
if (value > _this.props.option.max) {
value = _this.props.option.max;
}
ryzenControllerAppContext.updateCurrentSettings({
[name]: {
enabled: true,
value: value,
},
});
};
}
}
export default RyzenAdjOptionForm;

View File

@ -0,0 +1,73 @@
import * as React from "react";
import RyzenControllerAppContext from "../contexts/RyzenControllerAppContext";
type RyzenAdjOptionLabelProps = {
option: RyzenAdjOptionDefinition;
};
type RyzenAdjOptionLabelState = {
enabled: boolean;
};
class RyzenAdjOptionLabel extends React.PureComponent<RyzenAdjOptionLabelProps, RyzenAdjOptionLabelState> {
state = {
enabled: false,
};
isEnabled(ryzenControllerAppContext: RyzenControllerAppContextType): boolean {
const isEnabled: boolean =
ryzenControllerAppContext.currentSettings[this.props.option.ryzenadj_arg]?.enabled || false;
if (this.state.enabled !== isEnabled) {
this.setState({
enabled: isEnabled,
});
}
return isEnabled;
}
render() {
return (
<RyzenControllerAppContext.Consumer>
{ryzenControllerAppContext => (
<div className="uk-grid-small" uk-grid="">
<div className="uk-width-expend">
<h2>
<label className="uk-pointer">
<input
className="uk-margin-right uk-checkbox"
type="checkbox"
checked={this.isEnabled(ryzenControllerAppContext)}
onChange={this.handleChange(ryzenControllerAppContext).bind(this)}
/>
{this.props.option.label}
</label>
<span
uk-icon="info"
className="uk-margin-left"
uk-tooltip={`title: ${this.props.option.description}`}
/>
</h2>
</div>
</div>
)}
</RyzenControllerAppContext.Consumer>
);
}
handleChange(ryzenControllerAppContext: RyzenControllerAppContextType): Function {
return (event: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({
enabled: event.target.checked,
});
let newCurrentSettings: PartialRyzenAdjOptionListType = {
[this.props.option.ryzenadj_arg]: {
enabled: event.target.checked,
value: ryzenControllerAppContext.currentSettings[this.props.option.ryzenadj_arg].value,
},
};
ryzenControllerAppContext.updateCurrentSettings(newCurrentSettings);
};
}
}
export default RyzenAdjOptionLabel;

View File

@ -0,0 +1,31 @@
import * as React from "react";
import RyzenAdjOptions from "../components/RyzenAdjOptions";
import RyzenAdjOptionForm from "../components/RyzenAdjOptionForm";
import RyzenAdjOptionLabel from "../components/RyzenAdjOptionLabel";
type RyzenAdjOptionListProps = {
filter: RyzenControllerTabForRyzenAdj;
};
class RyzenAdjOptionList extends React.PureComponent<RyzenAdjOptionListProps, {}> {
render() {
return <RyzenAdjOptions tab={this.props.filter} render={this.renderRyzenAdjOptionFormList.bind(this)} />;
}
renderRyzenAdjOptionFormList(options: Array<RyzenAdjOptionDefinition>): React.ReactNode {
return (
<React.Fragment>
{options.map((option: RyzenAdjOptionDefinition) => {
return (
<div key={option.ryzenadj_arg} className="uk-margin">
<RyzenAdjOptionLabel option={option} />
<RyzenAdjOptionForm enabled={true} option={option} />
</div>
);
})}
</React.Fragment>
);
}
}
export default RyzenAdjOptionList;

View File

@ -0,0 +1,19 @@
import * as React from "react";
import { RyzenAdjOptionDefinitions } from "../contexts/RyzenAdjContext";
type RyzenAdjOptionsProps = {
tab?: RyzenControllerTabForRyzenAdj;
render(options: Array<RyzenAdjOptionDefinition>): React.ReactNode;
};
function RyzenAdjOptions(props: RyzenAdjOptionsProps) {
let currentTabs: Array<RyzenAdjOptionDefinition> = RyzenAdjOptionDefinitions;
if (props.tab) {
currentTabs = RyzenAdjOptionDefinitions.filter(option => option.tab === props.tab);
}
return <React.Fragment>{props.render(currentTabs)}</React.Fragment>;
}
export default RyzenAdjOptions;
export { RyzenAdjOptionDefinitions };

View File

@ -0,0 +1,92 @@
import * as React from "react";
import { Link, Switch, Route } from "react-router-dom";
import LightModeContext from "../contexts/LightModeContext";
import { getTranslation } from "../contexts/LocaleContext";
function Tabs(props: { tabName: string; tabLocation: string; currentLocation: string }) {
let isActive = "";
if (props.currentLocation === props.tabLocation) {
isActive = "uk-active";
}
return (
<li className={isActive}>
<Link to={props.tabLocation}>{props.tabName}</Link>
</li>
);
}
function SceneSelector() {
return (
<LightModeContext.Consumer>
{lm => {
const classes = lm.mode === "light" ? "uk-dark uk-background-default" : "uk-light uk-background-secondary";
return (
<nav className={classes} uk-sticky="sel-target: .uk-navbar-container; cls-active: uk-navbar-sticky">
<Switch>
<Route
render={props => {
const currentLocation = props.location.pathname;
return (
<ul className="uk-tab uk-margin-remove-bottom">
<Tabs
tabName={getTranslation("sceneSelector.cpuTitle", "CPU")}
tabLocation="/cpu"
currentLocation={currentLocation}
/>
<Tabs
tabName={getTranslation("sceneSelector.gpuTitle", "GPU")}
tabLocation="/gpu"
currentLocation={currentLocation}
/>
<Tabs
tabName={getTranslation("sceneSelector.powerTitle", "Power")}
tabLocation="/power"
currentLocation={currentLocation}
/>
<Tabs
tabName={getTranslation("sceneSelector.presetsTitle", "Presets")}
tabLocation="/presets"
currentLocation={currentLocation}
/>
<Tabs
tabName={getTranslation("sceneSelector.settingsTitle", "Settings")}
tabLocation="/settings"
currentLocation={currentLocation}
/>
<li>
<a
href="https://gitlab.com/ryzen-controller-team/ryzen-controller/-/releases"
onClick={openExternal("https://gitlab.com/ryzen-controller-team/ryzen-controller/-/releases")}
>
<span uk-icon="link"></span>
{getTranslation("sceneSelector.releasesTitle", "Releases")}
</a>
</li>
</ul>
);
}}
></Route>
</Switch>
</nav>
);
}}
</LightModeContext.Consumer>
);
}
/**
* This method open the given URL using external browser.
* @param url The URL to be opened.
* @return function
*/
function openExternal(url: string) {
return function openExternalNow(e: React.MouseEvent<HTMLAnchorElement>) {
e.preventDefault();
window.require("electron").remote.shell.openExternal(url);
return false;
};
}
export default SceneSelector;

View File

@ -0,0 +1,7 @@
import * as React from "react";
function SceneTitle(props: { title: string; className?: string }) {
return <h2 className={`uk-margin uk-margin-left uk-margin-right ${props.className}`}>{props.title}</h2>;
}
export default SceneTitle;

View File

@ -0,0 +1,90 @@
import * as React from "react";
import { getTranslation } from "../contexts/LocaleContext";
type ElectronFileDialogType = {
filePaths?: Array<string>;
};
type SettingFormProps = {
setting: RyzenControllerSettingDefinition;
value: boolean | string | number;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
};
class SettingForm extends React.PureComponent<SettingFormProps> {
render(): React.ReactNode {
return (
<div uk-tooltip={this.props.setting.description || ""} className="uk-form-controls uk-form-controls-text">
{this.props.setting.displayTitle ? <h4 className="uk-margin-top">{this.props.setting.name}</h4> : null}
<label className="uk-form-label uk-pointer">
{this.renderType(this.props.setting.type)} {this.props.setting.short_description}
</label>
</div>
);
}
renderType(type: "boolean" | "range" | "path"): React.ReactNode {
switch (type) {
case "boolean":
const checked = !!this.props.value;
return <input onChange={this.props.onChange} checked={checked} className="uk-checkbox" type="checkbox" />;
case "range":
const number = parseInt(`${this.props.value}`) || 0;
return (
<div className="uk-inline uk-margin-small-right">
<input
onChange={this.props.onChange}
defaultValue={number}
className="uk-input uk-form-width-small"
type="number"
/>
</div>
);
case "path":
const path = `${this.props.value}`;
return (
<React.Fragment>
<p className="uk-margin-small-bottom">
{getTranslation("settingForm.currentPath", "Current path:")} <code>{path || "default"}</code>
</p>
<button onClick={this.findFile.bind(this)} className="uk-button uk-button-default">
{getTranslation("settingForm.browseBtn", "Browse")}
</button>
</React.Fragment>
);
default:
throw new Error("Unknown setting type.");
}
}
findFile(event: React.MouseEvent<HTMLButtonElement>): void {
event.preventDefault();
const allowedFiles =
window.require("electron").remote.process.platform === "linux"
? []
: [
{
name: getTranslation("settingForm.windowsBinFileType", "Windows Binary"),
extensions: ["exe"],
},
];
window
.require("electron")
.remote.dialog.showOpenDialog({
properties: ["onpenFile"],
filters: allowedFiles,
})
.then((data: ElectronFileDialogType) => {
if (!data.filePaths) {
return;
}
// @ts-ignore
this.props.onChange({ target: { value: data.filePaths[0] } });
});
}
}
export default SettingForm;

View File

@ -0,0 +1,62 @@
import * as React from "react";
import RyzenControllerAppContext, {
RyzenControllerSettingsDefinitions,
getSettingDefinition,
} from "../contexts/RyzenControllerAppContext";
import SettingForm from "./SettingForm";
class SettingsList extends React.PureComponent {
state = {
settings: {},
};
render() {
const platform: "win32" | "linux" = window.require("os").platform();
return (
<form className="uk-margin-left uk-margin-right">
<RyzenControllerAppContext.Consumer>
{(ryzenControllerAppContext: RyzenControllerAppContextType) => (
<fieldset className="uk-fieldset">
{Object.keys(RyzenControllerSettingsDefinitions).map(
(key: string): React.ReactNode => {
// @ts-ignore
const settingKey: RyzenControllerSettingsNames = key;
if (!RyzenControllerSettingsDefinitions[settingKey].compatibility[platform]) {
return null;
}
return (
<div key={settingKey}>
<SettingForm
setting={RyzenControllerSettingsDefinitions[settingKey]}
value={ryzenControllerAppContext.settings[settingKey]}
onChange={this.updateSetting(ryzenControllerAppContext, settingKey)}
/>
</div>
);
}
)}
</fieldset>
)}
</RyzenControllerAppContext.Consumer>
</form>
);
}
updateSetting(
ryzenControllerAppContext: RyzenControllerAppContextType,
settingKey: RyzenControllerSettingsNames
): (e: React.ChangeEvent<HTMLInputElement>) => void {
return function(e: React.ChangeEvent<HTMLInputElement>): void {
const def = getSettingDefinition(settingKey);
if (!def) {
return;
}
const value = def.type === "boolean" ? e.target.checked : e.target.value;
ryzenControllerAppContext.updateSettings({
[settingKey]: value,
});
};
}
}
export default SettingsList;

View File

@ -0,0 +1,90 @@
import * as React from "react";
import Card from "./Card";
import SysInfoContext from "../contexts/SysInfoContext";
import { getTranslation } from "../contexts/LocaleContext";
function SysInfoCards() {
return (
<SysInfoContext.Consumer>
{sysInfoContext => {
if (sysInfoContext.error) {
return (
<Card title="Error">
{getTranslation("sysInfoCards.unableToGetSysInfo", "Unable to get system info:")}
<br />
{sysInfoContext.error}
</Card>
);
}
const system = sysInfoContext.system;
const cpu = sysInfoContext.cpu;
const mem = sysInfoContext.mem;
const gpu = sysInfoContext.graphics;
const bios = sysInfoContext.bios;
return (
<div className="uk-margin-small-right uk-margin-small-left uk-flex uk-flex-center uk-flex-around uk-flex-wrap">
<Card title={getTranslation("sysInfoCards.basicInfoTitle", "Basic Information")}>
{system === false || mem === false || bios === false ? (
<div uk-spinner=""></div>
) : (
<React.Fragment>
{system.manufacturer} {system.model} {system.version}
<br />
{getTranslation("sysInfoCards.biosVersion", "Bios version:")} {bios.version}{" "}
{bios.revision ? `rev${bios.revision}` : ""}
<br />
{(mem.total / (1024 * 1024 * 1024)).toFixed(2)}Gb Ram
</React.Fragment>
)}
</Card>
<Card title={getTranslation("sysInfoCards.CPUInfoTitle", "CPU Information")}>
{cpu === false ? (
<div uk-spinner=""></div>
) : (
<React.Fragment>
{cpu.manufacturer} {cpu.brand}
<br />
{getTranslation(
"sysInfoCards.cpuPerfDesc",
"{speedmax}Ghz on {physicalCores} cores, {cores} threads.",
{
speedmax: cpu.speedmax,
physicalCores: cpu.physicalCores.toString(),
cores: cpu.cores.toString(),
}
)}
</React.Fragment>
)}
</Card>
{gpu === false ? (
<Card title={getTranslation("sysInfoCards.GPUInfoTitle", "GPU #{index} Information", { index: "0" })}>
<div uk-spinner=""></div>
</Card>
) : (
<React.Fragment>
{gpu.controllers.map((controller, index) => (
<Card
key={`gpu-${index}`}
title={getTranslation("sysInfoCards.GPUInfoTitle", "GPU #{index} Information", {
index: `${index}`,
})}
>
{controller.vendor} {controller.model}
<br />
{getTranslation("sysInfoCards.gpuPerfDesc", "{ram}Mb (Dynamic vram: {dyn}).", {
ram: controller.vram.toString(),
dyn: controller.vramDynamic.toString(),
})}
</Card>
))}
</React.Fragment>
)}
</div>
);
}}
</SysInfoContext.Consumer>
);
}
export default SysInfoCards;

72
src/components/TopBar.tsx Normal file
View File

@ -0,0 +1,72 @@
import * as React from "react";
import logo from "../assets/icon.png";
import Badge from "./Badge";
import LightModeContext from "../contexts/LightModeContext";
import { getTranslation } from "../contexts/LocaleContext";
function TopBar() {
return (
<header>
<div
style={{
width: "128px",
height: "128px",
display: "inline-block",
}}
>
<img src={logo} alt="Ryzen Controller" width="128px" height="128px" />
</div>
<Badge
className="uk-margin-left"
value={process.env.REACT_APP_VERSION || "dev"}
onClick={openExternal("https://gitlab.com/ryzen-controller-team/ryzen-controller/releases")}
background="#EE0000"
/>
<Badge
className="uk-margin-left"
value={getTranslation("topbar.beer", "Buy us some beers ❤️")}
onClick={openExternal("https://www.patreon.com/ryzencontrollerteam")}
background="#888888"
/>
<Badge
className="uk-margin-left"
value={getTranslation("topbar.discord", "Join us on discord")}
onClick={openExternal("https://discord.gg/EahayUv")}
background="#7289da"
/>
<LightModeContext.Consumer>
{mode => (
<Badge
className="uk-margin-left"
value={mode.mode === "dark" ? "☀️" : "🌙"}
onClick={mode.switch}
background={mode.mode === "dark" ? "#FFF" : "#000"}
/>
)}
</LightModeContext.Consumer>
<Badge
className="uk-margin-left"
value="🇧🇱"
onClick={() => {
require("uikit")
.modal(document.getElementById("locale-selector-modal"))
.show();
}}
background="rgba(0, 0, 0, 0)"
/>
</header>
);
}
/**
* This method open the given URL using external browser.
* @param url The URL to be opened.
* @return function
*/
function openExternal(url: string) {
return function openExternalNow() {
window.require("electron").remote.shell.openExternal(url);
};
}
export default TopBar;

View File

@ -0,0 +1,9 @@
import { createContext } from "react";
const LightModeContext = createContext({
mode: "light",
switch: (): void => {},
});
LightModeContext.displayName = "LightModeContext";
export default LightModeContext;

View File

@ -0,0 +1,103 @@
import { createContext } from "react";
import LocaleTranslations from "../locales/LocaleTranslations";
const fs = window.require("fs");
const LocaleContext = createContext({
is: "en",
change: (to: AvailableLanguages): void => {},
getTranslation: (id: string, fallback?: string, variables?: Record<string, string>): string => "",
});
LocaleContext.displayName = "LocaleContext";
/**
* Will add the key to the current locale file.
*
* @param id The message id
* @param currentLocale The current locale
* @param fallback The fallback message for the given message id
*/
function addKeyToLocale(_id: string, _currentLocale: string, _fallback: string | undefined): void {
let id = _id;
let currentLocale = _currentLocale;
let fallback = _fallback;
let localeFile = `src/locales/${_currentLocale}.json`;
let inter = setInterval(() => {
let lock = window.require("electron-settings").get("lock");
if (lock) {
return;
}
clearInterval(inter);
window.require("electron-settings").set("lock", true);
console.log(`Writting key ${id} to locale ${currentLocale}...`);
fs.readFile(localeFile, (err: string | null, data: string) => {
if (err) {
console.warn(err);
return;
}
let localeTranslation = JSON.parse(data);
localeTranslation[id] = "";
if (currentLocale === "en" && fallback) {
localeTranslation[id] = fallback;
}
fs.writeFile(localeFile, JSON.stringify(localeTranslation, null, 4), function(err: string | null) {
window.require("electron-settings").delete("lock");
if (err) {
console.log("error", err);
return;
}
console.log(`Written key ${id} to locale ${currentLocale}.`);
});
});
}, 1000);
}
/**
* Will return the translated message.
*
* Exemple:
* - Given the message id "test" which give "Hello {firstname}!" as translation
* - Usage would be getTranslation("test", {firstname: "Bob"})
* - Would return "Hello Bob!"
*
* Warning:
* - For "en" locale, fallback prevail over locales/en.json
* - Using dev, id are added in the current locale
* - Using dev and "en" locale, en.json will be updated with fallback content
*
* @param id The message id
* @param fallback The fallback message for the given message id
* @param variables Variables to replace in the sentence
*/
function getTranslation(id: string, fallback?: string, variables?: Record<string, string>): string {
const electronSettings = window.require("electron-settings");
const currentLocale = electronSettings.get("locale") ? (electronSettings.get("locale") as AvailableLanguages) : "en";
var sentence: string | undefined = LocaleTranslations[currentLocale][id];
if (!sentence && sentence !== "") {
console.warn(`Missing translation for ${id} in locale ${currentLocale}.`);
if (process.env.REACT_APP_VERSION?.indexOf("-dev") !== -1) {
addKeyToLocale(id, currentLocale, fallback);
}
}
if (!sentence || currentLocale === "en") {
sentence = fallback ? fallback : id;
}
if (variables) {
for (const variable in variables) {
if (variables.hasOwnProperty(variable)) {
const value = variables[variable];
sentence = sentence.replace(new RegExp(`{${variable}}`, "g"), value);
}
}
}
return sentence;
}
export { getTranslation };
export default LocaleContext;

View File

@ -0,0 +1,104 @@
const UIkit = require("uikit");
const app = window.require("electron").remote.app;
type SystemNotifications = Array<Notification>;
type GroupedSystemNotifications = {
[groupName: string]: SystemNotifications;
};
let systemNotifications: GroupedSystemNotifications = {};
type NotificationSettingStatusType = "primary" | "success" | "warning" | "danger";
type NotificationSettingPosType =
| "top-left"
| "top-center"
| "top-right"
| "bottom-left"
| "bottom-center"
| "bottom-right";
type NotificationSettingsType = {
message: string;
status?: NotificationSettingStatusType;
timeout?: number;
group?: string;
pos?: NotificationSettingPosType;
};
const custom = function(settings: NotificationSettingsType): void {
const window = app.getWindow();
if (window.isVisible() && !window.isMinimized()) {
if (settings.group) {
UIkit.notification.closeAll(settings.group);
}
UIkit.notification({
status: "primary",
timeout: 4000,
group: null,
pos: "top-center",
...settings,
});
} else {
let notif;
let group = "none";
if (settings.group) {
group = settings.group;
}
if (!systemNotifications.hasOwnProperty(group)) {
systemNotifications[group] = [];
}
while ((notif = systemNotifications[group].pop())) {
notif.close();
}
notif = new Notification("Ryzen Controller", {
body: settings.message,
silent: group !== "none",
requireInteraction: group === "none",
});
notif.onclick = () => {
const window = app.getWindow();
window.show();
if (window.isMinimized()) {
window.restore();
}
if (window.isVisible()) {
window.focus();
}
};
systemNotifications[group].push(notif);
}
};
const success = function(message: string, group?: string): void {
custom({
message: message + " 😃",
status: "success",
group,
});
};
const talk = function(message: string, group?: string): void {
custom({ message: message, group });
};
const warning = function(message: string, group?: string): void {
custom({
message: message + " 🙁",
status: "warning",
group,
});
};
const error = function(message: string, group?: string): void {
custom({
message: message + " 😬",
status: "danger",
group,
});
};
export default {
success,
talk,
warning,
error,
custom,
};

View File

@ -0,0 +1,27 @@
import { createContext } from "react";
let context: PresetsOnlineContextType = {
loading: true,
list: [],
update() {},
uploadPreset(preset) {
return new Promise(res => {
res();
});
},
upvote() {
return new Promise(res => {
res();
});
},
downvote() {
return new Promise(res => {
res();
});
},
};
const PresetsOnlineContext = createContext(context);
PresetsOnlineContext.displayName = "PresetsOnlineContext";
export default PresetsOnlineContext;

View File

@ -0,0 +1,273 @@
import NotificationContext from "./NotificationContext";
import { getRyzenAdjExecutablePath } from "./RyzenControllerAppContext";
import { getTranslation } from "./LocaleContext";
const RyzenAdjOptionDefinitions: Array<RyzenAdjOptionDefinition> = [
{
description: getTranslation(
"ryzenAdj.slowTime.desc",
"This define the period to be used out of boost period to deliver a constant power to be delivered to the socket."
),
label: getTranslation("ryzenAdj.slowTime.label", "Package Power Tracking (PPT) - Slow period"),
tab: "power",
min: 1,
max: 3600,
step: 1,
default: 900,
ryzenadj_arg: "--slow-time=",
ryzenadj_value_convert: "toThousand",
},
{
description: getTranslation(
"ryzenAdj.psi0Current.desc",
"The limit of current we let the motherboard deliver to the PSI0."
),
label: getTranslation("ryzenAdj.psi0Current.label", "PSI0 Current Limit (mA)"),
tab: "power",
min: 20,
max: 100,
step: 1,
default: 20,
ryzenadj_arg: "--psi0-current=",
ryzenadj_value_convert: "toHex",
},
{
description: getTranslation(
"ryzenAdj.vrmmaxCurrent.desc",
"The limit of current we let the motherboard deliver to the CPU."
),
label: getTranslation("ryzenAdj.vrmmaxCurrent.label", "VRM Current (A)"),
tab: "power",
min: 20,
max: 75,
step: 1,
default: 30,
ryzenadj_arg: "--vrmmax-current=",
ryzenadj_value_convert: "toThousand",
},
{
description: getTranslation(
"ryzenAdj.minGfxclk.desc",
"The minimum clock speed the integrated (Vega) GPU is allowed to run at."
),
label: getTranslation("ryzenAdj.minGfxclk.label", "Minimum Vega iGPU Clock Frequency (Mhz)"),
tab: "gpu",
min: 400,
max: 1300,
step: 1,
default: 400,
ryzenadj_arg: "--min-gfxclk=",
ryzenadj_value_convert: "roundTen",
},
{
description: getTranslation(
"ryzenAdj.maxGfxclk.desc",
"The maximum clock speed the integrated (Vega) GPU is allowed to run at."
),
label: getTranslation("ryzenAdj.maxGfxclk.label", "Maximum Vega iGPU Clock Frequency (Mhz)"),
tab: "gpu",
min: 400,
max: 1300,
step: 1,
default: 1100,
ryzenadj_arg: "--max-gfxclk=",
ryzenadj_value_convert: "roundTen",
},
{
description: getTranslation(
"ryzenAdj.minFclkFrequency.desc",
"Infinity Fabric is AMD's marketing term for the bus connection that connects processor dies (GPU/CPU). This define the bus's min. clock limit."
),
label: getTranslation("ryzenAdj.minFclkFrequency.label", "Minimum Infinity Fabric frequency (Mhz)"),
tab: "gpu",
min: 800,
max: 1600,
step: 1,
default: 800,
ryzenadj_arg: "--min-fclk-frequency=",
ryzenadj_value_convert: null,
},
{
description: getTranslation(
"ryzenAdj.maxFclkFrequency.desc",
"Infinity Fabric is AMD's marketing term for the bus connection that connects processor dies (GPU/CPU). This define the bus's max. clock limit."
),
label: getTranslation("ryzenAdj.maxFclkFrequency.label", "Maximum Infinity Fabric frequency (Mhz)"),
tab: "gpu",
min: 800,
max: 1600,
step: 1,
default: 1200,
ryzenadj_arg: "--max-fclk-frequency=",
ryzenadj_value_convert: null,
},
{
description: getTranslation("ryzenAdj.tctlTemp.desc", "The temperature the CPU can reach before boost levels off."),
label: getTranslation("ryzenAdj.tctlTemp.label", "Temperature Limit (°C)"),
tab: "cpu",
min: 50,
max: 100,
step: 1,
default: 75,
ryzenadj_arg: "--tctl-temp=",
ryzenadj_value_convert: null,
},
{
description: getTranslation(
"ryzenAdj.stapmLimit.desc",
"Skin Temperature Aware Power Management. This will define the socket power package limit which is used to manage the device boost period."
),
label: getTranslation("ryzenAdj.stapmLimit.label", "CPU TDP (W)"),
tab: "cpu",
min: 5,
max: 60,
step: 1,
default: 20,
ryzenadj_arg: "--stapm-limit=",
ryzenadj_value_convert: "toThousand",
},
{
description: getTranslation(
"ryzenAdj.stapmTime.desc",
"Skin Temperature Aware Power Management. This will define the boost period to be used."
),
label: getTranslation("ryzenAdj.stapmTime.label", "CPU Boost Period"),
tab: "cpu",
min: 1,
max: 3600,
step: 1,
default: 900,
ryzenadj_arg: "--stapm-time=",
ryzenadj_value_convert: "toThousand",
},
{
description: getTranslation(
"ryzenAdj.fastLimit.desc",
"The amount of power the CPU can draw while boost levels on."
),
label: getTranslation("ryzenAdj.fastLimit.label", "CPU Boost TDP (W)"),
tab: "cpu",
min: 5,
max: 60,
step: 1,
default: 25,
ryzenadj_arg: "--fast-limit=",
ryzenadj_value_convert: "toThousand",
},
{
description: getTranslation(
"ryzenAdj.slowLimit.desc",
"The amount of power the CPU can draw while boost levels off."
),
label: getTranslation("ryzenAdj.slowLimit.label", "CPU Min TDP (W)"),
tab: "cpu",
min: 5,
max: 60,
step: 1,
default: 10,
ryzenadj_arg: "--slow-limit=",
ryzenadj_value_convert: "toThousand",
},
];
const valueConverter = function(converterName: RyzenAdjConverter, value: number): string {
switch (converterName) {
case "toHex":
if (value < 0) {
value = 0xffffffff + value * 1000 + 1;
}
return "0x" + value.toString(16).toUpperCase();
case "roundTen":
return `${Math.round((value / 10) * 10)}`;
case "toThousand":
return `${value * 1000}`;
case null:
return `${value}`;
default:
return "";
}
};
const getOptionDefinition = function(name: RyzenAdjArguments): RyzenAdjOptionDefinition {
return RyzenAdjOptionDefinitions.filter(definition => {
return definition.ryzenadj_arg === name;
})[0];
};
const createRyzenAdjCommandLine = function(preset: RyzenAdjOptionListType): Array<string> {
let commandLine: Array<string> = [];
for (const key in preset) {
if (!preset.hasOwnProperty(key)) {
continue;
}
// @ts-ignore
const arg: RyzenAdjArguments = key;
const optionValue = preset[arg];
if (!optionValue.enabled) {
continue;
}
commandLine.push(`${arg}${valueConverter(getOptionDefinition(arg).ryzenadj_value_convert, optionValue.value)}`);
}
return commandLine;
};
const ryzenAdjProcess = function(parameters: Array<string>): Promise<string> {
return new Promise((res, rej) => {
const child = window.require("child_process").execFile;
const executablePath = getRyzenAdjExecutablePath();
if (parameters.length === 0) {
NotificationContext.warning(
getTranslation("ryzenAdj.pleaseAddSomeOptions", "Please add some options before applying ryzenAdj.")
);
return;
}
console.log(`${executablePath} ${parameters.join(" ")}`);
child(executablePath, parameters, function(err: string, data: Buffer) {
var output = data?.toString();
if (err) {
rej(err);
} else if (output) {
res(output);
}
res("no output");
});
});
};
const executeRyzenAdj = function(parameters: Array<string>, notification: boolean = true, retry = 3) {
if (retry === 0) {
NotificationContext.error(getTranslation("ryzenAdj.unableToApply", "Unable to apply ryzenadj"), "ryzenadj_applied");
return;
}
if (parameters.length === 0) {
NotificationContext.warning(
getTranslation("ryzenAdj.pleaseAddSomeOptions", "Please add some options before applying ryzenAdj.")
);
return;
}
ryzenAdjProcess(parameters)
.then((output: string) => {
if (notification) {
NotificationContext.success(
getTranslation("ryzenAdj.applySuccess", "RyzenAdj has been executed successfully."),
"ryzenadj_applied"
);
console.log(output);
}
})
.catch(err => {
executeRyzenAdj(parameters, notification, retry - 1);
console.error(err);
});
};
export { RyzenAdjOptionDefinitions, getOptionDefinition, executeRyzenAdj, createRyzenAdjCommandLine };

View File

@ -0,0 +1,414 @@
import { createContext } from "react";
import { getOptionDefinition, executeRyzenAdj, createRyzenAdjCommandLine } from "./RyzenAdjContext";
import { isNumber } from "util";
import compareVersions from "compare-versions";
import NotificationContext from "./NotificationContext";
import { getTranslation } from "./LocaleContext";
const isDev = window.require("electron-is-dev");
const electronSettings = window.require("electron-settings");
const fileSystem = window.require("fs");
const app_version_as_string = process.env?.REACT_APP_VERSION?.replace(/\./g, "_") || "dev";
const defaultPreset = {
"--slow-time=": { enabled: false, value: getOptionDefinition("--slow-time=").default },
"--psi0-current=": { enabled: false, value: getOptionDefinition("--psi0-current=").default },
"--vrmmax-current=": { enabled: false, value: getOptionDefinition("--vrmmax-current=").default },
"--min-gfxclk=": { enabled: false, value: getOptionDefinition("--min-gfxclk=").default },
"--max-gfxclk=": { enabled: false, value: getOptionDefinition("--max-gfxclk=").default },
"--min-fclk-frequency=": { enabled: false, value: getOptionDefinition("--min-fclk-frequency=").default },
"--max-fclk-frequency=": { enabled: false, value: getOptionDefinition("--max-fclk-frequency=").default },
"--tctl-temp=": { enabled: false, value: getOptionDefinition("--tctl-temp=").default },
"--stapm-limit=": { enabled: false, value: getOptionDefinition("--stapm-limit=").default },
"--stapm-time=": { enabled: false, value: getOptionDefinition("--stapm-time=").default },
"--fast-limit=": { enabled: false, value: getOptionDefinition("--fast-limit=").default },
"--slow-limit=": { enabled: false, value: getOptionDefinition("--slow-limit=").default },
};
const getRyzenAdjExecutablePath = function(): string {
const cwd = window.require("electron").remote.app.getAppPath();
let path: string | undefined = electronSettings.get(app_version_as_string)?.settings?.ryzenAdjPath;
if (path) {
return path;
}
path = `${cwd}\\${isDev ? "public\\" : "build\\"}bin\\ryzenadj.exe`;
return path;
};
const RyzenControllerSettingsDefinitions: RyzenControllerSettingDefinitionList = {
autoStartOnBoot: {
displayTitle: false,
name: getTranslation("appContext.autoStartOnBoot.name", "Auto start on boot"),
type: "boolean",
default: false,
short_description: getTranslation(
"appContext.autoStartOnBoot.shortDesc",
"Launch Ryzen Controller on computer start."
),
compatibility: {
linux: false,
win32: true,
},
apply(toBeEnabled) {
const platform: "win32" | "linux" = window.require("os").platform();
const AutoLaunch = window.require("auto-launch");
let autoLaunch = new AutoLaunch({
name: "Ryzen Controller",
});
return new Promise((res, rej) => {
if (platform === "linux") {
return autoLaunch.isEnabled().then((isEnabled: boolean) => {
try {
if (isEnabled) {
autoLaunch.disable();
}
if (toBeEnabled) {
autoLaunch.enable();
}
return true;
} catch (error) {
return error;
}
});
} else if (platform === "win32") {
// Ensure the old autolaunch has been deleted.
autoLaunch.isEnabled().then((isEnabled: boolean) => {
try {
if (isEnabled) {
autoLaunch.disable();
}
} catch (error) {
console.log(error);
}
});
// Handle new auto launch system.
const path = `${window
.require("electron")
.remote.app.getAppPath()}.unpacked\\build\\bin\\auto-start-ryzen-controller.bat`;
try {
window.require("electron").remote.app.setLoginItemSettings({
openAtLogin: toBeEnabled,
path: path,
});
} catch (error) {
rej(error);
}
res(true);
}
});
},
},
minimizeOnLaunch: {
displayTitle: false,
name: getTranslation("appContext.minimizeOnLaunch.name", "Minimize on launch"),
type: "boolean",
default: false,
short_description: getTranslation("appContext.minimizeOnLaunch.shortDesc", "Launch Ryzen Controller minimized."),
compatibility: {
linux: true,
win32: true,
},
apply() {
return new Promise(resolve => {
resolve(true);
});
},
},
minimizeToTray: {
displayTitle: false,
name: getTranslation("appContext.minimizeToTray.name", "Minimize to tray"),
type: "boolean",
default: false,
short_description: getTranslation(
"appContext.minimizeToTray.shortDesc",
"Minimize Ryzen Controller to tray instead of taskbar."
),
compatibility: {
linux: true,
win32: true,
},
apply() {
return new Promise(resolve => {
resolve(true);
});
},
},
reApplyPeriodically: {
displayTitle: true,
name: getTranslation("appContext.reApplyPeriodically.name", "Re-apply periodically"),
type: "range",
default: 0,
short_description: getTranslation(
"appContext.reApplyPeriodically.shortDesc",
"Allow you to re-apply RyzenAdj settings periodically (every X seconds)."
),
description: getTranslation(
"appContext.reApplyPeriodically.desc",
"On some laptops, the bios sometimes reset what RyzenAdj is trying to do. You can use this to apply the settings periodically."
),
compatibility: {
linux: true,
win32: true,
},
apply(seconds) {
// @ts-ignore
let parsedSeconds: number = parseInt(seconds) >= 0 ? parseInt(seconds) * 1000 : 0;
if (!isNumber(parsedSeconds)) {
return new Promise((resolve, reject) => {
reject("ERROR: Value must be of number type.");
});
}
return new Promise((resolve, reject) => {
try {
let intervalId = electronSettings.get("reApplyPeriodically");
clearInterval(intervalId);
intervalId = false;
electronSettings.set("reApplyPeriodically", false);
if (parsedSeconds <= 0) {
resolve(false);
return;
}
electronSettings.set(
"reApplyPeriodically",
setInterval(() => {
let preset = electronSettings.get(app_version_as_string).currentSettings;
if (!isPresetValid(preset)) {
return;
}
executeRyzenAdj(createRyzenAdjCommandLine(preset), false);
}, parsedSeconds)
);
resolve(true);
} catch (error) {
reject(error);
}
});
},
},
ryzenAdjPath: {
displayTitle: true,
name: getTranslation("appContext.ryzenAdjPath.name", "RyzenAdj path"),
type: "path",
default: "\\bin\\ryzenadj.exe",
short_description: getTranslation("appContext.ryzenAdjPath.shortDesc", "The full path to ryzenadj binary."),
compatibility: {
linux: true,
win32: true,
},
apply(path) {
return new Promise((resolve, reject) => {
if (!path) {
path = getRyzenAdjExecutablePath();
}
if (!fileSystem.existsSync(path)) {
reject(
getTranslation(
"appContext.ryzenAdjPath.wrongPath",
"Path to ryzenadj.exe is wrong, please fix it in settings tab."
)
);
}
resolve(true);
});
},
},
onLaptopPluggedIn: {
displayTitle: false,
name: "",
type: "path",
default: false,
short_description: "",
compatibility: {
linux: false,
win32: false,
},
apply() {
return new Promise(resolve => {
resolve(true);
});
},
},
onLaptopPluggedOut: {
displayTitle: false,
name: "",
type: "path",
default: false,
short_description: "",
compatibility: {
linux: false,
win32: false,
},
apply() {
return new Promise(resolve => {
resolve(true);
});
},
},
onRCStart: {
displayTitle: false,
name: "",
type: "path",
default: false,
short_description: "",
compatibility: {
linux: false,
win32: false,
},
apply() {
return new Promise(resolve => {
resolve(true);
});
},
},
onSessionResume: {
displayTitle: false,
name: "",
type: "path",
default: false,
short_description: "",
compatibility: {
linux: false,
win32: false,
},
apply() {
return new Promise(resolve => {
resolve(true);
});
},
},
};
const getSettingDefinition = function(
name: keyof RyzenControllerSettingDefinitionList
): RyzenControllerSettingDefinition | false {
if (RyzenControllerSettingsDefinitions.hasOwnProperty(name)) {
return RyzenControllerSettingsDefinitions[name];
}
return false;
};
const defaultRyzenControllerAppContext: RyzenControllerAppContextType = {
latestSettings: defaultPreset,
currentSettings: defaultPreset,
presets: {},
settings: {
autoStartOnBoot: false,
minimizeOnLaunch: false,
minimizeToTray: false,
reApplyPeriodically: false,
ryzenAdjPath: "",
onLaptopPluggedIn: false,
onLaptopPluggedOut: false,
onRCStart: false,
onSessionResume: false,
},
updateLatestSettings() {},
updateCurrentSettings(list) {},
addPreset(name, preset) {},
removePreset(name) {},
updateSettings(settings) {},
};
const isPresetValid = function(preset: PartialRyzenAdjOptionListType): boolean {
try {
for (const key in preset) {
// @ts-ignore
const arg: RyzenAdjArguments = key;
if (!defaultPreset.hasOwnProperty(arg)) {
throw new Error(`ERROR: key "${arg}" in preset does not exist in defaultPreset.`);
} else {
const value = preset[arg]?.value || -1;
const min = getOptionDefinition(arg).min;
const max = getOptionDefinition(arg).max;
if (min > value || value > max) {
throw new Error(`ERROR: "${arg}" with value ${value} is out of bound (${min} - ${max}) in preset.`);
}
}
}
} catch (error) {
console.error(error);
return false;
}
return true;
};
let loadedContext = window.require("electron-settings").get(app_version_as_string);
let context = defaultRyzenControllerAppContext;
if (loadedContext) {
context = {
...defaultRyzenControllerAppContext,
...loadedContext,
};
}
const RyzenControllerAppContext = createContext<RyzenControllerAppContextType>(context);
RyzenControllerAppContext.displayName = "RyzenControllerAppContext";
const persistentSave = function(context: RyzenControllerAppContextType) {
let savedContext: RyzenControllerAppContextType = {
...context,
currentSettings: context.latestSettings,
};
window.require("electron-settings").set(app_version_as_string, savedContext);
};
const executeRyzenAdjUsingPreset = function(presetName: string): boolean {
const presets = electronSettings.get(app_version_as_string)?.presets;
if (!presets.hasOwnProperty(presetName)) {
return false;
}
if (!isPresetValid(presets[presetName])) {
NotificationContext.warning(getTranslation("appContext.invalidPreset", "Unable to apply invalid preset"));
return false;
}
executeRyzenAdj(createRyzenAdjCommandLine(presets[presetName]));
return true;
};
const checkIfNewerReleaseExist = function(): void {
const currentVersion = process.env?.REACT_APP_VERSION;
if (!currentVersion) {
return;
}
fetch("https://gitlab.com/api/v4/projects/11046417/releases")
.then(response => response.json())
.then(data => {
const last_released_version = data[0]?.tag_name;
if (!last_released_version) {
throw new Error("Unable to check for new release");
}
if (compareVersions.compare(currentVersion, last_released_version, "<")) {
return true;
}
return false;
})
.then((isNewReleaseExist: boolean) => {
if (isNewReleaseExist) {
NotificationContext.talk(
getTranslation("appContext.newReleaseAvailable", "A new release is available, please check the release tab.")
);
}
})
.catch(err => {
console.warn(err);
});
};
export default RyzenControllerAppContext;
export {
context as defaultRyzenControllerAppContext,
defaultPreset,
persistentSave,
RyzenControllerSettingsDefinitions,
getSettingDefinition,
getRyzenAdjExecutablePath,
app_version_as_string,
executeRyzenAdjUsingPreset,
checkIfNewerReleaseExist as checkNewVersion,
isPresetValid,
};

View File

@ -0,0 +1,53 @@
import { createContext } from "react";
import { Systeminformation } from "systeminformation";
const hasher = require("object-hash");
export type SysInfoState = {
cpu: Systeminformation.CpuData | false;
graphics: Systeminformation.GraphicsData | false;
mem: Systeminformation.MemData | false;
memLayout: Array<Systeminformation.MemLayoutData> | false;
system: Systeminformation.SystemData | false;
bios: Systeminformation.BiosData | false;
signature: string | false;
error?: string;
};
const createMachineSignature = function(data: SysInfoState): string | false {
if (data.error) {
return false;
}
if (data.system === false || data.mem === false || data.cpu === false || data.graphics === false) {
return false;
}
return hasher({
"system.manufacturer": data.system.manufacturer,
"system.model": data.system.model,
"system.version": data.system.version,
"mem.total": data.mem.total,
"cpu.manufacturer": data.cpu.manufacturer,
"cpu.brand": data.cpu.brand,
"cpu.speedmax": data.cpu.speedmax,
"cpu.physicalCores": data.cpu.physicalCores,
"cpu.cores": data.cpu.cores,
"gpu.controllers": data.graphics.controllers,
});
};
let context: SysInfoState = {
cpu: false,
graphics: false,
mem: false,
memLayout: false,
system: false,
bios: false,
signature: false,
};
const SysInfoContext = createContext(context);
SysInfoContext.displayName = "SysInfoContext";
export default SysInfoContext;
export { createMachineSignature };

29
src/index.scss Normal file
View File

@ -0,0 +1,29 @@
@import "~uikit/dist/css/uikit";
html {
#root {
padding-bottom: 50px;
}
.uk-pointer,
.uk-range {
cursor: pointer;
}
.uk-modal {
background-color: rgba(50, 50, 50, 0.6);
.uk-modal-dialog {
background-color: #1e87f0;
.uk-form-stacked {
.uk-modal-footer {
background-color: #1e87f0;
}
}
.uk-modal-footer {
background-color: #1e87f0;
}
}
}
}

20
src/index.tsx Normal file
View File

@ -0,0 +1,20 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import "./index.scss";
import App from "./App";
const UIkit = require("uikit");
const Icons = require("uikit/dist/js/uikit-icons");
UIkit.use(Icons);
const rootEl = document.getElementById("root");
ReactDOM.render(<App />, rootEl);
// For hot reload
// See https://github.com/vitaliy-bobrov/angular-hot-loader/issues/5#issuecomment-377785900
if ((module as any).hot) {
(module as any).hot.accept("./App", () => {
const NextApp = require("./App").default;
ReactDOM.render(<NextApp />, rootEl);
});
}

View File

@ -0,0 +1,8 @@
/* Add language here with the corresponding json file, use en.json as reference */
export default {
en: require("./en.json") as Record<string, string>,
fr: require("./fr.json") as Record<string, string>,
ch: require("./ch.json") as Record<string, string>,
de: require("./de.json") as Record<string, string>,
tr: require("./tr.json") as Record<string, string>,
};

102
src/locales/ch.json Normal file
View File

@ -0,0 +1,102 @@
{
"topbar.discord": "加入我们的Discord群组",
"topbar.beer": "捐助我们 ❤️",
"presetButtons.applyPresetTooltip": "将性能预设填充到所有调整项, 且立即应用调整指令",
"presetButtons.loadPresetTooltip": "将性能预设填充到所有调整项, 但暂不应用调整指令",
"presetAutoApply.whenSessionResume": "重新打开窗口时",
"presetAutoApply.whenRCStart": "当Ryzen Controller启动时",
"presetButtons.confirmDeletion": "确认删除预设 \"{preset}\"?",
"presetButtons.loadedPreset": "已删除预设 {preset} .",
"presetButtons.uploadPresetConfirmation": "确认上传预设 {preset}?",
"presetListEmpty.youDontHaveAny": "当前没有性能预设",
"presetListEmpty.sentencePart1": "请点击",
"presetListEmpty.createPresetBtn": "新增预设",
"presetListEmpty.sentencePart2": "按钮来创建一个性能预设. 该按钮可在Ryzen Adj设置 (CPU, GPU, ...) 选项卡中找到. ",
"PresetOnline.listNotLoadedYet": "列表尚未加载或暂无在线列表",
"PresetOnline.loadPresetListBtn": "加载预设列表",
"PresetOnline.sentencePart1": "请点击预设列表中的",
"PresetOnline.uploadBtn": "上传",
"PresetOnline.sentencePart2": "按钮来在线分享你的预设. ",
"sceneSelector.cpuTitle": "CPU",
"sceneSelector.gpuTitle": "GPU",
"ryzenAdj.maxGfxclk.label": "最大 Vega iGPU 时钟频率 (Mhz)",
"ryzenAdj.stapmTime.desc": "表面温度感应功耗管理 (STAMP) . 该选项将定义GPU睿频时间. ",
"ryzenAdj.slowLimit.desc": "睿频稳定时, CPU可达到的最大功耗",
"appContext.minimizeOnLaunch.name": "软件启动时最小化",
"appContext.ryzenAdjPath.name": "RyzenAdj所在的文件夹路径",
"sceneSelector.settingsTitle": "设置",
"sceneSelector.powerTitle": "电源管理",
"ryzenAdj.slowTime.desc": "在睿频周期以外",
"ryzenAdj.slowTime.label": "封装电源追踪 (PPT) - 减速期 (Slow Period)",
"ryzenAdj.minFclkFrequency.desc": "Infinity Fabric (IF总线) 是AMD新架构中连接核心 (CPU/GPU) 的Bus总线. 这一选项将定义总线的最低时钟频率. ",
"ryzenAdj.maxFclkFrequency.desc": "Infinity Fabric (IF总线) 是AMD新架构中连接核心 (CPU/GPU) 的Bus总线. 这一选项将定义总线的最高时钟频率. ",
"ryzenAdj.stapmLimit.desc": "表面温度感应功耗管理 (STAMP) . 该选项将定义插槽电源包 (Socket Power Package) 的限值, 该限值用于管理设备的睿频时长. ",
"ryzenAdj.stapmTime.label": "CPU 睿频时间",
"ryzenAdj.vrmmaxCurrent.desc": "主板向CPU传输电流的限值.",
"ryzenAdj.vrmmaxCurrent.label": "VRM 电流 (A)",
"appContext.ryzenAdjPath.shortDesc": "ryzenadj 目录的完整路径.",
"ryzenAdj.maxFclkFrequency.label": "最大 Infinity Fabric (IF总线) 频率 (Mhz)",
"ryzenAdj.stapmLimit.label": "CPU 功耗 TDP (W)",
"appContext.autoStartOnBoot.shortDesc": "设置 Ryzen Controller 开机自启.",
"sceneSelector.presetsTitle": "预设",
"ryzenAdj.psi0Current.label": "PSI0 电流限制 (mA)",
"ryzenAdj.maxGfxclk.desc": "Vega核心显卡的最高运行频率.",
"ryzenAdj.minFclkFrequency.label": " Infinity Fabric (IF总线) 的最低频率 (Mhz)",
"ryzenAdj.slowLimit.label": "CPU 最小 TDP (W)",
"appContext.minimizeOnLaunch.shortDesc": "最小化启动 Ryzen Controller .",
"ryzenAdj.minGfxclk.label": "最低 Vega iGPU 时钟频率 (Mhz)",
"ryzenAdj.fastLimit.desc": "睿频加速时, CPU可以达到的最大功耗.",
"appContext.minimizeToTray.name": "最小化到托盘",
"ryzenAdj.psi0Current.desc": "主板向PSI0传输电流的限值.",
"ryzenAdj.minGfxclk.desc": "Vega核心显卡的最低运行频率.",
"ryzenAdj.fastLimit.label": "CPU 睿频功耗 TDP (W)",
"appContext.autoStartOnBoot.name": "开机时自启",
"appContext.minimizeToTray.shortDesc": "将 Ryzen Controller 最小化到托盘而不是任务栏. ",
"appContext.reApplyPeriodically.desc": "在某些笔记本上, Bios会自动覆盖Ryzen Adj发送的的性能调整指令. 启用这一选项, RyzenAdj将循环发送性能调整指令. ",
"sceneSelector.releasesTitle": "版本更新",
"ryzenAdj.tctlTemp.desc": "在降频之前笔记本可以达到的温度 (温度墙) .",
"appContext.reApplyPeriodically.name": "循环应用",
"ryzenAdj.tctlTemp.label": "温度墙 (°C)",
"appContext.reApplyPeriodically.shortDesc": "循环执行 RyzenAdj 指令(每 X 秒一次).",
"PresetsScene.onlinePresetTitle": "云端性能预设",
"PresetsScene.localPresetTitle": "本地性能预设",
"PresetsScene.autoApplyTitle": "自动应用预设",
"SettingsScene.settingsTitle": "软件设置",
"settingForm.currentPath": "当前路径:",
"sysInfoCards.biosVersion": "Bios 版本号:",
"sysInfoCards.gpuPerfDesc": "{ram}Mb (动态 vram: {dyn}).",
"SettingsScene.sysInfoTitle": "系统信息",
"sysInfoCards.basicInfoTitle": "基本信息",
"settingForm.browseBtn": "浏览",
"SettingsScene.systemHashDesc": "该选项用于保证下载预设时的的稳定性.",
"sysInfoCards.CPUInfoTitle": "CPU 信息",
"sysInfoCards.cpuPerfDesc": "最高频率{speedmax}Ghz, {physicalCores} 核心, {cores} 线程.",
"sysInfoCards.GPUInfoTitle": "GPU #{index} 信息",
"SettingsScene.loadingSysHash": "正在加载...",
"ryzenAdjBottomBar.prompt": "新预设的名称",
"ryzenAdjBottomBar.mustProvideName": "请填写预设名称",
"ryzenAdjBottomBar.presetCreated": "预设 \"{newPresetName}\" 已经创建",
"ryzenAdjBottomBar.presetWithSameNameExist": "名为 \"{newPresetName}\" 的预设已经存在",
"presetButtons.apply": "应用",
"presetButtons.delete": "删除",
"presetButtons.load": "加载",
"presetAutoApply.nonePreset": "无",
"presetButtons.upload": "上传",
"ryzenAdj.applySuccess": "RyzenAdj 已经成功执行性能调整指令.",
"presetButtons.uploadSucceed": "预设 {preset} 已上传",
"presetOnlineBtn.downloadTooltip": "预设将被存储到本地. ",
"presetOnlineBtn.loadTooltip": "不保存性能预设, 也不应用预设, 仅将预设数据填充在RyzenAdj的性能调整项中. ",
"presetOnlineBtn.download": "下载",
"presetOnlineBtn.load": "加载",
"presetButtons.presetWithSameNameAlreadyExistOnline": "在线服务器中已存在同名预设",
"presetOnlineBtn.presetDownloaded": "预设 \"{presetName}\" 已加载",
"presetOnlineBtn.presetSameNameExist": "同名预设已存在",
"PresetsScene.cantVoteTwiceSamePreset": "不能为相同的预设多次投票",
"PresetsScene.confirmVote": "确定要 {vote} 这一预设吗? ",
"PresetsScene.updatingVotes": "正在上传投票内容... ",
"notification.settingsSaveSuccess": "设置已经成功保存",
"ryzenAdjBottomBar.apply": "应用",
"ryzenAdjBottomBar.createPreset": "新增预设",
"ryzenAdjBottomBar.reset": "重置",
"app.localeSelectorModalTitle": "更改语言"
}

102
src/locales/de.json Normal file
View File

@ -0,0 +1,102 @@
{
"topbar.discord": "Schließ dich uns in Discord an",
"topbar.beer": "Spendier uns ein Bierchen ❤️",
"presetButtons.applyPresetTooltip": "Die preset wird im RyzenAdj's Tab geladen und angewandt.",
"presetButtons.loadPresetTooltip": "Die preset wird im RyzenAdj's Tab geladen jedoch nicht angewandt.",
"presetAutoApply.whenSessionResume": "Wenn die session besteht",
"presetAutoApply.whenRCStart": "Wenn Ryzen Controller startet",
"presetButtons.confirmDeletion": "Bist du sicher, die \"{preset}\" zu entfernen?",
"presetButtons.loadedPreset": "Preset {preset} wurde geladen.",
"presetButtons.uploadPresetConfirmation": "Bist du sicher, die preset {preset} hochzuladen?",
"presetListEmpty.youDontHaveAny": "Du hast noch keinen preset",
"presetListEmpty.sentencePart1": "Du kannst einen erstellen, indem du den Button",
"presetListEmpty.createPresetBtn": "Preset erstellen",
"presetListEmpty.sentencePart2": "in den RyzenAdj Einstellungen Tab (CPU, GPU, ...) anklickst.",
"PresetOnline.listNotLoadedYet": "Die Liste wurde noch nicht geladen oder es gibt noch keine Online Presets.",
"PresetOnline.loadPresetListBtn": "Preset Liste laden",
"PresetOnline.sentencePart1": "Du kannst deine selbsterstellten presets teilen, in dem du den Button",
"PresetOnline.uploadBtn": "Hochladen",
"PresetOnline.sentencePart2": "in den Presets Tab anklickst.",
"sceneSelector.cpuTitle": "CPU",
"sceneSelector.gpuTitle": "GPU",
"ryzenAdj.maxGfxclk.label": "Maximale Vega iGPU Takt Frequenz (Mhz)",
"ryzenAdj.stapmTime.desc": "Skin Temperature Aware Power Management (STAPM). Dies definiert die Boost Periode, die genutzt werden soll.",
"ryzenAdj.slowLimit.desc": "Die Menge der Leistung, die die CPU ohne Boost aufbrauchen soll.",
"appContext.minimizeOnLaunch.name": "Minimieren",
"appContext.ryzenAdjPath.name": "RyzenAdj Pfad",
"sceneSelector.settingsTitle": "Einstellungen",
"sceneSelector.powerTitle": "Leistung",
"ryzenAdj.slowTime.desc": "Dies definiert die Periode der konstanten Energiezufuhr zum Sockel während der Boostfreien Phase.",
"ryzenAdj.slowTime.label": "Package Power Tracking (PPT) - Slow period (Langsame Phase)",
"ryzenAdj.minFclkFrequency.desc": "Infinity Fabric ist AMD's Marketingbegriff für die Busverbindung, welche die Prozessor dies (CPU/GPU) verbindet. Dies definiert die mindest Takt Frequenz des Bus.",
"ryzenAdj.maxFclkFrequency.desc": "Infinity Fabric ist AMD's Marketingbegriff für die Busverbindung, welche die Prozessor dies (CPU/GPU) verbindet. Dies definiert die maximale Takt Frequenz des Bus.",
"ryzenAdj.stapmLimit.desc": "Skin Temperature Aware Power Management (STAPM). Dies definiert das Sockel power package Limit, welches für die Verwaltung der Boost Periode genutzt wird.",
"ryzenAdj.stapmTime.label": "CPU Boost Periode",
"ryzenAdj.vrmmaxCurrent.desc": "Das aktuelle Limit der Zufuhr durch die Motherboard an die CPU.",
"ryzenAdj.vrmmaxCurrent.label": "VRM Current (A)",
"appContext.ryzenAdjPath.shortDesc": "Der volle Pfad zur RyzenAdj Binary.",
"ryzenAdj.maxFclkFrequency.label": "Maximale Infinity Fabric Frequenz (Mhz)",
"ryzenAdj.stapmLimit.label": "CPU TDP (W)",
"appContext.autoStartOnBoot.shortDesc": "Starte Ryzen Controller bei Systemstart.",
"sceneSelector.presetsTitle": "Presets",
"ryzenAdj.psi0Current.label": "PSI0 Current Limit (mA)",
"ryzenAdj.maxGfxclk.desc": "Die maximale Takt Frequenz auf der die die integrierte GPU (Vega) laufen darf.",
"ryzenAdj.minFclkFrequency.label": "Minimale Infinity Fabric Frequenz (Mhz)",
"ryzenAdj.slowLimit.label": "CPU Min TDP (W)",
"appContext.minimizeOnLaunch.shortDesc": "Starte Ryzen Controller minimiert.",
"ryzenAdj.minGfxclk.label": "Minimale Vega iGPU Takt Frequenz (Mhz)",
"ryzenAdj.fastLimit.desc": "Die Menge der Leistung, die die CPU während des Boosts aufbrauchen soll.",
"appContext.minimizeToTray.name": "Minimiere in den Infobereich",
"ryzenAdj.psi0Current.desc": "Das Limit, das die Motherboard an den PSI0 zuführen darf.",
"ryzenAdj.minGfxclk.desc": "Die minimale Takt Frequenz auf der die die integrierte GPU (Vega) laufen darf.",
"ryzenAdj.fastLimit.label": "CPU Boost TDP (W)",
"appContext.autoStartOnBoot.name": "Automatischer Start beim booten",
"appContext.minimizeToTray.shortDesc": "Minimiere Ryzen Controller in den Infobereich anstatt der Taskleiste.",
"appContext.reApplyPeriodically.desc": "Auf einigen Laptops setzt das BIOS Einstellungen von RyztenAdj zurück. Damit kannst du die Einstellungen automatisiert in Perioden automatisch anwenden lassen.",
"sceneSelector.releasesTitle": "Versionen",
"ryzenAdj.tctlTemp.desc": "Die Temperatur, die die CPU erreichen kann, bevor Boost ausgeschaltet wird.",
"appContext.reApplyPeriodically.name": "Periodisch erneut anwenden",
"ryzenAdj.tctlTemp.label": "Temperatur Limit (°C)",
"appContext.reApplyPeriodically.shortDesc": "Erlaubt dir die RyzenAdj Einstellungen periodisch erneut anzuwenden (\"alle X Sekunden\").",
"PresetsScene.onlinePresetTitle": "Online Presets",
"PresetsScene.localPresetTitle": "Lokale Presets",
"PresetsScene.autoApplyTitle": "Presets unter bestimmten Bedingungen automatisch anwenden",
"SettingsScene.settingsTitle": "Einstellungen",
"settingForm.currentPath": "Aktueller Pfad:",
"sysInfoCards.biosVersion": "BIOS Version:",
"sysInfoCards.gpuPerfDesc": "{ram}Mb (Dynamic vram: {dyn}).",
"SettingsScene.sysInfoTitle": "System Info",
"sysInfoCards.basicInfoTitle": "Basic Information",
"settingForm.browseBtn": "Auswählen",
"SettingsScene.systemHashDesc": "Dies wird genutzt, um bei heruntergeladenen presets die Kompatibilität zu gewährleisten.",
"sysInfoCards.CPUInfoTitle": "CPU Information",
"sysInfoCards.cpuPerfDesc": "{speedmax}Ghz auf {physicalCores} Kernen, {cores} threads.",
"sysInfoCards.GPUInfoTitle": "GPU #{index} Information",
"SettingsScene.loadingSysHash": "Lädt...",
"ryzenAdjBottomBar.prompt": "Neuer preset Name",
"ryzenAdjBottomBar.mustProvideName": "Du musst einen Namen eingeben",
"ryzenAdjBottomBar.presetCreated": "Preset \"{newPresetName}\" erstellt",
"ryzenAdjBottomBar.presetWithSameNameExist": "Ein neuer Preset mit dem Namen \"{newPresetName}\" existiert bereits",
"presetButtons.apply": "Anwenden",
"presetButtons.delete": "Entfernen",
"presetButtons.load": "Laden",
"presetAutoApply.nonePreset": "Keine",
"presetButtons.upload": "Hochladen",
"ryzenAdj.applySuccess": "RyzenAdj wurde erfolgreich ausgeführt.",
"presetButtons.uploadSucceed": "Preset {preset} wurde hochgeladen",
"presetOnlineBtn.downloadTooltip": "Speichert den preset zur lokalen Preset-Sammlung.",
"presetOnlineBtn.loadTooltip": "Ohne Speichern des Presets, wird dieser in RyzenAdj's Tabs geladen, aber nicht angewendet.",
"presetOnlineBtn.download": "Herunterladen",
"presetOnlineBtn.load": "Laden",
"presetButtons.presetWithSameNameAlreadyExistOnline": "Ein Preset mit dem gleichen Namen existiert Online bereits",
"presetOnlineBtn.presetDownloaded": "Preset \"{presetName}\" wurde geladen",
"presetOnlineBtn.presetSameNameExist": "Du hast bereits einen Preset mit den gleichen Namen",
"PresetsScene.cantVoteTwiceSamePreset": "Du kannst nicht mehrfach für den gleichen Preset abstimmen",
"PresetsScene.confirmVote": "Bist du sicher, für dieses Preset {vote} abzustimmen?",
"PresetsScene.updatingVotes": "Aktualisiere Abstimmungen...",
"notification.settingsSaveSuccess": "Einstellungen wurden erfolgreich gespeichert",
"ryzenAdjBottomBar.apply": "Anwenden",
"ryzenAdjBottomBar.createPreset": "Preset erstellen",
"ryzenAdjBottomBar.reset": "Zurücksetzen",
"app.localeSelectorModalTitle": "Sprache ändern"
}

102
src/locales/en.json Normal file
View File

@ -0,0 +1,102 @@
{
"topbar.discord": "Join us on Discord",
"topbar.beer": "Buy us some beers ❤️",
"presetButtons.applyPresetTooltip": "The preset will be loaded in RyzenAdj's tabs and applied.",
"presetButtons.loadPresetTooltip": "The preset will be loaded in RyzenAdj's tabs but not applied.",
"presetAutoApply.whenSessionResume": "When session resume",
"presetAutoApply.whenRCStart": "When Ryzen Controller starts",
"presetButtons.confirmDeletion": "Are you sure to delete \"{preset}\"?",
"presetButtons.loadedPreset": "Preset {preset} has been loaded.",
"presetButtons.uploadPresetConfirmation": "Are you sure to upload the preset {preset}?",
"presetListEmpty.youDontHaveAny": "You don't have any preset yet",
"presetListEmpty.sentencePart1": "You can create one by using the",
"presetListEmpty.createPresetBtn": "Create preset",
"presetListEmpty.sentencePart2": "button available on RyzenAdj settings tabs (CPU, GPU, ...).",
"PresetOnline.listNotLoadedYet": "List hasn't been loaded or there is no online preset yet.",
"PresetOnline.loadPresetListBtn": "Load preset list",
"PresetOnline.sentencePart1": "You can share your own preset by clicking on the",
"PresetOnline.uploadBtn": "Upload",
"PresetOnline.sentencePart2": "button available on your presets.",
"sceneSelector.cpuTitle": "CPU",
"sceneSelector.gpuTitle": "GPU",
"ryzenAdj.maxGfxclk.label": "Maximum Vega iGPU Clock Frequency (Mhz)",
"ryzenAdj.stapmTime.desc": "Skin Temperature Aware Power Management (STAPM). This defines the boost period to be used.",
"ryzenAdj.slowLimit.desc": "The amount of power the CPU can draw while boost levels off.",
"appContext.minimizeOnLaunch.name": "Minimize on launch",
"appContext.ryzenAdjPath.name": "RyzenAdj path",
"sceneSelector.settingsTitle": "Settings",
"sceneSelector.powerTitle": "Power",
"ryzenAdj.slowTime.desc": "This defines the period to be used out of boost period to deliver a constant power to be delivered to the socket.",
"ryzenAdj.slowTime.label": "Package Power Tracking (PPT) - Slow period",
"ryzenAdj.minFclkFrequency.desc": "Infinity Fabric is AMD's marketing term for the bus connection that connects processor dies (GPU/CPU). This defines the bus's min. clock limit.",
"ryzenAdj.maxFclkFrequency.desc": "Infinity Fabric is AMD's marketing term for the bus connection that connects processor dies (GPU/CPU). This defines the bus's max. clock limit.",
"ryzenAdj.stapmLimit.desc": "Skin Temperature Aware Power Management (STAPM). This defines the socket power package limit which is used to manage the device boost period.",
"ryzenAdj.stapmTime.label": "CPU Boost Period",
"ryzenAdj.vrmmaxCurrent.desc": "The limit of current we let the motherboard deliver to the CPU.",
"ryzenAdj.vrmmaxCurrent.label": "VRM Current (A)",
"appContext.ryzenAdjPath.shortDesc": "The full path to RyzenAdj binary.",
"ryzenAdj.maxFclkFrequency.label": "Maximum Infinity Fabric frequency (Mhz)",
"ryzenAdj.stapmLimit.label": "CPU TDP (W)",
"appContext.autoStartOnBoot.shortDesc": "Launch Ryzen Controller on computer start.",
"sceneSelector.presetsTitle": "Presets",
"ryzenAdj.psi0Current.label": "PSI0 Current Limit (mA)",
"ryzenAdj.maxGfxclk.desc": "The maximum clock speed the integrated (Vega) GPU is allowed to run at.",
"ryzenAdj.minFclkFrequency.label": "Minimum Infinity Fabric frequency (Mhz)",
"ryzenAdj.slowLimit.label": "CPU Min TDP (W)",
"appContext.minimizeOnLaunch.shortDesc": "Launch Ryzen Controller minimized.",
"ryzenAdj.minGfxclk.label": "Minimum Vega iGPU Clock Frequency (Mhz)",
"ryzenAdj.fastLimit.desc": "The amount of power the CPU can draw while boost levels on.",
"appContext.minimizeToTray.name": "Minimize to tray",
"ryzenAdj.psi0Current.desc": "The limit of current we let the motherboard deliver to the PSI0.",
"ryzenAdj.minGfxclk.desc": "The minimum clock speed the integrated (Vega) GPU is allowed to run at.",
"ryzenAdj.fastLimit.label": "CPU Boost TDP (W)",
"appContext.autoStartOnBoot.name": "Auto start on boot",
"appContext.minimizeToTray.shortDesc": "Minimize Ryzen Controller to tray instead of taskbar.",
"appContext.reApplyPeriodically.desc": "On some laptops, the BIOS sometimes reset what RyzenAdj is trying to do. You can use this to apply the settings periodically.",
"sceneSelector.releasesTitle": "Releases",
"ryzenAdj.tctlTemp.desc": "The temperature the CPU can reach before boost levels off.",
"appContext.reApplyPeriodically.name": "Re-apply periodically",
"ryzenAdj.tctlTemp.label": "Temperature Limit (°C)",
"appContext.reApplyPeriodically.shortDesc": "Allow you to re-apply RyzenAdj settings periodically (every X seconds).",
"PresetsScene.onlinePresetTitle": "Online Presets",
"PresetsScene.localPresetTitle": "Local Presets",
"PresetsScene.autoApplyTitle": "Auto apply preset",
"SettingsScene.settingsTitle": "Settings",
"settingForm.currentPath": "Current path:",
"sysInfoCards.biosVersion": "BIOS version:",
"sysInfoCards.gpuPerfDesc": "{ram}Mb (Dynamic vram: {dyn}).",
"SettingsScene.sysInfoTitle": "System Info",
"sysInfoCards.basicInfoTitle": "Basic Information",
"settingForm.browseBtn": "Browse",
"SettingsScene.systemHashDesc": "This will be used to ensure downloaded presets compatibility.",
"sysInfoCards.CPUInfoTitle": "CPU Information",
"sysInfoCards.cpuPerfDesc": "{speedmax}Ghz on {physicalCores} cores, {cores} threads.",
"sysInfoCards.GPUInfoTitle": "GPU #{index} Information",
"SettingsScene.loadingSysHash": "Loading...",
"ryzenAdjBottomBar.prompt": "New preset name",
"ryzenAdjBottomBar.mustProvideName": "You must provide a name",
"ryzenAdjBottomBar.presetCreated": "Preset \"{newPresetName}\" created",
"ryzenAdjBottomBar.presetWithSameNameExist": "A preset with the name \"{newPresetName}\" already exist",
"presetButtons.apply": "Apply",
"presetButtons.delete": "Delete",
"presetButtons.load": "Load",
"presetAutoApply.nonePreset": "None",
"presetButtons.upload": "Upload",
"ryzenAdj.applySuccess": "RyzenAdj has been executed successfully.",
"presetButtons.uploadSucceed": "Preset {preset} has been uploaded",
"presetOnlineBtn.downloadTooltip": "Will save the preset to your local preset.",
"presetOnlineBtn.loadTooltip": "Without saving the preset, it will be loaded in RyzenAdj's tabs but not applied.",
"presetOnlineBtn.download": "Download",
"presetOnlineBtn.load": "Load",
"presetButtons.presetWithSameNameAlreadyExistOnline": "A preset with the same name already exist online",
"presetOnlineBtn.presetDownloaded": "Preset \"{presetName}\" has been loaded",
"presetOnlineBtn.presetSameNameExist": "You already have a preset with the same name",
"PresetsScene.cantVoteTwiceSamePreset": "You can't vote twice for the same preset",
"PresetsScene.confirmVote": "Are you sure to {vote} this preset?",
"PresetsScene.updatingVotes": "Updating votes...",
"notification.settingsSaveSuccess": "Settings has been saved successfully",
"ryzenAdjBottomBar.apply": "Apply",
"ryzenAdjBottomBar.createPreset": "Create preset",
"ryzenAdjBottomBar.reset": "Reset",
"app.localeSelectorModalTitle": "Change language"
}

102
src/locales/fr.json Normal file
View File

@ -0,0 +1,102 @@
{
"topbar.discord": "Rejoignez-nous sur discord",
"topbar.beer": "Une bière pour les devs ❤️",
"presetButtons.applyPresetTooltip": "Le preset sera chargé dans les onglets de RyzenAdj et appliqué.",
"presetButtons.loadPresetTooltip": "Le preset sera chargé dans les onglets de RyzenAdj mais ne sera pas appliqué.",
"presetAutoApply.whenSessionResume": "Lorsque la session reprend",
"presetAutoApply.whenRCStart": "Quand Ryzen Controller est lancé",
"presetButtons.confirmDeletion": "Êtes-vous sûr de supprimer \"{preset}\" ?",
"presetButtons.loadedPreset": "Le preset {preset} a été chargée.",
"presetButtons.uploadPresetConfirmation": "Êtes-vous sûr de charger le preset {preset} ?",
"presetListEmpty.youDontHaveAny": "Vous n'avez pas encore de preset",
"presetListEmpty.sentencePart1": "Vous pouvez en créer un en utilisant le button",
"presetListEmpty.createPresetBtn": "Créer preset",
"presetListEmpty.sentencePart2": "disponible dans les onglets des paramètres RyzenAdj (CPU, GPU, ...).",
"PresetOnline.listNotLoadedYet": "La liste n'a pas été chargée ou il n'y a pas encore de preset en ligne.",
"PresetOnline.loadPresetListBtn": "Actualiser la liste",
"PresetOnline.sentencePart1": "Vous pouvez partager votre propre preset en cliquant sur le button",
"PresetOnline.uploadBtn": "Upload",
"PresetOnline.sentencePart2": "disponible sur vos presets.",
"sceneSelector.cpuTitle": "CPU",
"sceneSelector.gpuTitle": "GPU",
"ryzenAdj.maxGfxclk.label": "Fréquence maximale de l'horloge Vega iGPU (Mhz)",
"ryzenAdj.stapmTime.desc": "Skin Temperature Aware Power Management. Ceci définira la période de boost à utiliser.",
"ryzenAdj.slowLimit.desc": "La limite de puissance du CPU pendant la période de boost est désactivé.",
"appContext.minimizeOnLaunch.name": "Minimiser au lancement",
"appContext.ryzenAdjPath.name": "Le chemin vers RyzenAdj",
"sceneSelector.settingsTitle": "Paramètres",
"sceneSelector.powerTitle": "Power",
"ryzenAdj.slowTime.desc": "Il définit la période à utiliser en dehors de la période de boost pour fournir une puissance constante.",
"ryzenAdj.slowTime.label": "Package Power Tracking (PPT) - Période de ralentissement",
"ryzenAdj.minFclkFrequency.desc": "Infinity Fabric est le terme marketing d'AMD pour la connexion de bus qui relie les matrices de processeur (GPU/CPU). Ce terme définit la limite d'horloge minimale du bus.",
"ryzenAdj.maxFclkFrequency.desc": "Infinity Fabric est le terme marketing d'AMD pour la connexion de bus qui relie les puces de processeur (GPU/CPU). Ce terme définit la limite d'horloge maximale du bus.",
"ryzenAdj.stapmLimit.desc": "Skin Temperature Aware Power Management (gestion de l'énergie en fonction de la température). Ceci définit la limite de la puissance de la prise qui est utilisée pour gérer la période de boost du dispositif.",
"ryzenAdj.stapmTime.label": "Durée du Boost CPU",
"ryzenAdj.vrmmaxCurrent.desc": "La limite de courant que la carte mère est autorisé à livrer au CPU.",
"ryzenAdj.vrmmaxCurrent.label": "Courant du VRM (A)",
"appContext.ryzenAdjPath.shortDesc": "Le chemin complet vers le binaire ryzenadj.",
"ryzenAdj.maxFclkFrequency.label": "Fréquence maximale de l'Infinity Fabric (Mhz)",
"ryzenAdj.stapmLimit.label": "CPU TDP (W)",
"appContext.autoStartOnBoot.shortDesc": "Lancer Ryzen Controller au démarrage de l'ordinateur.",
"sceneSelector.presetsTitle": "presets",
"ryzenAdj.psi0Current.label": "Limite de courant PSI0 (mA)",
"ryzenAdj.maxGfxclk.desc": "La vitesse d'horloge maximale à laquelle le GPU intégré (Vega) est autorisé à fonctionner.",
"ryzenAdj.minFclkFrequency.label": "Fréquence minimale du tissu à l'infini (Mhz)",
"ryzenAdj.slowLimit.label": "CPU Min TDP (W)",
"appContext.minimizeOnLaunch.shortDesc": "Lancer Ryzen Controller minimisé.",
"ryzenAdj.minGfxclk.label": "Fréquence d'horloge minimale du Vega iGPU (Mhz)",
"ryzenAdj.fastLimit.desc": "La limite de puissance du CPU pendant la période de boost.",
"appContext.minimizeToTray.name": "Réduire dans la barre d'état'.",
"ryzenAdj.psi0Current.desc": "La limite de courant que la carte mère est autorisé à livrer au PSI0.",
"ryzenAdj.minGfxclk.desc": "La vitesse d'horloge minimale à laquelle le GPU intégré (Vega) est autorisé à fonctionner.",
"ryzenAdj.fastLimit.label": "TDP de Boost CPU (W)",
"appContext.autoStartOnBoot.name": "Démarrage automatique",
"appContext.minimizeToTray.shortDesc": "Réduire Ryzen Controller à la barre d'état au lieu de la barre des tâches.",
"appContext.reApplyPeriodically.desc": "Sur certains ordinateurs portables, le bios réinitialise parfois ce que RyzenAdj essaie de faire. Vous pouvez l'utiliser pour appliquer les réglages périodiquement.",
"sceneSelector.releasesTitle": "Releases",
"ryzenAdj.tctlTemp.desc": "La température que le CPU peut atteindre avant la période de boost se désactive.",
"appContext.reApplyPeriodically.name": "Ré-appliquer RyzenAdj périodiquement",
"ryzenAdj.tctlTemp.label": "Limite de température (°C)",
"appContext.reApplyPeriodically.shortDesc": "Permet de réappliquer les paramètres de RyzenAdj périodiquement (toutes les X secondes).",
"PresetsScene.onlinePresetTitle": "presets en ligne",
"PresetsScene.localPresetTitle": "presets en local",
"PresetsScene.autoApplyTitle": "Application automatique de preset",
"SettingsScene.settingsTitle": "Paramètres",
"settingForm.currentPath": "Chemin actuel :",
"sysInfoCards.biosVersion": "Version du bios :",
"sysInfoCards.gpuPerfDesc": "{ram}Mb (RAM dynamique : {dyn}).",
"SettingsScene.sysInfoTitle": "Informations sur le système",
"sysInfoCards.basicInfoTitle": "Informations de base",
"settingForm.browseBtn": "Changer",
"SettingsScene.systemHashDesc": "Cela servira à assurer la compatibilité des presets téléchargés.",
"sysInfoCards.CPUInfoTitle": "Informations sur le CPU",
"sysInfoCards.cpuPerfDesc": "{speedmax}Ghz avec {physicalCores} cores et {cores} threads.",
"sysInfoCards.GPUInfoTitle": "Information sur le GPU #{index}",
"SettingsScene.loadingSysHash": "Chargement...",
"ryzenAdjBottomBar.prompt": "Nom du nouveau preset",
"ryzenAdjBottomBar.mustProvideName": "Vous devez renseigner un nom",
"ryzenAdjBottomBar.presetCreated": "preset \"{newPresetName}\" créé",
"ryzenAdjBottomBar.presetWithSameNameExist": "Un preset \"{newPresetName}\" existe déjà",
"presetButtons.apply": "Appliquer",
"presetButtons.delete": "Supprimer",
"presetButtons.load": "Charger",
"presetAutoApply.nonePreset": "Aucun",
"presetButtons.upload": "Upload",
"ryzenAdj.applySuccess": "RyzenAdj a été exécuté avec succès.",
"presetButtons.uploadSucceed": "Le preset {preset} a été uploadé",
"presetOnlineBtn.downloadTooltip": "Sauvegardera le preset dans vos preset en local.",
"presetOnlineBtn.loadTooltip": "Sans sauvegarder le preset, il sera chargé dans les onglets de RyzenAdj mais ne sera pas appliqué.",
"presetOnlineBtn.download": "Télécharger",
"presetOnlineBtn.load": "Charger",
"presetButtons.presetWithSameNameAlreadyExistOnline": "Un preset du même nom existe déjà en ligne",
"presetOnlineBtn.presetDownloaded": "Le preset \"{presetName}\" a été chargée",
"presetOnlineBtn.presetSameNameExist": "Vous avez déjà un preset du même nom",
"PresetsScene.cantVoteTwiceSamePreset": "Vous ne pouvez pas voter deux fois pour le même preset",
"PresetsScene.confirmVote": "Vous allez {vote} ce preset, êtes-vous sûr ?",
"PresetsScene.updatingVotes": "Mise à jour des votes...",
"notification.settingsSaveSuccess": "Les réglages ont été sauvegardés avec succès",
"ryzenAdjBottomBar.apply": "Appliquer",
"ryzenAdjBottomBar.reset": "Reset",
"ryzenAdjBottomBar.createPreset": "Créer preset",
"app.localeSelectorModalTitle": "Changer de langage"
}

102
src/locales/tr.json Normal file
View File

@ -0,0 +1,102 @@
{
"topbar.discord": "Discord'a katıl",
"topbar.beer": "Bağış yap ❤️",
"presetButtons.applyPresetTooltip": "Preset RyzenAdj sekmesinde yüklendi ve uygulandı",
"presetButtons.loadPresetTooltip": "Preset RyzenAdj sekmesinde yüklendi ama uygulanmadı",
"presetAutoApply.whenSessionResume": "Oturum mevcut ise",
"presetAutoApply.whenRCStart": "Ryzen Controller başlar ise",
"presetButtons.confirmDeletion": "\"{preset}\" kaldırılmasına eminmisin?",
"presetButtons.loadedPreset": "Preset {preset} yüklendi.",
"presetButtons.uploadPresetConfirmation": "Preset {preset} online yüklenmesine eminmisin?",
"presetListEmpty.youDontHaveAny": "Henüz Preset yok",
"presetListEmpty.sentencePart1": "Birini oluşturmak için, RyzenAdj Ayarlar sekmesindeki (CPU, GPU, ...)",
"presetListEmpty.createPresetBtn": "Preset oluştur",
"presetListEmpty.sentencePart2": "buttonuna tıkla.",
"PresetOnline.listNotLoadedYet": "Liste henüz yüklenilmedi veya hiçbir online Preset mevcut değil.",
"PresetOnline.loadPresetListBtn": "Preset Listeyi yükle",
"PresetOnline.sentencePart1": "Oluşturduğunuz Preset'leri Preset sekmesindeki",
"PresetOnline.uploadBtn": "Online yükle",
"PresetOnline.sentencePart2": "buttonu ile paylaşabilirsin.",
"sceneSelector.cpuTitle": "CPU",
"sceneSelector.gpuTitle": "GPU",
"ryzenAdj.maxGfxclk.label": "Maksimum Vega iGPU Saat Frekans (Mhz)",
"ryzenAdj.stapmTime.desc": "Skin Temperature Aware Power Management (STAPM). Bu kullanılacak Boost dönemini ayarlıyor.",
"ryzenAdj.slowLimit.desc": "CPU'nun Boost kullanılmadığı dönemde, en az kullanacak performans miktarı.",
"appContext.minimizeOnLaunch.name": "Minimieren",
"appContext.ryzenAdjPath.name": "RyzenAdj dizin yolu",
"sceneSelector.settingsTitle": "Ayarlar",
"sceneSelector.powerTitle": "Performans",
"ryzenAdj.slowTime.desc": "Bu Sokete (Boost kullanılmadığı dönemde) sürekli enerji sağlama dönemini ayarlıyor.",
"ryzenAdj.slowTime.label": "Package Power Tracking (PPT) - Slow period (Yavaş Dönem)",
"ryzenAdj.minFclkFrequency.desc": "Infinity Fabric AMD'nin Piyasadaki terimidir ve işlemci die'lerini bağlayan veri yolu bağlantısıdır. Bu ayar bağlantının (IF) minimum saat frekansını ayarlıyor.",
"ryzenAdj.maxFclkFrequency.desc": "Infinity Fabric AMD'nin Piyasadaki terimidir ve işlemci die'lerini bağlayan veri yolu bağlantısıdır. Bu ayar bağlantının (IF) maksimum saat frekansını ayarlıyor.",
"ryzenAdj.stapmLimit.desc": "Skin Temperature Aware Power Management (STAPM). Bu Soket power package limit'i ayarlıyor. Boost dönemi yönetimi ayarlıyor.",
"ryzenAdj.stapmTime.label": "CPU Boost Dönemi",
"ryzenAdj.vrmmaxCurrent.desc": "Anakarttan CPU'ya giden güncel limit",
"ryzenAdj.vrmmaxCurrent.label": "VRM Current (A)",
"appContext.ryzenAdjPath.shortDesc": "RyzenAdj Binary'nin dizin yolu.",
"ryzenAdj.maxFclkFrequency.label": "Maksimum Infinity Fabric Frekans (Mhz)",
"ryzenAdj.stapmLimit.label": "CPU TDP (W)",
"appContext.autoStartOnBoot.shortDesc": "Ryzen Controller uygulamasını System ile başlat.",
"sceneSelector.presetsTitle": "Preset'ler",
"ryzenAdj.psi0Current.label": "PSI0 Current Limit (mA)",
"ryzenAdj.maxGfxclk.desc": "Maksimum iGPU (Vega) Saat Frekans.",
"ryzenAdj.minFclkFrequency.label": "Minimum Infinity Fabric Frekans (Mhz)",
"ryzenAdj.slowLimit.label": "CPU Min TDP (W)",
"appContext.minimizeOnLaunch.shortDesc": "Ryzen Controller uygulamasını küçülterek başlat.",
"ryzenAdj.minGfxclk.label": "Minimum Vega iGPU Saat Frekans (Mhz)",
"ryzenAdj.fastLimit.desc": "CPU'nun Boost döneminde kullanabilecek performans miktarı.",
"appContext.minimizeToTray.name": "Bildirim alanına küçült",
"ryzenAdj.psi0Current.desc": "Anakarttan PSI0'ya giden limit.",
"ryzenAdj.minGfxclk.desc": "Minimum iGPU (Vega) Saat Frekansı.",
"ryzenAdj.fastLimit.label": "CPU Boost TDP (W)",
"appContext.autoStartOnBoot.name": "Açılışta otomatik başlatma",
"appContext.minimizeToTray.shortDesc": "Ryzen Controller uygulamasını bildirim alanına küçült.",
"appContext.reApplyPeriodically.desc": "Bazı Laptop BIOS ayarları RyzenAdj ayarlarını sıfırlıyor. Bu ayar ile ayarlanmış vakitlerde ayarlar otomatik şekilde tekrar uygulanabilir.",
"sceneSelector.releasesTitle": "Versiyonlar",
"ryzenAdj.tctlTemp.desc": "Boost kapanmadan CPU'nun ulaşabileceği sıcaklık.",
"appContext.reApplyPeriodically.name": "Periyodik olarak yeniden uygula",
"ryzenAdj.tctlTemp.label": "Sıcaklık Limit (°C)",
"appContext.reApplyPeriodically.shortDesc": "RyzenAdj ayarlarını periyodik yeniden uygulamasını sağlıyor (\"her X saniye\").",
"PresetsScene.onlinePresetTitle": "Online Preset'ler",
"PresetsScene.localPresetTitle": "Yerel Preset'ler",
"PresetsScene.autoApplyTitle": "Preset'leri bazı koşullarda uygula",
"SettingsScene.settingsTitle": "Ayarlar",
"settingForm.currentPath": "Güncel dizin yolu:",
"sysInfoCards.biosVersion": "BIOS Versiyonu:",
"sysInfoCards.gpuPerfDesc": "{ram}Mb (Dynamic vram: {dyn}).",
"SettingsScene.sysInfoTitle": "System Bilgi",
"sysInfoCards.basicInfoTitle": "Basic Bilgi",
"settingForm.browseBtn": "Seç",
"SettingsScene.systemHashDesc": "Bu bilgi, indirilmiş Preset'lerin bu system ve uygulama versiyon uygunluklarını sağlamak için kullanılıyor.",
"sysInfoCards.CPUInfoTitle": "CPU Bilgi",
"sysInfoCards.cpuPerfDesc": "{speedmax}Ghz, {physicalCores} çekirdek, {cores} threads.",
"sysInfoCards.GPUInfoTitle": "GPU #{index} Bilgi",
"SettingsScene.loadingSysHash": "Yükleniyor...",
"ryzenAdjBottomBar.prompt": "Yeni Preset adı",
"ryzenAdjBottomBar.mustProvideName": "Bir ad girilmesi lazım",
"ryzenAdjBottomBar.presetCreated": "Preset \"{newPresetName}\" oluşturuldu",
"ryzenAdjBottomBar.presetWithSameNameExist": "Yeni bir \"{newPresetName}\" adlı Preset zaten mevcut",
"presetButtons.apply": "Uygula",
"presetButtons.delete": "Kaldır",
"presetButtons.load": "Yükle",
"presetAutoApply.nonePreset": "Yok",
"presetButtons.upload": "Online yükle",
"ryzenAdj.applySuccess": "RyzenAdj başarıyla uygulandı.",
"presetButtons.uploadSucceed": "Preset {preset} online yüklendi",
"presetOnlineBtn.downloadTooltip": "Preset'i yerel (offline) Preset-Koleksiyonuna yüklüyor.",
"presetOnlineBtn.loadTooltip": "Yüklenilmez ise, Preset RyzenAdj sekmesine yüklenilir, ama uygulanılmaz.",
"presetOnlineBtn.download": "Indir",
"presetOnlineBtn.load": "Yükle",
"presetButtons.presetWithSameNameAlreadyExistOnline": "Aynı adlı bir Online Preset zaten mevcut",
"presetOnlineBtn.presetDownloaded": "Preset \"{presetName}\" yüklendi",
"presetOnlineBtn.presetSameNameExist": "Aynı adlı bir Preset zaten mevcut",
"PresetsScene.cantVoteTwiceSamePreset": "Bir Preset için bir den fazla oy verilemiyor",
"PresetsScene.confirmVote": "Bu Preset için {vote} oyu vermeye eminmisin?",
"PresetsScene.updatingVotes": "Oylar güncelleniyor...",
"notification.settingsSaveSuccess": "Ayarlar başarıyla kaydedildi",
"ryzenAdjBottomBar.apply": "Uygula",
"ryzenAdjBottomBar.createPreset": "Preset oluştur",
"ryzenAdjBottomBar.reset": "Sıfırla",
"app.localeSelectorModalTitle": "Dil değiştir"
}

123
src/react-app-env.d.ts vendored Normal file
View File

@ -0,0 +1,123 @@
/// <reference types="react-scripts" />
/* Add language here */
type AvailableLanguages = "en" | "fr" | "ch" | "de" | "tr";
type RyzenControllerTabForRyzenAdj = "power" | "cpu" | "gpu";
type RyzenAdjArguments =
| "--slow-time="
| "--psi0-current="
| "--vrmmax-current="
| "--min-gfxclk="
| "--max-gfxclk="
| "--min-fclk-frequency="
| "--max-fclk-frequency="
| "--tctl-temp="
| "--stapm-limit="
| "--stapm-time="
| "--fast-limit="
| "--slow-limit=";
type RyzenAdjConverter = "toHex" | "roundTen" | "toThousand" | null;
type RyzenAdjOptionDefinition = {
description: string;
label: string;
tab: RyzenControllerTabForRyzenAdj;
min: number;
max: number;
step: number;
default: number;
ryzenadj_arg: RyzenAdjArguments;
ryzenadj_value_convert: RyzenAdjConverter;
};
type RyzenAdjOptionValue = {
enabled: boolean;
value: number;
};
type RyzenAdjOptionListType = { [args in RyzenAdjArguments]: RyzenAdjOptionValue };
type RyzenAdjOptionListNamedType = { [name: string]: RyzenAdjOptionListType };
type PartialRyzenAdjOptionListType = { [args in RyzenAdjArguments]?: RyzenAdjOptionValue };
type PartialRyzenAdjOptionListNamedType = {
[name: string]: PartialRyzenAdjOptionListType;
};
type RyzenAdjOptionContextType = {
update(name: RyzenAdjArguments, value: Partial<RyzenAdjOptionValue>): void;
list: PartialRyzenAdjOptionListType;
};
type RyzenControllerSettingsNames =
| "autoStartOnBoot"
| "minimizeOnLaunch"
| "minimizeToTray"
| "reApplyPeriodically"
| "ryzenAdjPath"
| "onLaptopPluggedIn"
| "onLaptopPluggedOut"
| "onRCStart"
| "onSessionResume";
type RyzenControllerSettings = {
autoStartOnBoot: boolean;
minimizeOnLaunch: boolean;
minimizeToTray: boolean;
reApplyPeriodically: number | false;
ryzenAdjPath: string;
onLaptopPluggedIn: string | false;
onLaptopPluggedOut: string | false;
onRCStart: string | false;
onSessionResume: string | false;
};
type RyzenControllerSettingDefinition = {
displayTitle: boolean;
name: string;
short_description: string;
description?: string;
type: "boolean" | "range" | "path";
default: boolean | number | string;
compatibility: {
win32: boolean;
linux: boolean;
};
apply(value: boolean | number | string): Promise<string | boolean>;
};
type RyzenControllerSettingDefinitionList = {
[args in RyzenControllerSettingsNames]: RyzenControllerSettingDefinition;
};
type RyzenControllerAppContextType = {
latestSettings: RyzenAdjOptionListType;
currentSettings: RyzenAdjOptionListType;
presets: RyzenAdjOptionListNamedType;
settings: RyzenControllerSettings;
updateLatestSettings(): void;
updateCurrentSettings(list: PartialRyzenAdjOptionListType): void;
addPreset(name: string, preset: PartialRyzenAdjOptionListType): void;
removePreset(name: keyof RyzenAdjOptionListNamedType): void;
updateSettings(settings: Partial<RyzenControllerSettings>): void;
};
type ApiPreset = {
id: number;
systemHash: string;
upvote: number;
downvote: number;
name: string;
ryzenAdjArguments: RyzenAdjOptionListType;
};
type PresetsOnlineContextType = {
loading: boolean;
list: Array<ApiPreset>;
update(): void;
uploadPreset(preset: Partial<ApiPreset>): Promise<ApiPreset>;
upvote(presetId: number): Promise<ApiPreset>;
downvote(presetId: number): Promise<ApiPreset>;
};

220
src/scenes/PresetsScene.tsx Normal file
View File

@ -0,0 +1,220 @@
import * as React from "react";
import RyzenControllerAppContext from "../contexts/RyzenControllerAppContext";
import PresetListEmpty from "../components/PresetListEmpty";
import PresetLine from "../components/PresetLine";
import SceneTitle from "../components/SceneTitle";
import PresetAutoApplyCards from "../components/PresetAutoApplyCards";
import PresetOnline from "../components/PresetOnline";
import NotificationContext from "../contexts/NotificationContext";
import PresetsOnlineContext from "../contexts/PresetsOnline";
import { getTranslation } from "../contexts/LocaleContext";
const uikit = window.require("uikit");
class PresetsScene extends React.Component<{}, PresetsOnlineContextType> {
_isMounted = false;
state: PresetsOnlineContextType = {
loading: false,
list: [],
update: this.updatePresetList.bind(this),
uploadPreset: this.uploadPreset.bind(this),
upvote: this.upvote.bind(this),
downvote: this.downvote.bind(this),
};
__constructor() {
this.updatePresetList = this.updatePresetList.bind(this);
this.uploadPreset = this.uploadPreset.bind(this);
this.upvote = this.upvote.bind(this);
this.downvote = this.downvote.bind(this);
this.vote = this.vote.bind(this);
this.retainVotedPreset = this.retainVotedPreset.bind(this);
this.isUserAlreadyVotedForThisPreset = this.isUserAlreadyVotedForThisPreset.bind(this);
}
uploadPreset(preset: ApiPreset) {
const requestOption: RequestInit = {
method: "POST",
headers: {
accept: "application/ld+json",
"Content-Type": "application/ld+json",
},
body: JSON.stringify(preset),
};
return fetch(process.env.REACT_APP_SERVER_ENDPOINT + "/presets", requestOption)
.then(response => response.json())
.then((data: ApiPreset | any) => {
if (data?.violations?.length) {
data.violations.map((violation: any) => {
NotificationContext.error(`${violation.pathProperty}: ${violation.message}`);
return false;
});
throw new Error("Violation detected while POST request to API.");
}
if (data?.id) {
// @ts-ignore
let newSavedPreset: ApiPreset = data;
return newSavedPreset;
}
throw new Error("Unable to upload the preset.");
});
}
updatePresetList() {
this.setState({ loading: true });
const requestOption: RequestInit = {
method: "GET",
headers: {
accept: "application/json",
"Content-Type": "application/json",
},
};
fetch(process.env.REACT_APP_SERVER_ENDPOINT + "/presets", requestOption)
.then(response => response.json())
.then((data: Array<ApiPreset>) => {
if (this._isMounted) this.setState({ list: data, loading: false });
});
}
downvote(presetId: number): Promise<ApiPreset> {
if (this.isUserAlreadyVotedForThisPreset(presetId)) {
let message = getTranslation("PresetsScene.cantVoteTwiceSamePreset", "You can't vote twice for the same preset");
NotificationContext.warning(message);
return new Promise((res, rej) => {
rej(message);
});
}
let confirmMessage = getTranslation("PresetsScene.confirmVote", "Are you sure to {vote} this preset?", {
vote: "👎",
});
return uikit.modal.confirm(confirmMessage).then(() => {
return this.vote(presetId, "down");
});
}
upvote(presetId: number): Promise<ApiPreset> {
if (this.isUserAlreadyVotedForThisPreset(presetId)) {
let message = getTranslation("PresetsScene.cantVoteTwiceSamePreset", "You can't vote twice for the same preset");
NotificationContext.warning(message);
return new Promise((res, rej) => {
rej(message);
});
}
let confirmMessage = getTranslation("PresetsScene.confirmVote", "Are you sure to {vote} this preset?", {
vote: "👍",
});
return uikit.modal.confirm(confirmMessage).then(() => {
return this.vote(presetId, "up");
});
}
vote(presetId: number, action: "up" | "down"): Promise<ApiPreset> {
const url = `${process.env.REACT_APP_SERVER_ENDPOINT}/presets/${presetId}`;
const requestOptionGet: RequestInit = {
method: "GET",
headers: {
accept: "application/json",
"Content-Type": "application/json",
},
};
const requestOptionPatch: RequestInit = {
method: "PATCH",
headers: {
accept: "application/json",
"Content-Type": "application/merge-patch+json",
},
};
NotificationContext.talk(getTranslation("PresetsScene.updatingVotes", "Updating votes..."));
return fetch(url, requestOptionGet)
.then(response => response.json())
.then((data: ApiPreset) => {
const presetField = `${action}vote`;
const value = action === "up" ? data.upvote + 1 : data.downvote - 1;
const newPresetValue: Partial<ApiPreset> = {
[presetField]: value,
};
return fetch(url, {
...requestOptionPatch,
body: JSON.stringify(newPresetValue),
})
.then(response => response.json())
.then((data: ApiPreset) => {
this.retainVotedPreset(presetId);
this.updatePresetList();
return data;
});
})
.catch(error => {
NotificationContext.error(getTranslation("PresetsScene.errorWhileSendingVote", "Error while sending vote"));
throw new Error(error);
});
}
isUserAlreadyVotedForThisPreset(presetId: number): boolean {
const votedPresets: Array<number> = window.require("electron-settings").get("votedPresets");
if (!votedPresets) {
return false;
}
return votedPresets.indexOf(presetId) !== -1;
}
retainVotedPreset(presetId: number): void {
let votedPresets = window.require("electron-settings").get("votedPresets");
if (!votedPresets) {
votedPresets = [];
}
votedPresets.push(presetId);
window.require("electron-settings").set("votedPresets", votedPresets);
}
componentDidMount() {
this._isMounted = true;
this.updatePresetList();
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const onlinePresetTitle = getTranslation("PresetsScene.onlinePresetTitle", "Online Presets");
const localPresetTitle = getTranslation("PresetsScene.localPresetTitle", "Local Presets");
const autoApplyTitle = getTranslation("PresetsScene.autoApplyTitle", "Auto apply preset");
return (
<PresetsOnlineContext.Provider value={this.state}>
<RyzenControllerAppContext.Consumer>
{(ryzenControllerAppContext: RyzenControllerAppContextType) => {
if (Object.entries(ryzenControllerAppContext.presets).length <= 0) {
return (
<React.Fragment>
<PresetListEmpty />
<SceneTitle title={onlinePresetTitle} />
<PresetOnline />
</React.Fragment>
);
}
const presetNames = Object.keys(ryzenControllerAppContext.presets);
return (
<React.Fragment>
<SceneTitle title={localPresetTitle} />
<ul className="uk-margin uk-list uk-list-large uk-list-striped">
{presetNames.map(presetName => {
const preset = ryzenControllerAppContext.presets[presetName];
return <PresetLine key={`0_${presetName}`} presetName={presetName} preset={preset} />;
})}
</ul>
<SceneTitle title={onlinePresetTitle} />
<PresetOnline />
<SceneTitle title={autoApplyTitle} />
<PresetAutoApplyCards />
</React.Fragment>
);
}}
</RyzenControllerAppContext.Consumer>
</PresetsOnlineContext.Provider>
);
}
}
export default PresetsScene;

View File

@ -0,0 +1,14 @@
import * as React from "react";
import RyzenAdjOptionList from "../components/RyzenAdjOptionList";
import RyzenAdjBottomBar from "../components/RyzenAdjBottomBar";
function RyzenAdjScene(props: { filter: RyzenControllerTabForRyzenAdj }) {
return (
<div className="uk-container uk-container-expend">
<RyzenAdjOptionList filter={props.filter} />
<RyzenAdjBottomBar />
</div>
);
}
export default RyzenAdjScene;

193
src/scenes/Scene.tsx Normal file
View File

@ -0,0 +1,193 @@
import * as React from "react";
import { Route, Switch, Redirect } from "react-router-dom";
import RyzenControllerAppContext, {
defaultRyzenControllerAppContext,
defaultPreset,
persistentSave,
getSettingDefinition,
app_version_as_string,
executeRyzenAdjUsingPreset,
} from "../contexts/RyzenControllerAppContext";
import RyzenAdjScene from "../scenes/RyzenAdjScene";
import PresetsScene from "../scenes/PresetsScene";
import SettingsScene from "../scenes/SettingsScene";
import NotificationContext from "../contexts/NotificationContext";
import { getTranslation } from "../contexts/LocaleContext";
const electronSettings = window.require("electron-settings");
const powerMonitor = window.require("electron").remote.powerMonitor;
class Scene extends React.Component<{}, RyzenControllerAppContextType> {
state: RyzenControllerAppContextType = {
...defaultRyzenControllerAppContext,
updateLatestSettings: this.updateLatestSettings.bind(this),
updateCurrentSettings: this.updateCurrentSettings.bind(this),
addPreset: this.addPreset.bind(this),
removePreset: this.removePreset.bind(this),
updateSettings: this.updateSettings.bind(this),
};
componentDidMount() {
let settings = electronSettings.get(app_version_as_string);
if (settings) {
settings = settings.settings;
} else {
settings = defaultRyzenControllerAppContext.settings;
}
let newSettingsPromises: Array<Promise<string | boolean>> = [];
for (const key in settings) {
if (settings.hasOwnProperty(key)) {
// @ts-ignore
const arg: RyzenControllerSettingsNames = key;
const settingValue = settings[arg];
const settingDef = getSettingDefinition(arg);
if (settingDef && typeof settingValue !== "undefined") {
newSettingsPromises.push(settingDef.apply(settingValue));
}
if (arg === "onRCStart" && settingValue !== false && settingValue !== "") {
executeRyzenAdjUsingPreset(settingValue);
}
}
}
if (newSettingsPromises.length > 0) {
Promise.all(newSettingsPromises).catch((error: string) => {
NotificationContext.error(error);
});
}
powerMonitor.on("unlock-screen", () => {
const presetName = electronSettings.get(app_version_as_string)?.settings?.onSessionResume;
if (presetName) {
executeRyzenAdjUsingPreset(presetName);
}
});
if (window.require("os").platform() === "win32") {
this.handleBatteryStatusChange();
}
}
componentDidUpdate() {
persistentSave(this.state);
}
handleBatteryStatusChange() {
powerMonitor.on("on-ac", () => {
const presetName = electronSettings.get(app_version_as_string)?.settings?.onLaptopPluggedIn;
if (presetName !== false && presetName !== "") {
executeRyzenAdjUsingPreset(presetName);
}
});
powerMonitor.on("on-battery", () => {
const presetName = electronSettings.get(app_version_as_string)?.settings?.onLaptopPluggedOut;
if (presetName !== false && presetName !== "") {
executeRyzenAdjUsingPreset(presetName);
}
});
}
updateLatestSettings() {
const newLatestSettings: RyzenAdjOptionListType = {
...defaultPreset,
...this.state.currentSettings,
};
this.setState({
latestSettings: newLatestSettings,
});
}
updateCurrentSettings(list: PartialRyzenAdjOptionListType) {
const newCurrentSettings: RyzenAdjOptionListType = {
...defaultPreset,
...this.state.currentSettings,
...list,
};
this.setState({
currentSettings: newCurrentSettings,
});
}
addPreset(name: string, preset: PartialRyzenAdjOptionListType) {
const newPreset = {
[name]: {
...defaultPreset,
...preset,
},
};
if (this.state.presets.hasOwnProperty(name)) {
// @TODO handle preset creation when name already existing.
}
this.setState({
presets: {
...this.state.presets,
...newPreset,
},
});
}
removePreset(name: keyof RyzenAdjOptionListNamedType) {
let newState = { ...this.state };
delete newState.presets[name];
this.setState(newState);
}
updateSettings(settings: Partial<RyzenControllerSettings>) {
let newSettings = {
...this.state.settings,
...settings,
};
let newSettingsPromises: Array<Promise<string | boolean>> = [];
for (const key in settings) {
if (settings.hasOwnProperty(key)) {
// @ts-ignore
const arg: RyzenControllerSettingsNames = key;
const newSettingValue = settings[arg];
const settingDef = getSettingDefinition(arg);
if (settingDef && typeof newSettingValue !== "undefined") {
newSettingsPromises.push(settingDef.apply(newSettingValue));
}
}
}
if (newSettingsPromises.length > 0) {
Promise.all(newSettingsPromises)
.then(results => {
NotificationContext.success(
getTranslation("notification.settingsSaveSuccess", "Settings has been saved successfully"),
"settings_applied"
);
this.setState({ settings: newSettings });
})
.catch((error: string) => {
NotificationContext.error(error);
});
}
}
render() {
return (
<RyzenControllerAppContext.Provider value={this.state}>
<Switch>
<Redirect exact from="/" to="/cpu" />
<Route exact path="/cpu" render={() => <RyzenAdjScene filter="cpu" />} />
<Route exact path="/gpu" render={() => <RyzenAdjScene filter="gpu" />} />
<Route exact path="/power" render={() => <RyzenAdjScene filter="power" />} />
<Route exact path="/presets">
<PresetsScene />
</Route>
<Route exact path="/settings">
<SettingsScene />
</Route>
</Switch>
</RyzenControllerAppContext.Provider>
);
}
}
export default Scene;

View File

@ -0,0 +1,37 @@
import * as React from "react";
import SysInfoCards from "../components/SysInfoCards";
import SceneTitle from "../components/SceneTitle";
import SettingsList from "../components/SettingsList";
import SysInfoContext from "../contexts/SysInfoContext";
import { getTranslation } from "../contexts/LocaleContext";
class SettingsScene extends React.PureComponent<{}, {}> {
render() {
return (
<React.Fragment>
<SceneTitle title={getTranslation("SettingsScene.settingsTitle", "Settings")} />
<SettingsList />
<SceneTitle
title={getTranslation("SettingsScene.sysInfoTitle", "System Info")}
className="uk-margin-remove-bottom"
/>
<SysInfoCards />
<SysInfoContext.Consumer>
{sysInfoContext => (
<p
className="uk-text-small uk-text-italic uk-margin-left uk-margin-remove-bottom"
uk-tooltip={`pos: top-left; title: ${getTranslation(
"SettingsScene.systemHashDesc",
"This will be used to ensure downloaded presets compatibility."
)}`}
>
System hash: {sysInfoContext.signature || getTranslation("SettingsScene.loadingSysHash", "Loading...")}
</p>
)}
</SysInfoContext.Consumer>
</React.Fragment>
);
}
}
export default SettingsScene;

5
src/setupTests.js Normal file
View File

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom/extend-expect";

25
tsconfig.json Normal file
View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": [
"src"
]
}

BIN
vendor/7z/7z.dll vendored

Binary file not shown.

BIN
vendor/7z/7z.exe vendored

Binary file not shown.

BIN
vendor/screencast.webm vendored

Binary file not shown.

12756
yarn.lock Normal file

File diff suppressed because it is too large Load Diff