├── .circleci
└── config.yml
├── .eslintrc.json
├── .github
└── FUNDING.yml
├── .gitignore
├── .vscodeignore
├── CHANGELOG.md
├── README.md
├── demo
├── activityBar.png
├── capture.gif
├── copy.gif
├── draw.gif
├── save.gif
├── screenify.gif
├── screenify1080.gif
└── upload.gif
├── jsconfig.json
├── package-lock.json
├── package.json
├── resources
├── continue.png
├── github.png
├── heart.png
├── icon-big old1.png
├── icon-big old2.png
├── icon-big old4.png
├── icon-big.png
├── icon-big2.png
├── icon-bigold10.png
├── icon-bigold3.png
├── icon-bigold5.png
├── icon-bigold6.png
├── icon-bigold7.png
├── icon-bigold8.png
├── icon-bigold9.png
├── icon-heart.svg
├── icon.png
├── market-icon.png
├── new-icon.png
├── question.png
└── twitter.svg
├── src
├── extension.js
└── utils.js
├── test
├── runTest.js
└── suite
│ ├── extension.test.js
│ └── index.js
├── vsc-extension-quickstart.md
└── webview
├── assets
├── clear-symbol.png
├── copy.png
├── line.png
├── paint-brush.png
├── rounded-rectangle-stroked.png
├── undo.png
└── upload-to-cloud.png
├── html2canvas.js
├── index.html
├── index.js
├── paint.js
├── pickr.js
├── pointer.js
├── styles.css
└── vivus.js
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # version: 2.1
2 | # orbs:
3 | # node: circleci/node@1.1.6
4 | # jobs:
5 | # build-and-test:
6 | # executor:
7 | # name: node/default
8 | # steps:
9 | # - checkout
10 | # - node/with-cache:
11 | # steps:
12 | # - run: npm install
13 | # - test:
14 | version: 2
15 | jobs:
16 | build:
17 | docker:
18 | - image: circleci/node:10.15
19 | steps:
20 | - checkout
21 | - run:
22 | name: install-npm
23 | command: npm install
24 |
25 | - save_cache:
26 | key: dependency-cache-{{ checksum "package.json" }}
27 | paths:
28 | - ./node_modules
29 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es6": true,
6 | // "dom": true,
7 | "node": true,
8 | "mocha": true,
9 | "webextensions": true
10 |
11 | },
12 | "parserOptions": {
13 | "ecmaVersion": 2018,
14 | "ecmaFeatures": {
15 | "jsx": true
16 | },
17 | "sourceType": "module"
18 | },
19 | "rules": {
20 | "no-const-assign": "off",
21 | "no-this-before-super": "off",
22 | "no-undef": "warn",
23 | "no-unreachable": "warn",
24 | "no-unused-vars": "warn",
25 | "constructor-super": "warn",
26 | "valid-typeof": "warn"
27 | }
28 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | patreon: adammomen
4 | open_collective: adam-momen
5 | ko_fi: # Replace with a single Ko-fi username
6 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
7 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
8 | liberapay: # Replace with a single Liberapay username
9 | issuehunt: # Replace with a single IssueHunt username
10 | otechie: # Replace with a single Otechie username
11 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .history
2 | .vscode-test
3 | *.vsix
4 | .DS_Store
5 | npm-debug.log
6 | Thumbs.db
7 | */node_modules/
8 | */out/
9 | */.vs/
10 | node_modules
11 | .vscode
12 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | test/**
4 | .gitignore
5 | vsc-extension-quickstart.md
6 | **/jsconfig.json
7 | **/*.map
8 | **/.eslintrc.json
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to the "screenify" extension will be documented in this file.
4 |
5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
6 |
7 | ## [Unreleased]
8 |
9 | - Initial release
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Screenify
8 |
9 |
10 |
11 | Screenify your code
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | ## Features
28 |
29 | - Quickly save screenshots of your code
30 | - Copy screenshots to your clipboard
31 | - Draw over your screenshot.
32 | - Upload your image online.
33 |
34 | ## Getting Started
35 |
36 | -
Linux `Ctrl+Shift+S`
37 | -
macOS `⌘Shift+S`
38 | -
Windows `Ctrl+Shift+S`
39 |
40 | **Tips**:
41 |
42 | - You can also start secreenify by clicking on the camera icon 📸 on the statusbar.
43 | - Default key binding to start Screenify is `Ctrl+Shift+S` or `⌘Shift+S` If you'd like to bind screenify to another hotkey, open up your keyboard shortcut settings and bind `screenify.activate` to a custom keybinding.
44 |
45 | - If you'd like to copy to clipboard instead of saving, click the image and press the copy keyboard shortcut (defaults are Ctrl+C on Windows and Linux, Cmd+C on OS X).
46 |
47 | ## Examples
48 |
49 | [Nord](https://github.com/arcticicestudio/nord-visual-studio-code) + [Input Mono](http://input.fontbureau.com)
50 |
51 | 
52 |
53 | [Monokai Pro](https://marketplace.visualstudio.com/items?itemName=monokai.theme-monokai-pro-vscode) + [Operator Mono](https://www.typography.com/blog/introducing-operator)
54 |
55 | 
56 |
57 | [Material Theme Palenight](https://marketplace.visualstudio.com/items?itemName=Equinusocio.vsc-material-theme) + [Fira Code](https://github.com/tonsky/FiraCode)
58 |
59 | 
60 |
61 | ## Demo
62 |
63 | ### *Share Your Code online*
64 |
65 | 
66 |
67 | Share your code snippets online, screenify uploads your images that you can share the image url with others.
68 |
69 | ### *Save your captured snippet on your local directory*
70 |
71 | 
72 |
73 | Save your code as an image on your local machine directory.
74 |
75 | ## Known Issues
76 |
77 | >Note: drawing experiense is stil little bit laggy and not smooth and it's still in development.
78 |
79 | ## Tip
80 |
81 | - When running out of horizontal space, try the command `View: Toggle Editor Group Vertical/Horizontal Layout`.
82 |
83 | ## Credit
84 |
85 | Thanks to the great Polacode.
86 |
87 | Many color are taken from the elegant [Nord](https://github.com/arcticicestudio/nord) theme by [@arcticicestudio](https://github.com/arcticicestudio).
88 |
89 | Download button animation is made with [Vivus](https://github.com/maxwellito/vivus).
90 |
91 | Special Thanks to [SougCrypto](https://github.com/Soug-crypto) for helping out with desgin concepts of the MVP.
92 |
93 | ## Contributing
94 |
95 | Please, report issues/bugs and suggestions for improvements to the issue [here](https://github.com/screenify/screenify-vscode/issues).
96 |
97 | We're not users of Light versions so we need help to make light versions better. Please contribute if you have any suggestions. **PRs are welcomed!** :rocket:
98 |
99 | Copyright (C) 2020 by [AM](https://github.com/adammomen)
100 | -----------------------------------------------------------------------------------------------------------
101 | ***Enjoy! Screenifying 📸***
102 |
--------------------------------------------------------------------------------
/demo/activityBar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/demo/activityBar.png
--------------------------------------------------------------------------------
/demo/capture.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/demo/capture.gif
--------------------------------------------------------------------------------
/demo/copy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/demo/copy.gif
--------------------------------------------------------------------------------
/demo/draw.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/demo/draw.gif
--------------------------------------------------------------------------------
/demo/save.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/demo/save.gif
--------------------------------------------------------------------------------
/demo/screenify.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/demo/screenify.gif
--------------------------------------------------------------------------------
/demo/screenify1080.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/demo/screenify1080.gif
--------------------------------------------------------------------------------
/demo/upload.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/demo/upload.gif
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "lib": [
5 | // "dom", "dom.iterable",
6 | "esnext", "es2016", "esnext.asynciterable"
7 | ],
8 | "target": "es6",
9 | "moduleResolution": "node",
10 | "checkJs": false,
11 | /* Typecheck .js files. */
12 | },
13 | "exclude": [
14 | "node_modules",
15 | "**/node_modules/*"
16 | ]
17 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "screenify",
3 | "displayName": "Screenify",
4 | "description": "Capture your code snippets and turn them into canvas for drawing, then share them others by uploading the image to screenify CDN server.",
5 | "version": "0.0.4",
6 | "engines": {
7 | "vscode": "^1.42.0"
8 | },
9 | "keywords": [
10 | "screenshot",
11 | "snippet",
12 | "snap",
13 | "clipboard",
14 | "share",
15 | "polacode",
16 | "carbon",
17 | "codesnap",
18 | "upload"
19 | ],
20 | "categories": [
21 | "Other"
22 | ],
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/screenify/screenify-vscode.git"
26 | },
27 | "publisher": "AdamMomen",
28 | "galleryBanner": {
29 | "color": "#fbfbfb",
30 | "theme": "light"
31 | },
32 | "icon": "resources/icon-big.png",
33 | "activationEvents": [
34 | "onCommand:screenify.activate",
35 | "onWebviewPanel:screenify",
36 | "onView:help"
37 | ],
38 | "main": "./src/extension",
39 | "contributes": {
40 | "viewsContainers": {
41 | "activitybar": [{
42 | "id": "screenify",
43 | "title": "Screenify",
44 | "icon": "resources/icon-big.png"
45 | }]
46 | },
47 | "views": {
48 | "screenify": [{
49 | "id": "gettingStarted",
50 | "name": "Getting Started"
51 | },
52 | {
53 | "id": "help",
54 | "name": "More"
55 | }
56 | ]
57 | },
58 | "menu": {
59 | "view/item/context": [{
60 | "command": "help.openUri",
61 | "when": "view == help && viewItem == openUrl",
62 | "group": "inline"
63 | }],
64 | "view/title": [{
65 | "command": "screenify.supportScreenify",
66 | "when": "view == help",
67 | "group": "navigation"
68 | }]
69 | },
70 | "viewsWelcome": [{
71 | "view": "gettingStarted",
72 | "contents": "Click on 📸 to start using screenify:\n[📸](command:screenify.activate)"
73 | }],
74 | "commands": [{
75 | "command": "screenify.activate",
76 | "title": "Screenify 📸"
77 | },
78 | {
79 | "command": "help.openUri",
80 | "title": "Open Uri ❤"
81 | }
82 | ],
83 | "keybindings": [{
84 | "command": "screenify.activate",
85 | "key": "ctrl+shift+s",
86 | "mac": "cmd+shift+s"
87 | }],
88 | "configuration": {
89 | "title": "Screenify",
90 | "properties": {
91 | "screenify.shadow": {
92 | "type": "string",
93 | "description": "Shadow of the snippet node. Use any value for CSS `box-shadow`",
94 | "default": "rgba(0, 0, 0, 0.55) 0px 20px 68px"
95 | },
96 | "screenify.transparentBackground": {
97 | "type": "boolean",
98 | "description": "Transparent background for containers",
99 | "default": false
100 | },
101 | "screenify.backgroundColor": {
102 | "type": "string",
103 | "description": "Background color of snippet container. Use any value for CSS `background-color`",
104 | "format": "color-hex",
105 | "default": "#f2f2f2"
106 | },
107 | "screenify.target": {
108 | "type": "string",
109 | "description": "Shoot with or without container",
110 | "default": "container",
111 | "enum": [
112 | "container",
113 | "snippet"
114 | ],
115 | "enumDescriptions": [
116 | "Shoot with the container.",
117 | "Shoot with the snippet alone. If you want transparent padding, use `container` with `\"screenify.transparentBackground\": true`"
118 | ]
119 | },
120 | "screenify.serverUrl": {
121 | "type": "string",
122 | "description": "`",
123 | "default": "screenify-njy7ok457q-ez.a.run.app"
124 | }
125 | }
126 | }
127 | },
128 | "scripts": {
129 | "lint": "eslint .",
130 | "pretest": "npm run lint",
131 | "test": "node ./test/runTest.js"
132 | },
133 | "devDependencies": {
134 | "@types/glob": "^7.1.1",
135 | "@types/mocha": "^7.0.1",
136 | "@types/node": "^12.11.7",
137 | "@types/vscode": "^1.42.0",
138 | "electron": "file:node_modules/electron",
139 | "eslint": "*",
140 | "glob": "^7.1.6",
141 | "mocha": "^7.0.1",
142 | "typescript": "^3.7.5",
143 | "vscode-test": "^1.3.0"
144 | },
145 | "dependencies": {
146 | "bluebird": "^3.7.2",
147 | "fs": "0.0.1-security",
148 | "img-clipboard": "^1.0.4",
149 | "node-fetch": "^3.1.1",
150 | "node-powershell": "^4.0.0",
151 | "os": "^0.1.1",
152 | "path": "^0.12.7",
153 | "randomstring": "^1.1.5"
154 | }
155 | }
--------------------------------------------------------------------------------
/resources/continue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/continue.png
--------------------------------------------------------------------------------
/resources/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/github.png
--------------------------------------------------------------------------------
/resources/heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/heart.png
--------------------------------------------------------------------------------
/resources/icon-big old1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/icon-big old1.png
--------------------------------------------------------------------------------
/resources/icon-big old2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/icon-big old2.png
--------------------------------------------------------------------------------
/resources/icon-big old4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/icon-big old4.png
--------------------------------------------------------------------------------
/resources/icon-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/icon-big.png
--------------------------------------------------------------------------------
/resources/icon-big2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/icon-big2.png
--------------------------------------------------------------------------------
/resources/icon-bigold10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/icon-bigold10.png
--------------------------------------------------------------------------------
/resources/icon-bigold3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/icon-bigold3.png
--------------------------------------------------------------------------------
/resources/icon-bigold5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/icon-bigold5.png
--------------------------------------------------------------------------------
/resources/icon-bigold6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/icon-bigold6.png
--------------------------------------------------------------------------------
/resources/icon-bigold7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/icon-bigold7.png
--------------------------------------------------------------------------------
/resources/icon-bigold8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/icon-bigold8.png
--------------------------------------------------------------------------------
/resources/icon-bigold9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/icon-bigold9.png
--------------------------------------------------------------------------------
/resources/icon-heart.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/icon.png
--------------------------------------------------------------------------------
/resources/market-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/market-icon.png
--------------------------------------------------------------------------------
/resources/new-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/new-icon.png
--------------------------------------------------------------------------------
/resources/question.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/resources/question.png
--------------------------------------------------------------------------------
/resources/twitter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/extension.js:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright(c) Screenify📸.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 | const vscode = require("vscode"),
6 | fs = require("fs"),
7 | path = require("path"),
8 | os = require("os"),
9 | P_TITLE = "Screenify 📸",
10 | fetch = require("node-fetch"),
11 | Bluebird = require("bluebird"),
12 | {
13 | copyImg,
14 | } = require("img-clipboard"),
15 | {
16 | readHtml,
17 | } = require("./utils");
18 | fetch.Promise = Bluebird;
19 |
20 | /**
21 | * @param {vscode.ExtensionContext} context
22 | * Extension Acativation
23 | **/
24 | function activate(context) {
25 | const {
26 | subscriptions,
27 | } = context;
28 |
29 | /** Status Bar configuration **/
30 | statusBarItem = vscode.window.createStatusBarItem(
31 | vscode.StatusBarAlignment.Right,
32 | 100,
33 | );
34 | statusBarItem.command = "screenify.activate";
35 | statusBarItem.text = `$(device-camera) Screenify`;
36 | statusBarItem.tooltip = "Capture Code Snippet";
37 | statusBarItem.show();
38 | subscriptions.push(statusBarItem);
39 |
40 | /** @class HelpDataProvider
41 | * Tree institation for view container tree items
42 | **/
43 | class HelpDataProvider {
44 | constructor() {
45 | this.data = [
46 | new TreeItem(
47 | "Give me your feedback",
48 | "twitter.svg",
49 | "https://twitter.com/adammuman81",
50 | ),
51 | new TreeItem(
52 | "Report an issue",
53 | "github.png",
54 | "https://github.com/AdamMomen/screenify-vscode/issues",
55 | ),
56 | new TreeItem(
57 | "Support",
58 | "icon-heart.svg",
59 | "https://www.patreon.com/adammomen",
60 | ),
61 | ];
62 | }
63 | getTreeItem(element) {
64 | return element;
65 | }
66 |
67 | getChildren(element = undefined) {
68 | if (element === undefined) {
69 | return this.data;
70 | }
71 | return element.children;
72 | }
73 | }
74 |
75 | /** @class GettingStartedDataProvider
76 | * Tree institation for view container tree items
77 | **/
78 | class GettingStartedDataProvider {
79 | constructor() {
80 | this.data = [
81 | new TreeItem("Start Screenify 📸", "", "", {
82 | title: "Start Screenify",
83 | command: "screenify.activate",
84 | context: "start",
85 | }),
86 | ];
87 | }
88 | getTreeItem(element) {
89 | return element;
90 | }
91 |
92 | getChildren(element = undefined) {
93 | if (element === undefined) {
94 | return this.data;
95 | }
96 | return element.children;
97 | }
98 | }
99 | class TreeItem extends vscode.TreeItem {
100 | constructor(label, icon, uri, cmd = {
101 | title: "Open Uri",
102 | command: "help.openUri",
103 | context: "openUrl",
104 | }) {
105 | super(label, vscode.TreeItemCollapsibleState.None);
106 | this.iconPath = path.join(context.extensionPath, "resources", icon);
107 | this.command = {
108 | title: cmd.title,
109 | command: cmd.command,
110 | arguments: [uri],
111 | };
112 | this._uri = uri;
113 | this.contextValue = cmd.context;
114 | }
115 | }
116 | /** Register Tree Data provider **/
117 | vscode.window.registerTreeDataProvider("help", new HelpDataProvider());
118 |
119 | /** Register Tree Data provider **/
120 | vscode.window.registerTreeDataProvider(
121 | "gettingStarted",
122 | new GettingStartedDataProvider(),
123 | );
124 |
125 | /** Register command**/
126 | vscode.commands.registerCommand("help.openUri", (node) => {
127 | vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(node));
128 | });
129 |
130 | /** Path to Html file **/
131 | const htmlPath = path.resolve(context.extensionPath, "webview/index.html");
132 |
133 | /** Path to the last saved image **/
134 | let lastUsedImageUri = vscode.Uri.file(
135 | path.resolve(os.homedir(), "Desktop/code.png"),
136 | );
137 | let panel;
138 |
139 | /** Regiseter Webview Pannl Serializer **/
140 | vscode.window.registerWebviewPanelSerializer(
141 | "screenify", {
142 | async deserializeWebviewPanel(_panel, state) {
143 | panel = _panel;
144 | panel.webview.html = await readHtml(htmlPath, _panel);
145 | panel.webview.postMessage({
146 | type: "restore",
147 | innerHTML: state.innerHTML,
148 | bgColor: context.globalState.get("screenify.bgColor", "#2e3440"),
149 | });
150 | const selectionListener = setupSelectionSync();
151 | panel.onDidDispose(() => {
152 | selectionListener.dispose();
153 | });
154 | setupMessageListeners();
155 | },
156 | },
157 | );
158 |
159 | /** Regiseter Screenify Acitivation Command **/
160 | vscode.commands.registerCommand("screenify.activate", async() => {
161 | /** Show welcome inforamation message **/
162 | vscode.window.showInformationMessage(
163 | "Screenify is enabled and running, happy shooting 📸 😊 ",
164 | );
165 |
166 | /** Creates Webview Panel **/
167 | panel = vscode.window.createWebviewPanel("screenify", P_TITLE, 2, {
168 | enableScripts: true,
169 | localResourceRoots: [
170 | vscode.Uri.file(path.join(context.extensionPath, "webview")),
171 | ],
172 | });
173 |
174 | /** Set webview Html content from the html path file **/
175 | panel.webview.html = await readHtml(htmlPath, panel);
176 |
177 | /** Selcetion Listener **/
178 | const selectionListener = setupSelectionSync();
179 | panel.onDidDispose(() => {
180 | selectionListener.dispose();
181 | });
182 |
183 | setupMessageListeners();
184 |
185 | /** Get fontFamily from the editor configuration **/
186 | const fontFamily = vscode.workspace.getConfiguration("editor").fontFamily;
187 |
188 | /** Get bgColor and if NULL set it to #2e3440 **/
189 | const bgColor = context.globalState.get("screenify.bgColor", "#2e3440");
190 |
191 | /** Post resquest to webview **/
192 | panel.webview.postMessage({
193 | type: "init",
194 | fontFamily,
195 | bgColor,
196 | });
197 | syncSettings();
198 | });
199 |
200 | /** Syncs Updates **/
201 | vscode.workspace.onDidChangeConfiguration((e) => {
202 | if (
203 | e.affectsConfiguration("screenify") || e.affectsConfiguration("editor")
204 | ) {
205 | syncSettings();
206 | }
207 | });
208 |
209 | /**
210 | * Copyies serial blob to the clipboard or uploads the blob to CDN uploaders
211 | * @param {Blob} serializedBlobHandler
212 | * @return {Promise}
213 | */
214 | function serializedBlobHandler(serializeBlob, isUpload) {
215 | /** if blob is undefined */
216 | if (!serializeBlob) return;
217 |
218 | /** Convert Serialize Blob to array of butes **/
219 | const bytes = new Uint8Array(serializeBlob.split(","));
220 |
221 | /** uploads state is true, then uploads the blob **/
222 | if (isUpload) return upload(serializeBlob);
223 |
224 | /** else it will copy the blob to clipboard **/
225 | return copyImg(Buffer.from(bytes));
226 | }
227 |
228 | /**
229 | * Saves blob to into file
230 | * @param {Blob} serializeBlob
231 | * @return {Promise}
232 | */
233 | function writeSerializedBlobToFile(serializeBlob, fileName) {
234 | /** Convert Serialize Blob to array of butes **/
235 | const bytes = new Uint8Array(serializeBlob.split(","));
236 |
237 | /** write buffer into file **/
238 | fs.writeFileSync(fileName, Buffer.from(bytes));
239 | }
240 |
241 | function setupMessageListeners() {
242 | panel.webview.onDidReceiveMessage(({
243 | type,
244 | data,
245 | }) => {
246 | switch (type) {
247 | /** Save the image locally **/
248 | case "shoot":
249 | vscode.window
250 | .showSaveDialog({
251 | defaultUri: lastUsedImageUri,
252 | filters: {
253 | Images: ["png"],
254 | },
255 | })
256 | .then((uri) => {
257 | if (uri) {
258 | writeSerializedBlobToFile(data.serializedBlob, uri.fsPath);
259 | vscode.window.showInformationMessage("Snippet saved ✅");
260 | lastUsedImageUri = uri;
261 | }
262 | });
263 | break;
264 |
265 | /** Copy image to the clipboard **/
266 |
267 | case "copy":
268 | serializedBlobHandler(data.serializedBlob, data.upload)
269 | .then(() => {
270 | vscode.window.showInformationMessage(
271 | "Snippet copied! 📋 ctrl + V to paste",
272 | "Close",
273 | );
274 | })
275 | .catch((err) => {
276 | vscode.window.showErrorMessage(
277 | `Ops! Something went wrong! ❌: ${err}`,
278 | "Close",
279 | );
280 | });
281 | break;
282 |
283 | /** Updates Cache Settings **/
284 |
285 | case "getAndUpdateCacheAndSettings":
286 | panel.webview.postMessage({
287 | type: "restoreBgColor",
288 | bgColor: context.globalState.get("screenify.bgColor", "#2e3440"),
289 | });
290 |
291 | syncSettings();
292 | break;
293 | case "updateBgColor":
294 | context.globalState.update("screenify.bgColor", data.bgColor);
295 | break;
296 | case "invalidPasteContent":
297 | vscode.window.showInformationMessage(
298 | "Pasted content is invalid. Only copy from VS Code and check if your shortcuts for copy/paste have conflicts.",
299 | );
300 | break;
301 | }
302 | });
303 | }
304 |
305 | function syncSettings() {
306 | const settings = vscode.workspace.getConfiguration("screenify");
307 | const editorSettings = vscode.workspace.getConfiguration("editor", null);
308 | panel.webview.postMessage({
309 | type: "updateSettings",
310 | shadow: settings.get("shadow"),
311 | transparentBackground: settings.get("transparentBackground"),
312 | backgroundColor: settings.get("backgroundColor"),
313 | target: settings.get("target"),
314 | ligature: editorSettings.get("fontLigatures"),
315 | });
316 | }
317 |
318 | function setupSelectionSync() {
319 | return vscode.window.onDidChangeTextEditorSelection((e) => {
320 | if (e.selections[0] && !e.selections[0].isEmpty) {
321 | vscode.commands.executeCommand(
322 | "editor.action.clipboardCopyWithSyntaxHighlightingAction",
323 | );
324 | panel.postMessage({
325 | type: "update",
326 | });
327 | }
328 | });
329 | }
330 |
331 | /**
332 | * @function upload
333 | * @param {Buffer} image
334 | * @return {Promise}
335 | * Sends Http requset to screenify backend API to upload the image online.
336 | */
337 | function upload(buffer) {
338 | /** Server Url **/
339 | let serverUrl = `https://${settings.get("serverUrl")}/api/upload`;
340 |
341 | /** Show a progress loader... **/
342 | vscode.window.withProgress({
343 | location: 15,
344 | title: "Uploading Image...",
345 | }, (progress, token) => {
346 | token.onCancellationRequested(() => {
347 | return;
348 | });
349 |
350 | /** Sending POST requset send with image buffer as the body **/
351 | return fetch(serverUrl, {
352 | method: "POST",
353 | body: JSON.stringify({
354 | buffer,
355 | }),
356 | headers: {
357 | "Content-Type": "application/json",
358 | },
359 | });
360 | })
361 | /** convert the response into JSON **/
362 | .then((res) => res.json())
363 | .then((response) => {
364 | const {
365 | url,
366 | } = response;
367 |
368 | /** Copy url to the clipboard **/
369 | vscode.env.clipboard.writeText(url)
370 | .then(() => {
371 | /** Sends post message with the uploaded image url to webview api **/
372 | panel.webview.postMessage({
373 | type: "successfulUplaod",
374 | url,
375 | });
376 |
377 | /** Sucessful Upload info message **/
378 | vscode.window.showInformationMessage(
379 | `Snippet uploaded! ✅ Url is copied to the clipboard 📋: `,
380 | url,
381 | "Copy",
382 | );
383 | });
384 | })
385 | .catch((err) => {
386 | /** Error message **/
387 | vscode.window.showErrorMessage(
388 | `Ops! Something went wrong! ❌: ${err}`,
389 | "Close",
390 | );
391 | });
392 | }
393 | }
394 |
395 | /** Get Screenify settings **/
396 | const settings = vscode.workspace.getConfiguration("screenify");
397 |
398 | /** Extension Decativation **/
399 | function deactivate() {
400 | // TODO:complete implementing extension deactivation routine
401 | // #1 Clear cache
402 | // #2 Garbage collection
403 | }
404 |
405 | exports.activate = activate;
406 | exports.deactivate = deactivate;
407 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const vscode = require('vscode');
4 | const path = require('path');
5 | const {
6 | readFile,
7 | writeFile
8 | } = require('fs').promises;
9 |
10 | const readHtml = async (htmlPath, panel) =>
11 | (await readFile(htmlPath, 'utf-8'))
12 | .replace(/%CSP_SOURCE%/gu, panel.webview.cspSource)
13 | .replace(
14 | /(src|href)="([^"]*)"/gu,
15 | (_, type, src) =>
16 | `${type}="${panel.webview.asWebviewUri(
17 | vscode.Uri.file(path.resolve(htmlPath, '..', src))
18 | )}"`
19 | );
20 |
21 | const getSettings = (group, keys) => {
22 | const settings = vscode.workspace.getConfiguration(group, null);
23 | const editor = vscode.window.activeTextEditor;
24 | const language = editor && editor.document && editor.document.languageId;
25 | const languageSettings =
26 | language && vscode.workspace.getConfiguration(null, null).get(`[${language}]`);
27 | return keys.reduce((acc, k) => {
28 | acc[k] = languageSettings && languageSettings[`${group}.${k}`];
29 | if (acc[k] == null) acc[k] = settings.get(k);
30 | return acc;
31 | }, {});
32 | };
33 |
34 | module.exports = {
35 | readHtml,
36 | writeFile,
37 | getSettings
38 | };
--------------------------------------------------------------------------------
/test/runTest.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const {
4 | runTests
5 | } = require('vscode-test');
6 |
7 | async function main() {
8 | try {
9 | // The folder containing the Extension Manifest package.json
10 | // Passed to `--extensionDevelopmentPath`
11 | const extensionDevelopmentPath = path.resolve(__dirname, '../');
12 |
13 | // The path to the extension test script
14 | // Passed to --extensionTestsPath
15 | const extensionTestsPath = path.resolve(__dirname, './suite/index');
16 |
17 | // Download VS Code, unzip it and run the integration test
18 | await runTests({
19 | extensionDevelopmentPath,
20 | extensionTestsPath
21 | });
22 | } catch (err) {
23 | console.error('Failed to run tests');
24 | process.exit(1);
25 | }
26 | }
27 |
28 | main();
--------------------------------------------------------------------------------
/test/suite/extension.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | // You can import and use all API from the 'vscode' module
4 | // as well as import your extension to test it
5 | const vscode = require('vscode');
6 | // const screenify = require("../../src/extension")
7 | // const {
8 | // shootSnippet
9 | // } = require("../../webview/index")
10 | suite('Extension Test Suite', () => {
11 | vscode.window.showInformationMessage('Start all tests.');
12 |
13 | test("should be present", () => {
14 | assert.ok(vscode.extensions.getExtension("adammomen.screenify"));
15 | });
16 | test("should be able to register screenify commands", () => {
17 | return vscode.commands.getCommands(true).then((commands) => {
18 | const SCREENIFY_COMMANDS = [
19 | "editor.action.clipboardCopyWithSyntaxHighlightingAction",
20 | "getAndUpdateCacheAndSettings.action.getAndUpdateCacheAndSettings"
21 |
22 | ]
23 | const foundScreenifyCommands = commands.filter((value) => {
24 | return SCREENIFY_COMMANDS.indexOf(value) >= 0 || value.startsWith("screenify.");
25 | });
26 | const errorMsg = "Some screenify commands are not registered properly or a new command is not added to the test";
27 | assert.equal(foundScreenifyCommands.length, SCREENIFY_COMMANDS.length,
28 | errorMsg
29 | );
30 | });
31 | })
32 | })
--------------------------------------------------------------------------------
/test/suite/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const Mocha = require('mocha');
3 | const glob = require('glob');
4 |
5 | function run() {
6 | // Create the mocha test
7 | const mocha = new Mocha({
8 | ui: 'tdd'
9 | });
10 | // Use any mocha API
11 | mocha.useColors(true);
12 |
13 | const testsRoot = path.resolve(__dirname, '..');
14 |
15 | return new Promise((c, e) => {
16 | glob('**/**.test.js', {
17 | cwd: testsRoot
18 | }, (err, files) => {
19 | if (err) {
20 | return e(err);
21 | }
22 |
23 | // Add files to the test suite
24 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
25 |
26 | try {
27 | // Run the mocha test
28 | mocha.run(failures => {
29 | if (failures > 0) {
30 | e(new Error(`${failures} tests failed.`));
31 | } else {
32 | c();
33 | }
34 | });
35 | } catch (err) {
36 | console.error(err);
37 | e(err);
38 | }
39 | });
40 | });
41 | }
42 |
43 | module.exports = {
44 | run
45 | };
--------------------------------------------------------------------------------
/vsc-extension-quickstart.md:
--------------------------------------------------------------------------------
1 | # Welcome to your VS Code Extension
2 |
3 | ## What's in the folder
4 |
5 | * This folder contains all of the files necessary for your extension.
6 | * `package.json` - this is the manifest file in which you declare your extension and command.
7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin.
8 | * `extension.js` - this is the main file where you will provide the implementation of your command.
9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
11 |
12 | ## Get up and running straight away
13 |
14 | * Press `F5` to open a new window with your extension loaded.
15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
16 | * Set breakpoints in your code inside `extension.js` to debug your extension.
17 | * Find output from your extension in the debug console.
18 |
19 | ## Make changes
20 |
21 | * You can relaunch the extension from the debug toolbar after changing code in `extension.js`.
22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
23 |
24 | ## Explore the API
25 |
26 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
27 |
28 | ## Run tests
29 |
30 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`.
31 | * Press `F5` to run the tests in a new window with your extension loaded.
32 | * See the output of the test result in the debug console.
33 | * Make changes to `src/test/suite/extension.test.js` or create new test files inside the `test/suite` folder.
34 | * The provided test runner will only consider files matching the name pattern `**.test.ts`.
35 | * You can create folders inside the `test` folder to structure your tests any way you want.
36 | ## Go further
37 |
38 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace.
39 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
40 |
--------------------------------------------------------------------------------
/webview/assets/clear-symbol.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/webview/assets/clear-symbol.png
--------------------------------------------------------------------------------
/webview/assets/copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/webview/assets/copy.png
--------------------------------------------------------------------------------
/webview/assets/line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/webview/assets/line.png
--------------------------------------------------------------------------------
/webview/assets/paint-brush.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/webview/assets/paint-brush.png
--------------------------------------------------------------------------------
/webview/assets/rounded-rectangle-stroked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/webview/assets/rounded-rectangle-stroked.png
--------------------------------------------------------------------------------
/webview/assets/undo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/webview/assets/undo.png
--------------------------------------------------------------------------------
/webview/assets/upload-to-cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/screenify/screenify-vscode/4d20a05636d504b445d090ef18c9c26cac023183/webview/assets/upload-to-cloud.png
--------------------------------------------------------------------------------
/webview/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
34 |
35 |
36 |
37 |
38 |
...
x
39 | ...
40 |
41 |
42 |
43 |
44 |
45 |
52 |
53 |
55 |
56 | console.log('0. Run command `Screenify 📸 `
57 | ')
58 |
59 |
60 | console.log('1. Copy some code
61 | ')
62 |
63 |
64 | console.log('2. Paste into Screenify view
65 | ')
66 |
67 |
68 | console.log('3. Click the button 📸
69 | ')
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
98 |
SNAP !
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/webview/index.js:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Screenify.
3 | * Licensed under the MIT License
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | window.onload = function() {
7 | (function() {
8 |
9 | /** PointerJs initialization on page launch with init color of @Pickr color picker **/
10 | init_pointer({
11 | pointerColor: "#42445A"
12 | })
13 |
14 | /** Snippet Container Background Color */
15 | let backgroundColor = "#f2f2f2";
16 |
17 |
18 | /** vscode-api **/
19 | const vscode = acquireVsCodeApi(),
20 | oldState = vscode.getState(),
21 |
22 | /** Main Snippet Container **/
23 | snippetContainerNode = document.getElementById("snippet-container"),
24 |
25 | /** Snippet **/
26 | snippetNode = document.getElementById("snippet"),
27 |
28 | /** Snap Button **/
29 | obturateurLogo = document.getElementById("save_logo"),
30 |
31 | /** Drawing Canvas **/
32 | canvas = document.getElementById('my-canvas'),
33 |
34 | /** Canvas Context **/
35 | ctx = canvas.getContext('2d'),
36 |
37 | /** Brush icon Tool **/
38 | brush = document.getElementById("brush"),
39 |
40 | /** Line Tool **/
41 | line = document.getElementById("line"),
42 |
43 | /** Rectangle Tool **/
44 | rectangle = document.getElementById("rectangle"),
45 |
46 | /** Snippet Height Text **/
47 | snippetHeight = document.getElementById("snippetHeight"),
48 |
49 | /** Snippet Width Text **/
50 | snippetWidth = document.getElementById("snippetWidth"),
51 |
52 | /** Undo Tool **/
53 | undo = document.getElementById("undo"),
54 |
55 | /** Copy Tool **/
56 | copyBtn = document.getElementById("copy"),
57 | /** Upload Tool **/
58 | upload = document.getElementById("upload"),
59 |
60 | /** Uploaded Url Container **/
61 | uploadedUrlContainer = document.getElementById("upload-container"),
62 |
63 | /** clear tool **/
64 | clear = document.getElementById("clear"),
65 | flashFx = document.getElementById("flash-fx");
66 |
67 | const cameraFlashAnimation = async() => {
68 | flashFx.style.display = 'block';
69 | redraw(flashFx);
70 | flashFx.style.opacity = '0';
71 | await once(flashFx, 'transitionend');
72 | flashFx.style.display = 'none';
73 | flashFx.style.opacity = '1';
74 | };
75 | const redraw = node => node.clientHeight;
76 | const once = (elem, evt) =>
77 | new Promise(done => elem.addEventListener(evt, done, {
78 | once: true
79 | }));
80 |
81 | /** Changing toolbar color to different color
82 | * @Note TODO: Update toolbar color to vscode color theme. **/
83 | document.getElementsByClassName("toolbar")[0].style.backgroundColor = "#362b1b";
84 |
85 | /** Post a message to vscode api, update cache and settings. **/
86 | vscode.postMessage({
87 | type: "getAndUpdateCacheAndSettings"
88 | });
89 |
90 | /** Set @SnippetContainer node opacity to 1 **/
91 | snippetContainerNode.style.opacity = "1";
92 | if (oldState && oldState.innerHTML) {
93 | snippetNode.innerHTML = oldState.innerHTML;
94 | }
95 |
96 | /**
97 | * @function getInitialHtml
98 | * @param {String} fontFamily
99 | * Setup Custom Html with to vscode webview interface wtih font family.
100 | **/
101 | const getInitialHtml = fontFamily => {
102 | const cameraWithFlashEmoji = String.fromCodePoint(128248);
103 | const monoFontStack = `${fontFamily},SFMono-Regular,Consolas,DejaVu Sans Mono,Ubuntu Mono,Liberation Mono,Menlo,Courier,monospace`;
104 | return `console.log('0. Run command \`Screenify ${cameraWithFlashEmoji}\`')
console.log('1. Copy some code')
console.log('2. Paste into Screenify view')
console.log('3. Click the button ${cameraWithFlashEmoji}')
`;
105 | };
106 |
107 | /**
108 | * @function serializeBlob
109 | * @param {Blob} blob
110 | * @param {CallBack} cb
111 | * Converts a blob into serialized blob.
112 | **/
113 | const serializeBlob = (blob, cb) => {
114 | const fileReader = new FileReader();
115 |
116 | fileReader.onload = () => {
117 | const bytes = new Uint8Array(fileReader.result);
118 | cb(Array.from(bytes).join(","));
119 | };
120 | fileReader.readAsArrayBuffer(blob);
121 | };
122 |
123 | /**
124 | * @function shoot
125 | * @param {Blob} serializedBlob
126 | * Sends serializedBlob as post request to vscode api to save the blob locally.
127 | **/
128 | function shoot(serializedBlob) {
129 | cameraFlashAnimation()
130 | vscode.postMessage({
131 | type: "shoot",
132 | data: {
133 | serializedBlob
134 | }
135 | });
136 | }
137 |
138 | /**
139 | * @function copy
140 | * @param {Blob} serializedBlob
141 | * @param {Boolean} upload
142 | * Sends serializedBlob as post request to vscode api to either copy the blob to clipboard or updload the blob to online CDN.
143 | **/
144 | function copy(serializedBlob, upload = false) {
145 | cameraFlashAnimation()
146 | vscode.postMessage({
147 | type: "copy",
148 | data: {
149 | "serializedBlob": serializedBlob,
150 | "upload": upload,
151 | }
152 | });
153 | }
154 |
155 | /**
156 | * @function getBrightness
157 | * @param {String} hexColor
158 | * Converts hex color value into rgb value.
159 | **/
160 | function getBrightness(hexColor) {
161 | const rgb = parseInt(hexColor.slice(1), 16);
162 | const r = (rgb >> 16) & 0xff;
163 | const g = (rgb >> 8) & 0xff;
164 | const b = (rgb >> 0) & 0xff;
165 | return (r * 299 + g * 587 + b * 114) / 1000;
166 | }
167 |
168 | /**
169 | * @function isDark
170 | * @param {String} hexColor
171 | * checks if the color is dark.
172 | **/
173 | function isDark(hexColor) {
174 | return getBrightness(hexColor) < 128;
175 | }
176 |
177 | /**
178 | * @function getSnippetBgColor
179 | * @param {String} html
180 | * Gets snippet color from html string.
181 | **/
182 | function getSnippetBgColor(html) {
183 | const match = html.match(/background-color: (#[a-fA-F0-9]+)/);
184 | return match ? match[1] : undefined;
185 | }
186 |
187 | /**
188 | * @function updateEnvironment
189 | * @param {String} snippetBgColor
190 | * Updates the snippet background color
191 | **/
192 | function updateEnvironment(snippetBgColor) {
193 |
194 | /** update snippet bg color **/
195 | document.getElementById("snippet").style.backgroundColor = snippetBgColor;
196 |
197 | /** update backdrop color **/
198 | if (isDark(snippetBgColor)) {
199 |
200 | /** set background colorof snippet container to white #f2f2f2 **/
201 | snippetContainerNode.style.backgroundColor = "#f2f2f2";
202 | } else {
203 |
204 | /** set to none **/
205 | snippetContainerNode.style.background = "none";
206 | }
207 | }
208 |
209 | function getMinIndent(code) {
210 | const arr = code.split("\n");
211 |
212 | let minIndentCount = Number.MAX_VALUE;
213 | for (let i = 0; i < arr.length; i++) {
214 | const wsCount = arr[i].search(/\S/);
215 | if (wsCount !== -1) {
216 | if (wsCount < minIndentCount) {
217 | minIndentCount = wsCount;
218 | }
219 | }
220 | }
221 |
222 | return minIndentCount;
223 | }
224 |
225 | function stripInitialIndent(html, indent) {
226 | const doc = new DOMParser().parseFromString(html, "text/html");
227 | const initialSpans = doc.querySelectorAll("div > div span:first-child");
228 | for (let i = 0; i < initialSpans.length; i++) {
229 | initialSpans[i].textContent = initialSpans[i].textContent.slice(indent);
230 | }
231 | return doc.body.innerHTML;
232 | }
233 | /** On paste event, of user code captured in the snippet container **/
234 | document.addEventListener("paste", e => {
235 |
236 | /** clear the canvas on new incoming code snippet **/
237 | ctx.clearRect(0, 0, canvas.width, canvas.height);
238 | const innerHTML = e.clipboardData.getData("text/html");
239 |
240 | const code = e.clipboardData.getData("text/plain");
241 | const minIndent = getMinIndent(code);
242 |
243 | const snippetBgColor = getSnippetBgColor(innerHTML);
244 | if (snippetBgColor) {
245 | vscode.postMessage({
246 | type: "updateBgColor",
247 | data: {
248 | bgColor: snippetBgColor
249 | }
250 | });
251 | updateEnvironment(snippetBgColor);
252 |
253 | }
254 |
255 | if (minIndent !== 0) {
256 | snippetNode.innerHTML = stripInitialIndent(innerHTML, minIndent);
257 | } else {
258 | snippetNode.innerHTML = innerHTML;
259 | }
260 |
261 | vscode.setState({
262 | innerHTML
263 | });
264 | });
265 |
266 | /** Brush tool On Click Event Listener **/
267 | brush.addEventListener("click", () => {
268 | changeTool("brush")
269 | })
270 |
271 | /** Line tool On Click Event Listener **/
272 | line.addEventListener("click", () => {
273 | changeTool("line")
274 | })
275 |
276 | /** Rectangle tool On Click Event Listener **/
277 | rectangle.addEventListener("click", () => {
278 | changeTool("rectangle")
279 | })
280 | /** Undo tool On Click Event Listener **/
281 | undo.addEventListener("click", () => {
282 | restoreState()
283 | })
284 | /** CopyBtn tool On Click Event Listener **/
285 | copyBtn.addEventListener("click", () => {
286 | copyImage()
287 | })
288 |
289 | /** Upload tool On Click Event Listener **/
290 | upload.addEventListener("click", () => {
291 | uploadImage()
292 | })
293 |
294 | /** Clear tool On Click Event Listener **/
295 | clear.addEventListener("click", () => {
296 | ctx.clearRect(0, 0, canvas.width, canvas.height);
297 | // clear the undo array
298 | undo_array = []
299 | brushPoints = []
300 | currentState = 0;
301 | saveCanvasImage()
302 | redrawCanvasImage()
303 | })
304 |
305 | /** Snap button on click Event Listener **/
306 | obturateurLogo.addEventListener("click", () => {
307 | snippetHandler();
308 | })
309 |
310 | /** Snippet on Resize Event Observer **/
311 | const ro = new ResizeObserver((entries) => {
312 | for (let entry of entries) {
313 |
314 | /** Content Rectangular dimentions **/
315 | const cr = entry.contentRect;
316 | reactToContainerResize(cr.width, cr.height)
317 | }
318 | });
319 |
320 | /** Observe one or multiple elements **/
321 | ro.observe(snippetNode);
322 |
323 |
324 | /**
325 | * @function reactToContainerResize
326 | * @param {Number} width
327 | * @param {Number} height
328 | * Updates the height and width of the snippet on the dom body and also updates the canvas height and width.
329 | **/
330 | function reactToContainerResize(width, height) {
331 |
332 | /** HeightX Width conrdinates Update of the continer **/
333 | snippetHeight.innerText = Math.floor(new Number(height))
334 | snippetWidth.innerText = Math.floor(new Number(width))
335 |
336 | /** @NOTE The following Saving and redrawing canvas appraoch is expensive on the memroy, a better design has to be implemented! **/
337 |
338 | /** Save the canvas before update the size **/
339 | saveCanvasImage()
340 |
341 | /** Update canvas height and width with continer with 20 as margin **/
342 | canvasHeight = canvas.height = height + 20;
343 | canvasWidth = canvas.width = width + 20;
344 |
345 | /** redraw the image **/
346 | redrawCanvasImage()
347 |
348 | /** Save the canvas again! **/
349 | saveCanvasImage();
350 |
351 | /** Redraw the canvas again! **/
352 | redrawCanvasImage()
353 | }
354 |
355 | /**
356 | * @function html2blob
357 | * @returns {Promise}
358 | * An abstract function that calls @html2Canvas function as a promise, which convers the canvas to blob.
359 | **/
360 | function html2blob() {
361 |
362 | /** Multiping the container height and width by 2 make room for scaling for the new canvas **/
363 | const width = snippetContainerNode.offsetWidth * 2;
364 | const height = snippetContainerNode.offsetHeight * 2;
365 |
366 | /** Hiding the resizable handle on capture **/
367 | snippetContainerNode.style.resize = "none";
368 |
369 | /** Changing the snippet container background to transparenet temporary on capture **/
370 | snippetContainerNode.style.backgroundColor = "transparent";
371 |
372 | /** Scale snippetContainer by 2 temporary on capture **/
373 | snippetContainerNode.style.transform = "scale(2)";
374 |
375 | /** Canvas Options **/
376 | const options = {
377 | removeContainer: true,
378 | width,
379 | height,
380 | }
381 |
382 | return new Promise((resolve, reject) => {
383 | html2canvas(snippetContainerNode, options).then((canvas) => {
384 | canvas.toBlob((blob) => {
385 | if (blob) {
386 |
387 | /** Reset color **/
388 | snippetContainerNode.style.backgroundColor = "#f2f2f2"
389 |
390 | /** Reset scaling to previous **/
391 | snippetContainerNode.style.transform = "none"
392 |
393 | /** show resize handle **/
394 | snippetContainerNode.style.resize = "";
395 |
396 | /** resolve passing blob as an argument **/
397 | resolve(blob)
398 | } else reject(new Error("something bad happend"))
399 | })
400 | })
401 | })
402 | }
403 |
404 | /**
405 | * @function snippetHandler
406 | * @param {Boolean} copyFlag
407 | * @param {Boolean} upload
408 | * Main function that handles canvas capturing and blob serializing and sending blob to vscode api.
409 | **/
410 | function snippetHandler(copyFlag = false, upload) {
411 | html2blob()
412 | .then(blob => {
413 | serializeBlob(blob, serializedBlob => {
414 |
415 | if (copyFlag) copy(serializedBlob, upload);
416 | else shoot(serializedBlob);
417 | });
418 | })
419 | }
420 |
421 | /** Animation flag for the SNAP button watcing and keep track of the animation state **/
422 | let isInAnimation = false;
423 |
424 | /** Snap button onhover Event Listener **/
425 | obturateurLogo.addEventListener("mouseover", () => {
426 | if (!isInAnimation) {
427 | isInAnimation = true;
428 |
429 | new Vivus(
430 | "save_logo", {
431 | duration: 40,
432 | onReady: () => {
433 | obturateurLogo.className = "obturateur filling";
434 | }
435 | },
436 | () => {
437 | setTimeout(() => {
438 | isInAnimation = false;
439 | obturateurLogo.className = "obturateur";
440 | }, 700);
441 | }
442 | );
443 | }
444 | });
445 |
446 | window.addEventListener("message", e => {
447 | if (e) {
448 | if (e.data.type === "init") {
449 | const {
450 | fontFamily,
451 | bgColor
452 | } = e.data;
453 |
454 | const initialHtml = getInitialHtml(fontFamily);
455 | snippetNode.innerHTML = initialHtml;
456 | vscode.setState({
457 | innerHTML: initialHtml
458 | });
459 |
460 | /** update backdrop color, using bgColor from last pasted snippet cannot deduce from initialHtml since it's always using Nord color **/
461 | if (isDark(bgColor)) {
462 | snippetContainerNode.style.backgroundColor = "#f2f2f2";
463 | } else {
464 | snippetContainerNode.style.background = "none";
465 | }
466 |
467 | /** Event for successful Uplaod of the image from vscode api **/
468 | } else if (e.data.type === "successfulUplaod") {
469 |
470 | /** Append the Upload url of the image to the body of the @UploadedUrlContainer as Html tags. **/
471 | uploadedUrlContainer.innerHTML =
472 | `
473 |
474 |
475 |
478 |
479 |
482 |
Copied!
483 |
484 |
485 | `
486 | /** Click Event Listener for clipboard button of the uploaded url **/
487 | document.getElementById("clipboardBtn").addEventListener("click", () => {
488 | document.getElementById("copyNotif").className = "show"
489 | })
490 |
491 | /** On update event from vscode api **/
492 | } else if (e.data.type === "update") {
493 | document.execCommand("paste");
494 | } else if (e.data.type === "restore") {
495 | snippetNode.innerHTML = e.data.innerHTML;
496 | updateEnvironment(e.data.bgColor);
497 | } else if (e.data.type === "restoreBgColor") {
498 | updateEnvironment(e.data.bgColor);
499 | } else if (e.data.type === "updateSettings") {
500 | snippetNode.style.boxShadow = e.data.shadow;
501 | target = e.data.target;
502 | transparentBackground = e.data.transparentBackground;
503 | snippetContainerNode.style.backgroundColor = e.data.backgroundColor;
504 | backgroundColor = e.data.backgroundColor;
505 | if (e.data.ligature) {
506 | snippetNode.style.fontVariantLigatures = "normal";
507 | } else {
508 | snippetNode.style.fontVariantLigatures = "none";
509 | }
510 |
511 | }
512 | }
513 | });
514 |
515 | /** On key press event Listner **/
516 | window.addEventListener("keypress", ReactToKeyup)
517 |
518 | /** On key up event Listner **/
519 | window.addEventListener("keyup", ReactToKeyup)
520 |
521 | /**
522 | * @function ReactToKeyup
523 | * @param {Object} event
524 | * Reacts to key up keyboard key presses with toolbars functions such as saving the image on local directory or copying the image to clipboard.
525 | **/
526 | function ReactToKeyup(event) {
527 |
528 | /** Ctrl + S or Cmd + S keyboard keypress for saving canvas as an image on the computer **/
529 | if (event.which == 115 && (event.ctrlKey || event.metaKey) || (event.which == 19)) {
530 | event.preventDefault();
531 | snippetHandler();
532 |
533 | /** Ctrl + Z or Cmd + Z keyboard keypress for undo drawing **/
534 | } else if (event.which == 90 && (event.ctrlKey || event.metaKey) || (event.which == 19)) {
535 | restoreState()
536 |
537 | /** Ctrl + C or Cmd + C keyboard keypress for copying image **/
538 | } else if (event.which == 67 && (event.ctrlKey || event.metaKey) || (event.which == 19)) {
539 | copyImage()
540 | }
541 | }
542 |
543 | /**
544 | * PaintJS
545 | * Paint Canvas API
546 | **/
547 |
548 | let savedImageData,
549 |
550 | /** Stores whether I'm currently dragging the mouse or not **/
551 | dragging = false,
552 |
553 | /** Stroke Color of the brush **/
554 | strokeColor = 'black',
555 |
556 | /** Stroke Color of the rectangle **/
557 | fillColor = 'black',
558 |
559 | /** Line width for all tools **/
560 | line_Width = 1,
561 |
562 | /** Tool currently used **/
563 | currentTool = 'brush',
564 |
565 | /** Set canvas width to snippet width with 20px margin. **/
566 | canvasWidth = snippetNode.clientWidth + 20,
567 |
568 | /** Set canvas height to snippet height with 20px margin. **/
569 | canvasHeight = snippetNode.clientHeight + 20,
570 |
571 | /** Boolean for to check if Brush tool is being used. **/
572 | usingBrush = false,
573 |
574 | /** Brush Points Storage **/
575 | brushPoints = new Array(),
576 |
577 | /** History of canvas Drawing Storage **/
578 | undo_array = new Array(),
579 |
580 | /** Pointer to track the currnet of the canvas drawing **/
581 | currentState = 0;
582 |
583 | /**
584 | * @class ShapeBoundingBox
585 | * Stores size data used to create rubber band shapes that will redraw as the user moves the mouse.
586 | **/
587 | class ShapeBoundingBox {
588 | constructor(left, top, width, height) {
589 | this.left = left;
590 | this.top = top;
591 | this.width = width;
592 | this.height = height;
593 | }
594 | }
595 |
596 | /**
597 | * @class MouseDownPos
598 | * Holds x & y position where clicked
599 | **/
600 | class MouseDownPos {
601 | constructor(x, y) {
602 | this.x = x,
603 | this.y = y;
604 | }
605 | }
606 |
607 | /**
608 | * @class Location
609 | * Holds x & y location of the mouse
610 | **/
611 | class Location {
612 | constructor(x, y) {
613 | this.x = x,
614 | this.y = y;
615 | }
616 | }
617 |
618 | /** Stores top left x & y and size of rubber band box **/
619 | let shapeBoundingBox = new ShapeBoundingBox(0, 0, 0, 0);
620 |
621 | /** Holds x & y position where clicked **/
622 | let mousedown = new MouseDownPos(0, 0);
623 |
624 | /** Holds x & y location of the mouse **/
625 | let loc = new Location(0, 0);
626 | ctx.strokeStyle = strokeColor;
627 | ctx.lineWidth = line_Width;
628 |
629 | /** Execute ReactToMouseDown when the mouse is clicked **/
630 | canvas.addEventListener("mousedown", ReactToMouseDown);
631 |
632 | /** Execute ReactToMouseMove when the mouse is clicked **/
633 | canvas.addEventListener("mousemove", ReactToMouseMove);
634 |
635 | /** Execute ReactToMouseUp when the mouse is clicked **/
636 | canvas.addEventListener("mouseup", ReactToMouseUp);
637 |
638 | /**
639 | * @function changeTool
640 | * @param {String} toolClicked
641 | * Changes the current tool to the tool selcted and applyies selected class on the currently used tool.
642 | **/
643 | function changeTool(toolClicked) {
644 |
645 | /** remove class Selected from the unused tools **/
646 | document.getElementById("brush").className = "";
647 | document.getElementById("line").className = "";
648 | document.getElementById("rectangle").className = "";
649 |
650 | /** Highlight the last selected tool on toolbar **/
651 | document.getElementById(toolClicked).className = "selected";
652 |
653 | /** Change current tool used for drawing **/
654 | currentTool = toolClicked;
655 | }
656 |
657 | /**
658 | * @function GetMousePosition
659 | * @param {Number} x
660 | * @param {Number} y
661 | * @returns {Object}
662 | * Returns mouse x & y position based on canvas position in page
663 | **/
664 | function GetMousePosition(x, y) {
665 |
666 | /** Get canvas size and position in web page **/
667 | let canvasSizeData = canvas.getBoundingClientRect();
668 | return {
669 | x: (x - canvasSizeData.left) * (canvas.width / canvasSizeData.width),
670 | y: (y - canvasSizeData.top) * (canvas.height / canvasSizeData.height)
671 | };
672 | }
673 |
674 | /**
675 | * @function saveCanvasImage
676 | * Saves the current canvas data.
677 | **/
678 | function saveCanvasImage() {
679 | if (currentState != undo_array.length - 1) {
680 | undo_array.splice(currentState + 1, undo_array.length);
681 | }
682 | savedImageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
683 | undo_array.push({
684 | currentTool,
685 | savedImageData
686 | })
687 | currentState++
688 | }
689 |
690 | /**
691 | * @function redrawCanvasImage
692 | * Redraws the last saved canvas data.
693 | **/
694 | function redrawCanvasImage() {
695 | if (savedImageData) ctx.putImageData(savedImageData, 0, 0);
696 |
697 | /** added this to cancel the bug of intial state **/
698 | else {
699 | saveCanvasImage()
700 | redrawCanvasImage()
701 | }
702 | }
703 |
704 | /**
705 | * @function UpdateRubberbandBoxSizeData
706 | * @param {Object} loc
707 | * Updates Rubberband Box Size with mouse location
708 | **/
709 | function UpdateRubberbandBoxSizeData(loc) {
710 |
711 | /** Height & width are the difference between were clicked and current mouse position **/
712 | shapeBoundingBox.width = Math.abs(loc.x - mousedown.x);
713 | shapeBoundingBox.height = Math.abs(loc.y - mousedown.y);
714 |
715 | /** If mouse is below where mouse was clicked originally **/
716 | if (loc.x > mousedown.x) {
717 |
718 | /** Store mousedown because it is farthest left **/
719 | shapeBoundingBox.left = mousedown.x;
720 | } else {
721 |
722 | /** Store mouse location because it is most left **/
723 | shapeBoundingBox.left = loc.x;
724 | }
725 |
726 | /** If mouse location is below where clicked originally **/
727 | if (loc.y > mousedown.y) {
728 |
729 | /** Store mousedown because it is closer to the top of the canvas**/
730 | shapeBoundingBox.top = mousedown.y;
731 | } else {
732 |
733 | /** Otherwise store mouse position **/
734 | shapeBoundingBox.top = loc.y;
735 | }
736 | }
737 |
738 |
739 | /** Called to draw the line **/
740 | function drawRubberbandShape(loc) {
741 |
742 | ctx.strokeStyle = strokeColor;
743 | ctx.fillStyle = fillColor;
744 | if (currentTool === "brush") {
745 |
746 | /** Create paint brush **/
747 | DrawBrush();
748 | } else if (currentTool === "line") {
749 |
750 | /** Draw Line **/
751 | ctx.beginPath();
752 | ctx.moveTo(mousedown.x, mousedown.y);
753 | ctx.lineTo(loc.x, loc.y);
754 | ctx.stroke();
755 | } else if (currentTool === "rectangle") {
756 |
757 | /** Draw rectangle **/
758 | ctx.strokeRect(shapeBoundingBox.left, shapeBoundingBox.top, shapeBoundingBox.width, shapeBoundingBox.height);
759 | }
760 | }
761 |
762 | /**
763 | * @function UpdateRubberbandBoxOnMove
764 | * @param {Object} loc
765 | * Updates Rubberband Box On the Move with x and y mouse locations.
766 | **/
767 | function UpdateRubberbandBoxOnMove(loc) {
768 |
769 | /** Stores changing height, width, x & y position of most top left point being either the click or mouse location **/
770 | UpdateRubberbandBoxSizeData(loc);
771 |
772 | /** Redraw the shape **/
773 | drawRubberbandShape(loc);
774 | }
775 |
776 | /**
777 | * @function AddBrushPoint
778 | * @param {Number} x
779 | * @param {Number} y
780 | * @param {Boolean} mouseDown
781 | * @param {String} brushColor
782 | * @param {String} brushSize
783 | * @param {String} mode
784 | * @param {String} tool
785 | * Store each point as the mouse moves and whether the mouse button is currently being dragged or not and the color being used and also the tool was used back
786 | **/
787 | function AddBrushPoint(x, y, mouseDown, brushColor, brushSize, mode = none, tool) {
788 |
789 | let point = {
790 | tool: tool,
791 | "x": x,
792 | "y": y,
793 | "isDrawing": mouseDown,
794 | size: brushSize,
795 | color: brushColor,
796 | mode: mode
797 | }
798 | brushPoints.push(point)
799 | }
800 |
801 | /**
802 | * @function DrawBrush
803 | * Cycle through all brush points and connect them with lines
804 | **/
805 | function DrawBrush() {
806 | if (brushPoints.length == 0) return;
807 |
808 | for (var i = 0; i < brushPoints.length; i++) {
809 | let pt = brushPoints[i];
810 | if (pt.tool !== "brush" || !pt.mode || !pt) return;
811 | let begin = false;
812 | ctx.strokeStyle = pt.color
813 | if (pt.mode == "begin" || begin) {
814 | ctx.beginPath();
815 | ctx.moveTo(pt.x, pt.y);
816 | }
817 | ctx.lineTo(pt.x, pt.y);
818 | if (pt.mode == "end") {
819 | ctx.stroke();
820 | }
821 | }
822 | ctx.stroke();
823 | }
824 |
825 | /**
826 | * @function ReactToMouseDown
827 | * @param {Object} e Event
828 | * React on mouse down event.
829 | **/
830 | function ReactToMouseDown(e) {
831 |
832 | /** Change the mouse pointer to a crosshair **/
833 | canvas.style.cursor = "crosshair";
834 |
835 | /** Store location **/
836 | loc = GetMousePosition(e.clientX, e.clientY);
837 |
838 | /** Store mouse position when clicked **/
839 | mousedown.x = loc.x;
840 | mousedown.y = loc.y;
841 |
842 | /** Store that yes the mouse is being held down **/
843 | dragging = true;
844 |
845 | /** Brush will store points in an array **/
846 | if (currentTool === 'brush') {
847 | usingBrush = true;
848 | AddBrushPoint(loc.x, loc.y, mouseDown = false, brushColor = strokeColor, brushSize = line_Width, mode = "begin", tool = currentTool);
849 | }
850 | };
851 |
852 | /**
853 | * @function ReactToMouseMove
854 | * @param {Object} e Event
855 | * React on mounse event move.
856 | **/
857 | function ReactToMouseMove(e) {
858 |
859 | /** Set the cursor to crosshair**/
860 | canvas.style.cursor = "crosshair"
861 |
862 | /** get and location on the move **/
863 | loc = GetMousePosition(e.clientX, e.clientY);
864 |
865 | /** If using brush tool and dragging store each point **/
866 | if (currentTool === 'brush' && dragging && usingBrush) {
867 | if (loc.x > 0 && loc.x < canvasWidth - 5 && loc.y > 0 && loc.y < canvasHeight - 5) {
868 | ctx.lineTo(loc.x, loc.y);
869 | ctx.stroke();
870 | AddBrushPoint(loc.x, loc.y, mouseDown = true, brushColor = strokeColor, brushSize = line_Width, mode = "draw", tool = currentTool);
871 | }
872 |
873 | /** Make the drawing stops on exceding canvas bounderies **/
874 | else if (loc.x < 0 || loc.x > canvasWidth - 5 || loc.y < 0 || loc.y > canvasHeight - 5) {
875 | AddBrushPoint(loc.x, loc.y, mouseDown = false, brushColor = fillColor, brushSize = line_Width, mode = "end", tool = currentTool);
876 | ctx.stroke();
877 | dragging = false
878 | }
879 |
880 | redrawCanvasImage();
881 | DrawBrush();
882 | } else {
883 | if (dragging) {
884 | redrawCanvasImage();
885 | UpdateRubberbandBoxOnMove(loc);
886 | }
887 | }
888 | };
889 |
890 | /**
891 | * @function ReactTomMouseUp
892 | * @param {Event} e Event
893 | * React to mouse Up event
894 | **/
895 | function ReactToMouseUp(e) {
896 |
897 | /** if the mouse is beign dragged return **/
898 | if (!dragging) return;
899 |
900 | /** Save canvas **/
901 | saveCanvasImage()
902 |
903 | /** If current tool is "brush" tool then add brush point to draw **/
904 | if (currentTool === "brush") AddBrushPoint(loc.x, loc.y, mouseDown = false, brushColor = fillColor, brushSize = line_Width, mode = "end", tool = currentTool);
905 |
906 | /** set cursor style to default **/
907 | canvas.style.cursor = "defualt";
908 |
909 | /** Update mouse location **/
910 | loc = GetMousePosition(e.clientX, e.clientY);
911 |
912 | /** Set dragging flag to false **/
913 | dragging = false;
914 |
915 | /** Redraw canvas image **/
916 | redrawCanvasImage();
917 |
918 | /** Update Rubber Band On move **/
919 | UpdateRubberbandBoxOnMove(loc);
920 |
921 | /** Set usingbrush state to false **/
922 | usingBrush = false;
923 | }
924 |
925 |
926 | /**
927 | * @function restoreState
928 | * Restores the previous state of canvas and set it the current state.
929 | **/
930 | function restoreState() {
931 |
932 | /** if the array is empry or current status pointer is negtive return **/
933 | if (!undo_array.length || currentState <= 0) return;
934 |
935 | /** take the previous canavas history **/
936 | restore_state = undo_array[--currentState]
937 |
938 | /** if Brush tool was used deletes undo last brush points**/
939 | if (restore_state.currentTool === "brush") {
940 |
941 | /** Delete last brush points **/
942 | deleteLastBrushPoint()
943 |
944 | /** Draws brush points after delete **/
945 | DrawBrush()
946 | }
947 | /** set current canvas image to previous canvas image **/
948 | savedImageData = restore_state.savedImageData
949 |
950 | /** Redraw the canvas **/
951 | redrawCanvasImage()
952 | }
953 |
954 | /**
955 | * @function deleteLastBrushPoint
956 | * Delete last brush points from the brushpoints array
957 | **/
958 | function deleteLastBrushPoint() {
959 |
960 | /** Iterates through the all brush points and remove the last one **/
961 | brushPoints.forEach((pt, i) => {
962 | if (pt.mode === "begin") {
963 | return brushPoints.splice(i, brushPoints.length)
964 | }
965 | })
966 | }
967 |
968 |
969 | /**
970 | * @function copyImage
971 | * @param {Boolean} upload default False
972 | * Calls @snippetHandler that handles the snippet for uploading or copying functionality.
973 | */
974 | function copyImage(upload = false) {
975 | /** calling @snippetHandler passing copyFlag set to True and upload paramater **/
976 | snippetHandler(copyFlag = true, upload);
977 | }
978 |
979 | /**
980 | * @function uploadImage
981 | * Calls @snippetHandler that handles the snippet uploading functionality.
982 | */
983 | function uploadImage() {
984 | /** call @copyImage funciton with upload flag set to True for uploading **/
985 | copyImage(true)
986 | }
987 |
988 | /**
989 | * @
990 | * Color Picker API
991 | **/
992 | const pickr = Pickr.create({
993 | el: '#pickr',
994 | theme: 'nano',
995 |
996 | /** Different Color options to pick from **/
997 | swatches: [
998 | 'rgba(244, 67, 54, 1)',
999 | 'rgba(233, 30, 99, 0.95)',
1000 | 'rgba(156, 39, 176, 0.9)',
1001 | 'rgba(103, 58, 183, 0.85)',
1002 | 'rgba(63, 81, 181, 0.8)',
1003 | 'rgba(33, 150, 243, 0.75)',
1004 | 'rgba(3, 169, 244, 0.7)',
1005 | 'rgba(0, 188, 212, 0.7)',
1006 | 'rgba(0, 150, 136, 0.75)',
1007 | 'rgba(76, 175, 80, 0.8)',
1008 | 'rgba(139, 195, 74, 0.85)',
1009 | 'rgba(205, 220, 57, 0.9)',
1010 | 'rgba(255, 235, 59, 0.95)',
1011 | 'rgba(255, 193, 7, 1)'
1012 | ],
1013 |
1014 | components: {
1015 |
1016 | /** Main components **/
1017 | preview: true,
1018 | opacity: true,
1019 | hue: true,
1020 | }
1021 | });
1022 |
1023 | /** Pickr Initialization **/
1024 | pickr.on('init', (instance) => {
1025 |
1026 | /** Convert color to hex value **/
1027 | const hexColor = color.toHEXA().toString();
1028 |
1029 | /** Update fill and stroke color with picked color **/
1030 | fillColor = strokeColor = hexColor
1031 | });
1032 |
1033 | /** Pickr Color Change **/
1034 | pickr.on('change', (color) => {
1035 |
1036 | /** Convert color to hex value **/
1037 | const hexColor = color.toHEXA().toString();
1038 |
1039 | /** Update fill and stroke color with picked color **/
1040 | fillColor = strokeColor = hexColor
1041 |
1042 | /** Update PointerJs Ring Color **/
1043 | init_pointer({
1044 | pointerColor: hexColor,
1045 | })
1046 | })
1047 |
1048 | /**
1049 | * TODO:
1050 | * Redo feature
1051 | * to able to redo the last undo drawing implemented on the canvas.
1052 | **/
1053 | })
1054 | ();
1055 | }
--------------------------------------------------------------------------------
/webview/paint.js:
--------------------------------------------------------------------------------
1 | window.onload = function () {
2 | // Reference to the canvas element
3 | let canvas;
4 | // Context provides functions used for drawing and
5 | // working with Canvas
6 | let ctx;
7 | // Stores previously drawn image data to restore after
8 | // new drawings are added
9 | let savedImageData;
10 | // Stores whether I'm currently dragging the mouse
11 | let dragging = false;
12 | let strokeColor = 'black';
13 | let fillColor = 'black';
14 | let line_Width = 2;
15 | let polygonSides = 6;
16 | // Tool currently using
17 | let currentTool = 'brush';
18 | let canvasWidth = 600;
19 | let canvasHeight = 600;
20 |
21 | // Stores size data used to create rubber band shapes
22 | // that will redraw as the user moves the mouse
23 | class ShapeBoundingBox {
24 | constructor(left, top, width, height) {
25 | this.left = left;
26 | this.top = top;
27 | this.width = width;
28 | this.height = height;
29 | }
30 | }
31 |
32 | // Holds x & y position where clicked
33 | class MouseDownPos {
34 | constructor(x, y) {
35 | this.x = x,
36 | this.y = y;
37 | }
38 | }
39 |
40 | // Holds x & y location of the mouse
41 | class Location {
42 | constructor(x, y) {
43 | this.x = x,
44 | this.y = y;
45 | }
46 | }
47 |
48 | // Holds x & y polygon point values
49 | class PolygonPoint {
50 | constructor(x, y) {
51 | this.x = x,
52 | this.y = y;
53 | }
54 | }
55 | // Stores top left x & y and size of rubber band box
56 | let shapeBoundingBox = new ShapeBoundingBox(0, 0, 0, 0);
57 | // Holds x & y position where clicked
58 | let mousedown = new MouseDownPos(0, 0);
59 | // Holds x & y location of the mouse
60 | let loc = new Location(0, 0);
61 |
62 | // Call for our function to execute when page is loaded
63 | document.addEventListener('DOMContentLoaded', setupCanvas);
64 |
65 | function setupCanvas() {
66 | // Get reference to canvas element
67 | canvas = document.getElementById('my-canvas');
68 | // Get methods for manipulating the canvas
69 | ctx = canvas.getContext('2d');
70 | ctx.strokeStyle = strokeColor;
71 | ctx.lineWidth = line_Width;
72 | // Execute ReactToMouseDown when the mouse is clicked
73 | canvas.addEventListener("mousedown", ReactToMouseDown);
74 | // Execute ReactToMouseMove when the mouse is clicked
75 | canvas.addEventListener("mousemove", ReactToMouseMove);
76 | // Execute ReactToMouseUp when the mouse is clicked
77 | canvas.addEventListener("mouseup", ReactToMouseUp);
78 | }
79 |
80 | function ChangeTool(toolClicked) {
81 | document.getElementById("open").className = "";
82 | document.getElementById("save").className = "";
83 | document.getElementById("brush").className = "";
84 | document.getElementById("line").className = "";
85 | document.getElementById("rectangle").className = "";
86 | document.getElementById("circle").className = "";
87 | document.getElementById("ellipse").className = "";
88 | document.getElementById("polygon").className = "";
89 | // Highlight the last selected tool on toolbar
90 | document.getElementById(toolClicked).className = "selected";
91 | // Change current tool used for drawing
92 | currentTool = toolClicked;
93 | }
94 |
95 | // Returns mouse x & y position based on canvas position in page
96 | function GetMousePosition(x, y) {
97 | // Get canvas size and position in web page
98 | let canvasSizeData = canvas.getBoundingClientRect();
99 | return {
100 | x: (x - canvasSizeData.left) * (canvas.width / canvasSizeData.width),
101 | y: (y - canvasSizeData.top) * (canvas.height / canvasSizeData.height)
102 | };
103 | }
104 |
105 | function SaveCanvasImage() {
106 | // Save image
107 | savedImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
108 | }
109 |
110 | function RedrawCanvasImage() {
111 | // Restore image
112 | ctx.putImageData(savedImageData, 0, 0);
113 | }
114 |
115 | function UpdateRubberbandSizeData(loc) {
116 | // Height & width are the difference between were clicked
117 | // and current mouse position
118 | shapeBoundingBox.width = Math.abs(loc.x - mousedown.x);
119 | shapeBoundingBox.height = Math.abs(loc.y - mousedown.y);
120 |
121 | // If mouse is below where mouse was clicked originally
122 | if (loc.x > mousedown.x) {
123 |
124 | // Store mousedown because it is farthest left
125 | shapeBoundingBox.left = mousedown.x;
126 | } else {
127 |
128 | // Store mouse location because it is most left
129 | shapeBoundingBox.left = loc.x;
130 | }
131 |
132 | // If mouse location is below where clicked originally
133 | if (loc.y > mousedown.y) {
134 |
135 | // Store mousedown because it is closer to the top
136 | // of the canvas
137 | shapeBoundingBox.top = mousedown.y;
138 | } else {
139 |
140 | // Otherwise store mouse position
141 | shapeBoundingBox.top = loc.y;
142 | }
143 | }
144 |
145 | // Returns the angle using x and y
146 | // x = Adjacent Side
147 | // y = Opposite Side
148 | // Tan(Angle) = Opposite / Adjacent
149 | // Angle = ArcTan(Opposite / Adjacent)
150 | function getAngleUsingXAndY(mouselocX, mouselocY) {
151 | let adjacent = mousedown.x - mouselocX;
152 | let opposite = mousedown.y - mouselocY;
153 |
154 | return radiansToDegrees(Math.atan2(opposite, adjacent));
155 | }
156 |
157 | function radiansToDegrees(rad) {
158 | if (rad < 0) {
159 | // Correct the bottom error by adding the negative
160 | // angle to 360 to get the correct result around
161 | // the whole circle
162 | return (360.0 + (rad * (180 / Math.PI))).toFixed(2);
163 | } else {
164 | return (rad * (180 / Math.PI)).toFixed(2);
165 | }
166 | }
167 |
168 | // Converts degrees to radians
169 | function degreesToRadians(degrees) {
170 | return degrees * (Math.PI / 180);
171 | }
172 |
173 | function ReactToMouseDown(e) {
174 | // Change the mouse pointer to a crosshair
175 | canvas.style.cursor = "crosshair";
176 | // Store location
177 | loc = GetMousePosition(e.clientX, e.clientY);
178 | // Save the current canvas image
179 | SaveCanvasImage();
180 | // Store mouse position when clicked
181 | mousedown.x = loc.x;
182 | mousedown.y = loc.y;
183 | // Store that yes the mouse is being held down
184 | dragging = true;
185 | }
186 |
187 | function ReactToMouseMove(e) {
188 | canvas.style.cursor = "crosshair";
189 | loc = GetMousePosition(e.clientX, e.clientY);
190 | };
191 |
192 | function ReactToMouseUp(e) {
193 | canvas.style.cursor = "default";
194 | loc = GetMousePosition(e.clientX, e.clientY);
195 | RedrawCanvasImage();
196 | UpdateRubberbandOnMove(loc);
197 | dragging = false;
198 | usingBrush = false;
199 | }
200 |
201 | // Saves the image in your default download directory
202 | function SaveImage() {
203 | // Get a reference to the link element
204 | var imageFile = document.getElementById("img-file");
205 | // Set that you want to download the image when link is clicked
206 | imageFile.setAttribute('download', 'image.png');
207 | // Reference the image in canvas for download
208 | imageFile.setAttribute('href', canvas.toDataURL());
209 | }
210 |
211 | function OpenImage() {
212 | let img = new Image();
213 | // Once the image is loaded clear the canvas and draw it
214 | img.onload = function () {
215 | ctx.clearRect(0, 0, canvas.width, canvas.height);
216 | ctx.drawImage(img, 0, 0);
217 | }
218 | img.src = 'image.png';
219 |
220 | }
221 | }
--------------------------------------------------------------------------------
/webview/pickr.js:
--------------------------------------------------------------------------------
1 | /*! Pickr 1.5.1 MIT | https://github.com/Simonwep/pickr */ ! function (t, e) {
2 | "object" == typeof exports && "object" == typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define([], e) : "object" == typeof exports ? exports.Pickr = e() : t.Pickr = e()
3 | }(window, (function () {
4 | return function (t) {
5 | var e = {};
6 |
7 | function o(n) {
8 | if (e[n]) return e[n].exports;
9 | var i = e[n] = {
10 | i: n,
11 | l: !1,
12 | exports: {}
13 | };
14 | return t[n].call(i.exports, i, i.exports, o), i.l = !0, i.exports
15 | }
16 | return o.m = t, o.c = e, o.d = function (t, e, n) {
17 | o.o(t, e) || Object.defineProperty(t, e, {
18 | enumerable: !0,
19 | get: n
20 | })
21 | }, o.r = function (t) {
22 | "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(t, Symbol.toStringTag, {
23 | value: "Module"
24 | }), Object.defineProperty(t, "__esModule", {
25 | value: !0
26 | })
27 | }, o.t = function (t, e) {
28 | if (1 & e && (t = o(t)), 8 & e) return t;
29 | if (4 & e && "object" == typeof t && t && t.__esModule) return t;
30 | var n = Object.create(null);
31 | if (o.r(n), Object.defineProperty(n, "default", {
32 | enumerable: !0,
33 | value: t
34 | }), 2 & e && "string" != typeof t)
35 | for (var i in t) o.d(n, i, function (e) {
36 | return t[e]
37 | }.bind(null, i));
38 | return n
39 | }, o.n = function (t) {
40 | var e = t && t.__esModule ? function () {
41 | return t.default
42 | } : function () {
43 | return t
44 | };
45 | return o.d(e, "a", e), e
46 | }, o.o = function (t, e) {
47 | return Object.prototype.hasOwnProperty.call(t, e)
48 | }, o.p = "", o(o.s = 1)
49 | }([function (t) {
50 | t.exports = JSON.parse('{"a":"1.5.1"}')
51 | }, function (t, e, o) {
52 | "use strict";
53 | o.r(e);
54 | var n = {};
55 |
56 | function i(t, e) {
57 | var o = Object.keys(t);
58 | if (Object.getOwnPropertySymbols) {
59 | var n = Object.getOwnPropertySymbols(t);
60 | e && (n = n.filter((function (e) {
61 | return Object.getOwnPropertyDescriptor(t, e).enumerable
62 | }))), o.push.apply(o, n)
63 | }
64 | return o
65 | }
66 |
67 | function r(t) {
68 | for (var e = 1; e < arguments.length; e++) {
69 | var o = null != arguments[e] ? arguments[e] : {};
70 | e % 2 ? i(Object(o), !0).forEach((function (e) {
71 | s(t, e, o[e])
72 | })) : Object.getOwnPropertyDescriptors ? Object.defineProperties(t, Object.getOwnPropertyDescriptors(o)) : i(Object(o)).forEach((function (e) {
73 | Object.defineProperty(t, e, Object.getOwnPropertyDescriptor(o, e))
74 | }))
75 | }
76 | return t
77 | }
78 |
79 | function s(t, e, o) {
80 | return e in t ? Object.defineProperty(t, e, {
81 | value: o,
82 | enumerable: !0,
83 | configurable: !0,
84 | writable: !0
85 | }) : t[e] = o, t
86 | }
87 |
88 | function c(t, e, o, n, i = {}) {
89 | e instanceof HTMLCollection || e instanceof NodeList ? e = Array.from(e) : Array.isArray(e) || (e = [e]), Array.isArray(o) || (o = [o]);
90 | for (const s of e)
91 | for (const e of o) s[t](e, n, r({
92 | capture: !1
93 | }, i));
94 | return Array.prototype.slice.call(arguments, 1)
95 | }
96 | o.r(n), o.d(n, "on", (function () {
97 | return a
98 | })), o.d(n, "off", (function () {
99 | return l
100 | })), o.d(n, "createElementFromString", (function () {
101 | return p
102 | })), o.d(n, "removeAttribute", (function () {
103 | return u
104 | })), o.d(n, "createFromTemplate", (function () {
105 | return h
106 | })), o.d(n, "eventPath", (function () {
107 | return d
108 | })), o.d(n, "resolveElement", (function () {
109 | return f
110 | })), o.d(n, "adjustableInputNumbers", (function () {
111 | return m
112 | }));
113 | const a = c.bind(null, "addEventListener"),
114 | l = c.bind(null, "removeEventListener");
115 |
116 | function p(t) {
117 | const e = document.createElement("div");
118 | return e.innerHTML = t.trim(), e.firstElementChild
119 | }
120 |
121 | function u(t, e) {
122 | const o = t.getAttribute(e);
123 | return t.removeAttribute(e), o
124 | }
125 |
126 | function h(t) {
127 | return function t(e, o = {}) {
128 | const n = u(e, ":obj"),
129 | i = u(e, ":ref"),
130 | r = n ? o[n] = {} : o;
131 | i && (o[i] = e);
132 | for (const o of Array.from(e.children)) {
133 | const e = u(o, ":arr"),
134 | n = t(o, e ? {} : r);
135 | e && (r[e] || (r[e] = [])).push(Object.keys(n).length ? n : o)
136 | }
137 | return o
138 | }(p(t))
139 | }
140 |
141 | function d(t) {
142 | let e = t.path || t.composedPath && t.composedPath();
143 | if (e) return e;
144 | let o = t.target.parentElement;
145 | for (e = [t.target, o]; o = o.parentElement;) e.push(o);
146 | return e.push(document, window), e
147 | }
148 |
149 | function f(t) {
150 | return t instanceof Element ? t : "string" == typeof t ? t.split(/>>/g).reduce((t, e, o, n) => (t = t.querySelector(e), o < n.length - 1 ? t.shadowRoot : t), document) : null
151 | }
152 |
153 | function m(t, e = (t => t)) {
154 | function o(o) {
155 | const n = [.001, .01, .1][Number(o.shiftKey || 2 * o.ctrlKey)] * (o.deltaY < 0 ? 1 : -1);
156 | let i = 0,
157 | r = t.selectionStart;
158 | t.value = t.value.replace(/[\d.]+/g, (t, o) => o <= r && o + t.length >= r ? (r = o, e(Number(t), n, i)) : (i++, t)), t.focus(), t.setSelectionRange(r, r), o.preventDefault(), t.dispatchEvent(new Event("input"))
159 | }
160 | a(t, "focus", () => a(window, "wheel", o, {
161 | passive: !1
162 | })), a(t, "blur", () => l(window, "wheel", o))
163 | }
164 | var v = o(0);
165 | const {
166 | min: b,
167 | max: y,
168 | floor: g,
169 | round: _
170 | } = Math;
171 |
172 | function w(t, e, o) {
173 | e /= 100, o /= 100;
174 | const n = g(t = t / 360 * 6),
175 | i = t - n,
176 | r = o * (1 - e),
177 | s = o * (1 - i * e),
178 | c = o * (1 - (1 - i) * e),
179 | a = n % 6;
180 | return [255 * [o, s, r, r, c, o][a], 255 * [c, o, o, s, r, r][a], 255 * [r, r, c, o, o, s][a]]
181 | }
182 |
183 | function A(t, e, o) {
184 | const n = (2 - (e /= 100)) * (o /= 100) / 2;
185 | return 0 !== n && (e = 1 === n ? 0 : n < .5 ? e * o / (2 * n) : e * o / (2 - 2 * n)), [t, 100 * e, 100 * n]
186 | }
187 |
188 | function C(t, e, o) {
189 | const n = b(t /= 255, e /= 255, o /= 255),
190 | i = y(t, e, o),
191 | r = i - n;
192 | let s, c;
193 | if (0 === r) s = c = 0;
194 | else {
195 | c = r / i;
196 | const n = ((i - t) / 6 + r / 2) / r,
197 | a = ((i - e) / 6 + r / 2) / r,
198 | l = ((i - o) / 6 + r / 2) / r;
199 | t === i ? s = l - a : e === i ? s = 1 / 3 + n - l : o === i && (s = 2 / 3 + a - n), s < 0 ? s += 1 : s > 1 && (s -= 1)
200 | }
201 | return [360 * s, 100 * c, 100 * i]
202 | }
203 |
204 | function k(t, e, o, n) {
205 | return e /= 100, o /= 100, [...C(255 * (1 - b(1, (t /= 100) * (1 - (n /= 100)) + n)), 255 * (1 - b(1, e * (1 - n) + n)), 255 * (1 - b(1, o * (1 - n) + n)))]
206 | }
207 |
208 | function S(t, e, o) {
209 | return e /= 100, [t, 2 * (e *= (o /= 100) < .5 ? o : 1 - o) / (o + e) * 100, 100 * (o + e)]
210 | }
211 |
212 | function O(t) {
213 | return C(...t.match(/.{2}/g).map(t => parseInt(t, 16)))
214 | }
215 |
216 | function j(t) {
217 | t = t.match(/^[a-zA-Z]+$/) ? function (t) {
218 | if ("black" === t.toLowerCase()) return "#000";
219 | const e = document.createElement("canvas").getContext("2d");
220 | return e.fillStyle = t, "#000" === e.fillStyle ? null : e.fillStyle
221 | }(t) : t;
222 | const e = {
223 | cmyk: /^cmyk[\D]+([\d.]+)[\D]+([\d.]+)[\D]+([\d.]+)[\D]+([\d.]+)/i,
224 | rgba: /^((rgba)|rgb)[\D]+([\d.]+)[\D]+([\d.]+)[\D]+([\d.]+)[\D]*?([\d.]+|$)/i,
225 | hsla: /^((hsla)|hsl)[\D]+([\d.]+)[\D]+([\d.]+)[\D]+([\d.]+)[\D]*?([\d.]+|$)/i,
226 | hsva: /^((hsva)|hsv)[\D]+([\d.]+)[\D]+([\d.]+)[\D]+([\d.]+)[\D]*?([\d.]+|$)/i,
227 | hexa: /^#?(([\dA-Fa-f]{3,4})|([\dA-Fa-f]{6})|([\dA-Fa-f]{8}))$/i
228 | },
229 | o = t => t.map(t => /^(|\d+)\.\d+|\d+$/.test(t) ? Number(t) : void 0);
230 | let n;
231 | t: for (const i in e) {
232 | if (!(n = e[i].exec(t))) continue;
233 | const r = t => !!n[2] == ("number" == typeof t);
234 | switch (i) {
235 | case "cmyk": {
236 | const [, t, e, r, s] = o(n);
237 | if (t > 100 || e > 100 || r > 100 || s > 100) break t;
238 | return {
239 | values: k(t, e, r, s),
240 | type: i
241 | }
242 | }
243 | case "rgba": {
244 | const [, , , t, e, s, c] = o(n);
245 | if (t > 255 || e > 255 || s > 255 || c < 0 || c > 1 || !r(c)) break t;
246 | return {
247 | values: [...C(t, e, s), c],
248 | a: c,
249 | type: i
250 | }
251 | }
252 | case "hexa": {
253 | let [, t] = n;
254 | 4 !== t.length && 3 !== t.length || (t = t.split("").map(t => t + t).join(""));
255 | const e = t.substring(0, 6);
256 | let o = t.substring(6);
257 | return o = o ? parseInt(o, 16) / 255 : void 0, {
258 | values: [...O(e), o],
259 | a: o,
260 | type: i
261 | }
262 | }
263 | case "hsla": {
264 | const [, , , t, e, s, c] = o(n);
265 | if (t > 360 || e > 100 || s > 100 || c < 0 || c > 1 || !r(c)) break t;
266 | return {
267 | values: [...S(t, e, s), c],
268 | a: c,
269 | type: i
270 | }
271 | }
272 | case "hsva": {
273 | const [, , , t, e, s, c] = o(n);
274 | if (t > 360 || e > 100 || s > 100 || c < 0 || c > 1 || !r(c)) break t;
275 | return {
276 | values: [t, e, s, c],
277 | a: c,
278 | type: i
279 | }
280 | }
281 | }
282 | }
283 | return {
284 | values: null,
285 | type: null
286 | }
287 | }
288 |
289 | function x(t = 0, e = 0, o = 0, n = 1) {
290 | const i = (t, e) => (o = -1) => e(~o ? t.map(t => Number(t.toFixed(o))) : t),
291 | r = {
292 | h: t,
293 | s: e,
294 | v: o,
295 | a: n,
296 | toHSVA() {
297 | const t = [r.h, r.s, r.v, r.a];
298 | return t.toString = i(t, t => "hsva(".concat(t[0], ", ").concat(t[1], "%, ").concat(t[2], "%, ").concat(r.a, ")")), t
299 | },
300 | toHSLA() {
301 | const t = [...A(r.h, r.s, r.v), r.a];
302 | return t.toString = i(t, t => "hsla(".concat(t[0], ", ").concat(t[1], "%, ").concat(t[2], "%, ").concat(r.a, ")")), t
303 | },
304 | toRGBA() {
305 | const t = [...w(r.h, r.s, r.v), r.a];
306 | return t.toString = i(t, t => "rgba(".concat(t[0], ", ").concat(t[1], ", ").concat(t[2], ", ").concat(r.a, ")")), t
307 | },
308 | toCMYK() {
309 | const t = function (t, e, o) {
310 | const n = w(t, e, o),
311 | i = n[0] / 255,
312 | r = n[1] / 255,
313 | s = n[2] / 255,
314 | c = b(1 - i, 1 - r, 1 - s);
315 | return [100 * (1 === c ? 0 : (1 - i - c) / (1 - c)), 100 * (1 === c ? 0 : (1 - r - c) / (1 - c)), 100 * (1 === c ? 0 : (1 - s - c) / (1 - c)), 100 * c]
316 | }(r.h, r.s, r.v);
317 | return t.toString = i(t, t => "cmyk(".concat(t[0], "%, ").concat(t[1], "%, ").concat(t[2], "%, ").concat(t[3], "%)")), t
318 | },
319 | toHEXA() {
320 | const t = function (t, e, o) {
321 | return w(t, e, o).map(t => _(t).toString(16).padStart(2, "0"))
322 | }(r.h, r.s, r.v),
323 | e = r.a >= 1 ? "" : Number((255 * r.a).toFixed(0)).toString(16).toUpperCase().padStart(2, "0");
324 | return e && t.push(e), t.toString = () => "#".concat(t.join("").toUpperCase()), t
325 | },
326 | clone: () => x(r.h, r.s, r.v, r.a)
327 | };
328 | return r
329 | }
330 | const E = t => Math.max(Math.min(t, 1), 0);
331 |
332 | function L(t) {
333 | const e = {
334 | options: Object.assign({
335 | lock: null,
336 | onchange: () => 0,
337 | onstop: () => 0
338 | }, t),
339 | _keyboard(t) {
340 | const {
341 | options: o
342 | } = e, {
343 | type: n,
344 | key: i
345 | } = t;
346 | if (document.activeElement === o.wrapper) {
347 | const {
348 | lock: o
349 | } = e.options, r = "ArrowUp" === i, s = "ArrowRight" === i, c = "ArrowDown" === i, a = "ArrowLeft" === i;
350 | if ("keydown" === n && (r || s || c || a)) {
351 | let n = 0,
352 | i = 0;
353 | "v" === o ? n = r || s ? 1 : -1 : "h" === o ? n = r || s ? -1 : 1 : (i = r ? -1 : c ? 1 : 0, n = a ? -1 : s ? 1 : 0), e.update(E(e.cache.x + .01 * n), E(e.cache.y + .01 * i)), t.preventDefault()
354 | } else i.startsWith("Arrow") && (e.options.onstop(), t.preventDefault())
355 | }
356 | },
357 | _tapstart(t) {
358 | a(document, ["mouseup", "touchend", "touchcancel"], e._tapstop), a(document, ["mousemove", "touchmove"], e._tapmove), t.preventDefault(), e._tapmove(t)
359 | },
360 | _tapmove(t) {
361 | const {
362 | options: o,
363 | cache: n
364 | } = e, {
365 | lock: i,
366 | element: r,
367 | wrapper: s
368 | } = o, c = s.getBoundingClientRect();
369 | let a = 0,
370 | l = 0;
371 | if (t) {
372 | const e = t && t.touches && t.touches[0];
373 | a = t ? (e || t).clientX : 0, l = t ? (e || t).clientY : 0, a < c.left ? a = c.left : a > c.left + c.width && (a = c.left + c.width), l < c.top ? l = c.top : l > c.top + c.height && (l = c.top + c.height), a -= c.left, l -= c.top
374 | } else n && (a = n.x * c.width, l = n.y * c.height);
375 | "h" !== i && (r.style.left = "calc(".concat(a / c.width * 100, "% - ").concat(r.offsetWidth / 2, "px)")), "v" !== i && (r.style.top = "calc(".concat(l / c.height * 100, "% - ").concat(r.offsetHeight / 2, "px)")), e.cache = {
376 | x: a / c.width,
377 | y: l / c.height
378 | };
379 | const p = E(a / c.width),
380 | u = E(l / c.height);
381 | switch (i) {
382 | case "v":
383 | return o.onchange(p);
384 | case "h":
385 | return o.onchange(u);
386 | default:
387 | return o.onchange(p, u)
388 | }
389 | },
390 | _tapstop() {
391 | e.options.onstop(), l(document, ["mouseup", "touchend", "touchcancel"], e._tapstop), l(document, ["mousemove", "touchmove"], e._tapmove)
392 | },
393 | trigger() {
394 | e._tapmove()
395 | },
396 | update(t = 0, o = 0) {
397 | const {
398 | left: n,
399 | top: i,
400 | width: r,
401 | height: s
402 | } = e.options.wrapper.getBoundingClientRect();
403 | "h" === e.options.lock && (o = t), e._tapmove({
404 | clientX: n + r * t,
405 | clientY: i + s * o
406 | })
407 | },
408 | destroy() {
409 | const {
410 | options: t,
411 | _tapstart: o,
412 | _keyboard: n
413 | } = e;
414 | l(document, ["keydown", "keyup"], n), l([t.wrapper, t.element], "mousedown", o), l([t.wrapper, t.element], "touchstart", o, {
415 | passive: !1
416 | })
417 | }
418 | },
419 | {
420 | options: o,
421 | _tapstart: n,
422 | _keyboard: i
423 | } = e;
424 | return a([o.wrapper, o.element], "mousedown", n), a([o.wrapper, o.element], "touchstart", n, {
425 | passive: !1
426 | }), a(document, ["keydown", "keyup"], i), e
427 | }
428 |
429 | function P(t = {}) {
430 | t = Object.assign({
431 | onchange: () => 0,
432 | className: "",
433 | elements: []
434 | }, t);
435 | const e = a(t.elements, "click", e => {
436 | t.elements.forEach(o => o.classList[e.target === o ? "add" : "remove"](t.className)), t.onchange(e)
437 | });
438 | return {
439 | destroy: () => l(...e)
440 | }
441 | }
442 |
443 | function B({
444 | el: t,
445 | reference: e,
446 | padding: o = 8
447 | }) {
448 | const n = {
449 | start: "sme",
450 | middle: "mse",
451 | end: "ems"
452 | },
453 | i = {
454 | top: "tbrl",
455 | right: "rltb",
456 | bottom: "btrl",
457 | left: "lrbt"
458 | },
459 | r = ((t = {}) => (e, o = t[e]) => {
460 | if (o) return o;
461 | const [n, i = "middle"] = e.split("-"), r = "top" === n || "bottom" === n;
462 | return t[e] = {
463 | position: n,
464 | variant: i,
465 | isVertical: r
466 | }
467 | })();
468 | return {
469 | update(s, c = !1) {
470 | const {
471 | position: a,
472 | variant: l,
473 | isVertical: p
474 | } = r(s), u = e.getBoundingClientRect(), h = t.getBoundingClientRect(), d = t => t ? {
475 | t: u.top - h.height - o,
476 | b: u.bottom + o
477 | } : {
478 | r: u.right + o,
479 | l: u.left - h.width - o
480 | }, f = t => t ? {
481 | s: u.left + u.width - h.width,
482 | m: -h.width / 2 + (u.left + u.width / 2),
483 | e: u.left
484 | } : {
485 | s: u.bottom - h.height,
486 | m: u.bottom - u.height / 2 - h.height / 2,
487 | e: u.bottom - u.height
488 | }, m = {}, v = (t, e, o) => {
489 | const n = "top" === o,
490 | i = n ? h.height : h.width,
491 | r = window[n ? "innerHeight" : "innerWidth"];
492 | for (const n of t) {
493 | const t = e[n],
494 | s = m[o] = "".concat(t, "px");
495 | if (t > 0 && t + i < r) return s
496 | }
497 | return null
498 | };
499 | for (const e of [p, !p]) {
500 | const o = e ? "top" : "left",
501 | r = e ? "left" : "top",
502 | s = v(i[a], d(e), o),
503 | c = v(n[l], f(e), r);
504 | if (s && c) return t.style[r] = c, void(t.style[o] = s)
505 | }
506 | c ? (t.style.top = "".concat((window.innerHeight - h.height) / 2, "px"), t.style.left = "".concat((window.innerWidth - h.width) / 2, "px")) : (t.style.left = m.left, t.style.top = m.top)
507 | }
508 | }
509 | }
510 |
511 | function H(t, e, o) {
512 | return e in t ? Object.defineProperty(t, e, {
513 | value: o,
514 | enumerable: !0,
515 | configurable: !0,
516 | writable: !0
517 | }) : t[e] = o, t
518 | }
519 | class R {
520 | constructor(t) {
521 | H(this, "_initializingActive", !0), H(this, "_recalc", !0), H(this, "_nanopop", null), H(this, "_root", null), H(this, "_color", x()), H(this, "_lastColor", x()), H(this, "_swatchColors", []), H(this, "_eventListener", {
522 | init: [],
523 | save: [],
524 | hide: [],
525 | show: [],
526 | clear: [],
527 | change: [],
528 | changestop: [],
529 | cancel: [],
530 | swatchselect: []
531 | }), this.options = t = Object.assign({
532 | appClass: null,
533 | theme: "classic",
534 | useAsButton: !1,
535 | padding: 8,
536 | disabled: !1,
537 | comparison: !0,
538 | closeOnScroll: !1,
539 | outputPrecision: 0,
540 | lockOpacity: !1,
541 | autoReposition: !0,
542 | container: "body",
543 | components: {
544 | interaction: {}
545 | },
546 | strings: {},
547 | swatches: null,
548 | inline: !1,
549 | sliders: null,
550 | default: "#42445a",
551 | defaultRepresentation: null,
552 | position: "bottom-middle",
553 | adjustableNumbers: !0,
554 | showAlways: !1,
555 | closeWithKey: "Escape"
556 | }, t);
557 | const {
558 | swatches: e,
559 | components: o,
560 | theme: n,
561 | sliders: i,
562 | lockOpacity: r,
563 | padding: s
564 | } = t;
565 | ["nano", "monolith"].includes(n) && !i && (t.sliders = "h"), o.interaction || (o.interaction = {});
566 | const {
567 | preview: c,
568 | opacity: a,
569 | hue: l,
570 | palette: p
571 | } = o;
572 | o.opacity = !r && a, o.palette = p || c || a || l, this._preBuild(), this._buildComponents(), this._bindEvents(), this._finalBuild(), e && e.length && e.forEach(t => this.addSwatch(t));
573 | const {
574 | button: u,
575 | app: h
576 | } = this._root;
577 | this._nanopop = B({
578 | reference: u,
579 | padding: s,
580 | el: h
581 | }), u.setAttribute("role", "button"), u.setAttribute("aria-label", "toggle color picker dialog");
582 | const d = this;
583 | requestAnimationFrame((function e() {
584 | if (!h.offsetWidth && h.parentElement !== t.container) return requestAnimationFrame(e);
585 | d.setColor(t.default), d._rePositioningPicker(), t.defaultRepresentation && (d._representation = t.defaultRepresentation, d.setColorRepresentation(d._representation)), t.showAlways && d.show(), d._initializingActive = !1, d._emit("init")
586 | }))
587 | }
588 | _preBuild() {
589 | const t = this.options;
590 | for (const e of ["el", "container"]) t[e] = f(t[e]);
591 | this._root = (({
592 | components: t,
593 | strings: e,
594 | useAsButton: o,
595 | inline: n,
596 | appClass: i,
597 | theme: r,
598 | lockOpacity: s
599 | }) => {
600 | const c = t => t ? "" : 'style="display:none" hidden',
601 | a = h('\n \n\n '.concat(o ? "" : '
', '\n\n
\n
\n ')),
602 | l = a.interaction;
603 | return l.options.find(t => !t.hidden && !t.classList.add("active")), l.type = () => l.options.find(t => t.classList.contains("active")), a
604 | })(t), t.useAsButton && (this._root.button = t.el), t.container.appendChild(this._root.root)
605 | }
606 | _finalBuild() {
607 | const t = this.options,
608 | e = this._root;
609 | if (t.container.removeChild(e.root), t.inline) {
610 | const o = t.el.parentElement;
611 | t.el.nextSibling ? o.insertBefore(e.app, t.el.nextSibling) : o.appendChild(e.app)
612 | } else t.container.appendChild(e.app);
613 | t.useAsButton ? t.inline && t.el.remove() : t.el.parentNode.replaceChild(e.root, t.el), t.disabled && this.disable(), t.comparison || (e.button.style.transition = "none", t.useAsButton || (e.preview.lastColor.style.transition = "none")), this.hide()
614 | }
615 | _buildComponents() {
616 | const t = this,
617 | e = this.options.components,
618 | o = (t.options.sliders || "v").repeat(2),
619 | [n, i] = o.match(/^[vh]+$/g) ? o : [],
620 | r = () => this._color || (this._color = this._lastColor.clone()),
621 | s = {
622 | palette: L({
623 | element: t._root.palette.picker,
624 | wrapper: t._root.palette.palette,
625 | onstop: () => t._emit("changestop", t),
626 | onchange(o, n) {
627 | if (!e.palette) return;
628 | const i = r(),
629 | {
630 | _root: s,
631 | options: c
632 | } = t,
633 | {
634 | lastColor: a,
635 | currentColor: l
636 | } = s.preview;
637 | t._recalc && (i.s = 100 * o, i.v = 100 - 100 * n, i.v < 0 && (i.v = 0), t._updateOutput());
638 | const p = i.toRGBA().toString(0);
639 | this.element.style.background = p, this.wrapper.style.background = "\n linear-gradient(to top, rgba(0, 0, 0, ".concat(i.a, "), transparent),\n linear-gradient(to left, hsla(").concat(i.h, ", 100%, 50%, ").concat(i.a, "), rgba(255, 255, 255, ").concat(i.a, "))\n "), c.comparison ? c.useAsButton || t._lastColor || (a.style.color = p) : (s.button.style.color = p, s.button.classList.remove("clear"));
640 | const u = i.toHEXA().toString();
641 | for (const {
642 | el: e,
643 | color: o
644 | } of t._swatchColors) e.classList[u === o.toHEXA().toString() ? "add" : "remove"]("pcr-active");
645 | l.style.color = p
646 | }
647 | }),
648 | hue: L({
649 | lock: "v" === i ? "h" : "v",
650 | element: t._root.hue.picker,
651 | wrapper: t._root.hue.slider,
652 | onstop: () => t._emit("changestop", t),
653 | onchange(o) {
654 | if (!e.hue || !e.palette) return;
655 | const n = r();
656 | t._recalc && (n.h = 360 * o), this.element.style.backgroundColor = "hsl(".concat(n.h, ", 100%, 50%)"), s.palette.trigger()
657 | }
658 | }),
659 | opacity: L({
660 | lock: "v" === n ? "h" : "v",
661 | element: t._root.opacity.picker,
662 | wrapper: t._root.opacity.slider,
663 | onstop: () => t._emit("changestop", t),
664 | onchange(o) {
665 | if (!e.opacity || !e.palette) return;
666 | const n = r();
667 | t._recalc && (n.a = Math.round(100 * o) / 100), this.element.style.background = "rgba(0, 0, 0, ".concat(n.a, ")"), s.palette.trigger()
668 | }
669 | }),
670 | selectable: P({
671 | elements: t._root.interaction.options,
672 | className: "active",
673 | onchange(e) {
674 | t._representation = e.target.getAttribute("data-type").toUpperCase(), t._recalc && t._updateOutput()
675 | }
676 | })
677 | };
678 | this._components = s
679 | }
680 | _bindEvents() {
681 | const {
682 | _root: t,
683 | options: e
684 | } = this, o = [a(t.interaction.clear, "click", () => this._clearColor()), a([t.interaction.cancel, t.preview.lastColor], "click", () => {
685 | this._emit("cancel", this), this.setHSVA(...(this._lastColor || this._color).toHSVA(), !0)
686 | }), a(t.interaction.save, "click", () => {
687 | !this.applyColor() && !e.showAlways && this.hide()
688 | }), a(t.interaction.result, ["keyup", "input"], t => {
689 | this.setColor(t.target.value, !0) && !this._initializingActive && this._emit("change", this._color), t.stopImmediatePropagation()
690 | }), a(t.interaction.result, ["focus", "blur"], t => {
691 | this._recalc = "blur" === t.type, this._recalc && this._updateOutput()
692 | }), a([t.palette.palette, t.palette.picker, t.hue.slider, t.hue.picker, t.opacity.slider, t.opacity.picker], ["mousedown", "touchstart"], () => this._recalc = !0)];
693 | if (!e.showAlways) {
694 | const n = e.closeWithKey;
695 | o.push(a(t.button, "click", () => this.isOpen() ? this.hide() : this.show()), a(document, "keyup", t => this.isOpen() && (t.key === n || t.code === n) && this.hide()), a(document, ["touchstart", "mousedown"], e => {
696 | this.isOpen() && !d(e).some(e => e === t.app || e === t.button) && this.hide()
697 | }, {
698 | capture: !0
699 | }))
700 | }
701 | if (e.adjustableNumbers) {
702 | const e = {
703 | rgba: [255, 255, 255, 1],
704 | hsva: [360, 100, 100, 1],
705 | hsla: [360, 100, 100, 1],
706 | cmyk: [100, 100, 100, 100]
707 | };
708 | m(t.interaction.result, (t, o, n) => {
709 | const i = e[this.getColorRepresentation().toLowerCase()];
710 | if (i) {
711 | const e = i[n],
712 | r = t + (e >= 100 ? 1e3 * o : o);
713 | return r <= 0 ? 0 : Number((r < e ? r : e).toPrecision(3))
714 | }
715 | return t
716 | })
717 | }
718 | if (e.autoReposition && !e.inline) {
719 | let t = null;
720 | const n = this;
721 | o.push(a(window, ["scroll", "resize"], () => {
722 | n.isOpen() && (e.closeOnScroll && n.hide(), null === t ? (t = setTimeout(() => t = null, 100), requestAnimationFrame((function e() {
723 | n._rePositioningPicker(), null !== t && requestAnimationFrame(e)
724 | }))) : (clearTimeout(t), t = setTimeout(() => t = null, 100)))
725 | }, {
726 | capture: !0
727 | }))
728 | }
729 | this._eventBindings = o
730 | }
731 | _rePositioningPicker() {
732 | const {
733 | options: t
734 | } = this;
735 | t.inline || this._nanopop.update(t.position, !this._recalc)
736 | }
737 | _updateOutput() {
738 | const {
739 | _root: t,
740 | _color: e,
741 | options: o
742 | } = this;
743 | if (t.interaction.type()) {
744 | const n = "to".concat(t.interaction.type().getAttribute("data-type"));
745 | t.interaction.result.value = "function" == typeof e[n] ? e[n]().toString(o.outputPrecision) : ""
746 | }!this._initializingActive && this._recalc && this._emit("change", e)
747 | }
748 | _clearColor(t = !1) {
749 | const {
750 | _root: e,
751 | options: o
752 | } = this;
753 | o.useAsButton || (e.button.style.color = "rgba(0, 0, 0, 0.15)"), e.button.classList.add("clear"), o.showAlways || this.hide(), this._lastColor = null, this._initializingActive || t || (this._emit("save", null), this._emit("clear", this))
754 | }
755 | _parseLocalColor(t) {
756 | const {
757 | values: e,
758 | type: o,
759 | a: n
760 | } = j(t), {
761 | lockOpacity: i
762 | } = this.options, r = void 0 !== n && 1 !== n;
763 | return e && 3 === e.length && (e[3] = void 0), {
764 | values: !e || i && r ? null : e,
765 | type: o
766 | }
767 | }
768 | _emit(t, ...e) {
769 | this._eventListener[t].forEach(t => t(...e, this))
770 | }
771 | on(t, e) {
772 | return "function" == typeof e && "string" == typeof t && t in this._eventListener && this._eventListener[t].push(e), this
773 | }
774 | off(t, e) {
775 | const o = this._eventListener[t];
776 | if (o) {
777 | const t = o.indexOf(e);
778 | ~t && o.splice(t, 1)
779 | }
780 | return this
781 | }
782 | addSwatch(t) {
783 | const {
784 | values: e
785 | } = this._parseLocalColor(t);
786 | if (e) {
787 | const {
788 | _swatchColors: t,
789 | _root: o
790 | } = this, n = x(...e), i = p(''));
791 | return o.swatches.appendChild(i), t.push({
792 | el: i,
793 | color: n
794 | }), this._eventBindings.push(a(i, "click", () => {
795 | this.setHSVA(...n.toHSVA(), !0), this._emit("swatchselect", n), this._emit("change", n)
796 | })), !0
797 | }
798 | return !1
799 | }
800 | removeSwatch(t) {
801 | const e = this._swatchColors[t];
802 | if (e) {
803 | const {
804 | el: o
805 | } = e;
806 | return this._root.swatches.removeChild(o), this._swatchColors.splice(t, 1), !0
807 | }
808 | return !1
809 | }
810 | applyColor(t = !1) {
811 | const {
812 | preview: e,
813 | button: o
814 | } = this._root, n = this._color.toRGBA().toString(0);
815 | return e.lastColor.style.color = n, this.options.useAsButton || (o.style.color = n), o.classList.remove("clear"), this._lastColor = this._color.clone(), this._initializingActive || t || this._emit("save", this._color), this
816 | }
817 | destroy() {
818 | this._eventBindings.forEach(t => l(...t)), Object.keys(this._components).forEach(t => this._components[t].destroy())
819 | }
820 | destroyAndRemove() {
821 | this.destroy();
822 | const {
823 | root: t,
824 | app: e
825 | } = this._root;
826 | t.parentElement && t.parentElement.removeChild(t), e.parentElement.removeChild(e), Object.keys(this).forEach(t => this[t] = null)
827 | }
828 | hide() {
829 | return this._root.app.classList.remove("visible"), this._emit("hide", this), this
830 | }
831 | show() {
832 | return this.options.disabled || (this._root.app.classList.add("visible"), this._rePositioningPicker(), this._emit("show", this)), this
833 | }
834 | isOpen() {
835 | return this._root.app.classList.contains("visible")
836 | }
837 | setHSVA(t = 360, e = 0, o = 0, n = 1, i = !1) {
838 | const r = this._recalc;
839 | if (this._recalc = !1, t < 0 || t > 360 || e < 0 || e > 100 || o < 0 || o > 100 || n < 0 || n > 1) return !1;
840 | this._color = x(t, e, o, n);
841 | const {
842 | hue: s,
843 | opacity: c,
844 | palette: a
845 | } = this._components;
846 | return s.update(t / 360), c.update(n), a.update(e / 100, 1 - o / 100), i || this.applyColor(), r && this._updateOutput(), this._recalc = r, !0
847 | }
848 | setColor(t, e = !1) {
849 | if (null === t) return this._clearColor(e), !0;
850 | const {
851 | values: o,
852 | type: n
853 | } = this._parseLocalColor(t);
854 | if (o) {
855 | const t = n.toUpperCase(),
856 | {
857 | options: i
858 | } = this._root.interaction,
859 | r = i.find(e => e.getAttribute("data-type") === t);
860 | if (r && !r.hidden)
861 | for (const t of i) t.classList[t === r ? "add" : "remove"]("active");
862 | return !!this.setHSVA(...o, e) && this.setColorRepresentation(t)
863 | }
864 | return !1
865 | }
866 | setColorRepresentation(t) {
867 | return t = t.toUpperCase(), !!this._root.interaction.options.find(e => e.getAttribute("data-type").startsWith(t) && !e.click())
868 | }
869 | getColorRepresentation() {
870 | return this._representation
871 | }
872 | getColor() {
873 | return this._color
874 | }
875 | getSelectedColor() {
876 | return this._lastColor
877 | }
878 | getRoot() {
879 | return this._root
880 | }
881 | disable() {
882 | return this.hide(), this.options.disabled = !0, this._root.button.classList.add("disabled"), this
883 | }
884 | enable() {
885 | return this.options.disabled = !1, this._root.button.classList.remove("disabled"), this
886 | }
887 | }
888 | R.utils = n, R.libs = {
889 | HSVaColor: x,
890 | Moveable: L,
891 | Nanopop: B,
892 | Selectable: P
893 | }, R.create = t => new R(t), R.version = v.a;
894 | e.default = R
895 | }]).default
896 | }));
897 | //# sourceMappingURL=pickr.min.js.map
--------------------------------------------------------------------------------
/webview/pointer.js:
--------------------------------------------------------------------------------
1 | /*
2 | pointer.js was created by OwL for use on websites,
3 | and can be found at https://seattleowl.com/pointer.
4 | */
5 |
6 | const pointer = document.createElement("div")
7 | pointer.id = "pointer-dot"
8 | const ring = document.createElement("div")
9 | ring.id = "pointer-ring"
10 | document.body.insertBefore(pointer, document.body.children[0])
11 | document.body.insertBefore(ring, document.body.children[0])
12 |
13 | let mouseX = -100
14 | let mouseY = -100
15 | let ringX = -100
16 | let ringY = -100
17 | let isHover = false
18 | let mouseDown = false
19 | const init_pointer = (options) => {
20 |
21 | window.onmousemove = (mouse) => {
22 | mouseX = mouse.clientX
23 | mouseY = mouse.clientY
24 | }
25 |
26 | window.onmousedown = (mouse) => {
27 | mouseDown = true
28 | }
29 |
30 | window.onmouseup = (mouse) => {
31 | mouseDown = false
32 | }
33 |
34 | const trace = (a, b, n) => {
35 | return (1 - n) * a + n * b;
36 | }
37 | window["trace"] = trace
38 |
39 | const getOption = (option) => {
40 | let defaultObj = {
41 | pointerColor: "#750c7e",
42 | ringSize: 15,
43 | ringClickSize: (options["ringSize"] || 15) - 5,
44 | }
45 | if (options[option] == undefined) {
46 | return defaultObj[option]
47 | } else {
48 | return options[option]
49 | }
50 | }
51 |
52 | const render = () => {
53 | ringX = trace(ringX, mouseX, 0.2)
54 | ringY = trace(ringY, mouseY, 0.2)
55 |
56 | if (document.querySelector(".p-action-click:hover")) {
57 | pointer.style.borderColor = getOption("pointerColor")
58 | isHover = true
59 | } else {
60 | pointer.style.borderColor = "white"
61 | isHover = false
62 | }
63 | ring.style.borderColor = getOption("pointerColor")
64 | if (mouseDown) {
65 | ring.style.padding = getOption("ringClickSize") + "px"
66 | } else {
67 | ring.style.padding = getOption("ringSize") + "px"
68 | }
69 |
70 | pointer.style.transform = `translate(${mouseX}px, ${mouseY}px)`
71 | ring.style.transform = `translate(${ringX - (mouseDown ? getOption("ringClickSize") : getOption("ringSize"))}px, ${ringY - (mouseDown ? getOption("ringClickSize") : getOption("ringSize"))}px)`
72 |
73 | requestAnimationFrame(render)
74 | }
75 | requestAnimationFrame(render)
76 | }
--------------------------------------------------------------------------------
/webview/vivus.js:
--------------------------------------------------------------------------------
1 | /**
2 | * vivus - JavaScript library to make drawing animation on SVG
3 | * @version v0.4.2
4 | * @link https://github.com/maxwellito/vivus
5 | * @license MIT
6 | */
7 | "use strict";
8 | !(function() {
9 | function t(t) {
10 | if ("undefined" == typeof t)
11 | throw new Error(
12 | 'Pathformer [constructor]: "element" parameter is required'
13 | );
14 | if (t.constructor === String && ((t = document.getElementById(t)), !t))
15 | throw new Error(
16 | 'Pathformer [constructor]: "element" parameter is not related to an existing ID'
17 | );
18 | if (
19 | !(
20 | t instanceof window.SVGElement ||
21 | t instanceof window.SVGGElement ||
22 | /^svg$/i.test(t.nodeName)
23 | )
24 | )
25 | throw new Error(
26 | 'Pathformer [constructor]: "element" parameter must be a string or a SVGelement'
27 | );
28 | (this.el = t), this.scan(t);
29 | }
30 | function e(t, e, n) {
31 | r(),
32 | (this.isReady = !1),
33 | this.setElement(t, e),
34 | this.setOptions(e),
35 | this.setCallback(n),
36 | this.isReady && this.init();
37 | }
38 | (t.prototype.TYPES = [
39 | "line",
40 | "ellipse",
41 | "circle",
42 | "polygon",
43 | "polyline",
44 | "rect"
45 | ]),
46 | (t.prototype.ATTR_WATCH = [
47 | "cx",
48 | "cy",
49 | "points",
50 | "r",
51 | "rx",
52 | "ry",
53 | "x",
54 | "x1",
55 | "x2",
56 | "y",
57 | "y1",
58 | "y2"
59 | ]),
60 | (t.prototype.scan = function(t) {
61 | for (
62 | var e, r, n, i, a = t.querySelectorAll(this.TYPES.join(",")), o = 0;
63 | o < a.length;
64 | o++
65 | )
66 | (r = a[o]),
67 | (e = this[r.tagName.toLowerCase() + "ToPath"]),
68 | (n = e(this.parseAttr(r.attributes))),
69 | (i = this.pathMaker(r, n)),
70 | r.parentNode.replaceChild(i, r);
71 | }),
72 | (t.prototype.lineToPath = function(t) {
73 | var e = {},
74 | r = t.x1 || 0,
75 | n = t.y1 || 0,
76 | i = t.x2 || 0,
77 | a = t.y2 || 0;
78 | return (e.d = "M" + r + "," + n + "L" + i + "," + a), e;
79 | }),
80 | (t.prototype.rectToPath = function(t) {
81 | var e = {},
82 | r = parseFloat(t.x) || 0,
83 | n = parseFloat(t.y) || 0,
84 | i = parseFloat(t.width) || 0,
85 | a = parseFloat(t.height) || 0;
86 | if (t.rx || t.ry) {
87 | var o = parseInt(t.rx, 10) || -1,
88 | s = parseInt(t.ry, 10) || -1;
89 | (o = Math.min(Math.max(0 > o ? s : o, 0), i / 2)),
90 | (s = Math.min(Math.max(0 > s ? o : s, 0), a / 2)),
91 | (e.d =
92 | "M " +
93 | (r + o) +
94 | "," +
95 | n +
96 | " L " +
97 | (r + i - o) +
98 | "," +
99 | n +
100 | " A " +
101 | o +
102 | "," +
103 | s +
104 | ",0,0,1," +
105 | (r + i) +
106 | "," +
107 | (n + s) +
108 | " L " +
109 | (r + i) +
110 | "," +
111 | (n + a - s) +
112 | " A " +
113 | o +
114 | "," +
115 | s +
116 | ",0,0,1," +
117 | (r + i - o) +
118 | "," +
119 | (n + a) +
120 | " L " +
121 | (r + o) +
122 | "," +
123 | (n + a) +
124 | " A " +
125 | o +
126 | "," +
127 | s +
128 | ",0,0,1," +
129 | r +
130 | "," +
131 | (n + a - s) +
132 | " L " +
133 | r +
134 | "," +
135 | (n + s) +
136 | " A " +
137 | o +
138 | "," +
139 | s +
140 | ",0,0,1," +
141 | (r + o) +
142 | "," +
143 | n);
144 | } else
145 | e.d =
146 | "M" +
147 | r +
148 | " " +
149 | n +
150 | " L" +
151 | (r + i) +
152 | " " +
153 | n +
154 | " L" +
155 | (r + i) +
156 | " " +
157 | (n + a) +
158 | " L" +
159 | r +
160 | " " +
161 | (n + a) +
162 | " Z";
163 | return e;
164 | }),
165 | (t.prototype.polylineToPath = function(t) {
166 | var e,
167 | r,
168 | n = {},
169 | i = t.points.trim().split(" ");
170 | if (-1 === t.points.indexOf(",")) {
171 | var a = [];
172 | for (e = 0; e < i.length; e += 2) a.push(i[e] + "," + i[e + 1]);
173 | i = a;
174 | }
175 | for (r = "M" + i[0], e = 1; e < i.length; e++)
176 | -1 !== i[e].indexOf(",") && (r += "L" + i[e]);
177 | return (n.d = r), n;
178 | }),
179 | (t.prototype.polygonToPath = function(e) {
180 | var r = t.prototype.polylineToPath(e);
181 | return (r.d += "Z"), r;
182 | }),
183 | (t.prototype.ellipseToPath = function(t) {
184 | var e = {},
185 | r = parseFloat(t.rx) || 0,
186 | n = parseFloat(t.ry) || 0,
187 | i = parseFloat(t.cx) || 0,
188 | a = parseFloat(t.cy) || 0,
189 | o = i - r,
190 | s = a,
191 | h = parseFloat(i) + parseFloat(r),
192 | l = a;
193 | return (
194 | (e.d =
195 | "M" +
196 | o +
197 | "," +
198 | s +
199 | "A" +
200 | r +
201 | "," +
202 | n +
203 | " 0,1,1 " +
204 | h +
205 | "," +
206 | l +
207 | "A" +
208 | r +
209 | "," +
210 | n +
211 | " 0,1,1 " +
212 | o +
213 | "," +
214 | l),
215 | e
216 | );
217 | }),
218 | (t.prototype.circleToPath = function(t) {
219 | var e = {},
220 | r = parseFloat(t.r) || 0,
221 | n = parseFloat(t.cx) || 0,
222 | i = parseFloat(t.cy) || 0,
223 | a = n - r,
224 | o = i,
225 | s = parseFloat(n) + parseFloat(r),
226 | h = i;
227 | return (
228 | (e.d =
229 | "M" +
230 | a +
231 | "," +
232 | o +
233 | "A" +
234 | r +
235 | "," +
236 | r +
237 | " 0,1,1 " +
238 | s +
239 | "," +
240 | h +
241 | "A" +
242 | r +
243 | "," +
244 | r +
245 | " 0,1,1 " +
246 | a +
247 | "," +
248 | h),
249 | e
250 | );
251 | }),
252 | (t.prototype.pathMaker = function(t, e) {
253 | var r,
254 | n,
255 | i = document.createElementNS("http://www.w3.org/2000/svg", "path");
256 | for (r = 0; r < t.attributes.length; r++)
257 | (n = t.attributes[r]),
258 | -1 === this.ATTR_WATCH.indexOf(n.name) &&
259 | i.setAttribute(n.name, n.value);
260 | for (r in e) i.setAttribute(r, e[r]);
261 | return i;
262 | }),
263 | (t.prototype.parseAttr = function(t) {
264 | for (var e, r = {}, n = 0; n < t.length; n++) {
265 | if (
266 | ((e = t[n]),
267 | -1 !== this.ATTR_WATCH.indexOf(e.name) && -1 !== e.value.indexOf("%"))
268 | )
269 | throw new Error(
270 | "Pathformer [parseAttr]: a SVG shape got values in percentage. This cannot be transformed into 'path' tags. Please use 'viewBox'."
271 | );
272 | r[e.name] = e.value;
273 | }
274 | return r;
275 | });
276 | var r, n, i, a;
277 | (e.LINEAR = function(t) {
278 | return t;
279 | }),
280 | (e.EASE = function(t) {
281 | return -Math.cos(t * Math.PI) / 2 + 0.5;
282 | }),
283 | (e.EASE_OUT = function(t) {
284 | return 1 - Math.pow(1 - t, 3);
285 | }),
286 | (e.EASE_IN = function(t) {
287 | return Math.pow(t, 3);
288 | }),
289 | (e.EASE_OUT_BOUNCE = function(t) {
290 | var e = -Math.cos(0.5 * t * Math.PI) + 1,
291 | r = Math.pow(e, 1.5),
292 | n = Math.pow(1 - t, 2),
293 | i = -Math.abs(Math.cos(2.5 * r * Math.PI)) + 1;
294 | return 1 - n + i * n;
295 | }),
296 | (e.prototype.setElement = function(t, e) {
297 | if ("undefined" == typeof t)
298 | throw new Error('Vivus [constructor]: "element" parameter is required');
299 | if (t.constructor === String && ((t = document.getElementById(t)), !t))
300 | throw new Error(
301 | 'Vivus [constructor]: "element" parameter is not related to an existing ID'
302 | );
303 | if (((this.parentEl = t), e && e.file)) {
304 | var r = document.createElement("object");
305 | r.setAttribute("type", "image/svg+xml"),
306 | r.setAttribute("data", e.file),
307 | r.setAttribute("built-by-vivus", "true"),
308 | t.appendChild(r),
309 | (t = r);
310 | }
311 | switch (t.constructor) {
312 | case window.SVGSVGElement:
313 | case window.SVGElement:
314 | case window.SVGGElement:
315 | (this.el = t), (this.isReady = !0);
316 | break;
317 | case window.HTMLObjectElement:
318 | var n, i;
319 | (i = this),
320 | (n = function(e) {
321 | if (!i.isReady) {
322 | if (
323 | ((i.el =
324 | t.contentDocument &&
325 | t.contentDocument.querySelector("svg")),
326 | !i.el && e)
327 | )
328 | throw new Error(
329 | "Vivus [constructor]: object loaded does not contain any SVG"
330 | );
331 | return i.el
332 | ? (t.getAttribute("built-by-vivus") &&
333 | (i.parentEl.insertBefore(i.el, t),
334 | i.parentEl.removeChild(t),
335 | i.el.setAttribute("width", "100%"),
336 | i.el.setAttribute("height", "100%")),
337 | (i.isReady = !0),
338 | i.init(),
339 | !0)
340 | : void 0;
341 | }
342 | }),
343 | n() || t.addEventListener("load", n);
344 | break;
345 | default:
346 | throw new Error(
347 | 'Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)'
348 | );
349 | }
350 | }),
351 | (e.prototype.setOptions = function(t) {
352 | var r = [
353 | "delayed",
354 | "sync",
355 | "async",
356 | "nsync",
357 | "oneByOne",
358 | "scenario",
359 | "scenario-sync"
360 | ],
361 | n = ["inViewport", "manual", "autostart"];
362 | if (void 0 !== t && t.constructor !== Object)
363 | throw new Error(
364 | 'Vivus [constructor]: "options" parameter must be an object'
365 | );
366 | if (((t = t || {}), t.type && -1 === r.indexOf(t.type)))
367 | throw new Error(
368 | "Vivus [constructor]: " +
369 | t.type +
370 | " is not an existing animation `type`"
371 | );
372 | if (((this.type = t.type || r[0]), t.start && -1 === n.indexOf(t.start)))
373 | throw new Error(
374 | "Vivus [constructor]: " +
375 | t.start +
376 | " is not an existing `start` option"
377 | );
378 | if (
379 | ((this.start = t.start || n[0]),
380 | (this.isIE =
381 | -1 !== window.navigator.userAgent.indexOf("MSIE") ||
382 | -1 !== window.navigator.userAgent.indexOf("Trident/") ||
383 | -1 !== window.navigator.userAgent.indexOf("Edge/")),
384 | (this.duration = a(t.duration, 120)),
385 | (this.delay = a(t.delay, null)),
386 | (this.dashGap = a(t.dashGap, 1)),
387 | (this.forceRender = t.hasOwnProperty("forceRender")
388 | ? !!t.forceRender
389 | : this.isIE),
390 | (this.reverseStack = !!t.reverseStack),
391 | (this.selfDestroy = !!t.selfDestroy),
392 | (this.onReady = t.onReady),
393 | (this.map = []),
394 | (this.frameLength = this.currentFrame = this.delayUnit = this.speed = this.handle = null),
395 | (this.ignoreInvisible = t.hasOwnProperty("ignoreInvisible")
396 | ? !!t.ignoreInvisible
397 | : !1),
398 | (this.animTimingFunction = t.animTimingFunction || e.LINEAR),
399 | (this.pathTimingFunction = t.pathTimingFunction || e.LINEAR),
400 | this.delay >= this.duration)
401 | )
402 | throw new Error(
403 | "Vivus [constructor]: delay must be shorter than duration"
404 | );
405 | }),
406 | (e.prototype.setCallback = function(t) {
407 | if (t && t.constructor !== Function)
408 | throw new Error(
409 | 'Vivus [constructor]: "callback" parameter must be a function'
410 | );
411 | this.callback = t || function() {};
412 | }),
413 | (e.prototype.mapping = function() {
414 | var t, e, r, n, i, o, s, h;
415 | for (
416 | h = o = s = 0, e = this.el.querySelectorAll("path"), t = 0;
417 | t < e.length;
418 | t++
419 | )
420 | (r = e[t]),
421 | this.isInvisible(r) ||
422 | ((i = { el: r, length: Math.ceil(r.getTotalLength()) }),
423 | isNaN(i.length)
424 | ? window.console &&
425 | console.warn &&
426 | console.warn(
427 | "Vivus [mapping]: cannot retrieve a path element length",
428 | r
429 | )
430 | : (this.map.push(i),
431 | (r.style.strokeDasharray =
432 | i.length + " " + (i.length + 2 * this.dashGap)),
433 | (r.style.strokeDashoffset = i.length + this.dashGap),
434 | (i.length += this.dashGap),
435 | (o += i.length),
436 | this.renderPath(t)));
437 | for (
438 | o = 0 === o ? 1 : o,
439 | this.delay = null === this.delay ? this.duration / 3 : this.delay,
440 | this.delayUnit = this.delay / (e.length > 1 ? e.length - 1 : 1),
441 | this.reverseStack && this.map.reverse(),
442 | t = 0;
443 | t < this.map.length;
444 | t++
445 | ) {
446 | switch (((i = this.map[t]), this.type)) {
447 | case "delayed":
448 | (i.startAt = this.delayUnit * t),
449 | (i.duration = this.duration - this.delay);
450 | break;
451 | case "oneByOne":
452 | (i.startAt = (s / o) * this.duration),
453 | (i.duration = (i.length / o) * this.duration);
454 | break;
455 | case "sync":
456 | case "async":
457 | case "nsync":
458 | (i.startAt = 0), (i.duration = this.duration);
459 | break;
460 | case "scenario-sync":
461 | (r = i.el),
462 | (n = this.parseAttr(r)),
463 | (i.startAt = h + (a(n["data-delay"], this.delayUnit) || 0)),
464 | (i.duration = a(n["data-duration"], this.duration)),
465 | (h =
466 | void 0 !== n["data-async"]
467 | ? i.startAt
468 | : i.startAt + i.duration),
469 | (this.frameLength = Math.max(
470 | this.frameLength,
471 | i.startAt + i.duration
472 | ));
473 | break;
474 | case "scenario":
475 | (r = i.el),
476 | (n = this.parseAttr(r)),
477 | (i.startAt = a(n["data-start"], this.delayUnit) || 0),
478 | (i.duration = a(n["data-duration"], this.duration)),
479 | (this.frameLength = Math.max(
480 | this.frameLength,
481 | i.startAt + i.duration
482 | ));
483 | }
484 | (s += i.length), (this.frameLength = this.frameLength || this.duration);
485 | }
486 | }),
487 | (e.prototype.drawer = function() {
488 | var t = this;
489 | if (((this.currentFrame += this.speed), this.currentFrame <= 0))
490 | this.stop(), this.reset();
491 | else {
492 | if (!(this.currentFrame >= this.frameLength))
493 | return (
494 | this.trace(),
495 | (this.handle = n(function() {
496 | t.drawer();
497 | })),
498 | void 0
499 | );
500 | this.stop(),
501 | (this.currentFrame = this.frameLength),
502 | this.trace(),
503 | this.selfDestroy && this.destroy();
504 | }
505 | this.callback(this),
506 | this.instanceCallback &&
507 | (this.instanceCallback(this), (this.instanceCallback = null));
508 | }),
509 | (e.prototype.trace = function() {
510 | var t, e, r, n;
511 | for (
512 | n =
513 | this.animTimingFunction(this.currentFrame / this.frameLength) *
514 | this.frameLength,
515 | t = 0;
516 | t < this.map.length;
517 | t++
518 | )
519 | (r = this.map[t]),
520 | (e = (n - r.startAt) / r.duration),
521 | (e = this.pathTimingFunction(Math.max(0, Math.min(1, e)))),
522 | r.progress !== e &&
523 | ((r.progress = e),
524 | (r.el.style.strokeDashoffset = Math.floor(r.length * (1 - e))),
525 | this.renderPath(t));
526 | }),
527 | (e.prototype.renderPath = function(t) {
528 | if (this.forceRender && this.map && this.map[t]) {
529 | var e = this.map[t],
530 | r = e.el.cloneNode(!0);
531 | e.el.parentNode.replaceChild(r, e.el), (e.el = r);
532 | }
533 | }),
534 | (e.prototype.init = function() {
535 | (this.frameLength = 0),
536 | (this.currentFrame = 0),
537 | (this.map = []),
538 | new t(this.el),
539 | this.mapping(),
540 | this.starter(),
541 | this.onReady && this.onReady(this);
542 | }),
543 | (e.prototype.starter = function() {
544 | switch (this.start) {
545 | case "manual":
546 | return;
547 | case "autostart":
548 | this.play();
549 | break;
550 | case "inViewport":
551 | var t = this,
552 | e = function() {
553 | t.isInViewport(t.parentEl, 1) &&
554 | (t.play(), window.removeEventListener("scroll", e));
555 | };
556 | window.addEventListener("scroll", e), e();
557 | }
558 | }),
559 | (e.prototype.getStatus = function() {
560 | return 0 === this.currentFrame
561 | ? "start"
562 | : this.currentFrame === this.frameLength
563 | ? "end"
564 | : "progress";
565 | }),
566 | (e.prototype.reset = function() {
567 | return this.setFrameProgress(0);
568 | }),
569 | (e.prototype.finish = function() {
570 | return this.setFrameProgress(1);
571 | }),
572 | (e.prototype.setFrameProgress = function(t) {
573 | return (
574 | (t = Math.min(1, Math.max(0, t))),
575 | (this.currentFrame = Math.round(this.frameLength * t)),
576 | this.trace(),
577 | this
578 | );
579 | }),
580 | (e.prototype.play = function(t, e) {
581 | if (((this.instanceCallback = null), t && "function" == typeof t))
582 | (this.instanceCallback = t), (t = null);
583 | else if (t && "number" != typeof t)
584 | throw new Error("Vivus [play]: invalid speed");
585 | return (
586 | e &&
587 | "function" == typeof e &&
588 | !this.instanceCallback &&
589 | (this.instanceCallback = e),
590 | (this.speed = t || 1),
591 | this.handle || this.drawer(),
592 | this
593 | );
594 | }),
595 | (e.prototype.stop = function() {
596 | return this.handle && (i(this.handle), (this.handle = null)), this;
597 | }),
598 | (e.prototype.destroy = function() {
599 | this.stop();
600 | var t, e;
601 | for (t = 0; t < this.map.length; t++)
602 | (e = this.map[t]),
603 | (e.el.style.strokeDashoffset = null),
604 | (e.el.style.strokeDasharray = null),
605 | this.renderPath(t);
606 | }),
607 | (e.prototype.isInvisible = function(t) {
608 | var e,
609 | r = t.getAttribute("data-ignore");
610 | return null !== r
611 | ? "false" !== r
612 | : this.ignoreInvisible
613 | ? ((e = t.getBoundingClientRect()), !e.width && !e.height)
614 | : !1;
615 | }),
616 | (e.prototype.parseAttr = function(t) {
617 | var e,
618 | r = {};
619 | if (t && t.attributes)
620 | for (var n = 0; n < t.attributes.length; n++)
621 | (e = t.attributes[n]), (r[e.name] = e.value);
622 | return r;
623 | }),
624 | (e.prototype.isInViewport = function(t, e) {
625 | var r = this.scrollY(),
626 | n = r + this.getViewportH(),
627 | i = t.getBoundingClientRect(),
628 | a = i.height,
629 | o = r + i.top,
630 | s = o + a;
631 | return (e = e || 0), n >= o + a * e && s >= r;
632 | }),
633 | (e.prototype.getViewportH = function() {
634 | var t = this.docElem.clientHeight,
635 | e = window.innerHeight;
636 | return e > t ? e : t;
637 | }),
638 | (e.prototype.scrollY = function() {
639 | return window.pageYOffset || this.docElem.scrollTop;
640 | }),
641 | (r = function() {
642 | e.prototype.docElem ||
643 | ((e.prototype.docElem = window.document.documentElement),
644 | (n = (function() {
645 | return (
646 | window.requestAnimationFrame ||
647 | window.webkitRequestAnimationFrame ||
648 | window.mozRequestAnimationFrame ||
649 | window.oRequestAnimationFrame ||
650 | window.msRequestAnimationFrame ||
651 | function(t) {
652 | return window.setTimeout(t, 1e3 / 60);
653 | }
654 | );
655 | })()),
656 | (i = (function() {
657 | return (
658 | window.cancelAnimationFrame ||
659 | window.webkitCancelAnimationFrame ||
660 | window.mozCancelAnimationFrame ||
661 | window.oCancelAnimationFrame ||
662 | window.msCancelAnimationFrame ||
663 | function(t) {
664 | return window.clearTimeout(t);
665 | }
666 | );
667 | })()));
668 | }),
669 | (a = function(t, e) {
670 | var r = parseInt(t, 10);
671 | return r >= 0 ? r : e;
672 | }),
673 | "function" == typeof define && define.amd
674 | ? define([], function() {
675 | return e;
676 | })
677 | : "object" == typeof exports
678 | ? (module.exports = e)
679 | : (window.Vivus = e);
680 | })();
681 |
--------------------------------------------------------------------------------