├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── lock-closed.yml │ └── stale.yml ├── .gitignore ├── CONTRIBUTING.md ├── DEVELOPMENT.md ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── WHATSNEW.md ├── babel.cfg ├── gulpfile.js ├── octoprint_touchui ├── __init__.py ├── api.py ├── customization.py ├── decorators.py ├── static │ ├── README.md │ ├── css │ │ ├── .gitignore │ │ └── touchui.css │ ├── fonts │ │ ├── fontawesome │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── sourcecodepro │ │ │ ├── LICENSE.txt │ │ │ ├── SourceCodePro-Bold.woff │ │ │ ├── SourceCodePro-Light.woff │ │ │ ├── SourceCodePro-Regular.woff │ │ │ └── SourceCodePro-Semibold.woff │ │ └── touchui │ │ │ ├── touchui.eot │ │ │ ├── touchui.svg │ │ │ ├── touchui.ttf │ │ │ └── touchui.woff │ ├── img │ │ └── colorpicker.png │ ├── js │ │ ├── touchui.bootstrap.js │ │ ├── touchui.bundled.js │ │ └── touchui.libraries.js │ └── less │ │ └── touchui.bundled.less └── templates │ ├── empty.jinja2 │ ├── touchui_load_css.jinja2 │ ├── touchui_menu_item.jinja2 │ ├── touchui_modal.jinja2 │ └── touchui_settings.jinja2 ├── package-lock.json ├── package.json ├── requirements.txt ├── setup.py └── source ├── js ├── animate │ └── hide.js ├── bootstrap.js ├── components │ ├── dropdown.js │ ├── fullscreen.js │ ├── keyboard.js │ ├── modal.js │ ├── slider.js │ ├── touch-list.js │ └── touchscreen.js ├── constructor.js ├── core │ ├── _init.js │ ├── boot.js │ ├── bridge.js │ ├── less.js │ └── version.js ├── dom │ ├── _init.js │ ├── cookies.js │ ├── create │ │ ├── dropdown.js │ │ ├── printer.js │ │ ├── tabbar.js │ │ └── webcam.js │ ├── localstorage.js │ ├── move │ │ ├── afterTabAndNav.js │ │ ├── connection.js │ │ ├── controls.js │ │ ├── navbar.js │ │ ├── overlays.js │ │ ├── sidebar.js │ │ ├── tabbar.js │ │ └── terminal.js │ ├── overwrite │ │ ├── modal.js │ │ ├── pnotify.js │ │ ├── tabbar.js │ │ └── tabdrop.js │ └── storage.js ├── knockout │ ├── bindings.js │ ├── isLoading.js │ ├── isReady.js │ └── viewModel.js ├── plugins │ ├── _init.js │ ├── disable.js │ ├── multiwebcam.js │ ├── psucontrol.js │ └── screensquish.js └── scroll │ ├── _beforeLoad.js │ ├── _init.js │ ├── block-events.js │ ├── body.js │ ├── modal.js │ ├── overlay.js │ ├── overwrite.js │ └── terminal.js ├── less ├── _mixins.less ├── _variables.less ├── components │ ├── dropdown.less │ ├── emulate.touch.less │ ├── files.less │ ├── keyboard.less │ ├── modal.less │ ├── notifications.less │ ├── overlays.less │ ├── pagination.less │ ├── popover.less │ ├── progress.less │ ├── scroll.less │ ├── scrollbars.less │ └── tinycolorpicker.less ├── fonts │ ├── fontawesome.less │ ├── sourcecodepro.less │ └── touchui.less ├── layout │ ├── buttons.less │ ├── footer.less │ ├── form.less │ ├── general.less │ ├── header.less │ ├── links.less │ ├── navbar.less │ ├── tabbar.less │ └── touchscreen.less ├── plugins │ ├── eeprommarlin.less │ ├── history.less │ ├── m33fio.less │ ├── multicam.less │ ├── navbartemp.less │ ├── octolapse.less │ ├── printerstatistics.less │ └── psucontrol.less ├── settings │ └── touchui.less ├── tabs │ ├── connection.less │ ├── controls.less │ ├── gcode.less │ ├── printer.less │ ├── temperature.less │ ├── terminal.less │ ├── timelapse.less │ └── webcam.less └── touchui.less ├── svg ├── C.svg ├── H.svg ├── O.svg ├── T.svg ├── U.svg ├── UI.svg └── fan.svg └── vendors └── tinycolorpicker └── lib ├── jquery.tinycolorpicker.js ├── jquery.tinycolorpicker.min.js ├── tinycolorpicker.js └── tinycolorpicker.min.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | indent_style = tab 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | octoprint_touchui/static/css/*.css binary 2 | octoprint_touchui/static/less/touchui.*.less binary 3 | octoprint_touchui/static/js/touchui.*.js binary 4 | -------------------------------------------------------------------------------- /.github/workflows/lock-closed.yml: -------------------------------------------------------------------------------- 1 | name: Lock Closed Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 1/13 * * *' 6 | 7 | jobs: 8 | lock: 9 | name: Lock Closed Issues 10 | if: github.repository == 'BillyBlaze/OctoPrint-TouchUI' 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: dessant/lock-threads@v2 16 | with: 17 | github-token: ${{ secrets.GITHUB_TOKEN }} 18 | process-only: 'issues' 19 | issue-lock-inactive-days: '14' 20 | issue-exclude-created-before: '' 21 | issue-exclude-labels: 'no-locking' 22 | issue-lock-labels: '' 23 | issue-lock-comment: > 24 | This issue has been automatically locked since there 25 | has not been any recent activity after it was closed. 26 | Please open a new issue for related bugs. 27 | issue-lock-reason: '' 28 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: "30 1 * * *" 6 | 7 | jobs: 8 | stale: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/stale@v1 14 | with: 15 | repo-token: ${{ secrets.GITHUB_TOKEN }} 16 | days-before-stale: 14 17 | days-before-close: 7 18 | stale-issue-message: > 19 | This issue has been automatically marked as inactive because it has not had 20 | recent activity. It will be closed if no further activity occurs. Thank you 21 | for your contributions. 22 | stale-pr-message: > 23 | This issue has been automatically marked as inactive because it has not had 24 | recent activity. It will be closed if no further activity occurs. Thank you 25 | for your contributions. 26 | stale-issue-label: 'inactive' 27 | stale-pr-label: 'inactive' 28 | exempt-issue-labels: 'help wanted,up for grabs,fixed,confirmed,pending' 29 | exempt-pr-labels: 'help wanted,up for grabs,fixed,confirmed,pending' 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | .idea 4 | *.iml 5 | build 6 | dist 7 | *.egg* 8 | .DS_Store 9 | *.zip 10 | node_modules 11 | .vscode/ 12 | octoprint_touchui/WHATSNEW.md 13 | !source/vendors/tinycolorpicker/* 14 | source/vendors/ 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to TouchUI 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | ## How Can I Contribute? 6 | 7 | ### Reporting Bugs 8 | 9 | This section guides you through submitting a bug report for TouchUI. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:. 10 | 11 | Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). If you'd like, you can use [this template](#template-for-submitting-bug-reports) to structure the information. 12 | 13 | #### OctoPrint and plugins 14 | Sometimes bugs can be related to a specific plugin or OctoPrint version. Since TouchUI extends the code base of OctoPrint, a bug can also exist in the default interface. 15 | * Check if the bug also exists in the default interface. If yes, report this problem to [OctoPrint](https://github.com/foosel/OctoPrint). 16 | * Check if you have installed a plugin that interferes with TouchUI or your bug. If you do; try to disable it. If it has no effect, add this to your bug report. 17 | 18 | #### Before Submitting A Bug Report 19 | 20 | * **Check the [Troubleshooting on the Wiki](https://github.com/BillyBlaze/OctoPrint-TouchUI/wiki/Setup:-Troubleshooting)** for a list of common questions and problems. 21 | * **Perform a [cursory search](https://github.com/BillyBlaze/OctoPrint-TouchUI/issues?utf8=%E2%9C%93&q=)** to see if the problem has already been reported. If it has, add a comment to the existing issue instead of opening a new one. 22 | 23 | #### How Do I Submit A (Good) Bug Report? 24 | 25 | Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](#octoprint-and-plugins) your bug is related to, create an issue on that repository and provide the following information. 26 | 27 | Explain the problem and include additional details to help maintainers reproduce the problem: 28 | 29 | * **Which version of OctoPrint and TouchUI are you using**? 30 | * **What's the name and version of the Browser you're using**? 31 | * **Use a clear and descriptive title** for the issue to identify the problem. 32 | * **Describe the exact steps which reproduce the problem** in as many details as possible. Don't just say what you did, but explain how you did it. For example, if you moved the cursor to the end of a line, explain if you used the mouse, or a touch swipe. 33 | * **Explain which behavior you expected to see instead and why.** 34 | * ~~**If the problem is related to performance**, include a [CPU profile capture and a screenshot](https://atom.io/docs/latest/hacking-atom-debugging#diagnose-performance-problems-with-the-dev-tools-cpu-profiler) with your report.~~ [work-in-progress] 35 | 36 | ##### **When reporting a browser issues:** 37 | * **Include the output of your JavaScript console**: 38 | * See [How to open the Javascript Console in different browsers](http://webmasters.stackexchange.com/a/77337) 39 | * Use pastebin or [gist](https://gist.github.com/) to store your javascript log 40 | 41 | ##### **When reporting a layout issues:** 42 | * **Include screenshots and animated GIFs**. You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on OSX and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. 43 | 44 | ##### **When reporting OS/Touchscreen issues:** 45 | * **What's the name and version of the OS you're using**? 46 | Include details about your configuration and environment: 47 | 48 | 49 | #### Template For Submitting Bug Reports 50 | ``` 51 | [Short description of problem here] 52 | 53 | **Reproduction Steps:** 54 | 1. [First Step] 55 | 2. [Second Step] 56 | 3. [Other Steps...] 57 | 58 | **Expected behavior:** 59 | [Describe expected behavior here] 60 | 61 | **Observed behavior:** 62 | [Describe observed behavior here] 63 | 64 | **Screenshots and GIFs** 65 | ![If required: Screenshots and GIFs which follow reproduction steps to demonstrate the problem](url) 66 | 67 | **Javascript console** 68 | ![If required: Link to pastebin or gist](url) 69 | 70 | **Browser version:** [Enter Browser version here] 71 | **OctoPrint version:** [Enter OctoPrint version here] 72 | **TouchUI version:** [Enter TouchUI version here] 73 | 74 | **Installed plugins:** 75 | 1. [package 1] 76 | 2. [package 2] 77 | ``` 78 | 79 | ### Your First Code Contribution 80 | 81 | Unsure where to begin contributing to TouchUI? You can start by looking through `help-wanted` issues: 82 | 83 | * [Help wanted issues][help-wanted] - issues which should be a bit more involved than `beginner` issues. 84 | 85 | ### Pull Requests 86 | 87 | * Include screenshots and animated GIFs in your pull request whenever possible. 88 | * ~~Follow the [JavaScript](https://github.com/styleguide/javascript), 89 | and [LESS](https://github.com/styleguide/css) styleguides.~~ [work-in-progress] 90 | * End files with a newline. 91 | * Target the branch maintenance 92 | 93 | ## Styleguides 94 | 95 | ### Git Commit Messages 96 | 97 | * Use the present tense ("Add feature" not "Added feature") 98 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 99 | * Limit the first line to 72 characters or less 100 | * Reference issues and pull requests liberally 101 | * Consider starting the commit message with an applicable emoji: 102 | * :sparkles: `:sparkles:` when adding a new feature 103 | * :bug: `:bug:` when fixing a bug 104 | * :fire: `:fire:` when removing code or files 105 | * :art: `:art:` when improving the format/structure of the code 106 | * :racehorse: `:racehorse:` when improving performance 107 | * :non-potable_water: `:non-potable_water:` when plugging memory leaks 108 | * :memo: `:memo:` when writing docs 109 | * :white_check_mark: `:white_check_mark:` when adding tests 110 | * :lock: `:lock:` when dealing with security 111 | * :arrow_up: `:arrow_up:` when upgrading dependencies 112 | * :arrow_down: `:arrow_down:` when downgrading dependencies 113 | * :shirt: `:shirt:` when removing linter warnings 114 | 115 | --------- 116 | 117 | Thanks to Atom for proving a solid contributing template. 118 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Development envoirment 2 | * We manage javascript libraries with **Bower** 3 | 1. Libraries are downloaded and extrated into ``Source/vendors/`` 4 | * We compile all files in the `Source` directory into multiple files with **Gulp**: 5 | 1. LESS compiles into a combined LESS file for the template of Customization. 6 | 1. LESS compiles into a CSS file used if Customization is disabled. 7 | 1. JS files is combined into one file: 8 | - Core files is written to touchui.bundled.js 9 | - Libraries is written to touchui.libraries.js 10 | - Knockout is written to touchui.libraries.js 11 | 12 | 13 | ## Prerequisites 14 | Install [NodeJS](http://www.nodejs.org/) 15 | 16 | 1. Install required global NPM plguins: 17 | ``` 18 | (sudo) npm install gulp-cli -g 19 | ``` 20 | 21 | 1. Install development dependencies 22 | ``` 23 | npm install 24 | ``` 25 | 26 | ## Commands 27 | - **Build all** 28 | Run `gulp` 29 | 30 | - **Watch** 31 | Run `gulp watch` 32 | 33 | - **LESS** 34 | Run `gulp less` 35 | This will concat all files in `source/less` to `touchui.bundled.less` 36 | 37 | - **JS** 38 | Run `gulp js` 39 | This will concat all files in `source/js` to `touchui.bundled.js`/`touchui.libraries.js`/`touchui.knockout.js` 40 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OctoPrint-TouchUI 2 | This plugin will transform the OctoPrint layout into a Mobile/TFT friendly layout. With larger buttons and a responsive layout down to the smallest resolution possible. It will mimic pointer events as touch, so you can hook up those touchscreens. It also supports a virtual keyboard. 3 | 4 | All these settings are set client-side, so we won't interfere with other clients. All settings are stored in your localstorage or as a delicious cookie. You can find the `TouchUI settings` in a dedicated modal. Remember they're stored on your device, so if you login with your desktop computer you won't get the touch interface. 5 | 6 | ![TouchUI Interface](https://billyblaze.github.io/OctoPrint-TouchUI/images/touchui-v030.gif) 7 | 8 | ## Setup 9 | Install via the bundled [Plugin Manager](https://github.com/foosel/OctoPrint/wiki/Plugin:-Plugin-Manager) 10 | or manually using this URL: 11 | 12 | https://github.com/BillyBlaze/OctoPrint-TouchUI/archive/master.zip 13 | 14 | - **Touchscreens** 15 | Read more about [setting up a touchscreen](https://github.com/BillyBlaze/OctoPrint-TouchUI/wiki/Setup#raspberrypi--touchscreen) on our Wiki. 16 | 17 | ## Configuration 18 | The interface will automatically start when your browser is smaller then 980 pixels in width or if you're browsing with a touch device. You can turn this manually on and off in the ``TouchUI settings`` modal. Alternatively you can force TouchUI to load by adding ``#touch`` on the end of your URL. 19 | 20 | Read more [configuration options](https://github.com/BillyBlaze/OctoPrint-TouchUI/wiki/Configuration) on our Wiki. 21 | 22 | - **Customization** 23 | You can change 4 main colors of the interface with the power of LESS. If you would like to change more colors, then you're free to add your own LESS file. [Read more about this and the variables](https://github.com/BillyBlaze/OctoPrint-TouchUI/wiki/Customize:-Use-your-own-file) on our wiki. 24 | 25 | ## Supported browsers 26 | 1. Chrome 30+ 27 | 1. Firefox 40+ 28 | 1. Safari Mobile 29 | 1. Chrome Mobile 30 | -------------------------------------------------------------------------------- /WHATSNEW.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/WHATSNEW.md -------------------------------------------------------------------------------- /babel.cfg: -------------------------------------------------------------------------------- 1 | [python: */**.py] 2 | [jinja2: */**.jinja2] 3 | extensions=jinja2.ext.autoescape, jinja2.ext.with_ 4 | 5 | [javascript: */**.js] 6 | extract_messages = gettext, ngettext 7 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var gulp = require('gulp'); 3 | var less = require('gulp-less'); 4 | var del = require('del'); 5 | var rename = require("gulp-rename"); 6 | var uglify = require("gulp-uglify"); 7 | var through = require("through2"); 8 | var gulpif = require("gulp-if"); 9 | var trimlines = require('gulp-trimlines'); 10 | var concat = require('gulp-concat'); 11 | var stripCssComments = require('gulp-strip-css-comments'); 12 | 13 | gulp.task('lessc', function () { 14 | return gulp.src('source/less/touchui.less') 15 | .pipe(less({compress: true})) 16 | .pipe(through.obj(function(file, enc, cb) { 17 | var contents = file.contents.toString(); 18 | contents = contents.replace(/mixin\:placeholder\;/g, ''); 19 | 20 | file.contents = Buffer.from(contents); 21 | 22 | cb(null, file); 23 | })) 24 | .pipe(gulp.dest('octoprint_touchui/static/css')); 25 | }); 26 | 27 | gulp.task("less:concat", function() { 28 | return gulp.src('source/less/touchui.less') 29 | .pipe(through.obj(function(file, enc, cb) { 30 | var contents = file.contents.toString(); 31 | var regex = /@import \"(.*)\"\;/gm; 32 | 33 | while ((m = regex.exec(contents)) !== null) { 34 | if (m.index === regex.lastIndex) { 35 | regex.lastIndex++; 36 | } 37 | 38 | var replaceString = m[0]; 39 | var fileName = m[1]; 40 | 41 | var importContents = fs.readFileSync(__dirname + '/source/less/' + fileName); 42 | 43 | contents = contents.replace(replaceString, importContents); 44 | } 45 | 46 | file.contents = Buffer.from(contents); 47 | 48 | cb(null, file); 49 | })) 50 | .pipe(stripCssComments()) 51 | .pipe(rename("touchui.bundled.less")) 52 | .pipe(trimlines()) 53 | .pipe(through.obj(function(file, enc, cb) { 54 | var contents = file.contents.toString(); 55 | contents = contents.replace(/\n\s*\n/g, '\n'); 56 | 57 | file.contents = Buffer.from(contents); 58 | 59 | cb(null, file); 60 | })) 61 | .pipe(gulp.dest('octoprint_touchui/static/less/')); 62 | }); 63 | 64 | gulp.task('clean:hash', function () { 65 | return del([ 66 | 'octoprint_touchui/static/css/hash.*.touchui', 67 | ]); 68 | }); 69 | 70 | gulp.task('js:concat:libs', function () { 71 | return gulp.src([ 72 | 'node_modules/keyboard/dist/js/jquery.keyboard.min.js', 73 | 'node_modules/jquery-fullscreen-kayahr/jquery.fullscreen-min.js', 74 | 'node_modules/iscroll/build/iscroll.js', 75 | 'source/vendors/tinycolorpicker/lib/jquery.tinycolorpicker.min.js' 76 | ]) 77 | .pipe(gulpif("**/iscroll.js", uglify())) 78 | .pipe(concat('touchui.libraries.js')) 79 | .pipe(gulp.dest('octoprint_touchui/static/js/')); 80 | }); 81 | 82 | gulp.task('js:concat:app', function () { 83 | return gulp.src([ 84 | 'source/js/constructor.js', 85 | 'source/js/**/*.js', 86 | 'source/js/**/**/*.js', 87 | '!source/js/bootstrap.js' 88 | ]) 89 | .pipe(concat('touchui.bundled.js')) 90 | //.pipe(uglify()) 91 | .pipe(gulp.dest('octoprint_touchui/static/js/')); 92 | }); 93 | 94 | gulp.task('js:concat:bootstrap', function () { 95 | return gulp.src([ 96 | 'source/js/bootstrap.js' 97 | ]) 98 | .pipe(rename("touchui.bootstrap.js")) 99 | .pipe(uglify()) 100 | .pipe(gulp.dest('octoprint_touchui/static/js/')); 101 | }); 102 | 103 | gulp.task('watch', function () { 104 | gulp.watch( 105 | ['source/js/**/*.js', 'source/js/*.js'], 106 | gulp.series('js') 107 | ); 108 | gulp.watch( 109 | ['source/less/touchui.less', 'source/less/**/*.less'], 110 | gulp.series('less') 111 | ); 112 | }); 113 | 114 | gulp.task('default', gulp.series('lessc', 'clean:hash', 'less:concat', 'js:concat:app', 'js:concat:libs', 'js:concat:bootstrap')); 115 | gulp.task('less', gulp.series('lessc', 'clean:hash', 'less:concat')); 116 | gulp.task('js', gulp.series('js:concat:app', 'js:concat:libs', 'js:concat:bootstrap')); 117 | -------------------------------------------------------------------------------- /octoprint_touchui/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import absolute_import 3 | 4 | from .api import touchui_api 5 | from .customization import touchui_customization 6 | 7 | import octoprint.plugin 8 | import octoprint.settings 9 | import octoprint.util 10 | import hashlib 11 | import time 12 | import os 13 | 14 | class touchui_core( touchui_api, 15 | touchui_customization, 16 | octoprint.plugin.SettingsPlugin, 17 | octoprint.plugin.AssetPlugin, 18 | octoprint.plugin.TemplatePlugin, 19 | octoprint.plugin.StartupPlugin): 20 | 21 | def __init__(self): 22 | super(touchui_core, self).__init__() 23 | self._whatsNewPath = os.path.dirname(__file__) + "/WHATSNEW.md" 24 | 25 | def on_startup(self, host, port): 26 | self._startup_customization(host, port) 27 | 28 | def on_settings_load(self): 29 | return self._load_custom_settings() 30 | 31 | def on_settings_save(self, data): 32 | self._save_custom_settings(data) 33 | 34 | def on_after_startup(self): 35 | self._check_customization() 36 | 37 | def get_template_vars(self): 38 | if os.path.isfile(self._customCssPath) and self._settings.get(["useCustomization"]): 39 | with open(self._customCssPath, 'r') as contentFile: 40 | return dict(cssPath="./plugin/touchui/static/css/touchui.custom.{port}.css".format(port=self._port), timestamp=hashlib.md5(contentFile.read().encode('utf-8')).hexdigest()[:9]) 41 | else: 42 | with open(self._cssPath, 'r') as contentFile: 43 | return dict(cssPath="./plugin/touchui/static/css/touchui.css", timestamp=hashlib.md5(contentFile.read().encode('utf-8')).hexdigest()[:9]) 44 | 45 | def get_assets(self): 46 | return dict( 47 | js=["js/touchui.libraries.js", "js/touchui.bundled.js", "js/touchui.bootstrap.js"] 48 | ) 49 | 50 | def get_template_configs(self): 51 | files = [ 52 | dict(type="generic", template="touchui_modal.jinja2", custom_bindings=True), 53 | dict(type="settings", template="touchui_settings.jinja2", custom_bindings=True), 54 | dict(type="navbar", template="touchui_menu_item.jinja2", custom_bindings=True), 55 | dict(type="generic", template="touchui_load_css.jinja2", custom_bindings=False) 56 | ] 57 | 58 | if self._settings.get(["automaticallyLoadResolution"]): 59 | files.append( 60 | dict(type="navbar", template="empty.jinja2", custom_bindings=False, div="touchui_auto_load_resolution") 61 | ) 62 | 63 | if self._settings.get(["automaticallyLoadTouch"]): 64 | files.append( 65 | dict(type="navbar", template="empty.jinja2", custom_bindings=False, div="touchui_auto_load_touch") 66 | ) 67 | 68 | return files 69 | 70 | def get_settings_defaults(self): 71 | return dict( 72 | hasVisibleSettings=True, 73 | automaticallyLoadResolution=True, 74 | automaticallyLoadTouch=True, 75 | useCustomization=False, 76 | closeDialogsOutside=False, 77 | colors=dict( 78 | mainColor="#00B0FF", 79 | termColor="#0F0", 80 | bgColor="#000", 81 | textColor="#FFF", 82 | fontSize="16", 83 | customPath="", 84 | useLocalFile=False 85 | ) 86 | ) 87 | 88 | def get_version(self): 89 | return self._plugin_version 90 | 91 | def get_update_information(self): 92 | return dict( 93 | touchui=dict( 94 | displayName="TouchUI", 95 | displayVersion=self._plugin_version, 96 | type="github_release", 97 | user="BillyBlaze", 98 | repo="OctoPrint-TouchUI", 99 | current=self._plugin_version, 100 | pip="https://github.com/BillyBlaze/OctoPrint-TouchUI/archive/{target_version}.zip" 101 | ) 102 | ) 103 | 104 | __plugin_name__ = "TouchUI" 105 | __plugin_pythoncompat__ = ">=2.7,<4" 106 | def __plugin_load__(): 107 | touchui = touchui_core() 108 | 109 | global __plugin_implementation__ 110 | __plugin_implementation__ = touchui 111 | 112 | global __plugin_hooks__ 113 | __plugin_hooks__ = { 114 | "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information, 115 | "octoprint.server.http.bodysize": __plugin_implementation__.increase_upload_bodysize 116 | } 117 | -------------------------------------------------------------------------------- /octoprint_touchui/api.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import absolute_import 3 | 4 | from .decorators import crossdomain, touchui_admin_permission 5 | from octoprint.server.util.flask import restricted_access 6 | 7 | import octoprint.plugin 8 | import octoprint.settings 9 | import octoprint.util 10 | import flask 11 | import functools 12 | import os 13 | 14 | class touchui_api(octoprint.plugin.BlueprintPlugin): 15 | def increase_upload_bodysize(self, current_max_body_sizes, *args, **kwargs): 16 | return [("POST", r"/css", 1 * 1024 * 1024), ("GET", r"/css", 1 * 1024 * 1024)] 17 | 18 | @octoprint.plugin.BlueprintPlugin.route("/css", methods=["POST"]) 19 | @restricted_access 20 | @touchui_admin_permission 21 | def saveCSS(self): 22 | if not "css" in flask.request.values: 23 | return flask.make_response("Expected a CSS value.", 400) 24 | 25 | try: 26 | self._save_custom_css(flask.request.values["css"]) 27 | 28 | except Exception as e: 29 | self._logger.warn("Exception while generating LESS file: {message}".format(message=str(e))) 30 | return flask.make_response(str(e), 400) 31 | 32 | return flask.make_response("Ok.", 200) 33 | 34 | @octoprint.plugin.BlueprintPlugin.route("/css", methods=["GET"]) 35 | @restricted_access 36 | @touchui_admin_permission 37 | def getCSS(self): 38 | data = "" 39 | 40 | if not "path" in flask.request.values: 41 | return flask.make_response("Expected a path value.", 400) 42 | 43 | try: 44 | with open(flask.request.values["path"], 'r') as contentFile: 45 | data = contentFile.read() 46 | 47 | except Exception as e: 48 | self._logger.warn("Exception while generating LESS file: {message}".format(message=str(e))) 49 | return flask.make_response(str(e), 400) 50 | 51 | return flask.make_response(data, 200) 52 | 53 | @octoprint.plugin.BlueprintPlugin.route("/ping", methods=["GET"]) 54 | @crossdomain(origin='*') 55 | def ping(self): 56 | return flask.make_response("Ok.", 200) 57 | 58 | def is_blueprint_protected(self): 59 | return False 60 | -------------------------------------------------------------------------------- /octoprint_touchui/customization.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import absolute_import 3 | 4 | try: 5 | from octoprint.access.permissions import Permissions 6 | except: 7 | from octoprint.server import admin_permission 8 | 9 | import octoprint.plugin 10 | import octoprint.settings 11 | import octoprint.util 12 | import hashlib 13 | import time 14 | import os 15 | 16 | class touchui_customization(object): 17 | def _startup_customization(self, host, port): 18 | self._port = port 19 | self._cssPath = "{base}/static/css/touchui.css".format(base=os.path.dirname(__file__)) 20 | self._customCssPath = "{base}/static/css/touchui.custom.{port}.css".format(base=os.path.dirname(__file__), port=port) 21 | self._customLessPath = "{base}/static/less/touchui.bundled.less".format(base=os.path.dirname(__file__)) 22 | self._customHashPath = "{base}/static/css/hash.{port}.touchui".format(base=os.path.dirname(__file__), port=port) 23 | self._requireNewCSS = False 24 | self._refreshCSS = False 25 | self._refreshTime = 0 26 | 27 | # Migrate old css file to path with port 28 | if os.path.isfile("{base}/static/css/touchui.custom.css".format(base=os.path.dirname(__file__))): 29 | os.rename( 30 | "{base}/static/css/touchui.custom.css".format(base=os.path.dirname(__file__)), 31 | "{base}/static/css/touchui.custom.{port}.css".format(base=os.path.dirname(__file__), port=port) 32 | ) 33 | 34 | # Migrate old hash file to path with port 35 | if os.path.isfile("{base}/static/css/hash.touchui".format(base=os.path.dirname(__file__))): 36 | os.rename( 37 | "{base}/static/css/hash.touchui".format(base=os.path.dirname(__file__)), 38 | "{base}/static/css/hash.{port}.touchui".format(base=os.path.dirname(__file__), port=port) 39 | ) 40 | 41 | def _check_customization(self): 42 | # When generating LESS to CSS we also store the LESS contents into a md5 hash 43 | # if the hash of the local LESS file doesn't match this saved hash, then that indicates 44 | # that the LESS file has been update and that it requires a new compile. 45 | # 46 | # Therefor we going to put _requireNewCSS on TRUE, and we then let an admin compile the 47 | # LESS to CSS and store it in local CSS file. 48 | if self._settings.get(["useCustomization"]): 49 | hashedNew = "1" 50 | hashedOld = "2" 51 | 52 | if os.path.isfile(self._customLessPath): 53 | with open(self._customLessPath, 'r') as contentFile: 54 | hashedNew = hashlib.md5(contentFile.read().encode('utf-8')).hexdigest() 55 | 56 | if os.path.isfile(self._customHashPath): 57 | with open(self._customHashPath, 'r') as contentFile: 58 | hashedOld = contentFile.read() 59 | 60 | if hashedNew != hashedOld: 61 | self._requireNewCSS = True 62 | 63 | else: 64 | self._remove_custom_css() 65 | 66 | def _load_custom_settings(self): 67 | data = dict(octoprint.plugin.SettingsPlugin.on_settings_load(self)) 68 | data["hasCustom"] = os.path.isfile(self._customCssPath) 69 | data["requireNewCSS"] = self._requireNewCSS 70 | data["refreshCSS"] = self._refreshCSS 71 | data["whatsNew"] = False 72 | 73 | try: 74 | plugin_permission = Permissions.SETTINGS.can() 75 | except: 76 | plugin_permission = admin_permission.can() 77 | 78 | if plugin_permission: 79 | if os.path.isfile(self._whatsNewPath): 80 | with open(self._whatsNewPath, 'r') as contentFile: 81 | data["whatsNew"] = contentFile.read() 82 | os.unlink(self._whatsNewPath) 83 | 84 | if self._requireNewCSS is True: 85 | self._requireNewCSS = False 86 | 87 | if self._settings.get(["useCustomization"]): 88 | if os.path.isfile(self._customHashPath) is not True: 89 | data["requireNewCSS"] = True 90 | 91 | if self._refreshCSS: 92 | if self._refreshTime < time.time(): 93 | data["refreshCSS"] = False 94 | self._refreshCSS = False 95 | self._refreshTime = 0 96 | 97 | return data 98 | 99 | def _save_custom_settings(self, data): 100 | octoprint.plugin.SettingsPlugin.on_settings_save(self, data) 101 | 102 | if self._settings.get(["useCustomization"]) is False: 103 | self._remove_custom_css() 104 | else: 105 | self._refreshCSS = True 106 | self._refreshTime = time.time() + 10 107 | 108 | def _save_custom_css(self, data): 109 | self._requireNewCSS = False 110 | hashed = "" 111 | 112 | with open(self._customCssPath, "w+") as customCSS: 113 | customCSS.write('{css}'.format(css=data)) 114 | 115 | if os.path.isfile(self._customLessPath): 116 | with open(self._customLessPath, 'r') as contentFile: 117 | hashed = hashlib.md5(contentFile.read().encode('utf-8')).hexdigest() 118 | 119 | with open(self._customHashPath, "w+") as customHash: 120 | customHash.write('{hash}'.format(hash=hashed)) 121 | 122 | def _remove_custom_css(self): 123 | self._requireNewCSS = False 124 | 125 | if os.path.isfile(self._customCssPath): 126 | os.unlink(self._customCssPath) 127 | 128 | if os.path.isfile(self._customHashPath): 129 | os.unlink(self._customHashPath) 130 | -------------------------------------------------------------------------------- /octoprint_touchui/decorators.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import absolute_import 3 | 4 | from datetime import timedelta 5 | from flask import make_response, request, current_app, abort 6 | from functools import update_wrapper, wraps 7 | 8 | try: 9 | from octoprint.access.permissions import Permissions 10 | except: 11 | from octoprint.server import admin_permission 12 | 13 | def touchui_admin_permission(f): 14 | @wraps(f) 15 | def decorated_function(*args, **kwargs): 16 | try: 17 | plugin_permission = Permissions.SETTINGS.can() 18 | except: 19 | plugin_permission = admin_permission.can() 20 | 21 | if not plugin_permission: 22 | return abort(403) 23 | 24 | return f(*args, **kwargs) 25 | return decorated_function 26 | 27 | def crossdomain(origin=None, methods=None, headers=None, 28 | max_age=21600, attach_to_all=True, 29 | automatic_options=True): 30 | 31 | # Py3 compatibility with Py2 32 | try: 33 | basestring 34 | except NameError: 35 | basestring = str 36 | 37 | if methods is not None: 38 | methods = ', '.join(sorted(x.upper() for x in methods)) 39 | if headers is not None and not isinstance(headers, basestring): 40 | headers = ', '.join(x.upper() for x in headers) 41 | if not isinstance(origin, basestring): 42 | origin = ', '.join(origin) 43 | if isinstance(max_age, timedelta): 44 | max_age = max_age.total_seconds() 45 | 46 | def get_methods(): 47 | if methods is not None: 48 | return methods 49 | 50 | options_resp = current_app.make_default_options_response() 51 | return options_resp.headers['allow'] 52 | 53 | def decorator(f): 54 | def wrapped_function(*args, **kwargs): 55 | if automatic_options and request.method == 'OPTIONS': 56 | resp = current_app.make_default_options_response() 57 | else: 58 | resp = make_response(f(*args, **kwargs)) 59 | if not attach_to_all and request.method != 'OPTIONS': 60 | return resp 61 | 62 | h = resp.headers 63 | 64 | h['Access-Control-Allow-Origin'] = origin 65 | h['Access-Control-Allow-Methods'] = get_methods() 66 | h['Access-Control-Max-Age'] = str(max_age) 67 | if headers is not None: 68 | h['Access-Control-Allow-Headers'] = headers 69 | return resp 70 | 71 | f.provide_automatic_options = False 72 | return update_wrapper(wrapped_function, f) 73 | return decorator 74 | -------------------------------------------------------------------------------- /octoprint_touchui/static/README.md: -------------------------------------------------------------------------------- 1 | # All JS/CSS and LESS files are generated! 2 | 3 | Read the [DEVELOPMENT.md](https://github.com/BillyBlaze/OctoPrint-TouchUI/blob/master/DEVELOPMENT.md) for more details about chaning codes and re-generating it. 4 | -------------------------------------------------------------------------------- /octoprint_touchui/static/css/.gitignore: -------------------------------------------------------------------------------- 1 | touchui.custom.*.css 2 | hash.*.touchui 3 | -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/fontawesome/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/static/fonts/fontawesome/FontAwesome.otf -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/fontawesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/static/fonts/fontawesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/fontawesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/static/fonts/fontawesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/fontawesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/static/fonts/fontawesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/fontawesome/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/static/fonts/fontawesome/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/sourcecodepro/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | 5 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/sourcecodepro/SourceCodePro-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/static/fonts/sourcecodepro/SourceCodePro-Bold.woff -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/sourcecodepro/SourceCodePro-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/static/fonts/sourcecodepro/SourceCodePro-Light.woff -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/sourcecodepro/SourceCodePro-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/static/fonts/sourcecodepro/SourceCodePro-Regular.woff -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/sourcecodepro/SourceCodePro-Semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/static/fonts/sourcecodepro/SourceCodePro-Semibold.woff -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/touchui/touchui.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/static/fonts/touchui/touchui.eot -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/touchui/touchui.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/touchui/touchui.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/static/fonts/touchui/touchui.ttf -------------------------------------------------------------------------------- /octoprint_touchui/static/fonts/touchui/touchui.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/static/fonts/touchui/touchui.woff -------------------------------------------------------------------------------- /octoprint_touchui/static/img/colorpicker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/static/img/colorpicker.png -------------------------------------------------------------------------------- /octoprint_touchui/static/js/touchui.bootstrap.js: -------------------------------------------------------------------------------- 1 | !function(){if(TouchUI.prototype.settings.hasBootloader&&window.log&&window.log.error){var r=window.log.error;window.log.error=function(o,n){window.top.postMessage([n,""],"*"),r.apply(window.log,arguments)}}var o=new TouchUI;o.domLoading(),$(function(){o.domReady(),OCTOPRINT_VIEWMODELS.push([o.koStartup,o.TOUCHUI_REQUIRED_VIEWMODELS,o.TOUCHUI_ELEMENTS,o.TOUCHUI_REQUIRED_VIEWMODELS])})}(); -------------------------------------------------------------------------------- /octoprint_touchui/templates/empty.jinja2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BillyBlaze/OctoPrint-TouchUI/73665b2d6fd9d735f383339620a049771dfe0f6f/octoprint_touchui/templates/empty.jinja2 -------------------------------------------------------------------------------- /octoprint_touchui/templates/touchui_load_css.jinja2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /octoprint_touchui/templates/touchui_menu_item.jinja2: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /octoprint_touchui/templates/touchui_modal.jinja2: -------------------------------------------------------------------------------- 1 | 38 | -------------------------------------------------------------------------------- /octoprint_touchui/templates/touchui_settings.jinja2: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 |
8 | 11 |
12 | 13 |
14 | 17 |
18 | 19 |
20 | 23 |
24 | 25 |
26 | 29 |
30 | 31 |
32 | 35 |
36 | 37 |
38 | 39 |
40 |
41 | 42 | 43 | Customization 44 | 48 | 49 | 50 |
51 |
52 | 53 |
54 | 55 |
56 |
57 |
58 | 59 |
60 |
61 |
62 | 63 |
64 | 65 | 66 |
67 | 68 |
69 |
70 | 71 |
72 | 73 |
74 | 75 | 76 |
77 | 78 |
79 |
80 | 81 |
82 | 83 |
84 | 85 | 86 |
87 | 88 |
89 |
90 | 91 |
92 | 93 |
94 | 95 | 96 |
97 | 98 |
99 |
100 | 101 |
102 | 103 | 109 |
110 |
111 | 112 |
113 |
114 |
115 |
116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "", 3 | "name": "OctoPrint-TouchUI", 4 | "version": "0.0.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/BillyBlaze/OctoPrint-TouchUI.git" 8 | }, 9 | "dependencies": { 10 | "virtual-keyboard": "https://github.com/Mottie/Keyboard/archive/v1.27.2.tar.gz" 11 | }, 12 | "devDependencies": { 13 | "del": "^5.1.0", 14 | "gulp": "^4.0.2", 15 | "gulp-concat": "^2.6.1", 16 | "gulp-if": "^3.0.0", 17 | "gulp-less": "^4.0.1", 18 | "gulp-rename": "^2.0.0", 19 | "gulp-strip-css-comments": "^2.0.0", 20 | "gulp-trimlines": "^1.0.1", 21 | "gulp-uglify": "^3.0.2", 22 | "iscroll": "~5.2.0", 23 | "jquery-fullscreen-kayahr": "~1.1.5", 24 | "keyboard": "https://github.com/Mottie/Keyboard/archive/v1.27.2.tar.gz", 25 | "through2": "^4.0.2" 26 | }, 27 | "optionalDependencies": {}, 28 | "engines": { 29 | "node": "*" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ### 2 | # This file is only here to make sure that something like 3 | # 4 | # pip install -e . 5 | # 6 | # works as expected. Requirements can be found in setup.py. 7 | ### 8 | 9 | . 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | plugin_identifier = "touchui" 4 | plugin_package = "octoprint_touchui" 5 | plugin_name = "TouchUI" 6 | plugin_version = "0.3.18" 7 | plugin_description = """A touch friendly interface for a small TFT modules and or phones""" 8 | plugin_author = "Paul de Vries" 9 | plugin_author_email = "pablo+octoprint+touch+ui@aerosol.me" 10 | plugin_url = "https://github.com/BillyBlaze/OctoPrint-TouchUI" 11 | plugin_license = "AGPLv3" 12 | plugin_requires = ["OctoPrint>=1.2.4"] 13 | plugin_additional_data = [] 14 | plugin_addtional_packages = [] 15 | plugin_ignored_packages = [] 16 | additional_setup_parameters = {} 17 | 18 | from setuptools import setup 19 | 20 | try: 21 | import octoprint_setuptools 22 | except: 23 | print("Could not import OctoPrint's setuptools, are you sure you are running that under " 24 | "the same python installation that OctoPrint is installed under?") 25 | import sys 26 | sys.exit(-1) 27 | 28 | setup_parameters = octoprint_setuptools.create_plugin_setup_parameters( 29 | identifier=plugin_identifier, 30 | package=plugin_package, 31 | name=plugin_name, 32 | version=plugin_version, 33 | description=plugin_description, 34 | author=plugin_author, 35 | mail=plugin_author_email, 36 | url=plugin_url, 37 | license=plugin_license, 38 | requires=plugin_requires, 39 | additional_packages=plugin_addtional_packages, 40 | ignored_packages=plugin_ignored_packages, 41 | additional_data=plugin_additional_data 42 | ) 43 | 44 | if len(additional_setup_parameters): 45 | from octoprint.util import dict_merge 46 | setup_parameters = dict_merge(setup_parameters, additional_setup_parameters) 47 | 48 | setup(**setup_parameters) 49 | 50 | try: 51 | import os 52 | fileOut = "./octoprint_touchui/WHATSNEW.md" 53 | fileIn = "./WHATSNEW.md" 54 | 55 | if os.path.isfile(fileOut): 56 | os.unlink(fileOut) 57 | 58 | with open(fileIn, 'r') as contentFile: 59 | whatsNew = contentFile.read() 60 | 61 | with open(fileOut, "w+") as writeFile: 62 | writeFile.write('{WHATSNEW}'.format(WHATSNEW=whatsNew)) 63 | 64 | except Exception as e: 65 | print("\nCopying the WHATSNEW.md failed, however it shouldn't matter, just read the release notes on github if you like.\nThe error: {message}".format(message=str(e))) 66 | -------------------------------------------------------------------------------- /source/js/animate/hide.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.animate.hide = function(what) { 2 | var self = this; 3 | 4 | //Lets hide the navbar by scroll 5 | if( what === "navbar" ) { 6 | if( this.animate.isHidebarActive() ) { 7 | var navbar = $("#navbar"), 8 | navbarHeight = parseFloat(navbar.height()); 9 | 10 | if( this.settings.hasTouch ) { 11 | // Hide navigation bar on mobile 12 | window.scrollTo(0,1); 13 | 14 | if(parseFloat($("html,body").prop('scrollHeight')) > ($(window).height() + navbarHeight)) {//hasEnoughScroll? 15 | $("html,body").stop().animate({ 16 | scrollTop: navbarHeight 17 | }, 160, "swing"); 18 | } 19 | 20 | } else { 21 | var scroll = self.scroll.iScrolls.body; 22 | 23 | if(scroll.isAnimating) { 24 | setTimeout(function() { 25 | self.animate.hide.call(self, what); 26 | }, 10); 27 | return; 28 | } 29 | 30 | setTimeout(function() { 31 | if(Math.abs(scroll.maxScrollY) > 0) { 32 | scroll.scrollTo(0, -navbarHeight, 160); 33 | } 34 | }, 0); 35 | 36 | } 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /source/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | !function() { 2 | 3 | // Catch errors 4 | if (TouchUI.prototype.settings.hasBootloader) { 5 | if (window.log && window.log.error) { 6 | var old = window.log.error; 7 | window.log.error = function(plugin, msg) { 8 | window.top.postMessage([msg, ''], "*"); 9 | old.apply(window.log, arguments); 10 | } 11 | } 12 | } 13 | 14 | var Touch = new TouchUI(); 15 | Touch.domLoading(); 16 | 17 | $(function() { 18 | Touch.domReady(); 19 | 20 | OCTOPRINT_VIEWMODELS.push([ 21 | Touch.koStartup, 22 | Touch.TOUCHUI_REQUIRED_VIEWMODELS, 23 | Touch.TOUCHUI_ELEMENTS, 24 | Touch.TOUCHUI_REQUIRED_VIEWMODELS 25 | ]); 26 | }); 27 | 28 | }(); 29 | -------------------------------------------------------------------------------- /source/js/components/dropdown.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.components.dropdown = { 2 | 3 | init: function() { 4 | this.components.dropdown.toggleSubmenu.call( this ); 5 | this.components.dropdown.toggle.call( this ); 6 | }, 7 | 8 | // Rewrite opening of dropdowns 9 | toggle: function() { 10 | var self = this; 11 | var namespace = ".touchui.dropdown"; 12 | 13 | $(document) 14 | .off('.dropdown') 15 | .on('touchstart.dropdown.data-api', '.dropdown-menu', function (e) { e.stopPropagation() }) 16 | .on('click.dropdown.data-api', '[data-toggle=dropdown]', function(e) { 17 | var $dropdownToggle = $(e.currentTarget); 18 | var $dropdownContainer = $dropdownToggle.parent(); 19 | 20 | // Stop the hashtag from propagating 21 | e.preventDefault(); 22 | 23 | // Toggle the targeted dropdown 24 | $dropdownContainer.toggleClass("open"); 25 | 26 | // Refresh current scroll and add a min-height so we can reach the dropdown if needed 27 | self.components.dropdown.containerMinHeight.call(self, $dropdownContainer, $dropdownToggle); 28 | 29 | // Skip everything if we are in a dropdown toggling a dropdown (one click event is enuff!) 30 | if( $dropdownContainer.parents('.open > .dropdown-menu').length > 0 ) { 31 | return; 32 | } 33 | 34 | // Remove all other active dropdowns 35 | $('.open [data-toggle="dropdown"]').not($dropdownToggle).parent().removeClass('open'); 36 | 37 | if ( !self.settings.hasTouch ) { 38 | self.scroll.iScrolls.terminal.disable(); 39 | } 40 | 41 | $(document).off("click"+namespace).on("click"+namespace, function(eve) { 42 | // Check if we scrolled (touch devices wont trigger this click event after scrolling so assume we didn't move) 43 | var moved = ( !self.settings.hasTouch ) ? self.scroll.currentActive.moved : false, 44 | $target = $(eve.target); 45 | 46 | if ( 47 | !moved && // If scrolling did not move 48 | $target.parents(".ui-pnotify").length === 0 && // if not a click within notifiaction 49 | ( 50 | !$target.parents().is($dropdownContainer) || // if clicks are not made within the dropdown container 51 | $target.is('a:not([data-toggle="dropdown"])') // Unless it's a link but not a [data-toggle] 52 | ) 53 | ) { 54 | $(document).off(eve); 55 | $dropdownContainer.removeClass('open'); 56 | 57 | if ( !self.settings.hasTouch ) { 58 | $('.octoprint-container').css("min-height", 0); 59 | self.scroll.currentActive.refresh(); 60 | self.scroll.iScrolls.terminal.enable(); 61 | } 62 | } 63 | }); 64 | }); 65 | 66 | }, 67 | 68 | // Support 1.3.0 onMouseOver dropdowns 69 | toggleSubmenu: function() { 70 | $(".dropdown-submenu").addClass("dropdown"); 71 | $(".dropdown-submenu > a").attr("data-toggle", "dropdown"); 72 | }, 73 | 74 | // Refresh current scroll and add a min-height so we can reach the dropdown if needed 75 | containerMinHeight: function($dropdownContainer, $dropdownToggle) { 76 | var self = this; 77 | 78 | // Touch devices can reach the dropdown by CSS, only if we're using iScroll 79 | if ( !self.settings.hasTouch ) { 80 | // Get active container 81 | var $container = ($dropdownContainer.parents('.modal').length === 0 ) ? $('.octoprint-container') : $dropdownContainer.parents('.modal .modal-body'); 82 | 83 | // If we toggle within the dropdown then get the parent dropdown for total height 84 | var $dropdownMenu = ( $dropdownContainer.parents('.open > .dropdown-menu').length > 0 ) ? $dropdownContainer.parents('.open > .dropdown-menu') : $dropdownToggle.next(); 85 | 86 | setTimeout(function() { 87 | 88 | //If the main dropdown has closed (by toggle) then let's remove the min-height else 89 | if(!$dropdownMenu.parent().hasClass("open")) { 90 | $container.css("min-height", 0); 91 | self.scroll.currentActive.refresh(); 92 | } else { 93 | var y = Math.abs(self.scroll.currentActive.y), 94 | height = $dropdownMenu.outerHeight(), 95 | top = $dropdownMenu.offset().top; 96 | 97 | $container.css("min-height", y + top + height); 98 | self.scroll.currentActive.refresh(); 99 | } 100 | 101 | }, 0); 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /source/js/components/fullscreen.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.components.fullscreen = { 2 | init: function() { 3 | var self = this; 4 | 5 | // Bind fullscreenChange to knockout 6 | $(document).bind("fullscreenchange", function() { 7 | self.settings.isFullscreen($(document).fullScreen() !== false); 8 | self.DOM.storage.set("fullscreen", self.settings.isFullscreen()); 9 | }); 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source/js/components/keyboard.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.components.keyboard = { 2 | 3 | isActive: ko.observable(false), 4 | config: { 5 | 6 | default: { 7 | usePreview: false, 8 | autoAccept: true, 9 | 10 | display: { 11 | 'accept' : 'Save', 12 | 'bksp' : ' ', 13 | 'default': 'ABC', 14 | 'meta1' : '.?123', 15 | 'meta2' : '#+=' 16 | }, 17 | 18 | layout: 'custom', 19 | customLayout: { 20 | 'default': [ 21 | 'q w e r t y u i o p', 22 | 'a s d f g h j k l', 23 | '{bksp} {s} z x c v b n m', 24 | '{accept} {c} {left} {right} {meta1} {space}' 25 | ], 26 | 'shift': [ 27 | 'Q W E R T Y U I O P', 28 | 'A S D F G H J K L', 29 | '{bksp} {s} Z X C V B N M', 30 | '{accept} {c} {left} {right} {meta1} {space}' 31 | ], 32 | 'meta1': [ 33 | '1 2 3 4 5 6 7 8 9 0', 34 | '- / : ; ( ) \u20ac & @', 35 | '{bksp} {meta2} . , ? ! \' "', 36 | '{accept} {c} {left} {right} {default} {space}' 37 | ], 38 | 'meta2': [ 39 | '[ ] { } # % ^ * + =', 40 | '_ \\ | ~ < > $ \u00a3 \u00a5', 41 | '{bksp} {meta1} . , ? ! \' "', 42 | '{accept} {c} {left} {right} {default} {space}' 43 | ] 44 | } 45 | 46 | }, 47 | terminal: { 48 | usePreview: false, 49 | autoAccept: true, 50 | 51 | display: { 52 | 'bksp' : ' ', 53 | 'accept' : 'Save', 54 | 'default': 'ABC', 55 | 'meta1' : '.?123', 56 | 'meta2' : '#+=' 57 | }, 58 | 59 | layout: 'custom', 60 | customLayout: { 61 | 'default': [ 62 | 'Q W E R T Y U I O P', 63 | 'A S D F G H J K L', 64 | '{bksp} {s} Z X C V B N M', 65 | '{accept} {c} {left} {right} {meta1} {space}' 66 | ], 67 | 'meta1': [ 68 | '1 2 3 4 5 6 7 8 9 0', 69 | '- / : ; ( ) \u20ac & @', 70 | '{bksp} {meta2} . , ? ! \' "', 71 | '{accept} {c} {left} {right} {default} {space}' 72 | ], 73 | 'meta2': [ 74 | '[ ] { } # % ^ * + =', 75 | '_ \\ | ~ < > $ \u00a3 \u00a5', 76 | '{bksp} {meta1} . , ? ! \' "', 77 | '{accept} {c} {left} {right} {default} {space}' 78 | ] 79 | } 80 | 81 | }, 82 | number: { 83 | usePreview: false, 84 | autoAccept: true, 85 | 86 | display: { 87 | 'bksp' : ' ', 88 | 'a' : 'Save', 89 | 'c' : 'Cancel' 90 | }, 91 | 92 | layout: 'custom', 93 | customLayout: { 94 | 'default' : [ 95 | '{bksp} 1 2 3 4 5 6 7 ', 96 | '{accept} {c} 8 9 0 - , . ' 97 | ] 98 | }, 99 | } 100 | 101 | 102 | }, 103 | 104 | init: function() { 105 | var self = this; 106 | 107 | // Add virtual keyboard 108 | var obj = { 109 | visible: self.components.keyboard.onShow.bind(self), 110 | beforeClose: self.components.keyboard.onClose 111 | }; 112 | 113 | var notThis = ['[type="file"]','[type="checkbox"]','[type="radio"]']; 114 | $(document).on("mousedown", 'input:not('+notThis+'), textarea', function(e) { 115 | var $elm = $(e.target); 116 | 117 | if(!self.components.keyboard.isActive()) { 118 | 119 | if($elm.data("keyboard")) { 120 | $elm.data("keyboard").close().destroy(); 121 | } 122 | 123 | } else { 124 | // $elm already has a keyboard 125 | if($elm.data("keyboard")) { 126 | $elm.data('keyboard').reveal(); 127 | return; 128 | } 129 | 130 | if($elm.attr("type") === "number") { 131 | $elm.keyboard($.extend(self.components.keyboard.config.number, obj)); 132 | } else if($elm.attr("id") === "terminal-command") { 133 | $elm.keyboard($.extend(self.components.keyboard.config.terminal, obj)); 134 | } else { 135 | $elm.keyboard($.extend(self.components.keyboard.config.default, obj)); 136 | } 137 | } 138 | 139 | }); 140 | }, 141 | 142 | onShow: function(event, keyboard, el) { 143 | var self = this; 144 | 145 | keyboard.$keyboard.find("button").on("mousedown, touchstart", function(e) { 146 | var $elm = $(e.target); 147 | $elm.addClass("touch-focus"); 148 | 149 | if($elm.data("timeout")) { 150 | clearTimeout($elm.data("timeout")); 151 | } 152 | 153 | var timeout = setTimeout(function() { 154 | $elm.removeClass("touch-focus").data("timeout", ""); 155 | }, 1000); 156 | 157 | $elm.data("timeout", timeout); 158 | }); 159 | 160 | if(!self.settings.hasTouch) { 161 | var height = keyboard.$keyboard.height(); 162 | $('#page-container-main').css('padding-bottom', height); 163 | 164 | // Force iScroll to stop following the mouse (bug) 165 | self.scroll.currentActive._end(new Event('click')); 166 | setTimeout(function() { 167 | self.scroll.currentActive.scrollToElement(keyboard.$el[0], 200, 0, -30); 168 | }, 100); 169 | 170 | } 171 | }, 172 | 173 | onClose: function(event, keyboard, el) { 174 | keyboard.$keyboard.find("button").off("mousedown, touchstart"); 175 | $('#page-container-main').css('padding-bottom', 0); 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /source/js/components/modal.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.components.modal = { 2 | 3 | init: function() { 4 | if($("#settings_dialog_menu").length > 0) { 5 | this.components.modal.dropdown.create.call(this, "#settings_dialog_menu", "special-dropdown-uni", "#settings_dialog_label"); 6 | } 7 | if($("#usersettings_dialog ul.nav").length > 0) { 8 | this.components.modal.dropdown.create.call(this, "#usersettings_dialog ul.nav", "special-dropdown-uni-2", "#usersettings_dialog h3"); 9 | } 10 | }, 11 | 12 | dropdown: { 13 | create: function(cloneId, newId, appendTo) { 14 | var self = this; 15 | 16 | // Remove unwanted whitespaces 17 | $(appendTo).text($(appendTo).text().trim()); 18 | 19 | // Create a label that is clickable 20 | var $settingsLabel = $("") 21 | .addClass("hidden") 22 | .attr("id", newId) 23 | .appendTo(appendTo) 24 | .text($(cloneId+" .active").text().trim()) 25 | .on("click", function(e) { 26 | 27 | // Stop if we clicked on the dropdown and stop the dropdown from regenerating more then once 28 | if(e.target !== this || (e.target === this && $(".show-dropdown").length > 0)) { 29 | return; 30 | } 31 | 32 | // Clone the main settings menu 33 | var elm = $(cloneId) 34 | .clone() 35 | .attr("id", "") 36 | .appendTo(this) 37 | .addClass("show-dropdown"); 38 | 39 | // Add click binder to close down the dropdown 40 | $(document).on("click", function(event) { 41 | 42 | if( 43 | $(event.target).closest('[data-toggle="tab"]').length > 0 || //Check if we clicked on a tab-link 44 | $(event.target).closest("#"+newId).length === 0 //Check if we clicked outside the dropdown 45 | ) { 46 | var href = $settingsLabel.find(".active").find('[data-toggle="tab"]').attr("href"); 47 | $(document).off(event).trigger("dropdown-closed.touchui"); // Trigger event for enabling scrolling 48 | 49 | $('.show-dropdown').remove(); 50 | $('[href="'+href+'"]').click(); 51 | $settingsLabel.text($('[href="'+href+'"]').text()); 52 | 53 | if( !self.settings.hasTouch ) { 54 | setTimeout(function() { 55 | self.scroll.modal.stack[self.scroll.modal.stack.length-1].refresh(); 56 | }, 0); 57 | } 58 | } 59 | 60 | }); 61 | 62 | // Trigger event for disabling scrolling 63 | $(document).trigger("dropdown-open.touchui", elm[0]); 64 | }); 65 | 66 | // reset the active text in dropdown on open 67 | $(appendTo) 68 | .closest(".modal") 69 | .on("modal.touchui", function() { 70 | var href = $(cloneId) 71 | .find(".active") 72 | .find('[data-toggle="tab"]') 73 | .attr("href"); 74 | 75 | $settingsLabel.text($('[href="'+href+'"]').text()); 76 | }); 77 | 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /source/js/components/slider.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.components.slider = { 2 | 3 | init: function() { 4 | 5 | ko.bindingHandlers.slider = { 6 | init: function (element, valueAccessor) { 7 | var $element = $(element); 8 | 9 | // Set value on input field 10 | $element.val(valueAccessor().value()); 11 | 12 | // Create container 13 | var div = $('
').insertBefore(element); 14 | 15 | // Wait untill next DOM bindings are executed 16 | setTimeout(function() { 17 | var $button = $(element).next('button'); 18 | var id = _.uniqueId("ui-inp"); 19 | var text = $button.text().split(":")[0].replace(" ", ""); 20 | 21 | $button.appendTo(div); 22 | $element.appendTo(div); 23 | 24 | $(div).find('input').attr("id", id); 25 | 26 | var lbl = $(''); 27 | lbl.appendTo('.octoprint-container') 28 | $element.attr("style", "padding-left:" + (lbl.width() + 15) + "px"); 29 | lbl.appendTo(div); 30 | 31 | if (valueAccessor().tools && valueAccessor().tools.length === 0) { 32 | div.hide(); 33 | } else { 34 | div.show(); 35 | } 36 | 37 | }, 60); 38 | 39 | $element.on("change", function(e) { 40 | valueAccessor().value(parseFloat($element.val())); 41 | }).attr({ 42 | max: valueAccessor().max, 43 | min: valueAccessor().min, 44 | step: valueAccessor().step, 45 | }); 46 | 47 | }, 48 | update: function (element, valueAccessor) { 49 | $(element).val(parseFloat(valueAccessor().value())); 50 | 51 | if (valueAccessor().tools && valueAccessor().tools.length === 0) { 52 | $(element).parent().hide(); 53 | } else { 54 | $(element).parent().show(); 55 | } 56 | } 57 | }; 58 | 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /source/js/components/touch-list.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.components.touchList = { 2 | init: function() { 3 | 4 | /* Add touch friendly files list */ 5 | var self = this; 6 | var touch = false; 7 | var start = 0; 8 | var namespace = ".files.touchui"; 9 | 10 | $(document).on("mousedown touchstart", "#files .entry:not(.back), #temp .row-fluid", function(e) { 11 | try { 12 | touch = e.currentTarget; 13 | start = e.pageX || e.originalEvent.targetTouches[0].pageX; 14 | } catch(err) { 15 | return; 16 | } 17 | 18 | $(document).one("mouseup"+namespace+" touchend"+namespace, function(e) { 19 | touch = false; 20 | start = 0; 21 | 22 | $(document).off(namespace); 23 | }); 24 | 25 | $(document).on("mousemove"+namespace+" touchmove"+namespace, function(event) { 26 | if(touch !== false) { 27 | try { 28 | var current = event.pageX || event.originalEvent.targetTouches[0].pageX; 29 | 30 | if(current > start + 80) { 31 | $(document).trigger("fileclose" + namespace, event.target); 32 | $(touch).removeClass("open"); 33 | start = current; 34 | } else if(current < start - 80) { 35 | $(document).trigger("fileopen" + namespace, event.target); 36 | $(touch).addClass("open"); 37 | start = current; 38 | 39 | if( $(touch).find(".btn-group").children().length > 4 ) { 40 | $(touch).addClass("large"); 41 | } 42 | } 43 | } catch(err) { 44 | //Ignore step 45 | } 46 | } 47 | }); 48 | 49 | }); 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /source/js/components/touchscreen.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.components.touchscreen = { 2 | 3 | init: function () { 4 | $("html").addClass("isTouchscreenUI"); 5 | 6 | if (this.settings.isEpiphanyOrKweb) { 7 | this.settings.hasTouch = false; 8 | this.scroll.defaults.iScroll.disableTouch = true; 9 | this.scroll.defaults.iScroll.disableMouse = false; 10 | } 11 | 12 | this.settings.isTouchscreen(true); 13 | 14 | if (this.settings.hasBootloader) { 15 | this.settings.hasFullscreen(false); 16 | } 17 | 18 | $('.modal.fade').removeClass('fade'); 19 | $('#gcode_link').remove(); 20 | 21 | // Improve performace 22 | this.scroll.defaults.iScroll.scrollbars = false; 23 | this.scroll.defaults.iScroll.interactiveScrollbars = false; 24 | this.scroll.defaults.iScroll.useTransition = false; 25 | // this.scroll.defaults.iScroll.useTransform = false; 26 | // this.scroll.defaults.iScroll.HWCompositing = false; 27 | 28 | // Remove any links opening in a new tab 29 | $('[target="_blank"]').each(function(ind, elm) { 30 | $(elm) 31 | .attr("target", "") 32 | .on("click", function(e) { 33 | return confirm("Do you want to navigate away from TouchUI?"); 34 | }); 35 | }); 36 | 37 | // disable dashboard layer progress 38 | if ($('.dashboardGridItem').length > 0) { 39 | $('.dashboardGridItem').each(function(i, elm) { 40 | if ($(elm).find('[title="Layer Progress"]').length > 0) { 41 | $(elm).remove(); 42 | } 43 | }); 44 | } 45 | }, 46 | 47 | isLoading: function (viewModels) { 48 | 49 | if(this.settings.isTouchscreen()) { 50 | // Disable fancy functionality 51 | if(viewModels.terminalViewModel.enableFancyFunctionality) { //TODO: check if 1.2.9 to not throw errors in 1.2.8< 52 | viewModels.terminalViewModel.enableFancyFunctionality(false); 53 | } 54 | 55 | // Disable GCodeViewer in touchscreen mode 56 | if (viewModels.gcodeViewModel) { 57 | console.info("TouchUI: GCodeViewer is disabled while TouchUI is active and in touchscreen mode."); 58 | viewModels.gcodeViewModel.enabled = false; 59 | viewModels.gcodeViewModel.initialize = _.noop; 60 | viewModels.gcodeViewModel.clear = _.noop; 61 | viewModels.gcodeViewModel._processData = _.noop; 62 | } 63 | } 64 | 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /source/js/constructor.js: -------------------------------------------------------------------------------- 1 | var TouchUI = function() { 2 | this.core.init.call(this); 3 | this.knockout.viewModel.call(this); 4 | this.knockout.bindings.call(this); 5 | return this.core.bridge.call(this); 6 | }; 7 | 8 | TouchUI.prototype = { 9 | constructor: TouchUI, 10 | isActive: ko.observable(false), 11 | 12 | settings: { 13 | id: "touch", 14 | version: 0, 15 | requiredBootloaderVersion: 1, 16 | 17 | isFullscreen: ko.observable(false), 18 | isTouchscreen: ko.observable(false), 19 | 20 | isEpiphanyOrKweb: window.navigator.userAgent.indexOf("AppleWebKit") !== -1 && window.navigator.userAgent.indexOf("ARM Mac OS X") !== -1, 21 | isChromiumArm: window.navigator.userAgent.indexOf("X11") !== -1 && window.navigator.userAgent.indexOf("Chromium") !== -1 && window.navigator.userAgent.indexOf("armv7l") !== -1, 22 | 23 | hasBootloader: window.navigator.userAgent.indexOf("TouchUI") !== -1, 24 | hasFullscreen: ko.observable(document.webkitCancelFullScreen || document.msCancelFullScreen || document.oCancelFullScreen || document.mozCancelFullScreen || document.cancelFullScreen), 25 | hasLocalStorage: ('localStorage' in window), 26 | hasTouch: ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0), 27 | 28 | canBoot: { 29 | resolution: $("#touchui_auto_load_resolution").length > 0, 30 | touch: $("#touchui_auto_load_touch").length > 0 31 | }, 32 | 33 | whatsNew: ko.observable(false) 34 | }, 35 | 36 | core: {}, 37 | components: {}, 38 | knockout: {}, 39 | plugins: {}, 40 | animate: { 41 | isHidebarActive: ko.observable(false) 42 | }, 43 | DOM: { 44 | create: {}, 45 | move: {}, 46 | overwrite: {} 47 | }, 48 | scroll: { 49 | 50 | defaults: { 51 | iScroll: { 52 | eventPassthrough: 'horizontal', 53 | scrollbars: true, 54 | mouseWheel: true, 55 | interactiveScrollbars: true, 56 | shrinkScrollbars: "scale", 57 | fadeScrollbars: true 58 | } 59 | }, 60 | 61 | iScrolls: {}, 62 | currentActive: null 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /source/js/core/_init.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.core.init = function() { 2 | 3 | // Migrate old cookies into localstorage 4 | this.DOM.storage.migration.call(this); 5 | 6 | // Bootup TouchUI if Touch, Small resolution or storage say's so 7 | if (this.core.boot.call(this)) { 8 | 9 | // Send Touchscreen loading status 10 | if (window.top.postMessage) { 11 | window.top.postMessage("loading", "*"); 12 | } 13 | 14 | // Attach id for TouchUI styling 15 | $("html").attr("id", this.settings.id); 16 | 17 | // Force mobile browser to set the window size to their format 18 | $('').appendTo("head"); 19 | $('').appendTo("head"); 20 | $('').appendTo("head"); 21 | 22 | this.isActive(true); 23 | 24 | // Enforce active cookie 25 | this.DOM.storage.set("active", true); 26 | 27 | var isTouchDevice = this.settings.isEpiphanyOrKweb || this.settings.isChromiumArm || this.settings.hasBootloader; 28 | 29 | // Create keyboard cookie if not existing 30 | if (this.DOM.storage.get("keyboardActive") === undefined) { 31 | if (!this.settings.hasTouch || isTouchDevice) { 32 | this.DOM.storage.set("keyboardActive", true); 33 | } else { 34 | this.DOM.storage.set("keyboardActive", false); 35 | } 36 | } 37 | 38 | // Create hide navbar on click if not existing 39 | if (this.DOM.storage.get("hideNavbarActive") === undefined) { 40 | this.DOM.storage.set("hideNavbarActive", false); 41 | } 42 | 43 | // Treat KWEB3 as a special Touchscreen mode or enabled by cookie 44 | if ( 45 | this.DOM.storage.get("touchscreenActive") || 46 | ( 47 | isTouchDevice && 48 | this.DOM.storage.get("touchscreenActive") === undefined 49 | ) 50 | ) { 51 | this.components.touchscreen.init.call(this); 52 | } 53 | 54 | // If TouchUI has been started through bootloader then initialize the process during reloads 55 | if (this.settings.hasBootloader && window.top.postMessage) { 56 | window.onbeforeunload = function() { 57 | window.top.postMessage("reset", "*"); 58 | }; 59 | } 60 | 61 | // Get state of cookies and store them in KO 62 | this.components.keyboard.isActive(this.DOM.storage.get("keyboardActive")); 63 | this.animate.isHidebarActive(this.DOM.storage.get("hideNavbarActive")); 64 | this.settings.isFullscreen($(document).fullScreen() !== false); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /source/js/core/boot.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.core.boot = function() { 2 | 3 | // This should always start TouchUI 4 | if( 5 | document.location.hash === "#touch" || 6 | document.location.href.indexOf("?touch") > 0 || 7 | this.DOM.storage.get("active") || 8 | this.settings.hasBootloader 9 | ) { 10 | 11 | return true; 12 | 13 | } else if( 14 | this.DOM.storage.get("active") !== false 15 | ) { 16 | 17 | if($(window).width() < 980 && this.settings.canBoot.resolution) { 18 | return true; 19 | } 20 | 21 | if(this.settings.hasTouch && this.settings.canBoot.touch) { 22 | return true; 23 | } 24 | 25 | } 26 | 27 | return false; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /source/js/core/bridge.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.core.bridge = function() { 2 | var self = this; 3 | 4 | this.core.bridge = { 5 | 6 | allViewModels: {}, 7 | TOUCHUI_REQUIRED_VIEWMODELS: [ 8 | "terminalViewModel", 9 | "connectionViewModel", 10 | "settingsViewModel", 11 | "softwareUpdateViewModel", 12 | "controlViewModel", 13 | "gcodeFilesViewModel", 14 | "navigationViewModel", 15 | "pluginManagerViewModel", 16 | "temperatureViewModel", 17 | "loginStateViewModel" 18 | ], 19 | TOUCHUI_ELEMENTS: [ 20 | "#touchui_settings_dialog", 21 | "#settings_plugin_touchui", 22 | "#navbar_plugin_touchui" 23 | ], 24 | 25 | domLoading: function() { 26 | if (self.isActive()) { 27 | self.scroll.beforeLoad.call(self); 28 | self.DOM.init.call(self); 29 | 30 | if (moment && moment.locale) { 31 | // Overwrite the 'moment.locale' fuction and call original: 32 | // The purpose is that we want to remove data before 33 | // registering it to OctoPrint. Moment.locale is called 34 | // just before this happens. 35 | var old = moment.locale; 36 | moment.locale = function() { 37 | self.plugins.disable.init.call(self); 38 | old.apply(moment, arguments); 39 | }; 40 | } 41 | } 42 | }, 43 | 44 | domReady: function() { 45 | if (self.isActive()) { 46 | 47 | if($("#gcode").length > 0) { 48 | self.core.bridge.TOUCHUI_REQUIRED_VIEWMODELS = self.core.bridge.TOUCHUI_REQUIRED_VIEWMODELS.concat(["gcodeViewModel"]); 49 | } 50 | 51 | self.components.dropdown.init.call(self); 52 | self.components.fullscreen.init.call(self); 53 | self.components.keyboard.init.call(self); 54 | self.components.modal.init.call(self); 55 | self.components.touchList.init.call(self); 56 | self.components.slider.init.call(self); 57 | 58 | self.scroll.init.call(self); 59 | } 60 | }, 61 | 62 | koStartup: function TouchUIViewModel(viewModels) { 63 | self.core.bridge.allViewModels = _.object(self.core.bridge.TOUCHUI_REQUIRED_VIEWMODELS, viewModels); 64 | self.knockout.isLoading.call(self, self.core.bridge.allViewModels); 65 | return self; 66 | } 67 | } 68 | 69 | return this.core.bridge; 70 | } 71 | -------------------------------------------------------------------------------- /source/js/core/less.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.core.less = { 2 | 3 | options: { 4 | template: { 5 | importUrl: "./plugin/touchui/static/less/touchui.bundled.less?t=" + new Date().getTime(), 6 | import: '@import "{importUrl}"; \n', 7 | variables: "@main-color: {mainColor}; \n" + 8 | "@terminal-color: {termColor}; \n" + 9 | "@text-color: {textColor}; \n" + 10 | "@main-font-size: {fontSize}px; \n" + 11 | "@main-background: {bgColor}; \n\n" 12 | }, 13 | API: "./plugin/touchui/css" 14 | }, 15 | 16 | save: function() { 17 | var options = this.core.less.options; 18 | var self = this; 19 | 20 | if(self.settings.useCustomization()) { 21 | if(self.settings.colors.useLocalFile()) { 22 | 23 | $.get(options.API, { 24 | path: self.settings.colors.customPath() 25 | }) 26 | .done(function(response) { 27 | self.core.less.render.call(self, options.template.import.replace("{importUrl}", options.template.importUrl) + response); 28 | }) 29 | .error(function(error) { 30 | self.core.less.error.call(self, error); 31 | }); 32 | 33 | } else { 34 | 35 | self.core.less.render.call(self, "" + 36 | options.template.import.replace("{importUrl}", options.template.importUrl) + 37 | options.template.variables.replace("{mainColor}", self.settings.colors.mainColor()) 38 | .replace("{termColor}", self.settings.colors.termColor()) 39 | .replace("{textColor}", self.settings.colors.textColor()) 40 | .replace("{bgColor}", self.settings.colors.bgColor()) 41 | .replace("{fontSize}", self.settings.colors.fontSize()) 42 | ); 43 | 44 | } 45 | } 46 | }, 47 | 48 | render: function(data) { 49 | var self = this; 50 | var callback = function(error, result) { 51 | 52 | if (error) { 53 | self.core.less.error.call(self, { responseText: 'Less parser: ' + error.message, status: 0 }); 54 | console.error(error); 55 | } else { 56 | result.css = result.css.replace(/mixin\:placeholder\;/g, ''); 57 | 58 | $.post(self.core.less.options.API, { 59 | css: result.css 60 | }) 61 | .done(function() { 62 | self.settings.refreshCSS(true); 63 | $(window).trigger('resize'); 64 | }) 65 | .fail(function(error) { 66 | self.core.less.error.call(self, error); 67 | }); 68 | 69 | } 70 | } 71 | 72 | if(window.less.render) { 73 | window.less.render(data, { 74 | compress: true 75 | }, callback); 76 | } else { 77 | window.less.Parser({}).parse(data, function(error, result) { 78 | if(result) { 79 | result = { 80 | css: result.toCSS({ 81 | compress: true 82 | }) 83 | } 84 | } 85 | callback.call(this, error, result); 86 | }); 87 | } 88 | }, 89 | 90 | error: function(error) { 91 | var content = error.responseText; 92 | if(content && content.trim() && error.status !== 401) { 93 | new PNotify({ 94 | title: 'TouchUI: Whoops, something went wrong...', 95 | text: content, 96 | icon: 'glyphicon glyphicon-question-sign', 97 | type: 'error', 98 | hide: false 99 | }); 100 | } 101 | 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /source/js/core/version.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.core.version = { 2 | 3 | init: function(softwareUpdateViewModel) { 4 | var self = this; 5 | 6 | $("").appendTo("#terminal-output"); 7 | 8 | if(softwareUpdateViewModel) { 9 | 10 | softwareUpdateViewModel.versions.items.subscribe(function(changes) { 11 | 12 | var touchui = softwareUpdateViewModel.versions.getItem(function(elm) { 13 | return (elm.key === "touchui"); 14 | }, true) || false; 15 | 16 | if( touchui !== false && touchui.information !== null ) { 17 | var remote = Number(touchui.information.remote.value.split('.').join('')), 18 | local = Number(touchui.information.local.value.split('.').join('')); 19 | 20 | if(remote > local) { 21 | $("#touch_updates_css").remove(); 22 | $('head').append(''); 23 | } else { 24 | if( $("#touch_updates_css").length === 0 ) { 25 | $('head').append(''); 26 | } 27 | } 28 | } 29 | 30 | }); 31 | 32 | } 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /source/js/dom/_init.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.init = function() { 2 | 3 | // Create new tab with printer status and make it active 4 | this.DOM.create.printer.init(this.DOM.create.tabbar); 5 | this.DOM.create.printer.menu.$elm.find('a').trigger("click"); 6 | 7 | // Create a new persistent dropdown 8 | this.DOM.create.dropdown.init.call( this.DOM.create.dropdown ); 9 | 10 | // Add a webcam tab if it's defined 11 | if ($("#webcam_container").length > 0) { 12 | this.DOM.create.webcam.init(this.DOM.create.tabbar); 13 | } 14 | 15 | // Move all other items from tabbar into dropdown 16 | this.DOM.move.sidebar.init.call(this); 17 | this.DOM.move.navbar.init.call(this); 18 | this.DOM.move.tabbar.init.call(this); 19 | this.DOM.move.afterTabAndNav.call(this); 20 | this.DOM.move.overlays.init.call(this); 21 | this.DOM.move.terminal.init.call(this); 22 | 23 | // Move connection sidebar into a new modal 24 | this.DOM.move.connection.init(this.DOM.create.tabbar); 25 | 26 | // Manipulate controls div 27 | this.DOM.move.controls.init(); 28 | 29 | // Disable these bootstrap/jquery plugins 30 | this.DOM.overwrite.tabdrop.call(this); 31 | this.DOM.overwrite.modal.call(this); 32 | this.DOM.overwrite.pnotify.call(this); 33 | 34 | // Add class with how many tab-items 35 | $("#tabs, #navbar").addClass("items-" + $("#tabs li:not(.hidden_touch)").length); 36 | 37 | // Remove active class when clicking on a tab in the tabbar 38 | $('#tabs [data-toggle=tab]').on("click", function() { 39 | $("#all_touchui_settings").removeClass("item_active"); 40 | }); 41 | 42 | // If touch emulator is enabled, then disable dragging of a menu item for scrolling 43 | if(!this.settings.hasTouch) { 44 | $("#navbar ul.nav > li a").on("dragstart drop", function(e) { 45 | return false; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/js/dom/cookies.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.cookies = { 2 | 3 | get: function(key, isPlain) { 4 | var name = (isPlain) ? key + "=" : "TouchUI." + key + "="; 5 | var ca = document.cookie.split(';'); 6 | var tmp; 7 | for(var i=0; i' + 12 | '' + 13 | $('navbar_show_settings').text() || $('navbar_show_settings').attr("title") + 14 | '' + 15 | '').prependTo(this.menuItem.cloneTo); 16 | 17 | this.container = $('').appendTo(this.menuItem.menu); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /source/js/dom/create/printer.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.create.printer = { 2 | 3 | menu: { 4 | cloneTo: "#tabs" 5 | }, 6 | 7 | container: { 8 | cloneTo: "#temp" 9 | }, 10 | 11 | move: { 12 | $state: $("#state_wrapper"), 13 | $files: $("#files_wrapper") 14 | }, 15 | 16 | init: function( tabbar ) { 17 | this.menu.$elm = tabbar.createItem("print_link", "printer", "tab").prependTo(this.menu.cloneTo); 18 | this.container.$elm = $('
').insertBefore(this.container.cloneTo); 19 | 20 | // Move the contents of the hidden accordions to the new print status and files tab 21 | this.move.$state.appendTo(this.container.$elm.find(".row-fluid")); 22 | this.move.$files.insertAfter(this.container.$elm.find(".row-fluid #state_wrapper")); 23 | 24 | // Create an upload button in the header 25 | $('
' + 26 | '' + 27 | '' + 28 | '' + 29 | '
') 30 | .appendTo('#files_wrapper .accordion-heading') 31 | .find('a[href="#"]') 32 | .on('click', function(e) { 33 | e.preventDefault(); 34 | $('#gcode_upload').click(); 35 | }); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /source/js/dom/create/tabbar.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.create.tabbar = { 2 | 3 | createItem: function(itemId, linkId, toggle, text) { 4 | text = (text) ? text : ""; 5 | return $('
  • '+text+'
  • '); 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /source/js/dom/create/webcam.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.create.webcam = { 2 | 3 | menu: { 4 | webcam: { 5 | cloneTo: "#tabs #control_link" 6 | } 7 | }, 8 | 9 | container: { 10 | cloneTo: "#tabs + .tab-content", 11 | 12 | webcam: { 13 | $container: $("#webcam_container"), 14 | cloneTo: "#webcam" 15 | } 16 | }, 17 | 18 | init: function( tabbar ) { 19 | this.container.$elm = $('
    ').appendTo(this.container.cloneTo); 20 | this.menu.webcam.$elm = tabbar.createItem("webcam_link", "webcam", "tab").insertAfter(this.menu.webcam.cloneTo).find('a').text('Webcam'); 21 | 22 | this.container.webcam.$container.next().appendTo(this.container.webcam.cloneTo); 23 | this.container.webcam.$container.prependTo(this.container.webcam.cloneTo); 24 | 25 | $('').insertBefore(this.container.$elm); 26 | $('').insertAfter(this.container.$elm); 27 | 28 | $("#webcam_container").attr("data-bind", $("#webcam_container").attr("data-bind").replace("keydown: onKeyDown, ", "")); 29 | $("#webcam_image").on("mousedown", function(e) { 30 | e.preventDefault(); 31 | }); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /source/js/dom/localstorage.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.localstorage = { 2 | store: JSON.parse(localStorage["TouchUI"] || "{}"), 3 | 4 | get: function (key) { 5 | return this.store[key]; 6 | }, 7 | 8 | set: function (key, value) { 9 | this.store[key] = value; 10 | localStorage["TouchUI"] = JSON.stringify(this.store); 11 | return this.store[key]; 12 | }, 13 | 14 | toggleBoolean: function (key) { 15 | var value = this.store[key] || false; 16 | 17 | if(value === true) { 18 | this.set(key, false); 19 | } else { 20 | this.set(key, true); 21 | } 22 | 23 | return !value; 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /source/js/dom/move/afterTabAndNav.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.move.afterTabAndNav = function() { 2 | 3 | this.DOM.create.dropdown.container.children().each(function(ind, elm) { 4 | var $elm = $(elm); 5 | $('').insertBefore($elm); 6 | $('').insertAfter($elm); 7 | }); 8 | 9 | //Add hr before the settings icon 10 | $('
  • ').insertBefore("#navbar_settings"); 11 | $('').insertBefore("#navbar_systemmenu").attr("data-bind", $("#navbar_systemmenu").attr("data-bind")); 12 | 13 | if ($("#touchui_text_nonlink_container").length > 0) { 14 | $('
  • ').insertBefore($("#touchui_text_nonlink_container").parent()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/js/dom/move/connection.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.move.connection = { 2 | $container: null, 3 | containerId: "connection_dialog", 4 | $cloneContainer: $("#usersettings_dialog"), 5 | $cloneModal: $("#connection_wrapper"), 6 | cloneTo: "#all_touchui_settings > ul", 7 | 8 | init: function( tabbar ) { 9 | var text = this.$cloneModal.find(".accordion-heading").text().trim(); 10 | 11 | // Clone usersettings modal 12 | this.$container = this.$cloneContainer.clone().attr("id", this.containerId).insertAfter(this.$cloneContainer); 13 | this.$containerBody = this.$container.find(".modal-body"); 14 | 15 | // Remove all html from clone 16 | this.$containerBody.html(""); 17 | 18 | // Append tab contents to modal 19 | this.$cloneModal.appendTo(this.$containerBody); 20 | 21 | // Set modal header to accordion header 22 | this.$container.find(".modal-header h3").text(text); 23 | 24 | // Create a link in the dropdown 25 | this.$menuItem = tabbar.createItem("conn_link2", this.containerId, "modal", text) 26 | .attr("data-bind", "visible: loginState.isAdmin") 27 | .prependTo(this.cloneTo); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /source/js/dom/move/controls.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.move.controls = { 2 | 3 | init: function() { 4 | 5 | if($('#control-jog-feedrate .input-append').length === 0) { 6 | // <1.4.1 7 | $("#control-jog-feedrate").insertBefore("#control-jog-extrusion"); 8 | $("#control-jog-extrusion button:last-child").prependTo("#control-jog-feedrate"); 9 | $("#control-jog-extrusion input:last-child").attr('data-bind', $("#control-jog-extrusion input:last-child").attr('data-bind').replace('slider: {', 'slider: {tools: tools(), ')).prependTo("#control-jog-feedrate"); 10 | $("#control-jog-extrusion .slider:last-child").prependTo("#control-jog-feedrate"); 11 | } 12 | 13 | // Move Z-panel 14 | $("#control-jog-general").insertAfter("#control-jog-z"); 15 | 16 | // Create panel 17 | var $jog = $('
    ').attr('id', 'control-jog-rate').insertBefore('#control-jog-extrusion'); 18 | $("#control div.distance").appendTo($jog); 19 | $("#control-jog-feedrate").appendTo($jog); 20 | $("#control-jog-flowrate").appendTo($jog); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /source/js/dom/move/navbar.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.move.navbar = { 2 | mainItems: ['#all_touchui_settings', '#navbar_login', '.hidden_touch', '#touchui_auto_load_touch', '#touchui_auto_load_resolution'], 3 | init: function() { 4 | 5 | var $items = $("#navbar ul.nav > li:not("+this.DOM.move.navbar.mainItems+")"); 6 | var hasTextLinks = false; 7 | $($items.get().reverse()).each(function(ind, elm) { 8 | var $elm = $(elm); 9 | 10 | if($elm.children('a').length > 0) { 11 | var elme = $elm.children('a')[0]; 12 | 13 | $elm.prependTo(this.DOM.create.dropdown.container); 14 | 15 | $.each(elme.childNodes, function(key, node) { 16 | if(node.nodeName === "#text") { 17 | node.nodeValue = node.nodeValue.trim(); 18 | } 19 | }); 20 | 21 | if(!$(elme).text()) { 22 | if(!$(elme).text()) { 23 | $(elme).text($(elme).attr('title')); 24 | } 25 | } 26 | } else { 27 | if(!hasTextLinks) { 28 | hasTextLinks = true; 29 | $('
  • ').appendTo(this.DOM.create.dropdown.container); 30 | } 31 | 32 | $elm.appendTo("#touchui_text_nonlink_container"); 33 | } 34 | }.bind(this)); 35 | 36 | $(document).on('click', function(elm) { 37 | if($(elm.target).parents('#tabs').length > 0) { 38 | $('#tabs .itemActive').removeClass('itemActive'); 39 | $(elm.target).addClass('itemActive'); 40 | } 41 | }); 42 | 43 | $('[href="'+document.location.hash+'"]').addClass('itemActive'); 44 | 45 | // Move TouchUI to main dropdown 46 | $("#navbar_plugin_touchui").insertAfter("#navbar_settings"); 47 | 48 | // Create and Move login form to main dropdown 49 | $('
    • ').insertAfter("#navbar_plugin_touchui"); 50 | 51 | $('#navbar_login') 52 | .appendTo('#youcanhazlogin') 53 | .find('a.dropdown-toggle') 54 | .text($('#youcanhazlogin').find('a.dropdown-toggle').text().trim()) 55 | .attr("data-bind", "visible: !loginState.loggedIn()"); 56 | 57 | // Create a fake dropdown link that will be overlapped by settings icon 58 | $('').appendTo("#tabs"); 59 | 60 | // Move the navbar temp plugin 61 | this.plugins.psuControl.call(this); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /source/js/dom/move/overlays.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.move.overlays = { 2 | 3 | mainItems: ['#offline_overlay', '#reloadui_overlay', '#drop_overlay'], 4 | init: function() { 5 | 6 | $(this.DOM.move.overlays.mainItems).each(function(ind, elm) { 7 | var $elm = $(elm); 8 | $elm.appendTo('body'); 9 | }.bind(this)); 10 | 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /source/js/dom/move/sidebar.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.move.sidebar = { 2 | 3 | items: ".octoprint-container > .row > .accordion > div", 4 | 5 | menu: { 6 | cloneTo: "#tabs" 7 | }, 8 | 9 | container: { 10 | cloneTo: "#temp" 11 | }, 12 | 13 | doNotMove: [ 14 | '#sidebar_plugin_printer_safety_check_wrapper', 15 | '#connection_wrapper' 16 | ], 17 | 18 | init: function() { 19 | var tabbar = this.DOM.create.tabbar; 20 | $(this.DOM.move.sidebar.items + ':not(' + this.DOM.move.sidebar.doNotMove + ')').each(function(ind, elm) { 21 | var id = $(elm).attr('id'); 22 | 23 | tabbar.createItem(id + "_link", id, "tab") 24 | .appendTo(this.menu.cloneTo) 25 | .find('[data-toggle="tab"]') 26 | .text($(elm).find('.accordion-toggle').text().trim()); 27 | 28 | $('
      ') 29 | .insertBefore(this.container.cloneTo) 30 | .children().get(0) 31 | .prepend(elm); 32 | 33 | }.bind(this.DOM.move.sidebar)); 34 | 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /source/js/dom/move/tabbar.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.move.tabbar = { 2 | init: function(viewModels) { 3 | 4 | var $items = $("#tabs > li:not(#print_link, #touchui_dropdown_link, .hidden_touch)"); 5 | $($items.get().reverse()).each(function(ind, elm) { 6 | var $elm = $(elm); 7 | 8 | // Clone the items into the dropdown, and make it click the orginal link 9 | $elm 10 | .clone() 11 | .attr("id", $elm.attr("id")+"2") 12 | .removeAttr('style') 13 | .removeAttr('data-bind') 14 | .prependTo(this.DOM.create.dropdown.container) 15 | .find("a") 16 | .off("click") 17 | .on("click", function(e) { 18 | $elm.find('a').click(); 19 | $("#all_touchui_settings").addClass("item_active"); 20 | e.preventDefault(); 21 | return false; 22 | }); 23 | 24 | // $elm.addClass("hidden_touch"); 25 | }.bind(this)); 26 | 27 | $("#tabs > li > a").each(function(ind, elm) { 28 | $(elm).text(""); 29 | }); 30 | 31 | var resize = function() { 32 | var width = $('#print_link').width(); 33 | var winWidth = $(window).width(); 34 | var $items = $('#tabs > li:not("#touchui_dropdown_link")'); 35 | var itemsFit = Math.floor(winWidth / width) - 2; 36 | 37 | // Loop over items; if they contain display: none; then do 38 | // not show them in the dropdown menu and filter them out from items 39 | $items = $items.filter(function(i, elm) { 40 | if (($(elm).attr('style') || "").indexOf('none') !== -1) { 41 | $('#' + $(elm).attr('id') + '2').addClass('hidden_touch'); 42 | return false; 43 | } 44 | 45 | return true; 46 | }); 47 | 48 | if (winWidth > (width * 2)) { 49 | $items.each(function(key, elm) { 50 | if (key > itemsFit) { 51 | $(elm).addClass('hidden_touch'); 52 | $('#' + $(elm).attr('id') + '2').removeClass('hidden_touch'); 53 | } else { 54 | $(elm).removeClass('hidden_touch'); 55 | $('#' + $(elm).attr('id') + '2').addClass('hidden_touch'); 56 | } 57 | }); 58 | } 59 | 60 | // Sync width of dropdown link 61 | $('#all_touchui_settings').width(width); 62 | } 63 | 64 | $(window).on('resize.touchui.tabbar', resize); 65 | $(window).on('resize.touchui.tabbar', _.debounce(resize, 200)); 66 | $(window).on('resize.touchui.tabbar', _.debounce(resize, 600)); 67 | 68 | $(window).trigger('resize.touchui.tabbar'); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /source/js/dom/move/terminal.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.move.terminal = { 2 | 3 | init: function() { 4 | 5 | // Add version number placeholder 6 | $('').prependTo("#terminal-output"); 7 | 8 | // Create iScroll container for terminal 9 | var container = $('
      ').insertBefore("#terminal-output"); 10 | var inner = $('
      ').appendTo(container); 11 | $("#terminal-output").appendTo(inner); 12 | $("#terminal-output-lowfi").appendTo(inner); 13 | 14 | } 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /source/js/dom/overwrite/modal.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.overwrite.modal = function() { 2 | 3 | //We need a reliable event for catching new modals for attaching a scrolling bar 4 | $.fn.modalBup = $.fn.modal; 5 | $.fn.modal = function(options, args) { 6 | // Update any other modifications made by others (i.e. OctoPrint itself) 7 | $.fn.modalBup.defaults = $.fn.modal.defaults; 8 | 9 | // Create modal, store into variable so we can trigger an event first before return 10 | var tmp = $(this).modalBup(options, args); 11 | if (options !== "hide") { 12 | $(this).trigger("modal.touchui", this); 13 | } 14 | return tmp; 15 | }; 16 | $.fn.modal.prototype = { constructor: $.fn.modal }; 17 | $.fn.modal.Constructor = $.fn.modal; 18 | $.fn.modal.defaults = $.fn.modalBup.defaults; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /source/js/dom/overwrite/pnotify.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.overwrite.pnotify = function() { 2 | 3 | if(!this.settings.hasTouch) { 4 | var tmp = PNotify.prototype.options.stack; 5 | tmp.context = $('#scroll .page-container'); 6 | PNotify.prototype.options.stack = tmp; 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /source/js/dom/overwrite/tabbar.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.overwrite.tabbar = function() { 2 | 3 | // Force the webcam tab to load the webcam feed that original is located on the controls tab 4 | $('#tabs [data-toggle=tab]').each(function(ind, elm) { 5 | 6 | var data_event = jQuery._data(elm, "events"); 7 | if (data_event && data_event.show) { 8 | 9 | // Get the currently attached events to the toggle 10 | var events = $.extend([], data_event.show), 11 | $elm = $(elm); 12 | 13 | // Remove all previous set events and call them after manipulating a few things 14 | $elm.off("show").on("show", function(e) { 15 | var scope = this, 16 | current = e.target.hash, 17 | previous = e.relatedTarget.hash; 18 | 19 | current = (current === "#control") ? "#control_without_webcam" : current; 20 | current = (current === "#webcam") ? "#control" : current; 21 | 22 | previous = (previous === "#control") ? "#control_without_webcam" : previous; 23 | previous = (previous === "#webcam") ? "#control" : previous; 24 | 25 | // Call previous unset functions (e.g. let's trigger the event onTabChange in all the viewModels) 26 | $.each(events, function(key, event) { 27 | event.handler.call(scope, { 28 | target: { 29 | hash: current 30 | }, 31 | relatedTarget: { 32 | hash: previous 33 | } 34 | }); 35 | }); 36 | }); 37 | } 38 | 39 | }); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /source/js/dom/overwrite/tabdrop.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.DOM.overwrite.tabdrop = function() { 2 | $.fn.tabdrop = function() {}; 3 | $.fn.tabdrop.prototype = { constructor: $.fn.tabdrop }; 4 | $.fn.tabdrop.Constructor = $.fn.tabdrop; 5 | } 6 | -------------------------------------------------------------------------------- /source/js/dom/storage.js: -------------------------------------------------------------------------------- 1 | // Since I messed up by releasing start_kweb3.xinit without disabling private 2 | // mode, we now need to check if we can store anything at all in localstorage 3 | // the missing -P will prevent any localstorage 4 | if (TouchUI.prototype.settings.hasLocalStorage) { 5 | try { 6 | localStorage["TouchUIcanWeHazStorage"] = "true"; 7 | TouchUI.prototype.DOM.storage = TouchUI.prototype.DOM.localstorage; 8 | delete localStorage["TouchUIcanWeHazStorage"]; 9 | } catch(err) { 10 | 11 | // TODO: remove this is future 12 | if(TouchUI.prototype.settings.isEpiphanyOrKweb) { 13 | $(function() { 14 | new PNotify({ 15 | type: 'error', 16 | title: "Private Mode detection:", 17 | text: "Edit the startup file 'start_kweb3.xinit' in '~/OctoPrint-TouchUI-autostart/' "+ 18 | "and add the parameter 'P' after the dash. \n\n" + 19 | "For more information see the v0.3.3 release notes.", 20 | hide: false 21 | }); 22 | }); 23 | } 24 | 25 | console.info("Localstorage defined but failback to cookies due to errors."); 26 | TouchUI.prototype.DOM.storage = TouchUI.prototype.DOM.cookies; 27 | } 28 | } else { 29 | TouchUI.prototype.DOM.storage = TouchUI.prototype.DOM.cookies; 30 | } 31 | 32 | TouchUI.prototype.DOM.storage.migration = (TouchUI.prototype.DOM.storage === TouchUI.prototype.DOM.localstorage) ? function migration() { 33 | 34 | if (this.settings.hasLocalStorage) { 35 | if (document.cookie.indexOf("TouchUI.") !== -1) { 36 | console.info("TouchUI cookies migration."); 37 | 38 | var name = "TouchUI."; 39 | var ca = document.cookie.split(';'); 40 | for (var i=0; i .dropdown-menu").length > 0) { 35 | $(document).trigger("click"); 36 | } 37 | }); 38 | 39 | // Redo scroll-to-end interface 40 | $("#term .terminal small.pull-right").html('').on("click", function() { 41 | viewModels.terminalViewModel.scrollToEnd(); 42 | return false; 43 | }); 44 | 45 | // Resize height of low-fi terminal to enable scrolling 46 | if ($("#terminal-output-lowfi").prop("scrollHeight")) { 47 | viewModels.terminalViewModel.plainLogOutput.subscribe(function() { 48 | $("#terminal-output-lowfi").height($("#terminal-output-lowfi").prop("scrollHeight")); 49 | }); 50 | } 51 | 52 | // Overwrite terminal knockout functions (i.e. scroll to end) 53 | this.scroll.overwrite.call(this, viewModels.terminalViewModel); 54 | 55 | // Setup version tracking in terminal 56 | this.core.version.init.call(this, viewModels.softwareUpdateViewModel); 57 | 58 | // (Re-)Apply bindings to the new webcam div 59 | if ($("#webcam").length) { 60 | ko.applyBindings(viewModels.controlViewModel, $("#webcam")[0]); 61 | } 62 | 63 | // (Re-)Apply bindings to the new navigation div 64 | if ($("#navbar_login").length) { 65 | try { 66 | ko.applyBindings(viewModels.navigationViewModel, $("#navbar_login")[0]); 67 | } catch(err) {} 68 | 69 | viewModels.navigationViewModel.loginState.loggedIn.subscribe(function() { 70 | // Refresh scroll view when login state changed 71 | if( !self.settings.hasTouch ) { 72 | setTimeout(function() { 73 | self.scroll.currentActive.refresh(); 74 | }, 0); 75 | } 76 | }); 77 | } 78 | 79 | // (Re-)Apply bindings to the new system commands div 80 | if ($("#navbar_systemmenu").length) { 81 | ko.applyBindings(viewModels.navigationViewModel, $("#navbar_systemmenu")[0]); 82 | ko.applyBindings(viewModels.navigationViewModel, $("#divider_systemmenu")[0]); 83 | } 84 | 85 | // Force knockout to read the change 86 | $('.colorPicker').tinycolorpicker().on("change", function(e, hex, rgb, isTriggered) { 87 | if(isTriggered !== false) { 88 | $(this).find("input").trigger("change", [hex, rgb, false]); 89 | } 90 | }); 91 | 92 | // Reuse for code below 93 | var refreshUrl = function(href) { 94 | return href.split("?")[0] + "?ts=" + new Date().getTime(); 95 | } 96 | 97 | // Reload CSS if needed 98 | self.settings.refreshCSS.subscribe(function(hasRefresh) { 99 | if (hasRefresh || hasRefresh === "fast") { 100 | // Wait 2 seconds, so we're not too early 101 | setTimeout(function() { 102 | var $css = $("#touchui-css"); 103 | $css.attr("href", refreshUrl($css.attr("href"))); 104 | self.settings.refreshCSS(false); 105 | }, (hasRefresh === "fast") ? 0 : 1200); 106 | } 107 | }); 108 | 109 | // Reload CSS or LESS after saving our settings 110 | self.settings.hasCustom.subscribe(function(customCSS) { 111 | if(customCSS !== "") { 112 | var $css = $("#touchui-css"); 113 | var href = $css.attr("href"); 114 | 115 | if(customCSS) { 116 | href = href.replace("touchui.css", "touchui.custom.css"); 117 | } else { 118 | href = href.replace("touchui.custom.css", "touchui.css"); 119 | } 120 | 121 | $css.attr("href", refreshUrl(href)); 122 | } 123 | }); 124 | 125 | // Check if we need to update an old LESS file with a new LESS one 126 | var requireNewCSS = ko.computed(function() { 127 | return self.settings.requireNewCSS() && viewModels.loginStateViewModel.isAdmin(); 128 | }); 129 | requireNewCSS.subscribe(function(requireNewCSS) { 130 | if(requireNewCSS) { 131 | setTimeout(function() { 132 | self.core.less.save.call(self, self); 133 | }, 100); 134 | } 135 | }); 136 | 137 | // Evuluate computed subscriber defined above: 138 | // In OctoPrint >1.3.5 the settings will be defined upfront 139 | requireNewCSS.notifySubscribers(self.settings.requireNewCSS() && viewModels.loginStateViewModel.isAdmin()); 140 | 141 | //TODO: move this 142 | $("li.dropdown#navbar_login > a.dropdown-toggle").off("click").on("click", function(e) { 143 | e.stopImmediatePropagation(); 144 | e.preventDefault(); 145 | 146 | $(this).parent().toggleClass("open"); 147 | }); 148 | 149 | if (window.top.postMessage) { 150 | // Tell bootloader we're ready with giving him the expected version for the bootloader 151 | // if version is lower on the bootloader, then the bootloader will throw an update msg 152 | window.top.postMessage(self.settings.requiredBootloaderVersion, "*"); 153 | 154 | // Sync customization with bootloader 155 | window.top.postMessage([true, self.settings.colors.mainColor(), self.settings.colors.bgColor()], "*"); 156 | 157 | ko.computed(function() { 158 | window.top.postMessage([true, self.settings.colors.mainColor(), self.settings.colors.bgColor()], "*"); 159 | }); 160 | 161 | // Stop watching for errors 162 | $(window).off("error.touchui"); 163 | 164 | // Trigger wake-up for iScroll 165 | if(window.dispatchEvent) { 166 | window.dispatchEvent(new Event('resize')); 167 | } 168 | } 169 | 170 | // Re-render tabbar 171 | $(window).trigger('resize.touchui.tabbar'); 172 | 173 | // We will win the DOM manipulation war! 174 | setTimeout(function() { 175 | self.plugins.multiWebCam.call(self); 176 | }, 0); 177 | 178 | // Disable clicking outside models 179 | if (viewModels.settingsViewModel.appearance_closeModalsWithClick) { 180 | $('#settings-appearanceCloseModalsWithClick').parent().addClass('touchui_disabled'); 181 | $('(Disabled and managed by TouchUI)').appendTo($('#settings-appearanceCloseModalsWithClick').parent()); 182 | 183 | this.settings.closeDialogsOutside.subscribe(function(close) { 184 | viewModels.settingsViewModel.appearance_closeModalsWithClick(close); 185 | }); 186 | 187 | this.settings.closeDialogsOutside.valueHasMutated(); 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /source/js/knockout/viewModel.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.knockout.viewModel = function() { 2 | var self = this; 3 | 4 | // Subscribe to OctoPrint events 5 | self.onStartupComplete = function () { 6 | if (self.isActive()) { 7 | self.DOM.overwrite.tabbar.call(self); 8 | } 9 | self.knockout.isReady.call(self, self.core.bridge.allViewModels); 10 | if (self.isActive()) { 11 | self.plugins.init.call(self, self.core.bridge.allViewModels); 12 | } 13 | } 14 | 15 | self.onBeforeBinding = function() { 16 | ko.mapping.fromJS(self.core.bridge.allViewModels.settingsViewModel.settings.plugins.touchui, {}, self.settings); 17 | } 18 | 19 | self.onSettingsBeforeSave = function() { 20 | self.core.less.save.call(self); 21 | } 22 | 23 | self.onTabChange = function() { 24 | if (self.isActive()) { 25 | self.animate.hide.call(self, "navbar"); 26 | 27 | if(!self.settings.hasTouch && self.scroll.currentActive) { 28 | self.scroll.currentActive.refresh(); 29 | setTimeout(function() { 30 | self.scroll.currentActive.refresh(); 31 | }, 0); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /source/js/plugins/_init.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.plugins.init = function (viewModels) { 2 | this.plugins.screenSquish(viewModels.pluginManagerViewModel); 3 | } 4 | -------------------------------------------------------------------------------- /source/js/plugins/disable.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.plugins.disable = { 2 | plugins: [ 3 | { 4 | htmlId: '#settings_plugin_themeify', 5 | name: 'Themeify' 6 | }, { 7 | functionName: 'TempsgraphViewModel', 8 | name: 'TempsGraph' 9 | }, { 10 | functionName: 'WebcamTabViewModel', 11 | name: 'WebcamTab', 12 | extra: function() { 13 | $('#tab_plugin_webcamtab_link').remove(); 14 | } 15 | }, { 16 | functionName: 'AblExpertViewModel', 17 | name: 'ABLExpert', 18 | extra: function() { 19 | $('#settings_plugin_ABL_Expert').hide(); 20 | $('#settings_plugin_ABL_Expert_link').hide(); 21 | $('#processing_dialog_plugin_ABL_Expert').hide(); 22 | $('#results_dialog_plugin_ABL_Expert').hide(); 23 | } 24 | } 25 | ], 26 | 27 | init: function () { 28 | var self = this; 29 | 30 | _.remove(OCTOPRINT_VIEWMODELS, function(viewModel) { 31 | return _.some( 32 | self.plugins.disable.plugins, 33 | self.plugins.disable.find.bind( 34 | _.flattenDeep( 35 | _.isPlainObject(viewModel) ? _.values(viewModel) : viewModel 36 | ) 37 | ) 38 | ); 39 | }); 40 | }, 41 | 42 | find: function(plugin) { 43 | var result = false; 44 | 45 | if (plugin.htmlId) { 46 | result = this.indexOf(plugin.htmlId) !== -1; 47 | } 48 | 49 | if (plugin.functionName) { 50 | result = _.some(this, function(viewModelProp) { 51 | return viewModelProp.name && viewModelProp.name === plugin.functionName; 52 | }); 53 | } 54 | 55 | if (result) { 56 | console.info("TouchUI: " + plugin.name + " is disabled while TouchUI is active."); 57 | 58 | if (plugin.extra) { 59 | plugin.extra(); 60 | } 61 | } 62 | 63 | return result; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /source/js/plugins/multiwebcam.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.plugins.multiWebCam = function() { 2 | 3 | // Manually move multiWebCam (hard move) 4 | if( $("#camControl").length) { 5 | $("#camControl").appendTo('#webcam'); 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /source/js/plugins/psucontrol.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.plugins.psuControl = function() { 2 | 3 | // Manually move navbar temp (hard move) 4 | if( $("#navbar_plugin_psucontrol a").length > 0 ) { 5 | $("#navbar_plugin_psucontrol a") 6 | .text('PSU Control'); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /source/js/plugins/screensquish.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.plugins.screenSquish = function(pluginManagerViewModel) { 2 | var shown = false; 3 | 4 | pluginManagerViewModel.plugins.items.subscribe(function() { 5 | 6 | var ScreenSquish = pluginManagerViewModel.plugins.getItem(function(elm) { 7 | return (elm.key === "ScreenSquish"); 8 | }, true) || false; 9 | 10 | if(!shown && ScreenSquish && ScreenSquish.enabled) { 11 | shown = true; 12 | new PNotify({ 13 | title: 'TouchUI: ScreenSquish is running', 14 | text: 'Running ScreenSquish and TouchUI will give issues since both plugins try the same, we recommend turning off ScreenSquish.', 15 | icon: 'glyphicon glyphicon-question-sign', 16 | type: 'error', 17 | hide: false, 18 | confirm: { 19 | confirm: true, 20 | buttons: [{ 21 | text: 'Disable ScreenSquish', 22 | addClass: 'btn-primary', 23 | click: function(notice) { 24 | if(!ScreenSquish.pending_disable) { 25 | pluginManagerViewModel.togglePlugin(ScreenSquish); 26 | } 27 | notice.remove(); 28 | } 29 | }] 30 | }, 31 | }); 32 | } 33 | 34 | }); 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /source/js/scroll/_beforeLoad.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.scroll.beforeLoad = function() { 2 | 3 | // Manipulate DOM for iScroll before knockout binding kicks in 4 | if (!this.settings.hasTouch) { 5 | $('
      ').insertBefore('.page-container'); 6 | $('.page-container').appendTo("#scroll"); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /source/js/scroll/_init.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.scroll.init = function() { 2 | var self = this; 3 | 4 | if ( this.settings.hasTouch ) { 5 | var width = $(window).width(); 6 | 7 | // Covert VH to the initial height (prevent height from jumping when navigation bar hides/shows) 8 | $("#temperature-graph").parent().height($("#temperature-graph").parent().outerHeight()); 9 | $("#terminal-scroll").height($("#terminal-scroll").outerHeight()); 10 | $("#terminal-sendpanel").css("top", $("#terminal-scroll").outerHeight()-1); 11 | 12 | $(window).on("resize", function() { 13 | 14 | if(width !== $(window).width()) { 15 | $("#temperature-graph").parent().height($("#temperature-graph").parent().outerHeight()); 16 | $("#terminal-scroll").css("height", "").height($("#terminal-scroll").outerHeight()); 17 | $("#terminal-sendpanel").css("top", $("#terminal-scroll").outerHeight()-1); 18 | width = $(window).width(); 19 | } 20 | 21 | 22 | }); 23 | 24 | } else { 25 | 26 | // Set overflow hidden for best performance 27 | $("html").addClass("emulateTouch"); 28 | 29 | self.scroll.terminal.init.call(self); 30 | self.scroll.body.init.call(self); 31 | self.scroll.modal.init.call(self); 32 | self.scroll.overlay.init.call(self); 33 | 34 | $(document).on("slideCompleted", function() { 35 | self.scroll.currentActive.refresh(); 36 | }); 37 | 38 | // Refresh body on dropdown click 39 | $(document).on("click", ".pagination ul li a", function() { 40 | setTimeout(function() { 41 | self.scroll.currentActive.refresh(); 42 | }, 0); 43 | }); 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /source/js/scroll/block-events.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.scroll.blockEvents = { 2 | className: "no-pointer", 3 | 4 | scrollStart: function($elm, iScrollInstance) { 5 | $elm.addClass(this.className); 6 | }, 7 | 8 | scrollEnd: function($elm, iScrollInstance) { 9 | $elm.removeClass(this.className); 10 | iScrollInstance.refresh(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /source/js/scroll/body.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.scroll.body = { 2 | 3 | init: function() { 4 | var self = this; 5 | var scrollStart = false; 6 | var $noPointer = $('.page-container'); 7 | 8 | // Create main body scroll 9 | self.scroll.iScrolls.body = new IScroll("#scroll", self.scroll.defaults.iScroll); 10 | self.scroll.currentActive = self.scroll.iScrolls.body; 11 | 12 | // Block everthing while scrolling 13 | var scrollStart = self.scroll.blockEvents.scrollStart.bind(self.scroll.blockEvents, $noPointer, self.scroll.iScrolls.body), 14 | scrollEnd = self.scroll.blockEvents.scrollEnd.bind(self.scroll.blockEvents, $noPointer, self.scroll.iScrolls.body); 15 | 16 | // Disable all JS events while scrolling for best performance 17 | self.scroll.iScrolls.body.on("scrollStart", scrollStart); 18 | self.scroll.iScrolls.body.on("onBeforeScrollStart", scrollStart); 19 | self.scroll.iScrolls.body.on("scrollEnd", scrollEnd); 20 | self.scroll.iScrolls.body.on("scrollCancel", scrollEnd); 21 | 22 | // Prevent any misfortune 23 | $(document).on("mouseup.prevent.pointer touchend.prevent.pointer", function() { 24 | $noPointer.removeClass('no-pointer'); 25 | }); 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /source/js/scroll/modal.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.scroll.modal = { 2 | stack: [], 3 | dropdown: null, 4 | 5 | init: function() { 6 | var $document = $(document), 7 | self = this; 8 | 9 | $document.on("modal.touchui", function(e, elm) { 10 | var $modalElm = $(elm), 11 | $modalContainer = $(elm).parent(); 12 | 13 | // Create temp iScroll within the modal 14 | var curModal = new IScroll($modalContainer[0], self.scroll.defaults.iScroll); 15 | 16 | // Store into stack 17 | self.scroll.modal.stack.push(curModal); 18 | self.scroll.currentActive = curModal; 19 | 20 | // Force iScroll to get the correct scrollHeight 21 | setTimeout(function() { 22 | if(curModal) { 23 | curModal.refresh(); 24 | } 25 | }, 0); 26 | // And Refresh again after animation 27 | setTimeout(function() { 28 | if(curModal) { 29 | curModal.refresh(); 30 | } 31 | }, 800); 32 | 33 | // Store bindings into variable for future reference 34 | var scrollStart = self.scroll.blockEvents.scrollStart.bind(self.scroll.blockEvents, $modalElm, curModal), 35 | scrollEnd = self.scroll.blockEvents.scrollEnd.bind(self.scroll.blockEvents, $modalElm, curModal); 36 | 37 | // Disable all JS events while scrolling for best performance 38 | curModal.on("scrollStart", scrollStart); 39 | curModal.on("scrollEnd", scrollEnd); 40 | curModal.on("scrollCancel", scrollEnd); 41 | 42 | // Refresh the scrollHeight and scroll back to top with these actions: 43 | $document.on("click.scrollHeightTouchUI", '[data-toggle="tab"], .pagination ul li a', function(e) { 44 | curModal._end(e); 45 | 46 | setTimeout(function() { 47 | curModal.refresh(); 48 | curModal.scrollTo(0, 0); 49 | }, 0); 50 | }); 51 | 52 | // Kill it with fire! 53 | $modalElm.one("destroy", function() { 54 | $document.off("click.scrollHeightTouchUI"); 55 | self.scroll.modal.stack.pop(); 56 | 57 | if(self.scroll.modal.stack.length > 0) { 58 | self.scroll.currentActive = self.scroll.modal.stack[self.scroll.modal.stack.length-1]; 59 | } else { 60 | self.scroll.currentActive = self.scroll.iScrolls.body; 61 | } 62 | 63 | curModal.destroy(); 64 | curModal.off("scrollStart", scrollStart); 65 | curModal.off("scrollEnd", scrollEnd); 66 | curModal.off("scrollCancel", scrollEnd); 67 | curModal = undefined; 68 | }); 69 | 70 | }); 71 | 72 | // Triggered when we create the dropdown and need scrolling 73 | $document.on("dropdown-open.touchui", function(e, elm) { 74 | var $elm = $(elm); 75 | 76 | // Create dropdown scroll 77 | self.scroll.modal.dropdown = new IScroll(elm, { 78 | scrollbars: true, 79 | mouseWheel: true, 80 | interactiveScrollbars: true, 81 | shrinkScrollbars: "scale" 82 | }); 83 | 84 | // Set scroll to active item 85 | self.scroll.modal.dropdown.scrollToElement($elm.find('li.active')[0], 0, 0, -30); 86 | 87 | // Disable scrolling in active modal 88 | self.scroll.modal.stack[self.scroll.modal.stack.length-1].disable(); 89 | 90 | // Store bindings into variable for future reference 91 | var scrollStart = self.scroll.blockEvents.scrollStart.bind(self.scroll.blockEvents, $elm, self.scroll.modal.dropdown), 92 | scrollEnd = self.scroll.blockEvents.scrollEnd.bind(self.scroll.blockEvents, $elm, self.scroll.modal.dropdown); 93 | 94 | // Disable all JS events for smooth scrolling 95 | self.scroll.modal.dropdown.on("scrollStart", scrollStart); 96 | self.scroll.modal.dropdown.on("scrollEnd", scrollEnd); 97 | self.scroll.modal.dropdown.on("scrollCancel", scrollEnd); 98 | 99 | $document.on("dropdown-closed.touchui", function() { 100 | // Enable active modal 101 | self.scroll.modal.stack[self.scroll.modal.stack.length-1].enable(); 102 | 103 | self.scroll.modal.dropdown.off("scrollStart", scrollStart); 104 | self.scroll.modal.dropdown.off("scrollEnd", scrollEnd); 105 | self.scroll.modal.dropdown.off("scrollCancel", scrollEnd); 106 | }); 107 | 108 | }); 109 | 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /source/js/scroll/overlay.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.scroll.overlay = { 2 | 3 | mainItems: ['#offline_overlay', '#reloadui_overlay'], 4 | init: function() { 5 | var self = this; 6 | 7 | self.scroll.iScrolls.overlay = []; 8 | 9 | var $items = $(this.scroll.overlay.mainItems); 10 | $items.each(function(ind, elm) { 11 | var child = $(elm).children("#" + $(elm).attr("id") + "_wrapper"); 12 | var div = $('
      ').prependTo(elm); 13 | child.appendTo(div); 14 | 15 | $(elm).addClass("iscroll"); 16 | 17 | self.scroll.iScrolls.overlay[ind] = new IScroll(elm, self.scroll.defaults.iScroll); 18 | }); 19 | 20 | }, 21 | 22 | refresh: function() { 23 | var self = this; 24 | 25 | setTimeout(function() { 26 | $.each(self.scroll.iScrolls.overlay, function(ind) { 27 | self.scroll.iScrolls.overlay[ind].refresh(); 28 | }); 29 | }, 0); 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /source/js/scroll/overwrite.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.scroll.overwrite = function(terminalViewModel) { 2 | var self = this; 3 | 4 | if ( !this.settings.hasTouch ) { 5 | 6 | // Enforce no scroll jumping 7 | $("#scroll").on("scroll", function() { 8 | if($("#scroll").scrollTop() !== 0) { 9 | $("#scroll").scrollTop(0); 10 | } 11 | }); 12 | 13 | // Refresh terminal scroll height 14 | terminalViewModel.displayedLines.subscribe(function() { 15 | self.scroll.iScrolls.terminal.refresh(); 16 | }); 17 | 18 | // Overwrite scrollToEnd function with iScroll functions 19 | terminalViewModel.scrollToEnd = function() { 20 | self.scroll.iScrolls.terminal.refresh(); 21 | self.scroll.iScrolls.terminal.scrollTo(0, self.scroll.iScrolls.terminal.maxScrollY); 22 | }; 23 | 24 | // Overwrite orginal helper, add one step and call the orginal function 25 | var showOfflineOverlay = window.showOfflineOverlay; 26 | window.showOfflineOverlay = function(title, message, reconnectCallback) { 27 | showOfflineOverlay.call(this, title, message, reconnectCallback); 28 | self.scroll.overlay.refresh.call(self); 29 | }; 30 | 31 | // Overwrite orginal helper, add one step and call the orginal function 32 | var showConfirmationDialog = window.showConfirmationDialog; 33 | window.showConfirmationDialog = function(message, onacknowledge) { 34 | self.scroll.iScrolls.body.scrollTo(0, 0, 500); 35 | showConfirmationDialog.call(this, message, onacknowledge); 36 | }; 37 | 38 | // Overwrite orginal helper, add one step and call the orginal function 39 | var showReloadOverlay = $.fn.show; 40 | $.fn.show = function(e,r,i) { 41 | if($(this).hasClass("iscroll")) { 42 | setTimeout(function() { 43 | self.scroll.overlay.refresh.call(self); 44 | }, 0); 45 | } 46 | 47 | return showReloadOverlay.call(this,e,r,i); 48 | } 49 | 50 | } else { 51 | 52 | // Overwrite scrollToEnd function with #terminal-scroll as scroller 53 | terminalViewModel.scrollToEnd = function() { 54 | var $container = $("#terminal-scroll"); 55 | if ($container.length) { 56 | $container.scrollTop($container[0].scrollHeight - $container.height()) 57 | } 58 | } 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /source/js/scroll/terminal.js: -------------------------------------------------------------------------------- 1 | TouchUI.prototype.scroll.terminal = { 2 | 3 | init: function() { 4 | var self = this; 5 | 6 | // Create scrolling for terminal 7 | self.scroll.iScrolls.terminal = new IScroll("#terminal-scroll", self.scroll.defaults.iScroll); 8 | 9 | // Enforce the right scrollheight and disable main scrolling if we have a scrolling content 10 | self.scroll.iScrolls.terminal.on("beforeScrollStart", function() { 11 | self.scroll.iScrolls.terminal.refresh(); 12 | 13 | if(this.hasVerticalScroll) { 14 | self.scroll.iScrolls.body.disable(); 15 | } 16 | }); 17 | self.scroll.iScrolls.terminal.on("scrollEnd", function() { 18 | self.scroll.iScrolls.body.enable(); 19 | }); 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/less/_variables.less: -------------------------------------------------------------------------------- 1 | @touchui-base-path: "../"; 2 | @fontawesome-path: "../fonts/fontawesome/"; 3 | @sourcecodepro-path: "../fonts/sourcecodepro/"; 4 | @touchui-path: "../fonts/touchui/"; 5 | 6 | /* General */ 7 | @main-font-size: 16; 8 | @main-color: #00B0FF; 9 | @main-color-text: contrast(@main-color, @dark-color, @light-color, 51%); 10 | 11 | @main-color-dark: darken(@main-color, 5%); 12 | @main-color-dark-text: contrast(@main-color-dark, @dark-color, @light-color, 60%); 13 | 14 | @main-color-darker: darken(@main-color, 15%); 15 | 16 | @main-background: @dark-color; 17 | @text-color: @light-color; 18 | @link-color: @main-color; 19 | 20 | /* Grid properties */ 21 | @main-gutter: 30px; 22 | @modal-gutter: 30px; 23 | @terminal-gutter: 10px; 24 | @dropdown-gutter: 15px; 25 | 26 | /* Tables, fileList, progress */ 27 | @table-row-background: mix(@main-background, contrast(@main-background), 80%); 28 | @table-row-header-background: @main-color; 29 | @table-row-header-text-color: @main-color-text; 30 | 31 | /* Progress */ 32 | @progress-background: @table-row-background; 33 | @progress-active-background: @main-color; 34 | 35 | /* fileList */ 36 | @swipe-list-background-color: @table-row-background; 37 | @swipe-list-background-open-color: mix(@main-color, @dark-color, 60%); 38 | 39 | /* Modal */ 40 | @modal-background: @light-color; 41 | @modal-backdrop: @dark-color; 42 | @modal-footer-background: fade(contrast(@modal-background), 5%); 43 | @modal-border: fade(contrast(@modal-background), 7%); 44 | @modal-text: @dark-color; 45 | 46 | /* Buttons */ 47 | @btn-background: @light-dark-color; 48 | @btn-text: contrast(@light-dark-color, @dark-color, @light-color); 49 | 50 | @btn-main-background: @main-color; 51 | @btn-main-text: @main-color-text; 52 | 53 | @btn-danger-background: #D40000; 54 | @btn-danger-text: contrast(@btn-danger-background, @dark-color, @light-color); 55 | 56 | /* Dropdown + indication */ 57 | @online-color: #9AFD00; 58 | @offline-color: red; 59 | @dropdown-color: @main-color; 60 | @dropdown-color-text: @main-color-text; 61 | @dropdown-divider-color: @main-color-dark; 62 | 63 | /* Termional */ 64 | @terminal-color: #0F0; 65 | @terminal-background: @main-background; 66 | @terminal-text-color: @terminal-color; 67 | 68 | /* iScroll Styling */ 69 | @terminal-iScroll-color: fade(@terminal-color, 70%); 70 | @dropdown-iScroll-color: fade(@light-darker-dark-color, 70%); 71 | @main-iScroll-color: fade(contrast(@main-background), 70%); 72 | 73 | /* Inputs */ 74 | @input-background: contrast(@main-background, @dark-light-color, @light-dark-color, 50%); 75 | @input-text-color: contrast(@input-background); 76 | 77 | @addon-background: @dark-lighter-color; 78 | @addon-text-color: @light-color; 79 | 80 | /* keyboard */ 81 | @keyboard-background: @dark-light-color; 82 | @keyboard-color: contrast(@keyboard-background); 83 | @keyboard-accept: #00C751; 84 | @keyboard-remove: #9E0000; 85 | 86 | /* More General styles used for contrast etc. */ 87 | @dark-color: black; 88 | @dark-light-color: lighten(@dark-color, 20%); 89 | @dark-lighter-color: lighten(@dark-color, 30%); 90 | 91 | @neutral-color: #555; 92 | 93 | @light-color: white; 94 | @light-dark-color: darken(@light-color, 2%); 95 | @light-darker-color: darken(@light-color, 20%); 96 | @light-darker-dark-color: darken(@light-color, 50%); 97 | @light-darker-darker-color: darken(@light-color, 55%); 98 | -------------------------------------------------------------------------------- /source/less/components/dropdown.less: -------------------------------------------------------------------------------- 1 | .dropdown { 2 | &-toggle { 3 | &.open { 4 | + .dropdown-menu, .dropdown-menu { 5 | display: block; 6 | } 7 | } 8 | } 9 | 10 | &.open { 11 | overflow: visible !important; 12 | 13 | .dropdown-menu { 14 | background-color: @dropdown-color !important; 15 | 16 | a { 17 | text-indent: 0 !important; 18 | } 19 | } 20 | } 21 | } 22 | 23 | .dropdown-submenu { 24 | > a { 25 | &::after { 26 | .font-icon-reset(); 27 | content: @fa-legacy-var-caret-down; 28 | float: none; 29 | position: absolute; 30 | top: 13px; 31 | margin: 0; 32 | right: 30px; 33 | border: 0 none !important; 34 | } 35 | } 36 | &:hover { 37 | >.dropdown-menu { 38 | display: none; 39 | } 40 | } 41 | &.open { 42 | .dropdown-menu { 43 | display: block !important; 44 | } 45 | > a { 46 | &:after { 47 | content: @fa-legacy-var-caret-up !important; 48 | } 49 | } 50 | } 51 | } 52 | 53 | .dropdown-menu { 54 | .border-radius(0); 55 | .transform(translateZ(0)); 56 | 57 | background: @dropdown-color; 58 | color: @dropdown-color-text; 59 | border: 1px solid @dropdown-divider-color !important; 60 | 61 | margin-top: 10px; 62 | padding: (@dropdown-gutter / 3) 0; 63 | max-width: 100vw; 64 | 65 | @media (max-width: 300px) { 66 | width: 90vw; 67 | min-width: 0 !important; 68 | } 69 | 70 | &:after { 71 | display: none; 72 | } 73 | 74 | li { 75 | p { 76 | .rem(font-size, 14); 77 | .rem(line-height, 25); 78 | 79 | float: none; 80 | color: inherit; 81 | margin: 0; 82 | padding: 0 @dropdown-gutter; 83 | } 84 | 85 | a { 86 | color: @dropdown-color-text; 87 | .rem(font-size, 15) !important; 88 | width: 100%; 89 | .box-sizing(border-box); 90 | 91 | padding: 0 @dropdown-gutter; 92 | .rem(line-height, 40); 93 | 94 | i { 95 | color: inherit; 96 | } 97 | 98 | &:hover, &:focus { 99 | color: @dropdown-color-text; 100 | background: @dropdown-color; 101 | } 102 | &:active { 103 | background: darken(@dropdown-color, 15%); 104 | } 105 | } 106 | } 107 | 108 | .dropdown-menu { 109 | position: static; 110 | z-index: 0; 111 | margin: 0; 112 | float: none; 113 | border: 0 none !important; 114 | padding: 0; 115 | 116 | &:before { 117 | display: none; 118 | } 119 | 120 | @media (max-width: 300px) { 121 | width: auto; 122 | } 123 | } 124 | 125 | &:before { 126 | right: 6vw; 127 | top: -20px; 128 | border-left-width: 10px; 129 | border-right-width: 10px; 130 | border-bottom-width: 10px; 131 | border-bottom-color: @dropdown-color !important; 132 | } 133 | 134 | .btn { 135 | background: darken(@btn-main-background, 5%); 136 | 137 | &:active { 138 | background: darken(@btn-main-background, 15%); 139 | } 140 | } 141 | 142 | .divider { 143 | margin: (@dropdown-gutter / 3) 0; 144 | background-color: @dropdown-divider-color; 145 | border: 0 none; 146 | } 147 | 148 | &:before { 149 | content: ""; 150 | border: 10px solid transparent; 151 | border-bottom-color: @dropdown-color; 152 | position: absolute; 153 | top: -18px; 154 | right: 43px; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /source/less/components/emulate.touch.less: -------------------------------------------------------------------------------- 1 | &.emulateTouch { 2 | overflow: hidden; 3 | user-select: none; 4 | 5 | body { 6 | overflow: hidden; 7 | 8 | .modal-scrollable, 9 | .show-dropdown { 10 | overflow: hidden; 11 | } 12 | 13 | .page-container { 14 | height: auto; 15 | min-height: 100%; 16 | } 17 | 18 | #webcam_container { 19 | pointer-events: none; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/less/components/notifications.less: -------------------------------------------------------------------------------- 1 | .ui-pnotify-container { 2 | .border-radius(0); 3 | } 4 | .ui-pnotify-closer { 5 | visibility: visible !important; 6 | padding: 5px 10px; 7 | .rem(font-size, 28); 8 | 9 | .glyphicon-remove:after { 10 | .font-icon-reset() !important; 11 | content: @fa-legacy-var-remove; 12 | } 13 | } 14 | .ui-pnotify-sticker { 15 | display: none; 16 | } 17 | .ui-pnotify-title { 18 | margin-bottom: 5px; 19 | } 20 | .ui-pnotify { 21 | position: absolute; 22 | left: 0; 23 | width: 100% !important; 24 | right: auto !important; 25 | } 26 | -------------------------------------------------------------------------------- /source/less/components/overlays.less: -------------------------------------------------------------------------------- 1 | #drop_overlay { 2 | display: none !important; 3 | } 4 | #offline_overlay, #reloadui_overlay { 5 | position: fixed; 6 | top:0; 7 | bottom:0; 8 | overflow-y:auto; 9 | -webkit-overflow-scrolling: touch; 10 | 11 | #offline_overlay_background { 12 | .transform(translateZ(0)); 13 | } 14 | 15 | #offline_overlay_wrapper, #reloadui_overlay_wrapper { 16 | padding-top: 20px; 17 | } 18 | 19 | .hero-unit { 20 | .rem(font-size, 18); 21 | .rem(line-height, 30); 22 | } 23 | 24 | .container { 25 | width: 100%; 26 | 27 | .hero-unit { 28 | background-color: @neutral-color; 29 | color: contrast(@neutral-color); 30 | padding: 20px; 31 | .border-radius(0); 32 | 33 | h1 { 34 | .rem(font-size, 32) !important; 35 | } 36 | 37 | h1, p { 38 | margin-bottom: 10px; 39 | } 40 | } 41 | } 42 | 43 | &.iscroll { 44 | top: 0; 45 | bottom: 0; 46 | left: 0; 47 | width: 100%; 48 | overflow: hidden; 49 | position: absolute; 50 | 51 | > div:first-child { 52 | position: absolute; 53 | z-index: 1; 54 | -webkit-tap-highlight-color: rgba(0,0,0,0); 55 | width: 100%; 56 | 57 | .transform(translateZ(0)); 58 | .user-select(none); 59 | .text-adjust(none); 60 | 61 | > div { 62 | position: relative; 63 | top: auto; 64 | left: auto; 65 | right: auto; 66 | bottom: auto; 67 | } 68 | } 69 | 70 | .iScrollIndicator { 71 | background: @main-iScroll-color !important; 72 | border: 0 none !important; 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /source/less/components/pagination.less: -------------------------------------------------------------------------------- 1 | .pagination { 2 | color: transparent; 3 | 4 | ul { 5 | > li { 6 | 7 | &.disabled, 8 | &.active { 9 | > a { 10 | background: transparent; 11 | color: @text-color; 12 | } 13 | } 14 | > a { 15 | padding: 10px 15px; 16 | .rem(font-size, 21); 17 | background: @main-color; 18 | color: @main-color-text; 19 | 20 | &:active { 21 | background: @main-color-darker; 22 | color: @main-color-dark-text; 23 | } 24 | } 25 | 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/less/components/popover.less: -------------------------------------------------------------------------------- 1 | .popover { 2 | background: @main-color; 3 | color: @main-color-text; 4 | border-color: @main-color-dark; 5 | padding: 0; 6 | border-radius: 0; 7 | 8 | &-title { 9 | background: @main-color-dark; 10 | border-bottom-color: @main-color-dark; 11 | color: @main-color-dark-text; 12 | font-weight: bold; 13 | border-radius: 0; 14 | } 15 | 16 | &-content { 17 | padding: 6px 0; 18 | } 19 | 20 | &.bottom .arrow:after { 21 | border-bottom-color: @main-color-dark; 22 | } 23 | 24 | &.top .arrow:after { 25 | border-top-color: @main-color-dark; 26 | } 27 | 28 | &.left .arrow:after { 29 | border-left-color: @main-color-dark; 30 | } 31 | 32 | &.right .arrow:after { 33 | border-right-color: @main-color-dark; 34 | } 35 | } -------------------------------------------------------------------------------- /source/less/components/progress.less: -------------------------------------------------------------------------------- 1 | .progress { 2 | background: @progress-background; 3 | 4 | .bar { 5 | background: @progress-active-background; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/less/components/scroll.less: -------------------------------------------------------------------------------- 1 | #scroll { 2 | position: absolute; 3 | z-index: 1; 4 | top: 0; 5 | bottom: 0; 6 | left: 0; 7 | width: 100%; 8 | overflow: hidden; 9 | 10 | .page-container, 11 | #terminal-scroll-inner, 12 | #terminal-scroll > pre { //TODO remove ' > pre' backwards compatibility for <1.3.0 13 | position: absolute; 14 | z-index: 1; 15 | -webkit-tap-highlight-color: rgba(0,0,0,0); 16 | width: 100%; 17 | 18 | .transform(translateZ(0)); 19 | .user-select(none); 20 | .text-adjust(none); 21 | } 22 | .page-container { 23 | z-index: auto; 24 | } 25 | #terminal-scroll { 26 | position: relative; 27 | } 28 | 29 | .iScrollIndicator { 30 | background: @main-iScroll-color !important; 31 | border: 0 none !important; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /source/less/components/scrollbars.less: -------------------------------------------------------------------------------- 1 | &.touch, 2 | &.touchevents { 3 | .show-dropdown, 4 | #terminal-scroll { 5 | overflow-y: scroll !important; 6 | -webkit-overflow-scrolling: auto; 7 | } 8 | 9 | ::-webkit-scrollbar { 10 | width: 2px; 11 | height: 2px; 12 | background: transparent; 13 | } 14 | 15 | ::-webkit-scrollbar-thumb { 16 | background: @main-color; 17 | } 18 | 19 | body { 20 | .modal-scrollable { 21 | position: fixed !important; 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /source/less/components/tinycolorpicker.less: -------------------------------------------------------------------------------- 1 | .colorPickers { 2 | .flex-display(); 3 | .flex-wrap(wrap); 4 | max-width: 520px; 5 | 6 | .controls { 7 | .box-sizing(); 8 | .flex(1 1 50%); 9 | max-width: 240px; 10 | padding: 0 20px 20px 0; 11 | } 12 | } 13 | .colorPicker { 14 | position: relative; 15 | clear: both; 16 | display: inline-block; 17 | max-width: 100%; 18 | width: 100%; 19 | 20 | .track { 21 | background: url("@{touchui-base-path}img/colorpicker.png") no-repeat center center; 22 | height: 150px; 23 | width: 150px; 24 | padding: 10px; 25 | position: absolute; 26 | cursor: crosshair; 27 | left: auto; 28 | right: -71px; 29 | top: -71px; 30 | display: none; 31 | border: 0 none; 32 | z-index: 12; 33 | -webkit-border-radius: 150px; 34 | -moz-border-radius: 150px; 35 | border-radius: 150px; 36 | } 37 | .color { 38 | width: 30px; 39 | height: 100%; 40 | padding: 1px; 41 | border: 1px solid #ccc; 42 | display: block; 43 | position: absolute; 44 | z-index: 11; 45 | right: 0; 46 | top: 0; 47 | background-color: #efefef; 48 | -webkit-border-radius: 0 5px 5px 0; 49 | -moz-border-radius: 0 5px 5px 0; 50 | border-radius: 0 5px 5px 0; 51 | -webkit-box-sizing: border-box; 52 | -moz-box-sizing: border-box; 53 | box-sizing: border-box; 54 | cursor: pointer; 55 | } 56 | .colorInner { 57 | display: block; 58 | width: 26px; 59 | height: 100%; 60 | -webkit-border-radius: 0 5px 5px 0; 61 | -moz-border-radius: 0 5px 5px 0; 62 | border-radius: 0 5px 5px 0; 63 | } 64 | .dropdown { 65 | list-style: none; 66 | display: none; 67 | width: 27px; 68 | position: absolute; 69 | top: 28px; 70 | border: 1px solid #ccc; 71 | left: 0; 72 | z-index: 1000; 73 | li { 74 | height: 25px; 75 | cursor: pointer; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /source/less/fonts/sourcecodepro.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Source Code Pro'; 3 | font-style: normal; 4 | font-weight: 300; 5 | src: local('Source Code Pro Light'), local('SourceCodePro-Light'), url('@{sourcecodepro-path}SourceCodePro-Light.woff') format('woff'); 6 | } 7 | @font-face { 8 | font-family: 'Source Code Pro'; 9 | font-style: normal; 10 | font-weight: 400; 11 | src: local('Source Code Pro'), local('SourceCodePro-Regular'), url('@{sourcecodepro-path}SourceCodePro-Regular.woff') format('woff'); 12 | } 13 | @font-face { 14 | font-family: 'Source Code Pro'; 15 | font-style: normal; 16 | font-weight: 600; 17 | src: local('Source Code Pro Semibold'), local('SourceCodePro-Semibold'), url('@{sourcecodepro-path}SourceCodePro-Semibold.woff') format('woff'); 18 | } 19 | @font-face { 20 | font-family: 'Source Code Pro'; 21 | font-style: normal; 22 | font-weight: 700; 23 | src: local('Source Code Pro Bold'), local('SourceCodePro-Bold'), url('@{sourcecodepro-path}SourceCodePro-Bold.woff') format('woff'); 24 | } 25 | -------------------------------------------------------------------------------- /source/less/fonts/touchui.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'touchui'; 3 | src:url('@{touchui-path}touchui.eot?-gkxfeg'); 4 | src:url('@{touchui-path}touchui.eot?-gkxfeg#iefix') format('embedded-opentype'), 5 | url('@{touchui-path}touchui.ttf?-gkxfeg') format('truetype'), 6 | url('@{touchui-path}touchui.woff?-gkxfeg') format('woff'), 7 | url('@{touchui-path}touchui.svg?-gkxfeg#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | -------------------------------------------------------------------------------- /source/less/layout/buttons.less: -------------------------------------------------------------------------------- 1 | button, 2 | .btn { 3 | border-color: darken(@btn-background, 10%); 4 | background: @btn-background; 5 | color: @btn-text; 6 | padding: 6px 15px; 7 | 8 | &:disabled { 9 | opacity: .4; 10 | } 11 | &:active { 12 | outline: 0 none; 13 | background: darken(@btn-background, 10%); 14 | color: @btn-text; 15 | } 16 | 17 | &-primary { 18 | border-color: darken(@btn-main-background, 10%); 19 | background: @btn-main-background; 20 | color: @btn-main-text; 21 | 22 | &:active { 23 | background: darken(@btn-main-background, 10%); 24 | color: @btn-main-text; 25 | } 26 | } 27 | 28 | &-danger { 29 | border-color: darken(@btn-danger-background, 10%); 30 | background: @btn-danger-background; 31 | color: @btn-danger-text; 32 | 33 | &:active { 34 | background: darken(@btn-danger-background, 10%); 35 | color: @btn-danger-text; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /source/less/layout/footer.less: -------------------------------------------------------------------------------- 1 | .footer { 2 | .rem(font-size, 14); 3 | position: absolute; 4 | left: 0; 5 | bottom: 0; 6 | width: 100%; 7 | padding: 0 @main-gutter 0; 8 | .box-sizing(border-box); 9 | 10 | .flex-display(flex); 11 | .flex-direction(row); 12 | .flex-wrap(nowrap); 13 | .justify-content(space-between); 14 | 15 | ul { 16 | color: mix(@main-background, contrast(@main-background), 50%); 17 | 18 | .flex-order(0); 19 | .flex(0 0 auto); 20 | .align-self(auto); 21 | 22 | &.pull-left { 23 | .flex-order(0); 24 | .flex(1 1 auto); 25 | .align-self(auto); 26 | overflow: hidden; 27 | max-width: 100%; 28 | 29 | li { 30 | display: inline-block; 31 | 32 | small { 33 | .box-sizing(border-box); 34 | white-space: nowrap; 35 | text-overflow: ellipsis; 36 | overflow: hidden; 37 | padding-right: 10px; 38 | display: block; 39 | } 40 | } 41 | } 42 | 43 | li { 44 | margin-left: 0; 45 | .rem(line-height, 20); 46 | 47 | a { 48 | display: inline-block; 49 | .rem(width, 20); 50 | .rem(height, 20); 51 | color: mix(@main-background, contrast(@main-background), 50%); 52 | overflow: hidden; 53 | margin: 0; 54 | .rem(font-size, 18); 55 | padding: 0; 56 | 57 | &:hover { 58 | color: mix(@main-background, contrast(@main-background), 20%); 59 | text-decoration: none; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /source/less/layout/form.less: -------------------------------------------------------------------------------- 1 | input, select, textarea { 2 | background: @input-background; 3 | color: @input-text-color; 4 | text-align: left; 5 | opacity: .8; 6 | .rem(font-size, 14); 7 | .rem(line-height, 20); 8 | 9 | &:active, &:focus, &.ui-keyboard-input-current { 10 | opacity: 1; 11 | border-color: fade(@main-color, 90%); 12 | outline: 0; 13 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px fade(@main-color, 60%) !important; 14 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px fade(@main-color, 60%) !important; 15 | box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px fade(@main-color, 60%) !important; 16 | } 17 | 18 | &.disabled, &[disabled] { 19 | opacity: .4; 20 | } 21 | 22 | &[type="file"] { 23 | color: transparent !important; 24 | opacity: 0 !important; 25 | } 26 | 27 | } 28 | 29 | .uneditable-input { 30 | opacity: .4; 31 | } 32 | 33 | .input-append .add-on { 34 | .rem(font-size, 14); 35 | .rem(height, 20); 36 | .rem(line-height, 20); 37 | } 38 | 39 | legend { 40 | margin-bottom: 0; 41 | } 42 | 43 | .control-group { 44 | margin-top: 0; 45 | margin-bottom: 20px; 46 | } -------------------------------------------------------------------------------- /source/less/layout/general.less: -------------------------------------------------------------------------------- 1 | .visible_touch { 2 | display: block; 3 | 4 | &#login_dropdown_loggedin { 5 | display: block !important; 6 | position: static; 7 | width: 100%; 8 | border: 0 none; 9 | margin: 0 !important; 10 | right: auto !important; 11 | padding: 0 !important; 12 | float: none; 13 | } 14 | } 15 | #navbar_login .hidden_touch { 16 | display: none !important; 17 | } 18 | .hidden_touch { 19 | display: none; 20 | } 21 | 22 | .accordion, 23 | #navbar .brand { 24 | position: absolute; 25 | top: -5000px; 26 | left: -5000px; 27 | } 28 | 29 | .page-container .octoprint-container .tab-content h1 { 30 | font-size: 1.3rem; 31 | line-height: 1.8rem; 32 | padding: 0 0 (@main-gutter / 2); 33 | } 34 | 35 | .touchui-accordion { 36 | .accordion-heading { 37 | a:extend(#touch body .page-container .octoprint-container .tab-content h1) { 38 | pointer-events: none; 39 | 40 | .fa { 41 | display: none; 42 | } 43 | } 44 | } 45 | 46 | .accordion-inner { 47 | padding: 0; 48 | } 49 | } 50 | 51 | .page-container { 52 | width: 100%; 53 | 54 | // &.no-pointer { 55 | // :active { 56 | // color: inherit !important; 57 | // background: inherit !important; 58 | // } 59 | // } 60 | 61 | .octoprint-container { 62 | margin: 0; 63 | 64 | * { 65 | border: 0 none; 66 | .border-radius(0) !important; 67 | } 68 | 69 | > .row { 70 | .rem(margin-bottom, 20); 71 | padding-bottom: 20px; 72 | } 73 | 74 | .tab-content { 75 | padding: @main-gutter @main-gutter 0; 76 | overflow: visible; 77 | .box-sizing(border-box); 78 | 79 | h1 { 80 | color: @text-color; 81 | margin: 0; 82 | } 83 | 84 | table { 85 | position: relative; 86 | margin-left: -@main-gutter; 87 | max-width: none; 88 | width: 100vw; 89 | 90 | tr { 91 | &:first-child { 92 | th { 93 | margin: 0; 94 | .rem(font-size, 18); 95 | border-bottom: 1px solid @main-background; 96 | background: @table-row-header-background; 97 | color: @table-row-header-text-color; 98 | padding: 13px 5px; 99 | } 100 | } 101 | 102 | td, th { 103 | &:first-child { 104 | padding-left: @main-gutter; 105 | } 106 | &:last-child { 107 | padding-right: @main-gutter; 108 | } 109 | } 110 | 111 | &:hover { 112 | background-color: @table-row-background; 113 | color: contrast(@table-row-background); 114 | } 115 | 116 | th, td { 117 | border-bottom: 1px solid @main-background; 118 | background-color: @table-row-background; 119 | color: contrast(@table-row-background); 120 | } 121 | } 122 | } 123 | 124 | } 125 | 126 | button, .btn { 127 | transition: background-color 0.2s ease, color 0.2s ease; 128 | background: @btn-main-background; 129 | color: @btn-main-text; 130 | 131 | &:active { 132 | background: darken(@btn-main-background, 10%); 133 | color: @btn-main-text; 134 | } 135 | 136 | &:focus { 137 | outline: 0 none; 138 | } 139 | 140 | &.disabled, &[disabled] { 141 | pointer-events: none; 142 | } 143 | } 144 | 145 | .add-on { 146 | background: @addon-background; 147 | color: @addon-text-color; 148 | border-left: 1px solid @main-background; 149 | } 150 | 151 | } 152 | } 153 | 154 | label, input, button, select, textarea, .input-mini { 155 | height: auto; 156 | .rem(font-size, 14); 157 | .rem(line-height, 20); 158 | } 159 | 160 | .control-group { 161 | .control-label, 162 | .controls { 163 | display: block; 164 | float: none; 165 | width: 100%; 166 | margin: 0; 167 | text-align: left; 168 | } 169 | 170 | } 171 | 172 | .tabbable, 173 | .container { 174 | width: 100%; 175 | } 176 | 177 | .container > .row, 178 | .container > .row > .tabbable { 179 | margin: 0; 180 | } 181 | -------------------------------------------------------------------------------- /source/less/layout/header.less: -------------------------------------------------------------------------------- 1 | #navbar { 2 | background-color: @main-color; 3 | border: 0 none !important; 4 | 5 | .rem(height, 70); 6 | width: 100%; 7 | 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | 12 | .navbar-inner { 13 | background: transparent !important; 14 | border: 0 none !important; 15 | padding: 0; 16 | margin: 0; 17 | } 18 | } 19 | 20 | .tabbable > .nav-tabs li, 21 | #navbar .nav > li { 22 | overflow: hidden; 23 | position: relative; 24 | 25 | > a { 26 | background: transparent; 27 | color: @main-color; 28 | 29 | .rem(height, 70); 30 | padding: 0; 31 | margin: 0; 32 | 33 | position: relative; 34 | z-index: 1000; 35 | white-space: nowrap; 36 | text-align: right; 37 | 38 | &:active { 39 | background: darken(@main-color, 15%); 40 | } 41 | 42 | &:before { 43 | .font-icon-reset(); 44 | content: @fa-legacy-var-question; 45 | color: @main-color-text; 46 | 47 | text-align: center; 48 | .rem(font-size, 32); 49 | .rem(line-height, 70); 50 | 51 | display: block; 52 | height: 100%; 53 | width: 100%; 54 | position: absolute; 55 | left: 0; 56 | top: 0; 57 | 58 | @media(max-width: 320px) { 59 | font-size: 10vw !important; 60 | } 61 | } 62 | } 63 | &#touchui_dropdown_link a:before { 64 | content: none; 65 | } 66 | } 67 | 68 | #tabs { 69 | .flex-display(); 70 | } 71 | 72 | #navbar .navbar-inner .container .nav li#all_touchui_settings { 73 | width: 200px; 74 | } 75 | 76 | #navbar, 77 | #tabs { 78 | .nav > li, 79 | &.nav > li { 80 | .flex(0 0 100%); 81 | max-width: 7rem; 82 | 83 | @media(max-width: 565px) { 84 | max-width: 5.5rem; 85 | } 86 | 87 | @media(max-width: 439px) { 88 | max-width: 5rem; 89 | } 90 | 91 | @media(max-width: 400px) { 92 | max-width: 4.5rem; 93 | } 94 | 95 | &#touchui_dropdown_link { 96 | pointer-events: none; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /source/less/layout/links.less: -------------------------------------------------------------------------------- 1 | a { 2 | color: @main-color; 3 | 4 | &:active { 5 | color: @main-color-darker; 6 | } 7 | &.active { 8 | background: @main-color; 9 | color: @main-color-text; 10 | } 11 | } 12 | li.active { 13 | a { 14 | background: @main-color; 15 | color: @main-color-text; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/less/layout/navbar.less: -------------------------------------------------------------------------------- 1 | #navbar { 2 | z-index: 999; 3 | 4 | .navbar-inner { 5 | 6 | .container { 7 | .rem(height, 66); 8 | border:0 none; 9 | 10 | .nav { 11 | position: relative; 12 | z-index: 23; 13 | 14 | li { 15 | width: 100%; 16 | 17 | &.dropdown { 18 | margin: 0; 19 | z-index: 2; 20 | 21 | &#all_touchui_settings { 22 | 23 | > .dropdown-menu { 24 | min-width: 230px; 25 | } 26 | 27 | #touchui_text_nonlink_container { 28 | margin: 0; 29 | list-style: none; 30 | overflow: hidden; 31 | padding: 5px 0; 32 | } 33 | 34 | b.caret { 35 | display: none; 36 | } 37 | 38 | &.item_active { 39 | background: @main-background; 40 | 41 | > a:before { 42 | color: contrast(@main-background); 43 | } 44 | } 45 | 46 | &.offline > a:before { 47 | animation-name: blink_offline; 48 | } 49 | 50 | &.online > a:before { 51 | animation-name: blink_online; 52 | } 53 | 54 | > a:before { 55 | animation-duration: 0.6s; 56 | animation-iteration-count: 5; 57 | .font-icon-reset(); 58 | content: @fa-legacy-var-navicon; 59 | } 60 | 61 | &.open { 62 | .dropdown-menu { 63 | .open { 64 | a.dropdown-toggle { 65 | background: transparent; 66 | 67 | &:after { 68 | .font-icon-reset(); 69 | line-height: 1; 70 | content: @fa-legacy-var-caret-up !important; 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | li { 78 | 79 | &.offline { 80 | a:before { 81 | color: @offline-color; 82 | } 83 | } 84 | 85 | &.online { 86 | a:before { 87 | color: @online-color; 88 | } 89 | } 90 | 91 | &#conn_link2 { 92 | a:before { 93 | content: @fa-legacy-var-wifi; 94 | } 95 | } 96 | 97 | &#timelapse_link2 { 98 | a:before { 99 | .rem(font-size, 18); 100 | } 101 | } 102 | 103 | &#gcode_link2 { 104 | a:before { 105 | .rem(font-size, 17); 106 | } 107 | } 108 | 109 | &#navbar_settings { 110 | a:before { 111 | content: @fa-legacy-var-gear; 112 | } 113 | } 114 | 115 | &#navbar_plugin_announcements { 116 | display: none; 117 | } 118 | 119 | #usersettings_button { 120 | &:before { 121 | content: @fa-legacy-var-user; 122 | } 123 | } 124 | 125 | #logout_button { 126 | &:before { 127 | content: @fa-legacy-var-sign-out; 128 | } 129 | } 130 | 131 | #navbar_login { 132 | a.dropdown-toggle { 133 | &:after { 134 | .font-icon-reset(); 135 | content: @fa-legacy-var-caret-down; 136 | position: absolute; 137 | right: 20px; 138 | margin: 0; 139 | .rem(line-height, 40); 140 | } 141 | 142 | &:before { 143 | content: @fa-legacy-var-lock; 144 | } 145 | } 146 | } 147 | 148 | &#navbar_plugin_touchui { 149 | a:before { 150 | content: @fa-legacy-var-mobile-phone; 151 | .rem(font-size, 28); 152 | .rem(line-height, 28); 153 | position: relative; 154 | top: 5px; 155 | margin-top: -9px; 156 | } 157 | } 158 | 159 | &#navbar_systemmenu { 160 | > a { 161 | position: relative; 162 | display: block; 163 | float: none; 164 | 165 | &.active { 166 | &:after { 167 | .font-icon-reset(); 168 | content: @fa-legacy-var-caret-up !important; 169 | } 170 | } 171 | 172 | &:before { 173 | .font-icon-reset(); 174 | content: @fa-legacy-var-gears; 175 | .rem(font-size, 19); 176 | } 177 | i { 178 | display: none; 179 | } 180 | &:after { 181 | .font-icon-reset(); 182 | content: @fa-legacy-var-caret-down; 183 | right: 18px; 184 | position: absolute; 185 | top: 12px; 186 | line-height: 1; 187 | } 188 | } 189 | .dropdown-menu { 190 | a { 191 | &:before { 192 | .font-icon-reset(); 193 | content: @fa-legacy-var-angle-double-right; 194 | } 195 | } 196 | } 197 | } 198 | 199 | a { 200 | .box-sizing(border-box); 201 | 202 | color: @main-color-text; 203 | display: block; 204 | text-align: left; 205 | width: 100% !important; 206 | max-width: 100% !important; 207 | height: auto !important; 208 | float: none; 209 | 210 | text-overflow: ellipsis; 211 | overflow: hidden; 212 | 213 | .icon-wrench{ 214 | display: none; 215 | } 216 | 217 | &:active { 218 | background: darken(@main-color, 15%); 219 | } 220 | 221 | &:hover { 222 | background: transparent; 223 | } 224 | 225 | &:before { 226 | .font-icon-reset(); 227 | content: @fa-legacy-var-question; 228 | display: inline-block; 229 | margin: 0 15px 0 0; 230 | .rem(font-size, 20); 231 | .rem(width, 20); 232 | position: relative; 233 | top: 2px; 234 | text-align: center; 235 | } 236 | 237 | } 238 | 239 | &:active > a { 240 | background: darken(@main-color, 15%); 241 | } 242 | } 243 | } 244 | } 245 | 246 | #youcanhazlogin { 247 | margin: 0; 248 | width: 100%; 249 | overflow: hidden; 250 | 251 | #navbar_login { 252 | &.open { 253 | #login_dropdown_loggedout { 254 | padding: 5px @dropdown-gutter @dropdown-gutter !important; 255 | display: block; 256 | } 257 | } 258 | } 259 | 260 | .pull-right small a { 261 | display: none; 262 | } 263 | 264 | #login_dropdown_loggedout { 265 | display: none; 266 | border-top: 1px solid @dropdown-divider-color; 267 | margin-top: 0; 268 | padding: 0; 269 | backface-visibility: hidden; 270 | 271 | * { 272 | .box-sizing(border-box); 273 | } 274 | 275 | input[type="text"], 276 | input[type="password"] { 277 | width: 100%; 278 | display: inline-block; 279 | height: 30px; 280 | padding-left: 0; 281 | padding-right: 0; 282 | text-indent: 12px; 283 | } 284 | 285 | &.hide + #login_dropdown_loggedin { 286 | display: block; 287 | } 288 | } 289 | } 290 | 291 | } 292 | } 293 | } 294 | } 295 | 296 | #navbar_plugin_touchui { 297 | float: none; 298 | } 299 | 300 | @keyframes blink_offline { 301 | 0% { 302 | color: @main-color-text; 303 | } 304 | 50% { 305 | color: @offline-color; 306 | } 307 | 100% { 308 | color: @main-color-text; 309 | } 310 | } 311 | 312 | @keyframes blink_online { 313 | 0% { 314 | color: @main-color-text; 315 | } 316 | 50% { 317 | color: @online-color; 318 | } 319 | 100% { 320 | color: @main-color-text; 321 | } 322 | } 323 | 324 | } 325 | -------------------------------------------------------------------------------- /source/less/layout/tabbar.less: -------------------------------------------------------------------------------- 1 | #tabs, 2 | #navbar #all_touchui_settings { 3 | a:before { 4 | .font-icon-reset(); 5 | } 6 | 7 | #navbar_plugin_pi_support { 8 | a:before { 9 | content: none; 10 | } 11 | span { 12 | margin-left: 5px; 13 | } 14 | } 15 | 16 | #temp_link, 17 | #temp_link2 { 18 | a:before { 19 | content: @fa-legacy-var-thermometer-2; 20 | } 21 | } 22 | #control_link, 23 | #control_link2 { 24 | a:before { 25 | content: @fa-legacy-var-gamepad; 26 | } 27 | } 28 | #gcode_link, 29 | #gcode_link2 { 30 | a:before { 31 | content: @fa-legacy-var-object-ungroup; 32 | } 33 | } 34 | #term_link, 35 | #term_link2 { 36 | a:before { 37 | content: @fa-legacy-var-terminal; 38 | } 39 | } 40 | #timelapse_link, 41 | #timelapse_link2 { 42 | a:before { 43 | content: @fa-legacy-var-camera; 44 | } 45 | } 46 | #webcam_link, 47 | #webcam_link2 { 48 | a:before { 49 | content: @fa-legacy-var-image; 50 | } 51 | } 52 | #print_link, 53 | #print_link2 { 54 | a:before { 55 | content: @fa-legacy-var-server; 56 | } 57 | } 58 | 59 | // Plugins 60 | 61 | #sidebar_plugin_filamentmanager_wrapper_link, 62 | #sidebar_plugin_filamentmanager_wrapper_link2 { 63 | a:before { 64 | content: @fa-legacy-var-tasks; 65 | } 66 | } 67 | 68 | #sidebar_plugin_estop_wrapper_link, 69 | #sidebar_plugin_estop_wrapper_link2 { 70 | a:before { 71 | content: @fa-legacy-var-exclamation-circle; 72 | } 73 | } 74 | 75 | #sidebar_plugin_bedlevelingwizard_wrapper_link, 76 | #sidebar_plugin_bedlevelingwizard_wrapper_link2 { 77 | a:before { 78 | content: @fa-legacy-var-arrows-alt; 79 | } 80 | } 81 | 82 | #sidebar_plugin_touchtest_wrapper_link, 83 | #sidebar_plugin_touchtest_wrapper_link2 { 84 | a:before { 85 | content: @fa-legacy-var-edit; 86 | } 87 | } 88 | 89 | #tab_plugin_enclosure_link, 90 | #tab_plugin_enclosure_link2 { 91 | a:before { 92 | content: @fa-legacy-var-cube; 93 | } 94 | } 95 | 96 | #tab_plugin_astroprint_link, 97 | #tab_plugin_astroprint_link2 { 98 | a:before { 99 | content: @fa-legacy-var-cloud; 100 | } 101 | } 102 | 103 | #navbar_plugin_discordremote, 104 | #navbar_plugin_discordremote2 { 105 | a:before { 106 | content: @fa-legacy-var-gamepad; 107 | } 108 | } 109 | 110 | #tab_plugin_dashboard_link, 111 | #tab_plugin_dashboard_link2 { 112 | a:before { 113 | content: @fa-legacy-var-tachometer; 114 | } 115 | } 116 | 117 | #tab_plugin_bedlevelvisualizer_link, 118 | #tab_plugin_bedlevelvisualizer_link2 { 119 | a:before { 120 | content: @fa-legacy-var-street-view; 121 | } 122 | } 123 | 124 | #tab_plugin_stlviewer_link, 125 | #tab_plugin_stlviewer_link2 { 126 | a:before { 127 | content: @fa-legacy-var-street-view; 128 | } 129 | } 130 | 131 | #sidebar_plugin_automaticshutdown_wrapper_link, 132 | #sidebar_plugin_automaticshutdown_wrapper_link2 { 133 | a:before { 134 | content: @fa-legacy-var-power-off; 135 | } 136 | } 137 | } 138 | 139 | #tabs { 140 | #webcam_link { 141 | .rem(font-size, 29); 142 | .rem(line-height, 73); 143 | } 144 | 145 | #print_link { 146 | .rem(line-height, 74); 147 | } 148 | 149 | // Pevent FOUT! (not dutch error!) 150 | &:first-child { 151 | &:before { 152 | font-family: "touchui"; 153 | content: ""; 154 | position: absolute; 155 | top: -5000px; 156 | } 157 | } 158 | 159 | &.nav-tabs { 160 | .itemActive { 161 | background: @main-background; 162 | 163 | a { 164 | .border-radius(0); 165 | color: @main-background; 166 | 167 | &:after, 168 | &:before { 169 | color: contrast(@main-background); 170 | } 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /source/less/layout/touchscreen.less: -------------------------------------------------------------------------------- 1 | &.isTouchscreenUI { 2 | 3 | .modal-backdrop { 4 | background: transparent !important; 5 | } 6 | 7 | .modal.fade { 8 | opacity: 1 !important; 9 | } 10 | 11 | #reloadui_overlay_background, 12 | #offline_overlay_background { 13 | background: transparent !important; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /source/less/plugins/eeprommarlin.less: -------------------------------------------------------------------------------- 1 | #settings_plugin_eeprom_marlin ul.nav.nav-pills{ 2 | display: block; 3 | } -------------------------------------------------------------------------------- /source/less/plugins/history.less: -------------------------------------------------------------------------------- 1 | #tab_plugin_printhistory_link2 { 2 | a:before { 3 | .font-icon-reset(); 4 | content: @fa-legacy-var-history !important; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /source/less/plugins/m33fio.less: -------------------------------------------------------------------------------- 1 | #control { 2 | #control-jog-xy { 3 | > div { 4 | height: auto; 5 | left: auto; 6 | } 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /source/less/plugins/multicam.less: -------------------------------------------------------------------------------- 1 | #camControl { 2 | .flex-display(); 3 | .flex-flow(column); 4 | padding: 20px 30px 0; 5 | 6 | > div { 7 | .flex-display(); 8 | .flex-flow(row); 9 | width: 100%; 10 | 11 | button { 12 | width: auto; 13 | margin: 0 10px 0 0; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/less/plugins/navbartemp.less: -------------------------------------------------------------------------------- 1 | #navbar_plugin_navbartemp { 2 | overflow: hidden; 3 | 4 | > div { 5 | margin: 0; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/less/plugins/octolapse.less: -------------------------------------------------------------------------------- 1 | #navbar_plugin_octolapse { 2 | display: none; 3 | } -------------------------------------------------------------------------------- /source/less/plugins/printerstatistics.less: -------------------------------------------------------------------------------- 1 | #tab_plugin_stats_link2 { 2 | a:before { 3 | .font-icon-reset(); 4 | content: @fa-legacy-var-area-chart !important; 5 | } 6 | } 7 | 8 | #tab_plugin_stats { 9 | table { 10 | tr { 11 | td, &:hover { 12 | background: transparent; 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/less/plugins/psucontrol.less: -------------------------------------------------------------------------------- 1 | @psucontrol-on-color: @online-color; 2 | @psucontrol-off-color: #808080; 3 | 4 | #navbar .navbar-inner .container .nav li.dropdown#all_touchui_settings li #psucontrol_indicator { 5 | &:before { 6 | .font-icon-reset(); 7 | content: @fa-legacy-var-bolt; 8 | } 9 | 10 | &.on:before { 11 | color: @psucontrol-on-color; 12 | } 13 | 14 | &.off:before { 15 | color: @psucontrol-off-color; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/less/settings/touchui.less: -------------------------------------------------------------------------------- 1 | /* Modal TouchUI (settings modal) */ 2 | #settings_plugin_touchui { 3 | 4 | div.disabled { 5 | opacity: 0.4; 6 | pointer-events: none; 7 | } 8 | 9 | legend { 10 | label { 11 | margin-top: 12px; 12 | 13 | @media (max-width: 400px) { 14 | float: none; 15 | display: block; 16 | margin: 0; 17 | } 18 | } 19 | } 20 | 21 | .track { 22 | .close { 23 | width: 25px; 24 | height: 25px; 25 | background: @main-color; 26 | border: 1px solid @main-color-darker; 27 | position: absolute; 28 | top: 15px; 29 | right: 15px; 30 | border-radius: 50%; 31 | opacity: 1; 32 | 33 | &:after { 34 | .font-icon-reset(); 35 | content: @fa-legacy-var-close; 36 | color: @main-color-text; 37 | text-align: center; 38 | display: block; 39 | .rem(line-height, 25); 40 | } 41 | } 42 | } 43 | 44 | } 45 | 46 | /* Modal TouchUI (not settings) */ 47 | #touchui_settings_dialog { 48 | 49 | .btn-box { 50 | .flex-display(); 51 | .flex-wrap(wrap); 52 | margin: 10px 0; 53 | 54 | &:first-child { 55 | margin-top: 0; 56 | } 57 | } 58 | 59 | button { 60 | border: 0 none; 61 | .border-radius(0); 62 | } 63 | 64 | .label { 65 | .flex-display(); 66 | .align-items(center); 67 | .rem(font-size, 14); 68 | .rem(line-height, 20); 69 | .border-radius(0); 70 | padding: 6px 9px; 71 | } 72 | 73 | } 74 | 75 | /* Nabar */ 76 | #navbar_plugin_touchui { 77 | float: right; 78 | 79 | #navbar_touchui_settings { 80 | i:before { 81 | .font-icon-reset(); 82 | content: @fa-legacy-var-mobile; 83 | .rem(font-size, 28); 84 | position: relative; 85 | top: 5px; 86 | margin-top: -9px; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /source/less/tabs/connection.less: -------------------------------------------------------------------------------- 1 | #connection { 2 | height: auto !important; 3 | } 4 | #connection_wrapper { 5 | 6 | button { 7 | margin-top: 20px; 8 | background-color: @main-color; 9 | color: @main-color-text; 10 | 11 | &:active { 12 | background-color: @main-color-dark; 13 | } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /source/less/tabs/gcode.less: -------------------------------------------------------------------------------- 1 | #gcode { 2 | #gcode_canvas { 3 | width: 100% !important; 4 | height: 100% !important; 5 | 6 | // Disable hard all events 7 | pointer-events: none !important; 8 | } 9 | 10 | .slider { 11 | display: none; 12 | } 13 | 14 | .progress { 15 | margin: 30px 0 20px; 16 | width: 100%; 17 | } 18 | 19 | @media (max-width: 500px) { 20 | .span7, .span5 { 21 | float: none; 22 | width: 100%; 23 | display: block; 24 | margin: 0; 25 | padding: 0; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/less/tabs/printer.less: -------------------------------------------------------------------------------- 1 | #printer { 2 | 3 | .accordion { 4 | &-heading { 5 | display: none; 6 | } 7 | &-inner { 8 | padding: 0; 9 | background: transparent !important; 10 | 11 | hr { 12 | display: none; 13 | } 14 | } 15 | } 16 | 17 | .print-control { 18 | .btn { 19 | .flex-display(); 20 | .align-items(center); 21 | .rem(height, 50); 22 | padding: 0; 23 | overflow: hidden; 24 | } 25 | i { 26 | .rem(font-size, 28); 27 | .flex(1 1 100%); 28 | min-width: 100%; 29 | } 30 | .icon-print:before { 31 | .font-icon-reset(); 32 | content: @fa-legacy-var-step-forward; 33 | } 34 | #job_cancel, 35 | #job_pause { 36 | background: @text-color; 37 | color: contrast(@text-color); 38 | 39 | &:active { 40 | background: fade(@text-color, 70%); 41 | color: contrast(@text-color); 42 | } 43 | } 44 | #job_pause { 45 | top: 40px; 46 | } 47 | #job_cancel { 48 | top: 80px; 49 | } 50 | } 51 | 52 | #state { 53 | padding-top: ~"calc(3.125rem + 60px) !important"; 54 | 55 | .progress, 56 | .print-control { 57 | position: absolute !important; 58 | top: ~"calc(3.125rem + 15px)"; 59 | left: 0; 60 | height: 20px; 61 | width: 100%; 62 | margin: 0; 63 | } 64 | .print-control { 65 | top: 0; 66 | } 67 | .progress { 68 | top: ~"calc(3.125rem + 15px) !important"; 69 | } 70 | } 71 | 72 | .slimScrollBar, .slimScrollRail { 73 | display: none !important; 74 | } 75 | .dropdown-menu { 76 | left: auto !important; 77 | right: -10px !important; 78 | } 79 | 80 | } 81 | 82 | .progress-text-centered { 83 | top: auto !important; 84 | } 85 | -------------------------------------------------------------------------------- /source/less/tabs/temperature.less: -------------------------------------------------------------------------------- 1 | #temp { 2 | margin-left: -@main-gutter; 3 | margin-right: -@main-gutter; 4 | overflow: hidden; 5 | 6 | position: absolute; 7 | left: -99999px; 8 | top: -99999px; 9 | display: block; 10 | max-width: 100vw; 11 | 12 | &.active { 13 | position: relative; 14 | left: auto; 15 | top: auto; 16 | } 17 | 18 | > div:first-child { 19 | .box-sizing(border-box); 20 | overflow: hidden; 21 | 22 | width: 100%; 23 | height: 60vh; 24 | max-height: none; 25 | min-height: 200px; 26 | 27 | padding: 0 @main-gutter; 28 | margin: 0 0 @main-gutter; 29 | 30 | #temperature-graph { 31 | .box-sizing(border-box); 32 | position: relative; 33 | 34 | background-position: center 37%; 35 | background-repeat: no-repeat; 36 | background-size: auto 80%; 37 | 38 | width: 100%; 39 | height: 100%; 40 | margin: 0; 41 | 42 | .tickLabel, .legendLabel { 43 | color: contrast(@main-background); 44 | } 45 | 46 | table { 47 | width: auto; 48 | 49 | tr { 50 | background: transparent !important; 51 | 52 | &:hover { 53 | background: transparent !important; 54 | 55 | td { 56 | background: transparent !important; 57 | } 58 | } 59 | 60 | td { 61 | background: transparent !important; 62 | color: contrast(@main-color); 63 | border: 0 none; 64 | 65 | &.legendLabel { 66 | padding: 0 15px 0 5px; 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | 75 | > div:last-child { 76 | .box-sizing(border-box); 77 | 78 | table { 79 | margin: 0 !important; 80 | width: 100%; 81 | 82 | tr { 83 | &:first-child { 84 | th { 85 | .rem(font-size, 18); 86 | 87 | &:first-child { 88 | width: 10% !important; 89 | } 90 | &:nth-child(3) { 91 | width: auto !important; 92 | } 93 | @media (max-width: 730px) { 94 | &:last-child { 95 | width: 38vw !important; 96 | .box-sizing(); 97 | } 98 | } 99 | } 100 | } 101 | 102 | th, td { 103 | .rem(font-size, 16); 104 | padding: 12px; 105 | 106 | &:nth-child(1) { 107 | text-align: right; 108 | padding-left: 0; 109 | } 110 | 111 | &:nth-child(2) { 112 | text-align: center !important; 113 | } 114 | 115 | /*&:nth-child(3) { 116 | padding-right: 30px; 117 | }*/ 118 | 119 | .dropdown-menu { 120 | margin: 2px 0 9px; 121 | bottom: 100%; 122 | top: auto; 123 | left: auto; 124 | right: 0; 125 | 126 | &:before { 127 | border: 10px solid transparent; 128 | border-top-color: @main-color; 129 | border-bottom-color: transparent !important; 130 | position: absolute; 131 | top: auto; 132 | bottom: -18px; 133 | right: 9px; 134 | } 135 | } 136 | 137 | &.temperature_offset { 138 | .form-inline { 139 | .input-append { 140 | .flex-wrap(wrap); 141 | max-width: 100%; 142 | } 143 | } 144 | } 145 | 146 | .form-inline, 147 | .input-append { 148 | .box-sizing(border-box); 149 | 150 | .flex-display(flex); 151 | .flex-direction(row); 152 | .flex-wrap(wrap); 153 | .justify-content(flex-start); 154 | .align-content(stretch); 155 | .align-items(flex-start); 156 | 157 | > * { 158 | .flex-order(0); 159 | .flex(0 1 auto); 160 | .align-self(auto); 161 | margin-top: 2px; 162 | margin-bottom: 2px; 163 | } 164 | 165 | > .input-append { 166 | .flex(1 1 0%); 167 | .flex-wrap(); 168 | padding-right: 10px; 169 | margin-bottom: 0; 170 | margin-top: 0; 171 | } 172 | 173 | input, .input-mini { 174 | .flex-order(0); 175 | .flex(1 1 14px); 176 | .align-self(auto); 177 | 178 | margin-right: 0px; 179 | padding: 7px 0 7px 15px; 180 | .border-radius(0); 181 | text-align: left; 182 | 183 | width: 0% !important; 184 | max-width: none !important; 185 | min-width: 0px; 186 | } 187 | .btn-group, 188 | > button { 189 | .border-radius(0); 190 | 191 | .fa, .icon { 192 | pointer-events: none; 193 | } 194 | } 195 | .add-on { 196 | padding: 7px; 197 | margin-right: 1px; 198 | .rem(font-size, 14); 199 | .rem(line-height, 20); 200 | height: auto; 201 | 202 | @media (max-width: 474px) { 203 | display: none; 204 | } 205 | } 206 | button { 207 | padding: 7px 15px; 208 | .border-radius(0); 209 | 210 | .caret { 211 | width: 11px; 212 | 213 | &:after { 214 | content: @fa-legacy-var-caret-down; 215 | .font-icon-reset(); 216 | .rem(font-size, 18); 217 | top: -7px; 218 | position: relative; 219 | } 220 | } 221 | } 222 | } 223 | } 224 | } 225 | } 226 | 227 | @media (max-width: 730px) { 228 | width: 138vw !important; 229 | margin: 0 0 @main-gutter 0 !important; 230 | transition: left 0.5s ease; 231 | cursor: w-resize; 232 | position: relative; 233 | left: 0; 234 | 235 | &.open { 236 | left: -38vw !important; 237 | } 238 | } 239 | } 240 | 241 | #temperature-table { 242 | @media (max-width: 490px) { 243 | td, 244 | th { 245 | .rem(font-size, 14) !important; 246 | } 247 | } 248 | 249 | td { 250 | .btn { 251 | border-left: 1px solid @table-row-background; 252 | } 253 | } 254 | 255 | th.temperature_target { 256 | .btn-group { 257 | float: none !important; 258 | margin-top: -5px; 259 | 260 | .caret:after { 261 | content: @fa-legacy-var-caret-down; 262 | .font-icon-reset(); 263 | font-size: 1.125rem; 264 | top: -7px; 265 | left: -6px; 266 | position: relative; 267 | } 268 | } 269 | } 270 | 271 | th.temperature_target, 272 | th.temperature_offset { 273 | text-align: left; 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /source/less/tabs/terminal.less: -------------------------------------------------------------------------------- 1 | #term { 2 | position: relative; 3 | z-index: 1; 4 | 5 | .icon-caret-down:before, 6 | .icon-caret-right:before { 7 | .font-icon-reset(); 8 | content: @fa-legacy-var-chevron-down; 9 | } 10 | .icon-caret-right:before { 11 | content: @fa-legacy-var-chevron-right; 12 | } 13 | 14 | span.muted { 15 | color: fade(@terminal-color, 50%); 16 | } 17 | 18 | .row-fluid { 19 | clear: both; 20 | padding-top: 5px; 21 | } 22 | 23 | ::-webkit-scrollbar-thumb { 24 | background: @terminal-color; 25 | } 26 | 27 | a { 28 | color: @terminal-color; 29 | } 30 | 31 | button, 32 | .btn { 33 | background: @terminal-color; 34 | color: contrast(@terminal-color, @dark-color, @light-color, 51%); 35 | 36 | &:active { 37 | background: transparent; 38 | color: @terminal-color; 39 | } 40 | } 41 | 42 | .terminal { 43 | 44 | small { 45 | &.pull-left { 46 | margin-right: 30px; 47 | 48 | span { 49 | display: block !important; 50 | padding: 10px 0 0 0 !important; 51 | 52 | + span { 53 | display: none !important;; 54 | } 55 | } 56 | } 57 | &.pull-right { 58 | position: absolute; 59 | right: 0; 60 | } 61 | 62 | button, 63 | .btn { 64 | padding: 3px 10px; 65 | background: transparent; 66 | color: @terminal-color; 67 | 68 | + span { 69 | padding-left: 7px; 70 | } 71 | &.active { 72 | background: @terminal-color; 73 | color: contrast(@terminal-color, @dark-color, @light-color, 51%); 74 | } 75 | } 76 | } 77 | a:not(.btn) { 78 | .rem(font-size, 35); 79 | 80 | i:before { 81 | .font-icon-reset(); 82 | } 83 | } 84 | 85 | button, 86 | .btn { 87 | padding: 4px 12px; 88 | background: @main-background; 89 | color: contrast(@main-background); 90 | 91 | &.active { 92 | background: @terminal-color; 93 | color: contrast(@terminal-color, @dark-color, @light-color, 51%); 94 | } 95 | } 96 | } 97 | 98 | #terminal-filterpanel, 99 | #termin-filterpanel {//TODO remove #termin in future; backwards compatibility with <1.3.0 100 | width: 100%; 101 | } 102 | 103 | #terminal-scroll { 104 | border: 1px solid @terminal-color; 105 | overflow: hidden; 106 | .rem(margin-bottom, 28); 107 | .border-radius(5px, 5px, 0, 0); 108 | height: 69vh; 109 | min-height: 150px; 110 | background: @terminal-background; 111 | z-index: 2; 112 | } 113 | 114 | #terminal-output, 115 | #terminal-output-lowfi { 116 | height: auto; 117 | min-height: 0; 118 | max-height: none; 119 | .box-sizing(border-box); 120 | margin-bottom: 26px; 121 | border: 0 none; 122 | margin: 0; 123 | padding: 10px @terminal-gutter @terminal-gutter; 124 | overflow: hidden; 125 | color: @terminal-text-color; 126 | background-color: transparent; 127 | font-size: 0.8rem; 128 | line-height: 1.4rem; 129 | 130 | &:before { 131 | font-family: "touchui"; 132 | content: "\e601\e605\e604\e603\e602\e600"; 133 | display: block; 134 | width: 100%; 135 | margin: 5px auto 0; 136 | position: relative; 137 | .rem(font-size, 34); 138 | .rem(line-height, 62); 139 | letter-spacing: 3px; 140 | text-align: center; 141 | 142 | @media (max-width: 350px) { 143 | font-size: 10vw; 144 | } 145 | 146 | @media (max-width: 280px) { 147 | font-size: 8vw; 148 | } 149 | 150 | } 151 | 152 | span:first-child { 153 | &:before { 154 | content: "v?.?.?"; 155 | display: block; 156 | position: relative; 157 | left: 0; 158 | margin: -5px 0 20px; 159 | .rem(line-height, 17); 160 | .rem(font-size, 14); 161 | width: 100%; 162 | text-align: center; 163 | } 164 | } 165 | } 166 | #terminal-sendpanel { 167 | position: absolute; 168 | left: 0; 169 | width: 100%; 170 | margin: 0; 171 | top: 69vh; 172 | .rem(font-size, 14); 173 | 174 | .muted { 175 | display: none; 176 | } 177 | 178 | input, button, .btn { 179 | &:disabled, .disabled { 180 | opacity: 1; 181 | background: mix(@terminal-color, @main-background, 30%); 182 | color: @terminal-color; 183 | } 184 | } 185 | .input-append { 186 | position: relative; 187 | } 188 | input { 189 | width: 100%; 190 | background-color: @main-background; 191 | color: @terminal-color; 192 | border: 1px solid @terminal-color; 193 | padding: 0; 194 | display: block; 195 | z-index: 2; 196 | 197 | .rem(font-size, 14); 198 | .rem(height, 30); 199 | text-indent: 10px; 200 | padding-right: 90px; 201 | 202 | .box-sizing(border-box); 203 | .border-radius(0, 0, 5px, 5px); 204 | } 205 | button, .btn { 206 | position: absolute; 207 | top: 0; 208 | right: 1px; 209 | background-color: @terminal-color; 210 | color: @main-background; 211 | border: 0 none; 212 | border-top: 1px solid @terminal-color; 213 | border-left: 1px solid @terminal-color; 214 | .text-shadow(0 0 0 transparent); 215 | .border-radius(0, 0, 5px, 0); 216 | .box-sizing(); 217 | .rem(height, 30); 218 | .rem(line-height, 20); 219 | .rem(font-size, 18); 220 | z-index: 21; 221 | padding: 4px 12px; 222 | } 223 | 224 | .btn { 225 | width: auto; 226 | } 227 | } 228 | } 229 | 230 | #scroll { 231 | #term { 232 | .iScrollIndicator { 233 | background: @terminal-iScroll-color !important; 234 | border: 0 none !important; 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /source/less/tabs/timelapse.less: -------------------------------------------------------------------------------- 1 | #timelapse { 2 | 3 | > h1 { 4 | margin: 30px 0 0; 5 | } 6 | 7 | .icon-unchecked:before, 8 | .icon-check-empty:before { 9 | .font-icon-reset(); 10 | content: @fa-legacy-var-copy; 11 | } 12 | 13 | .caret { 14 | display: none; 15 | } 16 | 17 | .pull-left, .pull-right { 18 | button { 19 | font-size: 1.1rem; 20 | padding: 10px 15px; 21 | } 22 | } 23 | 24 | input[type="text"], 25 | input[type="number"], 26 | input[type="password"], 27 | select { 28 | width: 100%; 29 | margin-bottom: 20px; 30 | text-align: left; 31 | } 32 | 33 | .help-block { 34 | color: fade(contrast(@main-background), 80%); 35 | padding: 0; 36 | font-size: 0.9em; 37 | line-height: 1rem; 38 | } 39 | 40 | .input-append { 41 | position: relative; 42 | margin-bottom: 20px; 43 | 44 | input[type="text"], 45 | input[type="number"], 46 | input[type="password"] { 47 | width: 100%; 48 | padding: 10px 50px 10px 10px; 49 | margin: 0 !important; 50 | height: 32px; 51 | .box-sizing(border-box); 52 | .border-radius(0); 53 | } 54 | 55 | .add-on { 56 | border-left: 1px solid @main-background; 57 | position: absolute; 58 | right: 0; 59 | padding: 6px; 60 | } 61 | } 62 | 63 | .checkbox { 64 | margin-bottom: 20px; 65 | } 66 | 67 | > table { 68 | tr { 69 | .timelapse_files_action { 70 | width: 75px; 71 | text-align: right; 72 | } 73 | .timelapse_files_size { 74 | text-align: center; 75 | } 76 | 77 | td { 78 | .rem(line-height, 40); 79 | 80 | a { 81 | padding: 6px; 82 | margin: -6px; 83 | display: inline-block; 84 | color: @link-color; 85 | 86 | &:before { 87 | .rem(font-size, 21); 88 | .rem(line-height, 36); 89 | } 90 | &:active { 91 | color: @link-color; 92 | } 93 | } 94 | } 95 | 96 | } 97 | 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /source/less/tabs/webcam.less: -------------------------------------------------------------------------------- 1 | #webcam { 2 | margin: (-@main-gutter + 1px) -@main-gutter 0; 3 | 4 | #webcam_rotator, 5 | #webcam_rotator .webcam_fixed_ratio .webcam_fixed_ratio_inner { 6 | display: flex; 7 | align-content: center; 8 | align-items: center; 9 | justify-content: center; 10 | } 11 | 12 | #webcam_rotator .webcam_fixed_ratio { 13 | padding: 0; 14 | height: 100%; 15 | width: 100%; 16 | } 17 | 18 | #webcam_container { 19 | background: @main-background; 20 | 21 | .text { 22 | color: @text-color; 23 | } 24 | } 25 | 26 | #webcam_image { 27 | display: block; 28 | width: 100%; 29 | height: auto; 30 | } 31 | 32 | .muted { 33 | padding: 10px 35px; 34 | display: block; 35 | } 36 | 37 | .rotate90, 38 | .webcam_rotated .webcam_fixed_ratio { 39 | transform: rotate(0deg) !important; 40 | height: auto; 41 | 42 | #webcam_image { 43 | transform: rotate(-90deg) !important; 44 | max-width: ~"calc(100vh - 69px)"; 45 | 46 | &.flipV { 47 | transform: rotate(-90deg) scaleX(-1) !important; 48 | } 49 | &.flipH { 50 | transform: rotate(-90deg) scaleY(-1) !important; 51 | } 52 | &.flipV.flipH { 53 | transform: rotate(-90deg) scaleY(-1) scaleX(-1) !important; 54 | } 55 | } 56 | } 57 | .webcam_rotated .webcam_fixed_ratio { 58 | padding: 0; 59 | } 60 | 61 | @supports (object-fit: contain) { 62 | #webcam_rotator, 63 | #webcam_rotator .webcam_fixed_ratio .webcam_fixed_ratio_inner { 64 | height: ~"calc(100vh - 69px)"; 65 | } 66 | 67 | #webcam_image { 68 | object-fit: contain; 69 | display: block; 70 | width: 100%; 71 | height: auto; 72 | max-height: 100%; 73 | max-width: 100%; 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /source/less/touchui.less: -------------------------------------------------------------------------------- 1 | @import "_variables.less"; 2 | @import "_mixins.less"; 3 | 4 | @import "fonts/sourcecodepro.less"; 5 | @import "fonts/touchui.less"; 6 | @import "fonts/fontawesome.less"; 7 | 8 | @import "settings/touchui.less"; 9 | @import "components/tinycolorpicker.less"; 10 | 11 | #touch { 12 | width: 100%; 13 | height: 100%; 14 | background: @main-background; 15 | font-size: @main-font-size + 0px; 16 | 17 | @import "components/emulate.touch.less"; 18 | @import "components/scrollbars.less"; 19 | @import "layout/touchscreen.less"; 20 | 21 | body { 22 | width: 100%; 23 | height: auto; 24 | min-height: 100%; 25 | background: @main-background; 26 | color: @text-color; 27 | position: relative; 28 | .rem(font-size, 14); 29 | .rem(line-height, 20); 30 | 31 | * { 32 | .text-shadow(none) !important; 33 | .box-shadow(none) !important; 34 | 35 | &:not([class^="icon-"]):not([class*=" icon-"]):not(.fa):not(.far):not(.fas):not(.fab) { 36 | font-family: 'Source Code Pro', monospace; 37 | } 38 | } 39 | 40 | .no-pointer * { 41 | pointer-events: none !important; 42 | } 43 | 44 | #touchui_auto_load_resolution, 45 | #touchui_auto_load_touch { 46 | display: none; 47 | } 48 | 49 | div.error { 50 | background: transparent !important; 51 | color: inherit !important; 52 | } 53 | 54 | .touchui_disabled { 55 | opacity: 0.7; 56 | pointer-events: none; 57 | } 58 | 59 | #settings-appearanceCloseModalsWithClick span { 60 | font-size: 11px; 61 | } 62 | 63 | @import "layout/header.less"; 64 | @import "layout/general.less"; 65 | @import "layout/links.less"; 66 | @import "layout/buttons.less"; 67 | @import "layout/navbar.less"; 68 | @import "layout/tabbar.less"; 69 | @import "layout/form.less"; 70 | 71 | @import "tabs/webcam.less"; 72 | @import "tabs/printer.less"; 73 | @import "tabs/terminal.less"; 74 | @import "tabs/temperature.less"; 75 | @import "tabs/controls.less"; 76 | @import "tabs/gcode.less"; 77 | @import "tabs/connection.less"; 78 | @import "tabs/timelapse.less"; 79 | 80 | @import "components/progress.less"; 81 | @import "components/overlays.less"; 82 | @import "components/files.less"; 83 | @import "components/modal.less"; 84 | @import "components/scroll.less"; 85 | @import "components/keyboard.less"; 86 | @import "components/dropdown.less"; 87 | @import "components/notifications.less"; 88 | @import "components/pagination.less"; 89 | @import "components/popover.less"; 90 | 91 | @import "plugins/navbartemp.less"; 92 | @import "plugins/history.less"; 93 | @import "plugins/printerstatistics.less"; 94 | @import "plugins/psucontrol.less"; 95 | @import "plugins/m33fio.less"; 96 | @import "plugins/eeprommarlin.less"; 97 | @import "plugins/octolapse.less"; 98 | @import "plugins/multicam.less"; 99 | 100 | @import "layout/footer.less"; 101 | 102 | // For phone users 103 | #files_wrapper .entry, 104 | #temp .row-fluid table { 105 | .transform(translateZ(0)); 106 | .user-select(none); 107 | } 108 | 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /source/svg/C.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /source/svg/H.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /source/svg/O.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /source/svg/T.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /source/svg/U.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /source/svg/UI.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /source/svg/fan.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/vendors/tinycolorpicker/lib/jquery.tinycolorpicker.min.js: -------------------------------------------------------------------------------- 1 | /*! tinycolorpicker - v0.9.4 - 2015-11-20 2 | * http://www.baijs.com/tinycolorpicker 3 | * 4 | * Copyright (c) 2015 Maarten Baijs ; 5 | * Licensed under the MIT license */ 6 | 7 | !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){function b(b,e){function f(){return s?(m=a(""),k.append(m),q=m[0].getContext("2d"),g()):a.each(j.options.colors,function(a,b){var c=p.clone();c.css("backgroundColor",b),c.attr("data-color",b),o.append(c)}),h(),j}function g(){var b=new Image,c=k.css("background-image").replace(/"/g,"").replace(/url\(|\)$/gi,"");b.crossOrigin="Anonymous",k.css("background-image","none"),a(b).on("load",function(){m.attr("width",this.width),m.attr("height",this.height),q.drawImage(b,0,0,this.width,this.height)}),b.src=j.options.backgroundUrl||c}function h(){var b=t?"touchstart":"mousedown";s?(l.bind(b,function(b){b.preventDefault(),b.stopPropagation(),k.toggle(),a(document).bind("mousedown.colorpicker",function(b){a(document).unbind(".colorpicker"),j.close()})}),t?(m.bind("touchstart",function(a){return r=!0,i(a.originalEvent.touches[0]),!1}),m.bind("touchmove",function(a){return i(a.originalEvent.touches[0]),!1}),m.bind("touchend",function(a){return j.close(),!1})):(m.mousedown(function(b){return r=!0,i(b),a(document).bind("mouseup.colorpicker",function(b){return a(document).unbind(".colorpicker"),j.close(),!1}),!1}),m.mousemove(i))):(l.bind("mousedown",function(a){a.preventDefault(),a.stopPropagation(),o.toggle()}),o.delegate("li","mousedown",function(b){b.preventDefault(),b.stopImmediatePropagation();var c=a(this).attr("data-color");j.setColor(c),o.hide()}))}function i(c){if(r){var d=a(c.target),e=d.offset(),f=q.getImageData(c.pageX-e.left,c.pageY-e.top,1,1).data;j.setColor("rgb("+f[0]+","+f[1]+","+f[2]+")"),b.trigger("change",[j.colorHex,j.colorRGB])}}this.options=a.extend({},d,e),this._defaults=d,this._name=c;var j=this,k=b.find(".track"),l=b.find(".color"),m=null,n=b.find(".colorInput"),o=b.find(".dropdown"),p=o.find("li").remove(),q=null,r=!1,s=!!document.createElement("canvas").getContext,t="ontouchstart"in document.documentElement;return this.colorHex="",this.colorRGB="",this.setColor=function(a){a.indexOf("#")>=0?(j.colorHex=a,j.colorRGB=j.hexToRgb(j.colorHex)):(j.colorRGB=a,j.colorHex=j.rgbToHex(j.colorRGB)),l.find(".colorInner").css("backgroundColor",j.colorHex),n.val(j.colorHex)},this.close=function(){r=!1,k.hide()},this.hexToRgb=function(a){var b=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(a);return"rgb("+parseInt(b[1],16)+","+parseInt(b[2],16)+","+parseInt(b[3],16)+")"},this.rgbToHex=function(a){function b(a){var b=new Array("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");return isNaN(a)?"00":b[(a-a%16)/16]+b[a%16]}var c=a.match(/\d+/g);return"#"+b(c[0])+b(c[1])+b(c[2])},f()}var c="tinycolorpicker",d={colors:["#ffffff","#A7194B","#FE2712","#FB9902","#FABC02","#FEFE33","#D0EA2B","#66B032","#0391CE","#0247FE","#3D01A5","#8601AF"],backgroundUrl:null};a.fn[c]=function(d){return this.each(function(){a.data(this,"plugin_"+c)||a.data(this,"plugin_"+c,new b(a(this),d))})}}); -------------------------------------------------------------------------------- /source/vendors/tinycolorpicker/lib/tinycolorpicker.min.js: -------------------------------------------------------------------------------- 1 | /*! tinycolorpicker - v0.9.4 - 2015-11-20 2 | * http://www.baijs.com/tinycolorpicker 3 | * 4 | * Copyright (c) 2015 Maarten Baijs ; 5 | * Licensed under the MIT license */ 6 | 7 | !function(a){"use strict";function b(){for(var a=1;a=0?(k.colorHex=a,k.colorRGB=k.hexToRgb(k.colorHex)):(k.colorRGB=a,k.colorHex=k.rgbToHex(k.colorRGB)),n.style.backgroundColor=k.colorHex,p.value=k.colorHex},this.close=function(){r=!1,l.style.display="none"},this.hexToRgb=function(a){var b=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(a);return"rgb("+parseInt(b[1],16)+","+parseInt(b[2],16)+","+parseInt(b[3],16)+")"},this.rgbToHex=function(a){function b(a){var b=new Array("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");return isNaN(a)?"00":b[(a-a%16)/16]+b[a%16]}var c=a.match(/\d+/g);return"#"+b(c[0])+b(c[1])+b(c[2])},g()}var d="tinycolorpicker",e={backgroundUrl:null},f=function(a,b){return new c(a,b)};"function"==typeof define&&define.amd?define(function(){return f}):"object"==typeof module&&module.exports?module.exports=f:a.tinycolorpicker=f}(window); --------------------------------------------------------------------------------