├── .vscode ├── settings.json ├── extensions.json ├── tasks.json └── launch.json ├── demo.gif ├── icon.png ├── webview ├── style │ ├── style.scss │ ├── _colorblind.scss │ ├── _theme.scss │ ├── _vscode.scss │ ├── _game.scss │ └── style.css ├── icon.svg ├── modules │ └── tinycolor.min.js └── main.js ├── .gitignore ├── tsconfig.json ├── .eslintrc.js ├── src ├── extension.ts └── modules │ ├── commands.ts │ └── panel.ts ├── README.md ├── package.json └── LICENSE /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jeusto/warm-up-vscode/HEAD/demo.gif -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jeusto/warm-up-vscode/HEAD/icon.png -------------------------------------------------------------------------------- /webview/style/style.scss: -------------------------------------------------------------------------------- 1 | @import "./vscode"; 2 | @import "./game"; 3 | @import "./theme"; 4 | @import "./colorblind"; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .vsix 3 | *.map 4 | out 5 | node_modules 6 | webviewPanel.js.map 7 | webviewPanel.min.js 8 | extension.min.js 9 | extension.js.map -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "dbaeumer.vscode-eslint" 8 | ] 9 | } -------------------------------------------------------------------------------- /webview/style/_colorblind.scss: -------------------------------------------------------------------------------- 1 | body.colorblind #text-display { 2 | color: #b4bdc7; 3 | } 4 | 5 | body.colorblind #input-field.wrong { 6 | color: #b2a048; 7 | } 8 | 9 | body.colorblind .highlight { 10 | color: #ffffff; 11 | } 12 | 13 | body.colorblind .correct { 14 | color: #6a91dc; 15 | } 16 | 17 | body.colorblind .wrong { 18 | color: #b2a048; 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "lib": [ 6 | "ES2019", 7 | "DOM" 8 | ], 9 | "outDir": "out", 10 | "sourceMap": true, 11 | "strict": true, 12 | "strictNullChecks": false, 13 | "rootDir": "src", 14 | "experimentalDecorators": true, 15 | "noImplicitAny": false 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | @type {import('eslint').Linter.Config} 2 | // eslint-disable-next-line no-undef 3 | module.exports = { 4 | root: true, 5 | parser: '@typescript-eslint/parser', 6 | plugins: [ 7 | '@typescript-eslint', 8 | ], 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | ], 13 | ignorePatterns: [ 14 | 'media' 15 | ], 16 | rules: { 17 | 'semi': [2, "always"], 18 | '@typescript-eslint/explicit-module-boundary-types': 0, 19 | '@typescript-eslint/no-non-null-assertion': 0, 20 | } 21 | }; -------------------------------------------------------------------------------- /webview/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": ["--extensionDevelopmentPath=${workspaceRoot}"], 14 | "outFiles": ["${workspaceFolder}/out/**/*.js"], 15 | "preLaunchTask": "npm: watch" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /webview/style/_theme.scss: -------------------------------------------------------------------------------- 1 | /* General */ 2 | #text-display, 3 | #code-display, 4 | #typing-area, 5 | #coding-area { 6 | background: var(--boxBackgroundColor); 7 | } 8 | ::-webkit-scrollbar-corner { 9 | background-color: var(--boxBackgroundColor); 10 | } 11 | 12 | /* Words mode */ 13 | #input-field.wrong { 14 | color: #e05561; 15 | } 16 | .highlight { 17 | color: #f0a45d; 18 | } 19 | .correct { 20 | color: #8cc265; 21 | } 22 | .wrong { 23 | color: #e05561; 24 | } 25 | 26 | /* Code snippet mode */ 27 | .char.notpassed { 28 | background: #e0556170; 29 | } 30 | #cursor { 31 | background: #5dbeff; 32 | } 33 | 34 | /* Prism.js */ 35 | code[class*="language-"], 36 | pre[class*="language-"] { 37 | color: #c1c9d4; 38 | } 39 | .token.comment, 40 | .token.prolog, 41 | .token.doctype, 42 | .token.cdata { 43 | color: #909aad; 44 | } 45 | .token.punctuation { 46 | color: #c1c9d4; 47 | } 48 | 49 | .vscode-light code[class*="language-"], 50 | .vscode-light pre[class*="language-"] { 51 | color: #454d5e; 52 | } 53 | .vscode-light .token.comment, 54 | .vscode-light .token.prolog, 55 | .vscode-light .token.doctype, 56 | .vscode-light .token.cdata { 57 | color: #99a5b6; 58 | } 59 | .vscode-light .token.punctuation { 60 | color: #454d5e; 61 | } 62 | 63 | .token.selector, 64 | .token.tag { 65 | color: #e06c75; 66 | } 67 | .token.property, 68 | .token.boolean, 69 | .token.number, 70 | .token.constant, 71 | .token.symbol, 72 | .token.attr-name, 73 | .token.deleted { 74 | color: #d19a66; 75 | } 76 | .token.string, 77 | .token.char, 78 | .token.attr-value, 79 | .token.builtin, 80 | .token.inserted { 81 | color: #98c379; 82 | } 83 | .token.operator, 84 | .token.entity, 85 | .token.url, 86 | .language-css .token.string, 87 | .style .token.string { 88 | color: #56b6c2; 89 | } 90 | .token.atrule, 91 | .token.keyword { 92 | color: #c678dd; 93 | } 94 | .token.function { 95 | color: #61afef; 96 | } 97 | .token.regex, 98 | .token.important, 99 | .token.variable { 100 | color: #c678dd; 101 | } 102 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | // This script will be run within VS Code 2 | // It can access the main VS Code APIs directly 3 | import { 4 | Uri, 5 | WebviewPanel, 6 | StatusBarItem, 7 | ExtensionContext, 8 | StatusBarAlignment, 9 | window, 10 | } from "vscode"; 11 | 12 | import WarmupWebview from "./modules/panel"; 13 | import registerCommands from "./modules/commands"; 14 | 15 | // Init status bar icon 16 | let startButton: StatusBarItem; 17 | 18 | // Function called after activation event 19 | export function activate(context: ExtensionContext) { 20 | // Fetch data from json file 21 | const fs = require("fs"); 22 | const rawdata = fs.readFileSync( 23 | `${context.extensionPath}/webview/data.json`, 24 | "utf8" 25 | ); 26 | const data = JSON.parse(rawdata); 27 | const words = data.words; 28 | const codes = data.codes; 29 | 30 | // Add status bar icon 31 | startButton = window.createStatusBarItem(StatusBarAlignment.Left, 1); 32 | startButton.command = "warmUp.start"; 33 | startButton.tooltip = "Start typing test"; 34 | startButton.text = `$(record-keys) Warm Up`; 35 | 36 | context.subscriptions.push(startButton); 37 | startButton.show(); 38 | 39 | // Register all the commands 40 | registerCommands(WarmupWebview, context, words, codes); 41 | 42 | // Register webview panel serializer 43 | if (window.registerWebviewPanelSerializer) { 44 | // Make sure we register a serializer in activation event 45 | window.registerWebviewPanelSerializer(WarmupWebview.viewType, { 46 | async deserializeWebviewPanel(webviewPanel: WebviewPanel, state: any) { 47 | // Reset the webview options so we use latest uri for `localResourceRoots`. 48 | webviewPanel.webview.options = { 49 | enableScripts: true, 50 | localResourceRoots: [Uri.joinPath(context.extensionUri, "webview")], 51 | }; 52 | WarmupWebview.revive(webviewPanel, context.extensionUri); 53 | 54 | // Send config 55 | if (WarmupWebview.currentPanel) { 56 | WarmupWebview.currentPanel.sendStartAndConfig(words, codes); 57 | } 58 | }, 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /webview/style/_vscode.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --container-paddding: 20px; 3 | --input-padding-vertical: 6px; 4 | --input-padding-horizontal: 4px; 5 | --input-margin-vertical: 4px; 6 | --input-margin-horizontal: 0; 7 | } 8 | 9 | body { 10 | padding: 0 var(--container-paddding); 11 | color: var(--vscode-editor-foreground); 12 | font-size: var(--vscode-font-size); 13 | font-weight: var(--vscode-font-weight); 14 | background-color: var(--vscode-editor-background); 15 | font-family: var(--vscode-editor-font-family); 16 | } 17 | 18 | svg { 19 | width: calc(var(--vscode-font-size) * 1.75); 20 | stroke: var(--vscode-foreground); 21 | } 22 | 23 | ol, 24 | ul { 25 | padding-left: var(--container-paddding); 26 | } 27 | 28 | body > *, 29 | form > * { 30 | margin-block-start: var(--input-margin-vertical); 31 | margin-block-end: var(--input-margin-vertical); 32 | } 33 | 34 | *:focus { 35 | outline-color: var(--vscode-foreground) !important; 36 | } 37 | 38 | a { 39 | color: var(--vscode-textLink-foreground); 40 | } 41 | 42 | a:hover, 43 | a:active { 44 | color: var(--vscode-textLink-activeForeground); 45 | } 46 | 47 | code { 48 | font-size: var(--vscode-editor-font-size); 49 | font-family: var(--vscode-editor-font-family); 50 | } 51 | 52 | button { 53 | border: none; 54 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 55 | text-align: center; 56 | outline: 1px solid transparent; 57 | color: var(--vscode-button-foreground); 58 | background: var(--vscode-button-background); 59 | } 60 | 61 | button:hover { 62 | background: var(--vscode-button-hoverBackground); 63 | } 64 | 65 | button:focus { 66 | outline-color: var(--vscode-foreground); 67 | } 68 | 69 | button.secondary { 70 | color: var(--vscode-button-secondaryForeground); 71 | background: var(--vscode-button-secondaryBackground); 72 | } 73 | 74 | button.secondary:hover { 75 | background: var(--vscode-button-secondaryHoverBackground); 76 | } 77 | 78 | input:not([type="checkbox"]), 79 | textarea { 80 | display: block; 81 | width: 100%; 82 | border: none; 83 | font-family: var(--vscode-font-family); 84 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 85 | color: var(--vscode-input-foreground); 86 | outline-color: var(--vscode-foreground); 87 | background-color: var(--vscode-input-background); 88 | } 89 | 90 | input::placeholder, 91 | textarea::placeholder { 92 | color: var(--vscode-input-placeholderForeground); 93 | } 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Warm Up 2 | 3 | [![Version](https://img.shields.io/visual-studio-marketplace/v/jeusto.warm-up-typing-test.svg)](https://marketplace.visualstudio.com/items?itemName=Jeusto.warm-up-typing-test) [![Downloads](https://img.shields.io/vscode-marketplace/d/jeusto.warm-up-typing-test.svg)](https://marketplace.visualstudio.com/items?itemName=Jeusto.warm-up-typing-test) [![Rating](https://img.shields.io/visual-studio-marketplace/stars/jeusto.warm-up-typing-test.svg)](https://marketplace.visualstudio.com/items?itemName=Jeusto.warm-up-typing-test) 4 | 5 | 🔥👨‍💻 A VS Code extension to practice and improve your typing speed right inside your code editor. Practice with simple words or code snippets. 6 | 7 | Choose between 12 natural languages, 12 programming languages or select anything in your editor and practice with your own code snippets. 8 | 9 | ## Install 10 | 11 | 1. Go to [VS Marketplace](https://marketplace.visualstudio.com/items?itemName=Jeusto.warm-up-typing-test) 12 | 2. Click on the "Install" button. 13 | 14 | ## Overview 15 | 16 | ![demo](https://raw.githubusercontent.com/Jeusto/warm-up-vscode/refs/heads/master/demo.gif) 17 | 18 | ### Start 19 | 20 | Open the extension panel by clicking on the keyboard icon in the status bar, by entering the `ctrl + alt + p` shortcut or by hitting `ctrl + shift + p` and executing the `warmUp.start` command. 21 | 22 | To practice with your own code snippets (or text), select anything in your editor and enter the `ctrl + alt + s` which will execute the command `warmUp.practiceWithSelection` 23 | 24 | ### Restart 25 | 26 | To restart the typing test, press `esc` or click the restart button. If you hold shift while clicking the restart button, the typing test will restart with the same words list/code snippet. 27 | 28 | ### Settings 29 | 30 | You can configure the typing test by changing the settings through commands or in the user settings editor. 31 | 32 | | Setting Name | Description | Default Value | 33 | | ---------------------------------- | -------------------------------------------------------------------------- | ---------------------- | 34 | | `warmUp.switchNaturalLanguage` | Choose a natural language to practice with. | `english` | 35 | | `warmUp.switchProgrammingLanguage` | Choose a programming language to practice with. | `javascript` | 36 | | `warmUp.changeTypingMode` | Practice a fixed amount of words, against the clock or with code snippets. | `words (fixed amount)` | 37 | | `warmUp.changeCount` | Change the amount of words or the timer (depending on the typing mode). | `15` | 38 | | `warmUp.togglePunctuation` | Enable or disable punctuation (doesn't affect \"code snippets\" mode). | `false` | 39 | | `warmUp.toggleColorBlindMode` | Enable or disable color bind mode (doesn't concern "code snippets" mode). | `false` | 40 | 41 | ## Contributing and Feedback 42 | 43 | Feel free to open issues or pull requests! Any feedback and contribution is highly appreciated. You can improve the existing code, add functionality, languages, code snippets etc. 44 | Contact me on twitter if you have any questions. 45 | 46 | ## Credits 47 | 48 | - Typings: Original website forked to make this extension. 49 | - Flaticon: Svgs used to make the extension icon. 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "warm-up-typing-test", 3 | "displayName": "Warm Up - Typing test", 4 | "publisher": "Jeusto", 5 | "description": "🔥👨‍💻 Improve your typing speed by practicing right inside your code editor. Practice with simple words or code snippets.", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/Jeusto/vscode-typing-test" 9 | }, 10 | "icon": "icon.png", 11 | "version": "1.1.0", 12 | "license": "GPL 3.0", 13 | "keywords": [ 14 | "arhun saday", 15 | "jeusto", 16 | "warm up", 17 | "warmup", 18 | "typing", 19 | "typing speed", 20 | "improve typing", 21 | "keyboard", 22 | "practice typing", 23 | "typing-test", 24 | "practice", 25 | "typing" 26 | ], 27 | "engines": { 28 | "vscode": "^1.47.0" 29 | }, 30 | "categories": [ 31 | "Other" 32 | ], 33 | "activationEvents": [ 34 | "onCommand:warmUp.start", 35 | "onWebviewPanel:warmUp", 36 | "onStartupFinished" 37 | ], 38 | "main": "./out/extension.js", 39 | "contributes": { 40 | "configuration": [ 41 | { 42 | "title": "Warm Up: Typing test", 43 | "properties": { 44 | "warmUp.switchNaturalLanguage": { 45 | "type": "string", 46 | "enum": [ 47 | "english", 48 | "italian", 49 | "german", 50 | "spanish", 51 | "chinese", 52 | "korean", 53 | "polish", 54 | "punjabi", 55 | "swedish", 56 | "french", 57 | "portuguese", 58 | "russian", 59 | "finnish", 60 | "turkish", 61 | "englishTop1000" 62 | ], 63 | "default": "english", 64 | "description": "Choose a natural language to practice with.", 65 | "scope": "window" 66 | } 67 | } 68 | }, 69 | { 70 | "title": "Warm Up: Typing test", 71 | "properties": { 72 | "warmUp.switchProgrammingLanguage": { 73 | "type": "string", 74 | "enum": [ 75 | "javascript", 76 | "python", 77 | "java", 78 | "csharp", 79 | "php", 80 | "typescript", 81 | "cpp", 82 | "c", 83 | "go", 84 | "kotlin", 85 | "ruby", 86 | "rust" 87 | ], 88 | "default": "javascript", 89 | "description": "Choose a specific programming language to practice with.", 90 | "scope": "window" 91 | } 92 | } 93 | }, 94 | { 95 | "title": "Warm Up: Typing test", 96 | "properties": { 97 | "warmUp.changeTypingMode": { 98 | "type": "string", 99 | "enum": [ 100 | "words (fixed amount)", 101 | "words (against the clock)", 102 | "code snippets" 103 | ], 104 | "default": "words (fixed amount)", 105 | "description": "Practice with a fixed amount of words, against the clock or with code snippets.", 106 | "scope": "window" 107 | } 108 | } 109 | }, 110 | { 111 | "title": "Warm Up: Typing test", 112 | "properties": { 113 | "warmUp.togglePunctuation": { 114 | "type": "string", 115 | "enum": [ 116 | "true", 117 | "false" 118 | ], 119 | "default": "false", 120 | "description": "Enable or disable punctuation (doesn't affect \"code snippets\" mode)", 121 | "scope": "window" 122 | } 123 | } 124 | }, 125 | { 126 | "title": "Warm Up: Typing test", 127 | "properties": { 128 | "warmUp.changeCount": { 129 | "type": "string", 130 | "enum": [ 131 | "15", 132 | "30", 133 | "60", 134 | "120", 135 | "240" 136 | ], 137 | "default": "15", 138 | "description": "Change the amount of words or the clock timer (doesn't concern \"code snippets\" mode).", 139 | "scope": "window" 140 | } 141 | } 142 | }, 143 | { 144 | "title": "Warm Up: Typing test", 145 | "properties": { 146 | "warmUp.toggleColorBlindMode": { 147 | "type": "string", 148 | "enum": [ 149 | "true", 150 | "false" 151 | ], 152 | "default": "false", 153 | "description": "Enable or disable color bind mode (doesn't concern \"code snippets\" mode)", 154 | "scope": "window" 155 | } 156 | } 157 | } 158 | ], 159 | "commands": [ 160 | { 161 | "command": "warmUp.start", 162 | "title": "Start typing test", 163 | "category": "Warm Up" 164 | }, 165 | { 166 | "command": "warmUp.switchNaturalLanguage", 167 | "title": "Switch natural language", 168 | "category": "Warm Up" 169 | }, 170 | { 171 | "command": "warmUp.switchProgrammingLanguage", 172 | "title": "Switch programming language", 173 | "category": "Warm Up" 174 | }, 175 | { 176 | "command": "warmUp.changeTypingMode", 177 | "title": "Change typing mode", 178 | "category": "Warm Up" 179 | }, 180 | { 181 | "command": "warmUp.changeCount", 182 | "title": "Change word/time count", 183 | "category": "Warm Up" 184 | }, 185 | { 186 | "command": "warmUp.togglePunctuation", 187 | "title": "Toggle punctuation", 188 | "category": "Warm Up" 189 | }, 190 | { 191 | "command": "warmUp.toggleColorBlindMode", 192 | "title": "Toggle color blind mode", 193 | "category": "Warm Up" 194 | }, 195 | { 196 | "command": "warmUp.practiceWithSelection", 197 | "title": "Practice with selected code snippet", 198 | "category": "Warm Up" 199 | } 200 | ], 201 | "keybindings": [ 202 | { 203 | "command": "warmUp.start", 204 | "key": "ctrl+alt+p", 205 | "mac": "cmd+alt+p", 206 | "when": "" 207 | }, 208 | { 209 | "command": "warmUp.practiceWithSelection", 210 | "key": "ctrl+alt+s", 211 | "mac": "cmd+alt+s", 212 | "when": "editorHasSelection" 213 | } 214 | ] 215 | }, 216 | "scripts": { 217 | "vscode:prepublish": "npm run compile", 218 | "compile": "tsc -p ./", 219 | "lint": "eslint . --ext .ts,.tsx", 220 | "watch": "tsc -w -p ./" 221 | }, 222 | "devDependencies": { 223 | "@types/node": "^12.12.0", 224 | "@types/vscode": "^1.47.0", 225 | "@types/vscode-webview": "^1.57.0", 226 | "@typescript-eslint/eslint-plugin": "^4.16.0", 227 | "@typescript-eslint/parser": "^4.16.0", 228 | "eslint": "^7.21.0", 229 | "typescript": "^4.3.5" 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /webview/style/_game.scss: -------------------------------------------------------------------------------- 1 | /* General */ 2 | :root { 3 | --editorBackgroundColor: var(--vscode-editor-background); 4 | --boxBackgroundColor: white; 5 | --charSize: var(--vscode-editor-font-size); 6 | } 7 | body { 8 | margin: 0; 9 | height: 100vh; 10 | min-height: 100vh; 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | justify-content: space-between; 15 | } 16 | *[onclick] { 17 | cursor: pointer; 18 | } 19 | .hidden { 20 | display: none !important; 21 | } 22 | 23 | /* Top */ 24 | #top { 25 | display: flex; 26 | flex-direction: column; 27 | justify-content: center; 28 | align-items: center; 29 | } 30 | #header { 31 | display: flex; 32 | justify-content: center; 33 | align-items: center; 34 | margin: 0; 35 | margin: 1.2rem 0 0 0; 36 | line-height: 2rem; 37 | } 38 | 39 | /* Main */ 40 | #command-center { 41 | margin: 0 0.4rem; 42 | display: flex; 43 | flex-direction: column; 44 | justify-content: center; 45 | transform: translateY(-2vh); 46 | max-height: calc(70vh - 4rem); 47 | } 48 | .bar { 49 | display: flex; 50 | justify-content: space-between; 51 | } 52 | #left-wing { 53 | color: inherit; 54 | } 55 | #word-count { 56 | color: inherit; 57 | } 58 | #word-count span { 59 | cursor: pointer; 60 | } 61 | #time-count { 62 | cursor: pointer; 63 | } 64 | #time-count span { 65 | cursor: pointer; 66 | } 67 | #language-selected { 68 | color: inherit; 69 | } 70 | #right-wing { 71 | color: inherit; 72 | } 73 | 74 | /* Words mode */ 75 | #typing-area { 76 | margin-top: 1rem; 77 | padding: 1.4rem 1rem; 78 | border-radius: 0.4rem; 79 | width: 80vw; 80 | max-width: 45rem; 81 | } 82 | #text-display { 83 | margin-bottom: 1rem; 84 | overflow: hidden; 85 | } 86 | #text-display > span { 87 | display: inline-block; 88 | margin: calc(var(--vscode-editor-font-size) * 0.3) 89 | calc(var(--vscode-editor-font-size) * 0.3); 90 | } 91 | #text-display span { 92 | font-size: var(--vscode-editor-font-size); 93 | } 94 | #input-field { 95 | width: 100%; 96 | border: none; 97 | font: inherit; 98 | padding: 0.4rem 1rem; 99 | border-radius: 0.2rem; 100 | font-size: 1.2rem; 101 | background: var(--vscode-editor-background); 102 | color: inherit; 103 | } 104 | #restart-button { 105 | margin-left: 0.7rem; 106 | border: none; 107 | font: inherit; 108 | padding: 0.4rem 1rem; 109 | border-radius: 0.2rem; 110 | font-size: 1.2rem; 111 | cursor: pointer; 112 | background: var(--vscode-editor-background); 113 | color: inherit; 114 | } 115 | 116 | /* Code snippet mode */ 117 | #coding-area { 118 | margin-top: 1rem; 119 | padding: 1rem 1rem; 120 | border-radius: 0.4rem; 121 | outline: none; 122 | width: 75vw; 123 | max-width: 55rem; 124 | max-height: 60vh; 125 | overflow: scroll; 126 | min-height: 4rem; 127 | position: relative; 128 | } 129 | #coding-area:after { 130 | content: ""; 131 | width: 98%; 132 | height: 102%; 133 | position: fixed; 134 | left: 0; 135 | top: -0.9rem; 136 | background: linear-gradient(transparent 88%, var(--boxBackgroundColor) 100%); 137 | } 138 | #code-display { 139 | overflow: hidden; 140 | } 141 | .code { 142 | position: relative; 143 | } 144 | #code-pre { 145 | border: none; 146 | outline: none; 147 | margin: 0; 148 | padding: 0; 149 | overflow: visible; 150 | } 151 | #code-code { 152 | display: inline; 153 | width: fit-content; 154 | font-family: var(--vscode-editor-font-family); 155 | border: none; 156 | outline: none; 157 | margin: 0; 158 | padding: 0; 159 | } 160 | .char.topass { 161 | opacity: 0.57; 162 | } 163 | .charDiv { 164 | display: inline; 165 | overflow: hidden; 166 | } 167 | .char { 168 | color: inherit; 169 | } 170 | #cursor { 171 | display: inline-block; 172 | position: absolute; 173 | height: calc(var(--vscode-editor-font-size) * 1.25); 174 | width: calc(var(--vscode-editor-font-size) * 0.15); 175 | opacity: 1; 176 | top: 0; 177 | animation: cursor 0.85s infinite ease-in; 178 | } 179 | @keyframes cursor { 180 | 0% { 181 | opacity: 0.6; 182 | } 183 | 100% { 184 | opacity: 0; 185 | } 186 | } 187 | .codeButton { 188 | position: fixed; 189 | right: 1.5rem; 190 | bottom: 1.5rem; 191 | z-index: 999; 192 | opacity: 70%; 193 | } 194 | .codeButton:hover { 195 | opacity: 100%; 196 | } 197 | /* For Javascript access */ 198 | #charDimensions { 199 | height: (--vscode-editor-font-size); 200 | width: (--vscode-editor-font-size); 201 | } 202 | #editorBackgroundColor { 203 | background: var(--vscode-editor-background); 204 | } 205 | 206 | /* Prism.js */ 207 | code[class*="language-"], 208 | pre[class*="language-"] { 209 | background: none; 210 | text-align: left; 211 | white-space: pre; 212 | word-spacing: normal; 213 | word-break: normal; 214 | word-wrap: normal; 215 | -moz-tab-size: 4; 216 | -o-tab-size: 4; 217 | tab-size: 4; 218 | -webkit-hyphens: none; 219 | -moz-hyphens: none; 220 | -ms-hyphens: none; 221 | hyphens: none; 222 | } 223 | @media print { 224 | code[class*="language-"], 225 | pre[class*="language-"] { 226 | text-shadow: none; 227 | } 228 | } 229 | pre[class*="language-"] { 230 | padding: 1em; 231 | margin: 0.5em 0; 232 | overflow: auto; 233 | } 234 | :not(pre) > code[class*="language-"] { 235 | padding: 0.1em; 236 | border-radius: 0.3em; 237 | white-space: normal; 238 | } 239 | 240 | .token.important, 241 | .token.bold { 242 | font-weight: bold; 243 | } 244 | .token.italic { 245 | font-style: italic; 246 | } 247 | .token.entity { 248 | cursor: help; 249 | } 250 | pre.line-numbers { 251 | position: relative; 252 | padding-left: 3.8em; 253 | counter-reset: linenumber; 254 | } 255 | pre.line-numbers > code { 256 | position: relative; 257 | } 258 | .line-numbers .line-numbers-rows { 259 | position: absolute; 260 | pointer-events: none; 261 | top: 0; 262 | font-size: 100%; 263 | left: -3.8em; 264 | width: 3em; 265 | letter-spacing: -1px; 266 | border-right: 0; 267 | -webkit-user-select: none; 268 | -moz-user-select: none; 269 | -ms-user-select: none; 270 | user-select: none; 271 | } 272 | .line-numbers-rows > span { 273 | pointer-events: none; 274 | display: block; 275 | counter-increment: linenumber; 276 | } 277 | .line-numbers-rows > span:before { 278 | content: counter(linenumber); 279 | display: block; 280 | padding-right: 0.8em; 281 | text-align: right; 282 | } 283 | -------------------------------------------------------------------------------- /src/modules/commands.ts: -------------------------------------------------------------------------------- 1 | import { ConfigurationTarget, window, commands, workspace } from "vscode"; 2 | 3 | export default function registerCommands(WarmupWebview, context, words, codes) { 4 | // Register start commandd 5 | context.subscriptions.push( 6 | commands.registerCommand("warmUp.start", () => { 7 | // Create or show WarmupWebview 8 | WarmupWebview.createOrShow(context.extensionUri); 9 | 10 | // Send all user settings to WarmupWebview to start 11 | if (WarmupWebview.currentPanel) { 12 | WarmupWebview.currentPanel.sendStartAndConfig(words, codes); 13 | } 14 | }) 15 | ); 16 | 17 | // Register practiceWithSelection command 18 | context.subscriptions.push( 19 | commands.registerCommand("warmUp.practiceWithSelection", () => { 20 | // Return if no editor open 21 | const editor = window.activeTextEditor; 22 | if (!editor) { 23 | return; 24 | } 25 | 26 | // Get selection 27 | const selections = editor.selections; 28 | let selectedCode = editor.document.getText(selections[0]); 29 | 30 | // Return if no selection 31 | if (selectedCode.length == 0) { 32 | return; 33 | } 34 | 35 | // Limit selection size 36 | selectedCode = selectedCode.substring(0, 3000); 37 | 38 | // Get editor file language 39 | let selectedCodeLanguage = window.activeTextEditor?.document.languageId; 40 | 41 | // Create or show WarmupWebview 42 | WarmupWebview.createOrShow(context.extensionUri); 43 | 44 | // Send all user settings to WarmupWebview to start with a selection 45 | if (WarmupWebview.currentPanel) { 46 | WarmupWebview.currentPanel.sendStartWithSelectionAndConfig( 47 | selectedCode, 48 | selectedCodeLanguage, 49 | words, 50 | codes 51 | ); 52 | } 53 | }) 54 | ); 55 | 56 | // Register switchNaturalLanguage command 57 | context.subscriptions.push( 58 | commands.registerCommand( 59 | "warmUp.switchNaturalLanguage", 60 | async function showQuickPick() { 61 | const userChoice = await window.showQuickPick( 62 | [ 63 | "english", 64 | "italian", 65 | "german", 66 | "spanish", 67 | "chinese", 68 | "korean", 69 | "polish", 70 | "swedish", 71 | "french", 72 | "portuguese", 73 | "russian", 74 | "finnish", 75 | "turkish", 76 | "englishTop1000", 77 | ], 78 | { 79 | placeHolder: "Choose a natural language to practice with.", 80 | } 81 | ); 82 | 83 | // Update the configuration value with user choice 84 | await workspace 85 | .getConfiguration() 86 | .update( 87 | "warmUp.switchNaturalLanguage", 88 | userChoice, 89 | ConfigurationTarget.Global 90 | ); 91 | 92 | // Send configuration change to WarmupWebview if it exists 93 | if (WarmupWebview.currentPanel) { 94 | WarmupWebview.currentPanel.sendConfigMessage( 95 | "switchNaturalLanguage", 96 | userChoice 97 | ); 98 | } 99 | } 100 | ) 101 | ); 102 | 103 | // Register switchProgrammingLanguage command 104 | context.subscriptions.push( 105 | commands.registerCommand( 106 | "warmUp.switchProgrammingLanguage", 107 | async function showQuickPick() { 108 | const userChoice = await window.showQuickPick( 109 | [ 110 | "javascript", 111 | "python", 112 | "java", 113 | "csharp", 114 | "php", 115 | "typescript", 116 | "cpp", 117 | "c", 118 | "go", 119 | "kotlin", 120 | "ruby", 121 | "rust", 122 | ], 123 | { 124 | placeHolder: "Choose a programming language to practice with.", 125 | } 126 | ); 127 | 128 | // Update the configuration value with user choice 129 | await workspace 130 | .getConfiguration() 131 | .update( 132 | "warmUp.switchProgrammingLanguage", 133 | userChoice, 134 | ConfigurationTarget.Global 135 | ); 136 | 137 | // Send configuration change to WarmupWebview if it exists 138 | if (WarmupWebview.currentPanel) { 139 | WarmupWebview.currentPanel.sendConfigMessage( 140 | "switchProgrammingLanguage", 141 | userChoice 142 | ); 143 | } 144 | } 145 | ) 146 | ); 147 | 148 | // Register changeTypingMode command 149 | context.subscriptions.push( 150 | commands.registerCommand( 151 | "warmUp.changeTypingMode", 152 | async function showQuickPick() { 153 | // Get user choice 154 | let userChoice = await window.showQuickPick( 155 | [ 156 | "$(book) words (fixed amount)", 157 | "$(watch) words (against the clock)", 158 | "$(code) code snippets", 159 | ], 160 | { 161 | placeHolder: 162 | "Practice a fixed amount of words, against the clock or with code snippets.", 163 | } 164 | ); 165 | if (userChoice === "$(book) words (fixed amount)") { 166 | userChoice = "words (fixed amount)"; 167 | } else if (userChoice === "$(watch) words (against the clock)") { 168 | userChoice = "words (against the clock)"; 169 | } else if (userChoice === "$(code) code snippets") { 170 | userChoice = "code snippets"; 171 | } 172 | 173 | // Update the configuration value with user choice 174 | await workspace 175 | .getConfiguration() 176 | .update( 177 | "warmUp.changeTypingMode", 178 | userChoice, 179 | ConfigurationTarget.Global 180 | ); 181 | 182 | // Send configuration change to WarmupWebview if it exists 183 | if (WarmupWebview.currentPanel) { 184 | WarmupWebview.currentPanel.sendConfigMessage( 185 | "changeTypingMode", 186 | userChoice 187 | ); 188 | } 189 | } 190 | ) 191 | ); 192 | 193 | // Register togglePunctuation command 194 | context.subscriptions.push( 195 | commands.registerCommand( 196 | "warmUp.togglePunctuation", 197 | async function showQuickPick() { 198 | // Get user choice 199 | let userChoice = await window.showQuickPick( 200 | ["$(circle-slash) false", "$(check) true"], 201 | { 202 | placeHolder: 203 | 'Enable or disable punctuation (doesn\'t affect "code snippets" mode).', 204 | } 205 | ); 206 | 207 | if (userChoice === "$(circle-slash) false") { 208 | userChoice = "false"; 209 | } else if (userChoice === "$(check) true") { 210 | userChoice = "true"; 211 | } 212 | 213 | // Update the configuration value with user choice 214 | await workspace 215 | .getConfiguration() 216 | .update( 217 | "warmUp.togglePunctuation", 218 | userChoice, 219 | ConfigurationTarget.Global 220 | ); 221 | 222 | // Send configuration change to WarmupWebview if it exists 223 | if (WarmupWebview.currentPanel) { 224 | WarmupWebview.currentPanel.sendConfigMessage( 225 | "togglePunctuation", 226 | userChoice 227 | ); 228 | } 229 | } 230 | ) 231 | ); 232 | 233 | // Register changeCount command 234 | context.subscriptions.push( 235 | commands.registerCommand( 236 | "warmUp.changeCount", 237 | async function showQuickPick() { 238 | // Get user choice 239 | const userChoice = await window.showQuickPick( 240 | ["15", "30", "60", "120", "240"], 241 | { 242 | placeHolder: 243 | 'Change the amount of words or the timer (doesn\'t affect "code snippets" mode).', 244 | } 245 | ); 246 | 247 | // Update the configuration value with user choice 248 | await workspace 249 | .getConfiguration() 250 | .update("warmUp.changeCount", userChoice, ConfigurationTarget.Global); 251 | 252 | // Send configuration change to WarmupWebview if it exists 253 | if (WarmupWebview.currentPanel) { 254 | WarmupWebview.currentPanel.sendConfigMessage( 255 | "changeCount", 256 | userChoice 257 | ); 258 | } 259 | } 260 | ) 261 | ); 262 | 263 | // Register toggleColorBlind command 264 | context.subscriptions.push( 265 | commands.registerCommand( 266 | "warmUp.toggleColorBlindMode", 267 | async function showQuickPick() { 268 | // Get user choice 269 | let userChoice = await window.showQuickPick( 270 | ["$(circle-slash) false", "$(check) true"], 271 | { 272 | placeHolder: 273 | 'Enable or disable color blind mode (doesn\'t affect "code snippets" mode).', 274 | } 275 | ); 276 | 277 | if (userChoice === "$(circle-slash) false") { 278 | userChoice = "false"; 279 | } else if (userChoice === "$(check) true") { 280 | userChoice = "true"; 281 | } 282 | 283 | // Update the configuration value with user choice 284 | await workspace 285 | .getConfiguration() 286 | .update( 287 | "warmUp.toggleColorBlindMode", 288 | userChoice, 289 | ConfigurationTarget.Global 290 | ); 291 | 292 | // Send configuration change to WarmupWebview if it exists 293 | if (WarmupWebview.currentPanel) { 294 | WarmupWebview.currentPanel.sendConfigMessage( 295 | "toggleColorBlindMode", 296 | userChoice 297 | ); 298 | } 299 | } 300 | ) 301 | ); 302 | } 303 | -------------------------------------------------------------------------------- /src/modules/panel.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Uri, 3 | Webview, 4 | Disposable, 5 | ViewColumn, 6 | WebviewPanel, 7 | ConfigurationTarget, 8 | window, 9 | workspace, 10 | } from "vscode"; 11 | 12 | // Manages webview panel 13 | export default class WarmupWebview { 14 | // Track the currently panel and only allow a single panel to exist at a time 15 | public static currentPanel: WarmupWebview | undefined; 16 | public static readonly viewType = "warmUp"; 17 | private readonly _panel: WebviewPanel; 18 | private readonly _extensionUri: Uri; 19 | private _disposables: Disposable[] = []; 20 | 21 | // Constructor function 22 | private constructor(panel: WebviewPanel, extensionUri: Uri) { 23 | this._panel = panel; 24 | this._extensionUri = extensionUri; 25 | 26 | // Set the webview's initial html content 27 | this.update(); 28 | 29 | // Listen for when the panel is disposed 30 | // This happens when the user closes the panel or when the panel is closed programmatically 31 | this._panel.onDidDispose(() => this.dispose(), null, this._disposables); 32 | 33 | // Handle messages from the webview 34 | this._panel.webview.onDidReceiveMessage( 35 | async (message) => { 36 | switch (message.command) { 37 | case "changeCount": 38 | // Update count value with user choice 39 | await workspace 40 | .getConfiguration() 41 | .update( 42 | "warmUp.changeCount", 43 | message.count.toString(), 44 | ConfigurationTarget.Global 45 | ); 46 | break; 47 | } 48 | }, 49 | null, 50 | this._disposables 51 | ); 52 | } 53 | 54 | // Function to create or show existing webview panel 55 | public static createOrShow(extensionUri: Uri) { 56 | const column = window.activeTextEditor 57 | ? window.activeTextEditor.viewColumn 58 | : undefined; 59 | 60 | // If we already have a panel, show it. 61 | if (WarmupWebview.currentPanel) { 62 | WarmupWebview.currentPanel._panel.reveal(column); 63 | return; 64 | } 65 | 66 | // Otherwise, create a new panel. 67 | const panel = window.createWebviewPanel( 68 | WarmupWebview.viewType, 69 | "Warm Up", 70 | column || ViewColumn.One, 71 | { 72 | // Enable javascript in the webview 73 | enableScripts: true, 74 | retainContextWhenHidden: true, 75 | 76 | // And restrict the webview to only loading content from our extension's "webview" directory. 77 | localResourceRoots: [Uri.joinPath(extensionUri, "webview")], 78 | } 79 | ); 80 | 81 | WarmupWebview.currentPanel = new WarmupWebview(panel, extensionUri); 82 | } 83 | 84 | // Function to restore webview panel when VSCode is closed and opened back 85 | public static revive(panel: WebviewPanel, extensionUri: Uri) { 86 | WarmupWebview.currentPanel = new WarmupWebview(panel, extensionUri); 87 | } 88 | 89 | // Function send all config and start webview 90 | public sendStartAndConfig( 91 | words: Record, 92 | codes: Record 93 | ) { 94 | this._panel.webview.postMessage({ 95 | type: "allConfig", 96 | words: words, 97 | codes: codes, 98 | language: workspace 99 | .getConfiguration() 100 | .get("warmUp.switchNaturalLanguage"), 101 | codeLanguage: workspace 102 | .getConfiguration() 103 | .get("warmUp.switchProgrammingLanguage"), 104 | mode: workspace.getConfiguration().get("warmUp.changeTypingMode"), 105 | count: workspace.getConfiguration().get("warmUp.changeCount"), 106 | punctuation: workspace.getConfiguration().get("warmUp.togglePunctuation"), 107 | colorBlindMode: workspace 108 | .getConfiguration() 109 | .get("warmUp.toggleColorBlindMode"), 110 | }); 111 | } 112 | 113 | // Function to send all config and start webview with selected code 114 | public sendStartWithSelectionAndConfig( 115 | selectedCode: string, 116 | selectedCodeLanguage: string, 117 | words: string[], 118 | codes: string[] 119 | ) { 120 | this._panel.webview.postMessage({ 121 | type: "practiceWithSelection", 122 | selectedCode, 123 | selectedCodeLanguage, 124 | words: words, 125 | codes: codes, 126 | language: workspace 127 | .getConfiguration() 128 | .get("warmUp.switchNaturalLanguage"), 129 | codeLanguage: workspace 130 | .getConfiguration() 131 | .get("warmUp.switchProgrammingLanguage"), 132 | mode: "code snippets", 133 | count: workspace.getConfiguration().get("warmUp.changeCount"), 134 | punctuation: workspace.getConfiguration().get("warmUp.togglePunctuation"), 135 | colorBlindMode: workspace 136 | .getConfiguration() 137 | .get("warmUp.toggleColorBlindMode"), 138 | }); 139 | } 140 | 141 | // Function to send a single config to webview 142 | public sendConfigMessage(config: string, value: any) { 143 | this._panel.webview.postMessage({ 144 | type: "singleConfig", 145 | config: config, 146 | value: value, 147 | }); 148 | } 149 | 150 | // Function to dispose of the webview panel 151 | public dispose() { 152 | WarmupWebview.currentPanel = undefined; 153 | 154 | // Clean up our resources 155 | this._panel.dispose(); 156 | 157 | while (this._disposables.length) { 158 | const x = this._disposables.pop(); 159 | if (x) { 160 | x.dispose(); 161 | } 162 | } 163 | } 164 | 165 | // Function to update the webview's html content and title 166 | private update() { 167 | const webview = this._panel.webview; 168 | this._panel.webview.html = this.getHtmlForWebview(webview); 169 | this._panel.title = "Warm Up"; 170 | this._panel.iconPath = Uri.joinPath( 171 | this._extensionUri, 172 | "webview", 173 | "icon.svg" 174 | ); 175 | } 176 | 177 | // Function that returns the html for the webview 178 | private getHtmlForWebview(webview: Webview) { 179 | // Uri we use to load this script in the webview 180 | const prismScriptUri = webview.asWebviewUri( 181 | Uri.joinPath(this._extensionUri, "webview", "modules/prism.min.js") 182 | ); 183 | const tinyColorScriptUri = webview.asWebviewUri( 184 | Uri.joinPath(this._extensionUri, "webview", "modules/tinycolor.min.js") 185 | ); 186 | const scriptUri = webview.asWebviewUri( 187 | Uri.joinPath(this._extensionUri, "webview", "main.js") 188 | ); 189 | 190 | // Uri to load styles into webview 191 | const styleUri = webview.asWebviewUri( 192 | Uri.joinPath(this._extensionUri, "webview", "style/style.css") 193 | ); 194 | 195 | // Use a nonce to only allow specific scripts to be run 196 | const nonce = getNonce(); 197 | 198 | return ` 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | Warm Up 207 | 208 | 209 |
210 | 213 |

