├── .babelrc
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── CONTRIBUTING.md
├── ISSUES.md
├── LICENSE.md
├── README.md
├── _config.yml
├── logo.png
├── package-lock.json
├── package.json
├── snappy-0.5.0.vsix
├── src
├── __test__
│ └── enzyme.test.tsx
├── extension.ts
├── functions
│ ├── loadLiquidGauge.ts
│ ├── optimizeFunctions.ts
│ ├── traverseParseFunctions.ts
│ └── webpackFunctions.ts
├── style
│ └── styles.css
└── views
│ ├── App.tsx
│ ├── components
│ ├── Assets.tsx
│ ├── Form.tsx
│ ├── Visualizations.tsx
│ └── visuals
│ │ └── LiquidGauges.tsx
│ ├── index.tsx
│ └── tsconfig.json
├── tsconfig.json
├── tslint.json
├── vsc-extension-quickstart.md
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-typescript",
4 | "@babel/preset-env",
5 | "@babel/preset-react"
6 | ],
7 | "plugins": [
8 | "@babel/plugin-transform-modules-commonjs",
9 | "@babel/plugin-transform-runtime",
10 | "@babel/proposal-class-properties",
11 | "@babel/proposal-object-rest-spread"
12 | ]
13 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | out
3 | .DS_Store
4 | yarn.lock
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": [
5 | "ms-vscode.vscode-typescript-tslint-plugin"
6 | ]
7 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 |
9 | {
10 | "name": "Run Extension",
11 | "type": "extensionHost",
12 | "request": "launch",
13 | "runtimeExecutable": "${execPath}",
14 | "args": [
15 | "--extensionDevelopmentPath=${workspaceFolder}"
16 | ],
17 | "outFiles": [
18 | "${workspaceFolder}/out/**/*.js"
19 | ],
20 | "preLaunchTask": "npm: watch"
21 | },
22 | {
23 | "name": "Extension Tests",
24 | "type": "extensionHost",
25 | "request": "launch",
26 | "runtimeExecutable": "${execPath}",
27 | "args": [
28 | "--extensionDevelopmentPath=${workspaceFolder}",
29 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
30 | ],
31 | "outFiles": [
32 | "${workspaceFolder}/out/test/**/*.js"
33 | ],
34 | "preLaunchTask": "npm: watch"
35 | }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "off"
11 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "watch",
9 | "problemMatcher": "$tsc-watch",
10 | "isBackground": true,
11 | "presentation": {
12 | "reveal": "never"
13 | },
14 | "group": {
15 | "kind": "build",
16 | "isDefault": true
17 | }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/test/**
4 | src/**
5 | .gitignore
6 | vsc-extension-quickstart.md
7 | **/tsconfig.json
8 | **/tslint.json
9 | **/*.map
10 | **/*.ts
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing to snAppy
2 | We love your input! We want to make contributing to snAppy as easy and transparent as possible, whether it's:
3 |
4 | ### Reporting a bug
5 | Discussing the current state of the code
6 | Submitting a fix
7 | Proposing new features
8 | We Develop with Github
9 | We use Github to host code, to track issues and feature requests, as well as accept pull requests.
10 |
11 | All Code Changes Happen Through Pull Requests
12 | Pull requests are the best way to propose changes to snAppy. We actively welcome your pull requests:
13 |
14 | Fork the repo and create your branch from dev.
15 | If you've added code that should be tested, add tests.
16 | If you've changed APIs, update the documentation.
17 | Ensure the test suite passes.
18 | Make sure your code lints.
19 | Issue that pull request!
20 | Any contributions you make will be under the MIT Software License
21 | In short, when you submit code changes, your submissions are understood to be under the same that covers the project. Feel free to contact the maintainers if that's a concern.
22 |
23 | ### Report bugs using Github's issues
24 | We use GitHub issues to track public bugs. Report a bug by opening a new issue; it's that easy!
25 |
26 | Write bug reports with detail, background, and sample code
27 | Great Bug Reports tend to have:
28 |
29 | ### A quick summary and/or background
30 | Steps to reproduce
31 | Be specific!
32 | Give sample code if you can. Include sample code that anyone can run to reproduce what you are experiencing
33 | What you expected would happen
34 | What actually happens
35 | Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
36 | People love thorough bug reports. I'm not even kidding.
37 |
38 | ### Use a Consistent Coding Style
39 | 2 spaces for indentation rather than tabs
40 | You can try running npm run lint for style unification
41 |
42 | #### License
43 | By contributing, you agree that your contributions will be licensed under its MIT License.
44 |
45 | #### References
46 | This document was adapted from the open-source contribution guidelines for Facebook's Draft
47 |
--------------------------------------------------------------------------------
/ISSUES.md:
--------------------------------------------------------------------------------
1 | ## Issue Template
2 |
3 | ### Prerequisites
4 |
5 | Please answer the following questions for yourself before submitting an issue.
6 |
7 | I am running the latest version
8 | I checked the documentation and found no answer
9 | I checked to make sure that this issue has not already been filed
10 | I'm reporting the issue to the correct repository (for multi-repository projects)
11 |
12 | ### Expected Behavior
13 | Please describe the behavior you are expecting
14 |
15 | ### Current Behavior
16 | What is the current behavior?
17 |
18 | ### Failure Information (for bugs)
19 | Please help provide information about the failure if this is a bug. If it is not a bug, please remove the rest of this template.
20 |
21 | ### Steps to Reproduce
22 | Please provide detailed steps for reproducing the issue.
23 |
24 | ### Failure Logs
25 | Please include any relevant log snippets or files here.
26 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 snAppy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # snAppy
2 |
3 | snAppy is a VS Code extension coupled with an interactive view to support your React front-end delivery. It automates dynamic and vendor code-splitting all within your workspace.
4 |
5 | ### Features
6 |
7 | snAppy includes Webpack configuration and bundling, automated dynamic/vendor code-splitting, and exporting of webpack.config.js and bundle report files. snAppy works on top of your code base, never deleting your code and you may choose to keep/delete any changes made.
8 |
9 | (gif/demo coming soon)
10 |
11 | ### Getting Started
12 |
13 | #### Extension Settings
14 |
15 | Pull down the Command Palette in your workspace/project and search: snAppy: Start on Current Workspace. Turn on auto-save to allow for rebundle of your application after optimizations have been made. You may still undo any changes afterwards.
16 |
17 | #### How to Use
18 |
19 | As recommended by the Webpack documentation, please name your output folder as "dist" and include your index.html inside. The script source should be "bundle.js". For the entry path within the Webview, please supply the relative path from the workspace's root folder to index.js. Please see below:
20 |
21 |
22 |
23 | ### Contributing and Issues
24 | We are always looking to improve. If there are any contributions or issues you have, please check out our documentation to submit.
25 |
26 | #### Release Notes
27 | Created by: Courtney Kwong, Jackie Lin, Olga Naumova, Rachel Park
28 | 0.5.0 | Initial release of snAppy. More to come!
29 |
30 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-architect
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/snAppy/a9b853934bf932ec1f25c970fe13b0fbf3b8496d/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "snappy",
3 | "displayName": "snAppy",
4 | "description": "Front-end Optimization Tool",
5 | "version": "0.5.0",
6 | "publisher": "snAppy",
7 | "engines": {
8 | "vscode": "^1.38.0"
9 | },
10 | "icon": "logo.png",
11 | "repository": {
12 | "type": "git",
13 | "directory": "https://github.com/oslabs-beta/snAppy.git"
14 | },
15 | "categories": [
16 | "Other"
17 | ],
18 | "keywords": [
19 | "front-end",
20 | "react",
21 | "optimization",
22 | "optimization tool",
23 | "front-end optimization tool",
24 | "load time optimization",
25 | "dynamic",
26 | "import"
27 | ],
28 | "activationEvents": [
29 | "onCommand:extension.startSnappy"
30 | ],
31 | "main": "./out/extension.js",
32 | "contributes": {
33 | "commands": [
34 | {
35 | "command": "extension.startSnappy",
36 | "title": "Start on Current Workspace",
37 | "category": "snAppy"
38 | }
39 | ]
40 | },
41 | "scripts": {
42 | "vscode:prepublish": "npm run compile",
43 | "compile": "npm-run-all compile:*",
44 | "compile:extension": "tsc",
45 | "compile:views": "webpack --mode development",
46 | "watch": "tsc -watch -p ./",
47 | "pretest": "npm run compile",
48 | "test": "jest --verbose",
49 | "build": "webpack",
50 | "stats": "webpack --profile --json > compilation-stats.json"
51 | },
52 | "jest": {
53 | "verbose": true
54 | },
55 | "devDependencies": {
56 | "@babel/core": "^7.6.0",
57 | "@babel/preset-env": "^7.6.0",
58 | "@babel/preset-react": "^7.6.3",
59 | "@types/d3": "^5.7.2",
60 | "@types/glob": "^7.1.1",
61 | "@types/node": "^10.12.21",
62 | "@types/react": "^16.9.2",
63 | "@types/react-dom": "^16.9.0",
64 | "@types/vscode": "^1.38.0",
65 | "babel-loader": "^8.0.6",
66 | "css-loader": "^3.2.0",
67 | "enzyme": "^3.10.0",
68 | "enzyme-adapter-react-16": "^1.15.1",
69 | "glob": "^7.1.4",
70 | "style-loader": "^1.0.0",
71 | "ts-loader": "^6.1.0",
72 | "tslint": "^5.12.1",
73 | "typescript": "^3.3.1",
74 | "vscode-test": "^1.2.0"
75 | },
76 | "dependencies": {
77 | "@babel/plugin-proposal-class-properties": "^7.5.5",
78 | "@babel/plugin-transform-runtime": "^7.6.2",
79 | "@babel/preset-typescript": "^7.6.0",
80 | "@babel/runtime": "^7.6.3",
81 | "@types/enzyme": "^3.10.3",
82 | "@types/enzyme-adapter-react-16": "^1.0.5",
83 | "@types/jest": "^24.0.19",
84 | "@types/webpack": "^4.39.5",
85 | "babel-plugin-transform-class-properties": "^6.24.1",
86 | "d3": "^5.12.0",
87 | "enzyme-to-json": "^3.4.3",
88 | "esprima": "^4.0.1",
89 | "jest": "^24.9.0",
90 | "npm-run-all": "^4.1.5",
91 | "react": "^16.9.0",
92 | "react-dom": "^16.9.0",
93 | "ts-import-plugin": "^1.6.1",
94 | "utf8": "^3.0.0",
95 | "vscode-uri": "^2.0.3",
96 | "webpack": "^4.40.2",
97 | "webpack-cli": "^3.3.8"
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/snappy-0.5.0.vsix:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/snAppy/a9b853934bf932ec1f25c970fe13b0fbf3b8496d/snappy-0.5.0.vsix
--------------------------------------------------------------------------------
/src/__test__/enzyme.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import {configure, shallow, ShallowWrapper} from 'enzyme';
3 | import * as Adapter from 'enzyme-adapter-react-16';
4 | import * as toJson from 'enzyme-to-json';
5 | import Assets from '../views/components/Assets';
6 | import Form from '../views/components/Form';
7 | import Visualization from '../views/components/Visualizations';
8 | import LiquidGauge from '../views/components/visuals/LiquidGauges';
9 |
10 | import App from '../views/App';
11 |
12 | configure({ adapter: new Adapter() });
13 |
14 | describe('', () => {
15 | let wrapper: ShallowWrapper;
16 | beforeAll(()=>{
17 | wrapper = shallow();
18 | })
19 | it('renders one components', () => {
20 | expect(wrapper.length).toEqual(1);
21 | });
22 | it('renders one h1', () => {
23 | expect(wrapper.find('h1').length).toEqual(1);
24 | });
25 | it('h1 renders logo in text', () => {
26 | expect(wrapper.find('h1').text()).toEqual('snAppy');
27 | });
28 | });
29 |
30 | const clickForm = jest.fn();
31 |
32 | describe('
', () => {
33 | interface Props {
34 | runFunc: Function;
35 | entryFunc: Function;
36 | entry: string;
37 | }
38 | let wrapper: ShallowWrapper;
39 | const runWebpackGetStats = jest.fn();
40 | const entryFunc = jest.fn();
41 | const entry = 'mock string';
42 |
43 | const props = {
44 | runFunc: runWebpackGetStats,
45 | entryFunc: entryFunc,
46 | entry: entry
47 | };
48 | beforeAll(()=> {
49 | wrapper=shallow();
50 | });
51 | it('is a div with a form', () => {
52 | expect(wrapper.find('form').length).toEqual(1);
53 | });
54 | it('has an onClick function', () => {
55 | const fakeEvent = { preventDefault: () => console.log('preventDefault') };
56 | wrapper
57 | .find('form')
58 | .simulate('submit', fakeEvent);
59 | expect(runWebpackGetStats).toHaveBeenCalled();
60 | });
61 | it('has an onChange function', () => {
62 | const fakeEvent = {
63 | target: { value: 'the-value' }
64 | };
65 | wrapper
66 | .find('form')
67 | .find('input#entryInput')
68 | .simulate('change', fakeEvent);
69 | expect(entryFunc).toBeCalledWith(fakeEvent);
70 | });
71 | });
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | import { ExtensionContext, commands, window, ViewColumn, Uri, workspace } from 'vscode';
2 | import { URI } from 'vscode-uri';
3 | const { exec } = require('child_process');
4 | import * as configs from "./functions/webpackFunctions";
5 | import traverseAndDynamicallyImport from "./functions/traverseParseFunctions";
6 | const path = require('path');
7 |
8 |
9 | function loadScript(context: ExtensionContext, path: string) {
10 | return ``;
11 | }
12 |
13 | export function activate(context: ExtensionContext) {
14 | const startCommand = commands.registerCommand('extension.startSnappy', () => {
15 | const panel = window.createWebviewPanel('snAppy', 'snAppy!', ViewColumn.Beside, { enableScripts: true , retainContextWhenHidden: true});
16 | //panel's html is created in function below (getWebviewContent)
17 | panel.webview.html = getWebviewContent(context);
18 | //ip to send messages/data from react frontend to node backend
19 | panel.webview.onDidReceiveMessage((message: any) => {
20 | interface Module {
21 | entry: string;
22 | css: boolean;
23 | jsx: boolean;
24 | less: boolean;
25 | sass: boolean;
26 | tsx: boolean;
27 | }
28 | switch (message.command) {
29 | //onClick(Bundle! button): build and get stats of application:
30 | case 'config':
31 | let moduleState: Module = {
32 | entry: message.entry,
33 | ...message.module,
34 | };
35 | configs.runWriteWebpackBundle(moduleState, panel);
36 | break;
37 | //onClick(Optimize button): parses file using AST to locate static imports and replacing it with dynamic imports
38 | case 'optimize':
39 | let resolvedEntry = path.resolve(`${(workspace.workspaceFolders? workspace.workspaceFolders[0].uri.path : '/') + message.entry}`);
40 | traverseAndDynamicallyImport(resolvedEntry, resolvedEntry);
41 | return exec('npx webpack --profile --json > compilation-stats.json', {cwd: __dirname}, (err : Error, stdout: string)=>{
42 | workspace.fs.readFile(URI.file(path.join(__dirname, 'compilation-stats.json')))
43 | .then(res => {
44 | return panel.webview.postMessage({command: 'post', field: res.toString()});
45 | });
46 | });
47 | break;
48 | case 'export':
49 | console.log('exporting files');
50 | workspace.fs.createDirectory((URI.file(workspace.workspaceFolders? workspace.workspaceFolders[0].uri.path + '/snappy': '/')));
51 | workspace.fs.readFile(URI.file(path.join(__dirname, 'webpack.config.js')))
52 | .then( res => {
53 | workspace.fs.writeFile(URI.file(workspace.workspaceFolders? workspace.workspaceFolders[0].uri.path + '/snappy/webpack.config.js': '/'), res);
54 | });
55 | workspace.fs.readFile(URI.file(path.join(__dirname, 'compilation-stats.json')))
56 | .then( res => {
57 | workspace.fs.writeFile(URI.file(workspace.workspaceFolders? workspace.workspaceFolders[0].uri.path + '/snappy/compilation-stats.json': '/'), res);
58 | });
59 | }
60 | });
61 | });
62 | context.subscriptions.push(startCommand);
63 | }
64 |
65 | function getWebviewContent(context: ExtensionContext) {
66 | return `
67 |
68 |
69 |
70 |
71 |
72 | Snappy
73 |
74 |
75 |
76 |
77 |
80 | ${loadScript(context, 'out/snappy.js')}
81 |
82 | `;
83 | }
84 |
85 |
86 | export function deactivate() {}
87 |
--------------------------------------------------------------------------------
/src/functions/loadLiquidGauge.ts:
--------------------------------------------------------------------------------
1 | import * as d3 from 'd3';
2 | // import { Area } from 'd3';
3 |
4 | interface ConfigI {
5 | minValue: number;
6 | maxValue: number;
7 | circleThickness: number;
8 | circleFillGap: number;
9 | circleColor: string;
10 | waveHeight: number;
11 | waveCount: number;
12 | waveRiseTime: number;
13 | waveAnimateTime: number;
14 | waveRise : boolean;
15 | waveHeightScaling: boolean;
16 | waveAnimate: boolean;
17 | waveColor: string;
18 | waveOffset: number;
19 | textVertPosition: number;
20 | textSize: number;
21 | valueCountUp: boolean;
22 | displayPercent: boolean;
23 | textColor: string;
24 | waveTextColor: string;
25 | }
26 |
27 | export function liquidFillGaugeDefaultSettings() : ConfigI {
28 | return {
29 | minValue: 0, // The gauge minimum value.
30 | maxValue: 100, // The gauge maximum value.
31 |
32 | circleThickness: 0.05, // The outer circle thickness as a percentage of it's radius.
33 | // The size of the gap between the outer circle and wave circle as a percentage of
34 | // the outer circles radius.
35 | circleFillGap: 0.05,
36 | circleColor: "#178BCA", // The color of the outer circle.
37 |
38 | waveHeight: 0.05, // The wave height as a percentage of the radius of the wave circle.
39 | waveCount: 1, // The number of full waves per width of the wave circle.
40 | waveRiseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
41 | waveAnimateTime: 18000, // The amount of time in milliseconds for a full wave to enter the wave circle.
42 |
43 | // Control if the wave should rise from 0 to it's full height, or start at it's full height.
44 | waveRise: true,
45 |
46 | // Controls wave size scaling at low and high fill percentages.
47 | // When true, wave height reaches it's maximum at 50% fill, and minimum at 0% and 100% fill.
48 | // This helps to prevent the wave from making the wave circle from appear totally full or empty
49 | // when near it's minimum or maximum fill.
50 | waveHeightScaling: true,
51 | waveAnimate: true, // Controls if the wave scrolls or is static.
52 | waveColor: "#178BCA", // The color of the fill wave.
53 | waveOffset: 0, // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave.
54 |
55 | // The height at which to display the percentage text withing the wave circle. 0 = bottom, 1 = top.
56 | textVertPosition: .5,
57 | textSize: 1, // The relative height of the text to display in the wave circle. 1 = 50%
58 |
59 | // If true, the displayed value counts up from 0 to it's final value upon loading.
60 | // If false, the final value is displayed.
61 | valueCountUp: true,
62 | displayPercent: true, // If true, a % symbol is displayed after the value.
63 | textColor: "#045681", // The color of the value text when the wave does not overlap it.
64 | waveTextColor: "#A4DBf8" // The color of the value text when the wave overlaps it.
65 | };
66 | }
67 |
68 | export function loadLiquidFillGauge(elementId: string, value:any, config:ConfigI, post:number) {
69 | if (config === undefined) { config = liquidFillGaugeDefaultSettings(); }
70 |
71 | const gauge = d3.select("#" + elementId);
72 | const radius = Math.min(parseInt(gauge.style("width")), parseInt(gauge.style("height"))) / 2;
73 |
74 | const locationX = parseInt(gauge.style("width")) / 2 - radius;
75 | const locationY = parseInt(gauge.style("height")) / 2 - radius;
76 | const fillPercent = value / config.maxValue;
77 |
78 | let waveHeightScale : d3.ScaleLinear;
79 | if (config.waveHeightScaling) {
80 | waveHeightScale = d3.scaleLinear()
81 | .range([0, config.waveHeight, 0])
82 | .domain([0, 50, 100]);
83 | } else {
84 | waveHeightScale = d3.scaleLinear()
85 | .range([config.waveHeight, config.waveHeight])
86 | .domain([0, 100]);
87 | }
88 |
89 | const textPixels = (config.textSize * radius / 2);
90 | const textFinalValue = parseFloat(value).toFixed(2);
91 | const textStartValue = config.valueCountUp ? config.minValue : textFinalValue;
92 | const percentText = config.displayPercent ? "" : "";
93 | const circleThickness = config.circleThickness * radius;
94 | const circleFillGap = config.circleFillGap * radius;
95 | const fillCircleMargin = circleThickness + circleFillGap;
96 | const fillCircleRadius = radius - fillCircleMargin;
97 | const waveHeight = fillCircleRadius * waveHeightScale(fillPercent * 100);
98 |
99 | const waveLength = fillCircleRadius * 2 / config.waveCount;
100 | const waveClipCount = 1 + config.waveCount;
101 | const waveClipWidth = waveLength * waveClipCount;
102 |
103 | // Rounding functions so that the correct number of decimal places is always displayed
104 | // as the value counts up.
105 | const format = d3.format(".0f");
106 |
107 | // Data for building the clip wave area.
108 | const data = [];
109 | for (let i = 0; i <= 40 * waveClipCount; i++) {
110 | data.push({x: i / (40 * waveClipCount), y: (i / (40))});
111 | }
112 |
113 | // Scales for drawing the outer circle.
114 | const gaugeCircleX = d3.scaleLinear().range([0, 2 * Math.PI]).domain([0, 1]);
115 | const gaugeCircleY = d3.scaleLinear().range([0, radius]).domain([0, radius]);
116 |
117 | // Scales for controlling the size of the clipping path.
118 | const waveScaleX = d3.scaleLinear().range([0, waveClipWidth]).domain([0, 1]);
119 | const waveScaleY = d3.scaleLinear().range([0, waveHeight]).domain([0, 1]);
120 |
121 | // Scales for controlling the position of the clipping path.
122 | const waveRiseScale = d3.scaleLinear()
123 | // The clipping area size is the height of the fill circle + the wave height,
124 | // so we position the clip wave such that the it will overlap the fill circle
125 | // at all when at 0%, and will totally cover the fill circle at 100%.
126 | .range([(fillCircleMargin + fillCircleRadius * 2 + waveHeight), (fillCircleMargin - waveHeight)])
127 | .domain([0, 1]);
128 |
129 | const waveAnimateScale = d3.scaleLinear()
130 | .range([0, waveClipWidth - fillCircleRadius * 2]) // Push the clip area one full wave then snap back.
131 | .domain([0, 1]);
132 |
133 | // Scale for controlling the position of the text within the gauge.
134 | const textRiseScaleY = d3.scaleLinear()
135 | .range([fillCircleMargin + fillCircleRadius * 2,(fillCircleMargin + textPixels * 0.7)])
136 | .domain([0, 1]);
137 |
138 | // Center the gauge within the parent SVG.
139 | const gaugeGroup = gauge.append("g")
140 | .attr('transform','translate(' + locationX + ',' + locationY + ')');
141 | // console.log('GAUAGE GROUP: ', locationX);
142 |
143 | // Draw the outer circle.
144 | const gaugeCircleArc = d3.arc()
145 | .startAngle(gaugeCircleX(0))
146 | .endAngle(gaugeCircleX(1))
147 | .outerRadius(gaugeCircleY(radius))
148 | .innerRadius(gaugeCircleY(radius - circleThickness));
149 |
150 | gaugeGroup.append("path")
151 | .attr("d", gaugeCircleArc)
152 | .style("fill", config.circleColor)
153 | .attr('transform','translate(' + radius + ',' + radius + ')');
154 | // console.log('gaugeGroup append PATH: ', radius);
155 | // Text where the wave does not overlap.
156 | gaugeGroup.append("text")
157 | .text(format(textStartValue) + percentText)
158 | .attr("class", "liquidFillGaugeText")
159 | .attr("text-anchor", "middle")
160 | .attr("font-size", textPixels + "px")
161 | .style("fill", config.textColor)
162 | .attr('transform','translate(' + radius + ',' + textRiseScaleY(config.textVertPosition) + ')');
163 |
164 | // The clipping wave area.
165 | const clipArea = d3.area()
166 | .x(function(d:any) { return waveScaleX(d.x); })
167 | .y0(function(d:any) { return waveScaleY(Math.sin(Math.PI * 2 * config.waveOffset * -1 + Math.PI * 2 * (1 - config.waveCount) + d.y * 2 * Math.PI));})
168 | .y1(function(d) { return (fillCircleRadius *2 + waveHeight); });
169 | const waveGroup = gaugeGroup.append("defs")
170 | .append("clipPath")
171 | .attr("id", "clipWave" + elementId);
172 | const wave = waveGroup.append("path")
173 | .datum(data)
174 | .attr("d", clipArea)
175 | .attr("T", 0);
176 |
177 | // The inner circle with the clipping wave attached.
178 | const fillCircleGroup = gaugeGroup.append("g")
179 | .attr("clip-path", "url(" + location.href + "#clipWave" + elementId + ")");
180 |
181 | fillCircleGroup.append("circle")
182 | .attr("cx", radius)
183 | .attr("cy", radius)
184 | .attr("r", fillCircleRadius)
185 | .style("fill", config.waveColor);
186 |
187 | // Text where the wave does overlap.
188 | fillCircleGroup.append("text")
189 | .text(format(textStartValue))
190 | .attr("class", "liquidFillGaugeText")
191 | .attr("text-anchor", "middle")
192 | .attr("font-size", textPixels + "px")
193 | .style("fill", config.waveTextColor)
194 | .attr('transform','translate(' + radius + ',' + textRiseScaleY(config.textVertPosition) + ')');
195 |
196 | // Make the value count up.
197 | if (config.valueCountUp) {
198 | gaugeGroup.selectAll("text.liquidFillGaugeText").transition()
199 | .duration(config.waveRiseTime)
200 | .tween("text", function(d) {
201 | var that = d3.select(this);
202 | var i = d3.interpolateNumber(that.text().replace("", ""), textFinalValue);
203 | return function(t) { that.text(format(i(t)) + percentText); };
204 | });
205 | }
206 |
207 | // Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement
208 | // can be controlled independently.
209 | const waveGroupXPosition = fillCircleMargin + fillCircleRadius * 2 - waveClipWidth;
210 |
211 | if (config.waveRise) {
212 | waveGroup.attr('transform','translate(' + waveGroupXPosition + ',' + waveRiseScale(0) + ')')
213 | .transition()
214 | .duration(config.waveRiseTime)
215 | .attr('transform','translate(' + waveGroupXPosition + ',' + waveRiseScale(fillPercent) + ')')
216 | .on("start", function() { wave.attr('transform','translate(1,0)'); });
217 | // This transform is necessary to get the clip wave positioned correctly when
218 | // waveRise=true and waveAnimate=false. The wave will not position correctly without
219 | // this, but it's not clear why this is actually necessary.
220 | } else {
221 | waveGroup.attr('transform','translate(' + waveGroupXPosition + ',' + waveRiseScale(fillPercent) + ')');
222 | }
223 |
224 | if(config.waveAnimate) { animateWave(); }
225 |
226 | function animateWave() {
227 | wave.attr('transform','translate(' + waveAnimateScale(wave.attr('T')) + ',0)');
228 | wave.transition()
229 | .duration(config.waveAnimateTime * (1 - wave.attr('T')))
230 | .ease(d3.easeLinear)
231 | .attr('transform','translate(' + waveAnimateScale(1) + ',0)')
232 | .attr('T', 1)
233 | .on('end', function() {
234 | wave.attr('T', 0);
235 | if (config.waveAnimate) { animateWave(); }
236 | });
237 | }
238 |
239 | class GaugeUpdater {
240 | constructor(){
241 |
242 | }
243 | setWaveAnimate(value:boolean) {
244 | // Note: must call update after setting value
245 | config.waveAnimate = value;
246 | }
247 |
248 | update (value:number) {
249 | gaugeGroup.selectAll("text.liquidFillGaugeText").transition()
250 | .duration(config.waveRiseTime)
251 | .tween("text", function(d) {
252 | var that = d3.select(this);
253 | var i = d3.interpolateNumber(that.text().replace("%", ""), value);
254 | return function(t) { that.text(format(i(t)) + percentText); };
255 | });
256 |
257 | var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value)) / config.maxValue;
258 | var waveHeight = fillCircleRadius * waveHeightScale(fillPercent * 100);
259 | var waveRiseScale = d3.scaleLinear()
260 | // The clipping area size is the height of the fill circle + the wave height, so we position
261 | // the clip wave such that the it will overlap the fill circle at all when at 0%, and will
262 | // totally cover the fill circle at 100%.
263 | .range([(fillCircleMargin + fillCircleRadius * 2 + waveHeight), (fillCircleMargin - waveHeight)])
264 | .domain([0,1]);
265 | var newHeight = waveRiseScale(fillPercent);
266 | var waveScaleX = d3.scaleLinear().range([0, waveClipWidth]).domain([0, 1]);
267 | var waveScaleY = d3.scaleLinear().range([0, waveHeight]).domain([0, 1]);
268 | var newClipArea;
269 |
270 | if (config.waveHeightScaling) {
271 | newClipArea = d3.area()
272 | .x(function(d:any) { return waveScaleX(d.x); } )
273 | .y0(function(d:any) {
274 | return waveScaleY(Math.sin(
275 | Math.PI * 2 * config.waveOffset * -1 + Math.PI * 2 * (1 - config.waveCount) + d.y * 2 * Math.PI));
276 | })
277 | .y1(function(d) { return (fillCircleRadius * 2 + waveHeight); });
278 | } else {
279 | newClipArea = clipArea;
280 | }
281 |
282 | var newWavePosition = config.waveAnimate ? waveAnimateScale(1) : 0;
283 | wave.transition()
284 | .duration(0)
285 | .transition()
286 | .duration(config.waveAnimate ? (config.waveAnimateTime * (1 - wave.attr('T'))) : config.waveRiseTime)
287 | .ease(d3.easeLinear)
288 | .attr('d', newClipArea)
289 | .attr('transform','translate(' + newWavePosition + ',0)')
290 | .attr('T','1')
291 | .on("end", function() {
292 | if (config.waveAnimate) {
293 | wave.attr('transform','translate(' + waveAnimateScale(0) + ',0)');
294 | animateWave();
295 | }
296 | });
297 |
298 | waveGroup.transition()
299 | .duration(config.waveRiseTime)
300 | .attr('transform','translate(' + waveGroupXPosition + ',' + newHeight + ')');
301 | }
302 | }
303 | return new GaugeUpdater();
304 | }
--------------------------------------------------------------------------------
/src/functions/optimizeFunctions.ts:
--------------------------------------------------------------------------------
1 | import { WorkspaceEdit, workspace, Position, Uri } from "vscode";
2 |
3 |
4 | export default function dynamicImportFunc(uri: Uri, uncommentLines: number[],exportLine: number, components: ComponentObject) {
5 | //will use that and the starting position to comment out static imports by using workspaceEdit.insert(URI, position, string)
6 | let edit = new WorkspaceEdit();
7 | for (let line of uncommentLines) {
8 | edit.insert(uri, new Position(line - 1, 0), "//");
9 | }
10 | workspace.applyEdit(edit).then(res => {
11 | let dynamicInjection = createDynamicInjection(components);
12 | insertFunc(uri, exportLine, dynamicInjection);
13 | });
14 | }
15 | interface EachComponent {
16 | name: string;
17 | source: string;
18 | path: string
19 | }
20 | interface ComponentObject {
21 | [propName: string]: EachComponent
22 | }
23 |
24 | const createDynamicInjection = (componentObject: ComponentObject) => {
25 | //an outside function that loops through the object and for each key will execute the function below to create a new instance of class
26 | //will have a varibale "injection" with the class declaration
27 | let injection = `class DynamicImport extends Component {
28 | constructor(props) {
29 | super(props);
30 | this.state = {
31 | component: null
32 | }
33 | }
34 | componentDidMount() {
35 | this.props.load()
36 | .then((component) => {
37 | this.setState(()=> ({
38 | component : component.default ? component.default : component
39 | }))
40 | })
41 | }
42 | render () {
43 | return this.props.children(this.state.component)
44 | }
45 | }
46 |
47 | `;
48 | for (let val in componentObject) {
49 | injection += newInstance(
50 | componentObject[val].name,
51 | componentObject[val].source
52 | );
53 | }
54 | //concatenate every new instance of class invoked with each key/values to injection string
55 | //return the resulting string with injection+ each instance of new class
56 | return injection;
57 | };
58 | //inside of the outside func there will be a function that injects a string literal with values from the object into string declaration of new instance of DynamicImports class
59 | function newInstance(name: string, path: string) {
60 | return `const ${name} = (props) => (
61 | import(/*webpackChunkName: "${name}-chunk"*/ '${path}')}>{
62 | (Component) => Component === null
63 | ?