diff --git a/.eslintrc b/.eslintrc index f9e7c71..cc49571 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,6 +4,11 @@ "xo-typescript" ], "rules": { - "@typescript-eslint/indent": ["error", 2, { "SwitchCase": 1 }] + "@typescript-eslint/indent": ["error", 2, { "SwitchCase": 1 }], + "@typescript-eslint/member-naming": 0, + "@typescript-eslint/interface-name-prefix": ["error", { "prefixWithI": "always" }] + }, + "env": { + "browser": true } } diff --git a/env.ts b/env.ts index 1fe98c7..83f674c 100644 --- a/env.ts +++ b/env.ts @@ -1,3 +1,5 @@ import * as path from 'path'; +export const SRC_FOLDER_PATH = path.resolve('./src'); export const BUILD_FOLDER_PATH = path.resolve('./build'); +export const TS_BUILD_FOLDER_PATH = path.resolve('./dist'); diff --git a/package.json b/package.json index 05fa6c8..90fc0a8 100644 --- a/package.json +++ b/package.json @@ -24,16 +24,12 @@ "vscode": ">=1.39.0" }, "scripts": { - "bu": "yarn cleanup && yarn build:ts && yarn build:generate-themes", - "lint": "eslint .", - "build": "yarn cleanup && yarn build:ts && yarn build-themes && yarn build-ui", + "build": "yarn cleanup && yarn build:ts && yarn build:generate-themes && yarn build:ui", "cleanup": "rimraf build && rimraf dist", + "lint": "eslint .", + "build:ui": "node dist/scripts/ui/index.js", "build:generate-themes": "node dist/scripts/generator/index.js", - "build-ui": "gulp build:copy-ui && yarn build-ui-release-notes", - "build-ui-release-notes": "browserify out/src/webviews/ui/release-notes/index.js > out/ui/release-notes.js", - "build-ui-only": "yarn cleanup && yarn build-ts && yarn build-ui", - "build:ts": "tsc -p ./tsconfig.json", - "test": "tslint **.ts", + "build:ts": "tsc -p ./tsconfig.json && cp -r dist/src/ build", "postinstall": "node ./node_modules/vscode/bin/install && opencollective postinstall && tsc -p tsconfig.json" }, "categories": [ @@ -48,7 +44,7 @@ "*" ], "extensionKind": "ui", - "main": "./out/src/material.theme.config", + "main": "./build/material.theme.config", "contributes": { "commands": [ { @@ -95,52 +91,52 @@ "themes": [ { "label": "Material Theme", - "path": "./out/themes/Material-Theme-Default.json", + "path": "./build/themes/Material-Theme-Default.json", "uiTheme": "vs-dark" }, { "label": "Material Theme High Contrast", - "path": "./out/themes/Material-Theme-Default-High-Contrast.json", + "path": "./build/themes/Material-Theme-Default-High-Contrast.json", "uiTheme": "vs-dark" }, { "label": "Material Theme Darker", - "path": "./out/themes/Material-Theme-Darker.json", + "path": "./build/themes/Material-Theme-Darker.json", "uiTheme": "vs-dark" }, { "label": "Material Theme Darker High Contrast", - "path": "./out/themes/Material-Theme-Darker-High-Contrast.json", + "path": "./build/themes/Material-Theme-Darker-High-Contrast.json", "uiTheme": "vs-dark" }, { "label": "Material Theme Palenight", - "path": "./out/themes/Material-Theme-Palenight.json", + "path": "./build/themes/Material-Theme-Palenight.json", "uiTheme": "vs-dark" }, { "label": "Material Theme Palenight High Contrast", - "path": "./out/themes/Material-Theme-Palenight-High-Contrast.json", + "path": "./build/themes/Material-Theme-Palenight-High-Contrast.json", "uiTheme": "vs-dark" }, { "label": "Material Theme Ocean", - "path": "./out/themes/Material-Theme-Ocean.json", + "path": "./build/themes/Material-Theme-Ocean.json", "uiTheme": "vs-dark" }, { "label": "Material Theme Ocean High Contrast", - "path": "./out/themes/Material-Theme-Ocean-High-Contrast.json", + "path": "./build/themes/Material-Theme-Ocean-High-Contrast.json", "uiTheme": "vs-dark" }, { "label": "Material Theme Lighter", - "path": "./out/themes/Material-Theme-Lighter.json", + "path": "./build/themes/Material-Theme-Lighter.json", "uiTheme": "vs" }, { "label": "Material Theme Lighter High Contrast", - "path": "./out/themes/Material-Theme-Lighter-High-Contrast.json", + "path": "./build/themes/Material-Theme-Lighter-High-Contrast.json", "uiTheme": "vs" } ] @@ -163,6 +159,7 @@ "devDependencies": { "@babel/register": "7.4.4", "@moxer/vscode-theme-generator": "1.1.0", + "@types/browserify": "12.0.36", "@types/gulp-if": "0.0.33", "@types/gulp-util": "3.0.34", "@types/mustache": "0.8.32", diff --git a/scripts/generator/index.ts b/scripts/generator/index.ts index d0c0a64..591e5bb 100644 --- a/scripts/generator/index.ts +++ b/scripts/generator/index.ts @@ -25,9 +25,13 @@ const generate = async (): Promise => { }); }; -try { - generate(); -} catch (error) { - console.error(error); - process.exit(1); -} +const run = async (): Promise => { + try { + await generate(); + } catch (error) { + console.error('ERROR build:generate-themes', error); + process.exit(1); + } +}; + +run(); diff --git a/scripts/ui/index.ts b/scripts/ui/index.ts new file mode 100644 index 0000000..0f30d8e --- /dev/null +++ b/scripts/ui/index.ts @@ -0,0 +1,44 @@ +import * as fs from 'fs-extra'; +import * as path from 'path'; +import browserify from 'browserify'; + +import {BUILD_FOLDER_PATH, SRC_FOLDER_PATH, TS_BUILD_FOLDER_PATH} from '../../env'; + +const UI_FOLDER_PATH = path.join(SRC_FOLDER_PATH, 'webviews', 'ui'); +const UI_JS_FOLDER_PATH = path.join(TS_BUILD_FOLDER_PATH, 'src', 'webviews', 'ui'); +const UI_FOLDER_BUILD_PATH = path.join(BUILD_FOLDER_PATH, 'ui'); + +const copyStatics = async (): Promise => { + const paths = [{ + src: path.join(UI_FOLDER_PATH, 'release-notes', 'release-notes.html'), + dest: path.join(UI_FOLDER_BUILD_PATH, 'release-notes.html') + }, { + src: path.join(UI_FOLDER_PATH, 'release-notes', 'style.css'), + dest: path.join(UI_FOLDER_BUILD_PATH, 'style.css') + }]; + + return Promise.all(paths.map(async path => fs.copyFile(path.src, path.dest))); +}; + +const buildJs = async (type: 'release-notes'): Promise => { + const jsBuildPath = path.join(UI_FOLDER_BUILD_PATH, `${type}.js`); + const b = browserify(); + await fs.createFile(jsBuildPath); + const jsBuildFileStream = fs.createWriteStream(jsBuildPath); + b.add(path.join(UI_JS_FOLDER_PATH, type, 'index.js')); + b.bundle().pipe(jsBuildFileStream); + return Promise.resolve(); +}; + +const run = async (): Promise => { + try { + await fs.mkdirp(UI_FOLDER_BUILD_PATH); + await copyStatics(); + await buildJs('release-notes'); + } catch (error) { + console.error('ERROR build:ui:', error); + process.exit(1); + } +}; + +run(); diff --git a/src/material.theme.config.ts b/src/material.theme.config.ts index dac89cd..966a435 100644 --- a/src/material.theme.config.ts +++ b/src/material.theme.config.ts @@ -10,7 +10,7 @@ import checkInstallation from './helpers/check-installation'; import writeChangelog from './helpers/write-changelog'; import {ReleaseNotesWebview} from './webviews/ReleaseNotes'; -export async function activate(context: ExtensionContext) { +export async function activate(context: ExtensionContext): Promise { const installationType = checkInstallation(); const releaseNotesView = new ReleaseNotesWebview(context); @@ -32,5 +32,5 @@ export async function activate(context: ExtensionContext) { await updateAccent(accentPicked); }); - Commands.registerCommand('materialTheme.showReleaseNotes', () => releaseNotesView.show()); + Commands.registerCommand('materialTheme.showReleaseNotes', async () => releaseNotesView.show()); } diff --git a/src/webviews/ReleaseNotes.ts b/src/webviews/ReleaseNotes.ts index ba650a8..f6e1ffa 100644 --- a/src/webviews/ReleaseNotes.ts +++ b/src/webviews/ReleaseNotes.ts @@ -1,13 +1,6 @@ import {WebviewController} from './Webview'; -import { - ExtensionContext -} from 'vscode'; export class ReleaseNotesWebview extends WebviewController<{}> { - constructor(context: ExtensionContext) { - super(context); - } - get filename(): string { return 'release-notes.html'; } @@ -24,7 +17,7 @@ export class ReleaseNotesWebview extends WebviewController<{}> { * This will be called by the WebviewController when init the view * passing as `window.bootstrap` to the view. */ - getBootstrap() { + getBootstrap(): {} { return {}; } } diff --git a/src/webviews/Settings.ts b/src/webviews/Settings.ts index 3b72e64..74ad510 100644 --- a/src/webviews/Settings.ts +++ b/src/webviews/Settings.ts @@ -1,18 +1,12 @@ import {WebviewController} from './Webview'; import { - workspace as Workspace, - - ExtensionContext + workspace as Workspace } from 'vscode'; -import {SettingsBootstrap} from './interfaces'; +import {ISettingsBootstrap} from './interfaces'; import {getCustomSettings} from '../helpers/settings'; import {getDefaultValues} from '../helpers/fs'; -export class SettingsWebview extends WebviewController { - constructor(context: ExtensionContext) { - super(context); - } - +export class SettingsWebview extends WebviewController { get filename(): string { return 'settings.html'; } @@ -25,26 +19,26 @@ export class SettingsWebview extends WebviewController { return 'Material Theme Settings'; } - private getAvailableScopes(): ['user' | 'workspace', string][] { - const scopes: ['user' | 'workspace', string][] = [['user', 'User']]; - return scopes - .concat( - Workspace.workspaceFolders !== undefined && Workspace.workspaceFolders.length ? - ['workspace', 'Workspace'] : - [] - ); - } - /** * This will be called by the WebviewController when init the view * passing as `window.bootstrap` to the view. */ - getBootstrap() { + getBootstrap(): ISettingsBootstrap { return { config: getCustomSettings(), defaults: getDefaultValues(), scope: 'user', scopes: this.getAvailableScopes() - } as SettingsBootstrap; + }; + } + + private getAvailableScopes(): Array<['user' | 'workspace', string]> { + const scopes: Array<['user' | 'workspace', string]> = [['user', 'User']]; + return scopes + .concat( + Workspace.workspaceFolders?.length ? + ['workspace', 'Workspace'] : + [] + ); } } diff --git a/src/webviews/Webview.ts b/src/webviews/Webview.ts index 96c6bff..4bf1812 100644 --- a/src/webviews/Webview.ts +++ b/src/webviews/Webview.ts @@ -13,13 +13,13 @@ import { } from 'vscode'; import {getCustomSettings} from '../helpers/settings'; -import {Invalidates, Message, SettingsChangedMessage} from './interfaces'; +import {Invalidates, Message, ISettingsChangedMessage} from './interfaces'; export abstract class WebviewController extends Disposable { private panel: WebviewPanel | undefined; private disposablePanel: Disposable | undefined; private invalidateOnVisible: Invalidates; - private context: ExtensionContext; + private readonly context: ExtensionContext; constructor(context: ExtensionContext) { // Applying dispose callback for our disposable function @@ -28,92 +28,12 @@ export abstract class WebviewController extends Disposable { this.context = context; } - abstract get filename(): string; - abstract get id(): string; - abstract get title(): string; - - abstract getBootstrap(): TBootstrap; - - dispose() { + dispose(): void { if (this.disposablePanel) { this.disposablePanel.dispose(); } } - private async getHtml(): Promise { - const doc = await Workspace - .openTextDocument(this.context.asAbsolutePath(path.join('out/ui', this.filename))); - return doc.getText(); - } - - private postMessage(message: Message, invalidates: Invalidates = 'all') { - if (this.panel === undefined) { - return false; - } - - const result = this.panel.webview.postMessage(message); - - // If post was ok, update invalidateOnVisible if different than default - if (!result && this.invalidateOnVisible !== 'all') { - this.invalidateOnVisible = invalidates; - } - - return result; - } - - private postUpdatedConfiguration() { - // Post full raw configuration - return this.postMessage({ - type: 'settingsChanged', - config: getCustomSettings() - } as SettingsChangedMessage, 'config'); - } - - private onPanelDisposed() { - if (this.disposablePanel) { - this.disposablePanel.dispose(); - } - - this.panel = undefined; - } - - private onViewStateChanged(event: WebviewPanelOnDidChangeViewStateEvent) { - console.log('WebviewEditor.onViewStateChanged', event.webviewPanel.visible); - - if (!this.invalidateOnVisible || !event.webviewPanel.visible) { - return; - } - - // Update the view since it can be outdated - const invalidContext = this.invalidateOnVisible; - this.invalidateOnVisible = undefined; - - switch (invalidContext) { - case 'config': - // Post the new configuration to the view - return this.postUpdatedConfiguration(); - default: - return this.show(); - } - } - - protected async onMessageReceived(event: Message) { - if (event === null) { - return; - } - - console.log(`WebviewEditor.onMessageReceived: type=${event.type}, data=${JSON.stringify(event)}`); - - switch (event.type) { - case 'saveSettings': - // TODO: update settings - return; - - default: - return; - } - } - async show(): Promise { const html = await this.getHtml(); @@ -154,4 +74,82 @@ export abstract class WebviewController extends Disposable { this.panel.webview.html = fullHtml; } + + protected onMessageReceived(event: Message): void { + if (event === null) { + return; + } + + console.log(`WebviewEditor.onMessageReceived: type=${event.type}, data=${JSON.stringify(event)}`); + + switch (event.type) { + case 'saveSettings': + // TODO: update settings + break; + default: + break; + } + } + + private async getHtml(): Promise { + const doc = await Workspace + .openTextDocument(this.context.asAbsolutePath(path.join('out/ui', this.filename))); + return doc.getText(); + } + + private async postMessage(message: Message, invalidates: Invalidates = 'all'): Promise { + if (this.panel === undefined) { + return false; + } + + const result = await this.panel.webview.postMessage(message); + + // If post was ok, update invalidateOnVisible if different than default + if (!result && this.invalidateOnVisible !== 'all') { + this.invalidateOnVisible = invalidates; + } + + return result; + } + + private async postUpdatedConfiguration(): Promise { + // Post full raw configuration + return this.postMessage({ + type: 'settingsChanged', + config: getCustomSettings() + } as ISettingsChangedMessage, 'config'); + } + + private onPanelDisposed(): void { + if (this.disposablePanel) { + this.disposablePanel.dispose(); + } + + this.panel = undefined; + } + + private async onViewStateChanged(event: WebviewPanelOnDidChangeViewStateEvent): Promise { + console.log('WebviewEditor.onViewStateChanged', event.webviewPanel.visible); + + if (!this.invalidateOnVisible || !event.webviewPanel.visible) { + return; + } + + // Update the view since it can be outdated + const invalidContext = this.invalidateOnVisible; + this.invalidateOnVisible = undefined; + + switch (invalidContext) { + case 'config': + // Post the new configuration to the view + return this.postUpdatedConfiguration(); + default: + return this.show(); + } + } + + abstract get filename(): string; + abstract get id(): string; + abstract get title(): string; + abstract getBootstrap(): TBootstrap; } diff --git a/src/webviews/interfaces.ts b/src/webviews/interfaces.ts index f3b7fe4..66a8c54 100644 --- a/src/webviews/interfaces.ts +++ b/src/webviews/interfaces.ts @@ -2,52 +2,55 @@ import {IThemeCustomSettings} from '../interfaces/itheme-custom-properties'; import {IDefaults} from '../interfaces/idefaults'; export interface IChangeType { - children: { - text: String; - }[]; + children: Array<{ + text: string; + }>; } export interface IPost { - title: String; - version: String; + title: string; + version: string; fixed: IChangeType[]; new: IChangeType[]; breaking: IChangeType[]; } export interface IPostNormalized { - title: String; - version: String; - fixed: String[]; - new: String[]; - breaking: String[]; + title: string; + version: string; + fixed: string[]; + new: string[]; + breaking: string[]; } -export interface SettingsChangedMessage { +export interface ISettingsChangedMessage { type: 'settingsChanged'; config: IThemeCustomSettings; } -export interface SaveSettingsMessage { +export interface ISaveSettingsMessage { type: 'saveSettings'; changes: { - [key: string]: any; + [key: string]: any; }; removes: string[]; scope: 'user' | 'workspace'; uri: string; } -export type Message = SaveSettingsMessage | SettingsChangedMessage; +export type Message = ISaveSettingsMessage | ISettingsChangedMessage; export type Invalidates = 'all' | 'config' | undefined; -export interface Bootstrap { +export interface IBootstrap { config: IThemeCustomSettings; } -export interface SettingsBootstrap extends Bootstrap { +export interface ISettingsBootstrap extends IBootstrap { scope: 'user' | 'workspace'; - scopes: ['user' | 'workspace', string][]; + scopes: Array<['user' | 'workspace', string]>; defaults: IDefaults; } declare global { - interface Window { bootstrap: Bootstrap | SettingsBootstrap | {}; } + // eslint-disable-next-line @typescript-eslint/interface-name-prefix + interface Window { + bootstrap: IBootstrap | ISettingsBootstrap | {}; + } } diff --git a/src/webviews/ui/release-notes/index.ts b/src/webviews/ui/release-notes/index.ts index 25dba37..22afe64 100644 --- a/src/webviews/ui/release-notes/index.ts +++ b/src/webviews/ui/release-notes/index.ts @@ -2,18 +2,19 @@ import * as sanityClient from '@sanity/client'; import {IPost, IPostNormalized} from '../../interfaces'; +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type const getClient = () => sanityClient({ projectId: 'v475t82f', dataset: 'production' }); -const getReleaseNotes = (): Promise => { +const getReleaseNotes = async (): Promise => { const query = '*[_type == "release"] | order(version desc)'; const client = getClient(); return client.fetch(query); }; -const renderTemplate = (posts: IPostNormalized[]) => { +const renderTemplate = (posts: IPostNormalized[]): string => { return `${posts.reduce((acc, {version, title, fixed, new: newItems, breaking}) => acc.concat(`
${version} @@ -21,11 +22,11 @@ const renderTemplate = (posts: IPostNormalized[]) => {
    ${fixed.reduce((accc: string, src) => - src.length > 0 ? accc.concat(`
  • ${src}
  • `) : '', '')} + src.length > 0 ? accc.concat(`
  • ${src}
  • `) : '', '')} ${newItems.reduce((accc: string, src) => - src.length > 0 ? accc.concat(`
  • ${src}
  • `) : '', '')} + src.length > 0 ? accc.concat(`
  • ${src}
  • `) : '', '')} ${breaking.reduce((accc: string, src) => - src.length > 0 ? accc.concat(`
  • ${src}
  • `) : '', '')} + src.length > 0 ? accc.concat(`
  • ${src}
  • `) : '', '')}
`), '')}`; }; diff --git a/src/webviews/ui/settings/index.ts b/src/webviews/ui/settings/index.ts index 9f9d80d..489b64d 100644 --- a/src/webviews/ui/settings/index.ts +++ b/src/webviews/ui/settings/index.ts @@ -1,17 +1,17 @@ -import {SettingsBootstrap} from '../../interfaces'; +import {ISettingsBootstrap} from '../../interfaces'; import accentsSelector from './lib/accents-selector'; -const run = () => { +const run = (): void => { bind(); - const {config, defaults} = window.bootstrap as SettingsBootstrap; + const {config, defaults} = window.bootstrap as ISettingsBootstrap; accentsSelector('[data-setting="accentSelector"]', defaults.accents, config.accent); console.log(defaults); console.log(config); }; -const bind = () => { +const bind = (): void => { document.querySelector('#fixIconsCTA').addEventListener('click', () => { console.log('Test click'); }); diff --git a/src/webviews/ui/settings/lib/accents-selector.ts b/src/webviews/ui/settings/lib/accents-selector.ts index 81d1193..2d103ad 100644 --- a/src/webviews/ui/settings/lib/accents-selector.ts +++ b/src/webviews/ui/settings/lib/accents-selector.ts @@ -9,7 +9,7 @@ const templateSingleAccent = (accentName: string, accentColor: string): string = `; }; -export default (containerSelector: string, accentsObject: IAccents, currentAccent: string) => { +export default (containerSelector: string, accentsObject: IAccents, currentAccent: string): void => { const container = document.querySelector(containerSelector); for (const accentKey of Object.keys(accentsObject)) { diff --git a/tsconfig.json b/tsconfig.json index 65dd37f..b9c07ff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "es7", "dom" ], + "esModuleInterop": true, "sourceMap": true, "allowUnreachableCode": false, "noUnusedLocals": true, diff --git a/yarn.lock b/yarn.lock index 5e2f7fb..7fb6606 100644 --- a/yarn.lock +++ b/yarn.lock @@ -112,6 +112,14 @@ resolved "https://registry.yarnpkg.com/@sanity/timed-out/-/timed-out-4.0.2.tgz#c9f61f9a1609baa1eb3e4235a24ea2a775022cdf" integrity sha512-NBDKGj14g9Z+bopIvZcQKWCzJq5JSrdmzRR1CS+iyA3Gm8SnIWBfZa7I3mTg2X6Nu8LQXG0EPKXdOGozLS4i3w== +"@types/browserify@12.0.36": + version "12.0.36" + resolved "https://registry.yarnpkg.com/@types/browserify/-/browserify-12.0.36.tgz#a7b662550bd4102b38ba83ef4ad6db871ea91331" + integrity sha512-hYXvPod5upkYTC7auziOATFsu/0MGxozbzNI80sZV044JTF7UtstHeNOM52b+bg7/taZ3fheK7oeb+jpm4C0/w== + dependencies: + "@types/insert-module-globals" "*" + "@types/node" "*" + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -161,6 +169,13 @@ "@types/vinyl" "*" chalk "^2.2.0" +"@types/insert-module-globals@*": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@types/insert-module-globals/-/insert-module-globals-7.0.1.tgz#234f9263f6b315088287e3597d7e98033804a031" + integrity sha512-qtSfo/jdYHO4jNO6QCp4CwR/TPrvR39Yan5K4nPU1iCmxcnTWiERKDXcvFGuXEmfpjrHeOCvrZPa0UrUsy+mvA== + dependencies: + "@types/node" "*" + "@types/json-schema@^7.0.3": version "7.0.3" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"