├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── support.md ├── dependabot.yml ├── stale.yml └── workflows │ ├── approve-and-merge-dependency-updates.yaml │ └── build.yaml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .stylelintignore ├── .stylelintrc.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── angular.json ├── docs ├── css │ ├── icomoon.css │ ├── ionicons.min.css │ ├── style.css │ └── theme.css ├── favicon.ico ├── fonts │ ├── icomoon │ │ ├── icomoon.eot │ │ ├── icomoon.svg │ │ ├── icomoon.ttf │ │ └── icomoon.woff │ └── ionicons │ │ ├── css │ │ ├── _ionicons.scss │ │ └── ionicons.min.css │ │ └── fonts │ │ ├── ionicons.eot │ │ ├── ionicons.svg │ │ ├── ionicons.ttf │ │ ├── ionicons.woff │ │ └── ionicons.woff2 ├── images │ ├── icon-main-title.svg │ ├── made-in-berlin.png │ └── main.png ├── index.html └── js │ ├── lib │ ├── aos.min.js │ ├── bootstrap.min.js │ ├── jquery-3.2.1.min.js │ ├── jquery-migrate-3.0.1.min.js │ ├── jquery.easing.1.3.js │ ├── jquery.min.js │ ├── jquery.stellar.min.js │ ├── jquery.waypoints.min.js │ ├── owl.carousel.min.js │ └── scrollax.min.js │ └── main.js ├── eslint.config.js ├── main.js ├── package-lock.json ├── package.json ├── screenshots ├── adjust_parameters_print.png ├── adjust_temperatures_main_screen.png ├── babystep_z.png ├── cancel.png ├── control.png ├── control_confirmation.png ├── error_message.png ├── filament_extruding.png ├── filament_heating.png ├── filament_purging.png ├── filament_selection.png ├── file_details.png ├── file_loaded.png ├── files_view.png ├── job.png ├── main-screen.png ├── no_job_no_touchscreen.png ├── paused.png ├── print_controls.png ├── settings.png └── sleeping.png ├── scripts ├── install.sh ├── remove.sh └── update.sh ├── src ├── app.module.ts ├── app.routing.module.ts ├── assets │ ├── adjust.svg │ ├── animations │ │ ├── checkmark.json │ │ ├── loading.json │ │ └── toggle-switch.json │ ├── cancel.svg │ ├── connect.svg │ ├── control.svg │ ├── fan.svg │ ├── filament.svg │ ├── folder.svg │ ├── fonts │ │ ├── Arvo-Bold.ttf │ │ ├── Cousine-Regular.ttf │ │ ├── Montserrat-Medium.ttf │ │ └── Montserrat-Regular.ttf │ ├── heat-bed.svg │ ├── heat.svg │ ├── height.svg │ ├── icon │ │ ├── icon-alt-dark-title.svg │ │ ├── icon-alt-dark.svg │ │ ├── icon-alt-title.svg │ │ ├── icon-alt.svg │ │ ├── icon-main-dark-title.svg │ │ ├── icon-main-dark.svg │ │ ├── icon-main-title.svg │ │ ├── icon-main.svg │ │ └── icon.png │ ├── invalid-config.svg │ ├── made-in-berlin.png │ ├── nozzle.svg │ ├── object.svg │ ├── pause.svg │ └── resume.svg ├── components │ ├── action-center │ │ ├── action-center.component.html │ │ ├── action-center.component.scss │ │ └── action-center.component.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.ts │ ├── control │ │ ├── control.component.html │ │ ├── control.component.scss │ │ └── control.component.ts │ ├── filament │ │ ├── change-filament │ │ │ ├── change-filament.component.html │ │ │ ├── change-filament.component.scss │ │ │ └── change-filament.component.ts │ │ ├── choose-filament │ │ │ ├── choose-filament.component.html │ │ │ ├── choose-filament.component.scss │ │ │ └── choose-filament.component.ts │ │ ├── choose-tool │ │ │ ├── choose-tool.component.html │ │ │ ├── choose-tool.component.scss │ │ │ └── choose-tool.component.ts │ │ ├── filament.component.html │ │ ├── filament.component.scss │ │ ├── filament.component.ts │ │ ├── heat-nozzle │ │ │ ├── heat-nozzle.component.html │ │ │ ├── heat-nozzle.component.scss │ │ │ └── heat-nozzle.component.ts │ │ ├── move-filament │ │ │ ├── move-filament.component.html │ │ │ ├── move-filament.component.scss │ │ │ └── move-filament.component.ts │ │ └── purge-filament │ │ │ ├── purge-filament.component.html │ │ │ ├── purge-filament.component.scss │ │ │ └── purge-filament.component.ts │ ├── files │ │ ├── files.component.html │ │ ├── files.component.scss │ │ └── files.component.ts │ ├── index.ts │ ├── main-screen │ │ ├── bottom-bar │ │ │ ├── bottom-bar.component.html │ │ │ ├── bottom-bar.component.scss │ │ │ └── bottom-bar.component.ts │ │ ├── job-status │ │ │ ├── height-progress │ │ │ │ ├── height-progress.component.html │ │ │ │ ├── height-progress.component.scss │ │ │ │ └── height-progress.component.ts │ │ │ ├── job-status.component.html │ │ │ ├── job-status.component.scss │ │ │ └── job-status.component.ts │ │ ├── main-menu │ │ │ ├── main-menu.component.html │ │ │ ├── main-menu.component.scss │ │ │ └── main-menu.component.ts │ │ ├── main-screen.component.html │ │ ├── main-screen.component.ts │ │ ├── print-control │ │ │ ├── print-control.component.html │ │ │ ├── print-control.component.scss │ │ │ └── print-control.component.ts │ │ └── printer-status │ │ │ ├── printer-status.component.html │ │ │ ├── printer-status.component.scss │ │ │ └── printer-status.component.ts │ ├── notification │ │ ├── notification.component.html │ │ ├── notification.component.scss │ │ └── notification.component.ts │ ├── reset │ │ ├── reset.component.html │ │ ├── reset.component.scss │ │ └── reset.component.ts │ ├── settings │ │ ├── settings-icon │ │ │ ├── settings-icon.component.html │ │ │ ├── settings-icon.component.scss │ │ │ └── settings-icon.component.ts │ │ ├── settings.component.html │ │ ├── settings.component.scss │ │ └── settings.component.ts │ ├── setup │ │ ├── discover-octoprint │ │ │ ├── discover-octoprint.component.html │ │ │ ├── discover-octoprint.component.scss │ │ │ └── discover-octoprint.component.ts │ │ ├── extruder-information │ │ │ ├── extruder-information.component.html │ │ │ ├── extruder-information.component.scss │ │ │ └── extruder-information.component.ts │ │ ├── invalid-config │ │ │ ├── invalid-config.component.html │ │ │ ├── invalid-config.component.scss │ │ │ └── invalid-config.component.ts │ │ ├── octoprint-authentication │ │ │ ├── octoprint-authentication.component.html │ │ │ ├── octoprint-authentication.component.scss │ │ │ ├── octoprint-authentication.component.ts │ │ │ └── octoprint-authentication.service.ts │ │ ├── personalization │ │ │ ├── personalization.component.html │ │ │ ├── personalization.component.scss │ │ │ ├── personalization.component.ts │ │ │ └── personalization.service.ts │ │ ├── plugins │ │ │ ├── plugins.component.html │ │ │ ├── plugins.component.scss │ │ │ └── plugins.component.ts │ │ ├── setup.component.html │ │ ├── setup.component.scss │ │ ├── setup.component.ts │ │ └── welcome │ │ │ ├── welcome.component.html │ │ │ ├── welcome.component.scss │ │ │ └── welcome.component.ts │ ├── shared │ │ ├── hotend-icon │ │ │ ├── hotend-icon.component.html │ │ │ ├── hotend-icon.component.scss │ │ │ └── hotend-icon.component.ts │ │ ├── quick-control │ │ │ ├── quick-control.component.html │ │ │ ├── quick-control.component.scss │ │ │ └── quick-control.component.ts │ │ ├── toggle-switch │ │ │ ├── toggle-switch.component.html │ │ │ ├── toggle-switch.component.scss │ │ │ └── toggle-switch.component.ts │ │ └── top-bar │ │ │ ├── top-bar.component.html │ │ │ ├── top-bar.component.scss │ │ │ └── top-bar.component.ts │ ├── standby │ │ ├── standby.component.html │ │ ├── standby.component.scss │ │ └── standby.component.ts │ └── update │ │ ├── update.component.html │ │ ├── update.component.scss │ │ └── update.component.ts ├── directives │ ├── index.ts │ └── long-press.directive.ts ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── helper │ ├── config.js │ ├── config.schema.json │ ├── discover.js │ ├── listener.js │ ├── locale.js │ ├── protocol.js │ ├── styles.js │ └── update.js ├── index.html ├── locale │ ├── messages.da.xlf │ ├── messages.de.xlf │ ├── messages.fr.xlf │ └── messages.xlf ├── main.ts ├── model │ ├── auth.model.ts │ ├── enclosure.model.ts │ ├── event.model.ts │ ├── filament.model.ts │ ├── files.model.ts │ ├── index.ts │ ├── job.model.ts │ ├── octoprint │ │ ├── auth.model.ts │ │ ├── connection.model.ts │ │ ├── file.model.ts │ │ ├── index.ts │ │ ├── job.model.ts │ │ ├── plugins │ │ │ ├── companion.model.ts │ │ │ ├── display-layer-progress.model.ts │ │ │ ├── enclosure.model.ts │ │ │ ├── filament-manager.model.ts │ │ │ ├── ophomplugstatus.model.ts │ │ │ ├── psucontrol.model.ts │ │ │ ├── spool-manager.model.ts │ │ │ ├── tasmota-mqtt.model.ts │ │ │ ├── tasmota.model.ts │ │ │ ├── tp-link.model.ts │ │ │ ├── tuya.model.ts │ │ │ └── wemo.model.ts │ │ ├── printer-commands.model.ts │ │ ├── printer-profile.model.ts │ │ └── socket.model.ts │ ├── printer-profile.model.ts │ ├── printer.model.ts │ ├── system.model.ts │ └── util-test.model.ts ├── pipes │ ├── index.ts │ └── url.pipe.ts ├── reset.css ├── services │ ├── app.service.ts │ ├── config.service.ts │ ├── conversion.service.ts │ ├── electron.service.ts │ ├── enclosure │ │ ├── enclosure.octoprint.service.ts │ │ └── enclosure.service.ts │ ├── event.service.ts │ ├── filament │ │ ├── filament-manager.octoprint.service.ts │ │ ├── filament-plugin.service.ts │ │ ├── filament.service.ts │ │ └── spool-manager.octoprint.service.ts │ ├── files │ │ ├── files.octoprint.service.ts │ │ └── files.service.ts │ ├── index.ts │ ├── job │ │ ├── job.octoprint.service.ts │ │ └── job.service.ts │ ├── notification.service.ts │ ├── printer │ │ ├── printer.octoprint.service.ts │ │ └── printer.service.ts │ ├── socket │ │ ├── socket.octoprint.service.ts │ │ └── socket.service.ts │ └── system │ │ ├── system.octoprint.service.ts │ │ └── system.service.ts └── styles.scss ├── themes ├── NOX │ ├── README.md │ ├── custom-styles.css │ └── screenshots │ │ ├── screenshot_file-loaded.png │ │ ├── screenshot_file.png │ │ ├── screenshot_files.png │ │ ├── screenshot_main-screen.png │ │ ├── screenshot_printing1.png │ │ └── screenshot_printing2.png ├── dark-nights │ ├── README.md │ ├── custom-styles.css │ └── screenshots │ │ ├── dashboard.png │ │ ├── filament-purge.png │ │ ├── filament-temp.png │ │ ├── file-browser.png │ │ ├── file-preview.png │ │ ├── file-selection.png │ │ ├── printing-adjust.png │ │ ├── printing-percent.png │ │ ├── printing-preview.png │ │ └── temp-adjust.png ├── square │ ├── README.md │ └── custom-styles.css └── theGarbz │ ├── BigFingers │ ├── README.md │ ├── custom-styles.css │ └── screenshots │ │ ├── icon.png │ │ ├── screenshot_filaments.png │ │ ├── screenshot_files.png │ │ └── screenshot_settings.png │ ├── Focus │ ├── README.md │ ├── custom-styles.css │ └── screenshots │ │ ├── icon.png │ │ ├── screenshot_adjust.png │ │ ├── screenshot_error.png │ │ ├── screenshot_filequeue.png │ │ ├── screenshot_fileselect.png │ │ ├── screenshot_main.png │ │ ├── screenshot_menu.png │ │ ├── screenshot_print.png │ │ └── screenshot_print2.png │ ├── Glanceable │ ├── README.md │ ├── custom-styles.css │ └── screenshots │ │ ├── icon.png │ │ ├── screenshot_printing_circle.png │ │ └── screenshot_printing_straight.png │ └── README.md ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://paypal.me/TimonGaebelein 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | --- 7 | 8 | **Describe the bug** 9 | A clear and concise description of what the bug is. 10 | 11 | **To Reproduce** 12 | Steps to reproduce the behavior: 13 | 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **General Information:** 26 | 27 | - Hardware [e.g. Raspberry Pi, if you have layout issues please also include your screen resolution] 28 | - OS Info [e.g. Raspbian Buster Lite, please also indicate if you used OctoPi] 29 | - OctoDash Version [e.g. v1.0.0] 30 | - OctoPrint Version [e.g. v1.0.0] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. If applicable also link other issues / bugs here. 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Link other projects** 16 | If you want OctoDash to include other OctoPrint plugins or similar please link them here. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support 3 | about: Something is not working or you have a question about your setup 4 | title: '' 5 | labels: support 6 | assignees: '' 7 | --- 8 | 9 | **What doesn't work?** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **What did you already try?** 13 | A clear and concise description of what you tried to make OctoDash work. 14 | 15 | **General Information:** 16 | 17 | - Hardware [e.g. Raspberry Pi, if you have layout issues please also include your screen resolution] 18 | - OS Info [e.g. Raspbian Buster Lite, please also indicate if you used OctoPi] 19 | - OctoDash Version [e.g. v1.0.0] 20 | - OctoPrint Version [e.g. v1.0.0] 21 | 22 | **Additional context** 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: '/' 5 | schedule: 6 | interval: daily 7 | time: '10:00' 8 | open-pull-requests-limit: 15 9 | labels: 10 | - dependencies 11 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 14 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 2 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - enhancement 10 | - bug 11 | - documentation 12 | - code 13 | # Label to use when marking an issue as stale 14 | staleLabel: stale 15 | # Comment to post when marking an issue as stale. Set to `false` to disable 16 | markComment: > 17 | This issue has been automatically marked as stale because it has not had 18 | recent activity. It will be closed if no further activity occurs. Thank you 19 | for your contributions. 20 | # Comment to post when closing a stale issue. Set to `false` to disable 21 | closeComment: false 22 | -------------------------------------------------------------------------------- /.github/workflows/approve-and-merge-dependency-updates.yaml: -------------------------------------------------------------------------------- 1 | name: Dependencies 2 | 3 | on: 4 | pull_request_target: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | auto-approve: 10 | name: Approve 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Auto Approve 14 | uses: hmarr/auto-approve-action@v4 15 | if: github.actor == 'dependabot[bot]' 16 | with: 17 | github-token: '${{ secrets.GITHUB_TOKEN }}' 18 | auto-merge: 19 | name: Merge 20 | needs: auto-approve 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Auto Merge 24 | uses: pascalgn/automerge-action@v0.16.4 25 | if: github.actor == 'dependabot[bot]' 26 | env: 27 | GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' 28 | MERGE_METHOD: 'squash' 29 | MERGE_LABELS: 'dependencies' 30 | MERGE_RETRIES: 15 31 | MERGE_RETRY_SLEEP: 60000 32 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout Repository 18 | uses: actions/checkout@v4 19 | - name: Cache node modules 20 | uses: actions/cache@v4 21 | env: 22 | cache-name: node-modules 23 | with: 24 | path: '**/node_modules' 25 | key: octodash-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 26 | restore-keys: | 27 | octodash-build-${{ env.cache-name }}- 28 | - name: Use Node.js 29 | uses: actions/setup-node@v4 30 | with: 31 | node-version: '22' 32 | - name: Install latest npm 33 | run: sudo npm install -g npm@latest 34 | - name: Installing Dependencies 35 | run: npm ci 36 | - name: Linting Application 37 | run: npm run lint 38 | - name: Checking if locale is updated 39 | run: |- 40 | npm run locale:extract 41 | if [ "$(git diff --name-only)" ]; then 42 | echo "" 43 | echo "ERROR! Locale file update detected! Please run 'npm run locale:update'." 44 | echo "" 45 | exit 1 46 | fi 47 | 48 | build: 49 | runs-on: ubuntu-latest 50 | 51 | steps: 52 | - name: Checkout Repository 53 | uses: actions/checkout@v4 54 | - name: Cache node modules 55 | uses: actions/cache@v4 56 | env: 57 | cache-name: node-modules 58 | with: 59 | path: '**/node_modules' 60 | key: octodash-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 61 | restore-keys: | 62 | octodash-build-${{ env.cache-name }}- 63 | - name: Use Node.js 64 | uses: actions/setup-node@v4 65 | with: 66 | node-version: '22' 67 | - name: Install latest npm 68 | run: sudo npm install -g npm@latest 69 | - name: Installing Dependencies 70 | run: npm ci 71 | - name: Building Application 72 | run: npm run build 73 | - name: Packaging Application 74 | run: npm run electron:pack 75 | env: 76 | GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' 77 | - name: Upload artifacts 78 | uses: actions/upload-artifact@v4 79 | with: 80 | name: build 81 | path: | 82 | package/*.deb 83 | package/*.yaml 84 | package/*.yml 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # System Files 4 | .DS_Store 5 | Thumbs.db 6 | 7 | # Generated files 8 | yarn.lock 9 | .angular 10 | npm-debug.log 11 | yarn-error.log 12 | testem.log 13 | **/config.testing.json 14 | src/locale/backups 15 | 16 | # compiled output 17 | /dist 18 | /package 19 | /tmp 20 | /out-tsc 21 | # Only exists if Bazel was run 22 | /bazel-out 23 | 24 | # dependencies 25 | **/node_modules 26 | 27 | # profiling files 28 | chrome-profiler-events.json 29 | speed-measure-plugin.json 30 | 31 | # IDEs and editors 32 | /.idea 33 | .project 34 | .classpath 35 | .c9/ 36 | *.launch 37 | .settings/ 38 | *.sublime-workspace 39 | 40 | # IDE - VSCode 41 | .vscode 42 | .vscode/* 43 | !.vscode/settings.json 44 | !.vscode/tasks.json 45 | !.vscode/launch.json 46 | !.vscode/extensions.json 47 | .history/* 48 | 49 | # misc 50 | /.sass-cache 51 | /connect.lock 52 | /coverage 53 | /libpeerconnection.log 54 | /typings 55 | 56 | 57 | docs/images/compilation.psd 58 | scripts/clean-github.sh 59 | src/model/config.model.ts 60 | src/helper/config.default.json -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | LICENSE.md 2 | *.min.* 3 | build 4 | dist 5 | 6 | docs/css/icomoon.css 7 | docs/css/theme.css 8 | src/reset.css 9 | src/assets/animations/ 10 | docs/fonts/ionicons 11 | docs/js/lib/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "arrowParens": "avoid", 5 | "bracketSpacing": true, 6 | "endOfLine": "lf", 7 | "bracketSameLine": true, 8 | "printWidth": 120, 9 | "semi": true, 10 | "singleQuote": true, 11 | "trailingComma": "all", 12 | "htmlWhitespaceSensitivity": "css" 13 | } 14 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | *.min.* 3 | docs/css/icomoon.css 4 | docs/css/theme.css 5 | docs/fonts/ionicons/ 6 | src/reset.css -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard-scss"], 3 | "rules": { 4 | "selector-class-pattern": null, 5 | "selector-id-pattern": null, 6 | "no-descending-specificity": null, 7 | "selector-pseudo-element-no-unknown": [ 8 | true, 9 | { 10 | "ignorePseudoElements": ["ng-deep"] 11 | } 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | .main-icon { 2 | width: 35%; 3 | margin-bottom: 6%; 4 | margin-top: 30%; 5 | } 6 | 7 | .icon-github::before { 8 | line-height: 190%; 9 | } 10 | 11 | .main-section { 12 | height: 95vh; 13 | } 14 | 15 | .navbar-brand { 16 | font-family: Arvo, sans-serif; 17 | font-weight: 600; 18 | } 19 | 20 | .btn { 21 | font-size: 150%; 22 | } 23 | 24 | .first-heading-title { 25 | margin-top: 10%; 26 | } 27 | 28 | .heading-title { 29 | margin-top: 3%; 30 | } 31 | 32 | p { 33 | font-size: 119%; 34 | } 35 | 36 | h5 { 37 | text-align: center; 38 | font-size: 150%; 39 | line-height: 300%; 40 | } 41 | 42 | .install-script { 43 | text-align: center; 44 | font-size: 120%; 45 | font-family: Cousine, monospace; 46 | background-color: #252932; 47 | padding: 2vh; 48 | border-radius: 8px; 49 | margin-bottom: 0.5vh; 50 | display: block; 51 | white-space: nowrap; 52 | width: auto; 53 | overflow-y: scroll; 54 | } 55 | 56 | .carousel-item { 57 | background-size: contain; 58 | margin-top: 1.5%; 59 | margin-bottom: 3.5%; 60 | } 61 | 62 | .carousel-inner { 63 | background-color: #252932; 64 | } 65 | 66 | h4 { 67 | float: left; 68 | margin-right: 15px; 69 | } 70 | 71 | .nav-item > a { 72 | line-height: 250%; 73 | } 74 | 75 | .nav-pills > li { 76 | width: 100%; 77 | } 78 | 79 | .main-button { 80 | padding-bottom: 1rem !important; 81 | } 82 | 83 | .subtitle { 84 | width: 100%; 85 | } 86 | 87 | .myaccordion .btn { 88 | text-transform: none; 89 | text-align: left; 90 | } 91 | 92 | .iframe-wrapper { 93 | position: relative; 94 | overflow: hidden; 95 | padding-top: 40%; 96 | } 97 | 98 | .iframe { 99 | position: absolute; 100 | top: 0; 101 | left: 0; 102 | width: 100%; 103 | height: 100%; 104 | border: 0; 105 | } 106 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/docs/favicon.ico -------------------------------------------------------------------------------- /docs/fonts/icomoon/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/docs/fonts/icomoon/icomoon.eot -------------------------------------------------------------------------------- /docs/fonts/icomoon/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/docs/fonts/icomoon/icomoon.ttf -------------------------------------------------------------------------------- /docs/fonts/icomoon/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/docs/fonts/icomoon/icomoon.woff -------------------------------------------------------------------------------- /docs/fonts/ionicons/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/docs/fonts/ionicons/fonts/ionicons.eot -------------------------------------------------------------------------------- /docs/fonts/ionicons/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/docs/fonts/ionicons/fonts/ionicons.ttf -------------------------------------------------------------------------------- /docs/fonts/ionicons/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/docs/fonts/ionicons/fonts/ionicons.woff -------------------------------------------------------------------------------- /docs/fonts/ionicons/fonts/ionicons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/docs/fonts/ionicons/fonts/ionicons.woff2 -------------------------------------------------------------------------------- /docs/images/made-in-berlin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/docs/images/made-in-berlin.png -------------------------------------------------------------------------------- /docs/images/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/docs/images/main.png -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { fileURLToPath } from 'node:url'; 3 | 4 | import electron from 'electron'; 5 | 6 | import activateListeners from './src/helper/listener.js'; 7 | import { getLocale } from './src/helper/locale.js'; 8 | import createProtocol from './src/helper/protocol.js'; 9 | 10 | const { app, BrowserWindow, ipcMain, protocol, screen } = electron; 11 | 12 | let window; 13 | let locale; 14 | let url; 15 | 16 | const dev = !!process.env.APP_DEV; 17 | const __dirname = fileURLToPath(new URL('.', import.meta.url)); 18 | 19 | if (!dev) { 20 | const scheme = 'app'; 21 | 22 | protocol.registerSchemesAsPrivileged([{ scheme: scheme, privileges: { standard: true } }]); 23 | createProtocol(scheme, path.join(__dirname, 'dist')); 24 | 25 | locale = getLocale(); 26 | } 27 | 28 | // Fixes rendering glitches on Raspberry Pi + Electron v27+ 29 | app.disableHardwareAcceleration(); 30 | 31 | app.commandLine.appendSwitch('touch-events', 'enabled'); 32 | 33 | function createWindow() { 34 | // if (!dev) { 35 | // session.defaultSession.webRequest.onHeadersReceived((details, callback) => { 36 | // callback({ 37 | // responseHeaders: { 38 | // ...details.responseHeaders, 39 | // "Content-Security-Policy": ["script-src 'self'"], 40 | // }, 41 | // }); 42 | // }); 43 | // } 44 | 45 | const mainScreen = screen.getPrimaryDisplay(); 46 | 47 | window = new BrowserWindow({ 48 | width: dev ? 1100 : mainScreen.size.width, 49 | height: dev ? 600 : mainScreen.size.height, 50 | frame: dev, 51 | backgroundColor: '#353b48', 52 | webPreferences: { 53 | nodeIntegration: true, 54 | enableRemoteModule: true, 55 | contextIsolation: false, 56 | }, 57 | icon: fileURLToPath(new URL(`./${dev ? 'src' : 'dist'}/assets/icon/icon.png`, import.meta.url)), 58 | }); 59 | 60 | if (dev) { 61 | url = 'http://localhost:4200'; 62 | let devtools = new BrowserWindow(); 63 | window.webContents.setDevToolsWebContents(devtools.webContents); 64 | window.webContents.openDevTools({ mode: 'detach' }); 65 | } else { 66 | url = `file://${__dirname}/dist/${locale}/index.html`; 67 | window.setFullScreen(true); 68 | } 69 | 70 | window.loadURL(url); 71 | activateListeners(ipcMain, window, app, url); 72 | 73 | window.on('closed', () => { 74 | window = null; 75 | }); 76 | } 77 | 78 | app.on('ready', createWindow); 79 | 80 | app.on('window-all-closed', () => { 81 | app.quit(); 82 | }); 83 | 84 | app.on('activate', () => { 85 | if (window === null) { 86 | createWindow(); 87 | } 88 | }); 89 | -------------------------------------------------------------------------------- /screenshots/adjust_parameters_print.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/adjust_parameters_print.png -------------------------------------------------------------------------------- /screenshots/adjust_temperatures_main_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/adjust_temperatures_main_screen.png -------------------------------------------------------------------------------- /screenshots/babystep_z.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/babystep_z.png -------------------------------------------------------------------------------- /screenshots/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/cancel.png -------------------------------------------------------------------------------- /screenshots/control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/control.png -------------------------------------------------------------------------------- /screenshots/control_confirmation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/control_confirmation.png -------------------------------------------------------------------------------- /screenshots/error_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/error_message.png -------------------------------------------------------------------------------- /screenshots/filament_extruding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/filament_extruding.png -------------------------------------------------------------------------------- /screenshots/filament_heating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/filament_heating.png -------------------------------------------------------------------------------- /screenshots/filament_purging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/filament_purging.png -------------------------------------------------------------------------------- /screenshots/filament_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/filament_selection.png -------------------------------------------------------------------------------- /screenshots/file_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/file_details.png -------------------------------------------------------------------------------- /screenshots/file_loaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/file_loaded.png -------------------------------------------------------------------------------- /screenshots/files_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/files_view.png -------------------------------------------------------------------------------- /screenshots/job.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/job.png -------------------------------------------------------------------------------- /screenshots/main-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/main-screen.png -------------------------------------------------------------------------------- /screenshots/no_job_no_touchscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/no_job_no_touchscreen.png -------------------------------------------------------------------------------- /screenshots/paused.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/paused.png -------------------------------------------------------------------------------- /screenshots/print_controls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/print_controls.png -------------------------------------------------------------------------------- /screenshots/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/settings.png -------------------------------------------------------------------------------- /screenshots/sleeping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/screenshots/sleeping.png -------------------------------------------------------------------------------- /scripts/remove.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Removing OctoDash ..." 4 | killall octodash 5 | 6 | sudo dpkg -P octodash 7 | 8 | rm -rf ~/.config/octodash/ 9 | 10 | sed -i '/xset s off/d' ~/.xinitrc 11 | sed -i '/xset s noblank/d' ~/.xinitrc 12 | sed -i '/xset -dpms/d' ~/.xinitrc 13 | sed -i '/ratpoison&/d' ~/.xinitrc 14 | sed -i '/octodash --no-sandbox/d' ~/.xinitrc 15 | 16 | echo "OctoDash has been removed :(. Please review your ~/.xinitrc and ~/.bashrc files to make sure everything got removed properly!" 17 | -------------------------------------------------------------------------------- /scripts/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -f "/etc/debian_version" ]; then 4 | echo "" 5 | echo "OctoDash is only compatible with Debian-based Linux installations!" 6 | echo "" 7 | fi 8 | 9 | arch=$(dpkg --print-architecture) 10 | if [[ $arch == armhf ]]; then 11 | releaseURL=$(curl -s "https://api.github.com/repos/UnchartedBull/OctoDash/releases/latest" | grep "browser_download_url.*armv7l.deb" | cut -d '"' -f 4) 12 | elif [[ $arch == arm64 ]]; then 13 | releaseURL=$(curl -s "https://api.github.com/repos/UnchartedBull/OctoDash/releases/latest" | grep "browser_download_url.*arm64.deb" | cut -d '"' -f 4) 14 | elif [[ $arch == amd64 ]]; then 15 | releaseURL=$(curl -s "https://api.github.com/repos/UnchartedBull/OctoDash/releases/latest" | grep "browser_download_url.*amd64.deb" | cut -d '"' -f 4) 16 | fi 17 | 18 | echo "Updating OctoDash" 19 | 20 | cd ~ 21 | 22 | wget -O octodash.deb $releaseURL -q --show-progress 23 | 24 | sudo dpkg -i octodash.deb 25 | 26 | rm octodash.deb 27 | 28 | echo "Done. Restart your Raspberry Pi to start the newest version!" 29 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { DragDropModule } from '@angular/cdk/drag-drop'; 2 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 3 | import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { MatRippleModule } from '@angular/material/core'; 6 | import { BrowserModule } from '@angular/platform-browser'; 7 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 8 | import { FaIconLibrary, FontAwesomeModule } from '@fortawesome/angular-fontawesome'; 9 | import { fas } from '@fortawesome/free-solid-svg-icons'; 10 | import { RoundProgressModule } from 'angular-svg-round-progressbar'; 11 | import lottiePlayer from 'lottie-web'; 12 | import { LottieComponent, provideCacheableAnimationLoader, provideLottieOptions } from 'ngx-lottie'; 13 | 14 | import { AppRoutingModule } from './app.routing.module'; 15 | import components from './components'; 16 | import { AppComponent } from './components/app.component'; 17 | import directives from './directives'; 18 | import pipes from './pipes'; 19 | import services from './services'; 20 | 21 | @NgModule({ 22 | declarations: [...components, ...directives, ...pipes], 23 | bootstrap: [AppComponent], 24 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 25 | imports: [ 26 | AppRoutingModule, 27 | BrowserAnimationsModule, 28 | BrowserModule, 29 | DragDropModule, 30 | FontAwesomeModule, 31 | FormsModule, 32 | MatRippleModule, 33 | RoundProgressModule, 34 | LottieComponent, 35 | ], 36 | providers: [ 37 | ...services, 38 | [provideLottieOptions({ player: () => lottiePlayer })], 39 | [provideCacheableAnimationLoader()], 40 | provideHttpClient(withInterceptorsFromDi()), 41 | ], 42 | }) 43 | export class AppModule { 44 | public constructor(library: FaIconLibrary) { 45 | library.addIconPacks(fas); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/app.routing.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | 5 | import { ControlComponent } from './components/control/control.component'; 6 | import { FilamentComponent } from './components/filament/filament.component'; 7 | import { FilesComponent } from './components/files/files.component'; 8 | import { MainScreenComponent } from './components/main-screen/main-screen.component'; 9 | import { SettingsComponent } from './components/settings/settings.component'; 10 | import { ConfigInvalidComponent } from './components/setup/invalid-config/invalid-config.component'; 11 | import { ConfigSetupComponent } from './components/setup/setup.component'; 12 | import { StandbyComponent } from './components/standby/standby.component'; 13 | 14 | const routes: Routes = [ 15 | { 16 | path: 'main-screen', 17 | component: MainScreenComponent, 18 | }, 19 | { 20 | path: 'control', 21 | component: ControlComponent, 22 | }, 23 | { 24 | path: 'filament', 25 | component: FilamentComponent, 26 | }, 27 | { 28 | path: 'files', 29 | component: FilesComponent, 30 | }, 31 | { 32 | path: 'invalid-config', 33 | component: ConfigInvalidComponent, 34 | }, 35 | { 36 | path: 'no-config', 37 | component: ConfigSetupComponent, 38 | }, 39 | { 40 | path: 'settings', 41 | component: SettingsComponent, 42 | }, 43 | { 44 | path: 'standby', 45 | component: StandbyComponent, 46 | }, 47 | ]; 48 | 49 | @NgModule({ 50 | declarations: [], 51 | imports: [CommonModule, RouterModule.forRoot(routes, { useHash: true, enableViewTransitions: true })], 52 | exports: [RouterModule], 53 | }) 54 | export class AppRoutingModule {} 55 | -------------------------------------------------------------------------------- /src/assets/adjust.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/cancel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/connect.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/control.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/fan.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/filament.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/fonts/Arvo-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/src/assets/fonts/Arvo-Bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Cousine-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/src/assets/fonts/Cousine-Regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Montserrat-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/src/assets/fonts/Montserrat-Medium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/src/assets/fonts/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /src/assets/heat-bed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/heat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/height.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/src/assets/icon/icon.png -------------------------------------------------------------------------------- /src/assets/invalid-config.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/made-in-berlin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/src/assets/made-in-berlin.png -------------------------------------------------------------------------------- /src/assets/nozzle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/object.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/pause.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/resume.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 5 |
6 |
7 | 8 | 9 | {{ status }} 14 | Initializing is taking longer than usual...
16 | Please make sure that OctoPrint is running and that CORS is enabled for the API.
18 |
19 | 20 | 21 |
22 | 23 | 24 | 29 | 30 | 31 | 32 | 37 | 38 | 39 | 40 | 45 | 46 | -------------------------------------------------------------------------------- /src/components/app.component.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 100%; 4 | display: block; 5 | position: relative; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/filament/change-filament/change-filament.component.html: -------------------------------------------------------------------------------- 1 | 2 | load new filament into tool {{ selectedTool }} 3 | only put a little filament in, I'll pull in the rest. 6 | 7 | 8 | 9 |
10 |
11 | {{ selectedSpool.material }} 16 |
17 | {{ selectedSpool.displayName }} 18 | {{ getSpoolWeightLeft(selectedSpool.weight, selectedSpool.used) 20 | }}g left 22 |
23 |
24 | 25 |
26 | done 29 |
30 | 31 | 32 |
33 | I'll wait for you. 34 |
35 |
36 | 37 | 38 | M600 sent 39 | follow the instructions on your printer's display 42 | 43 | -------------------------------------------------------------------------------- /src/components/filament/change-filament/change-filament.component.scss: -------------------------------------------------------------------------------- 1 | .change-filament { 2 | &__info { 3 | font-size: 60%; 4 | opacity: 0.8; 5 | font-style: italic; 6 | display: block; 7 | text-align: center; 8 | } 9 | 10 | &__filament { 11 | &-name { 12 | text-align: center; 13 | font-size: 0.8rem; 14 | opacity: 0.7; 15 | display: block; 16 | text-overflow: ellipsis; 17 | white-space: nowrap; 18 | } 19 | 20 | &-weight { 21 | opacity: 1; 22 | font-size: 1rem; 23 | line-height: 1.5rem; 24 | display: block; 25 | text-align: center; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/filament/change-filament/change-filament.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; 2 | 3 | import { FilamentSpool } from '../../../model'; 4 | import { ConfigService } from '../../../services/config.service'; 5 | import { PrinterService } from '../../../services/printer/printer.service'; 6 | 7 | @Component({ 8 | selector: 'app-filament-change-filament', 9 | templateUrl: './change-filament.component.html', 10 | styleUrls: [ 11 | './change-filament.component.scss', 12 | '../filament.component.scss', 13 | '../heat-nozzle/heat-nozzle.component.scss', 14 | ], 15 | standalone: false, 16 | }) 17 | export class ChangeFilamentComponent implements OnInit { 18 | @Input() selectedSpool: FilamentSpool; 19 | @Input() selectedTool: number; 20 | 21 | @Output() increasePage = new EventEmitter(); 22 | 23 | constructor( 24 | private configService: ConfigService, 25 | private printerService: PrinterService, 26 | ) {} 27 | 28 | ngOnInit(): void { 29 | if (this.configService.useM600()) { 30 | this.initiateM600FilamentChange(); 31 | } else { 32 | this.disableExtruderStepper(); 33 | } 34 | } 35 | 36 | private disableExtruderStepper(): void { 37 | this.printerService.executeGCode(`${this.configService.getDisableExtruderGCode()}`); 38 | } 39 | 40 | private initiateM600FilamentChange(): void { 41 | this.printerService.executeGCode(`M600 T${this.selectedTool}`); 42 | } 43 | 44 | public getSpoolWeightLeft(weight: number, used: number): number { 45 | return Math.floor(weight - used); 46 | } 47 | 48 | public usingM600(): boolean { 49 | return this.configService.useM600(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/components/filament/choose-filament/choose-filament.component.html: -------------------------------------------------------------------------------- 1 | select your new filament 2 | 3 | 4 | 5 | 15 | 18 | 21 | 24 | 28 | 29 |
16 | 17 | 19 | {{ spool.material }} 20 | 22 | {{ spool.displayName }} 23 | 25 | {{ getSpoolWeightLeft(spool.weight, spool.used) 26 | }}g left 27 |
30 |
31 |
32 | 33 | 34 |
no filament spools found
35 |
36 | 37 | 38 |
loading spools
39 |
40 | -------------------------------------------------------------------------------- /src/components/filament/choose-filament/choose-filament.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Output } from '@angular/core'; 2 | 3 | import { FilamentSpool } from '../../../model'; 4 | import { FilamentService } from '../../../services/filament/filament.service'; 5 | 6 | @Component({ 7 | selector: 'app-filament-choose-spool', 8 | templateUrl: './choose-filament.component.html', 9 | styleUrls: ['./choose-filament.component.scss', '../filament.component.scss'], 10 | standalone: false, 11 | }) 12 | export class ChooseFilamentComponent { 13 | @Output() spoolChange = new EventEmitter<{ spool: FilamentSpool; skipChange: boolean }>(); 14 | 15 | private currentSpools: number[]; 16 | 17 | constructor(public filament: FilamentService) { 18 | this.currentSpools = (filament.getCurrentSpools() || []).map(s => s.id); 19 | } 20 | 21 | public getSpoolWeightLeft(weight: number, used: number): number { 22 | return Math.floor(weight - used); 23 | } 24 | 25 | public setSpool(spool: FilamentSpool): void { 26 | setTimeout(() => { 27 | this.spoolChange.emit({ spool, skipChange: false }); 28 | }, 150); 29 | } 30 | 31 | public setSpoolSkipChange(spool: FilamentSpool): void { 32 | setTimeout(() => { 33 | this.spoolChange.emit({ spool, skipChange: true }); 34 | }, 150); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/filament/choose-tool/choose-tool.component.html: -------------------------------------------------------------------------------- 1 | select tool 2 | 3 | 4 | 12 | 13 |
10 |

Tool {{ i }}

11 |
14 | -------------------------------------------------------------------------------- /src/components/filament/choose-tool/choose-tool.component.scss: -------------------------------------------------------------------------------- 1 | .tools { 2 | margin-top: 2vh; 3 | height: auto; 4 | display: block; 5 | overflow-y: scroll; 6 | scrollbar-width: none; 7 | padding-right: 1.5vw; 8 | 9 | tr td { 10 | background-color: var(--background-3); 11 | border-radius: 1vw; 12 | padding: 0 1vw; 13 | display: block; 14 | width: 92vw; 15 | margin-left: 2vw; 16 | margin-bottom: 2vh; 17 | transition: color 0.25s; 18 | box-sizing: border-box; 19 | border: 0; 20 | position: relative; 21 | text-align: center; 22 | font-size: 6vw; 23 | font-weight: 500; 24 | 25 | &:first-of-type { 26 | margin-top: 3.5vh; 27 | } 28 | 29 | &:last-of-type { 30 | margin-bottom: 3.5vh; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/filament/choose-tool/choose-tool.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Output } from '@angular/core'; 2 | import { take } from 'rxjs/operators'; 3 | 4 | import { PrinterStatus } from '../../../model'; 5 | import { PrinterService } from '../../../services/printer/printer.service'; 6 | import { SocketService } from '../../../services/socket/socket.service'; 7 | 8 | @Component({ 9 | selector: 'app-filament-choose-tool', 10 | templateUrl: './choose-tool.component.html', 11 | styleUrls: ['./choose-tool.component.scss', '../filament.component.scss'], 12 | standalone: false, 13 | }) 14 | export class ChooseToolComponent { 15 | public toolCount = 1; 16 | 17 | @Output() toolChange = new EventEmitter(); 18 | 19 | public constructor( 20 | private printerService: PrinterService, 21 | private socketService: SocketService, 22 | ) { 23 | this.socketService 24 | .getPrinterStatusSubscribable() 25 | .pipe(take(1)) 26 | .subscribe((printerStatus: PrinterStatus): void => { 27 | this.toolCount = printerStatus.tools.length; 28 | }); 29 | } 30 | 31 | public setTool(tool: number): void { 32 | setTimeout(() => { 33 | this.toolChange.emit(tool); 34 | }, 150); 35 | } 36 | 37 | //function to return list of numbers from 0 to n-1 38 | numSequence(n: number): Array { 39 | return Array(n); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/filament/filament.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 |
8 | 9 |
10 | 13 | 14 | 20 | 25 | 30 | 36 | 40 |
41 | 45 |
46 |
47 | -------------------------------------------------------------------------------- /src/components/filament/filament.component.scss: -------------------------------------------------------------------------------- 1 | .filament { 2 | padding: 2vh 2vw 0; 3 | 4 | &__progress-bar { 5 | height: 4vh; 6 | border-radius: 2vh; 7 | background-color: var(--success); 8 | width: 0; 9 | transition: width 0.7s ease-in-out; 10 | 11 | &-wrapper { 12 | background-color: var(--background-3); 13 | height: 4vh; 14 | width: 20vw; 15 | display: inline-block; 16 | border-radius: 2vh; 17 | 18 | &-wide { 19 | width: 50vw; 20 | margin: 14vh auto 3vh; 21 | display: block; 22 | height: 7vh; 23 | background-color: transparent; 24 | border: 3px solid var(--border); 25 | border-radius: 3vh; 26 | 27 | > div { 28 | height: 7vh; 29 | width: 50vw; 30 | background-color: transparent; 31 | } 32 | } 33 | } 34 | } 35 | 36 | &-heading { 37 | display: block; 38 | text-align: center; 39 | font-size: 1rem; 40 | } 41 | 42 | &__wrapper-button { 43 | text-align: center; 44 | position: absolute; 45 | width: 96vw; 46 | bottom: 5vh; 47 | } 48 | 49 | &__done { 50 | display: inline-block; 51 | background-color: var(--success); 52 | padding: 2vh 2vw; 53 | border-radius: 8px; 54 | } 55 | } 56 | 57 | .checkmark { 58 | margin-top: 10vh; 59 | height: 80vh; 60 | display: block; 61 | } 62 | -------------------------------------------------------------------------------- /src/components/filament/heat-nozzle/heat-nozzle.component.html: -------------------------------------------------------------------------------- 1 | heating the nozzle for tool {{ selectedTool }} 2 |
3 |
4 |
5 |
6 | +1 7 |
8 |
9 | +10 10 |
11 |
12 |
13 | {{ isHeating ? hotendTemperature : hotendTarget }}°C 14 | /{{ hotendTarget }}°C 15 |
16 |
17 |
18 | -1 19 |
20 |
21 | -10 22 |
23 |
24 |
25 |
26 |
27 | {{ selectedSpool.material }} 32 |
33 | {{ selectedSpool.name }} 34 | {{ selectedSpool.vendor }} 35 |
36 | 37 | {{ getSpoolTemperatureOffsetString(selectedSpool) 38 | }}°C 39 | 40 |
41 |
42 |
43 |
44 | 45 | wait 46 | {{ automaticHeatingStartSeconds }} 47 | s or 48 | 49 | start 57 |
58 | -------------------------------------------------------------------------------- /src/components/filament/move-filament/move-filament.component.html: -------------------------------------------------------------------------------- 1 | {{ action === 'load' ? loadingMessage : unloadingMessage }} 3 | filament 5 |
6 |
10 |
11 | {{ feedSpeed }}mm/s 12 |
13 | stop 21 |
22 | -------------------------------------------------------------------------------- /src/components/filament/move-filament/move-filament.component.scss: -------------------------------------------------------------------------------- 1 | .move-filament { 2 | &__speed { 3 | font-size: 60%; 4 | opacity: 0.8; 5 | font-style: italic; 6 | display: block; 7 | text-align: center; 8 | } 9 | 10 | &__cancel { 11 | display: inline-block; 12 | background-color: var(--error); 13 | padding: 2vh 2vw; 14 | border-radius: 8px; 15 | } 16 | 17 | &__progress-bar { 18 | height: 4vh; 19 | border-radius: 2vh; 20 | background-color: var(--success); 21 | width: 0; 22 | transition: width 0.7s ease-in-out; 23 | 24 | &-wrapper { 25 | width: 50vw; 26 | margin: 14vh auto 3vh; 27 | display: block; 28 | height: 7vh; 29 | background-color: transparent; 30 | border: 3px solid var(--border); 31 | border-radius: 3vh; 32 | 33 | > div { 34 | height: 7vh; 35 | width: 50vw; 36 | background-color: transparent; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/filament/purge-filament/purge-filament.component.html: -------------------------------------------------------------------------------- 1 | purging filament 2 |
{{ purgeAmount }}mm
3 |
4 | +10mm 12 | done 20 |
21 | -------------------------------------------------------------------------------- /src/components/filament/purge-filament/purge-filament.component.scss: -------------------------------------------------------------------------------- 1 | .purge-filament { 2 | &__amount { 3 | text-align: center; 4 | margin-top: 12vh; 5 | font-size: 250%; 6 | } 7 | 8 | &__more, 9 | &__done { 10 | display: inline-block; 11 | padding: 2vh 2vw; 12 | border-radius: 8px; 13 | } 14 | 15 | &__more { 16 | background-color: #7f8fa6; 17 | margin-right: 10vw; 18 | } 19 | 20 | &__done { 21 | background-color: var(--success); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/filament/purge-filament/purge-filament.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; 2 | 3 | import { ConfigService } from '../../../services/config.service'; 4 | import { PrinterService } from '../../../services/printer/printer.service'; 5 | 6 | @Component({ 7 | selector: 'app-filament-purge-filament', 8 | templateUrl: './purge-filament.component.html', 9 | styleUrls: ['./purge-filament.component.scss', '../filament.component.scss'], 10 | standalone: false, 11 | }) 12 | export class PurgeFilamentComponent implements OnInit { 13 | @Input() selectedTool: number; 14 | 15 | @Output() purgeDone = new EventEmitter(); 16 | 17 | public purgeAmount: number; 18 | 19 | constructor( 20 | private configService: ConfigService, 21 | private printerService: PrinterService, 22 | ) {} 23 | 24 | ngOnInit(): void { 25 | this.purgeAmount = this.configService.useM600() ? 0 : this.configService.getPurgeDistance(); 26 | if (this.purgeAmount === 0) { 27 | this.purgeDone.emit(); 28 | } else { 29 | this.purgeFilament(this.purgeAmount); 30 | } 31 | } 32 | 33 | public increasePurgeAmount(length: number): void { 34 | this.purgeAmount += length; 35 | this.purgeFilament(length); 36 | } 37 | 38 | public purgeFilament(length: number): void { 39 | this.printerService.extrude(length, this.configService.getFeedSpeedSlow(), this.selectedTool); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/main-screen/bottom-bar/bottom-bar.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 11 | 12 |
{{ getPrinterName() }} 5 | 6 | {{ enclosureTemperature.temperature }}{{ enclosureTemperature.unit }} 7 | 9 | {{ statusText }} 10 |
13 | -------------------------------------------------------------------------------- /src/components/main-screen/bottom-bar/bottom-bar.component.scss: -------------------------------------------------------------------------------- 1 | .bottom-bar { 2 | position: absolute; 3 | bottom: 2.5vh; 4 | left: 2vw; 5 | width: calc(100% - 5.6vw); 6 | 7 | td { 8 | font-size: 4vw; 9 | } 10 | 11 | &__printer-name { 12 | text-overflow: ellipsis; 13 | } 14 | 15 | &__enclosure-temperature { 16 | text-align: center; 17 | width: 20%; 18 | 19 | &-icon { 20 | width: 2.5vw; 21 | display: inline-block; 22 | margin-bottom: -0.8vh; 23 | margin-right: 1vw; 24 | } 25 | } 26 | 27 | &__current-status { 28 | text-align: right; 29 | } 30 | 31 | &__error { 32 | color: var(--error); 33 | animation: blinker 2s linear infinite; 34 | } 35 | } 36 | 37 | @keyframes blinker { 38 | 20% { 39 | opacity: 1; 40 | } 41 | 42 | 50% { 43 | opacity: 0; 44 | } 45 | 46 | 80% { 47 | opacity: 1; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/components/main-screen/job-status/height-progress/height-progress.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Z 4 | 5 | {{ height }} 6 | 7 | mm 8 | 9 | 10 | 11 | 12 | Layer 17 | 18 | {{ height.current }} 19 | 20 | of 21 | 22 | {{ height.total >= 0 ? height.total : '---' }} 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/main-screen/job-status/height-progress/height-progress.component.scss: -------------------------------------------------------------------------------- 1 | .height-indication { 2 | width: 100%; 3 | display: block; 4 | margin-top: 1vh; 5 | text-align: center; 6 | font-size: 4vw; 7 | 8 | &__current-height { 9 | font-size: 6vw; 10 | font-weight: 500; 11 | } 12 | 13 | &__z { 14 | border: 3px solid white; 15 | padding: 0 0.2rem; 16 | font-size: 0.5rem; 17 | font-weight: bold; 18 | border-radius: 0.1rem; 19 | vertical-align: 0.05rem; 20 | display: inline-block; 21 | margin-right: 1.5vw; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/main-screen/job-status/height-progress/height-progress.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { Subscription } from 'rxjs'; 3 | 4 | import { ZHeightLayer } from '../../../../model'; 5 | import { SocketService } from '../../../../services/socket/socket.service'; 6 | 7 | @Component({ 8 | selector: 'app-height-progress', 9 | templateUrl: './height-progress.component.html', 10 | styleUrls: ['./height-progress.component.scss'], 11 | standalone: false, 12 | }) 13 | export class HeightProgressComponent implements OnInit, OnDestroy { 14 | private subscriptions: Subscription = new Subscription(); 15 | public height: number | ZHeightLayer; 16 | 17 | public constructor(private socketService: SocketService) {} 18 | 19 | public ngOnInit(): void { 20 | this.subscriptions.add( 21 | this.socketService.getJobStatusSubscribable().subscribe(jobStatus => { 22 | this.height = jobStatus.zHeight; 23 | }), 24 | ); 25 | } 26 | 27 | public ngOnDestroy(): void { 28 | this.subscriptions.unsubscribe(); 29 | } 30 | 31 | public isNumber(variable: number | ZHeightLayer): boolean { 32 | return typeof variable === 'number'; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/main-screen/job-status/job-status.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { Subscription } from 'rxjs'; 3 | 4 | import { JobStatus } from '../../../model'; 5 | import { ConfigService } from '../../../services/config.service'; 6 | import { EventService } from '../../../services/event.service'; 7 | import { FilesService } from '../../../services/files/files.service'; 8 | import { JobService } from '../../../services/job/job.service'; 9 | import { SocketService } from '../../../services/socket/socket.service'; 10 | 11 | @Component({ 12 | selector: 'app-job-status', 13 | templateUrl: './job-status.component.html', 14 | styleUrls: ['./job-status.component.scss'], 15 | standalone: false, 16 | }) 17 | export class JobStatusComponent implements OnInit, OnDestroy { 18 | private subscriptions: Subscription = new Subscription(); 19 | 20 | public jobStatus: JobStatus; 21 | public thumbnail: string; 22 | public showPreviewWhilePrinting: boolean; 23 | 24 | public constructor( 25 | private jobService: JobService, 26 | private fileService: FilesService, 27 | private socketService: SocketService, 28 | private eventService: EventService, 29 | private configService: ConfigService, 30 | ) { 31 | this.showPreviewWhilePrinting = this.configService.showThumbnailByDefault(); 32 | } 33 | 34 | public ngOnInit(): void { 35 | this.subscriptions.add( 36 | this.socketService.getJobStatusSubscribable().subscribe((jobStatus: JobStatus): void => { 37 | if (jobStatus.file !== this.jobStatus?.file) { 38 | this.fileService.getThumbnail(jobStatus.fullPath).subscribe(thumbnail => { 39 | this.thumbnail = thumbnail; 40 | }); 41 | } 42 | this.jobStatus = jobStatus; 43 | }), 44 | ); 45 | } 46 | 47 | public ngOnDestroy(): void { 48 | this.subscriptions.unsubscribe(); 49 | } 50 | 51 | public isFileLoaded(): boolean { 52 | return this.fileService.getLoadedFile(); 53 | } 54 | 55 | public isPreheatEnabled(): boolean { 56 | return this.configService.isPreheatPluginEnabled(); 57 | } 58 | 59 | public preheat(): void { 60 | this.jobService.preheat(); 61 | } 62 | 63 | public discardLoadedFile(): void { 64 | this.fileService.setLoadedFile(false); 65 | } 66 | 67 | public startJob(): void { 68 | this.jobService.startJob(); 69 | setTimeout((): void => { 70 | this.fileService.setLoadedFile(false); 71 | }, 5000); 72 | } 73 | 74 | public isPrinting(): boolean { 75 | return this.eventService.isPrinting(); 76 | } 77 | 78 | public togglePreview(): void { 79 | this.showPreviewWhilePrinting = !this.showPreviewWhilePrinting; 80 | } 81 | 82 | public hasProperty(object: Record, name: string): boolean { 83 | return Object.hasOwnProperty.bind(object)(name); 84 | } 85 | 86 | public useCircularProgressBar(): boolean { 87 | return this.configService.getPreviewProgressCircle(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/components/main-screen/main-menu/main-menu.component.html: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/components/main-screen/main-menu/main-menu.component.scss: -------------------------------------------------------------------------------- 1 | .main-menu { 2 | display: block; 3 | width: 100%; 4 | height: 58vh; 5 | 6 | &__heading { 7 | display: block; 8 | text-align: center; 9 | font-size: 5vw; 10 | font-weight: 500; 11 | margin-top: 4vh; 12 | font-family: Arvo, sans-serif; 13 | 14 | &__dot { 15 | color: var(--primary); 16 | opacity: 1; 17 | animation: breathe 5s ease-in-out infinite alternate; 18 | } 19 | } 20 | 21 | &__options { 22 | margin: auto; 23 | width: 90vw; 24 | 25 | & td { 26 | width: 29.66vw; 27 | display: inline-block; 28 | text-align: center; 29 | font-weight: 500; 30 | } 31 | } 32 | 33 | &__option-icon { 34 | display: block; 35 | padding-bottom: 4vh; 36 | margin: auto; 37 | 38 | &__1 { 39 | height: 25vh; 40 | margin-bottom: -1.3vh; 41 | } 42 | 43 | &__2 { 44 | height: 22vh; 45 | } 46 | 47 | &__3 { 48 | height: 19vh; 49 | margin-bottom: 1.6vh; 50 | } 51 | } 52 | } 53 | 54 | @keyframes breathe { 55 | 0% { 56 | opacity: 1; 57 | } 58 | 59 | 100% { 60 | opacity: 0.5; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/components/main-screen/main-menu/main-menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-main-menu', 5 | templateUrl: './main-menu.component.html', 6 | styleUrls: ['./main-menu.component.scss'], 7 | standalone: false, 8 | }) 9 | export class MainMenuComponent implements OnDestroy { 10 | public now = Date.now(); 11 | public interval; 12 | 13 | public constructor() { 14 | this.interval = setInterval(() => (this.now = Date.now()), 1); 15 | } 16 | 17 | public ngOnDestroy(): void { 18 | clearInterval(this.interval); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/main-screen/main-screen.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 | 10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /src/components/main-screen/main-screen.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { ConfigService } from '../../services/config.service'; 5 | import { EventService } from '../../services/event.service'; 6 | import { FilesService } from '../../services/files/files.service'; 7 | 8 | @Component({ 9 | selector: 'app-main-screen', 10 | templateUrl: './main-screen.component.html', 11 | standalone: false, 12 | }) 13 | export class MainScreenComponent { 14 | public printing = false; 15 | 16 | public constructor( 17 | private eventService: EventService, 18 | private fileService: FilesService, 19 | private configService: ConfigService, 20 | private router: Router, 21 | ) { 22 | if (!this.configService.isInitialized()) { 23 | this.router.navigate(['/']); 24 | } 25 | } 26 | 27 | public isPrinting(): boolean { 28 | return this.eventService.isPrinting(); 29 | } 30 | 31 | public isFileLoaded(): boolean { 32 | return this.fileService.getLoadedFile(); 33 | } 34 | 35 | public isTouchscreen(): boolean { 36 | return this.configService.isTouchscreen(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/main-screen/printer-status/printer-status.component.scss: -------------------------------------------------------------------------------- 1 | .printer-status { 2 | width: 95vw; 3 | margin: auto 3vh; 4 | height: 27vh; 5 | display: grid; 6 | grid-template-rows: repeat(1, 1fr); 7 | grid-template-columns: 1fr 2fr; 8 | gap: 1vw 2vw; 9 | place-items: center start; 10 | align-content: center; 11 | 12 | &.printer-status__multi-hotend { 13 | grid-template-rows: repeat(2, 1fr); 14 | grid-template-columns: repeat(1, 1fr); 15 | 16 | .printer-status__row { 17 | grid-column: 1 / -1; 18 | } 19 | } 20 | 21 | .printer-status__row { 22 | display: grid; 23 | width: 100%; 24 | grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); 25 | column-gap: 2vw; 26 | place-items: center start; 27 | align-content: center; 28 | 29 | &:last-child { 30 | grid-template-columns: repeat(2, 1fr); 31 | } 32 | 33 | .printer-status__item { 34 | width: 100%; 35 | 36 | .printer-status__icon { 37 | vertical-align: middle; 38 | display: inline-block; 39 | width: 6vw; 40 | } 41 | 42 | .printer-status__value { 43 | display: inline-block; 44 | width: calc(100% - 6vw); 45 | text-align: right; 46 | vertical-align: middle; 47 | line-height: 6vh; 48 | 49 | &.printer-status__small-text span { 50 | font-size: 2.5vw; 51 | } 52 | 53 | .printer-status__unit { 54 | font-size: 60%; 55 | } 56 | 57 | .printer-status__actual-value { 58 | font-size: 5vw; 59 | font-weight: 500; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/main-screen/printer-status/printer-status.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { Subscription } from 'rxjs'; 4 | 5 | import { PrinterExtruders, PrinterProfile, PrinterStatus } from '../../../model'; 6 | import { ConfigService } from '../../../services/config.service'; 7 | import { PrinterService } from '../../../services/printer/printer.service'; 8 | import { SocketService } from '../../../services/socket/socket.service'; 9 | 10 | @Component({ 11 | selector: 'app-printer-status', 12 | templateUrl: './printer-status.component.html', 13 | styleUrls: ['./printer-status.component.scss'], 14 | standalone: false, 15 | }) 16 | export class PrinterStatusComponent implements OnInit, OnDestroy { 17 | private subscriptions: Subscription = new Subscription(); 18 | public printerStatus: PrinterStatus; 19 | public extruderInfo: PrinterExtruders = { 20 | count: 1, 21 | offsets: [], 22 | sharedNozzle: false, 23 | }; 24 | public fanSpeed: number; 25 | public status: string; 26 | 27 | public selectedHotend: number; 28 | public sharedNozzle: boolean; 29 | 30 | public QuickControlView = QuickControlView; 31 | public view = QuickControlView.NONE; 32 | 33 | public constructor( 34 | private printerService: PrinterService, 35 | private configService: ConfigService, 36 | private socketService: SocketService, 37 | private router: Router, 38 | ) {} 39 | 40 | public ngOnInit(): void { 41 | this.subscriptions.add( 42 | this.printerService.getActiveProfile().subscribe({ 43 | next: (printerProfile: PrinterProfile) => (this.extruderInfo = printerProfile.extruder), 44 | }), 45 | ); 46 | this.subscriptions.add( 47 | this.socketService.getPrinterStatusSubscribable().subscribe((status: PrinterStatus): void => { 48 | this.printerStatus = status; 49 | }), 50 | ); 51 | } 52 | 53 | public ngOnDestroy(): void { 54 | this.subscriptions.unsubscribe(); 55 | } 56 | 57 | public showQuickControlHotend(tool: number): void { 58 | this.view = QuickControlView.HOTEND; 59 | this.selectedHotend = tool; 60 | } 61 | 62 | public showQuickControlHeatbed(): void { 63 | this.view = QuickControlView.HEATBED; 64 | } 65 | 66 | public showQuickControlFan(): void { 67 | this.view = QuickControlView.FAN; 68 | } 69 | 70 | public hideQuickControl(): void { 71 | this.view = QuickControlView.NONE; 72 | } 73 | 74 | public quickControlSetValue(value: number): void { 75 | switch (this.view) { 76 | case QuickControlView.HOTEND: 77 | this.printerService.setTemperatureHotend(value, this.selectedHotend); 78 | break; 79 | case QuickControlView.HEATBED: 80 | this.printerService.setTemperatureBed(value); 81 | break; 82 | case QuickControlView.FAN: 83 | this.printerService.setFanSpeed(value); 84 | break; 85 | } 86 | 87 | this.hideQuickControl(); 88 | } 89 | } 90 | 91 | enum QuickControlView { 92 | NONE, 93 | HOTEND, 94 | HEATBED, 95 | FAN, 96 | } 97 | -------------------------------------------------------------------------------- /src/components/notification/notification.component.html: -------------------------------------------------------------------------------- 1 |
5 | {{ notification?.time | date: 'HH:mm' }} 6 | {{ notification?.heading }} 7 | {{ notification?.text }} 8 | tap this card to close it 9 |
10 |
15 | {{ choice }} 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /src/components/notification/notification.component.scss: -------------------------------------------------------------------------------- 1 | .notification { 2 | display: block; 3 | position: fixed; 4 | width: 80vw; 5 | background-color: var(--background-2); 6 | border-radius: 1.3vw; 7 | left: 10vw; 8 | top: -75vh; 9 | transition: top 0.7s ease-in-out; 10 | box-shadow: 0 4px 10px -3px rgb(0 0 0 / 75%); 11 | z-index: 100; 12 | border-right: 1vw solid var(--background-2); 13 | 14 | &-prompt { 15 | &__choices { 16 | display: flex; 17 | flex-wrap: wrap; 18 | justify-content: space-evenly; 19 | } 20 | 21 | &__choice { 22 | display: block; 23 | font-size: 2.7vw; 24 | background-color: var(--primary); 25 | text-align: center; 26 | padding: 3vh; 27 | border-radius: 0.8vw; 28 | margin: 1vw; 29 | flex-grow: 100; 30 | 31 | &-first { 32 | background-color: var(--primary); 33 | } 34 | } 35 | } 36 | 37 | &__show { 38 | top: 5vh; 39 | } 40 | 41 | &__heading { 42 | display: block; 43 | text-align: center; 44 | font-size: 3.5vw; 45 | font-weight: 500; 46 | margin-top: 4vh; 47 | } 48 | 49 | &__text { 50 | font-size: 2.7vw; 51 | padding: 5vh 3vw 1vh; 52 | display: block; 53 | text-align: center; 54 | } 55 | 56 | &__close { 57 | font-size: 2vw; 58 | display: block; 59 | text-align: center; 60 | padding-bottom: 2vh; 61 | opacity: 0.4; 62 | } 63 | 64 | &__time { 65 | position: absolute; 66 | right: 0.3vw; 67 | top: 0.6vw; 68 | font-size: 2.4vw; 69 | opacity: 0.6; 70 | } 71 | 72 | &__border { 73 | &-3 { 74 | border-left: 1vw solid #a1abb7; 75 | } 76 | 77 | &-2 { 78 | border-left: 1vw solid #c23616; 79 | } 80 | 81 | &-1 { 82 | border-left: 1vw solid #fbc531; 83 | } 84 | 85 | &-0 { 86 | border-left: 1vw solid #4bae50; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/components/notification/notification.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, NgZone, OnDestroy } from '@angular/core'; 2 | import { Subscription } from 'rxjs'; 3 | 4 | import { Notification } from '../../model'; 5 | import { NotificationService } from '../../services/notification.service'; 6 | 7 | @Component({ 8 | selector: 'app-notification', 9 | templateUrl: './notification.component.html', 10 | styleUrls: ['./notification.component.scss'], 11 | standalone: false, 12 | }) 13 | export class NotificationComponent implements OnDestroy { 14 | private subscriptions: Subscription = new Subscription(); 15 | 16 | public notification?: Notification; 17 | public notificationCloseTimeout: ReturnType; 18 | public show = false; 19 | 20 | public constructor( 21 | private notificationService: NotificationService, 22 | private zone: NgZone, 23 | ) { 24 | this.subscriptions.add( 25 | this.notificationService 26 | .getObservable() 27 | .subscribe((notification: Notification | 'close'): void => this.setNotification(notification)), 28 | ); 29 | } 30 | 31 | public hideNotification(removeFromStack = true, userTriggered = false): void { 32 | if (!userTriggered || (userTriggered && !this.notification.choices)) { 33 | this.show = false; 34 | clearTimeout(this.notificationCloseTimeout); 35 | if (removeFromStack) this.notificationService.removeNotification(this.notification); 36 | } 37 | } 38 | 39 | public chooseAction(index: number, callback: (index: number) => void): void { 40 | callback(index); 41 | this.hideNotification(); 42 | } 43 | 44 | private setNotification(notification: Notification | 'close'): void { 45 | this.zone.run(() => { 46 | if (notification === 'close') { 47 | this.hideNotification(); 48 | } else { 49 | this.hideNotification(false); 50 | this.notification = notification; 51 | this.show = true; 52 | 53 | if (!notification.sticky) { 54 | clearTimeout(this.notificationCloseTimeout); 55 | this.notificationCloseTimeout = setTimeout(this.hideNotification.bind(this), 15 * 1000, false); 56 | } 57 | } 58 | }); 59 | } 60 | 61 | public ngOnDestroy(): void { 62 | this.subscriptions.unsubscribe(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/reset/reset.component.html: -------------------------------------------------------------------------------- 1 |
2 | Are you sure you want to reset the configuration? 3 |
4 | 5 | 6 |
7 |
8 | -------------------------------------------------------------------------------- /src/components/reset/reset.component.scss: -------------------------------------------------------------------------------- 1 | .reset { 2 | &-container { 3 | position: absolute; 4 | z-index: 20; 5 | top: 10vh; 6 | left: 20vw; 7 | height: 56vh; 8 | width: 60vw; 9 | padding-top: 25vh; 10 | background-color: var(--background); 11 | border-radius: 2vw; 12 | text-align: center; 13 | } 14 | 15 | &-button { 16 | padding: 1.4vh 2vw; 17 | border-radius: 1vw; 18 | box-shadow: 0 10px 19px -8px rgb(0 0 0 / 75%); 19 | font-size: 0.7rem; 20 | outline: 0; 21 | border: 0; 22 | margin: 7vh 2vw; 23 | 24 | &__no { 25 | background-color: var(--background-3); 26 | opacity: 0.8; 27 | } 28 | 29 | &__yes { 30 | background-color: var(--success); 31 | } 32 | 33 | &__wrapper { 34 | display: block; 35 | margin: auto; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/reset/reset.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Output } from '@angular/core'; 2 | 3 | import { AppService } from '../../services/app.service'; 4 | import { ElectronService } from '../../services/electron.service'; 5 | import { SystemService } from '../../services/system/system.service'; 6 | 7 | @Component({ 8 | selector: 'app-reset', 9 | templateUrl: './reset.component.html', 10 | styleUrls: ['./reset.component.scss'], 11 | standalone: false, 12 | }) 13 | export class ResetComponent { 14 | @Output() closeFunction = new EventEmitter(true); 15 | 16 | constructor( 17 | public service: AppService, 18 | private systemService: SystemService, 19 | private electronService: ElectronService, 20 | ) {} 21 | 22 | public closeResetWindow(): void { 23 | this.closeFunction.emit(); 24 | } 25 | 26 | public reset(): void { 27 | this.electronService.send('resetConfig'); 28 | this.electronService.send('reload'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/settings/settings-icon/settings-icon.component.html: -------------------------------------------------------------------------------- 1 |
7 | 8 |
9 |
10 | 11 | -------------------------------------------------------------------------------- /src/components/settings/settings-icon/settings-icon.component.scss: -------------------------------------------------------------------------------- 1 | .settings-icon { 2 | position: fixed; 3 | top: 5vh; 4 | right: 3vw; 5 | font-size: 6vw; 6 | 7 | &__update-notifier { 8 | position: absolute; 9 | width: 2.2vw; 10 | height: 2.2vw; 11 | border-radius: 100%; 12 | background-color: #e84118; 13 | bottom: 0; 14 | right: 0; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/settings/settings-icon/settings-icon.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { AppService } from '../../../services/app.service'; 4 | import { ConfigService } from '../../../services/config.service'; 5 | 6 | @Component({ 7 | selector: 'app-settings-icon', 8 | templateUrl: './settings-icon.component.html', 9 | styleUrls: ['./settings-icon.component.scss'], 10 | standalone: false, 11 | }) 12 | export class SettingsIconComponent { 13 | public constructor( 14 | public service: AppService, 15 | public configService: ConfigService, 16 | ) {} 17 | 18 | public settingsVisible = false; 19 | 20 | public showSettings(): void { 21 | this.settingsVisible = true; 22 | } 23 | 24 | public hideSettings(): void { 25 | setTimeout((): void => { 26 | this.settingsVisible = false; 27 | }, 350); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/setup/discover-octoprint/discover-octoprint.component.html: -------------------------------------------------------------------------------- 1 | 2 | First things first: Please select your OctoPrint instance from the list below or enter the IP/URL manually. 3 | 4 |
5 |
6 | 13 |
14 | {{ node.name }} 15 |
16 | 17 | Upgrade Required / 18 | 19 | 20 | Version {{ node.version }}, URL: {{ node.url }} 21 | 22 |
23 |
24 | searching 25 |
26 |
enter manually
27 |
28 |
29 |
30 | http:// 31 | 40 | : 41 | 50 |

51 | Port 5000 for vanilla OctoPrint, Port 80 for OctoPi 52 |

53 |
54 |
55 | search again 56 |
57 |
58 | -------------------------------------------------------------------------------- /src/components/setup/discover-octoprint/discover-octoprint.component.scss: -------------------------------------------------------------------------------- 1 | .discover-octoprint { 2 | &__wrapper { 3 | height: 40vh; 4 | padding: 4vh 3vw; 5 | margin-right: 2vw; 6 | overflow: hidden auto; 7 | } 8 | 9 | &__searching { 10 | display: block; 11 | text-align: center; 12 | margin-top: 10vh; 13 | font-size: 0.8rem; 14 | } 15 | 16 | &__node { 17 | display: block; 18 | background-color: var(--background-3); 19 | border-radius: 1vw; 20 | margin-bottom: 2vh; 21 | padding: 2vh 2vw; 22 | background-image: url('../../../assets/connect.svg'); 23 | background-repeat: no-repeat; 24 | background-position: right; 25 | 26 | &-disabled { 27 | pointer-events: none; 28 | opacity: 0.5; 29 | font-style: italic; 30 | } 31 | 32 | &-name { 33 | white-space: nowrap; 34 | text-overflow: ellipsis; 35 | font-size: 0.8rem; 36 | } 37 | 38 | &-details { 39 | font-size: 0.55rem; 40 | opacity: 0.7; 41 | } 42 | 43 | &-upgrade { 44 | font-size: 0.55rem; 45 | font-weight: bold; 46 | } 47 | } 48 | 49 | &__manual { 50 | text-align: center; 51 | font-size: 0.7rem; 52 | line-height: 14vh; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/components/setup/extruder-information/extruder-information.component.scss: -------------------------------------------------------------------------------- 1 | form { 2 | margin-left: 4vw; 3 | } 4 | 5 | .setup__explanation { 6 | margin-top: 4vh !important; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/setup/extruder-information/extruder-information.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-config-setup-extruder-information', 5 | templateUrl: './extruder-information.component.html', 6 | styleUrls: ['./extruder-information.component.scss', '../setup.component.scss'], 7 | standalone: false, 8 | }) 9 | export class ExtruderInformationComponent { 10 | @Input() feedLength: number; 11 | @Input() feedSpeed: number; 12 | 13 | @Output() feedLengthChange = new EventEmitter(); 14 | @Output() feedSpeedChange = new EventEmitter(); 15 | 16 | changeFeedLength(amount: number): void { 17 | if (this.feedLength + amount < 0) { 18 | this.feedLength = 0; 19 | } else if (this.feedLength + amount > 9999) { 20 | this.feedLength = 9999; 21 | } else { 22 | this.feedLength += amount; 23 | } 24 | this.feedLengthChange.emit(this.feedLength); 25 | } 26 | 27 | changeFeedSpeed(amount: number): void { 28 | if (this.feedSpeed + amount < 0) { 29 | this.feedSpeed = 0; 30 | } else if (this.feedSpeed + amount > 999) { 31 | this.feedSpeed = 999; 32 | } else { 33 | this.feedSpeed += amount; 34 | } 35 | this.feedSpeedChange.emit(this.feedSpeed); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/setup/invalid-config/invalid-config.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | Your config is invalid! 4 | Please correct the errors listed below! 5 |
    6 |
  • {{ error }}
  • 7 |
8 |
9 | -------------------------------------------------------------------------------- /src/components/setup/invalid-config/invalid-config.component.scss: -------------------------------------------------------------------------------- 1 | .invalid-config { 2 | &__top-icon { 3 | width: 16vw; 4 | display: block; 5 | margin-left: calc(50% - 8vw); 6 | margin-top: -4vh; 7 | margin-bottom: -8vh; 8 | } 9 | 10 | &__header { 11 | display: block; 12 | text-align: center; 13 | font-size: 6vw; 14 | } 15 | 16 | &__sub-header { 17 | text-align: center; 18 | font-size: 3vw; 19 | display: block; 20 | } 21 | 22 | &__error-list { 23 | margin-left: 2vw; 24 | margin-top: 5vh; 25 | list-style-type: disc; 26 | 27 | & li { 28 | margin-top: 2vh; 29 | font-size: 2.8vw; 30 | font-family: Cousine, monospace; 31 | 32 | &::before { 33 | content: '-'; 34 | margin-right: 2vw; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/setup/invalid-config/invalid-config.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { ConfigService } from '../../../services/config.service'; 4 | import { ElectronService } from '../../../services/electron.service'; 5 | 6 | @Component({ 7 | selector: 'app-config-invalid', 8 | templateUrl: './invalid-config.component.html', 9 | styleUrls: ['./invalid-config.component.scss'], 10 | standalone: false, 11 | }) 12 | export class ConfigInvalidComponent implements OnInit { 13 | public errors: string[]; 14 | 15 | public constructor( 16 | private configService: ConfigService, 17 | private electronService: ElectronService, 18 | ) { 19 | this.electronService.send('resetConfig'); 20 | } 21 | 22 | public ngOnInit(): void { 23 | this.errors = this.configService.getErrors(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/setup/octoprint-authentication/octoprint-authentication.component.html: -------------------------------------------------------------------------------- 1 | 2 | Please authenticate me now. You can either click the button below and confirm the request in your OctoPrint 3 | webinterface or enter an API Key manually. 4 | 5 | 8 |
9 | 10 | 19 |
20 | -------------------------------------------------------------------------------- /src/components/setup/octoprint-authentication/octoprint-authentication.component.scss: -------------------------------------------------------------------------------- 1 | .octoprint-authentication { 2 | &__login-button { 3 | text-align: center; 4 | padding: 8vh 0 0; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/components/setup/octoprint-authentication/octoprint-authentication.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpResponseBase } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { pluck } from 'rxjs/operators'; 5 | 6 | import { AppToken } from '../../../model/octoprint'; 7 | 8 | @Injectable({ 9 | providedIn: 'root', 10 | }) 11 | @Injectable() 12 | export class OctoprintAuthenticationService { 13 | constructor(private http: HttpClient) {} 14 | 15 | public probeAuthSupport(octoprintURL: string): Observable { 16 | return this.http.get(`${octoprintURL}plugin/appkeys/probe`, { observe: 'response' }); 17 | } 18 | 19 | public startAuthProcess(octoprintURL: string): Observable { 20 | return this.http 21 | .post(`${octoprintURL}plugin/appkeys/request`, { app: 'OctoDash' }) 22 | .pipe(pluck('app_token')); 23 | } 24 | 25 | public pollAuthProcessStatus(octoprintURL: string, token: string): Observable { 26 | return this.http.get(`${octoprintURL}plugin/appkeys/request/${token}`, { observe: 'response' }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/setup/personalization/personalization.component.html: -------------------------------------------------------------------------------- 1 | Now tell me some facts about your setup so I can personalize OctoDash for you. 4 |
5 | 6 | 15 | 16 | Use Touchscreen 17 | 18 |
19 |
20 | You can change all settings (and even more) in the settings menu anytime. There is also a description of each 21 | attribute available in the GitHub Wiki. 22 |
23 | -------------------------------------------------------------------------------- /src/components/setup/personalization/personalization.component.scss: -------------------------------------------------------------------------------- 1 | .personalization { 2 | &__input { 3 | margin: 5vh 0 3.5vh; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/setup/personalization/personalization.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; 2 | 3 | import { PersonalizationService } from './personalization.service'; 4 | 5 | @Component({ 6 | selector: 'app-config-setup-personalization', 7 | templateUrl: './personalization.component.html', 8 | styleUrls: ['./personalization.component.scss', '../setup.component.scss'], 9 | standalone: false, 10 | }) 11 | export class PersonalizationComponent implements OnInit { 12 | @Input() printerName: string; 13 | @Input() useTouchscreen: boolean; 14 | @Input() octoprintURL: string; 15 | @Input() apiKey: string; 16 | 17 | @Output() printerNameChange = new EventEmitter(); 18 | @Output() useTouchscreenChange = new EventEmitter(); 19 | 20 | constructor(private personalizationService: PersonalizationService) {} 21 | 22 | ngOnInit(): void { 23 | this.personalizationService 24 | .getActivePrinterProfileName(this.octoprintURL, this.apiKey) 25 | .subscribe((printerName: string) => { 26 | if (!this.printerName) { 27 | this.printerName = printerName; 28 | this.printerNameChange.emit(this.printerName); 29 | } 30 | }); 31 | } 32 | 33 | changeUseTouchscreen(): void { 34 | this.useTouchscreen = !this.useTouchscreen; 35 | this.useTouchscreenChange.emit(this.useTouchscreen); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/setup/personalization/personalization.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { defaultIfEmpty, map } from 'rxjs/operators'; 5 | 6 | import { OctoprintPrinterProfiles } from '../../../model/octoprint'; 7 | 8 | @Injectable({ 9 | providedIn: 'root', 10 | }) 11 | export class PersonalizationService { 12 | public constructor(private http: HttpClient) {} 13 | 14 | public getActivePrinterProfileName(octoprintURL: string, apiKey: string): Observable { 15 | return this.http 16 | .get(`${octoprintURL}api/printerprofiles`, { 17 | headers: new HttpHeaders({ 18 | 'x-api-key': apiKey, 19 | }), 20 | }) 21 | .pipe( 22 | map(profiles => { 23 | for (const profile of Object.values(profiles.profiles)) { 24 | if (profile.current) return profile.name; 25 | } 26 | }), 27 | defaultIfEmpty(''), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/setup/plugins/plugins.component.scss: -------------------------------------------------------------------------------- 1 | .setup__plugin-list { 2 | margin: 3vh 2.45vw 3vh 4vw; 3 | overflow-y: scroll; 4 | -webkit-overflow-scrolling: touch; 5 | height: 69vh; 6 | 7 | &::before { 8 | content: ''; 9 | width: 91vw; 10 | height: 70vh; 11 | position: fixed; 12 | left: 2vw; 13 | top: 25vh; 14 | z-index: 2; 15 | pointer-events: none; 16 | background: linear-gradient(var(--background), transparent 6%, transparent 94%, var(--background)); 17 | } 18 | 19 | span { 20 | display: block; 21 | font-size: 0.8rem; 22 | margin: 3vh 0; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/setup/plugins/plugins.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-config-setup-plugins', 5 | templateUrl: './plugins.component.html', 6 | styleUrls: ['./plugins.component.scss', '../setup.component.scss'], 7 | standalone: false, 8 | }) 9 | export class PluginsComponent { 10 | @Input() companionPlugin: boolean; 11 | @Input() displayLayerProgressPlugin: boolean; 12 | @Input() enclosurePlugin: boolean; 13 | @Input() filamentManagerPlugin: boolean; 14 | @Input() spoolManagerPlugin: boolean; 15 | @Input() preheatButtonPlugin: boolean; 16 | @Input() printTimeGeniusPlugin: boolean; 17 | @Input() psuControlPlugin: boolean; 18 | @Input() ophomPlugin: boolean; 19 | @Input() tpLinkSmartPlugPlugin: boolean; 20 | @Input() tuyaPlugin: boolean; 21 | @Input() tasmotaPlugin: boolean; 22 | @Input() tasmotaMqttPlugin: boolean; 23 | @Input() wemoPlugin: boolean; 24 | 25 | @Output() companionPluginChange = new EventEmitter(); 26 | @Output() displayLayerProgressPluginChange = new EventEmitter(); 27 | @Output() enclosurePluginChange = new EventEmitter(); 28 | @Output() filamentManagerPluginChange = new EventEmitter(); 29 | @Output() spoolManagerPluginChange = new EventEmitter(); 30 | @Output() preheatButtonPluginChange = new EventEmitter(); 31 | @Output() printTimeGeniusPluginChange = new EventEmitter(); 32 | @Output() psuControlPluginChange = new EventEmitter(); 33 | @Output() ophomPluginChange = new EventEmitter(); 34 | @Output() tpLinkSmartPlugPluginChange = new EventEmitter(); 35 | @Output() tuyaPluginChange = new EventEmitter(); 36 | @Output() tasmotaPluginChange = new EventEmitter(); 37 | @Output() tasmotaMqttPluginChange = new EventEmitter(); 38 | @Output() wemoPluginChange = new EventEmitter(); 39 | } 40 | -------------------------------------------------------------------------------- /src/components/setup/welcome/welcome.component.html: -------------------------------------------------------------------------------- 1 | Hey there! 2 | 3 | It looks like this is the first start of OctoDash. 4 |
5 | I'll help you set up your config and get you started. 6 |
7 | 8 | If you encounter any issues please check the troubleshooting guide in the GitHub Wiki. 9 | 10 |
11 |
12 | Note: you will need to connect a keyboard for the setup process 13 |
14 | 15 | Sorry for bothering you again ... 16 |
17 | 18 | We've released an update, so awesome, we needed to change the config. Please review your new config! 19 | 20 |
21 |
22 | Thanks for choosing OctoDash 23 | 24 |
25 | -------------------------------------------------------------------------------- /src/components/setup/welcome/welcome.component.scss: -------------------------------------------------------------------------------- 1 | .welcome-screen { 2 | &__heading { 3 | display: block; 4 | text-align: center; 5 | font-size: 6vw; 6 | margin-top: 2vh; 7 | margin-bottom: 6vh; 8 | font-weight: 500; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/setup/welcome/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-config-setup-welcome', 5 | templateUrl: './welcome.component.html', 6 | styleUrls: ['./welcome.component.scss', '../setup.component.scss'], 7 | standalone: false, 8 | }) 9 | export class WelcomeComponent { 10 | @Input() update: boolean; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/shared/hotend-icon/hotend-icon.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

{{ tool }}

4 |
5 | -------------------------------------------------------------------------------- /src/components/shared/hotend-icon/hotend-icon.component.scss: -------------------------------------------------------------------------------- 1 | .hotend-icon { 2 | position: relative; 3 | 4 | &__image { 5 | vertical-align: middle; 6 | display: inline-block; 7 | width: 6vw; 8 | } 9 | 10 | &__number { 11 | position: absolute; 12 | width: 6vw; 13 | text-align: center; 14 | top: 1.5vw; 15 | font-size: 1.5vw; 16 | text-shadow: 0 0 0.5vw red; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/shared/hotend-icon/hotend-icon.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-hotend-icon', 5 | templateUrl: './hotend-icon.component.html', 6 | styleUrls: ['./hotend-icon.component.scss'], 7 | standalone: false, 8 | }) 9 | export class HotendIconComponent { 10 | @Input() tool: number; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/shared/quick-control/quick-control.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 |
8 |
9 | +1 10 |
11 |
12 | +10 13 |
14 |
15 | 16 |
22 | {{ value }}{{ unit }} 23 |
24 | 25 |
26 |
27 | -1 28 |
29 |
30 | -10 31 |
32 |
33 | 34 |
35 |
41 | set 42 |
43 |
44 |
45 |
46 | -------------------------------------------------------------------------------- /src/components/shared/quick-control/quick-control.component.scss: -------------------------------------------------------------------------------- 1 | .quick-control { 2 | position: fixed; 3 | inset: 0; 4 | background-color: rgb(0 0 0 / 85%); 5 | transition: opacity 0.4s ease-in-out; 6 | z-index: 10; 7 | 8 | &__controller { 9 | width: 35vw; 10 | margin: 0 auto; 11 | border: solid 0.6vw; 12 | border-radius: 3vw; 13 | margin-top: 1.5vh; 14 | 15 | &-row { 16 | display: flex; 17 | } 18 | 19 | &-value { 20 | padding: 3vh 6vw; 21 | font-size: 8vw; 22 | font-weight: 500; 23 | text-align: center; 24 | 25 | &-unit { 26 | text-align: center; 27 | font-size: 4vw; 28 | font-weight: 400; 29 | } 30 | } 31 | 32 | &-increase { 33 | padding: 3vh 6vw; 34 | font-weight: 500; 35 | flex: 1; 36 | } 37 | 38 | &-decrease { 39 | padding: 3vh 6vw; 40 | font-weight: 500; 41 | flex: 1; 42 | } 43 | 44 | &-set { 45 | height: 10vw; 46 | border-top: solid 0.6vw; 47 | flex: 1; 48 | align-items: center; 49 | display: flex; 50 | 51 | &-span { 52 | text-align: center; 53 | flex: 1; 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/shared/quick-control/quick-control.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-quick-control', 5 | templateUrl: './quick-control.component.html', 6 | styleUrls: ['./quick-control.component.scss'], 7 | standalone: false, 8 | }) 9 | export class QuickControlComponent implements OnInit { 10 | @Input() icon: string; 11 | @Input() unit: string; 12 | @Input() defaultValue: number; 13 | 14 | @Output() onBack = new EventEmitter(); 15 | @Output() onSet = new EventEmitter(); 16 | 17 | public value: number; 18 | 19 | public ngOnInit() { 20 | this.value = this.defaultValue; 21 | } 22 | 23 | public changeValue(value: number): void { 24 | this.value += value; 25 | if (this.value < -999) { 26 | this.value = this.defaultValue; 27 | } else if (this.value < 0) { 28 | this.value = 0; 29 | } else if (this.value > 999) { 30 | this.value = 999; 31 | } 32 | } 33 | 34 | public setValue(): void { 35 | this.onSet.emit(this.value); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/shared/toggle-switch/toggle-switch.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 7 |
8 |
9 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /src/components/shared/toggle-switch/toggle-switch.component.scss: -------------------------------------------------------------------------------- 1 | .wrapper[disabled='true'] { 2 | opacity: 0.5; 3 | pointer-events: none; 4 | } 5 | 6 | .checkbox__wrapper { 7 | height: 2rem; 8 | width: 3rem; 9 | display: inline-block; 10 | vertical-align: middle; 11 | margin: -0.5rem 0; 12 | } 13 | 14 | .label { 15 | display: inline-block; 16 | vertical-align: text-bottom; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/shared/toggle-switch/toggle-switch.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { AnimationItem } from 'lottie-web'; 3 | import { AnimationOptions } from 'ngx-lottie'; 4 | 5 | @Component({ 6 | selector: 'app-toggle-switch', 7 | templateUrl: './toggle-switch.component.html', 8 | styleUrls: ['./toggle-switch.component.scss'], 9 | standalone: false, 10 | }) 11 | export class ToggleSwitchComponent { 12 | @Input() value: boolean; 13 | @Input() disabled: boolean; 14 | @Output() valueChange = new EventEmitter(); 15 | 16 | public toggleSwitchOptions: AnimationOptions = { 17 | path: 'assets/animations/toggle-switch.json', 18 | loop: false, 19 | }; 20 | 21 | private animation: AnimationItem; 22 | 23 | public animationCreated(animation: AnimationItem): void { 24 | this.animation = animation; 25 | this.animation.autoplay = false; 26 | this.animation.setSpeed(4); 27 | if (this.value) { 28 | this.animation.goToAndStop(46, true); 29 | } else { 30 | this.animation.goToAndStop(0, true); 31 | } 32 | } 33 | 34 | public toggleValue(): void { 35 | if (this.disabled) { 36 | return; 37 | } 38 | 39 | this.value = !this.value; 40 | this.valueChange.emit(this.value); 41 | if (this.value) { 42 | this.animation.playSegments([1, 46], true); 43 | } else { 44 | this.animation.playSegments([46, 91], true); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/shared/top-bar/top-bar.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 13 | 17 | 20 | 21 |
4 | 5 | {{ back.text }} 6 | 8 | 9 | 11 | 12 | 14 | {{ next.text }} 15 | 16 | 18 | 19 |
22 | -------------------------------------------------------------------------------- /src/components/shared/top-bar/top-bar.component.scss: -------------------------------------------------------------------------------- 1 | .top-bar { 2 | width: 100%; 3 | height: 10vh; 4 | 5 | & td { 6 | width: 33.3%; 7 | max-height: 10vh; 8 | } 9 | 10 | &__back { 11 | padding: 4vh 2vw 2vh; 12 | font-weight: 500; 13 | 14 | ::ng-deep &-icon { 15 | margin-right: 1.4vw; 16 | margin-left: 1vw; 17 | } 18 | } 19 | 20 | &__next { 21 | padding: 4vh 2vw 2vh; 22 | text-align: right; 23 | font-weight: 500; 24 | 25 | ::ng-deep &-icon { 26 | margin-left: 1.4vw; 27 | margin-right: 1vw; 28 | } 29 | } 30 | 31 | &__center { 32 | text-align: center; 33 | 34 | ::ng-deep &-icon { 35 | width: 7vw; 36 | max-height: 10vh; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/shared/top-bar/top-bar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | type Button = { 5 | text?: string; 6 | icon?: string; 7 | }; 8 | 9 | @Component({ 10 | selector: 'app-top-bar', 11 | templateUrl: './top-bar.component.html', 12 | styleUrls: ['./top-bar.component.scss'], 13 | standalone: false, 14 | }) 15 | export class TopBarComponent implements OnInit { 16 | @Input() backButton: Button | boolean; 17 | @Input() nextButton: Button | boolean; 18 | 19 | @Output() onBack = new EventEmitter(); 20 | @Output() onNext = new EventEmitter(); 21 | 22 | public back: Button; 23 | public next: Button; 24 | 25 | public constructor(private router: Router) {} 26 | 27 | ngOnInit(): void { 28 | if (this.backButton) { 29 | if (this.backButton === true) { 30 | this.backButton = {}; 31 | } 32 | this.back = { 33 | text: this.backButton?.text || $localize`:@@ui-back:back`, 34 | icon: this.backButton?.icon || 'chevron-left', 35 | }; 36 | } 37 | if (this.nextButton) { 38 | if (this.nextButton === true) { 39 | this.nextButton = {}; 40 | } 41 | this.next = { 42 | text: this.nextButton?.text || $localize`:@@ui-next:next`, 43 | icon: this.nextButton?.icon || 'chevron-right', 44 | }; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/standby/standby.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | Shhh! OctoDash is sleeping. 6 | Tap the screen to wake me up again. 7 | 8 | 9 | connecting 10 | 11 | Connection can't be established. Press screen to try again. 12 | 13 | 14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/standby/standby.component.scss: -------------------------------------------------------------------------------- 1 | .standby { 2 | height: 100%; 3 | 4 | &__main-window { 5 | width: 100vw; 6 | margin-top: 15vh; 7 | display: inline-block; 8 | } 9 | 10 | &__custom-actions { 11 | display: inline-block; 12 | position: absolute; 13 | top: 20vh; 14 | } 15 | 16 | &__icon { 17 | height: 45vh; 18 | display: block; 19 | margin: auto; 20 | margin-bottom: 11vh; 21 | } 22 | 23 | &__text { 24 | &-big { 25 | display: block; 26 | text-align: center; 27 | margin-bottom: 5vh; 28 | font-size: 4vw; 29 | animation: fadeIn ease 0.6s; 30 | } 31 | 32 | &-small { 33 | display: block; 34 | text-align: center; 35 | margin-bottom: 5vh; 36 | font-size: 2vw; 37 | animation: fadeIn ease 0.6s; 38 | } 39 | } 40 | 41 | &__error { 42 | position: fixed; 43 | top: 20vh; 44 | left: 20vw; 45 | right: 20vw; 46 | text-align: center; 47 | background-color: black; 48 | opacity: 0.92; 49 | border-radius: 2vw; 50 | padding: 5vh 3vw; 51 | font-size: 3vw; 52 | z-index: 100; 53 | 54 | &-close { 55 | font-size: 2vw; 56 | opacity: 0.8; 57 | display: block; 58 | margin-top: 4vh; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/standby/standby.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | 3 | import { PSUState } from '../../model'; 4 | import { AppService } from '../../services/app.service'; 5 | import { ConfigService } from '../../services/config.service'; 6 | import { EnclosureService } from '../../services/enclosure/enclosure.service'; 7 | import { SystemService } from '../../services/system/system.service'; 8 | 9 | @Component({ 10 | selector: 'app-standby', 11 | templateUrl: './standby.component.html', 12 | styleUrls: ['./standby.component.scss'], 13 | standalone: false, 14 | }) 15 | export class StandbyComponent implements OnInit, OnDestroy { 16 | public connecting = false; 17 | public showConnectionError = false; 18 | private displaySleepTimeout: ReturnType; 19 | private connectErrorTimeout: ReturnType; 20 | 21 | public constructor( 22 | private configService: ConfigService, 23 | private service: AppService, 24 | private enclosureService: EnclosureService, 25 | private systemService: SystemService, 26 | ) {} 27 | 28 | public ngOnInit(): void { 29 | setTimeout(() => { 30 | if (this.configService.getAutomaticScreenSleep()) { 31 | this.displaySleepTimeout = setTimeout(this.service.turnDisplayOff.bind(this.service), 300000); 32 | } 33 | }); 34 | } 35 | 36 | public ngOnDestroy(): void { 37 | clearTimeout(this.displaySleepTimeout); 38 | clearTimeout(this.connectErrorTimeout); 39 | if (this.configService.getAutomaticScreenSleep()) { 40 | this.service.turnDisplayOn(); 41 | } 42 | } 43 | 44 | public reconnect(): void { 45 | this.connecting = true; 46 | if (this.configService.getAutomaticPrinterPowerOn()) { 47 | this.enclosureService.setPSUState(PSUState.ON); 48 | setTimeout(this.connectPrinter.bind(this), 5000); 49 | } else { 50 | this.connectPrinter(); 51 | } 52 | } 53 | 54 | private connectPrinter(): void { 55 | this.systemService.connectPrinter(); 56 | this.connectErrorTimeout = setTimeout(() => { 57 | this.showConnectionError = true; 58 | this.connectErrorTimeout = setTimeout(() => { 59 | this.showConnectionError = false; 60 | this.connecting = false; 61 | }, 30000); 62 | }, 15000); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/update/update.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | downloading update 4 |
5 |
9 | {{ updateProgress.speed }} 10 | MB/s  11 |
12 |
13 | {{ updateProgress.total }}MB 14 | {{ updateProgress.eta }} minutes left 17 |
18 |
19 | installing update 20 |
21 |
22 |
23 | this might take a while 24 |
25 |
26 | v{{ service.latestVersion }} installed 27 | would you like to restart OctoDash now? 28 |
29 | 30 | 31 |
32 |
33 |
34 | -------------------------------------------------------------------------------- /src/components/update/update.component.scss: -------------------------------------------------------------------------------- 1 | .update { 2 | &-container { 3 | position: absolute; 4 | z-index: 20; 5 | top: 10vh; 6 | left: 20vw; 7 | height: 81vh; 8 | width: 60vw; 9 | background-color: var(--background); 10 | border-radius: 2vw; 11 | } 12 | 13 | &-progress-bar { 14 | height: 5.5vh; 15 | border-radius: 2vh; 16 | background-color: var(--success); 17 | width: 25vw; 18 | transition: width 0.7s ease-in-out; 19 | font-size: 0.5rem; 20 | text-align: right; 21 | padding-top: 1.5vh; 22 | overflow: visible; 23 | white-space: nowrap; 24 | 25 | &-no-percentage { 26 | width: 10vw; 27 | animation: bounce-bar 2s ease-in-out infinite; 28 | margin-left: 0; 29 | } 30 | 31 | &__wrapper { 32 | width: 50vw; 33 | margin: 14vh auto 3vh; 34 | display: block; 35 | height: 7vh; 36 | background-color: transparent; 37 | border: 3px solid var(--border); 38 | border-radius: 3vh; 39 | } 40 | } 41 | 42 | &-heading { 43 | display: block; 44 | text-align: center; 45 | margin-top: 12vh; 46 | } 47 | 48 | &-size__total { 49 | display: block; 50 | text-align: right; 51 | font-size: 0.6rem; 52 | margin-right: 5vw; 53 | margin-top: -2vh; 54 | opacity: 0.6; 55 | } 56 | 57 | &-time__remaining { 58 | display: block; 59 | text-align: center; 60 | margin-top: 10vh; 61 | } 62 | 63 | &-notice { 64 | display: block; 65 | text-align: center; 66 | font-size: 0.5rem; 67 | margin-top: 20vh; 68 | } 69 | 70 | &-restart { 71 | display: block; 72 | text-align: center; 73 | font-size: 0.65rem; 74 | margin-top: 10vh; 75 | margin-bottom: 8vw; 76 | 77 | button { 78 | padding: 1.4vh 2vw; 79 | border-radius: 1vw; 80 | box-shadow: 0 10px 19px -8px rgb(0 0 0 / 75%); 81 | font-size: 0.7rem; 82 | outline: 0; 83 | border: 0; 84 | margin: 7vh 2vw; 85 | 86 | .button__no { 87 | background-color: var(--background-3); 88 | opacity: 0.8; 89 | } 90 | 91 | .button__yes { 92 | background-color: var(--success); 93 | } 94 | 95 | .button__wrapper { 96 | display: block; 97 | text-align: center; 98 | } 99 | } 100 | } 101 | } 102 | 103 | @keyframes bounce-bar { 104 | 0%, 105 | 100% { 106 | margin-left: 0; 107 | } 108 | 109 | 50% { 110 | margin-left: 40vw; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/components/update/update.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, NgZone, OnInit, Output } from '@angular/core'; 2 | 3 | import { UpdateDownloadProgress, UpdateError } from '../../model'; 4 | import { AppService } from '../../services/app.service'; 5 | import { ElectronService } from '../../services/electron.service'; 6 | import { NotificationService } from '../../services/notification.service'; 7 | import { SystemService } from '../../services/system/system.service'; 8 | 9 | @Component({ 10 | selector: 'app-update', 11 | templateUrl: './update.component.html', 12 | styleUrls: ['./update.component.scss'], 13 | standalone: false, 14 | }) 15 | export class UpdateComponent implements OnInit { 16 | @Output() closeFunction = new EventEmitter(true); 17 | 18 | public updateProgress: UpdateDownloadProgress = { 19 | percentage: 0, 20 | transferred: 0, 21 | total: '--.-', 22 | remaining: 0, 23 | eta: '--:--', 24 | runtime: '--:--', 25 | delta: 0, 26 | speed: '--.-', 27 | }; 28 | public page = 1; 29 | 30 | constructor( 31 | public service: AppService, 32 | private notificationService: NotificationService, 33 | private systemService: SystemService, 34 | private zone: NgZone, 35 | private electronService: ElectronService, 36 | ) {} 37 | 38 | ngOnInit(): void { 39 | if (!this.service.getLatestVersion() || !this.service.getLatestVersionAssetsURL()) { 40 | this.notificationService.error( 41 | $localize`:@@error-update:Can't initiate update!`, 42 | $localize`:@@error-update-message:Some information is missing, please try again in an hour or update manually.`, 43 | ); 44 | this.closeUpdateWindow(); 45 | } else { 46 | this.setupListeners(); 47 | this.update(this.service.getLatestVersionAssetsURL()); 48 | } 49 | } 50 | 51 | private setupListeners(): void { 52 | this.electronService.on('updateError', (_, updateError: UpdateError): void => { 53 | this.notificationService.error( 54 | $localize`:@@error-install-update:Can't install update!`, 55 | updateError.error.message, 56 | ); 57 | this.closeUpdateWindow(); 58 | }); 59 | 60 | this.electronService.on('updateDownloadProgress', (_, updateDownloadProgress: UpdateDownloadProgress): void => { 61 | this.zone.run(() => { 62 | this.updateProgress = updateDownloadProgress; 63 | }); 64 | }); 65 | 66 | this.electronService.on('updateDownloadFinished', (): void => { 67 | this.zone.run(() => { 68 | this.page = 2; 69 | }); 70 | }); 71 | 72 | this.electronService.on('updateInstalled', (): void => { 73 | this.zone.run(() => { 74 | this.page = 3; 75 | }); 76 | }); 77 | } 78 | 79 | public closeUpdateWindow(): void { 80 | this.page = 1; 81 | this.closeFunction.emit(); 82 | } 83 | 84 | private update(assetsURL: string): void { 85 | this.electronService.send('update', { 86 | assetsURL: assetsURL, 87 | }); 88 | } 89 | 90 | public restart(): void { 91 | this.electronService.send('restart'); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/directives/index.ts: -------------------------------------------------------------------------------- 1 | import { LongPress } from './long-press.directive'; 2 | 3 | export default [LongPress]; 4 | -------------------------------------------------------------------------------- /src/directives/long-press.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[long-press]', 5 | standalone: false, 6 | }) 7 | export class LongPress { 8 | pressing: boolean; 9 | longPressing: boolean; 10 | timeout: ReturnType; 11 | interval: ReturnType; 12 | 13 | @Input() duration = 700; 14 | @Input() frequency = 100; 15 | 16 | @Output() 17 | onShortPress = new EventEmitter(); 18 | 19 | @Output() 20 | onLongPress = new EventEmitter(); 21 | 22 | @Output() 23 | onLongPressing = new EventEmitter(); 24 | 25 | @HostListener('pointerdown', ['$event']) 26 | onMouseDown(event: PointerEvent): void { 27 | // Right clicks count as instant long presses 28 | const duration = event.button == 2 ? 0 : this.duration; 29 | 30 | this.pressing = true; 31 | this.longPressing = false; 32 | 33 | this.timeout = setTimeout(() => { 34 | this.longPressing = true; 35 | this.onLongPress.emit(event); 36 | 37 | this.interval = setInterval(() => { 38 | this.onLongPressing.emit(event); 39 | }, this.frequency); 40 | }, duration); 41 | } 42 | 43 | @HostListener('pointerup', ['$event']) 44 | endPress(event: PointerEvent): void { 45 | clearTimeout(this.timeout); 46 | clearInterval(this.interval); 47 | 48 | if (!this.longPressing && this.pressing) { 49 | this.onShortPress.emit(event); 50 | } 51 | this.longPressing = false; 52 | this.pressing = false; 53 | } 54 | 55 | @HostListener('pointerleave', ['$event']) 56 | endPressMove(): void { 57 | clearTimeout(this.timeout); 58 | clearInterval(this.interval); 59 | 60 | this.longPressing = false; 61 | this.pressing = false; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/src/favicon.ico -------------------------------------------------------------------------------- /src/helper/config.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | 3 | import { Ajv } from 'ajv'; 4 | import Store from 'electron-store'; 5 | 6 | import configSchema from './config.schema.json' with { type: 'json' }; 7 | 8 | let store; 9 | 10 | const ajv = new Ajv({ useDefaults: true, allErrors: true }); 11 | 12 | // Define keywords for schema->TS converter 13 | ajv.addKeyword('tsEnumNames'); 14 | ajv.addKeyword('tsName'); 15 | ajv.addKeyword('tsType'); 16 | 17 | const validate = ajv.compile(configSchema); 18 | 19 | export function readConfig(window) { 20 | try { 21 | if (!store) { 22 | store = new Store(); 23 | } 24 | const config = store.get('config'); 25 | window.webContents.send('configRead', config); 26 | } catch { 27 | window.webContents.send('configError', "Can't read config file."); 28 | } 29 | } 30 | export function resetConfig(window) { 31 | try { 32 | store.delete('config'); 33 | window.webContents.send('configErased'); 34 | } catch { 35 | window.webContents.send('configError', "Can't reset config file."); 36 | } 37 | } 38 | export function saveConfig(window, config) { 39 | if (validate(config)) { 40 | try { 41 | store.set('config', config); 42 | window.webContents.send('configSaved', config); 43 | } catch { 44 | window.webContents.send('configError', "Can't save config file."); 45 | } 46 | } else { 47 | window.webContents.send('configSaveFail', getConfigErrors()); 48 | } 49 | } 50 | export function checkConfig(window, config) { 51 | if (!validate(config)) { 52 | window.webContents.send('configFail', getConfigErrors()); 53 | } else { 54 | window.webContents.send('configPass'); 55 | } 56 | } 57 | function getConfigErrors() { 58 | const errors = []; 59 | validate.errors?.forEach(error => { 60 | if (error.keyword === 'type') { 61 | errors.push(`${error.instancePath} ${error.message}`); 62 | } else { 63 | errors.push(`${error.instancePath === '' ? '/' : error.instancePath} ${error.message}`); 64 | } 65 | }); 66 | return errors; 67 | } 68 | 69 | function extractDefaults(data) { 70 | if ('default' in data) { 71 | return data.default; 72 | } 73 | 74 | if (data.type === 'object' && data.properties) { 75 | const obj = {}; 76 | for (const [key, propdata] of Object.entries(data.properties)) { 77 | obj[key] = extractDefaults(propdata); 78 | } 79 | return obj; 80 | } 81 | 82 | if (data.type === 'array' && data.default) { 83 | return data.default; 84 | } 85 | 86 | return undefined; 87 | } 88 | 89 | export function writeDefaultConfig() { 90 | try { 91 | const defaultConfig = extractDefaults(configSchema); 92 | fs.writeFileSync(new URL('./config.default.json', import.meta.url), JSON.stringify(defaultConfig)); 93 | } catch (e) { 94 | console.log(e); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/helper/discover.js: -------------------------------------------------------------------------------- 1 | import { exec } from 'node:child_process'; 2 | 3 | import { Bonjour } from 'bonjour-service'; 4 | 5 | const bonjour = new Bonjour(); 6 | 7 | let browser; 8 | let nodes = []; 9 | 10 | export function startDiscovery(window) { 11 | exec('hostname', (err, stdout) => { 12 | if (err) { 13 | discoverNodes(window, null); 14 | } else { 15 | discoverNodes(window, `${stdout}.local`); 16 | } 17 | }); 18 | } 19 | 20 | function discoverNodes(window, localDomain) { 21 | nodes = []; 22 | browser = bonjour.find({ type: 'octoprint' }); 23 | browser.on('up', service => { 24 | nodes.push({ 25 | id: service.addresses[0] + service.port, 26 | name: service.name, 27 | version: service.txt.version, 28 | url: `http://${service.host.replace(/\.$/, '')}:${service.port}${service.txt.path}`, 29 | local: service.host === localDomain, 30 | }); 31 | sendNodes(window); 32 | }); 33 | 34 | browser.on('down', service => { 35 | nodes = nodes.filter(node => node.id !== service.interfaceIndex); 36 | sendNodes(window); 37 | }); 38 | 39 | browser.start(); 40 | } 41 | 42 | export function stopDiscovery() { 43 | browser.stop(); 44 | } 45 | 46 | function sendNodes(window) { 47 | window.webContents.send('discoveredNodes', nodes); 48 | } 49 | -------------------------------------------------------------------------------- /src/helper/listener.js: -------------------------------------------------------------------------------- 1 | import { exec } from 'node:child_process'; 2 | 3 | import { checkConfig, readConfig, resetConfig, saveConfig } from './config.js'; 4 | import { startDiscovery, stopDiscovery } from './discover.js'; 5 | import sendCustomStyles from './styles.js'; 6 | import { downloadUpdate, restartOctoDash, sendVersionInfo } from './update.js'; 7 | 8 | function activateScreenSleepListener(ipcMain) { 9 | ipcMain.on('screenControl', (_, screenCommand) => exec(screenCommand.command)); 10 | } 11 | 12 | function activateReloadListener(ipcMain, window, url) { 13 | ipcMain.on('reload', () => { 14 | window.loadURL(url); 15 | }); 16 | } 17 | 18 | function activateAppInfoListener(ipcMain, window, app) { 19 | ipcMain.on('appInfo', () => { 20 | sendCustomStyles(window); 21 | sendVersionInfo(window, app); 22 | }); 23 | } 24 | 25 | function activateUpdateListener(ipcMain, window) { 26 | ipcMain.on('update', (_, updateInfo) => downloadUpdate(updateInfo, window)); 27 | } 28 | 29 | function activateRestartListener(ipcMain, app) { 30 | ipcMain.on('restart', () => restartOctoDash(app)); 31 | } 32 | 33 | function activateDiscoverListener(ipcMain, window) { 34 | ipcMain.on('discover', () => startDiscovery(window)); 35 | 36 | ipcMain.on('stopDiscover', () => stopDiscovery()); 37 | } 38 | 39 | function activateConfigListener(ipcMain, window) { 40 | ipcMain.on('readConfig', () => readConfig(window)); 41 | ipcMain.on('resetConfig', () => resetConfig(window)); 42 | ipcMain.on('saveConfig', (_, config) => saveConfig(window, config)); 43 | ipcMain.on('checkConfig', (_, config) => checkConfig(window, config)); 44 | } 45 | 46 | function activateListeners(ipcMain, window, app, url) { 47 | activateConfigListener(ipcMain, window); 48 | activateAppInfoListener(ipcMain, window, app); 49 | activateScreenSleepListener(ipcMain); 50 | activateReloadListener(ipcMain, window, url); 51 | activateRestartListener(ipcMain, app); 52 | activateUpdateListener(ipcMain, window); 53 | activateDiscoverListener(ipcMain, window); 54 | } 55 | 56 | export default activateListeners; 57 | -------------------------------------------------------------------------------- /src/helper/protocol.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | 4 | import electron from 'electron'; 5 | import mime from 'mime-types'; 6 | 7 | const { app, protocol } = electron; 8 | 9 | function createProtocol(scheme, basePath) { 10 | if (!app.isReady()) return app.on('ready', () => createProtocol(...arguments)); 11 | 12 | protocol.registerBufferProtocol(scheme, (request, callback) => { 13 | const filePath = path.join(basePath, request.url.replace(`${scheme}://`, '')); 14 | fs.readFile(filePath, (error, buffer) => { 15 | if (error) { 16 | fs.readFile(path.join(basePath, 'index.html'), (_, buffer) => { 17 | callback({ mimeType: mime.lookup('index.html'), data: buffer }); 18 | }); 19 | } else { 20 | callback({ mimeType: mime.lookup(filePath), data: buffer }); 21 | } 22 | }); 23 | }); 24 | } 25 | 26 | export default createProtocol; 27 | -------------------------------------------------------------------------------- /src/helper/styles.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | 4 | import electron from 'electron'; 5 | 6 | const { app } = electron; 7 | 8 | function sendCustomStyles(window) { 9 | fs.readFile(path.join(app.getPath('userData'), 'custom-styles.css'), 'utf-8', (err, data) => { 10 | if (err) { 11 | if (err.code === 'ENOENT') { 12 | fs.writeFile(path.join(app.getPath('userData'), 'custom-styles.css'), '', err => { 13 | if (err) { 14 | window.webContents.send('customStylesError', err); 15 | } else { 16 | window.webContents.send('customStyles', ''); 17 | } 18 | }); 19 | } else { 20 | window.webContents.send('customStylesError', err); 21 | } 22 | } else { 23 | window.webContents.send('customStyles', data); 24 | } 25 | }); 26 | } 27 | 28 | export default sendCustomStyles; 29 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OctoDash 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | by UnchartedBull + contributors 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { enableProdMode } from '@angular/core'; 4 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 5 | 6 | import { AppModule } from './app.module'; 7 | import { environment } from './environments/environment'; 8 | 9 | if (environment.production) { 10 | enableProdMode(); 11 | } 12 | 13 | platformBrowserDynamic() 14 | .bootstrapModule(AppModule) 15 | .catch((err): void => console.error(err)); 16 | -------------------------------------------------------------------------------- /src/model/auth.model.ts: -------------------------------------------------------------------------------- 1 | export interface SocketAuth { 2 | user: string; 3 | session: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/model/enclosure.model.ts: -------------------------------------------------------------------------------- 1 | export interface TemperatureReading { 2 | temperature: number; 3 | humidity: number; 4 | unit: string; 5 | } 6 | 7 | export enum PSUState { 8 | ON, 9 | OFF, 10 | } 11 | -------------------------------------------------------------------------------- /src/model/event.model.ts: -------------------------------------------------------------------------------- 1 | export enum PrinterEvent { 2 | PRINTING, 3 | PAUSED, 4 | CLOSED, 5 | CONNECTED, 6 | IDLE, 7 | UNKNOWN, 8 | } 9 | 10 | export interface PrinterNotification { 11 | message?: string; 12 | action?: string; 13 | text?: string; 14 | choices?: string[]; 15 | } 16 | -------------------------------------------------------------------------------- /src/model/filament.model.ts: -------------------------------------------------------------------------------- 1 | export interface FilamentSpoolList { 2 | spools: Array; 3 | } 4 | 5 | export interface FilamentSpool { 6 | id: number; 7 | name: string; 8 | displayName: string; 9 | color: string; 10 | material: string; 11 | temperatureOffset: number; 12 | used: number; 13 | weight: number; 14 | vendor: string; 15 | diameter: number; 16 | density: number; 17 | } 18 | -------------------------------------------------------------------------------- /src/model/files.model.ts: -------------------------------------------------------------------------------- 1 | export interface Directory { 2 | folders: Array; 3 | files: Array; 4 | } 5 | 6 | export interface Folder { 7 | origin: string; 8 | path: string; 9 | name: string; 10 | size: string; 11 | } 12 | 13 | export interface File { 14 | origin: string; 15 | path: string; 16 | name: string; 17 | size: string; 18 | thumbnail: string; 19 | printTime?: string; 20 | filamentWeight?: number; 21 | date?: string; 22 | successful: 'files__object--success' | 'files__object--failed' | 'files__object--unknown'; 23 | } 24 | -------------------------------------------------------------------------------- /src/model/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth.model'; 2 | export * from './config.model'; 3 | export * from './enclosure.model'; 4 | export * from './event.model'; 5 | export * from './filament.model'; 6 | export * from './files.model'; 7 | export * from './job.model'; 8 | export * from './printer-profile.model'; 9 | export * from './printer.model'; 10 | export * from './system.model'; 11 | export * from './util-test.model'; 12 | -------------------------------------------------------------------------------- /src/model/job.model.ts: -------------------------------------------------------------------------------- 1 | export interface JobStatus { 2 | file: string; 3 | fullPath: string; 4 | progress: number; 5 | zHeight: number | ZHeightLayer; 6 | filamentAmount?: number; 7 | timePrinted: Duration; 8 | timeLeft?: Duration; 9 | estimatedPrintTime?: Duration; 10 | estimatedEndTime?: string; 11 | } 12 | 13 | export interface Duration { 14 | value: string; 15 | unit: string; 16 | } 17 | 18 | export interface ZHeightLayer { 19 | current: number; 20 | total: number; 21 | } 22 | -------------------------------------------------------------------------------- /src/model/octoprint/auth.model.ts: -------------------------------------------------------------------------------- 1 | export interface OctoprintLogin { 2 | _is_external_client: boolean; 3 | _login_mechanism: string; 4 | active: boolean; 5 | admin: boolean; 6 | apikey: string; 7 | groups: Array; 8 | name: string; 9 | needs: { 10 | groups: Array; 11 | role: Array; 12 | }; 13 | permissions: Array; 14 | roles: Array; 15 | session: string; 16 | user: boolean; 17 | } 18 | 19 | export interface AppToken { 20 | app_token: string; 21 | } 22 | 23 | export interface TokenSuccess { 24 | api_key: string; 25 | } 26 | -------------------------------------------------------------------------------- /src/model/octoprint/connection.model.ts: -------------------------------------------------------------------------------- 1 | export interface ConnectCommand { 2 | command: string; 3 | port?: string; 4 | baudrate?: number; 5 | printerProfile?: string; 6 | save?: boolean; 7 | autoconnect?: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /src/model/octoprint/file.model.ts: -------------------------------------------------------------------------------- 1 | import { OctoprintFilament } from './socket.model'; 2 | 3 | export interface OctoprintFile { 4 | date: number; 5 | display: string; 6 | gcodeAnalysis?: OctoprintGCodeAnalysis; 7 | hash: string; 8 | name: string; 9 | origin: string; 10 | path: string; 11 | prints: OctoprintPrints; 12 | refs: OctoprintRefs; 13 | size: number; 14 | statistics?: Record; 15 | type: string; 16 | typePath: [string]; 17 | thumbnail?: string; 18 | } 19 | 20 | export interface OctoprintFolder { 21 | children: [OctoprintFile & OctoprintFolder]; 22 | display: string; 23 | name: string; 24 | origin: string; 25 | path: string; 26 | refs: OctoprintRefs; 27 | type: string; 28 | typePath: [string]; 29 | } 30 | 31 | interface OctoprintGCodeAnalysis { 32 | analysisFirstFilamentPrintTime: number; 33 | analysisLastFilamentPrintTime: number; 34 | analysisPending: boolean; 35 | analysisPrintTime: number; 36 | compensatedPrintTime: number; 37 | dimensions: { 38 | depth: number; 39 | height: number; 40 | width: number; 41 | }; 42 | estimatedPrintTime: number; 43 | filament: OctoprintFilament; 44 | firstFilament: number; 45 | lastFilament: number; 46 | printingArea: { 47 | maxX: number; 48 | maxY: number; 49 | maxZ: number; 50 | minX: number; 51 | minY: number; 52 | minZ: number; 53 | }; 54 | progress: [[number, number]]; 55 | } 56 | 57 | interface OctoprintPrints { 58 | failure: number; 59 | success: number; 60 | last: { 61 | date: number; 62 | printTime: number; 63 | success: boolean; 64 | }; 65 | } 66 | 67 | interface OctoprintRefs { 68 | download?: string; 69 | resource: string; 70 | } 71 | 72 | export interface FileCommand { 73 | command: 'select'; 74 | print: boolean; 75 | } 76 | -------------------------------------------------------------------------------- /src/model/octoprint/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth.model'; 2 | export * from './connection.model'; 3 | export * from './file.model'; 4 | export * from './job.model'; 5 | export * from './printer-commands.model'; 6 | export * from './printer-profile.model'; 7 | export * from './socket.model'; 8 | 9 | export * from './plugins/companion.model'; 10 | export * from './plugins/display-layer-progress.model'; 11 | export * from './plugins/enclosure.model'; 12 | export * from './plugins/filament-manager.model'; 13 | export * from './plugins/spool-manager.model'; 14 | export * from './plugins/psucontrol.model'; 15 | export * from './plugins/ophomplugstatus.model'; 16 | export * from './plugins/tp-link.model'; 17 | export * from './plugins/tasmota.model'; 18 | export * from './plugins/tasmota-mqtt.model'; 19 | export * from './plugins/tuya.model'; 20 | export * from './plugins/wemo.model'; 21 | -------------------------------------------------------------------------------- /src/model/octoprint/job.model.ts: -------------------------------------------------------------------------------- 1 | export interface JobCommand { 2 | command: string; 3 | action?: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/model/octoprint/plugins/companion.model.ts: -------------------------------------------------------------------------------- 1 | export interface CompanionData { 2 | fanspeed: object; 3 | } 4 | -------------------------------------------------------------------------------- /src/model/octoprint/plugins/display-layer-progress.model.ts: -------------------------------------------------------------------------------- 1 | export interface DisplayLayerProgressData { 2 | averageLayerDuration: string; 3 | averageLayerDurationInSeconds: string; 4 | changeFilamentCount: number; 5 | changeFilamentTimeLeft: string; 6 | changeFilamentTimeLeftInSeconds: string; 7 | currentHeight: string; 8 | currentHeightFormatted: string; 9 | currentLayer: string; 10 | estimatedChangeFilamentTime: string; 11 | estimatedEndTime: string; 12 | fanspeed: string; 13 | feedrate: string; 14 | feedrateG0: string; 15 | feedrateG1: string; 16 | lastLayerDuration: string; 17 | m73progress: string; 18 | printTimeLeft: string; 19 | printTimeLeftInSeconds: string; 20 | printerState: string; 21 | progress: string; 22 | totalHeight: string; 23 | totalHeightFormatted: string; 24 | totalLayer: string; 25 | updateReason: string; 26 | } 27 | -------------------------------------------------------------------------------- /src/model/octoprint/plugins/enclosure.model.ts: -------------------------------------------------------------------------------- 1 | export interface EnclosurePluginAPI { 2 | controlled_io: string; 3 | temp_sensor_address: string; 4 | temp_sensor_navbar: boolean; 5 | temp_sensor_temp: number; 6 | printer_action: string; 7 | filament_sensor_enabled: boolean; 8 | controlled_io_set_value: number; 9 | temp_sensor_type: string; 10 | temp_sensor_humidity: number; 11 | filament_sensor_timeout: number; 12 | edge: string; 13 | ds18b20_serial: string; 14 | action_type: string; 15 | input_pull_resistor: string; 16 | input_type: string; 17 | label: string; 18 | index_id: number; 19 | use_fahrenheit: boolean; 20 | gpio_pin: string; 21 | } 22 | 23 | export interface EnclosureColorBody { 24 | red: number; 25 | green: number; 26 | blue: number; 27 | } 28 | 29 | export interface EnclosureOutputBody { 30 | status: boolean; 31 | } 32 | 33 | export interface EnclosurePWMBody { 34 | duty_cycle: number; 35 | } 36 | -------------------------------------------------------------------------------- /src/model/octoprint/plugins/filament-manager.model.ts: -------------------------------------------------------------------------------- 1 | export interface FilamentManagerSpoolList { 2 | spools: FilamentManagerSpool[]; 3 | } 4 | 5 | export interface FilamentManagerSelections { 6 | selections: FilamentManagerSelection[]; 7 | } 8 | 9 | export interface FilamentManagerSelectionPatch { 10 | selection: { 11 | tool: number; 12 | spool: { 13 | id: number; 14 | }; 15 | }; 16 | } 17 | 18 | interface FilamentManagerSelection { 19 | client_id: string; 20 | spool: FilamentManagerSpool; 21 | tool: number; 22 | } 23 | 24 | export interface FilamentManagerSpool { 25 | cost: number; 26 | id: number; 27 | name: string; 28 | displayName?: string; 29 | color?: string; 30 | profile: FilamentManagerProfile; 31 | temp_offset: number; 32 | used: number; 33 | weight: number; 34 | } 35 | 36 | interface FilamentManagerProfile { 37 | density: number; 38 | diameter: number; 39 | id: number; 40 | material: string; 41 | vendor: string; 42 | } 43 | -------------------------------------------------------------------------------- /src/model/octoprint/plugins/ophomplugstatus.model.ts: -------------------------------------------------------------------------------- 1 | export interface OphomPlugStatus { 2 | reponse: number; 3 | } 4 | -------------------------------------------------------------------------------- /src/model/octoprint/plugins/psucontrol.model.ts: -------------------------------------------------------------------------------- 1 | export interface PSUControlCommand { 2 | command: 'turnPSUOn' | 'turnPSUOff'; 3 | } 4 | -------------------------------------------------------------------------------- /src/model/octoprint/plugins/spool-manager.model.ts: -------------------------------------------------------------------------------- 1 | export interface SpoolManagerSpoolList { 2 | allSpools: Array; 3 | selectedSpools: Array; 4 | catalogs: SpoolManagerCatalogs; 5 | templateSpool: SpoolManagerSpool; 6 | totalItemCount: string; 7 | } 8 | 9 | interface SpoolManagerCatalogs { 10 | labels: Array; 11 | materials: Array; 12 | vendors: Array; 13 | } 14 | 15 | export interface SpoolManagerSelectionPut { 16 | databaseId: number; 17 | toolIndex: number; 18 | } 19 | 20 | export interface SpoolManagerSpool { 21 | bedTemperature: number; 22 | code: unknown; 23 | color: string; 24 | colorName: string; 25 | cost: number; 26 | costUnit: string; 27 | created: string; 28 | databaseId: number; 29 | density: number; 30 | diameter: number; 31 | diameterTolerance: number; 32 | displayName: string; 33 | enclosureTemperature: number; 34 | firstUse: string; 35 | flowRateCompensation: number; 36 | isActive: boolean; 37 | isTemplate: boolean; 38 | labels: string; 39 | lastUse: string; 40 | material: string; 41 | materialCharacteristic: unknown; 42 | noteDeltaFormat: string; 43 | noteHtml: string; 44 | noteText: string; 45 | offsetBedTemperature: number; 46 | offsetEnclosureTemperature: number; 47 | offsetTemperature: number; 48 | originator: unknown; 49 | purchasedFrom: string; 50 | purchasedOn: string; 51 | remainingLength: string; 52 | remainingLengthPercentage: string; 53 | remainingPercentage: string; 54 | remainingWeight: string; 55 | spoolWeight: string; 56 | temperature: number; 57 | totalLength: number; 58 | totalWeight: number; 59 | updated: string; 60 | usedLength: number; 61 | usedLengthPercentage: string; 62 | usedPercentage: string; 63 | usedWeight: string; 64 | vendor: string; 65 | version: number; 66 | } 67 | -------------------------------------------------------------------------------- /src/model/octoprint/plugins/tasmota-mqtt.model.ts: -------------------------------------------------------------------------------- 1 | export interface TasmotaMqttCommand { 2 | command: 'turnOn' | 'turnOff'; 3 | topic: string; 4 | relayN: number; 5 | } 6 | -------------------------------------------------------------------------------- /src/model/octoprint/plugins/tasmota.model.ts: -------------------------------------------------------------------------------- 1 | export interface TasmotaCommand { 2 | command: 'turnOn' | 'turnOff'; 3 | ip: string; 4 | idx: number; 5 | } 6 | -------------------------------------------------------------------------------- /src/model/octoprint/plugins/tp-link.model.ts: -------------------------------------------------------------------------------- 1 | export interface TPLinkCommand { 2 | command: 'turnOn' | 'turnOff'; 3 | ip: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/model/octoprint/plugins/tuya.model.ts: -------------------------------------------------------------------------------- 1 | export interface TuyaCommand { 2 | command: 'turnOn' | 'turnOff'; 3 | label: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/model/octoprint/plugins/wemo.model.ts: -------------------------------------------------------------------------------- 1 | export interface WemoCommand { 2 | command: 'turnOn' | 'turnOff'; 3 | ip: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/model/octoprint/printer-commands.model.ts: -------------------------------------------------------------------------------- 1 | export interface JogCommand { 2 | command: 'jog'; 3 | x: number; 4 | y: number; 5 | z: number; 6 | speed: number; 7 | } 8 | 9 | export interface ExtrudeCommand { 10 | command: 'extrude'; 11 | amount: number; 12 | speed: number; 13 | } 14 | 15 | export interface GCodeCommand { 16 | commands: string[]; 17 | } 18 | 19 | export interface FeedrateCommand { 20 | command: string; 21 | factor: number; 22 | } 23 | 24 | export interface ToolCommand { 25 | command: string; 26 | tool: string; 27 | } 28 | 29 | export interface TemperatureHotendCommand { 30 | command: string; 31 | targets: { 32 | [K in string as K extends string ? `tool${K}` : never]: number; 33 | }; 34 | } 35 | 36 | export interface TemperatureHeatbedCommand { 37 | command: string; 38 | target: number; 39 | } 40 | 41 | export interface DisconnectCommand { 42 | command: string; 43 | } 44 | -------------------------------------------------------------------------------- /src/model/octoprint/printer-profile.model.ts: -------------------------------------------------------------------------------- 1 | export interface OctoprintPrinterProfiles { 2 | profiles: { 3 | [key: string]: OctoprintPrinterProfile; 4 | }; 5 | } 6 | 7 | export interface OctoprintPrinterProfile { 8 | current: boolean; 9 | name: string; 10 | model: string; 11 | axes: OctoprintPrinterAxis; 12 | extruder: OctoprintPrinterExtruders; 13 | } 14 | 15 | interface OctoprintPrinterAxis { 16 | x: OctoprintAxisDetails; 17 | y: OctoprintAxisDetails; 18 | z: OctoprintAxisDetails; 19 | } 20 | 21 | interface OctoprintAxisDetails { 22 | inverted: boolean; 23 | } 24 | 25 | interface OctoprintPrinterExtruders { 26 | count: number; 27 | offsets: OctoprintPrinterExtruderOffset[]; 28 | sharedNozzle: boolean; 29 | } 30 | 31 | interface OctoprintPrinterExtruderOffset { 32 | x: number; 33 | y: number; 34 | } 35 | -------------------------------------------------------------------------------- /src/model/octoprint/socket.model.ts: -------------------------------------------------------------------------------- 1 | import { OctoprintFile } from './file.model'; 2 | 3 | export interface OctoprintSocketCurrent { 4 | current: { 5 | busyFiles: Array; 6 | currentZ: number; 7 | job: OctoprintJob; 8 | logs: Array; 9 | messages: Array; 10 | offsets: OctoprintOffsets; 11 | progress: OctoprintProgress; 12 | resends: OctoprintSocketResends; 13 | serverTime: number; 14 | state: OctoprintSocketState; 15 | temps: OctoprintSocketTemperatures; 16 | }; 17 | } 18 | 19 | export interface OctoprintSocketEvent { 20 | event: { 21 | type: string; 22 | payload: { 23 | error: string; 24 | reason: string; 25 | }; 26 | }; 27 | } 28 | export interface OctoprintPluginMessage { 29 | plugin: { 30 | plugin: string; 31 | data: unknown; 32 | }; 33 | } 34 | 35 | interface OctoprintJob { 36 | averagePrintTime: number; 37 | estimatedPrintTime: number; 38 | filament: OctoprintFilament; 39 | file: OctoprintFile; 40 | lastPrintTime: string; 41 | user: string; 42 | } 43 | export interface OctoprintFilament { 44 | [key: string]: OctoprintFilamentValues; 45 | } 46 | 47 | interface OctoprintFilamentValues { 48 | length: number; 49 | volume: number; 50 | } 51 | 52 | type OctoprintOffsets = { 53 | [K in string as K extends string ? `tool${K}` : never]: number; 54 | }; 55 | 56 | interface OctoprintProgress { 57 | completion: number; 58 | filepos: number; 59 | printTime: number; 60 | printTimeLeft: number; 61 | printTimeLeftOrigin: string; 62 | } 63 | 64 | interface OctoprintSocketResends { 65 | count: number; 66 | transmitted: number; 67 | ratio: number; 68 | } 69 | 70 | interface OctoprintSocketState { 71 | text: string; 72 | flags: OctoprintSocketStateFlags; 73 | } 74 | 75 | interface OctoprintSocketStateFlags { 76 | cancelling: boolean; 77 | closedOrError: boolean; 78 | error: boolean; 79 | finishing: boolean; 80 | operational: boolean; 81 | paused: boolean; 82 | pausing: boolean; 83 | printing: boolean; 84 | ready: boolean; 85 | resuming: boolean; 86 | sdReady: boolean; 87 | } 88 | 89 | interface OctoprintSocketTemperatures { 90 | [key: number]: { 91 | time: number; 92 | bed: OctoprintSocketTemperature; 93 | chamber: OctoprintSocketTemperature; 94 | } & { 95 | [K in string as K extends string ? `tool${K}` : never]: OctoprintSocketTemperature; 96 | }; 97 | } 98 | 99 | interface OctoprintSocketTemperature { 100 | actual: number; 101 | target: number; 102 | } 103 | 104 | export interface OctoprintVersionInfo { 105 | api: string; 106 | server: string; 107 | text: string; 108 | } 109 | -------------------------------------------------------------------------------- /src/model/printer-profile.model.ts: -------------------------------------------------------------------------------- 1 | export interface PrinterProfile { 2 | current: boolean; 3 | name: string; 4 | model: string; 5 | axes: PrinterAxis; 6 | extruder: PrinterExtruders; 7 | } 8 | 9 | export interface PrinterAxis { 10 | x: AxisDetails; 11 | y: AxisDetails; 12 | z: AxisDetails; 13 | } 14 | 15 | export interface AxisDetails { 16 | inverted: boolean; 17 | } 18 | 19 | export interface PrinterExtruders { 20 | count: number; 21 | offsets: PrinterExtruderOffset[]; 22 | sharedNozzle: boolean; 23 | } 24 | 25 | interface PrinterExtruderOffset { 26 | x: number; 27 | y: number; 28 | } 29 | -------------------------------------------------------------------------------- /src/model/printer.model.ts: -------------------------------------------------------------------------------- 1 | export interface PrinterStatus { 2 | status: PrinterState; 3 | bed: Temperature; 4 | chamber: Temperature; 5 | fanSpeed: number; 6 | tools: Temperature[]; 7 | } 8 | 9 | interface Temperature { 10 | current: number; 11 | set: number; 12 | unit: string; 13 | } 14 | 15 | export enum PrinterState { 16 | operational, 17 | pausing, 18 | paused, 19 | printing, 20 | cancelling, 21 | closed, 22 | connecting, 23 | reconnecting, 24 | socketDead, 25 | } 26 | -------------------------------------------------------------------------------- /src/model/system.model.ts: -------------------------------------------------------------------------------- 1 | export interface Notification { 2 | heading: string; 3 | text: string; 4 | type: NotificationType; 5 | time?: Date; 6 | choices?: Array; 7 | callback?: (index: number) => void; 8 | sticky?: boolean; 9 | } 10 | 11 | export enum NotificationType { 12 | INFO, 13 | WARN, 14 | ERROR, 15 | PROMPT, 16 | } 17 | 18 | export interface UpdateError { 19 | error: { 20 | message: string; 21 | stack?: string; 22 | }; 23 | } 24 | 25 | export interface UpdateDownloadProgress { 26 | percentage: number; 27 | transferred: number; 28 | total: number | string; 29 | remaining: number; 30 | eta: string; 31 | runtime: string; 32 | delta: number; 33 | speed: number | string; 34 | } 35 | 36 | export interface URLSplit { 37 | host: string; 38 | port: number; 39 | } 40 | -------------------------------------------------------------------------------- /src/model/util-test.model.ts: -------------------------------------------------------------------------------- 1 | export interface TestAddress { 2 | address: string; 3 | is_lan_address: boolean; 4 | subnet: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/pipes/index.ts: -------------------------------------------------------------------------------- 1 | import { URLSafePipe } from './url.pipe'; 2 | 3 | export default [URLSafePipe]; 4 | -------------------------------------------------------------------------------- /src/pipes/url.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; 3 | 4 | @Pipe({ 5 | name: 'url', 6 | standalone: false, 7 | }) 8 | export class URLSafePipe implements PipeTransform { 9 | public constructor(private sanitizer: DomSanitizer) {} 10 | 11 | public transform(url: string): SafeResourceUrl { 12 | return this.sanitizer.bypassSecurityTrustResourceUrl(url); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/reset.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable */ 2 | 3 | /* http://meyerweb.com/eric/tools/css/reset/ 4 | v2.0 | 20110126 5 | License: none (public domain) 6 | */ 7 | 8 | html, 9 | body, 10 | div, 11 | span, 12 | applet, 13 | object, 14 | iframe, 15 | h1, 16 | h2, 17 | h3, 18 | h4, 19 | h5, 20 | h6, 21 | p, 22 | blockquote, 23 | pre, 24 | a, 25 | abbr, 26 | acronym, 27 | address, 28 | big, 29 | cite, 30 | code, 31 | del, 32 | dfn, 33 | em, 34 | img, 35 | ins, 36 | kbd, 37 | q, 38 | s, 39 | samp, 40 | small, 41 | strike, 42 | strong, 43 | sub, 44 | sup, 45 | tt, 46 | var, 47 | b, 48 | u, 49 | i, 50 | center, 51 | dl, 52 | dt, 53 | dd, 54 | ol, 55 | ul, 56 | li, 57 | fieldset, 58 | form, 59 | label, 60 | legend, 61 | table, 62 | caption, 63 | tbody, 64 | tfoot, 65 | thead, 66 | tr, 67 | th, 68 | td, 69 | article, 70 | aside, 71 | canvas, 72 | details, 73 | embed, 74 | figure, 75 | figcaption, 76 | footer, 77 | header, 78 | hgroup, 79 | menu, 80 | nav, 81 | output, 82 | ruby, 83 | section, 84 | summary, 85 | time, 86 | mark, 87 | audio, 88 | video { 89 | margin: 0; 90 | padding: 0; 91 | border: 0; 92 | font: inherit; 93 | vertical-align: baseline; 94 | } 95 | 96 | /* HTML5 display-role reset for older browsers */ 97 | article, 98 | aside, 99 | details, 100 | figcaption, 101 | figure, 102 | footer, 103 | header, 104 | hgroup, 105 | menu, 106 | nav, 107 | section { 108 | display: block; 109 | } 110 | 111 | body { 112 | line-height: 1.2; 113 | } 114 | 115 | ol, 116 | ul { 117 | list-style: none; 118 | } 119 | 120 | blockquote, 121 | q { 122 | quotes: none; 123 | } 124 | 125 | blockquote:before, 126 | blockquote:after, 127 | q:before, 128 | q:after { 129 | content: ''; 130 | content: none; 131 | } 132 | 133 | table { 134 | border-collapse: collapse; 135 | border-spacing: 0; 136 | } 137 | 138 | input[type='number']::-webkit-inner-spin-button, 139 | input[type='number']::-webkit-outer-spin-button { 140 | -webkit-appearance: none; 141 | -moz-appearance: none; 142 | margin: 0; 143 | } 144 | 145 | select { 146 | -moz-appearance: none; 147 | -webkit-appearance: none; 148 | appearance: none; 149 | border: none; 150 | outline: none; 151 | } 152 | 153 | div:focus, 154 | a:focus, 155 | span:focus, 156 | td:focus, 157 | p:focus, 158 | img:focus { 159 | outline: 0; 160 | } 161 | -------------------------------------------------------------------------------- /src/services/conversion.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { ConfigService } from './config.service'; 4 | 5 | @Injectable() 6 | export class ConversionService { 7 | constructor(private configService: ConfigService) {} 8 | 9 | public convertByteToMegabyte(byte: number): string { 10 | return (byte / 1000000).toFixed(1); 11 | } 12 | 13 | public convertDateToString(date: Date): string { 14 | return `${('0' + date.getDate()).slice(-2)}.${('0' + (date.getMonth() + 1)).slice(-2)}.${date.getFullYear()} ${( 15 | '0' + date.getHours() 16 | ).slice(-2)}:${('0' + date.getMinutes()).slice(-2)}:${('0' + date.getSeconds()).slice(-2)}`; 17 | } 18 | 19 | public convertSecondsToHours(input: number): string { 20 | const hours = input / 60 / 60; 21 | let roundedHours = Math.floor(hours); 22 | const minutes = (hours - roundedHours) * 60; 23 | let roundedMinutes = Math.round(minutes); 24 | if (roundedMinutes === 60) { 25 | roundedMinutes = 0; 26 | roundedHours += 1; 27 | } 28 | return roundedHours + ':' + ('0' + roundedMinutes).slice(-2); 29 | } 30 | 31 | public convertFilamentLengthToWeight(filamentLength: number): number { 32 | return this.convertFilamentVolumeToWeight( 33 | (filamentLength * Math.PI * Math.pow(this.configService.getFilamentThickness() / 2, 2)) / 1000, 34 | ); 35 | } 36 | 37 | private convertFilamentVolumeToWeight(filamentVolume: number): number { 38 | return Math.round(filamentVolume * this.configService.getFilamentDensity() * 10) / 10; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/services/electron.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { IpcRenderer } from 'electron'; 3 | 4 | import { NotificationService } from './notification.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class ElectronService { 10 | private ipcRenderer: IpcRenderer | undefined; 11 | 12 | constructor(private notificationService: NotificationService) { 13 | if (window.require) { 14 | this.ipcRenderer = window.require('electron').ipcRenderer; 15 | } else { 16 | this.notificationService.error( 17 | "Can't load electron library", 18 | 'Please restart your system and open a new issue on GitHub if this issue persists.', 19 | true, 20 | ); 21 | } 22 | } 23 | 24 | public on(channel: string, listener: (...args) => void): void { 25 | if (!this.ipcRenderer) { 26 | return; 27 | } 28 | this.ipcRenderer.on(channel, listener); 29 | } 30 | 31 | public removeListener(channel: string, listener: (...args) => void): void { 32 | if (!this.ipcRenderer) { 33 | return; 34 | } 35 | this.ipcRenderer.removeListener(channel, listener); 36 | } 37 | 38 | public send(channel: string, ...args): void { 39 | if (!this.ipcRenderer) { 40 | return; 41 | } 42 | this.ipcRenderer.send(channel, ...args); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/services/enclosure/enclosure.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | 4 | import { PSUState, TemperatureReading } from '../../model'; 5 | 6 | @Injectable() 7 | export abstract class EnclosureService { 8 | abstract getEnclosureTemperature(): Observable; 9 | 10 | abstract setLEDColor(identifier: number, red: number, green: number, blue: number): void; 11 | 12 | abstract setOutput(identifier: number, status: boolean): void; 13 | 14 | abstract setOutputPWM(identifier: number, dutyCycle: number): void; 15 | 16 | abstract runEnclosureShell(identifier: number): void; 17 | 18 | abstract setPSUState(state: PSUState): void; 19 | 20 | abstract togglePSU(): void; 21 | } 22 | -------------------------------------------------------------------------------- /src/services/event.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnDestroy } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { Subscription } from 'rxjs'; 4 | 5 | import { PrinterEvent } from '../model/event.model'; 6 | import { ConfigService } from './config.service'; 7 | import { SocketService } from './socket/socket.service'; 8 | 9 | @Injectable() 10 | export class EventService implements OnDestroy { 11 | private subscriptions: Subscription = new Subscription(); 12 | 13 | private printing = false; 14 | 15 | public constructor( 16 | private socketService: SocketService, 17 | private configService: ConfigService, 18 | private router: Router, 19 | ) { 20 | this.subscriptions.add( 21 | this.socketService.getEventSubscribable().subscribe((event: PrinterEvent) => this.handlePrinterEvent(event)), 22 | ); 23 | } 24 | 25 | private handlePrinterEvent(event: PrinterEvent): void { 26 | if (event === PrinterEvent.PRINTING || event === PrinterEvent.PAUSED) { 27 | setTimeout(() => { 28 | this.printing = true; 29 | }, 500); 30 | } else { 31 | setTimeout(() => { 32 | this.printing = false; 33 | }, 1000); 34 | } 35 | 36 | if (event === PrinterEvent.CLOSED) { 37 | this.router.navigate(['/standby']); 38 | } else if (event === PrinterEvent.CONNECTED) { 39 | setTimeout(() => { 40 | this.router.navigate(['/main-screen']); 41 | }, 500); 42 | } 43 | } 44 | 45 | ngOnDestroy(): void { 46 | this.subscriptions.unsubscribe(); 47 | } 48 | 49 | public isPrinting(): boolean { 50 | return this.printing; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/services/filament/filament-plugin.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | 4 | import { FilamentSpool } from '../../model'; 5 | 6 | @Injectable() 7 | export abstract class FilamentPluginService { 8 | abstract getSpools(): Observable>; 9 | 10 | abstract getCurrentSpools(): Observable; 11 | 12 | abstract getCurrentSpool(tool: number): Observable; 13 | 14 | abstract setSpool(spool: FilamentSpool, tool: number): Observable; 15 | } 16 | -------------------------------------------------------------------------------- /src/services/files/files.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | 4 | import { Directory, File } from '../../model'; 5 | 6 | @Injectable() 7 | export abstract class FilesService { 8 | abstract getFolderContent(folderPath: string): Observable; 9 | 10 | abstract getFile(filePath: string): Observable; 11 | 12 | abstract getThumbnail(filePath: string): Observable; 13 | 14 | abstract loadFile(filePath: string): void; 15 | 16 | abstract printFile(filePath: string): void; 17 | 18 | abstract deleteFile(filePath: string): void; 19 | 20 | abstract setLoadedFile(value: boolean): void; 21 | 22 | abstract getLoadedFile(): boolean; 23 | } 24 | -------------------------------------------------------------------------------- /src/services/job/job.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export abstract class JobService { 5 | abstract startJob(): void; 6 | 7 | abstract pauseJob(): void; 8 | 9 | abstract resumeJob(): void; 10 | 11 | abstract cancelJob(): void; 12 | 13 | abstract restartJob(): void; 14 | 15 | abstract preheat(): void; 16 | } 17 | -------------------------------------------------------------------------------- /src/services/notification.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, Observer } from 'rxjs'; 3 | import { shareReplay } from 'rxjs/operators'; 4 | 5 | import { Notification, NotificationType } from '../model'; 6 | 7 | @Injectable({ 8 | providedIn: 'root', 9 | }) 10 | export class NotificationService { 11 | private observable: Observable; 12 | private observer: Observer; 13 | private bootGrace = false; 14 | 15 | public notificationStack: Array = []; 16 | 17 | public constructor() { 18 | this.observable = new Observable((observer: Observer): void => { 19 | this.observer = observer; 20 | setTimeout((): void => { 21 | this.bootGrace = false; 22 | }, 30000); 23 | }).pipe(shareReplay({ bufferSize: 1, refCount: true })); 24 | } 25 | 26 | public info(heading: string, text: string, sticky?: boolean): void { 27 | this.setNotification({ 28 | heading, 29 | text, 30 | type: NotificationType.INFO, 31 | sticky, 32 | }); 33 | } 34 | 35 | public warn(heading: string, text: string, sticky?: boolean): void { 36 | this.setNotification({ 37 | heading, 38 | text, 39 | type: NotificationType.WARN, 40 | sticky, 41 | }); 42 | } 43 | 44 | public error(heading: string, text: string, sticky?: boolean): void { 45 | this.setNotification({ 46 | heading, 47 | text, 48 | type: NotificationType.ERROR, 49 | sticky, 50 | }); 51 | } 52 | 53 | public prompt(heading: string, text: string, choices: Array, callback: (index: number) => void): void { 54 | this.setNotification({ 55 | heading, 56 | text, 57 | type: NotificationType.PROMPT, 58 | choices, 59 | callback, 60 | sticky: true, 61 | }); 62 | } 63 | 64 | private setNotification(notification: Notification): void { 65 | if (!notification.time) { 66 | notification.time = new Date(); 67 | } 68 | if (this.observer) { 69 | this.observer.next(notification); 70 | this.notificationStack.push(notification); 71 | 72 | if (this.notificationStack.length > 25) { 73 | this.notificationStack.shift(); 74 | } 75 | } else { 76 | setTimeout(this.setNotification.bind(this), 1000, notification); 77 | } 78 | } 79 | 80 | public removeNotification(notification: Notification) { 81 | this.notificationStack = this.notificationStack.filter(n => n.time !== notification.time); 82 | } 83 | 84 | public closeNotification(): void { 85 | this.observer.next('close'); 86 | } 87 | 88 | public getObservable(): Observable { 89 | return this.observable; 90 | } 91 | 92 | public getBootGrace(): boolean { 93 | return this.bootGrace; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/services/printer/printer.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | 4 | import { PrinterProfile } from '../../model'; 5 | 6 | @Injectable() 7 | export abstract class PrinterService { 8 | abstract getActiveProfile(): Observable; 9 | 10 | abstract saveToEPROM(): void; 11 | 12 | abstract executeGCode(gCode: string): void; 13 | 14 | abstract jog(x: number, y: number, z: number): void; 15 | 16 | abstract extrude(amount: number, speed: number, tool?: number): void; 17 | 18 | abstract setTool(tool: number): void; 19 | 20 | abstract setTemperatureHotend(temperature: number, tool?: number): void; 21 | 22 | abstract setTemperatureBed(temperature: number): void; 23 | 24 | abstract setFanSpeed(percentage: number): void; 25 | 26 | abstract setFeedrate(feedrate: number): void; 27 | 28 | abstract setFlowrate(flowrate: number): void; 29 | 30 | abstract disconnectPrinter(): void; 31 | 32 | abstract emergencyStop(): void; 33 | } 34 | -------------------------------------------------------------------------------- /src/services/socket/socket.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | 4 | import { JobStatus, PrinterEvent, PrinterNotification, PrinterStatus } from '../../model'; 5 | 6 | @Injectable() 7 | export abstract class SocketService { 8 | abstract connect(): Promise; 9 | 10 | abstract getPrinterStatusSubscribable(): Observable; 11 | 12 | abstract getPrinterStatusText(): Observable; 13 | 14 | abstract getJobStatusSubscribable(): Observable; 15 | 16 | abstract getEventSubscribable(): Observable; 17 | } 18 | -------------------------------------------------------------------------------- /src/services/system/system.octoprint.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable, of } from 'rxjs'; 4 | import { catchError, map } from 'rxjs/operators'; 5 | 6 | import { SocketAuth, TestAddress } from '../../model'; 7 | import { ConnectCommand, OctoprintLogin } from '../../model/octoprint'; 8 | import { ConfigService } from '../../services/config.service'; 9 | import { NotificationService } from '../../services/notification.service'; 10 | import { SystemService } from './system.service'; 11 | 12 | @Injectable() 13 | export class SystemOctoprintService implements SystemService { 14 | constructor( 15 | private configService: ConfigService, 16 | private notificationService: NotificationService, 17 | private http: HttpClient, 18 | ) {} 19 | 20 | public getSessionKey(): Observable { 21 | return this.http 22 | .post( 23 | this.configService.getApiURL('login'), 24 | { passive: true }, 25 | this.configService.getHTTPHeaders(), 26 | ) 27 | .pipe( 28 | map(octoprintLogin => { 29 | return { 30 | user: octoprintLogin.name, 31 | session: octoprintLogin.session, 32 | } as SocketAuth; 33 | }), 34 | ); 35 | } 36 | 37 | public sendCommand(command: string): void { 38 | this.http 39 | .post(this.configService.getApiURL(`system/commands/core/${command}`), null, this.configService.getHTTPHeaders()) 40 | .pipe( 41 | catchError(error => { 42 | this.notificationService.error($localize`:@@error-execute:Can't execute ${command} command!`, error.message); 43 | return of(error); 44 | }), 45 | ) 46 | .subscribe(); 47 | } 48 | 49 | public connectPrinter(): void { 50 | const payload: ConnectCommand = { 51 | command: 'connect', 52 | save: false, 53 | }; 54 | 55 | this.http 56 | .post(this.configService.getApiURL('connection'), payload, this.configService.getHTTPHeaders()) 57 | .pipe( 58 | catchError(error => { 59 | this.notificationService.warn($localize`:@@error-connect:Can't connect to printer!`, error.message, true); 60 | return of(error); 61 | }), 62 | ) 63 | .subscribe(); 64 | } 65 | 66 | public getLocalIpAddress() { 67 | const payload = { 68 | command: 'address', 69 | }; 70 | 71 | return this.http 72 | .post(this.configService.getApiURL('util/test'), payload, this.configService.getHTTPHeaders()) 73 | .pipe(map(result => (result?.is_lan_address && result?.address ? result.address : null))); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/services/system/system.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | 4 | import { SocketAuth } from '../../model'; 5 | 6 | @Injectable() 7 | export abstract class SystemService { 8 | abstract getLocalIpAddress(): Observable; 9 | 10 | abstract getSessionKey(): Observable; 11 | 12 | abstract sendCommand(command: string): void; 13 | 14 | abstract connectPrinter(): void; 15 | } 16 | -------------------------------------------------------------------------------- /themes/NOX/README.md: -------------------------------------------------------------------------------- 1 | # NOX theme V0.2.2 2 | 3 | I suggest setting the thumbnail resolution in PrusaSlicer a little bit higher if you want a sharper image: 4 | 5 | ``` 6 | thumbnails = 16x16,330x186 7 | ``` 8 | 9 | You also need to activate this setting in OctoDash: 10 | 11 | **Settings > OctoDash > Preview by default while printing** 12 | 13 | If you want to use the round progress bar: 14 | 15 | **Settings > OctoDash > Always use circular progress bar** 16 | 17 | ### Screenshots: 18 | 19 | 1a. Printing Screen (progress bar): 20 | 21 | ![Printing screen 1](screenshots/screenshot_printing1.png) 22 | 23 | 1b. Printing Screen (round progress bar): 24 | 25 | ![Printing screen 2](screenshots/screenshot_printing2.png) 26 | 27 | 2. Main Screen: 28 | 29 | ![Main screen](screenshots/screenshot_main-screen.png) 30 | 31 | 3. File List: 32 | 33 | ![File list](screenshots/screenshot_files.png) 34 | 35 | 4. File Details: 36 | 37 | ![File details](screenshots/screenshot_file.png) 38 | 39 | 5. File loaded: 40 | 41 | ![File loaded](screenshots/screenshot_file-loaded.png) 42 | -------------------------------------------------------------------------------- /themes/NOX/screenshots/screenshot_file-loaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/NOX/screenshots/screenshot_file-loaded.png -------------------------------------------------------------------------------- /themes/NOX/screenshots/screenshot_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/NOX/screenshots/screenshot_file.png -------------------------------------------------------------------------------- /themes/NOX/screenshots/screenshot_files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/NOX/screenshots/screenshot_files.png -------------------------------------------------------------------------------- /themes/NOX/screenshots/screenshot_main-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/NOX/screenshots/screenshot_main-screen.png -------------------------------------------------------------------------------- /themes/NOX/screenshots/screenshot_printing1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/NOX/screenshots/screenshot_printing1.png -------------------------------------------------------------------------------- /themes/NOX/screenshots/screenshot_printing2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/NOX/screenshots/screenshot_printing2.png -------------------------------------------------------------------------------- /themes/dark-nights/README.md: -------------------------------------------------------------------------------- 1 | # Dark Nights 2 | 3 | A dark theme with customizeable accent color which matches my octoprint theme. 4 | 5 | ## Customize 6 | 7 | To customize the accent color edit the hex-codes at the beginning of the `custom-styles.css`. 8 | 9 | ```css 10 | :root { 11 | /* If a other accent color is more to your personal liking, change it here. ;) */ 12 | --accent: #d32f2f; 13 | --accent-transparent: #d32f2f24; 14 | ... 15 | } 16 | ``` 17 | 18 | Make sure to change also the transparent accent color. 19 | 20 | ## Screenshots 21 | 22 | ### Dashboard 23 | 24 | ![Dashboard](screenshots/dashboard.png) 25 | 26 | ![Dashboard](screenshots/temp-adjust.png) 27 | 28 | ### Files 29 | 30 | ![Dashboard](screenshots/file-browser.png) 31 | 32 | ![Dashboard](screenshots/file-selection.png) 33 | 34 | ![Dashboard](screenshots/file-preview.png) 35 | 36 | ### Filament change 37 | 38 | ![Dashboard](screenshots/filament-temp.png) 39 | 40 | ![Dashboard](screenshots/filament-purge.png) 41 | 42 | ### Printing 43 | 44 | ![Dashboard](screenshots/printing-percent.png) 45 | 46 | ![Dashboard](screenshots/printing-preview.png) 47 | 48 | ![Dashboard](screenshots/printing-adjust.png) 49 | -------------------------------------------------------------------------------- /themes/dark-nights/screenshots/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/dark-nights/screenshots/dashboard.png -------------------------------------------------------------------------------- /themes/dark-nights/screenshots/filament-purge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/dark-nights/screenshots/filament-purge.png -------------------------------------------------------------------------------- /themes/dark-nights/screenshots/filament-temp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/dark-nights/screenshots/filament-temp.png -------------------------------------------------------------------------------- /themes/dark-nights/screenshots/file-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/dark-nights/screenshots/file-browser.png -------------------------------------------------------------------------------- /themes/dark-nights/screenshots/file-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/dark-nights/screenshots/file-preview.png -------------------------------------------------------------------------------- /themes/dark-nights/screenshots/file-selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/dark-nights/screenshots/file-selection.png -------------------------------------------------------------------------------- /themes/dark-nights/screenshots/printing-adjust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/dark-nights/screenshots/printing-adjust.png -------------------------------------------------------------------------------- /themes/dark-nights/screenshots/printing-percent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/dark-nights/screenshots/printing-percent.png -------------------------------------------------------------------------------- /themes/dark-nights/screenshots/printing-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/dark-nights/screenshots/printing-preview.png -------------------------------------------------------------------------------- /themes/dark-nights/screenshots/temp-adjust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/dark-nights/screenshots/temp-adjust.png -------------------------------------------------------------------------------- /themes/square/README.md: -------------------------------------------------------------------------------- 1 | # sqaure theme V1.0 2 | 3 | Theme for displays with square aspect ratio, like the HyperPixel Square. Courtesy of Kevin from the Discord server. 4 | 5 | ### Screenshots: 6 | 7 | will be added later 8 | -------------------------------------------------------------------------------- /themes/theGarbz/BigFingers/README.md: -------------------------------------------------------------------------------- 1 | # BigFingers theme v0.3 2 | 3 | This variation on the standard theme increases the width of the scroll bars to make them easier to touch on small displays. 4 | No special requirements exist for this theme, however it is intended for small low resolution displays. 5 | 6 | To install copy the custom-styles.css file into the octodash config folder: 7 | 8 | ``` 9 | ~/.config/octodash/custom-styles.css 10 | ``` 11 | 12 | ###### Theme by theGarbz. 13 | 14 | ### Screenshots: 15 | 16 | 1. File List: 17 | 18 | ![File List](screenshots/screenshot_files.png) 19 | 20 | 2. Filament List: 21 | 22 | ![Filament List](screenshots/screenshot_filaments.png) 23 | 24 | 3. Settings: 25 | 26 | ![Settings](screenshots/screenshot_settings.png) 27 | 28 | ### Version History: 29 | 30 | **v0.3:** 31 | 32 | - Extra text removed from settings menu which ran off the screen. 33 | 34 | **v0.2:** 35 | 36 | - Hitbox of settings button increased. 37 | 38 | **v0.1:** 39 | 40 | - Initial Issue 41 | - Larger vertical scrollbar width. 42 | -------------------------------------------------------------------------------- /themes/theGarbz/BigFingers/custom-styles.css: -------------------------------------------------------------------------------- 1 | /**** Override scrollbars ****/ 2 | 3 | ::-webkit-scrollbar { 4 | width: 6vw !important; 5 | } 6 | 7 | /**** Resize file list ****/ 8 | 9 | .files { 10 | height: 73vh !important; 11 | margin-top: 2vh !important; 12 | } 13 | 14 | .files__object { 15 | width: 86.5vw !important; 16 | height: 13vh !important; 17 | line-height: 12vh !important; 18 | } 19 | 20 | .files__info { 21 | height: 12vh !important; 22 | padding: 0.8vw 0 !important; 23 | } 24 | 25 | .files__icon { 26 | margin: 2.4vh 1vw 0 !important; 27 | } 28 | 29 | .files__name { 30 | height: 13vh; 31 | line-height: 13vh; 32 | } 33 | 34 | /**** Resize filament list ****/ 35 | 36 | .filament tr { 37 | width: 86vw !important; 38 | height: 13vh !important; 39 | line-height: 13vh !important; 40 | } 41 | 42 | .filament-filaments { 43 | line-height: 13vh !important; 44 | margin-top: 4vh !important; 45 | } 46 | 47 | .filament-filaments td { 48 | line-height: 12vh !important; 49 | } 50 | 51 | .filament-filaments__name { 52 | width: 58vw !important; 53 | } 54 | 55 | /**** Make settings button easier to hit ****/ 56 | 57 | .main-menu__settings-icon { 58 | padding: 5vh 5vw 4vh !important; 59 | } 60 | 61 | /**** Update Settings ****/ 62 | 63 | .settings-container { 64 | height: 86vh !important; 65 | top: 8vh !important; 66 | width: 66vw !important; 67 | left: 17vw !important; 68 | } 69 | 70 | .settings__content { 71 | width: 59vw !important; 72 | } 73 | -------------------------------------------------------------------------------- /themes/theGarbz/BigFingers/screenshots/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/BigFingers/screenshots/icon.png -------------------------------------------------------------------------------- /themes/theGarbz/BigFingers/screenshots/screenshot_filaments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/BigFingers/screenshots/screenshot_filaments.png -------------------------------------------------------------------------------- /themes/theGarbz/BigFingers/screenshots/screenshot_files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/BigFingers/screenshots/screenshot_files.png -------------------------------------------------------------------------------- /themes/theGarbz/BigFingers/screenshots/screenshot_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/BigFingers/screenshots/screenshot_settings.png -------------------------------------------------------------------------------- /themes/theGarbz/Focus/README.md: -------------------------------------------------------------------------------- 1 | # Focus theme v0.4 2 | 3 | This theme is based on the classy [NOX theme](../../NOX/) by NoxHirsch and has been modified to Focus on highlighting the most important information for a user. The goal was to create a theme that could easily be read from a distance. Some of the features are listed below: 4 | 5 | - High contrast display of relevant information (e.g. print time, temperature) 6 | - Low contrast display of all other info (e.g. units, time elapsed) 7 | - Large horizontal and vertical progress bars readable from a distance. 8 | - Large print time remaining indicators 9 | - Pre-printing screen highlighting buttons. 10 | - Full screen menus and adjustments with coloured backgrounds for context. 11 | - Red highlighting of error messages 12 | 13 | To install copy the custom-styles.css file into the octodash config folder: 14 | 15 | ``` 16 | ~/.config/octodash/custom-styles.css 17 | ``` 18 | 19 | Please note: This theme like the theme it was based on makes use of more CSS effects than the default theme and does not perform as smoothly on under powered hardware such as older Raspberry Pis. If you have a problem with Octodash performance please try using the default theme before reporting any issues. This theme performs well on a Raspbery Pi4 with a 7" LCD. 20 | 21 | ###### Theme by theGarbz. 22 | 23 | ## Screenshots: 24 | 25 | 1. Printing with the Horizontal Progressbar: 26 | 27 | ![Horizontal Progressbar](screenshots/screenshot_print.png) 28 | 29 | 2. Printing with Circular Progressbar: 30 | 31 | ![Round Progressbar](screenshots/screenshot_print2.png) 32 | 33 | 3. File Selection: 34 | 35 | ![File Selection](screenshots/screenshot_fileselect.png) 36 | 37 | 4. Pre-print screen: 38 | 39 | ![Pre-print screen](screenshots/screenshot_filequeue.png) 40 | 41 | 5. Adjustments mid print: 42 | 43 | ![Adjustments](screenshots/screenshot_adjust.png) 44 | 45 | 6. Errors: 46 | 47 | ![Errors](screenshots/screenshot_error.png) 48 | 49 | 7. Main Screen: 50 | 51 | ![Main Screen](screenshots/screenshot_main.png) 52 | 53 | 8. Settings: 54 | 55 | ![Settings](screenshots/screenshot_menu.png) 56 | 57 | ### Version History: 58 | 59 | **v0.4:** 60 | 61 | - Fix for Layer display code - Reenables the icon in place of text. 62 | - Fixed settings items running off the edge of the screen. 63 | - Fxied settings scroll bars so they no longer run off the bottom of the screen. 64 | 65 | **v0.3:** 66 | 67 | - Temporary change in Layer display code to suit new Layer-Progress Component. 68 | 69 | **v0.2:** 70 | 71 | - Fixed bug which prevented the filament weight from being shown in the filament selector. 72 | 73 | **v0.1:** 74 | 75 | - Initial Issue 76 | -------------------------------------------------------------------------------- /themes/theGarbz/Focus/screenshots/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/Focus/screenshots/icon.png -------------------------------------------------------------------------------- /themes/theGarbz/Focus/screenshots/screenshot_adjust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/Focus/screenshots/screenshot_adjust.png -------------------------------------------------------------------------------- /themes/theGarbz/Focus/screenshots/screenshot_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/Focus/screenshots/screenshot_error.png -------------------------------------------------------------------------------- /themes/theGarbz/Focus/screenshots/screenshot_filequeue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/Focus/screenshots/screenshot_filequeue.png -------------------------------------------------------------------------------- /themes/theGarbz/Focus/screenshots/screenshot_fileselect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/Focus/screenshots/screenshot_fileselect.png -------------------------------------------------------------------------------- /themes/theGarbz/Focus/screenshots/screenshot_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/Focus/screenshots/screenshot_main.png -------------------------------------------------------------------------------- /themes/theGarbz/Focus/screenshots/screenshot_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/Focus/screenshots/screenshot_menu.png -------------------------------------------------------------------------------- /themes/theGarbz/Focus/screenshots/screenshot_print.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/Focus/screenshots/screenshot_print.png -------------------------------------------------------------------------------- /themes/theGarbz/Focus/screenshots/screenshot_print2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/Focus/screenshots/screenshot_print2.png -------------------------------------------------------------------------------- /themes/theGarbz/Glanceable/README.md: -------------------------------------------------------------------------------- 1 | # Glanceable theme v0.2 2 | 3 | This variation on the standard theme includes a nearly full width horizontal progress bar during printing. This makes it far easier to see the progress of a print when glancing at the screen from a distance, hence the name "Glanceable theme". 4 | 5 | To install copy the custom-styles.css file into the octodash config folder: 6 | 7 | ``` 8 | ~/.config/octodash/custom-styles.css 9 | ``` 10 | 11 | ###### Theme by theGarbz. 12 | 13 | ## Screenshots: 14 | 15 | 1. Printing with the Horizontal Progressbar: 16 | 17 | ![Filament List](screenshots/screenshot_printing_straight.png) 18 | 19 | 2. Printing with Circular Progressbar: 20 | 21 | ![File List](screenshots/screenshot_printing_circle.png) 22 | 23 | ### Version History: 24 | 25 | **v0.2:** 26 | 27 | - Theme updated to suit new layer progress layout in Octodash 2.2.0 28 | 29 | **v0.1:** 30 | 31 | - Initial Issue 32 | -------------------------------------------------------------------------------- /themes/theGarbz/Glanceable/custom-styles.css: -------------------------------------------------------------------------------- 1 | /**** Job-Info Resizes ****/ 2 | 3 | app-job-status ~ app-printer-status .printer-status { 4 | position: absolute !important; 5 | top: 42vh !important; 6 | left: 0 !important; 7 | } 8 | 9 | app-job-status ~ app-printer-status .printer-status__set-value { 10 | margin-top: -2vh !important; 11 | font-size: 3.9vw !important; 12 | } 13 | 14 | /**** Job-Info Layer Progress ****/ 15 | 16 | .height-indication { 17 | position: absolute !important; 18 | top: 66vh !important; 19 | left: 0 !important; 20 | font-size: 3vw !important; 21 | } 22 | 23 | .height-indication__current-height { 24 | font-size: 6vh !important; 25 | } 26 | 27 | .height-indication__total-height { 28 | font-size: 5vh !important; 29 | } 30 | 31 | /**** Job-Info Progress ****/ 32 | 33 | .job-info__progress-bar { 34 | height: 8vh !important; 35 | border-radius: 1.7vw !important; 36 | } 37 | 38 | .job-info__progress-bar__wrapper { 39 | position: fixed !important; 40 | top: 77vh !important; 41 | left: 3vw !important; 42 | width: 92vw !important; 43 | height: 8vh !important; 44 | border-radius: 2vw !important; 45 | background-color: rgb(38 44 53 / 100%); 46 | border-style: solid !important; 47 | border-color: rgb(121 121 121); 48 | border-width: 2px !important; 49 | } 50 | 51 | #progress-preview-bar + .job-info__progress-percentage { 52 | margin-top: -0.3vh !important; 53 | margin-left: 0 !important; 54 | font-size: 6.7vh !important; 55 | font-weight: 500 !important; 56 | display: block !important; 57 | position: fixed !important; 58 | left: 43.4vw !important; 59 | top: 77vh !important; 60 | z-index: 1 !important; 61 | color: white !important; 62 | text-shadow: 1px 1px 4px black; 63 | visibility: visible !important; 64 | } 65 | -------------------------------------------------------------------------------- /themes/theGarbz/Glanceable/screenshots/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/Glanceable/screenshots/icon.png -------------------------------------------------------------------------------- /themes/theGarbz/Glanceable/screenshots/screenshot_printing_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/Glanceable/screenshots/screenshot_printing_circle.png -------------------------------------------------------------------------------- /themes/theGarbz/Glanceable/screenshots/screenshot_printing_straight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnchartedBull/OctoDash/220ba0904dbbdef6973210a51dd56917006b1b81/themes/theGarbz/Glanceable/screenshots/screenshot_printing_straight.png -------------------------------------------------------------------------------- /themes/theGarbz/README.md: -------------------------------------------------------------------------------- 1 | # A collection of themes by Garbz 2 | 3 | ### [Focus](Focus/) 4 | 5 | This theme focuses on important information for the user. A variation on the NOX theme, it reduces the contrast of non-important elements and incorporates large very easy to see progress bars giving the user quick access to the most important of information. 6 | 7 | ![Icon Focus Theme](Focus/screenshots/icon.png) 8 | 9 | ### [Big-Fingers](BigFingers/) 10 | 11 | A variation on the default theme with wider vertical scrollbars. This is optimised for smaller screens and makes navigating files and filaments easier. 12 | 13 | ![Icon BigFingers Theme](BigFingers/screenshots/icon.png) 14 | 15 | ### [Glanceable](Glanceable/) 16 | 17 | A variation on the default theme which introduces the option of a horizontal progress bar during printing. This alternate view allows for easier view of print progress from a distance while retaining the look and feel of the rest of the default OctoDash theme. 18 | 19 | ![Icon Glanceable Theme](Glanceable/screenshots/icon.png) 20 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": ["@angular/localize"] 6 | }, 7 | "files": ["src/main.ts"], 8 | "include": ["src/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "ESNext", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "ES2022", 14 | "typeRoots": ["node_modules/@types"], 15 | "types": [], 16 | "lib": ["ES2022", "dom"], 17 | "esModuleInterop": true, 18 | "resolveJsonModule": true 19 | }, 20 | "exclude": ["**/node_modules/**"] 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": ["jasmine", "node", "@angular/localize"] 6 | }, 7 | "files": ["src/test.ts"], 8 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 9 | } 10 | --------------------------------------------------------------------------------