Hit "cmd/ctrl+shift+p" and enter "warmup" to see available commands

214 |
215 | 216 |
217 |
218 |
219 | 230 | 241 | 242 |
243 |
WPM: XX / ACC: XX
244 |
245 | 252 | 261 |
262 | 263 |
264 | 270 | 271 | 272 | 273 | 274 | `; 275 | } 276 | } 277 | 278 | // Function to generate nonce 279 | function getNonce() { 280 | let text = ""; 281 | const possible = 282 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 283 | for (let i = 0; i < 32; i++) { 284 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 285 | } 286 | return text; 287 | } 288 | -------------------------------------------------------------------------------- /webview/style/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --container-paddding: 20px; 3 | --input-padding-vertical: 6px; 4 | --input-padding-horizontal: 4px; 5 | --input-margin-vertical: 4px; 6 | --input-margin-horizontal: 0; 7 | } 8 | body { 9 | padding: 0 var(--container-paddding); 10 | color: var(--vscode-editor-foreground); 11 | font-size: var(--vscode-font-size); 12 | font-weight: var(--vscode-font-weight); 13 | background-color: var(--vscode-editor-background); 14 | font-family: var(--vscode-editor-font-family); 15 | } 16 | svg { 17 | width: calc(var(--vscode-font-size) * 1.75); 18 | stroke: var(--vscode-foreground); 19 | } 20 | ol, 21 | ul { 22 | padding-left: var(--container-paddding); 23 | } 24 | body > *, 25 | form > * { 26 | -webkit-margin-before: var(--input-margin-vertical); 27 | margin-block-start: var(--input-margin-vertical); 28 | -webkit-margin-after: var(--input-margin-vertical); 29 | margin-block-end: var(--input-margin-vertical); 30 | } 31 | *:focus { 32 | outline-color: var(--vscode-foreground) !important; 33 | } 34 | a { 35 | color: var(--vscode-textLink-foreground); 36 | } 37 | a:hover, 38 | a:active { 39 | color: var(--vscode-textLink-activeForeground); 40 | } 41 | code { 42 | font-size: var(--vscode-editor-font-size); 43 | font-family: var(--vscode-editor-font-family); 44 | } 45 | button { 46 | border: none; 47 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 48 | text-align: center; 49 | outline: 1px solid transparent; 50 | color: var(--vscode-button-foreground); 51 | background: var(--vscode-button-background); 52 | } 53 | button:hover { 54 | background: var(--vscode-button-hoverBackground); 55 | } 56 | button:focus { 57 | outline-color: var(--vscode-foreground); 58 | } 59 | button.secondary { 60 | color: var(--vscode-button-secondaryForeground); 61 | background: var(--vscode-button-secondaryBackground); 62 | } 63 | button.secondary:hover { 64 | background: var(--vscode-button-secondaryHoverBackground); 65 | } 66 | input:not([type="checkbox"]), 67 | textarea { 68 | display: block; 69 | width: 100%; 70 | border: none; 71 | font-family: var(--vscode-font-family); 72 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 73 | color: var(--vscode-input-foreground); 74 | outline-color: var(--vscode-foreground); 75 | background-color: var(--vscode-input-background); 76 | } 77 | input::-webkit-input-placeholder, 78 | textarea::-webkit-input-placeholder { 79 | color: var(--vscode-input-placeholderForeground); 80 | } 81 | input:-ms-input-placeholder, 82 | textarea:-ms-input-placeholder { 83 | color: var(--vscode-input-placeholderForeground); 84 | } 85 | input::-ms-input-placeholder, 86 | textarea::-ms-input-placeholder { 87 | color: var(--vscode-input-placeholderForeground); 88 | } 89 | input::placeholder, 90 | textarea::placeholder { 91 | color: var(--vscode-input-placeholderForeground); 92 | } 93 | :root { 94 | --editorBackgroundColor: var(--vscode-editor-background); 95 | --boxBackgroundColor: white; 96 | --charSize: var(--vscode-editor-font-size); 97 | } 98 | body { 99 | margin: 0; 100 | height: 100vh; 101 | min-height: 100vh; 102 | display: -webkit-box; 103 | display: -ms-flexbox; 104 | display: flex; 105 | -webkit-box-orient: vertical; 106 | -webkit-box-direction: normal; 107 | -ms-flex-direction: column; 108 | flex-direction: column; 109 | -webkit-box-align: center; 110 | -ms-flex-align: center; 111 | align-items: center; 112 | -webkit-box-pack: justify; 113 | -ms-flex-pack: justify; 114 | justify-content: space-between; 115 | } 116 | *[onclick] { 117 | cursor: pointer; 118 | } 119 | .hidden { 120 | display: none !important; 121 | } 122 | #top { 123 | display: -webkit-box; 124 | display: -ms-flexbox; 125 | display: flex; 126 | -webkit-box-orient: vertical; 127 | -webkit-box-direction: normal; 128 | -ms-flex-direction: column; 129 | flex-direction: column; 130 | -webkit-box-pack: center; 131 | -ms-flex-pack: center; 132 | justify-content: center; 133 | -webkit-box-align: center; 134 | -ms-flex-align: center; 135 | align-items: center; 136 | } 137 | #header { 138 | display: -webkit-box; 139 | display: -ms-flexbox; 140 | display: flex; 141 | -webkit-box-pack: center; 142 | -ms-flex-pack: center; 143 | justify-content: center; 144 | -webkit-box-align: center; 145 | -ms-flex-align: center; 146 | align-items: center; 147 | margin: 0; 148 | margin: 1.2rem 0 0 0; 149 | line-height: 2rem; 150 | } 151 | #command-center { 152 | margin: 0 0.4rem; 153 | display: -webkit-box; 154 | display: -ms-flexbox; 155 | display: flex; 156 | -webkit-box-orient: vertical; 157 | -webkit-box-direction: normal; 158 | -ms-flex-direction: column; 159 | flex-direction: column; 160 | -webkit-box-pack: center; 161 | -ms-flex-pack: center; 162 | justify-content: center; 163 | -webkit-transform: translateY(-2vh); 164 | transform: translateY(-2vh); 165 | max-height: calc(70vh - 4rem); 166 | } 167 | .bar { 168 | display: -webkit-box; 169 | display: -ms-flexbox; 170 | display: flex; 171 | -webkit-box-pack: justify; 172 | -ms-flex-pack: justify; 173 | justify-content: space-between; 174 | } 175 | #left-wing { 176 | color: inherit; 177 | } 178 | #word-count { 179 | color: inherit; 180 | } 181 | #word-count span { 182 | cursor: pointer; 183 | } 184 | #time-count { 185 | cursor: pointer; 186 | } 187 | #time-count span { 188 | cursor: pointer; 189 | } 190 | #language-selected { 191 | color: inherit; 192 | } 193 | #right-wing { 194 | color: inherit; 195 | } 196 | #typing-area { 197 | margin-top: 1rem; 198 | padding: 1.4rem 1rem; 199 | border-radius: 0.4rem; 200 | width: 80vw; 201 | max-width: 45rem; 202 | } 203 | #text-display { 204 | margin-bottom: 1rem; 205 | overflow: hidden; 206 | } 207 | #text-display > span { 208 | display: inline-block; 209 | margin: calc(var(--vscode-editor-font-size) * 0.3) 210 | calc(var(--vscode-editor-font-size) * 0.3); 211 | } 212 | #text-display span { 213 | font-size: var(--vscode-editor-font-size); 214 | } 215 | #input-field { 216 | width: 100%; 217 | border: none; 218 | font: inherit; 219 | padding: 0.4rem 1rem; 220 | border-radius: 0.2rem; 221 | font-size: 1.2rem; 222 | background: var(--vscode-editor-background); 223 | color: inherit; 224 | } 225 | #restart-button { 226 | margin-left: 0.7rem; 227 | border: none; 228 | font: inherit; 229 | padding: 0.4rem 1rem; 230 | border-radius: 0.2rem; 231 | font-size: 1.2rem; 232 | cursor: pointer; 233 | background: var(--vscode-editor-background); 234 | color: inherit; 235 | } 236 | #coding-area { 237 | margin-top: 1rem; 238 | padding: 1rem 1rem; 239 | border-radius: 0.4rem; 240 | outline: none; 241 | width: 75vw; 242 | max-width: 55rem; 243 | max-height: 60vh; 244 | overflow: scroll; 245 | min-height: 4rem; 246 | position: relative; 247 | } 248 | #coding-area:after { 249 | content: ""; 250 | width: 98%; 251 | height: 102%; 252 | position: fixed; 253 | left: 0; 254 | top: -0.9rem; 255 | background: -webkit-gradient( 256 | linear, 257 | left top, 258 | left bottom, 259 | color-stop(88%, transparent), 260 | to(var(--boxBackgroundColor)) 261 | ); 262 | background: linear-gradient(transparent 88%, var(--boxBackgroundColor) 100%); 263 | } 264 | #code-display { 265 | overflow: hidden; 266 | } 267 | .code { 268 | position: relative; 269 | } 270 | #code-pre { 271 | border: none; 272 | outline: none; 273 | margin: 0; 274 | padding: 0; 275 | overflow: visible; 276 | } 277 | #code-code { 278 | display: inline; 279 | width: -webkit-fit-content; 280 | width: -moz-fit-content; 281 | width: fit-content; 282 | font-family: var(--vscode-editor-font-family); 283 | border: none; 284 | outline: none; 285 | margin: 0; 286 | padding: 0; 287 | } 288 | .char.topass { 289 | opacity: 0.57; 290 | } 291 | .charDiv { 292 | display: inline; 293 | overflow: hidden; 294 | } 295 | .char { 296 | color: inherit; 297 | } 298 | #cursor { 299 | display: inline-block; 300 | position: absolute; 301 | height: calc(var(--vscode-editor-font-size) * 1.25); 302 | width: calc(var(--vscode-editor-font-size) * 0.15); 303 | opacity: 1; 304 | top: 0; 305 | -webkit-animation: cursor 0.85s infinite ease-in; 306 | animation: cursor 0.85s infinite ease-in; 307 | } 308 | @-webkit-keyframes cursor { 309 | 0% { 310 | opacity: 0.6; 311 | } 312 | 100% { 313 | opacity: 0; 314 | } 315 | } 316 | @keyframes cursor { 317 | 0% { 318 | opacity: 0.6; 319 | } 320 | 100% { 321 | opacity: 0; 322 | } 323 | } 324 | .codeButton { 325 | position: fixed; 326 | right: 1.5rem; 327 | bottom: 1.5rem; 328 | z-index: 999; 329 | opacity: 70%; 330 | } 331 | .codeButton:hover { 332 | opacity: 100%; 333 | } 334 | #charDimensions { 335 | height: --vscode-editor-font-size; 336 | width: --vscode-editor-font-size; 337 | } 338 | #editorBackgroundColor { 339 | background: var(--vscode-editor-background); 340 | } 341 | code[class*="language-"], 342 | pre[class*="language-"] { 343 | background: none; 344 | text-align: left; 345 | white-space: pre; 346 | word-spacing: normal; 347 | word-break: normal; 348 | word-wrap: normal; 349 | -moz-tab-size: 4; 350 | -o-tab-size: 4; 351 | tab-size: 4; 352 | -webkit-hyphens: none; 353 | -ms-hyphens: none; 354 | hyphens: none; 355 | } 356 | @media print { 357 | code[class*="language-"], 358 | pre[class*="language-"] { 359 | text-shadow: none; 360 | } 361 | } 362 | pre[class*="language-"] { 363 | padding: 1em; 364 | margin: 0.5em 0; 365 | overflow: auto; 366 | } 367 | :not(pre) > code[class*="language-"] { 368 | padding: 0.1em; 369 | border-radius: 0.3em; 370 | white-space: normal; 371 | } 372 | .token.important, 373 | .token.bold { 374 | font-weight: bold; 375 | } 376 | .token.italic { 377 | font-style: italic; 378 | } 379 | .token.entity { 380 | cursor: help; 381 | } 382 | pre.line-numbers { 383 | position: relative; 384 | padding-left: 3.8em; 385 | counter-reset: linenumber; 386 | } 387 | pre.line-numbers > code { 388 | position: relative; 389 | } 390 | .line-numbers .line-numbers-rows { 391 | position: absolute; 392 | pointer-events: none; 393 | top: 0; 394 | font-size: 100%; 395 | left: -3.8em; 396 | width: 3em; 397 | letter-spacing: -1px; 398 | border-right: 0; 399 | -webkit-user-select: none; 400 | -moz-user-select: none; 401 | -ms-user-select: none; 402 | user-select: none; 403 | } 404 | .line-numbers-rows > span { 405 | pointer-events: none; 406 | display: block; 407 | counter-increment: linenumber; 408 | } 409 | .line-numbers-rows > span:before { 410 | content: counter(linenumber); 411 | display: block; 412 | padding-right: 0.8em; 413 | text-align: right; 414 | } 415 | #text-display, 416 | #code-display, 417 | #typing-area, 418 | #coding-area { 419 | background: var(--boxBackgroundColor); 420 | } 421 | ::-webkit-scrollbar-corner { 422 | background-color: var(--boxBackgroundColor); 423 | } 424 | #input-field.wrong { 425 | color: #e05561; 426 | } 427 | .highlight { 428 | color: #f0a45d; 429 | } 430 | .correct { 431 | color: #8cc265; 432 | } 433 | .wrong { 434 | color: #e05561; 435 | } 436 | .char.notpassed { 437 | background: #e0556170; 438 | } 439 | #cursor { 440 | background: #5dbeff; 441 | } 442 | code[class*="language-"], 443 | pre[class*="language-"] { 444 | color: #c1c9d4; 445 | } 446 | .token.comment, 447 | .token.prolog, 448 | .token.doctype, 449 | .token.cdata { 450 | color: #909aad; 451 | } 452 | .token.punctuation { 453 | color: #c1c9d4; 454 | } 455 | .vscode-light code[class*="language-"], 456 | .vscode-light pre[class*="language-"] { 457 | color: #454d5e; 458 | } 459 | .vscode-light .token.comment, 460 | .vscode-light .token.prolog, 461 | .vscode-light .token.doctype, 462 | .vscode-light .token.cdata { 463 | color: #99a5b6; 464 | } 465 | .vscode-light .token.punctuation { 466 | color: #454d5e; 467 | } 468 | .token.selector, 469 | .token.tag { 470 | color: #e06c75; 471 | } 472 | .token.property, 473 | .token.boolean, 474 | .token.number, 475 | .token.constant, 476 | .token.symbol, 477 | .token.attr-name, 478 | .token.deleted { 479 | color: #d19a66; 480 | } 481 | .token.string, 482 | .token.char, 483 | .token.attr-value, 484 | .token.builtin, 485 | .token.inserted { 486 | color: #98c379; 487 | } 488 | .token.operator, 489 | .token.entity, 490 | .token.url, 491 | .language-css .token.string, 492 | .style .token.string { 493 | color: #56b6c2; 494 | } 495 | .token.atrule, 496 | .token.keyword { 497 | color: #c678dd; 498 | } 499 | .token.function { 500 | color: #61afef; 501 | } 502 | .token.regex, 503 | .token.important, 504 | .token.variable { 505 | color: #c678dd; 506 | } 507 | body.colorblind #text-display { 508 | color: #b4bdc7; 509 | } 510 | body.colorblind #input-field.wrong { 511 | color: #b2a048; 512 | } 513 | body.colorblind .highlight { 514 | color: #ffffff; 515 | } 516 | body.colorblind .correct { 517 | color: #6a91dc; 518 | } 519 | body.colorblind .wrong { 520 | color: #b2a048; 521 | } 522 | /*# sourceMappingURL=style.css.map */ 523 | -------------------------------------------------------------------------------- /webview/modules/tinycolor.min.js: -------------------------------------------------------------------------------- 1 | !(function (t) { 2 | var e = /^\s+/, 3 | r = /\s+$/, 4 | n = 0, 5 | a = t.round, 6 | i = t.min, 7 | s = t.max, 8 | o = t.random; 9 | function f(o, h) { 10 | if (((h = h || {}), (o = o || "") instanceof f)) return o; 11 | if (!(this instanceof f)) return new f(o, h); 12 | var u = (function (n) { 13 | var a = { r: 0, g: 0, b: 0 }, 14 | o = 1, 15 | f = null, 16 | h = null, 17 | u = null, 18 | l = !1, 19 | c = !1; 20 | "string" == typeof n && 21 | (n = (function (t) { 22 | t = t.replace(e, "").replace(r, "").toLowerCase(); 23 | var n, 24 | a = !1; 25 | if (H[t]) (t = H[t]), (a = !0); 26 | else if ("transparent" == t) 27 | return { r: 0, g: 0, b: 0, a: 0, format: "name" }; 28 | if ((n = O.rgb.exec(t))) return { r: n[1], g: n[2], b: n[3] }; 29 | if ((n = O.rgba.exec(t))) 30 | return { r: n[1], g: n[2], b: n[3], a: n[4] }; 31 | if ((n = O.hsl.exec(t))) return { h: n[1], s: n[2], l: n[3] }; 32 | if ((n = O.hsla.exec(t))) 33 | return { h: n[1], s: n[2], l: n[3], a: n[4] }; 34 | if ((n = O.hsv.exec(t))) return { h: n[1], s: n[2], v: n[3] }; 35 | if ((n = O.hsva.exec(t))) 36 | return { h: n[1], s: n[2], v: n[3], a: n[4] }; 37 | if ((n = O.hex8.exec(t))) 38 | return { 39 | r: M(n[1]), 40 | g: M(n[2]), 41 | b: M(n[3]), 42 | a: z(n[4]), 43 | format: a ? "name" : "hex8", 44 | }; 45 | if ((n = O.hex6.exec(t))) 46 | return { 47 | r: M(n[1]), 48 | g: M(n[2]), 49 | b: M(n[3]), 50 | format: a ? "name" : "hex", 51 | }; 52 | if ((n = O.hex4.exec(t))) 53 | return { 54 | r: M(n[1] + "" + n[1]), 55 | g: M(n[2] + "" + n[2]), 56 | b: M(n[3] + "" + n[3]), 57 | a: z(n[4] + "" + n[4]), 58 | format: a ? "name" : "hex8", 59 | }; 60 | if ((n = O.hex3.exec(t))) 61 | return { 62 | r: M(n[1] + "" + n[1]), 63 | g: M(n[2] + "" + n[2]), 64 | b: M(n[3] + "" + n[3]), 65 | format: a ? "name" : "hex", 66 | }; 67 | return !1; 68 | })(n)); 69 | "object" == typeof n && 70 | (P(n.r) && P(n.g) && P(n.b) 71 | ? ((g = n.r), 72 | (b = n.g), 73 | (d = n.b), 74 | (a = { 75 | r: 255 * C(g, 255), 76 | g: 255 * C(b, 255), 77 | b: 255 * C(d, 255), 78 | }), 79 | (l = !0), 80 | (c = "%" === String(n.r).substr(-1) ? "prgb" : "rgb")) 81 | : P(n.h) && P(n.s) && P(n.v) 82 | ? ((f = L(n.s)), 83 | (h = L(n.v)), 84 | (a = (function (e, r, n) { 85 | (e = 6 * C(e, 360)), (r = C(r, 100)), (n = C(n, 100)); 86 | var a = t.floor(e), 87 | i = e - a, 88 | s = n * (1 - r), 89 | o = n * (1 - i * r), 90 | f = n * (1 - (1 - i) * r), 91 | h = a % 6; 92 | return { 93 | r: 255 * [n, o, s, s, f, n][h], 94 | g: 255 * [f, n, n, o, s, s][h], 95 | b: 255 * [s, s, f, n, n, o][h], 96 | }; 97 | })(n.h, f, h)), 98 | (l = !0), 99 | (c = "hsv")) 100 | : P(n.h) && 101 | P(n.s) && 102 | P(n.l) && 103 | ((f = L(n.s)), 104 | (u = L(n.l)), 105 | (a = (function (t, e, r) { 106 | var n, a, i; 107 | function s(t, e, r) { 108 | return ( 109 | r < 0 && (r += 1), 110 | r > 1 && (r -= 1), 111 | r < 1 / 6 112 | ? t + 6 * (e - t) * r 113 | : r < 0.5 114 | ? e 115 | : r < 2 / 3 116 | ? t + (e - t) * (2 / 3 - r) * 6 117 | : t 118 | ); 119 | } 120 | if (((t = C(t, 360)), (e = C(e, 100)), (r = C(r, 100)), 0 === e)) 121 | n = a = i = r; 122 | else { 123 | var o = r < 0.5 ? r * (1 + e) : r + e - r * e, 124 | f = 2 * r - o; 125 | (n = s(f, o, t + 1 / 3)), 126 | (a = s(f, o, t)), 127 | (i = s(f, o, t - 1 / 3)); 128 | } 129 | return { r: 255 * n, g: 255 * a, b: 255 * i }; 130 | })(n.h, f, u)), 131 | (l = !0), 132 | (c = "hsl")), 133 | n.hasOwnProperty("a") && (o = n.a)); 134 | var g, b, d; 135 | return ( 136 | (o = F(o)), 137 | { 138 | ok: l, 139 | format: n.format || c, 140 | r: i(255, s(a.r, 0)), 141 | g: i(255, s(a.g, 0)), 142 | b: i(255, s(a.b, 0)), 143 | a: o, 144 | } 145 | ); 146 | })(o); 147 | (this._originalInput = o), 148 | (this._r = u.r), 149 | (this._g = u.g), 150 | (this._b = u.b), 151 | (this._a = u.a), 152 | (this._roundA = a(100 * this._a) / 100), 153 | (this._format = h.format || u.format), 154 | (this._gradientType = h.gradientType), 155 | this._r < 1 && (this._r = a(this._r)), 156 | this._g < 1 && (this._g = a(this._g)), 157 | this._b < 1 && (this._b = a(this._b)), 158 | (this._ok = u.ok), 159 | (this._tc_id = n++); 160 | } 161 | function h(t, e, r) { 162 | (t = C(t, 255)), (e = C(e, 255)), (r = C(r, 255)); 163 | var n, 164 | a, 165 | o = s(t, e, r), 166 | f = i(t, e, r), 167 | h = (o + f) / 2; 168 | if (o == f) n = a = 0; 169 | else { 170 | var u = o - f; 171 | switch (((a = h > 0.5 ? u / (2 - o - f) : u / (o + f)), o)) { 172 | case t: 173 | n = (e - r) / u + (e < r ? 6 : 0); 174 | break; 175 | case e: 176 | n = (r - t) / u + 2; 177 | break; 178 | case r: 179 | n = (t - e) / u + 4; 180 | } 181 | n /= 6; 182 | } 183 | return { h: n, s: a, l: h }; 184 | } 185 | function u(t, e, r) { 186 | (t = C(t, 255)), (e = C(e, 255)), (r = C(r, 255)); 187 | var n, 188 | a, 189 | o = s(t, e, r), 190 | f = i(t, e, r), 191 | h = o, 192 | u = o - f; 193 | if (((a = 0 === o ? 0 : u / o), o == f)) n = 0; 194 | else { 195 | switch (o) { 196 | case t: 197 | n = (e - r) / u + (e < r ? 6 : 0); 198 | break; 199 | case e: 200 | n = (r - t) / u + 2; 201 | break; 202 | case r: 203 | n = (t - e) / u + 4; 204 | } 205 | n /= 6; 206 | } 207 | return { h: n, s: a, v: h }; 208 | } 209 | function l(t, e, r, n) { 210 | var i = [I(a(t).toString(16)), I(a(e).toString(16)), I(a(r).toString(16))]; 211 | return n && 212 | i[0].charAt(0) == i[0].charAt(1) && 213 | i[1].charAt(0) == i[1].charAt(1) && 214 | i[2].charAt(0) == i[2].charAt(1) 215 | ? i[0].charAt(0) + i[1].charAt(0) + i[2].charAt(0) 216 | : i.join(""); 217 | } 218 | function c(t, e, r, n) { 219 | return [ 220 | I(N(n)), 221 | I(a(t).toString(16)), 222 | I(a(e).toString(16)), 223 | I(a(r).toString(16)), 224 | ].join(""); 225 | } 226 | function g(t, e) { 227 | e = 0 === e ? 0 : e || 10; 228 | var r = f(t).toHsl(); 229 | return (r.s -= e / 100), (r.s = q(r.s)), f(r); 230 | } 231 | function b(t, e) { 232 | e = 0 === e ? 0 : e || 10; 233 | var r = f(t).toHsl(); 234 | return (r.s += e / 100), (r.s = q(r.s)), f(r); 235 | } 236 | function d(t) { 237 | return f(t).desaturate(100); 238 | } 239 | function _(t, e) { 240 | e = 0 === e ? 0 : e || 10; 241 | var r = f(t).toHsl(); 242 | return (r.l += e / 100), (r.l = q(r.l)), f(r); 243 | } 244 | function p(t, e) { 245 | e = 0 === e ? 0 : e || 10; 246 | var r = f(t).toRgb(); 247 | return ( 248 | (r.r = s(0, i(255, r.r - a((-e / 100) * 255)))), 249 | (r.g = s(0, i(255, r.g - a((-e / 100) * 255)))), 250 | (r.b = s(0, i(255, r.b - a((-e / 100) * 255)))), 251 | f(r) 252 | ); 253 | } 254 | function m(t, e) { 255 | e = 0 === e ? 0 : e || 10; 256 | var r = f(t).toHsl(); 257 | return (r.l -= e / 100), (r.l = q(r.l)), f(r); 258 | } 259 | function v(t, e) { 260 | var r = f(t).toHsl(), 261 | n = (r.h + e) % 360; 262 | return (r.h = n < 0 ? 360 + n : n), f(r); 263 | } 264 | function y(t) { 265 | var e = f(t).toHsl(); 266 | return (e.h = (e.h + 180) % 360), f(e); 267 | } 268 | function A(t) { 269 | var e = f(t).toHsl(), 270 | r = e.h; 271 | return [ 272 | f(t), 273 | f({ h: (r + 120) % 360, s: e.s, l: e.l }), 274 | f({ h: (r + 240) % 360, s: e.s, l: e.l }), 275 | ]; 276 | } 277 | function x(t) { 278 | var e = f(t).toHsl(), 279 | r = e.h; 280 | return [ 281 | f(t), 282 | f({ h: (r + 90) % 360, s: e.s, l: e.l }), 283 | f({ h: (r + 180) % 360, s: e.s, l: e.l }), 284 | f({ h: (r + 270) % 360, s: e.s, l: e.l }), 285 | ]; 286 | } 287 | function k(t) { 288 | var e = f(t).toHsl(), 289 | r = e.h; 290 | return [ 291 | f(t), 292 | f({ h: (r + 72) % 360, s: e.s, l: e.l }), 293 | f({ h: (r + 216) % 360, s: e.s, l: e.l }), 294 | ]; 295 | } 296 | function w(t, e, r) { 297 | (e = e || 6), (r = r || 30); 298 | var n = f(t).toHsl(), 299 | a = 360 / r, 300 | i = [f(t)]; 301 | for (n.h = (n.h - ((a * e) >> 1) + 720) % 360; --e; ) 302 | (n.h = (n.h + a) % 360), i.push(f(n)); 303 | return i; 304 | } 305 | function S(t, e) { 306 | e = e || 6; 307 | for ( 308 | var r = f(t).toHsv(), n = r.h, a = r.s, i = r.v, s = [], o = 1 / e; 309 | e--; 310 | 311 | ) 312 | s.push(f({ h: n, s: a, v: i })), (i = (i + o) % 1); 313 | return s; 314 | } 315 | (f.prototype = { 316 | isDark: function () { 317 | return this.getBrightness() < 128; 318 | }, 319 | isLight: function () { 320 | return !this.isDark(); 321 | }, 322 | isValid: function () { 323 | return this._ok; 324 | }, 325 | getOriginalInput: function () { 326 | return this._originalInput; 327 | }, 328 | getFormat: function () { 329 | return this._format; 330 | }, 331 | getAlpha: function () { 332 | return this._a; 333 | }, 334 | getBrightness: function () { 335 | var t = this.toRgb(); 336 | return (299 * t.r + 587 * t.g + 114 * t.b) / 1e3; 337 | }, 338 | getLuminance: function () { 339 | var e, 340 | r, 341 | n, 342 | a = this.toRgb(); 343 | return ( 344 | (e = a.r / 255), 345 | (r = a.g / 255), 346 | (n = a.b / 255), 347 | 0.2126 * (e <= 0.03928 ? e / 12.92 : t.pow((e + 0.055) / 1.055, 2.4)) + 348 | 0.7152 * 349 | (r <= 0.03928 ? r / 12.92 : t.pow((r + 0.055) / 1.055, 2.4)) + 350 | 0.0722 * (n <= 0.03928 ? n / 12.92 : t.pow((n + 0.055) / 1.055, 2.4)) 351 | ); 352 | }, 353 | setAlpha: function (t) { 354 | return (this._a = F(t)), (this._roundA = a(100 * this._a) / 100), this; 355 | }, 356 | toHsv: function () { 357 | var t = u(this._r, this._g, this._b); 358 | return { h: 360 * t.h, s: t.s, v: t.v, a: this._a }; 359 | }, 360 | toHsvString: function () { 361 | var t = u(this._r, this._g, this._b), 362 | e = a(360 * t.h), 363 | r = a(100 * t.s), 364 | n = a(100 * t.v); 365 | return 1 == this._a 366 | ? "hsv(" + e + ", " + r + "%, " + n + "%)" 367 | : "hsva(" + e + ", " + r + "%, " + n + "%, " + this._roundA + ")"; 368 | }, 369 | toHsl: function () { 370 | var t = h(this._r, this._g, this._b); 371 | return { h: 360 * t.h, s: t.s, l: t.l, a: this._a }; 372 | }, 373 | toHslString: function () { 374 | var t = h(this._r, this._g, this._b), 375 | e = a(360 * t.h), 376 | r = a(100 * t.s), 377 | n = a(100 * t.l); 378 | return 1 == this._a 379 | ? "hsl(" + e + ", " + r + "%, " + n + "%)" 380 | : "hsla(" + e + ", " + r + "%, " + n + "%, " + this._roundA + ")"; 381 | }, 382 | toHex: function (t) { 383 | return l(this._r, this._g, this._b, t); 384 | }, 385 | toHexString: function (t) { 386 | return "#" + this.toHex(t); 387 | }, 388 | toHex8: function (t) { 389 | return (function (t, e, r, n, i) { 390 | var s = [ 391 | I(a(t).toString(16)), 392 | I(a(e).toString(16)), 393 | I(a(r).toString(16)), 394 | I(N(n)), 395 | ]; 396 | if ( 397 | i && 398 | s[0].charAt(0) == s[0].charAt(1) && 399 | s[1].charAt(0) == s[1].charAt(1) && 400 | s[2].charAt(0) == s[2].charAt(1) && 401 | s[3].charAt(0) == s[3].charAt(1) 402 | ) 403 | return ( 404 | s[0].charAt(0) + s[1].charAt(0) + s[2].charAt(0) + s[3].charAt(0) 405 | ); 406 | return s.join(""); 407 | })(this._r, this._g, this._b, this._a, t); 408 | }, 409 | toHex8String: function (t) { 410 | return "#" + this.toHex8(t); 411 | }, 412 | toRgb: function () { 413 | return { r: a(this._r), g: a(this._g), b: a(this._b), a: this._a }; 414 | }, 415 | toRgbString: function () { 416 | return 1 == this._a 417 | ? "rgb(" + a(this._r) + ", " + a(this._g) + ", " + a(this._b) + ")" 418 | : "rgba(" + 419 | a(this._r) + 420 | ", " + 421 | a(this._g) + 422 | ", " + 423 | a(this._b) + 424 | ", " + 425 | this._roundA + 426 | ")"; 427 | }, 428 | toPercentageRgb: function () { 429 | return { 430 | r: a(100 * C(this._r, 255)) + "%", 431 | g: a(100 * C(this._g, 255)) + "%", 432 | b: a(100 * C(this._b, 255)) + "%", 433 | a: this._a, 434 | }; 435 | }, 436 | toPercentageRgbString: function () { 437 | return 1 == this._a 438 | ? "rgb(" + 439 | a(100 * C(this._r, 255)) + 440 | "%, " + 441 | a(100 * C(this._g, 255)) + 442 | "%, " + 443 | a(100 * C(this._b, 255)) + 444 | "%)" 445 | : "rgba(" + 446 | a(100 * C(this._r, 255)) + 447 | "%, " + 448 | a(100 * C(this._g, 255)) + 449 | "%, " + 450 | a(100 * C(this._b, 255)) + 451 | "%, " + 452 | this._roundA + 453 | ")"; 454 | }, 455 | toName: function () { 456 | return 0 === this._a 457 | ? "transparent" 458 | : !(this._a < 1) && (R[l(this._r, this._g, this._b, !0)] || !1); 459 | }, 460 | toFilter: function (t) { 461 | var e = "#" + c(this._r, this._g, this._b, this._a), 462 | r = e, 463 | n = this._gradientType ? "GradientType = 1, " : ""; 464 | if (t) { 465 | var a = f(t); 466 | r = "#" + c(a._r, a._g, a._b, a._a); 467 | } 468 | return ( 469 | "progid:DXImageTransform.Microsoft.gradient(" + 470 | n + 471 | "startColorstr=" + 472 | e + 473 | ",endColorstr=" + 474 | r + 475 | ")" 476 | ); 477 | }, 478 | toString: function (t) { 479 | var e = !!t; 480 | t = t || this._format; 481 | var r = !1, 482 | n = this._a < 1 && this._a >= 0; 483 | return e || 484 | !n || 485 | ("hex" !== t && 486 | "hex6" !== t && 487 | "hex3" !== t && 488 | "hex4" !== t && 489 | "hex8" !== t && 490 | "name" !== t) 491 | ? ("rgb" === t && (r = this.toRgbString()), 492 | "prgb" === t && (r = this.toPercentageRgbString()), 493 | ("hex" !== t && "hex6" !== t) || (r = this.toHexString()), 494 | "hex3" === t && (r = this.toHexString(!0)), 495 | "hex4" === t && (r = this.toHex8String(!0)), 496 | "hex8" === t && (r = this.toHex8String()), 497 | "name" === t && (r = this.toName()), 498 | "hsl" === t && (r = this.toHslString()), 499 | "hsv" === t && (r = this.toHsvString()), 500 | r || this.toHexString()) 501 | : "name" === t && 0 === this._a 502 | ? this.toName() 503 | : this.toRgbString(); 504 | }, 505 | clone: function () { 506 | return f(this.toString()); 507 | }, 508 | _applyModification: function (t, e) { 509 | var r = t.apply(null, [this].concat([].slice.call(e))); 510 | return ( 511 | (this._r = r._r), 512 | (this._g = r._g), 513 | (this._b = r._b), 514 | this.setAlpha(r._a), 515 | this 516 | ); 517 | }, 518 | lighten: function () { 519 | return this._applyModification(_, arguments); 520 | }, 521 | brighten: function () { 522 | return this._applyModification(p, arguments); 523 | }, 524 | darken: function () { 525 | return this._applyModification(m, arguments); 526 | }, 527 | desaturate: function () { 528 | return this._applyModification(g, arguments); 529 | }, 530 | saturate: function () { 531 | return this._applyModification(b, arguments); 532 | }, 533 | greyscale: function () { 534 | return this._applyModification(d, arguments); 535 | }, 536 | spin: function () { 537 | return this._applyModification(v, arguments); 538 | }, 539 | _applyCombination: function (t, e) { 540 | return t.apply(null, [this].concat([].slice.call(e))); 541 | }, 542 | analogous: function () { 543 | return this._applyCombination(w, arguments); 544 | }, 545 | complement: function () { 546 | return this._applyCombination(y, arguments); 547 | }, 548 | monochromatic: function () { 549 | return this._applyCombination(S, arguments); 550 | }, 551 | splitcomplement: function () { 552 | return this._applyCombination(k, arguments); 553 | }, 554 | triad: function () { 555 | return this._applyCombination(A, arguments); 556 | }, 557 | tetrad: function () { 558 | return this._applyCombination(x, arguments); 559 | }, 560 | }), 561 | (f.fromRatio = function (t, e) { 562 | if ("object" == typeof t) { 563 | var r = {}; 564 | for (var n in t) 565 | t.hasOwnProperty(n) && (r[n] = "a" === n ? t[n] : L(t[n])); 566 | t = r; 567 | } 568 | return f(t, e); 569 | }), 570 | (f.equals = function (t, e) { 571 | return !(!t || !e) && f(t).toRgbString() == f(e).toRgbString(); 572 | }), 573 | (f.random = function () { 574 | return f.fromRatio({ r: o(), g: o(), b: o() }); 575 | }), 576 | (f.mix = function (t, e, r) { 577 | r = 0 === r ? 0 : r || 50; 578 | var n = f(t).toRgb(), 579 | a = f(e).toRgb(), 580 | i = r / 100; 581 | return f({ 582 | r: (a.r - n.r) * i + n.r, 583 | g: (a.g - n.g) * i + n.g, 584 | b: (a.b - n.b) * i + n.b, 585 | a: (a.a - n.a) * i + n.a, 586 | }); 587 | }), 588 | (f.readability = function (e, r) { 589 | var n = f(e), 590 | a = f(r); 591 | return ( 592 | (t.max(n.getLuminance(), a.getLuminance()) + 0.05) / 593 | (t.min(n.getLuminance(), a.getLuminance()) + 0.05) 594 | ); 595 | }), 596 | (f.isReadable = function (t, e, r) { 597 | var n, 598 | a, 599 | i = f.readability(t, e); 600 | switch ( 601 | ((a = !1), 602 | (n = (function (t) { 603 | var e, r; 604 | (e = ( 605 | (t = t || { level: "AA", size: "small" }).level || "AA" 606 | ).toUpperCase()), 607 | (r = (t.size || "small").toLowerCase()), 608 | "AA" !== e && "AAA" !== e && (e = "AA"); 609 | "small" !== r && "large" !== r && (r = "small"); 610 | return { level: e, size: r }; 611 | })(r)).level + n.size) 612 | ) { 613 | case "AAsmall": 614 | case "AAAlarge": 615 | a = i >= 4.5; 616 | break; 617 | case "AAlarge": 618 | a = i >= 3; 619 | break; 620 | case "AAAsmall": 621 | a = i >= 7; 622 | } 623 | return a; 624 | }), 625 | (f.mostReadable = function (t, e, r) { 626 | var n, 627 | a, 628 | i, 629 | s, 630 | o = null, 631 | h = 0; 632 | (a = (r = r || {}).includeFallbackColors), (i = r.level), (s = r.size); 633 | for (var u = 0; u < e.length; u++) 634 | (n = f.readability(t, e[u])) > h && ((h = n), (o = f(e[u]))); 635 | return f.isReadable(t, o, { level: i, size: s }) || !a 636 | ? o 637 | : ((r.includeFallbackColors = !1), 638 | f.mostReadable(t, ["#fff", "#000"], r)); 639 | }); 640 | var H = (f.names = { 641 | aliceblue: "f0f8ff", 642 | antiquewhite: "faebd7", 643 | aqua: "0ff", 644 | aquamarine: "7fffd4", 645 | azure: "f0ffff", 646 | beige: "f5f5dc", 647 | bisque: "ffe4c4", 648 | black: "000", 649 | blanchedalmond: "ffebcd", 650 | blue: "00f", 651 | blueviolet: "8a2be2", 652 | brown: "a52a2a", 653 | burlywood: "deb887", 654 | burntsienna: "ea7e5d", 655 | cadetblue: "5f9ea0", 656 | chartreuse: "7fff00", 657 | chocolate: "d2691e", 658 | coral: "ff7f50", 659 | cornflowerblue: "6495ed", 660 | cornsilk: "fff8dc", 661 | crimson: "dc143c", 662 | cyan: "0ff", 663 | darkblue: "00008b", 664 | darkcyan: "008b8b", 665 | darkgoldenrod: "b8860b", 666 | darkgray: "a9a9a9", 667 | darkgreen: "006400", 668 | darkgrey: "a9a9a9", 669 | darkkhaki: "bdb76b", 670 | darkmagenta: "8b008b", 671 | darkolivegreen: "556b2f", 672 | darkorange: "ff8c00", 673 | darkorchid: "9932cc", 674 | darkred: "8b0000", 675 | darksalmon: "e9967a", 676 | darkseagreen: "8fbc8f", 677 | darkslateblue: "483d8b", 678 | darkslategray: "2f4f4f", 679 | darkslategrey: "2f4f4f", 680 | darkturquoise: "00ced1", 681 | darkviolet: "9400d3", 682 | deeppink: "ff1493", 683 | deepskyblue: "00bfff", 684 | dimgray: "696969", 685 | dimgrey: "696969", 686 | dodgerblue: "1e90ff", 687 | firebrick: "b22222", 688 | floralwhite: "fffaf0", 689 | forestgreen: "228b22", 690 | fuchsia: "f0f", 691 | gainsboro: "dcdcdc", 692 | ghostwhite: "f8f8ff", 693 | gold: "ffd700", 694 | goldenrod: "daa520", 695 | gray: "808080", 696 | green: "008000", 697 | greenyellow: "adff2f", 698 | grey: "808080", 699 | honeydew: "f0fff0", 700 | hotpink: "ff69b4", 701 | indianred: "cd5c5c", 702 | indigo: "4b0082", 703 | ivory: "fffff0", 704 | khaki: "f0e68c", 705 | lavender: "e6e6fa", 706 | lavenderblush: "fff0f5", 707 | lawngreen: "7cfc00", 708 | lemonchiffon: "fffacd", 709 | lightblue: "add8e6", 710 | lightcoral: "f08080", 711 | lightcyan: "e0ffff", 712 | lightgoldenrodyellow: "fafad2", 713 | lightgray: "d3d3d3", 714 | lightgreen: "90ee90", 715 | lightgrey: "d3d3d3", 716 | lightpink: "ffb6c1", 717 | lightsalmon: "ffa07a", 718 | lightseagreen: "20b2aa", 719 | lightskyblue: "87cefa", 720 | lightslategray: "789", 721 | lightslategrey: "789", 722 | lightsteelblue: "b0c4de", 723 | lightyellow: "ffffe0", 724 | lime: "0f0", 725 | limegreen: "32cd32", 726 | linen: "faf0e6", 727 | magenta: "f0f", 728 | maroon: "800000", 729 | mediumaquamarine: "66cdaa", 730 | mediumblue: "0000cd", 731 | mediumorchid: "ba55d3", 732 | mediumpurple: "9370db", 733 | mediumseagreen: "3cb371", 734 | mediumslateblue: "7b68ee", 735 | mediumspringgreen: "00fa9a", 736 | mediumturquoise: "48d1cc", 737 | mediumvioletred: "c71585", 738 | midnightblue: "191970", 739 | mintcream: "f5fffa", 740 | mistyrose: "ffe4e1", 741 | moccasin: "ffe4b5", 742 | navajowhite: "ffdead", 743 | navy: "000080", 744 | oldlace: "fdf5e6", 745 | olive: "808000", 746 | olivedrab: "6b8e23", 747 | orange: "ffa500", 748 | orangered: "ff4500", 749 | orchid: "da70d6", 750 | palegoldenrod: "eee8aa", 751 | palegreen: "98fb98", 752 | paleturquoise: "afeeee", 753 | palevioletred: "db7093", 754 | papayawhip: "ffefd5", 755 | peachpuff: "ffdab9", 756 | peru: "cd853f", 757 | pink: "ffc0cb", 758 | plum: "dda0dd", 759 | powderblue: "b0e0e6", 760 | purple: "800080", 761 | rebeccapurple: "663399", 762 | red: "f00", 763 | rosybrown: "bc8f8f", 764 | royalblue: "4169e1", 765 | saddlebrown: "8b4513", 766 | salmon: "fa8072", 767 | sandybrown: "f4a460", 768 | seagreen: "2e8b57", 769 | seashell: "fff5ee", 770 | sienna: "a0522d", 771 | silver: "c0c0c0", 772 | skyblue: "87ceeb", 773 | slateblue: "6a5acd", 774 | slategray: "708090", 775 | slategrey: "708090", 776 | snow: "fffafa", 777 | springgreen: "00ff7f", 778 | steelblue: "4682b4", 779 | tan: "d2b48c", 780 | teal: "008080", 781 | thistle: "d8bfd8", 782 | tomato: "ff6347", 783 | turquoise: "40e0d0", 784 | violet: "ee82ee", 785 | wheat: "f5deb3", 786 | white: "fff", 787 | whitesmoke: "f5f5f5", 788 | yellow: "ff0", 789 | yellowgreen: "9acd32", 790 | }), 791 | R = (f.hexNames = (function (t) { 792 | var e = {}; 793 | for (var r in t) t.hasOwnProperty(r) && (e[t[r]] = r); 794 | return e; 795 | })(H)); 796 | function F(t) { 797 | return (t = parseFloat(t)), (isNaN(t) || t < 0 || t > 1) && (t = 1), t; 798 | } 799 | function C(e, r) { 800 | (function (t) { 801 | return ( 802 | "string" == typeof t && -1 != t.indexOf(".") && 1 === parseFloat(t) 803 | ); 804 | })(e) && (e = "100%"); 805 | var n = (function (t) { 806 | return "string" == typeof t && -1 != t.indexOf("%"); 807 | })(e); 808 | return ( 809 | (e = i(r, s(0, parseFloat(e)))), 810 | n && (e = parseInt(e * r, 10) / 100), 811 | t.abs(e - r) < 1e-6 ? 1 : (e % r) / parseFloat(r) 812 | ); 813 | } 814 | function q(t) { 815 | return i(1, s(0, t)); 816 | } 817 | function M(t) { 818 | return parseInt(t, 16); 819 | } 820 | function I(t) { 821 | return 1 == t.length ? "0" + t : "" + t; 822 | } 823 | function L(t) { 824 | return t <= 1 && (t = 100 * t + "%"), t; 825 | } 826 | function N(e) { 827 | return t.round(255 * parseFloat(e)).toString(16); 828 | } 829 | function z(t) { 830 | return M(t) / 255; 831 | } 832 | var E, 833 | T, 834 | j, 835 | O = 836 | ((T = 837 | "[\\s|\\(]+(" + 838 | (E = "(?:[-\\+]?\\d*\\.\\d+%?)|(?:[-\\+]?\\d+%?)") + 839 | ")[,|\\s]+(" + 840 | E + 841 | ")[,|\\s]+(" + 842 | E + 843 | ")\\s*\\)?"), 844 | (j = 845 | "[\\s|\\(]+(" + 846 | E + 847 | ")[,|\\s]+(" + 848 | E + 849 | ")[,|\\s]+(" + 850 | E + 851 | ")[,|\\s]+(" + 852 | E + 853 | ")\\s*\\)?"), 854 | { 855 | CSS_UNIT: new RegExp(E), 856 | rgb: new RegExp("rgb" + T), 857 | rgba: new RegExp("rgba" + j), 858 | hsl: new RegExp("hsl" + T), 859 | hsla: new RegExp("hsla" + j), 860 | hsv: new RegExp("hsv" + T), 861 | hsva: new RegExp("hsva" + j), 862 | hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, 863 | hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, 864 | hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, 865 | hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, 866 | }); 867 | function P(t) { 868 | return !!O.CSS_UNIT.exec(t); 869 | } 870 | "undefined" != typeof module && module.exports 871 | ? (module.exports = f) 872 | : "function" == typeof define && define.amd 873 | ? define(function () { 874 | return f; 875 | }) 876 | : (window.tinycolor = f); 877 | })(Math); 878 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GPL-3.0 License GPL-3.0 License GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /webview/main.js: -------------------------------------------------------------------------------- 1 | // This script will be run within the webview itself 2 | // It cannot access the main VS Code APIs directly 3 | // @ts-nocheck 4 | 5 | (function () { 6 | //==================================================== 7 | // Global 8 | //==================================================== 9 | const vscode = acquireVsCodeApi(); 10 | 11 | // Handle messages sent from the extension to the webview 12 | window.addEventListener("message", (event) => { 13 | const message = event.data; 14 | 15 | // Message sent when the extension activates and sends settings 16 | if (message.type === "allConfig") { 17 | setBoxBackgroundColor(); 18 | 19 | // Put words list and settings into a state 20 | vscode.setState({ 21 | allWords: message.words, 22 | allCodes: message.codes, 23 | language: message.language, 24 | codeLanguage: message.codeLanguage, 25 | count: message.count, 26 | mode: message.mode, 27 | punctuation: message.punctuation, 28 | colorBlindMode: message.colorBlindMode, 29 | }); 30 | extensionState = vscode.getState(); 31 | 32 | // Change words list and settings 33 | allWords = message.words; 34 | allCodes = message.codes; 35 | setLanguage(message.language); 36 | setCodeLanguage(message.codeLanguage); 37 | setWordCount(message.count); 38 | setTimeCount(message.count); 39 | setTypingMode(message.mode); 40 | setColorBlindMode(message.colorBlindMode); 41 | setPunctuation(message.punctuation); 42 | 43 | // Start typing test 44 | if (extensionState.mode === "code snippets") { 45 | setCodeText(); 46 | showCodeText(); 47 | } else { 48 | setText(); 49 | showText(); 50 | } 51 | } else if (message.type === "practiceWithSelection") { 52 | setBoxBackgroundColor(); 53 | 54 | // Put words list and settings into a state 55 | vscode.setState({ 56 | allWords: message.words, 57 | allCodes: message.codes, 58 | language: message.language, 59 | codeLanguage: message.codeLanguage, 60 | count: message.count, 61 | mode: message.mode, 62 | punctuation: message.punctuation, 63 | colorBlindMode: message.colorBlindMode, 64 | }); 65 | extensionState = vscode.getState(); 66 | 67 | // Change words list and settings 68 | allWords = message.words; 69 | allCodes = message.codes; 70 | setLanguage(message.language); 71 | setCodeLanguage(message.codeLanguage); 72 | setWordCount(message.count); 73 | setTimeCount(message.count); 74 | setTypingMode(message.mode); 75 | setColorBlindMode(message.colorBlindMode); 76 | setPunctuation(message.punctuation); 77 | 78 | // Start typing test 79 | setSelectedCodeText(message.selectedCode, message.selectedCodeLanguage); 80 | 81 | showCodeText(message.selectedCodeLanguage); 82 | } else { 83 | // Message to change a single setting 84 | switch (message.config) { 85 | case "switchNaturalLanguage": 86 | if (message.value) { 87 | setLanguage(message.value); 88 | 89 | vscode.setState({ ...extensionState, language: message.value }); 90 | extensionState = vscode.getState(); 91 | 92 | if (extensionState.mode !== "code snippets") { 93 | setText(); 94 | showText(); 95 | } 96 | } 97 | break; 98 | 99 | case "switchProgrammingLanguage": 100 | if (message.value) { 101 | setCodeLanguage(message.value); 102 | 103 | vscode.setState({ 104 | ...extensionState, 105 | codeLanguage: message.value, 106 | }); 107 | extensionState = vscode.getState(); 108 | 109 | if (extensionState.mode === "code snippets") { 110 | setCodeText(); 111 | showCodeText(); 112 | } 113 | } 114 | break; 115 | 116 | case "changeTypingMode": 117 | if (message.value) { 118 | vscode.setState({ 119 | ...extensionState, 120 | mode: message.value, 121 | }); 122 | extensionState = vscode.getState(); 123 | 124 | gameOver = true; 125 | setTypingMode(message.value); 126 | } 127 | break; 128 | 129 | case "togglePunctuation": 130 | if (message.value) { 131 | setPunctuation(message.value); 132 | 133 | vscode.setState({ 134 | ...extensionState, 135 | punctuation: message.value, 136 | }); 137 | extensionState = vscode.getState(); 138 | 139 | if (extensionState.mode !== "code snippets") { 140 | setText(); 141 | showText(); 142 | } 143 | } 144 | break; 145 | 146 | case "toggleColorBlindMode": 147 | setColorBlindMode(message.value); 148 | 149 | vscode.setState({ ...extensionState, colorBlindMode: message.value }); 150 | extensionState = vscode.getState(); 151 | break; 152 | 153 | case "changeCount": 154 | if (message.value) { 155 | setWordCount(message.value); 156 | setTimeCount(message.value); 157 | 158 | vscode.setState({ 159 | ...extensionState, 160 | count: message.value, 161 | }); 162 | extensionState = vscode.getState(); 163 | 164 | if (extensionState.mode !== "code snippets") { 165 | setText(); 166 | showText(); 167 | } 168 | } 169 | break; 170 | 171 | default: 172 | break; 173 | } 174 | } 175 | }); 176 | 177 | // Get document elements 178 | const textDisplay = document.querySelector("#text-display"); 179 | const inputField = document.querySelector("#input-field"); 180 | 181 | const cursor = document.getElementById("cursor"); 182 | const root = document.documentElement; 183 | 184 | // Initialize dynamic variables 185 | let selectedLanguageWords = []; 186 | let currentWordsList = []; 187 | let currentWord = 0; 188 | let correctKeys = 0; 189 | let punctuation = false; 190 | let wordCount; 191 | let timeCount; 192 | let startDate = 0; 193 | let timerActive = false; 194 | let timer; 195 | 196 | let allWords = []; 197 | let allCodes = []; 198 | let selectedLanguageCodes = []; 199 | let selectedLanguageName = ""; 200 | let currentCode = ""; 201 | let gameOver = true; 202 | let codeStartDate = 0; 203 | let codeState = { 204 | firstChar: null, 205 | lastChar: null, 206 | currentChar: null, 207 | currentCharNum: 0, 208 | cursorLeftOffset: 0, 209 | cursorTopOffset: 0, 210 | linesLastCursorPositions: [], 211 | }; 212 | 213 | // Get all words and settings from the state if it exists 214 | let extensionState = vscode.getState(); 215 | if (extensionState) { 216 | setLanguage(extensionState.language); 217 | setCodeLanguage(extensionState.codeLanguage); 218 | setWordCount(extensionState.count); 219 | setTimeCount(extensionState.count); 220 | setTypingMode(extensionState.mode); 221 | setColorBlindMode(extensionState.mode); 222 | setPunctuation(extensionState.punctuation); 223 | setText(); 224 | showText(); 225 | } 226 | 227 | // Restart if restart button hit 228 | document.querySelector("#restart-button").addEventListener("click", (e) => { 229 | setText(e); 230 | showText(); 231 | }); 232 | document.querySelector(".codeButton").addEventListener("click", (e) => { 233 | setCodeText(e); 234 | showCodeText(); 235 | }); 236 | 237 | // Restart if escape key hit 238 | document.addEventListener("keydown", (e) => { 239 | if (e.key === "Escape") { 240 | if (extensionState.mode === "code snippets") { 241 | setCodeText(e); 242 | showCodeText(); 243 | } else { 244 | setText(e); 245 | showText(); 246 | } 247 | } 248 | }); 249 | 250 | // Function to change typing mode 251 | function setTypingMode(_mode) { 252 | const mode = _mode.toLowerCase(); 253 | 254 | switch (mode) { 255 | case "words (fixed amount)": 256 | // Update ui 257 | document.querySelector("#coding-area").style.display = "none"; 258 | document.querySelector("#time-count").style.display = "none"; 259 | document.querySelector("#language-selected").style.display = "none"; 260 | document.querySelector("#typing-area").style.display = "inline"; 261 | document.querySelector("#word-count").style.display = "inline"; 262 | 263 | // Start typing test 264 | setText(); 265 | showText(); 266 | 267 | break; 268 | 269 | case "words (against the clock)": 270 | // Update ui 271 | document.querySelector("#coding-area").style.display = "none"; 272 | document.querySelector("#word-count").style.display = "none"; 273 | document.querySelector("#language-selected").style.display = "none"; 274 | document.querySelector("#typing-area").style.display = "inline"; 275 | document.querySelector("#time-count").style.display = "inline"; 276 | 277 | // Start typing test 278 | setText(); 279 | showText(); 280 | 281 | break; 282 | 283 | case "code snippets": 284 | // Update ui 285 | document.querySelector("#typing-area").style.display = "none"; 286 | document.querySelector("#word-count").style.display = "none"; 287 | document.querySelector("#time-count").style.display = "none"; 288 | document.querySelector("#coding-area").style.display = "inline"; 289 | document.querySelector("#language-selected").style.display = "inline"; 290 | 291 | // Start typing test 292 | setCodeText(); 293 | showCodeText(); 294 | 295 | break; 296 | 297 | default: 298 | console.error(`Mode ${mode} is undefined`); 299 | } 300 | } 301 | 302 | // Function to change color blind mode 303 | function setColorBlindMode(_mode) { 304 | let body = document.querySelector("body"); 305 | if (_mode === "true" && !body.classList.contains("colorblind")) { 306 | body.classList.add("colorblind"); 307 | } 308 | if (_mode === "false" && body.classList.contains("colorblind")) { 309 | body.classList.remove("colorblind"); 310 | } 311 | } 312 | 313 | // Function to set box backrgound color depending on editor background color 314 | function setBoxBackgroundColor() { 315 | let body = document.querySelector("body"); 316 | 317 | // Get current editor background color 318 | let editorBackgroundColor = tinycolor( 319 | getComputedStyle(root).getPropertyValue("--editorBackgroundColor") 320 | ); 321 | let boxBackgroundColor = ""; 322 | 323 | if (editorBackgroundColor.isLight()) { 324 | boxBackgroundColor = tinycolor(editorBackgroundColor) 325 | .darken(4) 326 | .toString(); 327 | } else { 328 | boxBackgroundColor = tinycolor(editorBackgroundColor) 329 | .lighten(4) 330 | .toString(); 331 | } 332 | 333 | root.style.setProperty("--boxBackgroundColor", boxBackgroundColor); 334 | } 335 | 336 | //==================================================== 337 | // Words mode 338 | //==================================================== 339 | // Function to generate a new list of words 340 | function setText(e) { 341 | e = e || window.event; 342 | var keepWordList = e && e.shiftKey; 343 | 344 | // Reset 345 | if (!keepWordList) { 346 | currentWordsList = []; 347 | } 348 | currentWord = 0; 349 | correctKeys = 0; 350 | inputField.value = ""; 351 | timerActive = false; 352 | clearTimeout(timer); 353 | textDisplay.style.display = "block"; 354 | inputField.className = ""; 355 | 356 | switch (extensionState.mode) { 357 | case "words (fixed amount)": 358 | textDisplay.style.height = "auto"; 359 | 360 | textDisplay.innerHTML = ""; 361 | if (!keepWordList) { 362 | currentWordsList = []; 363 | 364 | while (currentWordsList.length < wordCount) { 365 | const randomWord = 366 | selectedLanguageWords[ 367 | Math.floor(Math.random() * selectedLanguageWords.length) 368 | ]; 369 | if ( 370 | currentWordsList[currentWordsList.length - 1] !== randomWord || 371 | currentWordsList[currentWordsList.length - 1] === undefined 372 | ) { 373 | currentWordsList.push(randomWord); 374 | } 375 | } 376 | } 377 | break; 378 | 379 | case "words (against the clock)": 380 | textDisplay.style.height = "3.2rem"; 381 | 382 | document.querySelector(`#tc-${timeCount}`).innerHTML = timeCount; 383 | 384 | textDisplay.innerHTML = ""; 385 | if (!keepWordList) { 386 | currentWordsList = []; 387 | 388 | for (let i = 0; i < 500; i++) { 389 | let n = Math.floor(Math.random() * selectedLanguageWords.length); 390 | 391 | currentWordsList.push(selectedLanguageWords[n]); 392 | } 393 | } 394 | } 395 | 396 | if (punctuation) addPunctuations(); 397 | 398 | inputField.focus(); 399 | } 400 | 401 | // Function to display a list of words 402 | function showText() { 403 | currentWordsList.forEach((word) => { 404 | let span = document.createElement("span"); 405 | span.innerHTML = word + " "; 406 | 407 | textDisplay.appendChild(span); 408 | }); 409 | 410 | textDisplay.firstChild.classList.add("highlight"); 411 | } 412 | 413 | // Function to calculate and display result 414 | function showResult() { 415 | let words, minute, acc; 416 | switch (extensionState.mode) { 417 | case "words (fixed amount)": 418 | words = correctKeys / 5; 419 | minute = (Date.now() - startDate) / 1000 / 60; 420 | let totalKeys = -1; 421 | 422 | currentWordsList.forEach((e) => (totalKeys += e.length + 1)); 423 | acc = Math.floor((correctKeys / totalKeys) * 100); 424 | break; 425 | 426 | case "words (against the clock)": 427 | words = correctKeys / 5; 428 | 429 | minute = timeCount / 60; 430 | let sumKeys = -1; 431 | 432 | for (let i = 0; i < currentWord; i++) { 433 | sumKeys += currentWordsList[i].length + 1; 434 | } 435 | acc = acc = Math.min(Math.floor((correctKeys / sumKeys) * 100), 100); 436 | } 437 | 438 | let wpm = Math.floor(words / minute); 439 | 440 | document.querySelector( 441 | "#right-wing" 442 | ).innerHTML = `WPM: ${wpm} / ACC: ${acc}`; 443 | } 444 | 445 | // Key is pressed in input field (game logic) 446 | inputField.addEventListener("keydown", (e) => { 447 | // Add wrong class to input field 448 | switch (extensionState.mode) { 449 | case "words (fixed amount)": 450 | if (currentWord < currentWordsList.length) inputFieldClass(); 451 | case "words (against the clock)": 452 | if (timerActive) inputFieldClass(); 453 | } 454 | function inputFieldClass() { 455 | if ( 456 | (e.key >= "a" && e.key <= "z") || 457 | e.key === `'` || 458 | e.key === "," || 459 | e.key === "." || 460 | e.key === ";" 461 | ) { 462 | let inputWordSlice = inputField.value + e.key; 463 | 464 | let currentWordSlice = currentWordsList[currentWord].slice( 465 | 0, 466 | inputWordSlice.length 467 | ); 468 | 469 | inputField.className = 470 | inputWordSlice === currentWordSlice ? "" : "wrong"; 471 | } else if (e.key === "Backspace") { 472 | let inputWordSlice = e.ctrlKey 473 | ? "" 474 | : inputField.value.slice(0, inputField.value.length - 1); 475 | 476 | let currentWordSlice = currentWordsList[currentWord].slice( 477 | 0, 478 | inputWordSlice.length 479 | ); 480 | 481 | inputField.className = 482 | inputWordSlice === currentWordSlice ? "" : "wrong"; 483 | } else if (e.key === " ") { 484 | inputField.className = ""; 485 | } 486 | } 487 | 488 | // If it is the first character entered 489 | if (currentWord === 0 && inputField.value === "") { 490 | switch (extensionState.mode) { 491 | case "words (fixed amount)": 492 | startDate = Date.now(); 493 | break; 494 | 495 | case "words (against the clock)": 496 | if (!timerActive) { 497 | startTimer(timeCount); 498 | timerActive = true; 499 | } 500 | 501 | function startTimer(time) { 502 | if (time > 0) { 503 | document.querySelector(`#tc-${timeCount}`).innerHTML = time; 504 | timer = setTimeout(() => { 505 | time--; 506 | startTimer(time); 507 | }, 1000); 508 | } else { 509 | timerActive = false; 510 | 511 | textDisplay.style.display = "none"; 512 | 513 | inputField.className = ""; 514 | 515 | document.querySelector(`#tc-${timeCount}`).innerHTML = timeCount; 516 | showResult(); 517 | } 518 | } 519 | } 520 | } 521 | 522 | // If it is the space key check the word and add correct/wrong class 523 | if (e.key === " ") { 524 | e.preventDefault(); 525 | 526 | if (inputField.value !== "") { 527 | // Scroll down text when reach new line 528 | if (extensionState.mode === "words (against the clock)") { 529 | const currentWordPosition = 530 | textDisplay.childNodes[currentWord].getBoundingClientRect(); 531 | const nextWordPosition = 532 | textDisplay.childNodes[currentWord + 1].getBoundingClientRect(); 533 | if (currentWordPosition.top < nextWordPosition.top) { 534 | for (let i = 0; i < currentWord + 1; i++) 535 | textDisplay.childNodes[i].style.display = "none"; 536 | } 537 | } 538 | 539 | // If it is not the last word increment currentWord, 540 | if (currentWord < currentWordsList.length - 1) { 541 | if (inputField.value === currentWordsList[currentWord]) { 542 | textDisplay.childNodes[currentWord].classList.add("correct"); 543 | 544 | correctKeys += currentWordsList[currentWord].length + 1; 545 | } else { 546 | textDisplay.childNodes[currentWord].classList.add("wrong"); 547 | } 548 | 549 | textDisplay.childNodes[currentWord + 1].classList.add("highlight"); 550 | } else if (currentWord === currentWordsList.length - 1) { 551 | textDisplay.childNodes[currentWord].classList.add("wrong"); 552 | showResult(); 553 | } 554 | 555 | inputField.value = ""; 556 | currentWord++; 557 | } 558 | 559 | // Else if it is the last word and input word is correct show the result 560 | } else if (currentWord === currentWordsList.length - 1) { 561 | if (inputField.value + e.key === currentWordsList[currentWord]) { 562 | textDisplay.childNodes[currentWord].classList.add("correct"); 563 | 564 | correctKeys += currentWordsList[currentWord].length; 565 | currentWord++; 566 | showResult(); 567 | } 568 | } 569 | }); 570 | 571 | // Command center actions 572 | document.querySelector("#wc-15").addEventListener("click", () => { 573 | setWordCount(15); 574 | setText(); 575 | showText(); 576 | }); 577 | document.querySelector("#wc-30").addEventListener("click", () => { 578 | setWordCount(30); 579 | setText(); 580 | showText(); 581 | }); 582 | document.querySelector("#wc-60").addEventListener("click", () => { 583 | setWordCount(60); 584 | setText(); 585 | showText(); 586 | }); 587 | document.querySelector("#wc-120").addEventListener("click", () => { 588 | setWordCount(120); 589 | setText(); 590 | showText(); 591 | }); 592 | document.querySelector("#wc-240").addEventListener("click", () => { 593 | setWordCount(240); 594 | setText(); 595 | showText(); 596 | }); 597 | document.querySelector("#tc-15").addEventListener("click", () => { 598 | setTimeCount(15); 599 | setText(); 600 | showText(); 601 | }); 602 | document.querySelector("#tc-30").addEventListener("click", () => { 603 | setTimeCount(30); 604 | setText(); 605 | showText(); 606 | }); 607 | document.querySelector("#tc-60").addEventListener("click", () => { 608 | setTimeCount(60); 609 | setText(); 610 | showText(); 611 | }); 612 | document.querySelector("#tc-120").addEventListener("click", () => { 613 | setTimeCount(120); 614 | setText(); 615 | showText(); 616 | }); 617 | document.querySelector("#tc-240").addEventListener("click", () => { 618 | setTimeCount(240); 619 | setText(); 620 | showText(); 621 | }); 622 | 623 | // Function to add punctuation to a list of words 624 | function addPunctuations() { 625 | if (currentWordsList[0] !== undefined) { 626 | // Capitalize first word 627 | currentWordsList[0] = 628 | currentWordsList[0][0].toUpperCase() + currentWordsList[0].slice(1); 629 | 630 | // Add comma, fullstop, question mark, exclamation mark, semicolon. Capitalize the next word 631 | for (let i = 0; i < currentWordsList.length; i++) { 632 | const ran = Math.random(); 633 | 634 | if (i < currentWordsList.length - 1) { 635 | if (ran < 0.03) { 636 | currentWordsList[i] += ","; 637 | } else if (ran < 0.05) { 638 | currentWordsList[i] += "."; 639 | 640 | currentWordsList[i + 1] = 641 | currentWordsList[i + 1][0].toUpperCase() + 642 | currentWordsList[i + 1].slice(1); 643 | } else if (ran < 0.06) { 644 | currentWordsList[i] += "?"; 645 | 646 | currentWordsList[i + 1] = 647 | currentWordsList[i + 1][0].toUpperCase() + 648 | currentWordsList[i + 1].slice(1); 649 | } else if (ran < 0.07) { 650 | currentWordsList[i] += "!"; 651 | 652 | currentWordsList[i + 1] = 653 | currentWordsList[i + 1][0].toUpperCase() + 654 | currentWordsList[i + 1].slice(1); 655 | } else if (ran < 0.08) { 656 | currentWordsList[i] += ";"; 657 | } 658 | } 659 | } 660 | currentWordsList[currentWordsList.length - 1] += "."; 661 | } 662 | } 663 | 664 | // Functions to change language setting 665 | function setLanguage(lang) { 666 | selectedLanguageWords = extensionState.allWords[lang]; 667 | } 668 | 669 | // Function to change punctuation setting 670 | function setPunctuation(punct) { 671 | const punc = punct.toLowerCase(); 672 | if (punc === "true") { 673 | punctuation = true; 674 | } else if (punc === "false") { 675 | punctuation = false; 676 | } 677 | } 678 | 679 | // Function to change word count setting 680 | function setWordCount(wc) { 681 | wordCount = wc; 682 | document 683 | .querySelectorAll("#word-count > span") 684 | .forEach((e) => (e.style.borderBottom = "")); 685 | document.querySelector(`#wc-${wordCount}`).style.borderBottom = "2px solid"; 686 | 687 | // Change state 688 | vscode.setState({ ...extensionState, count: wordCount }); 689 | extensionState = vscode.getState(); 690 | 691 | // Send message to extension to update setting 692 | vscode.postMessage({ 693 | command: "changeCount", 694 | count: wordCount, 695 | }); 696 | } 697 | 698 | // Function to change time count setting 699 | function setTimeCount(tc) { 700 | timeCount = tc; 701 | document.querySelectorAll("#time-count > span").forEach((e) => { 702 | e.style.borderBottom = ""; 703 | e.innerHTML = e.id.substring(3, 6); 704 | }); 705 | document.querySelector(`#tc-${timeCount}`).style.borderBottom = "2px solid"; 706 | 707 | // Change state 708 | vscode.setState({ ...extensionState, count: timeCount }); 709 | extensionState = vscode.getState(); 710 | 711 | // Send message to extension to update setting 712 | vscode.postMessage({ 713 | command: "changeCount", 714 | count: timeCount, 715 | }); 716 | } 717 | 718 | //==================================================== 719 | // Code mode 720 | //==================================================== 721 | // Function to set new code snippet and reset states 722 | function setCodeText(e) { 723 | document.querySelector("#language-selected").innerHTML = 724 | extensionState.codeLanguage.charAt(0).toUpperCase() + 725 | extensionState.codeLanguage.slice(1); 726 | 727 | e = e || window.event; 728 | var keepWordList = e && e.shiftKey; 729 | 730 | // Change code snippet if shift key is not hit 731 | if (!keepWordList) { 732 | currentCode = 733 | selectedLanguageCodes[ 734 | Math.floor(Math.random() * selectedLanguageCodes.length) 735 | ]; 736 | } 737 | 738 | // Reset progress state 739 | clearTimeout(timer); 740 | gameOver = false; 741 | codeState = { 742 | firstChar: null, 743 | lastChar: null, 744 | currentChar: null, 745 | currentCharNum: 0, 746 | cursorLeftOffset: 0, 747 | cursorTopOffset: 0, 748 | linesLastCursorPositions: [], 749 | }; 750 | 751 | // Reset cursor position 752 | cursor.classList.remove("hidden"); 753 | updateCursorPosition(0, 0); 754 | 755 | return; 756 | } 757 | 758 | // Function to set selection as new code snippet and reset states 759 | function setSelectedCodeText(selectedCode, selectedLanguage) { 760 | // Change code snippet 761 | currentCode = selectedCode; 762 | document.querySelector("#language-selected").innerHTML = 763 | selectedLanguage.charAt(0).toUpperCase() + selectedLanguage.slice(1); 764 | 765 | // Reset progress state 766 | clearTimeout(timer); 767 | gameOver = false; 768 | codeState = { 769 | firstChar: null, 770 | lastChar: null, 771 | currentChar: null, 772 | currentCharNum: 0, 773 | cursorLeftOffset: 0, 774 | cursorTopOffset: 0, 775 | linesLastCursorPositions: [], 776 | }; 777 | 778 | // Reset cursor position 779 | cursor.classList.remove("hidden"); 780 | updateCursorPosition(0, 0); 781 | 782 | return; 783 | } 784 | 785 | // Function to show the code snippet in the dom 786 | function showCodeText(userSelectedLanguage) { 787 | if (userSelectedLanguage) { 788 | highlightCode(currentCode, userSelectedLanguage); 789 | } else { 790 | highlightCode(currentCode, selectedLanguageName); 791 | } 792 | 793 | // Update state with the correct characters 794 | updateStateChars(); 795 | 796 | // Focus into it 797 | document.getElementById("coding-area").focus(); 798 | return; 799 | } 800 | 801 | // Function to show end results for code snippets mode 802 | function showCodeResults() { 803 | let numberOfCharacters = document.querySelectorAll(".char").length; 804 | let numberOfCorrectTypings = document.querySelectorAll(".passed").length; 805 | 806 | let time = (Date.now() - codeStartDate) / 1000 / 60; 807 | let words = numberOfCorrectTypings / 5; 808 | 809 | let wpm = Math.floor(words / time); 810 | let acc = Math.floor((numberOfCorrectTypings / numberOfCharacters) * 100); 811 | 812 | document.querySelector( 813 | "#right-wing" 814 | ).innerHTML = `WPM: ${wpm} / ACC: ${acc}`; 815 | 816 | return; 817 | } 818 | 819 | // Retrieve cursor dimensions from css 820 | let cursorWidth = 0; 821 | let cursorHeight = 0; 822 | setTimeout(() => { 823 | // Gets the actual rendered width of the char 824 | cursorWidth = document 825 | .querySelectorAll(".char")[0] 826 | .getBoundingClientRect().width; 827 | cursorHeight = document 828 | .querySelectorAll(".char")[0] 829 | .getBoundingClientRect().height; 830 | }, 100); // Delay ensures that the data is read after rendering 831 | 832 | // Add event listeners for key presses 833 | document.addEventListener("keydown", (e) => handleKeyDown(e)); 834 | document.addEventListener("keypress", (e) => handleKeyPress(e)); 835 | 836 | // Function to set code language 837 | function setCodeLanguage(lang) { 838 | selectedLanguageName = lang; 839 | selectedLanguageCodes = allCodes[lang]; 840 | return; 841 | } 842 | 843 | // Function to update characters in the state 844 | function updateStateChars() { 845 | const toPassSymbols = document.getElementsByClassName("topass"); 846 | codeState = { 847 | ...codeState, 848 | firstChar: toPassSymbols[0], 849 | currentChar: toPassSymbols[0], 850 | lastChar: toPassSymbols[toPassSymbols.length - 1], 851 | }; 852 | } 853 | 854 | // Function that sets highlighted code in dom 855 | function highlightCode(codeSnippet, language) { 856 | let codeDiv = document.getElementById("code-code"); 857 | 858 | document.getElementById("code-pre").className = ""; 859 | document.getElementById("code-code").className = ""; 860 | 861 | codeDiv.classList.add(`language-${language}`); 862 | codeDiv.innerHTML = codeSnippet; 863 | 864 | Prism.highlightElement(codeDiv); 865 | 866 | codeDiv.innerHTML = cutCodeIntoPieces(codeDiv.innerHTML); 867 | } 868 | 869 | // Function that cuts highlighted code into spans of characters 870 | function cutCodeIntoPieces(highlightedCode) { 871 | const regexpTag = /(<\/?span.*?>)/; 872 | const tagsAndTextArr = highlightedCode.split(regexpTag); 873 | const regexpSpecialChar = /&[a-z]*;/; 874 | let codeToRender = ""; 875 | 876 | // Wrap code characters with 877 | for (let i = 0; i < tagsAndTextArr.length; i++) { 878 | // If text element, wrap each symbol with span 879 | if (tagsAndTextArr[i] !== "" && !regexpTag.test(tagsAndTextArr[i])) { 880 | let newHtml = ""; 881 | if (regexpSpecialChar.test(tagsAndTextArr[i])) { 882 | // Special characters 883 | const specialCharsArr = tagsAndTextArr[i].match(/&[a-z]*;/g); 884 | // If we have one special character without other symbols 885 | if ( 886 | specialCharsArr.length === 1 && 887 | specialCharsArr[0] === tagsAndTextArr[i] 888 | ) { 889 | newHtml += `${tagsAndTextArr[i]}`; 890 | // If we have a special character with other symbols 891 | } else { 892 | const otherCharsArr = tagsAndTextArr[i].split(regexpSpecialChar); 893 | for (let j = 0; j < otherCharsArr.length; j++) { 894 | if (otherCharsArr[j] === "" && j < specialCharsArr.length) { 895 | newHtml += `${specialCharsArr[0]}`; 896 | continue; 897 | } 898 | for (let k = 0; k < otherCharsArr[j].length; k++) { 899 | newHtml += `${otherCharsArr[j][k]}`; 900 | } 901 | if (j !== otherCharsArr.length - 1) { 902 | newHtml += `${specialCharsArr[0]}`; 903 | } 904 | } 905 | } 906 | } else { 907 | // Simple words and symbols 908 | for (let j = 0; j < tagsAndTextArr[i].length; j++) { 909 | newHtml += `${tagsAndTextArr[i][j]}`; 910 | } 911 | } 912 | tagsAndTextArr[i] = newHtml; 913 | } 914 | codeToRender += tagsAndTextArr[i]; 915 | } 916 | 917 | return codeToRender; 918 | } 919 | 920 | // Function that returns the next character to the cursor 921 | function getNextChar() { 922 | return document.getElementsByClassName("char")[ 923 | codeState.currentCharNum + 1 924 | ]; 925 | } 926 | 927 | // Function that returns the previous character to the cursor 928 | function getPrevChar() { 929 | return document.getElementsByClassName("char")[ 930 | codeState.currentCharNum - 1 931 | ]; 932 | } 933 | 934 | // Function that handles "tab" and "backspace" key presses 935 | function handleKeyDown(e) { 936 | if (gameOver) { 937 | e.submit(); 938 | } 939 | 940 | // If it's the first character, start timer 941 | if (codeState.currentCharNum === 0) { 942 | codeStartDate = Date.now(); 943 | } 944 | 945 | // Tab: move cursor further 946 | if (e.which === 9) { 947 | e.preventDefault(); 948 | const currentChar = codeState.currentChar; 949 | const currentCharCode = currentChar.innerText.charCodeAt(0); 950 | 951 | // If the current symbol is a tab character 952 | if (currentCharCode === 9) { 953 | handleKeyPress(e); 954 | } 955 | 956 | // If the current symbol is a tab consisting of spaces 957 | if (currentCharCode === 32) { 958 | // Count all next spaces 959 | let counter = 0; 960 | let summToAdd = 0; 961 | let currentEl = currentChar; 962 | 963 | // Calculate the distance to move the cursor and change classes of passed characters 964 | while (currentEl.innerText.charCodeAt(0) === 32) { 965 | summToAdd += cursorWidth; 966 | currentEl.classList.remove("topass"); 967 | currentEl.classList.add("passed"); 968 | currentEl = currentEl.nextElementSibling; 969 | counter++; 970 | } 971 | 972 | // Change state depending on how much spaces we have passed 973 | if (counter === 1) { 974 | // Single space just for space 975 | flashCursor(); 976 | } else { 977 | // Move cursor through spaces 978 | codeState = { 979 | ...codeState, 980 | currentCharNum: codeState.currentCharNum + (counter - 1), 981 | }; 982 | codeState = { 983 | ...codeState, 984 | cursorLeftOffset: codeState.cursorLeftOffset + summToAdd, 985 | currentChar: getNextChar(), 986 | currentCharNum: codeState.currentCharNum + 1, 987 | }; 988 | updateCursorPosition( 989 | codeState.cursorLeftOffset, 990 | codeState.cursorTopOffset 991 | ); 992 | } 993 | } 994 | } 995 | 996 | // Backspace: move cursor back 997 | if (e.key === "Backspace") { 998 | // If first element is reached, ignore 999 | if (codeState.currentChar === codeState.firstChar) { 1000 | flashCursor(); 1001 | return; 1002 | } 1003 | 1004 | // Else find out where we are and change state 1005 | const currentChar = getPrevChar(); 1006 | const currentCharCode = currentChar.innerText.charCodeAt(0); 1007 | 1008 | codeState = { ...codeState, currentChar: currentChar }; 1009 | currentChar.classList.remove("notpassed"); 1010 | currentChar.classList.add("topass"); 1011 | 1012 | // If we are at the beginning of the line, go to the previous line 1013 | if (currentCharCode === 10) { 1014 | const linesLastCursorPositions = codeState.linesLastCursorPositions; 1015 | 1016 | codeState = { 1017 | ...codeState, 1018 | cursorLeftOffset: linesLastCursorPositions.pop(), 1019 | cursorTopOffset: codeState.cursorTopOffset - cursorHeight, 1020 | linesLastCursorPositions, 1021 | currentCharNum: codeState.currentCharNum - 1, 1022 | }; 1023 | updateCursorPosition( 1024 | codeState.cursorLeftOffset, 1025 | codeState.cursorTopOffset 1026 | ); 1027 | 1028 | return; 1029 | } 1030 | 1031 | // If it's the same line, go one back 1032 | codeState = { 1033 | ...codeState, 1034 | cursorLeftOffset: codeState.cursorLeftOffset - cursorWidth, 1035 | currentCharNum: codeState.currentCharNum - 1, 1036 | }; 1037 | updateCursorPosition( 1038 | codeState.cursorLeftOffset, 1039 | codeState.cursorTopOffset 1040 | ); 1041 | } 1042 | } 1043 | 1044 | // Function that handles all the other key presses 1045 | function handleKeyPress(e) { 1046 | if (gameOver) { 1047 | e.submit(); 1048 | } 1049 | 1050 | // Other keys: change state depending on the key pressed 1051 | e.preventDefault(); 1052 | 1053 | const currentChar = codeState.currentChar; 1054 | const typedSymbolCode = e.which; 1055 | const currentCharCode = currentChar.innerText.charCodeAt(0); 1056 | 1057 | // If the current symbol is a new line, do nothing if 'enter' not hit 1058 | if (currentCharCode === 10 && typedSymbolCode !== 13) { 1059 | flashCursor(); 1060 | return; 1061 | } 1062 | 1063 | // If the current symbol is not new line, do nothing if 'enter' pressed 1064 | if (currentCharCode !== 10 && typedSymbolCode === 13) { 1065 | flashCursor(); 1066 | return; 1067 | } 1068 | 1069 | // Change classes of passed characters 1070 | currentChar.classList.remove("topass"); 1071 | 1072 | // Change class depending if you typed correct or wrong 1073 | if (typedSymbolCode === currentCharCode) { 1074 | currentChar.classList.add("passed"); 1075 | } else { 1076 | currentChar.classList.add("notpassed"); 1077 | } 1078 | 1079 | // If last symbol reached, hide cursor and show stats 1080 | if (codeState.currentChar === codeState.lastChar) { 1081 | cursor.classList.add("hidden"); 1082 | 1083 | showCodeResults(); 1084 | gameOver = true; 1085 | return; 1086 | } 1087 | 1088 | // Else, get next symbol and set it as current 1089 | const next = getNextChar(); 1090 | codeState = { ...codeState, currentChar: next }; 1091 | 1092 | // Moving the cursor to the next position 1093 | 1094 | // If it's new line 1095 | if (currentCharCode === 10 && typedSymbolCode === 13) { 1096 | const linesLastCursorPositions = codeState.linesLastCursorPositions; 1097 | linesLastCursorPositions.push(codeState.cursorLeftOffset); 1098 | codeState = { 1099 | ...codeState, 1100 | cursorLeftOffset: 0, 1101 | cursorTopOffset: codeState.cursorTopOffset + cursorHeight, 1102 | linesLastCursorPositions, 1103 | currentCharNum: codeState.currentCharNum + 1, 1104 | }; 1105 | updateCursorPosition( 1106 | codeState.cursorLeftOffset, 1107 | codeState.cursorTopOffset 1108 | ); 1109 | 1110 | return; 1111 | } 1112 | 1113 | // If tab symbol is reached 1114 | if (currentCharCode === 9) { 1115 | codeState = { 1116 | ...codeState, 1117 | cursorLeftOffset: codeState.cursorLeftOffset + cursorWidth, 1118 | currentCharNum: codeState.currentCharNum + 1, 1119 | }; 1120 | updateCursorPosition( 1121 | codeState.cursorLeftOffset, 1122 | codeState.cursorTopOffset 1123 | ); 1124 | 1125 | return; 1126 | } 1127 | 1128 | // If it's the same line 1129 | codeState = { 1130 | ...codeState, 1131 | cursorLeftOffset: codeState.cursorLeftOffset + cursorWidth, 1132 | currentCharNum: codeState.currentCharNum + 1, 1133 | }; 1134 | updateCursorPosition(codeState.cursorLeftOffset, codeState.cursorTopOffset); 1135 | } 1136 | 1137 | // Function to update cursor position in the dom 1138 | function updateCursorPosition(left, top) { 1139 | cursor.style.left = `${left}px `; 1140 | cursor.style.top = `${top}px`; 1141 | cursor.scrollIntoView({ 1142 | behavior: "smooth", 1143 | block: "center", 1144 | inline: "center", 1145 | }); 1146 | } 1147 | 1148 | // Function to visually flash the cursor 1149 | function flashCursor() { 1150 | cursor.style.background = "#e0556170"; 1151 | setTimeout(() => { 1152 | cursor.style.background = "#5dbeff"; 1153 | }, 100); 1154 | } 1155 | })(); 1156 | --------------------------------------------------------------------------------