├── .node-version ├── docs ├── CNAME ├── .gitignore ├── favicon.ico ├── README.md ├── assets │ └── images │ │ ├── graph.png │ │ ├── theme.png │ │ ├── console.png │ │ ├── layout.png │ │ ├── macros1.png │ │ ├── macros2.png │ │ ├── presets.png │ │ ├── preview.png │ │ ├── reprint.png │ │ ├── updates.png │ │ ├── bed_mesh.png │ │ ├── ogfluidd.png │ │ ├── thumbnails.png │ │ ├── auth_trusted.png │ │ ├── localization.png │ │ ├── print_stats.png │ │ ├── camera_settings.png │ │ ├── fluidd_social.png │ │ ├── notifications.png │ │ ├── preview_sliced.png │ │ ├── print_history.png │ │ ├── slicer-upload.png │ │ ├── automated-updates.png │ │ ├── physical-printer.png │ │ └── printer-selection.png ├── _sass │ └── color_schemes │ │ └── fluidd.scss ├── installation │ ├── manual.md │ ├── index.md │ ├── docker.md │ └── kiauh.md ├── customize │ ├── index.md │ ├── themes.md │ ├── layout.md │ └── hide_outputs.md ├── updates │ ├── index.md │ ├── automated.md │ └── manual-updates.md ├── features │ ├── index.md │ ├── presets.md │ ├── updates.md │ ├── med_mesh.md │ ├── localization.md │ ├── notifications.md │ ├── multiple_printers.md │ ├── chart.md │ ├── macros.md │ ├── sensors.md │ ├── console.md │ ├── print_history.md │ ├── slicer-uploads.md │ └── cameras.md ├── development │ ├── index.md │ └── localization.md ├── configuration │ ├── index.md │ ├── moonraker_conf.md │ └── fluidd.xyz.md ├── 404.html ├── Gemfile ├── authorization │ └── index.md └── _config.yml ├── .env.development.local.example ├── src ├── components │ ├── ui │ │ ├── AppTable.vue │ │ ├── AppCardDrag.vue │ │ ├── AppBtnGroup.vue │ │ ├── AppSwitch.vue │ │ ├── AppBtnCollapse.vue │ │ ├── AppInlineHelp.vue │ │ ├── AppBtn.vue │ │ ├── AppBtnCollapseGroup.vue │ │ ├── AppBtnToolheadMove.vue │ │ └── AppIcon.vue │ ├── layout │ │ ├── AppFooter.vue │ │ └── AppToolsDrawer.vue │ ├── widgets │ │ ├── macros │ │ │ └── MacrosCard.vue │ │ ├── retract │ │ │ └── RetractCard.vue │ │ ├── outputs │ │ │ ├── OutputsCard.vue │ │ │ ├── OutputItem.vue │ │ │ └── OutputPin.vue │ │ ├── limits │ │ │ └── PrinterLimitsCard.vue │ │ ├── jobs │ │ │ └── JobsCard.vue │ │ ├── gcode-preview │ │ │ └── GcodePreviewControlCheckbox.vue │ │ ├── toolhead │ │ │ └── ExtruderSelection.vue │ │ ├── camera │ │ │ └── CameraDialog.vue │ │ ├── status │ │ │ └── StatusLabel.vue │ │ ├── filesystem │ │ │ ├── FileSystemDragOverlay.vue │ │ │ ├── FileRowItem.vue │ │ │ └── FileSystemBulkActions.vue │ │ ├── system │ │ │ ├── MoonrakerLoadChart.vue │ │ │ ├── SystemMemoryChart.vue │ │ │ ├── KlipperLoadChart.vue │ │ │ └── SystemLoadChart.vue │ │ ├── runout-sensors │ │ │ └── RunoutSensorsCard.vue │ │ ├── history │ │ │ └── PrintHistoryCard.vue │ │ ├── thermals │ │ │ └── TemperaturePresetsMenu.vue │ │ └── endstops │ │ │ └── EndStopsCard.vue │ ├── _globals.ts │ ├── common │ │ ├── KlippyStatusCard.vue │ │ ├── SystemLayout.vue │ │ └── FlashMessage.vue │ └── settings │ │ └── auth │ │ └── ApiKeyDialog.vue ├── echarts-for-vue.d.ts ├── vue-inline-svg.d.ts ├── vue-virtual-scroller.d.ts ├── store │ ├── wait │ │ ├── types.ts │ │ ├── index.ts │ │ ├── actions.ts │ │ ├── mutations.ts │ │ └── getters.ts │ ├── power │ │ ├── getters.ts │ │ ├── types.ts │ │ ├── index.ts │ │ ├── mutations.ts │ │ └── actions.ts │ ├── socket │ │ ├── types.ts │ │ ├── getters.ts │ │ ├── mutations.ts │ │ └── index.ts │ ├── auth │ │ ├── types.ts │ │ ├── helpers.ts │ │ ├── getters.ts │ │ ├── index.ts │ │ └── mutations.ts │ ├── cameras │ │ ├── types.ts │ │ ├── index.ts │ │ ├── actions.ts │ │ ├── mutations.ts │ │ └── getters.ts │ ├── charts │ │ ├── types.ts │ │ ├── index.ts │ │ └── mutations.ts │ ├── macros │ │ ├── types.ts │ │ └── index.ts │ ├── notifications │ │ ├── getters.ts │ │ ├── types.ts │ │ ├── mutations.ts │ │ └── index.ts │ ├── layout │ │ ├── types.ts │ │ ├── actions.ts │ │ └── index.ts │ ├── mesh │ │ ├── mutations.ts │ │ ├── index.ts │ │ ├── actions.ts │ │ └── types.ts │ ├── files │ │ └── index.ts │ ├── console │ │ ├── index.ts │ │ ├── types.ts │ │ └── getters.ts │ ├── history │ │ ├── index.ts │ │ ├── types.ts │ │ ├── mutations.ts │ │ └── actions.ts │ ├── version │ │ ├── index.ts │ │ ├── mutations.ts │ │ └── types.ts │ ├── gcodePreview │ │ ├── index.ts │ │ ├── mutations.ts │ │ └── types.ts │ ├── server │ │ ├── index.ts │ │ └── mutations.ts │ └── types.ts ├── assets │ ├── logo.png │ └── example_thumb.gif ├── vue-headful.d.ts ├── shims-vue.d.ts ├── echarts-gl.d.ts ├── types │ ├── vuetify.ts │ ├── flashmessage.ts │ ├── index.ts │ ├── mesh.ts │ ├── dialogs.ts │ └── tableheaders.ts ├── consola.ts ├── directives │ └── blur.ts ├── scss │ ├── buttons.scss │ ├── chips.scss │ ├── dialogs.scss │ ├── helpers.scss │ ├── typeography.scss │ ├── global.scss │ ├── tables.scss │ ├── lists.scss │ ├── inputs.scss │ ├── animation.scss │ ├── cards.scss │ ├── misc.scss │ └── variables.scss ├── registerComponentHooks.ts ├── shims-tsx.d.ts ├── views │ ├── NotFound.vue │ ├── Jobs.vue │ ├── Icons.vue │ ├── History.vue │ ├── Tune.vue │ └── System.vue ├── workers │ └── parseGcode.worker.ts ├── plugins │ ├── workbox.ts │ └── vuetify.ts ├── util │ ├── get-all-layouts.ts │ ├── merge-file-update.ts │ ├── get-browser-locale.ts │ ├── get-file-paths.ts │ ├── get-klipper-type.ts │ ├── format-as-file.ts │ └── transform-mesh.ts ├── eventBus.ts ├── monaco │ └── README.md ├── mixins │ └── toolhead.ts ├── registerServiceWorker.ts └── api │ └── auth.api.ts ├── .eslintignore ├── public ├── robots.txt ├── favicon.ico ├── img │ └── icons │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── msapplication-icon-144x144.png │ │ ├── android-chrome-maskable-192x192.png │ │ └── android-chrome-maskable-512x512.png ├── logo_voron.svg ├── index.html ├── logo_eva.svg ├── logo_zerog.svg ├── color_picker.svg └── logo_ratrig.svg ├── .browserslistrc ├── .env ├── cypress.json ├── types.d.ts ├── .github ├── images │ ├── preview.png │ └── preview_sliced.png ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── bug_report.yml │ └── feature_request.yml ├── FUNDING.yml └── workflows │ └── build.yml ├── babel.config.js ├── .editorconfig ├── tests └── unit │ ├── utils.ts │ └── setup.ts ├── vue-i18n-extract.config.js ├── .gitignore ├── Dockerfile ├── jest.config.js ├── CONTRIBUTING.md ├── .eslintrc.js ├── tsconfig.json ├── server └── nginx-site.conf ├── README.md └── developer-certificate-of-origin /.node-version: -------------------------------------------------------------------------------- 1 | 16.13.2 2 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | docs.fluidd.xyz -------------------------------------------------------------------------------- /.env.development.local.example: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ui/AppTable.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/workers/*.worker.ts 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | VUE_APP_I18N_LOCALE=en 2 | VUE_APP_I18N_FALLBACK_LOCALE=en -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .jekyll-metadata 4 | -------------------------------------------------------------------------------- /src/echarts-for-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'echarts-for-vue'; 2 | -------------------------------------------------------------------------------- /src/vue-inline-svg.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue-inline-svg' 2 | -------------------------------------------------------------------------------- /src/vue-virtual-scroller.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue-virtual-scroller' 2 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/favicon.ico -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/favicon.ico -------------------------------------------------------------------------------- /src/store/wait/types.ts: -------------------------------------------------------------------------------- 1 | export interface WaitState { 2 | waits: string[]; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/src/assets/logo.png -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | https://socialify.git.ci/cadriel/fluidd 4 | Font = Raleway 5 | -------------------------------------------------------------------------------- /src/vue-headful.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue-headful' { 2 | export const vueHeadful: any 3 | } 4 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.yaml' { 2 | const data: any 3 | export default data 4 | } 5 | -------------------------------------------------------------------------------- /.github/images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/.github/images/preview.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /docs/assets/images/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/graph.png -------------------------------------------------------------------------------- /docs/assets/images/theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/theme.png -------------------------------------------------------------------------------- /public/img/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/favicon.ico -------------------------------------------------------------------------------- /src/assets/example_thumb.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/src/assets/example_thumb.gif -------------------------------------------------------------------------------- /docs/assets/images/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/console.png -------------------------------------------------------------------------------- /docs/assets/images/layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/layout.png -------------------------------------------------------------------------------- /docs/assets/images/macros1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/macros1.png -------------------------------------------------------------------------------- /docs/assets/images/macros2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/macros2.png -------------------------------------------------------------------------------- /docs/assets/images/presets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/presets.png -------------------------------------------------------------------------------- /docs/assets/images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/preview.png -------------------------------------------------------------------------------- /docs/assets/images/reprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/reprint.png -------------------------------------------------------------------------------- /docs/assets/images/updates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/updates.png -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | 4 | export default Vue 5 | } 6 | -------------------------------------------------------------------------------- /.github/images/preview_sliced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/.github/images/preview_sliced.png -------------------------------------------------------------------------------- /docs/assets/images/bed_mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/bed_mesh.png -------------------------------------------------------------------------------- /docs/assets/images/ogfluidd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/ogfluidd.png -------------------------------------------------------------------------------- /docs/assets/images/thumbnails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/thumbnails.png -------------------------------------------------------------------------------- /docs/assets/images/auth_trusted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/auth_trusted.png -------------------------------------------------------------------------------- /docs/assets/images/localization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/localization.png -------------------------------------------------------------------------------- /docs/assets/images/print_stats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/print_stats.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /docs/assets/images/camera_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/camera_settings.png -------------------------------------------------------------------------------- /docs/assets/images/fluidd_social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/fluidd_social.png -------------------------------------------------------------------------------- /docs/assets/images/notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/notifications.png -------------------------------------------------------------------------------- /docs/assets/images/preview_sliced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/preview_sliced.png -------------------------------------------------------------------------------- /docs/assets/images/print_history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/print_history.png -------------------------------------------------------------------------------- /docs/assets/images/slicer-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/slicer-upload.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/assets/images/automated-updates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/automated-updates.png -------------------------------------------------------------------------------- /docs/assets/images/physical-printer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/physical-printer.png -------------------------------------------------------------------------------- /docs/assets/images/printer-selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/docs/assets/images/printer-selection.png -------------------------------------------------------------------------------- /src/echarts-gl.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'echarts-gl' 2 | declare module 'echarts-gl/components' 3 | declare module 'echarts-gl/charts' 4 | -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-maskable-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/android-chrome-maskable-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-maskable-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/fluidd/develop/public/img/icons/android-chrome-maskable-512x512.png -------------------------------------------------------------------------------- /src/types/vuetify.ts: -------------------------------------------------------------------------------- 1 | export type VForm = Vue & { 2 | validate: () => boolean; 3 | reset: () => boolean; 4 | resetValidation: () => boolean; 5 | } 6 | -------------------------------------------------------------------------------- /src/consola.ts: -------------------------------------------------------------------------------- 1 | import Consola from 'consola' 2 | 3 | // Configure Consola 4 | Consola.wrapAll() 5 | Consola.level = 1 6 | 7 | if (process.env.NODE_ENV === 'development') Consola.level = 6 8 | -------------------------------------------------------------------------------- /src/types/flashmessage.ts: -------------------------------------------------------------------------------- 1 | export interface FlashMessage { 2 | type?: 'error' | 'warning' | 'primary' | 'secondary'; 3 | open: boolean; 4 | text?: string; 5 | timeout?: number; 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Fluidd Discord 4 | url: https://discord.gg/GZ3D5tqfcF/ 5 | about: Quickest way to get in contact 6 | -------------------------------------------------------------------------------- /src/directives/blur.ts: -------------------------------------------------------------------------------- 1 | export default (el: HTMLElement) => { 2 | el.onfocus = (ev) => { 3 | const target = ev.target as HTMLElement 4 | target.blur() 5 | // console.log('called blur', target) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/scss/buttons.scss: -------------------------------------------------------------------------------- 1 | /* Prevent zoom on mobile devices when furiosly clicking buttons */ 2 | button .v-btn { 3 | touch-action: manipulation; 4 | } 5 | 6 | button .v-icon { 7 | touch-action: manipulation; 8 | } 9 | -------------------------------------------------------------------------------- /src/store/power/getters.ts: -------------------------------------------------------------------------------- 1 | import { GetterTree } from 'vuex' 2 | import { DevicePowerState } from './types' 3 | import { RootState } from '../types' 4 | 5 | export const getters: GetterTree = { 6 | } 7 | -------------------------------------------------------------------------------- /tests/unit/utils.ts: -------------------------------------------------------------------------------- 1 | import MockDate from 'mockdate' 2 | 3 | export const timeTravel = (to: string, cb: () => any): any => { 4 | MockDate.set(to) 5 | const retVal = cb() 6 | MockDate.reset() 7 | 8 | return retVal 9 | } 10 | -------------------------------------------------------------------------------- /docs/_sass/color_schemes/fluidd.scss: -------------------------------------------------------------------------------- 1 | $brand-color: #2196F3; 2 | 3 | $link-color: $brand-color; 4 | $btn-primary-color: $brand-color; 5 | 6 | .site-logo { 7 | margin-top: 6px; 8 | width: 80% !important; 9 | height: 80% !important; 10 | } 11 | -------------------------------------------------------------------------------- /src/registerComponentHooks.ts: -------------------------------------------------------------------------------- 1 | // registerComponentHooks.ts 2 | // import { Component } from 'vue-property-decorator' 3 | import Component from 'vue-class-component' 4 | 5 | Component.registerHooks([ 6 | 'beforeRouteEnter', 7 | 'beforeRouteUpdate' 8 | ]) 9 | -------------------------------------------------------------------------------- /vue-i18n-extract.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | vueFilesPath: './src/**/*.?(ts|vue)', 3 | languageFilesPath: './src/locales/**/*.?(json|yaml|yml)', 4 | options: { 5 | output: false, 6 | add: false, 7 | dynamic: true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/scss/chips.scss: -------------------------------------------------------------------------------- 1 | .chip-group { 2 | .v-chip { 3 | margin: 2px 2px; 4 | } 5 | .v-chip:first-child { 6 | margin: 0 2px 0 0; 7 | } 8 | .v-chip:last-child { 9 | margin: 0 0 0 2px; 10 | } 11 | .v-chip:only-child { 12 | margin: 0; 13 | } 14 | } -------------------------------------------------------------------------------- /docs/installation/manual.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Manual Installation 4 | parent: Installation 5 | nav_order: 2 6 | permalink: /installation/manual 7 | --- 8 | 9 | # Manual Installation 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Manual configuration steps are coming soon. 15 | -------------------------------------------------------------------------------- /docs/customize/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Customize 4 | nav_order: 3 5 | has_children: true 6 | permalink: /customize 7 | --- 8 | 9 | # Customize 10 | 11 | Fluidd allows you to adjust the layout of your dashboard, and to set a core 12 | theme color. 13 | {: .fs-6 .fw-300 } 14 | -------------------------------------------------------------------------------- /docs/updates/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Updates 4 | nav_order: 6 5 | has_children: true 6 | permalink: /updates 7 | --- 8 | 9 | # Updates 10 | 11 | Updates can be configured and managed all with Fluidd, making updating klipper, 12 | Moonraker and Fluidd a breeze. 13 | {: .fs-6 .fw-300 } 14 | -------------------------------------------------------------------------------- /docs/installation/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Installation 4 | nav_order: 2 5 | has_children: true 6 | permalink: /installation 7 | --- 8 | 9 | # Installation 10 | 11 | For a single printer instance, the easiest approach to installing Fluidd is 12 | by flashing FluiddPI. 13 | {: .fs-6 .fw-300 } 14 | -------------------------------------------------------------------------------- /src/store/power/types.ts: -------------------------------------------------------------------------------- 1 | export interface DevicePowerState { 2 | [key: string]: Device[]; 3 | devices: Device[]; 4 | } 5 | 6 | export interface Device { 7 | device: string; 8 | status: 'init' | 'on' | 'off' | 'error'; 9 | type: 'gpio' | 'tplink_smartplug'; 10 | locked_while_printing: boolean; 11 | } 12 | -------------------------------------------------------------------------------- /docs/features/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Features 4 | nav_order: 7 5 | has_children: true 6 | permalink: /features 7 | --- 8 | 9 | # Features 10 | 11 | Fluidd bundles many features you might not be aware of. Check them out here, 12 | along with any configuration they might require. 13 | {: .fs-6 .fw-300 } 14 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import { FileSystemDialogData, NewDirectory } from './dialogs' 2 | import { FlashMessage } from './flashmessage' 3 | import { MeshData } from './mesh' 4 | import { AppTableHeader } from './tableheaders' 5 | 6 | export { 7 | FileSystemDialogData, 8 | NewDirectory, 9 | FlashMessage, 10 | MeshData, 11 | AppTableHeader 12 | } 13 | -------------------------------------------------------------------------------- /src/store/socket/types.ts: -------------------------------------------------------------------------------- 1 | export interface SocketState { 2 | apiConnected: boolean; 3 | open: boolean; 4 | connecting: boolean; 5 | disconnecting: boolean; 6 | ready: boolean; 7 | acceptingNotifications: boolean; 8 | error: SocketError | null; 9 | } 10 | 11 | export interface SocketError { 12 | code: number; 13 | message: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/store/auth/types.ts: -------------------------------------------------------------------------------- 1 | import { JwtPayload } from 'jwt-decode' 2 | 3 | export interface AuthState { 4 | authenticated: boolean; 5 | token: JwtPayload | null; 6 | refresh_token: JwtPayload | null; 7 | currentUser: AppUser | null; 8 | users: AppUser[]; 9 | apiKey: string; 10 | } 11 | 12 | export interface AppUser { 13 | username: string; 14 | created_on: number; 15 | } 16 | -------------------------------------------------------------------------------- /docs/development/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Development 4 | nav_order: 8 5 | has_children: true 6 | permalink: /development 7 | --- 8 | 9 | # Development 10 | 11 | Fluidd is built using VueJS, and the Vuetify Framework to provide a cohesive, 12 | easily to implement UI. 13 | {: .fs-6 .fw-300 } 14 | 15 | Further details on development environments will come at a later date. 16 | -------------------------------------------------------------------------------- /docs/features/presets.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Thermal Presets 4 | parent: Features 5 | nav_order: 8 6 | permalink: /features/presets 7 | --- 8 | 9 | # Thermal Presets 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd supports setting your own thermal presets. Click through to the UI 15 | settings page to add your own presets. 16 | 17 | ![screenshot](/assets/images/presets.png) 18 | -------------------------------------------------------------------------------- /src/scss/dialogs.scss: -------------------------------------------------------------------------------- 1 | // .v-dialog--scrollable > .v-card > .v-card__text { 2 | // &::-webkit-scrollbar { 3 | // transition: all .5s; 4 | // width: 5px; 5 | // height: 5px; 6 | // z-index: 10; 7 | // } 8 | 9 | // &::-webkit-scrollbar-track { 10 | // background: transparent; 11 | // } 12 | 13 | // &::-webkit-scrollbar-thumb { 14 | // background: #b3ada7; 15 | // } 16 | // } -------------------------------------------------------------------------------- /tests/unit/setup.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable-all typescript-eslint/no-var-requires */ 2 | import Vue from 'vue' 3 | import axios from 'axios' 4 | import { DayJSPlugin } from '@/plugins/dayjs' 5 | 6 | axios.defaults.adapter = require('axios/lib/adapters/xhr') 7 | 8 | Vue.config.productionTip = false 9 | Vue.config.devtools = false 10 | 11 | // === 12 | // Register Plugins 13 | // === 14 | Vue.use(DayJSPlugin) 15 | -------------------------------------------------------------------------------- /src/components/ui/AppCardDrag.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | -------------------------------------------------------------------------------- /src/store/cameras/types.ts: -------------------------------------------------------------------------------- 1 | export interface CamerasState { 2 | activeCamera: string; 3 | cameras: CameraConfig[]; 4 | } 5 | 6 | export interface CameraConfig { 7 | id: string; 8 | enabled: boolean; 9 | name: string; 10 | type: 'mjpgadaptive' | 'mjpgstream' | 'ipstream' | 'iframe'; 11 | url: string; 12 | fpstarget?: number; 13 | flipX: boolean; 14 | flipY: boolean; 15 | height?: number; 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | .credentials 9 | 10 | # local env files 11 | .env.local 12 | .env.*.local 13 | 14 | # Log files 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | pnpm-debug.log* 19 | 20 | # Editor directories and files 21 | .idea 22 | .vscode 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | -------------------------------------------------------------------------------- /src/store/auth/helpers.ts: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | import md5 from 'md5' 3 | 4 | /** 5 | * Determines the token key based on a given api configuration. 6 | */ 7 | export const getTokenKeys = () => { 8 | const url = store.state.config?.apiUrl 9 | const hash = (url) ? md5(url) : '' 10 | return { 11 | 'user-token': `user-token-${hash}`, 12 | 'refresh-token': `refresh-token-${hash}` 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/types/mesh.ts: -------------------------------------------------------------------------------- 1 | export interface MeshData { 2 | x: number[]; 3 | y: number[]; 4 | z: number[][]; 5 | type: string; 6 | intensity: number[]; 7 | cmin: number; 8 | cmax: number; 9 | showscale: boolean; 10 | autocolorscale?: boolean; 11 | colorscale?: string | Array<(number | string)[]>; 12 | colorbar: MeshColorBar; 13 | } 14 | 15 | export interface MeshColorBar { 16 | tickfont: {[key: string]: any}; 17 | } 18 | -------------------------------------------------------------------------------- /docs/features/updates.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Automated Updates 4 | parent: Features 5 | nav_order: 4 6 | permalink: /features/updates 7 | --- 8 | 9 | # Automated Updates 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd supports automated updates by way of Moonraker. Please see the moonraker 15 | [configuration docs](/configuration/moonraker) for further information. 16 | 17 | ![screenshot](/assets/images/updates.png) 18 | -------------------------------------------------------------------------------- /src/scss/helpers.scss: -------------------------------------------------------------------------------- 1 | .rotate-90 { 2 | -webkit-transform: rotate(90deg); 3 | -moz-transform: rotate(90deg); 4 | -ms-transform: rotate(90deg); 5 | -o-transform: rotate(90deg); 6 | transform: rotate(90deg); 7 | } 8 | 9 | .rotate-180 { 10 | -webkit-transform: rotate(180deg); 11 | -moz-transform: rotate(180deg); 12 | -ms-transform: rotate(180deg); 13 | -o-transform: rotate(180deg); 14 | transform: rotate(180deg); 15 | } 16 | -------------------------------------------------------------------------------- /src/store/charts/types.ts: -------------------------------------------------------------------------------- 1 | export interface ChartState { 2 | [index: string]: any; 3 | ready: boolean; // chart is ready, and we've process the initial store data. 4 | chart: ChartData[]; // chart data 5 | selectedLegends: ChartSelectedLegends; 6 | } 7 | 8 | export interface ChartData { 9 | [key: string]: number | Date; 10 | date: Date; 11 | } 12 | 13 | export interface ChartSelectedLegends { 14 | [key: string]: boolean; 15 | } 16 | -------------------------------------------------------------------------------- /src/scss/typeography.scss: -------------------------------------------------------------------------------- 1 | 2 | // Focused text 3 | .focus--text { 4 | font-size: 1.125rem; 5 | font-weight: 300 !important; 6 | letter-spacing: 0.0125em !important; 7 | line-height: auto; 8 | } 9 | 10 | .theme--light .dim--text { 11 | color: rgba(map-get($material-light, 'text-color'), 0.45); 12 | } 13 | 14 | .theme--dark .dim--text { 15 | color: rgba(map-get($material-dark, 'text-color'), 0.45); 16 | } 17 | 18 | // .disabled--text { 19 | // opacity: 0.45; 20 | // } 21 | -------------------------------------------------------------------------------- /docs/configuration/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Configuration 4 | nav_order: 4 5 | has_children: true 6 | permalink: /configuration 7 | --- 8 | 9 | # Configuration 10 | 11 | Because Fluidd relies on Moonraker and Klipper, configuration needs to happen 12 | in more than one location. 13 | {: .fs-6 .fw-300 } 14 | 15 | First steps should ensure you refer to the 16 | [initial setup](/configuration/initial_setup) section in order to ensure you 17 | have basic requirements setup first. 18 | -------------------------------------------------------------------------------- /docs/features/med_mesh.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Bed Mesh 4 | parent: Features 5 | nav_order: 9 6 | permalink: /features/bed_mesh 7 | --- 8 | 9 | # Bed Mesh 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd has a built in bed mesh viewer. Click through to the printer navigation 15 | item and calibrate a mesh to view. 16 | 17 | Note, you'll need to have configured the `bed_mesh` option in klipper for this 18 | option to be visible. 19 | 20 | ![screenshot](/assets/images/bed_mesh.png) 21 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | 18 | 19 |
20 |

404

21 | 22 |

Page not found :(

23 |

The requested page could not be found.

24 |
25 | -------------------------------------------------------------------------------- /docs/customize/themes.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Themes 4 | parent: Customize 5 | nav_order: 2 6 | permalink: /customize/themes 7 | --- 8 | 9 | # Themes 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd lets you choose a community preset, apply a color of your 15 | choosing - along with either a dark or light theme. 16 | 17 | Community presets are a way for Fluidd to support 3d printing communities. If 18 | you'd like to see your logo supported here, let us know! 19 | 20 | ![screenshot](/assets/images/theme.png) 21 | -------------------------------------------------------------------------------- /src/views/NotFound.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /src/workers/parseGcode.worker.ts: -------------------------------------------------------------------------------- 1 | import { expose } from 'threads/worker' 2 | import parseGcode from './parseGcode' 3 | import { Subject, Observable } from 'threads/observable' 4 | 5 | let progress = new Subject() 6 | 7 | expose({ 8 | parse (gcode: string) { 9 | const moves = parseGcode(gcode, progress) 10 | 11 | progress.complete() 12 | progress = new Subject() 13 | 14 | return moves 15 | }, 16 | progress (): Observable { 17 | return Observable.from(progress) 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /src/types/dialogs.ts: -------------------------------------------------------------------------------- 1 | import { AppDirectory, AppFile } from '@/store/files/types' 2 | import { InputValidationRules } from 'vuetify' 3 | 4 | export interface FileSystemDialogData { 5 | type: 'rename' | 'createdir' | 'createfile' | ''; 6 | active: boolean; 7 | valid: boolean; 8 | title: string; 9 | formLabel: string; 10 | rules: InputValidationRules; 11 | item: AppDirectory | NewDirectory | AppFile; 12 | original?: AppDirectory | AppFile; 13 | } 14 | 15 | export interface NewDirectory { 16 | name: string; 17 | } 18 | -------------------------------------------------------------------------------- /src/plugins/workbox.ts: -------------------------------------------------------------------------------- 1 | import _Vue from 'vue' 2 | import wb from '@/registerServiceWorker' 3 | import { Workbox } from 'workbox-window' 4 | 5 | // This will require https in order to work properly. 6 | export const WorkboxPlugin = { 7 | install (Vue: typeof _Vue) { 8 | Vue.prototype.$workbox = wb 9 | Vue.$workbox = wb 10 | } 11 | } 12 | 13 | declare module 'vue/types/vue' { 14 | interface Vue { 15 | $workbox: Workbox | null; 16 | } 17 | 18 | interface VueConstructor { 19 | $workbox: Workbox | null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/features/localization.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Localization 4 | parent: Features 5 | nav_order: 11 6 | permalink: /features/localization 7 | --- 8 | 9 | # Localization 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd supports localization. Initially, the browsers default language is used. However, you can force a language via interface settings. 15 | 16 | See the [developer localization](/development/localization) docs For more information on how to contribute to translations. 17 | 18 | ![screenshot](/assets/images/localization.png) 19 | -------------------------------------------------------------------------------- /src/store/macros/types.ts: -------------------------------------------------------------------------------- 1 | export interface MacrosState { 2 | stored: Macro[]; 3 | categories: MacroCategory[]; 4 | expanded: number[]; 5 | } 6 | 7 | export interface Macro { 8 | name: string; 9 | alias?: string; 10 | visible: boolean; 11 | categoryId?: string; 12 | category?: MacroCategory; 13 | assignTo?: string; 14 | disabledWhilePrinting?: boolean; 15 | color?: string; 16 | config?: any; 17 | } 18 | 19 | export interface MacroCategory { 20 | id: string; 21 | name: string; 22 | count?: number; 23 | visible?: number; 24 | } 25 | -------------------------------------------------------------------------------- /src/components/ui/AppBtnGroup.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | -------------------------------------------------------------------------------- /src/scss/global.scss: -------------------------------------------------------------------------------- 1 | /* Import our fonts */ 2 | @import "~@fontsource/roboto/300.css"; 3 | @import "~@fontsource/roboto/400.css"; 4 | @import "~@fontsource/roboto/500.css"; 5 | @import "~@fontsource/roboto/700.css"; 6 | @import "~@fontsource/raleway/400.css"; 7 | 8 | @import "./misc"; 9 | @import "./helpers"; 10 | @import "./dialogs"; 11 | @import "./cards"; 12 | @import "./typeography"; 13 | @import "./animation"; 14 | @import "./buttons"; 15 | @import "./chips"; 16 | @import "./file-system"; 17 | @import "./inputs"; 18 | @import "./lists"; 19 | @import "./tables"; 20 | -------------------------------------------------------------------------------- /src/types/tableheaders.ts: -------------------------------------------------------------------------------- 1 | import { DataTableHeader } from 'vuetify' 2 | 3 | /** 4 | * Vuetify table headers, extended with data so we can more easily save 5 | * and reference later. 6 | * key: Used to override the lookup key if required. 7 | * configurable: if a user can toggle this header or not. Defaults to true if not defined. 8 | */ 9 | export interface AppTablePartialHeader { 10 | value: string; 11 | key?: string; 12 | configurable?: boolean; 13 | visible?: boolean; 14 | } 15 | 16 | export interface AppTableHeader extends DataTableHeader, AppTablePartialHeader {} 17 | -------------------------------------------------------------------------------- /docs/installation/docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Docker 4 | parent: Installation 5 | nav_order: 2 6 | permalink: /installation/docker 7 | --- 8 | 9 | # Docker 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd can be hosted with Docker. This is considered an advanced install, but 15 | gives you the benefit of hosting a single instance of Fluidd, and having it 16 | connect to multiple printers. 17 | 18 | The container is updated automatically with each release of Fluidd. 19 | 20 | [View on Docker Hub](https://hub.docker.com/r/cadriel/fluidd){: .btn .fs-5 .mb-4 .mb-md-0 } 21 | -------------------------------------------------------------------------------- /docs/features/notifications.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Notifications 4 | parent: Features 5 | nav_order: 13 6 | permalink: /features/notifications 7 | --- 8 | 9 | # Notifications 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd has a built-in notification system to warn you of potential issues. 15 | 16 | Fluidd will fire a warning if you have updates pending, or if 17 | you've hit a throttle condition (if running on a Pi, or otherwise your host 18 | supports `vcgencmd`). Other warnings will come as they're available. 19 | 20 | ![screenshot](/assets/images/notifications.png) 21 | -------------------------------------------------------------------------------- /src/store/wait/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { WaitState } from './types' 6 | import { RootState } from '../types' 7 | 8 | export const defaultState = (): WaitState => { 9 | return { 10 | waits: [] 11 | } 12 | } 13 | 14 | export const state = defaultState() 15 | 16 | const namespaced = true 17 | 18 | export const wait: Module = { 19 | namespaced, 20 | state, 21 | getters, 22 | actions, 23 | mutations 24 | } 25 | -------------------------------------------------------------------------------- /src/util/get-all-layouts.ts: -------------------------------------------------------------------------------- 1 | import { LayoutConfig, Layouts } from '@/store/layout/types' 2 | 3 | export default (layouts: Layouts) => { 4 | let knownComponents: LayoutConfig[] = [] 5 | for (const _layout in layouts) { 6 | const layout = layouts[_layout] 7 | for (const _container in layout) { 8 | const container = layouts[_layout][_container] 9 | .map(config => ({ ...config, container: _container, layout: _layout })) 10 | knownComponents = [ 11 | ...knownComponents, 12 | ...container 13 | ] 14 | } 15 | } 16 | return knownComponents 17 | } 18 | -------------------------------------------------------------------------------- /src/util/merge-file-update.ts: -------------------------------------------------------------------------------- 1 | import getFilePaths from './get-file-paths' 2 | import { 3 | AppFile, 4 | AppFileWithMeta 5 | } from '@/store/files/types' 6 | 7 | /** 8 | * File Updates come in with the filename representing the filepath, 9 | * so we need to strip the path to reflect what we store. 10 | */ 11 | export default (root: string, existing: AppFile | AppFileWithMeta | {}, updates: AppFile | AppFileWithMeta): AppFileWithMeta | AppFile => { 12 | const paths = getFilePaths(updates.filename, root) 13 | updates.filename = paths.filename 14 | return { ...existing, ...updates } 15 | } 16 | -------------------------------------------------------------------------------- /docs/installation/kiauh.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: KIAUH 4 | parent: Installation 5 | nav_order: 2 6 | permalink: /installation/kiauh 7 | --- 8 | 9 | # KIAUH - Klipper Installation And Update Helper 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | KIAUH makes installation and updates a breeze. This is the recommended approach 15 | when you'd like to trial more than one user interface on your pi, or have more 16 | of an advanced setup. 17 | 18 | For more information on KIAUH, please visit its github page. 19 | 20 | [View on GitHub](https://github.com/th33xitus/kiauh){: .btn .fs-5 .mb-4 .mb-md-0 } 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest as unzip 2 | 3 | ARG FRONTEND_ZIP_URL=https://github.com/fluidd-core/fluidd/releases/latest/download/fluidd.zip 4 | 5 | WORKDIR /frontend 6 | 7 | ADD ${FRONTEND_ZIP_URL} /tmp/frontend.zip 8 | RUN unzip /tmp/frontend.zip -d /frontend 9 | 10 | FROM nginx:alpine 11 | 12 | ENV JPEG_STREAM_HOST localhost 13 | ENV JPEG_STREAM_PORT 8080 14 | 15 | COPY --chown=101:101 server/nginx-site.conf /etc/nginx/conf.d/default.conf 16 | COPY --from=unzip --chown=101:101 /frontend /usr/share/nginx/html 17 | COPY --chown=101:101 server/config.json /usr/share/nginx/html/config.json 18 | EXPOSE 80 19 | -------------------------------------------------------------------------------- /src/store/power/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { DevicePowerState } from './types' 6 | import { RootState } from '../types' 7 | 8 | export const defaultState = (): DevicePowerState => { 9 | return { 10 | devices: [] 11 | } 12 | } 13 | 14 | export const state = defaultState() 15 | 16 | const namespaced = true 17 | 18 | export const power: Module = { 19 | namespaced, 20 | state, 21 | getters, 22 | actions, 23 | mutations 24 | } 25 | -------------------------------------------------------------------------------- /src/util/get-browser-locale.ts: -------------------------------------------------------------------------------- 1 | const getBrowserLocale = (options = {}) => { 2 | const defaultOptions = { countryCodeOnly: false } 3 | 4 | const opt = { ...defaultOptions, ...options } 5 | 6 | const navigatorLocale = 7 | navigator.languages !== undefined 8 | ? navigator.languages[0] 9 | : navigator.language 10 | 11 | if (!navigatorLocale) { 12 | return undefined 13 | } 14 | 15 | const trimmedLocale = opt.countryCodeOnly 16 | ? navigatorLocale.trim().split(/-|_/)[0] 17 | : navigatorLocale.trim() 18 | return trimmedLocale 19 | } 20 | 21 | export default getBrowserLocale 22 | -------------------------------------------------------------------------------- /src/store/notifications/getters.ts: -------------------------------------------------------------------------------- 1 | import { GetterTree } from 'vuex' 2 | import { NotificationsState } from './types' 3 | import { RootState } from '../types' 4 | 5 | export const getters: GetterTree = { 6 | getNotifications: (state) => { 7 | // Sort by datestamp, most recent first. 8 | const sorted = [...state.notifications].sort((a, b) => { 9 | return b.timestamp - a.timestamp 10 | }) 11 | 12 | // Easier to read. 13 | return [ 14 | ...sorted.filter(n => n.type === 'error'), 15 | ...sorted.filter(n => n.type !== 'error') 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/scss/tables.scss: -------------------------------------------------------------------------------- 1 | // For those tables where a hover doesn't make sense... 2 | .theme--light.v-data-table.no-hover > .v-data-table__wrapper > table > tbody > tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper), 3 | .theme--dark.v-data-table.no-hover > .v-data-table__wrapper > table > tbody > tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) { 4 | background-color: transparent; 5 | } 6 | 7 | // For those tables where you want to mark a row inactive... 8 | .v-data-table__wrapper > table > tbody > tr.v-data-table__inactive { 9 | color: var(--v-secondary-base); 10 | } 11 | -------------------------------------------------------------------------------- /public/logo_voron.svg: -------------------------------------------------------------------------------- 1 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /src/scss/lists.scss: -------------------------------------------------------------------------------- 1 | /* Extra dense list items */ 2 | .v-list-item--x-dense .v-list-item__content, 3 | .v-list--x-dense .v-list-item .v-list-item__content { 4 | padding: 6px 0 !important; 5 | } 6 | 7 | .v-list-item--x-dense .v-list-item__action, 8 | .v-list--x-dense .v-list-item .v-list-item__action { 9 | margin: 8px 0; 10 | } 11 | 12 | /* Helps alignment for some outer buttons */ 13 | .v-text-field-outer-btn { 14 | .v-input__control { 15 | min-height: 36px !important; 16 | } 17 | .v-input__prepend-outer, .v-input__append-outer { 18 | margin-top: 0px !important; 19 | margin-bottom: 0 !important; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/features/multiple_printers.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Multiple Printers 4 | parent: Features 5 | nav_order: 1 6 | permalink: /features/printers 7 | --- 8 | 9 | # Multiple Printers 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd allows connecting and swapping between multiple printers. 15 | 16 | In some circumstances, moonraker must be configured to allow a connection from fluidd. Configuration may depend on your type of install. 17 | 18 | Please see the [multiple printers](/configuration/multiple_printers) configuration docs for more information on setup. 19 | 20 | ![screenshot](/assets/images/printer-selection.png) 21 | -------------------------------------------------------------------------------- /src/store/socket/getters.ts: -------------------------------------------------------------------------------- 1 | import { GetterTree } from 'vuex' 2 | import { SocketState } from './types' 3 | import { RootState } from '../types' 4 | 5 | export const getters: GetterTree = { 6 | /** 7 | * Indicates if our socket is connected / open. 8 | */ 9 | getConnectionState: (state): boolean => { 10 | return state.open 11 | }, 12 | 13 | /** 14 | * Indicates if our socket is attempting to connect still.. 15 | */ 16 | getConnectingState: (state): boolean => { 17 | return state.connecting 18 | }, 19 | 20 | getApiConnected: (state) => { 21 | return state.apiConnected 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/features/chart.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Thermals Chart 4 | parent: Features 5 | nav_order: 7 6 | permalink: /features/chart 7 | --- 8 | 9 | # Thermals Chart 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd's chart allow you to view not just temperatures, but also power applied 15 | to your heaters and fans. 16 | 17 | 1. Click the name of a heater, sensor or fan to toggle its visibility. 18 | 2. Click the power of a heater, sensor or fan to toggle its visibility. 19 | 3. Zoom the chart by holding SHIFT and scrolling your mouse wheel whilst the 20 | cursor is over the chart. 21 | 22 | ![screenshot](/assets/images/graph.png) 23 | -------------------------------------------------------------------------------- /src/store/wait/actions.ts: -------------------------------------------------------------------------------- 1 | import { ActionTree } from 'vuex' 2 | import { WaitState } from './types' 3 | import { RootState } from '../types' 4 | 5 | export const actions: ActionTree = { 6 | /** 7 | * Reset our store 8 | */ 9 | async reset ({ commit }) { 10 | commit('setReset') 11 | }, 12 | 13 | /** 14 | * Add's a wait to the list of waits. 15 | */ 16 | async addWait ({ commit }, wait) { 17 | commit('setAddWait', wait) 18 | }, 19 | 20 | /** 21 | * Removes a wait from the list of waits. 22 | */ 23 | async removeWait ({ commit }, wait) { 24 | commit('setRemoveWait', wait) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/util/get-file-paths.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FilePaths 3 | } from '@/store/files/types' 4 | 5 | /** 6 | * Takes a filename and root and provides; 7 | * - the filename, with no path. 8 | * - the path, with no root or filename. 9 | * - the path, including root. 10 | */ 11 | export default (filePath: string, root: string): FilePaths => { 12 | let path = filePath.substr(0, filePath.lastIndexOf('/')) 13 | path = (path && path.startsWith(root)) 14 | ? path.substring(root.length) 15 | : path 16 | return { 17 | filename: filePath.split('/').pop() || '', 18 | path, 19 | rootPath: (path.length === 0) ? root : root + '/' + path 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | process.env.TZ = 'GMT' 3 | 4 | module.exports = { 5 | preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', 6 | transformIgnorePatterns: [ 7 | '@/node_modules/(?!lodash-es|dayjs)' 8 | ], 9 | moduleNameMapper: { 10 | '^@/(.*)$': '/src/$1', 11 | '^tests/(.*)$': '/tests/$1' 12 | }, 13 | collectCoverageFrom: ['src/**/*.{ts,vue}', '!**/node_modules/**'], 14 | coverageReporters: ['html', 'text-summary'], 15 | setupFiles: [ 16 | '/tests/unit/setup.ts' 17 | ], 18 | globals: { 19 | 'ts-jest': { 20 | log: true 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/store/layout/types.ts: -------------------------------------------------------------------------------- 1 | export interface LayoutState { 2 | layouts: Layouts; 3 | } 4 | 5 | /** 6 | * As single layout. Should have at least one container. 7 | * Configuration id's should not be duplicated across containers. 8 | **/ 9 | export interface Layouts { 10 | [index: string]: LayoutContainer; 11 | } 12 | 13 | /** Containers, each of which is an array of configurations. */ 14 | export interface LayoutContainer { 15 | [index: string]: LayoutConfig[]; 16 | } 17 | 18 | /** Configuration of a layout item */ 19 | export interface LayoutConfig { 20 | id: string; 21 | enabled: boolean; 22 | collapsed: boolean; 23 | layout?: string; 24 | container?: string; 25 | } 26 | -------------------------------------------------------------------------------- /docs/customize/layout.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Application Layout 4 | parent: Customize 5 | nav_order: 1 6 | permalink: /customize/layout 7 | --- 8 | 9 | # Application Layout 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd allows you to adjust your dashboard layout to your liking. Use the 15 | hamburger menu and click the `adjust layout` option. 16 | 17 | Use the drag handles to move cards to / from the left and right columns. You 18 | can also easily disable cards if you have no use for them. 19 | 20 | Once you're done, click the exit layout mode button. You can reset back to 21 | the default layout by clicking reset layout. 22 | 23 | ![screenshot](/assets/images/layout.png) 24 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/store/auth/getters.ts: -------------------------------------------------------------------------------- 1 | import { GetterTree } from 'vuex' 2 | import { AuthState } from './types' 3 | import { RootState } from '../types' 4 | 5 | export const getters: GetterTree = { 6 | getAuthenticated: (state): boolean => { 7 | return state.authenticated 8 | }, 9 | 10 | getCurrentUser: (state) => { 11 | return state.currentUser 12 | }, 13 | 14 | getUsers: (state) => { 15 | return state.users 16 | }, 17 | 18 | getToken: (state) => { 19 | return state.token 20 | }, 21 | 22 | getRefreshToken: (state) => { 23 | return state.refresh_token 24 | }, 25 | 26 | getApiKey: (state) => { 27 | return state.apiKey 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/store/charts/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { ChartState } from './types' 6 | import { RootState } from '../types' 7 | 8 | /** 9 | * Maintains the state of the console 10 | */ 11 | export const defaultState = (): ChartState => { 12 | return { 13 | ready: false, 14 | chart: [], 15 | selectedLegends: {} 16 | } 17 | } 18 | 19 | export const state = defaultState() 20 | 21 | const namespaced = true 22 | 23 | export const charts: Module = { 24 | namespaced, 25 | state, 26 | getters, 27 | actions, 28 | mutations 29 | } 30 | -------------------------------------------------------------------------------- /src/store/macros/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { MacrosState } from './types' 6 | import { RootState } from '../types' 7 | 8 | /** 9 | * Maintains the state of the console 10 | */ 11 | export const defaultState = (): MacrosState => { 12 | return { 13 | stored: [], 14 | categories: [], 15 | expanded: [0] 16 | } 17 | } 18 | 19 | export const state = defaultState() 20 | 21 | const namespaced = true 22 | 23 | export const macros: Module = { 24 | namespaced, 25 | state, 26 | getters, 27 | actions, 28 | mutations 29 | } 30 | -------------------------------------------------------------------------------- /docs/features/macros.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Macros 4 | parent: Features 5 | nav_order: 14 6 | permalink: /features/macros 7 | --- 8 | 9 | # Macros 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd support turning your macros on and off, and categorizing them. You can 15 | also hide macros from Fluidd entirely by prefixing their name with an 16 | underscore (`_`). 17 | 18 | To move a macro to a category, you first create a category in the settings menu, 19 | then left-click on a macro in an existing category. This will bring up a menu 20 | where you can select the category your macro will be in as well as change the color. 21 | 22 | ![screenshot](/assets/images/macros2.png) 23 | ![screenshot](/assets/images/macros1.png) 24 | -------------------------------------------------------------------------------- /src/store/power/mutations.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { MutationTree } from 'vuex' 3 | import { DevicePowerState } from './types' 4 | import { defaultState } from './index' 5 | 6 | export const mutations: MutationTree = { 7 | /** 8 | * Reset state 9 | */ 10 | setReset (state) { 11 | Object.assign(state, defaultState()) 12 | }, 13 | 14 | setDevices (state, payload) { 15 | state.devices = payload.devices 16 | }, 17 | 18 | setStatus (state, payload) { 19 | for (const key in payload) { 20 | const i = state.devices.findIndex(device => device.device === key) 21 | if (i >= 0) { 22 | Vue.set(state.devices[i], 'status', payload[key]) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docs/customize/hide_outputs.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Hide macros, output pins and fans 4 | parent: Customize 5 | nav_order: 3 6 | permalink: /customize/hide 7 | --- 8 | 9 | # Hide macros, output pins and fans 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd allows you to hide macros, output pins and fans by prefixing them 15 | with an underscore (`_`). 16 | 17 | By doing this - you're removing them from Fluidd. This can be handy in 18 | situations where you have a large quantiy of macros, or whereby you have an 19 | output pin you may have no need to control in UI. 20 | 21 | Some examples; 22 | 23 | ```yaml 24 | [gcode_macro _MY_MACRO] 25 | gcode: 26 | G28 27 | ``` 28 | 29 | ```yaml 30 | [output_pin _BEEPER] 31 | pin: z:P1.30 32 | ``` 33 | -------------------------------------------------------------------------------- /src/store/mesh/mutations.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree } from 'vuex' 2 | import { defaultState } from './' 3 | import { MeshState } from './types' 4 | 5 | export const mutations: MutationTree = { 6 | /** 7 | * Reset state 8 | */ 9 | setReset (state) { 10 | Object.assign(state, defaultState()) 11 | }, 12 | 13 | setMatrix (state, payload) { 14 | state.matrix = payload 15 | }, 16 | 17 | setScale (state, payload) { 18 | state.scale = payload 19 | }, 20 | 21 | setBoxScale (state, payload) { 22 | state.boxScale = payload 23 | }, 24 | 25 | setWireframe (state, payload) { 26 | state.wireframe = payload 27 | }, 28 | 29 | setFlatSurface (state, payload) { 30 | state.flatSurface = payload 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/features/sensors.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Sensors 4 | parent: Features 5 | nav_order: 5 6 | permalink: /features/sensors 7 | --- 8 | 9 | # Sensors 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd supports many of the built-in sensors from Klipper. Some examples are; 15 | 16 | ## Raspberry Pi Temperature 17 | 18 | ```yaml 19 | [temperature_sensor raspberry_pi] 20 | sensor_type: temperature_host 21 | min_temp: 10 22 | max_temp: 100 23 | ``` 24 | 25 | ## ATSAM, ATAMD and STM32 temperature sensors 26 | 27 | ```yaml 28 | [temperature_sensor mcu_temp] 29 | sensor_type: temperature_mcu 30 | min_temp: 0 31 | max_temp: 100 32 | ``` 33 | 34 | More information concerning other supported sensors can be found in the 35 | [klipper documentation](http://klipper3d.org) 36 | -------------------------------------------------------------------------------- /.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: cadriel # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: cadriel # 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: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /src/store/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { AuthState } from './types' 6 | import { RootState } from '../types' 7 | 8 | /** 9 | * Maintains the state of the console 10 | */ 11 | export const defaultState = (): AuthState => { 12 | return { 13 | authenticated: false, 14 | token: null, 15 | refresh_token: null, 16 | currentUser: null, 17 | users: [], 18 | apiKey: '' 19 | } 20 | } 21 | 22 | export const state = defaultState() 23 | 24 | const namespaced = true 25 | 26 | export const auth: Module = { 27 | namespaced, 28 | state, 29 | getters, 30 | actions, 31 | mutations 32 | } 33 | -------------------------------------------------------------------------------- /src/store/mesh/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { MeshState } from './types' 6 | import { RootState } from '../types' 7 | 8 | /** 9 | * Maintains the state of the console 10 | */ 11 | export const defaultState = (): MeshState => { 12 | return { 13 | variance: 0, 14 | wireframe: false, 15 | scale: 0.2, 16 | boxScale: 2.0, 17 | flatSurface: false, 18 | matrix: 'mesh_matrix' 19 | } 20 | } 21 | 22 | export const state = defaultState() 23 | 24 | const namespaced = true 25 | 26 | export const mesh: Module = { 27 | namespaced, 28 | state, 29 | getters, 30 | actions, 31 | mutations 32 | } 33 | -------------------------------------------------------------------------------- /src/store/wait/mutations.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree } from 'vuex' 2 | import { WaitState } from './types' 3 | import { defaultState } from './index' 4 | 5 | export const mutations: MutationTree = { 6 | /** 7 | * Reset state 8 | */ 9 | setReset (state) { 10 | Object.assign(state, defaultState()) 11 | }, 12 | 13 | /** 14 | * Add a wait, ensuring we don't add dupes. 15 | */ 16 | setAddWait (state, payload) { 17 | const i = state.waits.findIndex(wait => wait === payload) 18 | if (i === -1) state.waits.push(payload) 19 | }, 20 | 21 | /** 22 | * Remove a wait, if found. 23 | */ 24 | setRemoveWait (state, payload) { 25 | if (state.waits.length) { 26 | state.waits.splice(state.waits.indexOf(payload, 0), 1) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/eventBus.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export const EventBus = { 4 | bus: new Vue(), 5 | $emit: (text?: string, options = {}): void => { 6 | const opts: FlashMessage = { 7 | open: true, 8 | timeout: -1, 9 | ...options 10 | } 11 | if (text) opts.text = text 12 | // if (type) opts.type = type 13 | // if (timeout) opts.timeout = timeout 14 | 15 | EventBus.bus.$emit('flashMessage', opts) // custom message 16 | } 17 | } 18 | 19 | export interface FlashMessage { 20 | type?: FlashMessageTypes; 21 | open: boolean; 22 | text?: string; 23 | timeout?: number; 24 | } 25 | 26 | export enum FlashMessageTypes { 27 | success = 'success', 28 | error = 'error', 29 | warning = 'warning', 30 | primary = 'primary', 31 | secondary = 'secondary' 32 | } 33 | -------------------------------------------------------------------------------- /docs/features/console.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Console 4 | parent: Features 5 | nav_order: 6 6 | permalink: /features/console 7 | --- 8 | 9 | # Console 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd's console has a bunch of hidden features; 15 | 16 | 1. Hit the ↑ up and ↓ down arrow keys to scroll through your console history. 17 | This history is saved across sessions! 18 | 19 | 2. The console has autocomplete, built in! Start typing, and hit the TAB key 20 | to retrieve a list of available commands. 21 | 22 | 3. Known commands in the console are clickable! Try clicking a command, and 23 | you'll see it appear in the console entry text area. 24 | 25 | 4. Want to know a full list of klipper commands? Type `help` and hit return! 26 | 27 | ![screenshot](/assets/images/console.png) 28 | -------------------------------------------------------------------------------- /src/monaco/README.md: -------------------------------------------------------------------------------- 1 | ### Monaco Support Credit 2 | A big thank you to; 3 | 4 | - Aerosov for the klipper-config TextMate grammar definition 5 | https://github.com/aeresov/vscode-klipper-config-syntax 6 | 7 | - Applied Engineering & Design for the gcode TextMate grammar definition 8 | https://github.com/appliedengdesign/vscode-gcode-syntax 9 | 10 | - Microsoft for the log grammar definition 11 | https://github.com/microsoft/vscode/blob/main/extensions/log/syntaxes/log.tmLanguage.json 12 | 13 | - NeekSandhu for the monaco textmate documentation and libs 14 | https://github.com/NeekSandhu/monaco-textmate 15 | https://github.com/NeekSandhu/monaco-editor-textmate 16 | 17 | - Nishkalkashyap for the vscode -> monaco theme converter 18 | https://github.com/Nishkalkashyap/monaco-vscode-textmate-theme-converter 19 | -------------------------------------------------------------------------------- /src/components/layout/AppFooter.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 27 | 28 | 36 | -------------------------------------------------------------------------------- /src/components/widgets/macros/MacrosCard.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | -------------------------------------------------------------------------------- /src/store/mesh/actions.ts: -------------------------------------------------------------------------------- 1 | import { ActionTree } from 'vuex' 2 | import { MeshState } from './types' 3 | import { RootState } from '../types' 4 | 5 | export const actions: ActionTree = { 6 | /** 7 | * Reset our store 8 | */ 9 | async reset ({ commit }) { 10 | commit('setReset') 11 | }, 12 | 13 | async onMatrix ({ commit }, payload) { 14 | commit('setMatrix', payload) 15 | }, 16 | 17 | async onScale ({ commit }, payload) { 18 | commit('setScale', payload) 19 | }, 20 | 21 | async onBoxScale ({ commit }, payload) { 22 | commit('setBoxScale', payload) 23 | }, 24 | 25 | async onWireframe ({ commit }, payload) { 26 | commit('setWireframe', payload) 27 | }, 28 | 29 | async onFlatSurface ({ commit }, payload) { 30 | commit('setFlatSurface', payload) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/widgets/retract/RetractCard.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | -------------------------------------------------------------------------------- /src/store/files/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { FilesState } from './types' 6 | import { RootState } from '../types' 7 | 8 | export const defaultState = (): FilesState => { 9 | return { 10 | uploads: [], 11 | download: null, 12 | currentPaths: {}, 13 | disk_usage: { 14 | total: 0, 15 | used: 0, 16 | free: 0 17 | }, 18 | gcodes: [], 19 | config: [], 20 | config_examples: [], 21 | docs: [], 22 | logs: [] 23 | } 24 | } 25 | 26 | export const state = defaultState() 27 | 28 | const namespaced = true 29 | 30 | export const files: Module = { 31 | namespaced, 32 | state, 33 | getters, 34 | actions, 35 | mutations 36 | } 37 | -------------------------------------------------------------------------------- /src/components/_globals.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { camelCase } from 'lodash' 3 | 4 | const requireComponent = require.context( 5 | // The relative path of the components folder 6 | '.', 7 | // Whether or not to look in subfolders 8 | true, 9 | // The regular expression used to match base component filenames 10 | /(ui|layout|common)\/[\w-]+\.vue$/ 11 | ) 12 | 13 | requireComponent.keys().forEach((fileName) => { 14 | // Get component config 15 | const componentConfig = requireComponent(fileName) 16 | 17 | // Get PascalCase name of component 18 | const componentName = camelCase( 19 | fileName 20 | .split('/') 21 | .pop() 22 | ?.replace(/\.\w+$/, '') 23 | ) 24 | 25 | // Globally register the component 26 | if (componentName) Vue.component(componentName, componentConfig.default || componentConfig) 27 | }) 28 | -------------------------------------------------------------------------------- /src/util/get-klipper-type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Map object prefixes and names to their generic types. 3 | * I.e., temperature_fans and heater_fans are both fans. 4 | */ 5 | export default (name: string) => { 6 | const fans = [ 7 | 'temperature_fan', 8 | 'controller_fan', 9 | 'heater_fan', 10 | 'fan_generic', 11 | 'fan' 12 | ] 13 | 14 | const sensors = [ 15 | 'temperature_sensor', 16 | 'temperature_probe' 17 | ] 18 | 19 | const heaters = [ 20 | 'heater_generic', 21 | 'extruder' 22 | ] 23 | 24 | const beds = [ 25 | 'heater_bed' 26 | ] 27 | 28 | if (fans.some(s => name.startsWith(s))) return 'fan' 29 | if (sensors.some(s => name.startsWith(s))) return 'sensor' 30 | if (heaters.some(s => name.startsWith(s))) return 'heater' 31 | if (beds.some(s => name.startsWith(s))) return 'bed' 32 | return '' 33 | } 34 | -------------------------------------------------------------------------------- /src/components/widgets/outputs/OutputsCard.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 31 | -------------------------------------------------------------------------------- /src/store/console/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { ConsoleState } from './types' 6 | import { RootState } from '../types' 7 | 8 | /** 9 | * Maintains the state of the console 10 | */ 11 | export const defaultState = (): ConsoleState => { 12 | return { 13 | consoleCommand: '', 14 | consoleEntryCount: 0, 15 | console: [], 16 | availableCommands: {}, 17 | commandHistory: [], 18 | autoScroll: true, 19 | consoleFilters: [], 20 | consoleFiltersRegexp: [] 21 | } 22 | } 23 | 24 | export const state = defaultState() 25 | 26 | const namespaced = true 27 | 28 | export const console: Module = { 29 | namespaced, 30 | state, 31 | getters, 32 | actions, 33 | mutations 34 | } 35 | -------------------------------------------------------------------------------- /src/components/widgets/limits/PrinterLimitsCard.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | -------------------------------------------------------------------------------- /docs/updates/automated.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Automated 4 | parent: Updates 5 | nav_order: 1 6 | permalink: /updates/automated 7 | --- 8 | 9 | # Automated Updates 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd allows you to update all of its components, including the host system in an automated way. 15 | It will notify you of available updates - and provide buttons to upgrade each individual component. 16 | 17 | The recommended order of updates should be; 18 | 19 | 1. Klipper 20 | 2. Moonraker 21 | 3. Fluidd 22 | 4. Other clients 23 | 24 | Klipper can be skipped if you have a reason to not update klipper immediately. 25 | 26 | In order for this feature to be enabled, you need to configure moonraker's update plugin. 27 | 28 | Please see here for the [moonraker configuration](/configuration/moonraker_conf) docs. 29 | 30 | ![screenshot](../assets/images/updates.png) 31 | -------------------------------------------------------------------------------- /src/scss/inputs.scss: -------------------------------------------------------------------------------- 1 | /* Extra dense text inputs */ 2 | .v-input--text-right input { 3 | text-align: right; 4 | } 5 | 6 | .v-input--width-x-small { 7 | width: 100px; 8 | } 9 | 10 | .v-input--width-small { 11 | width: 140px; 12 | } 13 | 14 | .v-input--width-medium { 15 | width: 180px; 16 | } 17 | 18 | .v-input--x-dense .v-input__slot { 19 | min-height: 25px !important; 20 | } 21 | 22 | .v-input--dense .v-input__slot { 23 | min-height: 36px !important; 24 | } 25 | 26 | .v-input--x-dense .v-select__selections { 27 | padding: 2px 0 !important; 28 | } 29 | 30 | .v-input--x-dense .v-input__append-inner { 31 | margin-top: 6px !important; 32 | } 33 | 34 | .v-text-field.v-text-field--solo .v-input__control{ 35 | min-height: 10px; 36 | } 37 | .v-input--dense .v-text-field--box .v-input__slot, .v-text-field--outline .v-input__slot { 38 | min-height: 30px !important; 39 | } 40 | -------------------------------------------------------------------------------- /src/store/history/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { HistoryState } from './types' 6 | import { RootState } from '../types' 7 | 8 | /** 9 | * Maintains the state of the console 10 | */ 11 | export const defaultState = (): HistoryState => { 12 | return { 13 | count: 0, 14 | jobs: [], 15 | job_totals: { 16 | total_jobs: 0, 17 | total_time: 0, 18 | total_print_time: 0, 19 | total_filament_used: 0, 20 | longest_job: 0, 21 | longest_print: 0 22 | } 23 | } 24 | } 25 | 26 | export const state = defaultState() 27 | 28 | const namespaced = true 29 | 30 | export const history: Module = { 31 | namespaced, 32 | state, 33 | getters, 34 | actions, 35 | mutations 36 | } 37 | -------------------------------------------------------------------------------- /public/logo_eva.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/store/version/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { VersionState } from './types' 6 | import { RootState } from '../types' 7 | 8 | export const defaultState = (): VersionState => { 9 | return { 10 | busy: false, // busy doing an update. 11 | refreshing: false, // busy refreshing version state. 12 | github_limit_reset_time: 0, 13 | github_rate_limit: 0, 14 | github_requests_remaining: 0, 15 | responses: [], 16 | version_info: {}, 17 | fluidd: { 18 | version: '', 19 | hash: '' 20 | } 21 | } 22 | } 23 | 24 | export const state = defaultState() 25 | 26 | const namespaced = true 27 | 28 | export const version: Module = { 29 | namespaced, 30 | state, 31 | getters, 32 | actions, 33 | mutations 34 | } 35 | -------------------------------------------------------------------------------- /src/views/Jobs.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 35 | -------------------------------------------------------------------------------- /src/store/mesh/types.ts: -------------------------------------------------------------------------------- 1 | export interface MeshState { 2 | variance: number; 3 | wireframe: boolean; 4 | scale: number; 5 | boxScale: number; 6 | flatSurface: boolean; 7 | matrix: 'probed_matrix' | 'mesh_matrix'; 8 | } 9 | 10 | export interface KlipperMesh { 11 | [index: string]: string | boolean | number[] | number[][] | undefined; 12 | profile_name: string; 13 | active: boolean; 14 | markedForRemoval?: boolean; 15 | markedForSave?: boolean; 16 | mesh_max?: number[]; 17 | mesh_min?: number[]; 18 | mesh_matrix?: number[][]; 19 | probed_matrix?: number[][]; 20 | } 21 | 22 | export interface AppMeshes { 23 | [index: string]: ProcessedMesh; 24 | } 25 | 26 | export interface ProcessedMesh { 27 | coordinates: MeshCoordinates[]; 28 | variance: number; 29 | min: number; 30 | mid: number; 31 | max: number; 32 | } 33 | 34 | export interface MeshCoordinates { 35 | name: string; 36 | value: number[]; 37 | } 38 | -------------------------------------------------------------------------------- /src/store/console/types.ts: -------------------------------------------------------------------------------- 1 | export interface ConsoleState { 2 | // [key: string]: string; 3 | consoleCommand: string; 4 | console: ConsoleEntry[]; // console stream 5 | availableCommands: GcodeCommands; // available gcode commands 6 | consoleEntryCount: number; // give each console entry a unique id. 7 | commandHistory: string[]; 8 | autoScroll: boolean; 9 | consoleFilters: ConsoleFilter[]; 10 | consoleFiltersRegexp: RegExp[]; 11 | } 12 | 13 | export interface ConsoleEntry { 14 | id?: number; 15 | message: string; 16 | type: 'command' | 'response'; 17 | time?: number; 18 | } 19 | 20 | export interface GcodeCommands { 21 | [key: string]: string; 22 | } 23 | 24 | export enum ConsoleFilterType { 25 | Contains, 26 | StartsWith, 27 | Expression 28 | } 29 | 30 | export interface ConsoleFilter { 31 | id: string; 32 | name: string; 33 | type: ConsoleFilterType; 34 | value: string; 35 | enabled: boolean; 36 | } 37 | -------------------------------------------------------------------------------- /src/components/widgets/jobs/JobsCard.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 36 | -------------------------------------------------------------------------------- /src/components/common/KlippyStatusCard.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Fluidd 2 | 3 | Fluidd exists as an independent client of Moonraker, and by extension - Klipper. 4 | Fluidd is built on VueJS, using TypeScript. 5 | 6 | - Source should always pass the linting rules defined, with no warnings or type errors. 7 | - A clean develop is preferred. This means squashing, and rebasing your feature branches prior to merge. 8 | - PR's should off a branch other than develop or master. 9 | - Commit messages should follow the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) standard, and should have a Signed-off-by line, for example; 10 | 11 | ```sh 12 | feat: My feature. 13 | 14 | Some description. 15 | 16 | Signed-off-by: Your Name 17 | ``` 18 | 19 | - By signing off on commits, you acknowledge that you agree to the [developer certificate of origin](/developer-certificate-of-origin). 20 | This must contain your real name and a current email address. 21 | -------------------------------------------------------------------------------- /src/store/cameras/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { CamerasState } from './types' 6 | import { RootState } from '../types' 7 | import { v4 as uuidv4 } from 'uuid' 8 | 9 | export const defaultState = (): CamerasState => { 10 | return { 11 | activeCamera: 'all', 12 | cameras: [ 13 | { 14 | id: uuidv4(), 15 | enabled: false, 16 | name: 'Default', 17 | type: 'mjpgadaptive', 18 | fpstarget: 15, 19 | url: '/webcam/?action=stream', 20 | flipX: false, 21 | flipY: false, 22 | height: 720 23 | } 24 | ] 25 | } 26 | } 27 | 28 | export const state = defaultState() 29 | 30 | const namespaced = true 31 | 32 | export const cameras: Module = { 33 | namespaced, 34 | state, 35 | getters, 36 | actions, 37 | mutations 38 | } 39 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | '@vue/standard', 9 | '@vue/typescript/recommended' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2020 13 | }, 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 17 | camelcase: 0, 18 | '@typescript-eslint/camelcase': 0, 19 | '@typescript-eslint/no-explicit-any': 0 20 | }, 21 | overrides: [ 22 | { 23 | files: [ 24 | '**/__tests__/*.{j,t}s?(x)', 25 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 26 | ], 27 | env: { 28 | mocha: true 29 | } 30 | }, 31 | { 32 | files: [ 33 | '**/__tests__/*.{j,t}s?(x)', 34 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 35 | ], 36 | env: { 37 | jest: true 38 | } 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /src/store/gcodePreview/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { GcodePreviewState } from './types' 6 | import { RootState } from '../types' 7 | 8 | /** 9 | * Maintains the state of the console 10 | */ 11 | export const defaultState = (): GcodePreviewState => { 12 | return { 13 | moves: [], 14 | file: undefined, 15 | parserProgress: 0, 16 | parserWorker: null, 17 | 18 | viewer: { 19 | showNextLayer: false, 20 | showPreviousLayer: false, 21 | showMoves: true, 22 | showExtrusions: true, 23 | showRetractions: true, 24 | followProgress: false 25 | } 26 | } 27 | } 28 | 29 | export const state = defaultState() 30 | 31 | const namespaced = true 32 | 33 | export const gcodePreview: Module = { 34 | namespaced, 35 | state, 36 | getters, 37 | actions, 38 | mutations 39 | } 40 | -------------------------------------------------------------------------------- /src/views/Icons.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 41 | -------------------------------------------------------------------------------- /src/plugins/vuetify.ts: -------------------------------------------------------------------------------- 1 | import { Icons } from '@/globals' 2 | import Vue from 'vue' 3 | import Vuetify from 'vuetify' 4 | import colors from 'vuetify/es5/util/colors' 5 | 6 | Vue.use(Vuetify) 7 | 8 | export default new Vuetify({ 9 | breakpoint: { 10 | mobileBreakpoint: 'xs' 11 | }, 12 | icons: { 13 | iconfont: 'mdiSvg', 14 | values: Icons 15 | }, 16 | theme: { 17 | dark: true, 18 | options: { 19 | customProperties: true 20 | }, 21 | themes: { 22 | dark: { 23 | primary: colors.blue.base, 24 | secondary: '#888888', // colors.grey.darken1, 25 | 'card-heading': '#333337', 26 | btncolor: '#4A4A4F', 27 | drawer: '#28282B', 28 | appbar: '#1E1E20' 29 | }, 30 | light: { 31 | primary: colors.blue.darken2, 32 | secondary: colors.grey.lighten1, 33 | 'card-heading': '#E9E9E9', 34 | btncolor: '#E9E9E9', 35 | drawer: '#F4F4F4', 36 | appbar: '#FFFFFF' 37 | } 38 | } 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /src/store/socket/mutations.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree } from 'vuex' 2 | import { SocketState } from './types' 3 | import { defaultState } from './index' 4 | 5 | export const mutations: MutationTree = { 6 | /** 7 | * Reset state 8 | */ 9 | setReset (state) { 10 | Object.assign(state, defaultState()) 11 | }, 12 | 13 | setSocketOpen (state, payload) { 14 | if (state.open !== payload) state.open = payload 15 | if (state.disconnecting) state.disconnecting = false 16 | }, 17 | 18 | setSocketConnecting (state, payload) { 19 | if (state.connecting !== payload) state.connecting = payload 20 | }, 21 | 22 | setSocketReadyState (state, payload) { 23 | state.ready = payload 24 | }, 25 | 26 | setAcceptNotifications (state, payload) { 27 | state.acceptingNotifications = payload 28 | }, 29 | 30 | setSocketDisconnecting (state, payload) { 31 | state.disconnecting = payload 32 | }, 33 | 34 | setApiConnected (state, payload) { 35 | state.apiConnected = payload 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /docs/features/print_history.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Print History 4 | parent: Features 5 | nav_order: 12 6 | permalink: /features/print_history 7 | --- 8 | 9 | # Print History 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd supports Moonrakers Print History component. This allows you to 15 | view print history, but more importantly - lets you sort your jobs by last 16 | print date, filter jobs you've already printed and view printer statistics 17 | over time - like total print time, total filament used. 18 | 19 | Secondarily, being able to re-print failed or cancelled jobs also comes with 20 | this feature. 21 | 22 | For the moment, Fluidd loads the last 50 prints from history. The limit is 23 | 10,000 history items - however, loading this up front may cause a performance 24 | hit and is likely unecessary. 25 | 26 | Feedback is welcome on the current state of this feature! 27 | 28 | ![screenshot](/assets/images/print_history.png) 29 | ![screenshot](/assets/images/print_stats.png) 30 | ![screenshot](/assets/images/reprint.png) 31 | -------------------------------------------------------------------------------- /src/components/widgets/gcode-preview/GcodePreviewControlCheckbox.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 44 | -------------------------------------------------------------------------------- /src/components/widgets/toolhead/ExtruderSelection.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 38 | -------------------------------------------------------------------------------- /src/store/notifications/types.ts: -------------------------------------------------------------------------------- 1 | import { AppNotificationType } from '@/store/types' 2 | 3 | export interface NotificationsState { 4 | notifications: AppNotification[]; 5 | } 6 | 7 | export interface AppNotification { 8 | id: string; 9 | type: AppNotificationType; 10 | title: string; 11 | description?: string; 12 | suffix?: string; 13 | suffixIcon?: string; 14 | to?: string; 15 | btnText?: string; 16 | icon?: string; 17 | timestamp: number; 18 | clear: boolean; // user can clear the notification. 19 | merge: boolean; // try to merge into an existing notification. 20 | noCount?: boolean; // if true, don't increment the count. 21 | } 22 | 23 | export interface AppPushNotification { 24 | id?: string; 25 | type: AppNotificationType; 26 | title: string; 27 | description?: string; 28 | suffix?: string; 29 | suffixIcon?: string; 30 | to?: string; 31 | btnText?: string; 32 | icon?: string; 33 | timestamp?: number; 34 | snackbar?: boolean; 35 | clear?: boolean; 36 | merge?: boolean; 37 | noCount?: boolean; 38 | } 39 | -------------------------------------------------------------------------------- /src/util/format-as-file.ts: -------------------------------------------------------------------------------- 1 | import getFilePaths from './get-file-paths' 2 | import { FileChangeItem, KlipperFile, KlipperFileWithMeta, AppFile, AppFileWithMeta } from '@/store/files/types' 3 | 4 | /** 5 | * Takes file change item and formats to represent an app file. 6 | */ 7 | export default (root: string, file: FileChangeItem | KlipperFile | KlipperFileWithMeta): AppFile | AppFileWithMeta => { 8 | // A FileChangeItem 9 | if ('path' in file) { 10 | const paths = getFilePaths(file.path, root) 11 | return { 12 | type: 'file', 13 | filename: paths.filename, 14 | extension: paths.filename.split('.').pop() || '', 15 | name: paths.filename, 16 | path: paths.path, 17 | size: file.size, 18 | modified: file.modified 19 | } 20 | } 21 | 22 | const paths = getFilePaths(file.filename, root) 23 | return { 24 | ...file, 25 | type: 'file', 26 | filename: paths.filename, 27 | extension: paths.filename.split('.').pop() || '', 28 | name: paths.filename, 29 | path: paths.path 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "resolveJsonModule": true, 10 | "experimentalDecorators": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "sourceMap": true, 15 | "baseUrl": ".", 16 | "types": [ 17 | "webpack-env", 18 | "jest", 19 | "vuetify", 20 | "vue-meta" 21 | ], 22 | "paths": { 23 | "@/*": [ 24 | "src/*" 25 | ] 26 | }, 27 | "lib": [ 28 | "esnext", 29 | "dom", 30 | "dom.iterable", 31 | "scripthost" 32 | ] 33 | }, 34 | "include": [ 35 | "src/**/*.ts", 36 | "src/**/*.tsx", 37 | "src/**/*.vue", 38 | "src/**/*.yaml", 39 | "src/**/*.yml", 40 | "tests/**/*.ts", 41 | "tests/**/*.tsx", 42 | "types.d.ts" 43 | ], 44 | "exclude": [ 45 | "node_modules" 46 | ] 47 | } -------------------------------------------------------------------------------- /src/components/common/SystemLayout.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | -------------------------------------------------------------------------------- /src/components/widgets/camera/CameraDialog.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 32 | 33 | 44 | -------------------------------------------------------------------------------- /src/store/wait/getters.ts: -------------------------------------------------------------------------------- 1 | import { GetterTree } from 'vuex' 2 | import { WaitState } from './types' 3 | import { RootState } from '../types' 4 | 5 | export const getters: GetterTree = { 6 | /** 7 | * Determine if we have a specific wait, or list of waits active or not. 8 | */ 9 | hasWait: (state) => (wait: string | string[]): boolean => { 10 | if (Array.isArray(wait) && wait.length) { 11 | let waits = wait as string[] 12 | waits = waits.filter(val => state.waits.includes(val)) 13 | return (waits.length > 0) 14 | } else { 15 | wait = wait as string 16 | return state.waits.includes(wait) 17 | } 18 | }, 19 | 20 | /** 21 | * Determine if we have any waits. 22 | */ 23 | hasWaits: (state) => { 24 | return state.waits.length > 0 25 | }, 26 | 27 | /** 28 | * Determine if we have any waits prefixed with... 29 | */ 30 | hasWaitsBy: (state) => (prefix: string) => { 31 | const waits = state.waits.filter(wait => wait.startsWith(prefix)) 32 | return waits.length > 0 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/logo_zerog.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /src/store/history/types.ts: -------------------------------------------------------------------------------- 1 | import { KlipperFileMeta } from '@/store/files/types' 2 | 3 | export interface HistoryState { 4 | count: number; 5 | jobs: HistoryItem[]; 6 | job_totals: HistoryRollUp; 7 | } 8 | 9 | export interface HistoryItem { 10 | job_id: string; 11 | exists: boolean; 12 | end_time: string | null; 13 | filament_used: number; 14 | filename: string; 15 | metadata?: KlipperFileMeta; 16 | print_duration: number; 17 | status: HistoryItemStatus; 18 | start_time: number; 19 | total_duration: number; 20 | path?: string; 21 | } 22 | 23 | export interface HistoryRollUp { 24 | total_jobs: number; 25 | total_time: number; 26 | total_print_time: number; 27 | total_filament_used: number; 28 | longest_job: number; 29 | longest_print: number; 30 | } 31 | 32 | export enum HistoryItemStatus { 33 | Completed = 'completed', 34 | Cancelled = 'cancelled', 35 | Error = 'error', 36 | Printing = 'printing', 37 | InProgress = 'in_progress', 38 | Server_Exit = 'server_exit', 39 | Klippy_Shutdown = 'klippy_shutdown', 40 | Klippy_Disconnect = 'klippy_disconnect' 41 | } 42 | -------------------------------------------------------------------------------- /docs/configuration/moonraker_conf.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: moonraker.conf 4 | parent: Configuration 5 | nav_order: 5 6 | permalink: /configuration/moonraker_conf 7 | --- 8 | 9 | # moonraker.conf 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | This is an example configuration, which should apply to most users. 15 | Your moonraker configuration can usually be found here: `~/klipper_config/moonraker.conf` 16 | 17 | ```yaml 18 | [server] 19 | host: 0.0.0.0 20 | port: 7125 21 | enable_debug_logging: False 22 | config_path: ~/klipper_config 23 | temperature_store_size: 600 24 | gcode_store_size: 1000 25 | log_path: ~/klipper_logs 26 | 27 | [authorization] 28 | force_logins: true 29 | 30 | cors_domains: 31 | *.local 32 | *.lan 33 | *://app.fluidd.xyz 34 | 35 | trusted_clients: 36 | 10.0.0.0/8 37 | 127.0.0.0/8 38 | 169.254.0.0/16 39 | 172.16.0.0/12 40 | 192.168.0.0/16 41 | FE80::/10 42 | ::1/128 43 | 44 | [history] 45 | 46 | [octoprint_compat] 47 | 48 | [update_manager] 49 | enable_auto_refresh: True 50 | 51 | [update_manager client fluidd] 52 | type: web 53 | repo: cadriel/fluidd 54 | path: ~/fluidd 55 | ``` 56 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: BUILD 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | - master 8 | pull_request: 9 | branches: 10 | - develop 11 | - master 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-20.04 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | 20 | - name: Install node 21 | uses: actions/setup-node@v2 22 | with: 23 | node-version: '16' 24 | 25 | - name: Install deps 26 | run: npm install 27 | 28 | - name: Run tests 29 | run: npm run test:unit 30 | 31 | - name: Build 32 | run: npm run build 33 | 34 | - name: Prepare Deploy 35 | if: github.ref == 'refs/heads/develop' 36 | run: | 37 | cp ./server/config.json ./dist/config.json 38 | 39 | - name: Deploy to Host 40 | if: github.ref == 'refs/heads/develop' 41 | env: 42 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 43 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 44 | run: | 45 | npm run deploy:host:dev 46 | -------------------------------------------------------------------------------- /server/nginx-site.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | listen [::]:80 default_server; 4 | 5 | access_log /var/log/nginx/fluidd-access.log; 6 | error_log /var/log/nginx/fluidd-error.log; 7 | 8 | # disable this section on smaller hardware like a pi zero 9 | gzip on; 10 | gzip_vary on; 11 | gzip_proxied any; 12 | gzip_proxied expired no-cache no-store private auth; 13 | gzip_comp_level 4; 14 | gzip_buffers 16 8k; 15 | gzip_http_version 1.1; 16 | gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/json application/xml; 17 | 18 | # web_path from fluidd static files 19 | root /usr/share/nginx/html; 20 | 21 | index index.html; 22 | server_name _; 23 | 24 | # disable max upload size checks 25 | client_max_body_size 0; 26 | 27 | # disable proxy request buffering 28 | proxy_request_buffering off; 29 | 30 | location / { 31 | try_files $uri $uri/ /index.html; 32 | } 33 | 34 | location = /index.html { 35 | add_header Cache-Control "no-store, no-cache, must-revalidate"; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /docs/features/slicer-uploads.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Slicer Uploads 4 | parent: Features 5 | nav_order: 2 6 | permalink: /features/slicer-uploads 7 | --- 8 | 9 | # Slicer Uploads 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | NOTE: Moonraker needs to be configured to support slicer uploads. Simply add 15 | the `[octoprint_compat]` option to your moonraker config. See moonraker 16 | [configuration docs](/configuration/moonraker_conf) for an example. 17 | 18 | You can upload your sliced `gcode` from within PrusaSlicer or SuperSlicer. 19 | 20 | - With PrusaSlicer / SuperSlicer open, click the "cog" icon right of the Printer profiles combo box and select `Add physical printer` 21 | - Type a descriptive printer name 22 | - choose proper printer preset 23 | - Ensure the type is set to `OctoPrint` 24 | - The `hostname, IP or URL` is your printer URL. Typically this would be `fluidd.local` or similar (you may also need to add moonraker port, ie 7125 in some cases.) 25 | - Enter some random characters in the API field. 26 | - Click test! 27 | 28 | ![screenshot](/assets/images/physical-printer.png) 29 | ![screenshot](/assets/images/slicer-upload.png) 30 | -------------------------------------------------------------------------------- /public/color_picker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/scss/animation.scss: -------------------------------------------------------------------------------- 1 | /* Add a class to help us spin an icon if we need.. */ 2 | .spin { 3 | animation-name: spin; 4 | animation-duration: 1000ms; 5 | animation-iteration-count: infinite; 6 | animation-timing-function: linear; 7 | } 8 | 9 | .spin-alt { 10 | animation-name: spin-alt; 11 | animation-duration: 1000ms; 12 | animation-iteration-count: infinite; 13 | animation-timing-function: linear; 14 | } 15 | 16 | @keyframes spin { 17 | from { 18 | transform: rotate(0deg); 19 | } to { 20 | transform: rotate(-360deg); 21 | } 22 | } 23 | 24 | @keyframes spin-alt { 25 | from { 26 | transform: rotate(-360deg); 27 | } to { 28 | transform: rotate(0deg); 29 | } 30 | } 31 | 32 | .wiggle { 33 | animation-name: wiggle; 34 | animation-duration: 5000ms; 35 | animation-iteration-count: infinite; 36 | animation-timing-function: linear; 37 | } 38 | 39 | /* Notification wiggle */ 40 | @keyframes wiggle { 41 | 0% { transform: rotate(0deg); } 42 | 2% { transform: rotate(-15deg); } 43 | 4% { transform: rotate(15deg); } 44 | 6% { transform: rotate(-15deg); } 45 | 8% { transform: rotate(0deg); } 46 | 100% { transform: rotate(0deg); } 47 | } 48 | -------------------------------------------------------------------------------- /src/store/auth/mutations.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree } from 'vuex' 2 | import { defaultState } from './' 3 | import { AuthState } from './types' 4 | import jwtDecode from 'jwt-decode' 5 | 6 | export const mutations: MutationTree = { 7 | /** 8 | * Reset state 9 | */ 10 | setReset (state) { 11 | Object.assign(state, defaultState()) 12 | }, 13 | 14 | setCurrentUser (state, user) { 15 | state.currentUser = user 16 | }, 17 | 18 | setToken (state, token) { 19 | state.token = (token) ? jwtDecode(token) : null 20 | }, 21 | 22 | setRefreshToken (state, token) { 23 | state.refresh_token = (token) ? jwtDecode(token) : null 24 | }, 25 | 26 | setAuthenticated (state, authenticated) { 27 | state.authenticated = authenticated 28 | }, 29 | 30 | setUsers (state, users) { 31 | state.users = users 32 | }, 33 | 34 | setAddUser (state, user) { 35 | state.users.push(user) 36 | }, 37 | 38 | setRemoveUser (state, user) { 39 | const i = state.users.findIndex(u => u.username === user.username) 40 | if (i >= 0) state.users.splice(i, 1) 41 | }, 42 | 43 | setApiKey (state, key) { 44 | state.apiKey = key 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/store/gcodePreview/mutations.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree } from 'vuex' 2 | import { defaultState } from './' 3 | import { GcodePreviewState } from './types' 4 | import Vue from 'vue' 5 | import { AppFile } from '@/store/files/types' 6 | import { Thread } from 'threads' 7 | 8 | export const mutations: MutationTree = { 9 | /** 10 | * Reset state 11 | */ 12 | setReset (state) { 13 | Object.assign(state, defaultState()) 14 | }, 15 | 16 | setMoves (state, payload) { 17 | Vue.set(state, 'moves', Object.freeze(payload.map(Object.freeze))) 18 | }, 19 | 20 | setFile (state, file: AppFile) { 21 | state.file = file 22 | }, 23 | 24 | clearFile (state) { 25 | state.file = undefined 26 | }, 27 | 28 | setViewerState (state, payload: any) { 29 | for (const key of Object.keys(state.viewer)) { 30 | if (payload[key] !== undefined) { 31 | Vue.set(state.viewer, key, payload[key]) 32 | } 33 | } 34 | }, 35 | 36 | setParserProgress (state, payload: number) { 37 | state.parserProgress = payload 38 | }, 39 | 40 | setParserWorker (state, payload: Thread) { 41 | state.parserWorker = payload 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/widgets/status/StatusLabel.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 26 | 27 | 44 | -------------------------------------------------------------------------------- /src/store/power/actions.ts: -------------------------------------------------------------------------------- 1 | import { ActionTree } from 'vuex' 2 | import { DevicePowerState } from './types' 3 | import { RootState } from '../types' 4 | import { SocketActions } from '@/api/socketActions' 5 | 6 | export const actions: ActionTree = { 7 | /** 8 | * Reset our store 9 | */ 10 | async reset ({ commit }) { 11 | commit('setReset') 12 | }, 13 | 14 | /** 15 | * Make a socket request to init the moonraker power component. 16 | */ 17 | async init () { 18 | SocketActions.machineDevicePowerDevices() 19 | }, 20 | 21 | /** 22 | * Inits the list of available devices. Notified by init action. 23 | */ 24 | async onInit ({ commit }, payload) { 25 | if ( 26 | payload.devices && 27 | payload.devices.length > 0 28 | ) { 29 | commit('setDevices', payload) 30 | } 31 | }, 32 | 33 | /** 34 | * Fires when we receive a notification of power changing 35 | */ 36 | async onStatus ({ commit }, payload) { 37 | commit('setStatus', payload) 38 | }, 39 | 40 | /** 41 | * On toggling a power device. 42 | */ 43 | async onToggle ({ commit }, payload) { 44 | commit('setStatus', payload) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/scss/cards.scss: -------------------------------------------------------------------------------- 1 | .v-card.collapsable-card .v-card__title.collapsable-card-title { 2 | position: relative; 3 | z-index: 2; 4 | font-size: 1rem; 5 | min-height: 36px; 6 | padding-top: 0; 7 | padding-bottom: 0; 8 | @media #{map-get($display-breakpoints, 'sm-and-up')} { 9 | font-size: 1.125rem; 10 | min-height: 42px; 11 | } 12 | } 13 | 14 | .v-card.collapsable-card:not(.collapsed) .v-card__title.collapsable-card-title { 15 | box-shadow: 16 | 0px 2px 4px -1px rgb(0 0 0 / 20%), 17 | 0px 4px 5px 0px rgb(0 0 0 / 14%), 18 | 0px 1px 10px 0px rgb(0 0 0 / 12%); 19 | } 20 | 21 | // Tabs in the title should be aligned to the left of the title. 22 | .v-card.collapsable-card .v-card__title.collapsable-card-title .v-tabs { 23 | margin-left: -16px; 24 | 25 | .v-tabs-bar__content > .v-tab:first-child:before { 26 | border-top-left-radius: $border-radius-root; 27 | } 28 | } 29 | 30 | .theme--dark .v-card .v-card__title.collapsable-card-title { 31 | border-bottom: thin solid rgba(map-get($shades, 'white'), 0.10) !important; 32 | } 33 | 34 | .theme--light .v-card .v-card__title.collapsable-card-title { 35 | border-bottom: thin solid rgba(map-get($shades, 'black'), 0.10) !important; 36 | } 37 | -------------------------------------------------------------------------------- /src/views/History.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 39 | -------------------------------------------------------------------------------- /src/components/widgets/filesystem/FileSystemDragOverlay.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 35 | 36 | 45 | -------------------------------------------------------------------------------- /src/mixins/toolhead.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { Component } from 'vue-property-decorator' 3 | 4 | @Component 5 | export default class ToolheadMixin extends Vue { 6 | get activeExtruder () { 7 | return this.$store.getters['printer/getActiveExtruder'] 8 | } 9 | 10 | get extruderReady () { 11 | const extruder = this.$store.getters['printer/getActiveExtruder'] 12 | return (extruder && extruder.temperature >= 0 && extruder.min_extrude_temp >= 0) 13 | ? (extruder.temperature >= extruder.min_extrude_temp) 14 | : false 15 | } 16 | 17 | get allHomed (): boolean { 18 | return this.$store.getters['printer/getHomedAxes']('xyz') 19 | } 20 | 21 | get xyHomed (): boolean { 22 | return this.$store.getters['printer/getHomedAxes']('xy') 23 | } 24 | 25 | get xHomed (): boolean { 26 | return this.$store.getters['printer/getHomedAxes']('x') 27 | } 28 | 29 | get yHomed (): boolean { 30 | return this.$store.getters['printer/getHomedAxes']('y') 31 | } 32 | 33 | get zHomed (): boolean { 34 | return this.$store.getters['printer/getHomedAxes']('z') 35 | } 36 | 37 | get hasHomingOverride (): boolean { 38 | return this.$store.getters['printer/getHasHomingOverride'] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/store/socket/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { SocketState } from './types' 6 | import { RootState } from '../types' 7 | 8 | /** 9 | * Maintains the state of the socket(s). 10 | */ 11 | export const defaultState = (): SocketState => { 12 | return { 13 | apiConnected: true, // api is connected, socket may not be. 14 | open: false, // socket is open or closed. 15 | connecting: false, // socket is trying to connect. 16 | disconnecting: false, // indicates we know a disconnect is coming, and to retry. 17 | ready: false, // indicates the socket is ready (and has first dump of data...) 18 | acceptingNotifications: false, // indicates we're accepting notification data because we've finished subscribing to objects 19 | error: null // if the socket has an error or not 20 | } 21 | } 22 | 23 | export const state = defaultState() 24 | 25 | const namespaced = true 26 | 27 | export const socket: Module = { 28 | namespaced, 29 | state, 30 | getters, 31 | actions, 32 | mutations 33 | } 34 | -------------------------------------------------------------------------------- /src/store/notifications/mutations.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { MutationTree } from 'vuex' 3 | import { defaultState } from './' 4 | import { NotificationsState, AppNotification } from './types' 5 | 6 | export const mutations: MutationTree = { 7 | /** 8 | * Reset state 9 | */ 10 | setReset (state) { 11 | Object.assign(state, defaultState()) 12 | }, 13 | 14 | setPushNotification (state, n: AppNotification) { 15 | state.notifications.push(n) 16 | }, 17 | 18 | setMergeNotification (state, options: { n: AppNotification; i: number }) { 19 | if (options && options.n && options.i >= 0) { 20 | Vue.set(state.notifications, options.i, options.n) 21 | } 22 | }, 23 | 24 | setClearNotification (state, notification: AppNotification | string) { 25 | let i = -1 26 | if (typeof notification === 'string') { 27 | i = state.notifications.findIndex(n => n.id === notification) 28 | } else { 29 | i = state.notifications.findIndex(n => n === notification) 30 | } 31 | if (i >= 0) { 32 | state.notifications.splice(i, 1) 33 | } 34 | }, 35 | 36 | setClearAllNotifications (state) { 37 | Vue.set(state, 'notifications', [...state.notifications.filter(n => !n.clear)]) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/configuration/fluidd.xyz.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: fluidd.xyz 4 | parent: Configuration 5 | nav_order: 4 6 | permalink: /configuration/fluidd_xyz 7 | --- 8 | 9 | # fluidd.xyz 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Have Mainsail installed, or don't want to install Fluidd locally? We support that! 15 | 16 | Fluidd is also hosted at `http://app.fluidd.xyz`. When used in this way, 17 | Fluidd is downloaded to your browser. 18 | 19 | It has no interaction outside of your network unless configured to do so, and 20 | essentially works in the same way as hosting Fluidd yourself. 21 | 22 | FluiddPi comes OOB with support for this configuration built in. 23 | 24 | If you've installed in some other way, then in order for Fluidd to connect to 25 | your printer, you'll need to configure Moonraker. 26 | 27 | In the `moonraker.conf` file is a section called `cors_domains:`. 28 | The fluidd.xyz host must be in this section for a successful connection to be 29 | made. 30 | 31 | Generally, you can find the moonraker.conf file here 32 | `~/klipper_configuration/moonraker.conf` for FluiddPi and Mainsail installs. 33 | 34 | Alternatively, you can edit the file via the file browser in Fluidd. 35 | 36 | A suitable example can be found [here](/configuration/moonraker_conf). 37 | -------------------------------------------------------------------------------- /src/components/ui/AppSwitch.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 55 | -------------------------------------------------------------------------------- /src/store/server/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { ServerState } from './types' 6 | import { RootState } from '../types' 7 | 8 | export const defaultState = (): ServerState => { 9 | return { 10 | klippy_retries: 0, // how many times have we attempted to reconnect to klippy. 11 | info: { 12 | failed_components: [], 13 | klippy_connected: false, // indicates if klippy is disconnected vs shutdown. 14 | klippy_state: '', 15 | components: [], 16 | registered_directories: [], 17 | warnings: [] 18 | }, 19 | system_info: null, 20 | config: { 21 | authorization: { 22 | enabled: true 23 | }, 24 | server: { 25 | gcode_store_size: 1000, 26 | temperature_store_size: 1200 27 | } 28 | }, 29 | moonraker_stats: [], 30 | throttled_state: { 31 | bits: 0, 32 | flags: [] 33 | }, 34 | cpu_temp: null 35 | } 36 | } 37 | 38 | export const state = defaultState() 39 | 40 | const namespaced = true 41 | 42 | export const server: Module = { 43 | namespaced, 44 | state, 45 | getters, 46 | actions, 47 | mutations 48 | } 49 | -------------------------------------------------------------------------------- /src/store/notifications/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { getters } from './getters' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { NotificationsState } from './types' 6 | import { RootState } from '../types' 7 | 8 | /** 9 | * Maintains the state of the console 10 | */ 11 | export const defaultState = (): NotificationsState => { 12 | return { 13 | notifications: [] 14 | 15 | // status: 16 | // printer.mcu.last_stats 17 | // printer.system_stats 18 | 19 | // notifications: 20 | // notify_cpu_throttled (to detect a throttle condition like under volt etc) 21 | 22 | // actions: 23 | // machine.proc.stats 24 | 25 | // cpu overtemp 26 | // mcu overtemp 27 | 28 | // mcu awake time graphing 29 | 30 | // If it was me, I'd probably just start with displaying everything on a single graph with 100% being one core usage. So, just graph sysload outright (sysload * 100), cputime change ((cputime - last_cputime) * 100), mcu_awake (formula above), mcu load (formula above). 31 | 32 | } 33 | } 34 | 35 | export const state = defaultState() 36 | 37 | const namespaced = true 38 | 39 | export const notifications: Module = { 40 | namespaced, 41 | state, 42 | getters, 43 | actions, 44 | mutations 45 | } 46 | -------------------------------------------------------------------------------- /src/components/common/FlashMessage.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 52 | 53 | 59 | -------------------------------------------------------------------------------- /src/components/ui/AppBtnCollapse.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 46 | 47 | 52 | -------------------------------------------------------------------------------- /src/components/ui/AppInlineHelp.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 51 | -------------------------------------------------------------------------------- /src/components/widgets/outputs/OutputItem.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 54 | -------------------------------------------------------------------------------- /src/store/cameras/actions.ts: -------------------------------------------------------------------------------- 1 | import { ActionTree } from 'vuex' 2 | import { CamerasState, CameraConfig } from './types' 3 | import { RootState } from '../types' 4 | import { SocketActions } from '@/api/socketActions' 5 | 6 | export const actions: ActionTree = { 7 | /** 8 | * Reset our store 9 | */ 10 | async reset ({ commit }) { 11 | commit('setReset') 12 | }, 13 | 14 | /** 15 | * Init any file configs we may have. 16 | */ 17 | async initCameras ({ commit }, payload: { cameras: CameraConfig[] }) { 18 | commit('setInitCameras', payload) 19 | }, 20 | 21 | /** 22 | * Add or update a given camera 23 | */ 24 | async updateCamera ({ commit, state }, payload) { 25 | commit('setCamera', payload) 26 | SocketActions.serverWrite('cameras.cameras', state.cameras) 27 | }, 28 | 29 | /** 30 | * Remove a camera 31 | */ 32 | async removeCamera ({ commit, state }, payload) { 33 | commit('setRemoveCamera', payload) 34 | SocketActions.serverWrite('cameras.cameras', state.cameras) 35 | SocketActions.serverWrite('cameras.activeCamera', state.activeCamera) 36 | }, 37 | 38 | /** 39 | * Sets active camera 40 | */ 41 | async updateActiveCamera ({ commit }, payload) { 42 | commit('setActiveCamera', payload) 43 | SocketActions.serverWrite('cameras.activeCamera', payload) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/ui/AppBtn.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 52 | -------------------------------------------------------------------------------- /public/logo_ratrig.svg: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/store/types.ts: -------------------------------------------------------------------------------- 1 | import { SocketState } from './socket/types' 2 | import { ServerState } from './server/types' 3 | import { PrinterState } from './printer/types' 4 | import { ConfigState } from './config/types' 5 | import { FilesState } from './files/types' 6 | import { ChartState } from './charts/types' 7 | import { ConsoleState } from './console/types' 8 | import { MacrosState } from './macros/types' 9 | import { DevicePowerState } from './power/types' 10 | import { HistoryState } from './history/types' 11 | import { VersionState } from './version/types' 12 | import { GcodePreviewState } from './gcodePreview/types' 13 | import { LayoutState } from './layout/types' 14 | import { MeshState } from './mesh/types' 15 | import { NotificationsState } from './notifications/types' 16 | import { AuthState } from './auth/types' 17 | 18 | export interface RootState { 19 | socket?: SocketState; 20 | auth?: AuthState; 21 | server?: ServerState; 22 | printer?: PrinterState; 23 | config?: ConfigState; 24 | layout?: LayoutState; 25 | mesh?: MeshState; 26 | files?: FilesState; 27 | charts?: ChartState; 28 | console?: ConsoleState; 29 | macros?: MacrosState; 30 | power?: DevicePowerState; 31 | history?: HistoryState; 32 | version?: VersionState; 33 | gcodePreview?: GcodePreviewState; 34 | notifications?: NotificationsState; 35 | } 36 | 37 | export type AppNotificationType = 'success' | 'info' | 'warning' | 'error' 38 | -------------------------------------------------------------------------------- /src/scss/misc.scss: -------------------------------------------------------------------------------- 1 | /* Force a constrained width for our pages.. */ 2 | .container--fluid.constrained-width { 3 | max-width: map-get($container-max-widths, 'xl'); 4 | } 5 | 6 | .fluidd.v-application blockquote { 7 | font-family: monospace, monospace 8 | // background-color: transparent; 9 | // color: #C0341D; 10 | // padding: 0 0.4rem; 11 | } 12 | 13 | /* force no pointer events to avoid issues with dragging */ 14 | .no-pointer-events * { 15 | pointer-events: none !important; 16 | } 17 | 18 | // Apply a common scrollbar application to all elements inside the app 19 | .v-application--wrap ::-webkit-scrollbar { 20 | transition: all .5s; 21 | width: 5px; 22 | height: 8px; 23 | z-index: 10; 24 | } 25 | 26 | .v-application--wrap ::-webkit-scrollbar-track { 27 | background: transparent; 28 | } 29 | 30 | .v-application--wrap ::-webkit-scrollbar-thumb { 31 | background: #b3ada7; 32 | } 33 | 34 | .v-application--wrap ::-webkit-scrollbar-corner { 35 | background: transparent; 36 | } 37 | 38 | // Ensure the number arrows don't appear on text inputs. 39 | /* Chrome, Safari, Edge, Opera */ 40 | input::-webkit-outer-spin-button, 41 | input::-webkit-inner-spin-button { 42 | -webkit-appearance: none; 43 | margin: 0; 44 | } 45 | 46 | /* Firefox */ 47 | input[type=number] { 48 | -moz-appearance: textfield; 49 | } 50 | 51 | // Disable double-tap zoom effects on buttons. 52 | .v-btn { 53 | touch-action: manipulation; 54 | } 55 | -------------------------------------------------------------------------------- /docs/development/localization.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Localization 4 | parent: Development 5 | nav_order: 1 6 | permalink: /development/localization 7 | --- 8 | 9 | # Localization 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd uses [vue-i18n](https://kazupon.github.io/vue-i18n/) for its localization. 15 | 16 | Locales can be found in the `src/locales` folder and are in YAML format. 17 | 18 | ## How to contribute 19 | 20 | You can contribute in a couple of different ways; 21 | 22 | 1. Edit translations using an existing tool. For example, [BabelEdit](https://www.codeandweb.com/babeledit) 23 | 24 | 2. Edit translations using VSCode, and i18n Ally. 25 | 26 | Once you have a translation in hand, you can either PR the code changes directly 27 | or create an issue with the translations attached. 28 | 29 | ## Tooling 30 | 31 | ### vue-i18n-extract 32 | 33 | Fluidd ships with [vue-i18n-extract](https://github.com/kazupon/vue-i18n-extensions) as a dev dependency, and has an npm script 34 | pre-defined. 35 | 36 | ```bash 37 | npm run i18n-extract 38 | ``` 39 | 40 | Running the above will output a list of missing translations, and un-used keys 41 | should there be any. 42 | 43 | ### i18n Ally 44 | 45 | [i18n Ally](https://marketplace.visualstudio.com/items?itemName=antfu.i18n-ally) is a VSCode extension, giving you inline detail about translations. 46 | If you're setup with VSCode, then this extension comes highly recommended. 47 | -------------------------------------------------------------------------------- /src/store/cameras/mutations.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { MutationTree } from 'vuex' 3 | import { CamerasState } from './types' 4 | import { defaultState } from './index' 5 | import { v4 as uuidv4 } from 'uuid' 6 | 7 | export const mutations: MutationTree = { 8 | /** 9 | * Reset state 10 | */ 11 | setReset (state) { 12 | Object.assign(state, defaultState()) 13 | }, 14 | 15 | /** 16 | * Inits UI settings from the db 17 | */ 18 | setInitCameras (state, payload) { 19 | if (payload) { 20 | if (payload) Object.assign(state, payload) 21 | } 22 | }, 23 | 24 | /** 25 | * Update / Add a temperature preset 26 | */ 27 | setCamera (state, payload) { 28 | if (payload.id === -1) { 29 | payload.id = uuidv4() 30 | state.cameras.push(payload) 31 | } else { 32 | const i = state.cameras.findIndex(camera => camera.id === payload.id) 33 | if (i >= 0) { 34 | Vue.set(state.cameras, i, payload) 35 | } 36 | } 37 | }, 38 | 39 | /** 40 | * Remove a camera 41 | */ 42 | setRemoveCamera (state, payload) { 43 | const i = state.cameras.findIndex(camera => camera.id === payload.id) 44 | state.cameras.splice(i, 1) 45 | if (state.activeCamera === payload.id) state.activeCamera = 'all' 46 | }, 47 | 48 | /** 49 | * Sets active camera 50 | */ 51 | setActiveCamera (state, payload) { 52 | state.activeCamera = payload 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/components/ui/AppBtnCollapseGroup.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 53 | 54 | 59 | -------------------------------------------------------------------------------- /src/components/widgets/system/MoonrakerLoadChart.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 58 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Hello! This is where you manage which Jekyll version is used to run. 4 | # When you want to use a different version, change it below, save the 5 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 6 | # 7 | # bundle exec jekyll serve 8 | # 9 | # This will help ensure the proper Jekyll version is running. 10 | # Happy Jekylling! 11 | # gem "jekyll", "~> 3.9.0" 12 | 13 | # This is the default theme for new Jekyll sites. You may change this to anything you like. 14 | gem "minima", "~> 2.0" 15 | 16 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 17 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 18 | #gem "github-pages", group: :jekyll_plugins 19 | gem "github-pages", "~> 212", group: :jekyll_plugins 20 | 21 | # If you have any plugins, put them here! 22 | group :jekyll_plugins do 23 | gem "jekyll-feed", "~> 0.6" 24 | end 25 | 26 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 27 | # and associated library. 28 | install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do 29 | gem "tzinfo", "~> 1.2" 30 | gem "tzinfo-data" 31 | end 32 | 33 | # Performance-booster for watching directories on Windows 34 | gem "wdm", "~> 0.1.0", :install_if => Gem.win_platform? 35 | 36 | # kramdown v2 ships without the gfm parser by default. If you're using 37 | # kramdown v1, comment out this line. 38 | gem "kramdown-parser-gfm" 39 | 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve 3 | labels: ["bug", "triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | This issue form is for reporting bugs only! 9 | If you have a feature request, please use [feature_request](/new?template=feature_request.yml) 10 | - type: textarea 11 | id: what-happened 12 | attributes: 13 | label: What happened 14 | description: >- 15 | A clear and concise description of what the bug is. 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: expected-behavior 20 | attributes: 21 | label: What did you expect to happen 22 | description: >- 23 | A clear and concise description of what you expected to happen. 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: repro-steps 28 | attributes: 29 | label: How to reproduce 30 | description: >- 31 | Minimal and precise steps to reproduce this bug. 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: additional-info 36 | attributes: 37 | label: Additional information 38 | description: | 39 | If you have any additional information for us, use the field below. 40 | 41 | Please note, you can attach screenshots or screen recordings here, by 42 | dragging and dropping files in the field below. 43 | -------------------------------------------------------------------------------- /src/components/widgets/system/SystemMemoryChart.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 58 | -------------------------------------------------------------------------------- /src/components/widgets/system/KlipperLoadChart.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 58 | -------------------------------------------------------------------------------- /src/components/layout/AppToolsDrawer.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 56 | -------------------------------------------------------------------------------- /src/components/widgets/filesystem/FileRowItem.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 53 | -------------------------------------------------------------------------------- /src/components/widgets/outputs/OutputPin.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 53 | -------------------------------------------------------------------------------- /src/store/cameras/getters.ts: -------------------------------------------------------------------------------- 1 | import { GetterTree } from 'vuex' 2 | import { CamerasState } from './types' 3 | import { RootState } from '../types' 4 | 5 | export const getters: GetterTree = { 6 | /** 7 | * Return all cameras. 8 | */ 9 | getCameras: (state) => { 10 | return [...state.cameras] 11 | .sort((a, b) => { 12 | const name1 = a.name.toLowerCase() 13 | const name2 = b.name.toLowerCase() 14 | return (name1 < name2) ? -1 : (name1 > name2) ? 1 : 0 15 | }) 16 | }, 17 | 18 | /** 19 | * Return all enabled cameras, 20 | */ 21 | getEnabledCameras: (state, getters) => { 22 | return [...getters.getCameras] 23 | .filter(camera => camera.enabled) 24 | }, 25 | 26 | /** 27 | * Return visible cameras. I.e., return cameras dependent on them being 28 | * 1. enabled and 2. filtered by the currently active state. 29 | */ 30 | getVisibleCameras: (state, getters) => { 31 | if (getters.getActiveCamera === 'all') return [...getters.getEnabledCameras] 32 | return [...getters.getEnabledCameras] 33 | .filter(camera => camera.id === getters.getActiveCamera) 34 | }, 35 | 36 | /** 37 | * Return a camera by its id. 38 | */ 39 | getCameraById: (state) => (id: string) => { 40 | return state.cameras.find(camera => camera.id === id) 41 | }, 42 | 43 | /** 44 | * Return the set active camera, being all or a specific id. 45 | */ 46 | getActiveCamera: (state) => { 47 | return state.activeCamera 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/components/settings/auth/ApiKeyDialog.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 53 | -------------------------------------------------------------------------------- /src/scss/variables.scss: -------------------------------------------------------------------------------- 1 | // Change our grid gutters to 16px. 2 | $grid-gutter: 16px; 3 | $container-padding-x: 16px; 4 | $container-padding-y: 16px; 5 | 6 | $grid-gutters: 7 | ( 8 | 'xs': 2px, 9 | 'sm': 3px, 10 | 'md': 4px, 11 | 'lg': 8px, 12 | 'xl': 16px 13 | ); 14 | 15 | // Standard height rows for our table should be.. 16 | $data-table-regular-row-height: 40px; 17 | $data-table-border-radius: none; 18 | 19 | $dialog-card-actions-padding: 0 24px 16px; 20 | 21 | $tooltip-background-color: rgba(10,10,10,0.90); 22 | 23 | $material-dark: ( 24 | 'background': #1E1E20, 25 | 'table': ( 26 | 'active':#313134, 27 | 'hover': #353539 28 | ), 29 | 'text': ( 30 | 'theme': rgba(#ffffff, 0.87), 31 | 'primary': rgba(#ffffff, 0.80), // standard text color (when not specifically set to primary--text) 32 | ), 33 | 'icons': ( 34 | 'active': rgba(#ffffff, 0.77) // incl checkboxes 35 | ), 36 | 'text-color': rgba(#ffffff, 0.87), 37 | 'fg-color': rgba(#ffffff, 0.87) 38 | ); 39 | 40 | $material-dark-elevation-colors: ( 41 | '0': #1E1E20, 42 | '1': #262629, 43 | '2': #2b2b2d, 44 | '3': #2d2d30, 45 | '4': #2f2f32, 46 | '6': #313134, 47 | '8': #333337, 48 | '12': #353539, 49 | '16': #38383b, 50 | '24': #3a3a3e 51 | ); 52 | 53 | $timeline-divider-width: 48px; 54 | 55 | $expansion-panel-active-margin: 16px; 56 | $expansion-panel-header-padding: 12px 16px; 57 | $expansion-panel-content-padding: 0 16px 16px; 58 | $expansion-panel-header-min-height: none; 59 | $expansion-panel-active-header-min-height: none; -------------------------------------------------------------------------------- /src/components/ui/AppBtnToolheadMove.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 58 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Have a cool feature in mind? Use this template! 3 | labels: ["enhancement"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | This issue form is for feature requests only! 9 | If you've found a bug, please use [bug_report](/new?template=bug_report.yml) 10 | - type: textarea 11 | id: problem-description 12 | attributes: 13 | label: Is your feature request related to a problem? Please describe 14 | description: >- 15 | A clear and concise description of what the problem is. 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: solution-description 20 | attributes: 21 | label: Describe the solution you'd like 22 | description: >- 23 | A clear and concise description of what you want to happen. 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: possible-alternatives 28 | attributes: 29 | label: Describe alternatives you've considered 30 | description: >- 31 | A clear and concise description of any alternative solutions or features you've considered. 32 | - type: textarea 33 | id: additional-info 34 | attributes: 35 | label: Additional information 36 | description: | 37 | If you have any additional information for us, use the field below. 38 | 39 | Please note, you can attach screenshots or screen recordings here, by 40 | dragging and dropping files in the field below. 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fluidd 2 | Fluidd is a free and open-source Klipper web interface for managing your 3d printer. 3 | 4 | ![Fluidd](/.github/images/preview_sliced.png?raw=true "Fluidd") 5 | 6 | ## Features 7 | - Responsive UI, supports desktop, tablets and mobile 8 | - Customizable layouts. Move any panel where YOU want 9 | - Built-in color themes 10 | - Manage multiple printers from one Fluidd install 11 | - [See our docs for more!](https://docs.fluidd.xyz) 12 | 13 | ## Support & Documentation 14 | See our [Docs](https://docs.fluidd.xyz). 15 | Join our [Discord!](https://discord.gg/GZ3D5tqfcF). 16 | 17 | ## Where to download? 18 | You can download the latest release [here](https://github.com/fluidd-core/fluidd/releases/latest). 19 | 20 | Older releases can be found [here](https://github.com/fluidd-core/fluidd/releases). 21 | 22 | ## Docker 23 | We have an official docker image. This is updated for each release. 24 | https://hub.docker.com/r/cadriel/fluidd 25 | 26 | ## How to use? 27 | Fluidd is distributed as a RaspbiOS image, with Fluidd pre-installed and setup alongside its dependencies. 28 | 29 | The recommended approach is to flash a fresh image using FluiddPI. 30 | 31 | Please see the [docs](https://docs.fluidd.xyz) for help with installation and configuration. 32 | 33 | ### Credit 34 | A big thank you to; 35 | - the [Voron Community](http://vorondesign.com/) 36 | - Kevin O'Connor for [Klipper](https://github.com/KevinOConnor/klipper) 37 | - Eric Callahan for [Moonraker](https://github.com/Arksine/moonraker) 38 | - Ray for [MainsailOS](https://github.com/raymondh2/MainsailOS) 39 | -------------------------------------------------------------------------------- /src/components/widgets/runout-sensors/RunoutSensorsCard.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 48 | -------------------------------------------------------------------------------- /developer-certificate-of-origin: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. 38 | -------------------------------------------------------------------------------- /src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | // import { register } from 'register-service-worker' 2 | import { Workbox } from 'workbox-window' 3 | 4 | let wb: Workbox | null 5 | 6 | if ('serviceWorker' in navigator) { 7 | wb = new Workbox(`${process.env.BASE_URL}service-worker.js`) 8 | 9 | wb.addEventListener('controlling', () => { 10 | window.location.reload() 11 | }) 12 | 13 | wb.register() 14 | } else { 15 | wb = null 16 | } 17 | 18 | export default wb 19 | 20 | // Relevant read? 21 | // https://medium.com/@stephen.trevor.wong/3-steps-to-add-pwa-to-vue-js-in-2020-9f9daa56f9 22 | // if (process.env.NODE_ENV === 'production') { 23 | // register(`${process.env.BASE_URL}service-worker.js`, { 24 | // ready () { 25 | // consola.log( 26 | // 'App is being served from cache by a service worker.\n' + 27 | // 'For more details, visit https://goo.gl/AFskqB' 28 | // ) 29 | // }, 30 | // registered () { 31 | // consola.log('Service worker has been registered.') 32 | // }, 33 | // cached () { 34 | // consola.log('Content has been cached for offline use.') 35 | // }, 36 | // updatefound () { 37 | // consola.log('New content is downloading.') 38 | // }, 39 | // updated () { 40 | // consola.log('New content is available; please refresh.') 41 | // }, 42 | // offline () { 43 | // consola.log('No internet connection found. App is running in offline mode.') 44 | // }, 45 | // error (error) { 46 | // consola.error('Error during service worker registration:', error) 47 | // } 48 | // }) 49 | // } 50 | -------------------------------------------------------------------------------- /docs/features/cameras.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Cameras 4 | parent: Features 5 | nav_order: 10 6 | permalink: /features/cameras 7 | --- 8 | 9 | # Cameras 10 | {: .no_toc } 11 | 12 | --- 13 | 14 | Fluidd (and FluiddPI) has built in support for web cameras. 15 | 16 | On older versions of FluiddPI (pre 1.15) you can edit the file found here; 17 | `/boot/fluiddpi.txt`. 18 | 19 | Versions of FluiddPI after 1.14 allow configuring your cameras from the same 20 | place as your other configuration files, and can be found in the `webcam.txt` 21 | file. 22 | 23 | In both cases, Instructions are contained within the files. 24 | 25 | You can add up to four cameras to display on your dashboard. 26 | 27 | Currently supported types are; 28 | 29 | - MJPEG Stream 30 | This is the traditional mjpegstream service. The service pushes images to 31 | fluidd at the configured resolution and FPS you have setup. This requires 32 | a lot of bandwidth, and can cause issues with unstable network connections. 33 | 34 | - MJPEG Adaptive 35 | This will PULL images from the mjpegstream service, using the snapshot URL. 36 | You can set a target FPS, and your browser will try to keep up with the 37 | intended target. This can be a more reliable approach for some. 38 | 39 | - IP Camera 40 | This is an experimental option. Effectively, it swaps out the `` tag 41 | for a `