diff --git a/app/data/i18n/English.json b/app/data/i18n/English.json index a7546ba09..beab3fdf3 100644 --- a/app/data/i18n/English.json +++ b/app/data/i18n/English.json @@ -157,7 +157,7 @@ "theme": "Theme", "themeDay": "Light", "themeNight": "Dark", - "themeSpringSream": "Spring Stream", + "themeSpringStream": "Spring Stream", "themeLucasDracula": "Lucas Dracula", "types": "Types", "zipExport": "Export for web", diff --git a/gulpfile.js b/gulpfile.js index 3679023a2..533449c6b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,11 +3,11 @@ /* eslint no-console: 0 */ const path = require('path'), gulp = require('gulp'), - changed = require('gulp-changed'), concat = require('gulp-concat'), replace = require('gulp-replace'), sourcemaps = require('gulp-sourcemaps'), minimist = require('minimist'), + ts = require('gulp-typescript'), stylus = require('gulp-stylus'), riot = require('gulp-riot'), pug = require('gulp-pug'), @@ -149,14 +149,29 @@ const concatScripts = () => console.error('[scripts error]', err); }) .on('change', fileChangeNotifier); + const copyRequires = () => - gulp.src('./src/node_requires/**/*') + gulp.src([ + './src/node_requires/**/*', + '!./src/node_requires/**/*.ts' + ]) .pipe(sourcemaps.init()) // ¯\_(ツ)_/¯ .pipe(sourcemaps.mapSources((sourcePath) => '../../src/' + sourcePath)) .pipe(sourcemaps.write()) .pipe(gulp.dest('./app/data/node_requires')); +const tsProject = ts.createProject('tsconfig.json'); + +const processRequiresTS = () => + gulp.src('./src/node_requires/**/*.ts') + .pipe(sourcemaps.init()) + .pipe(tsProject()) + .pipe(sourcemaps.write()) + .pipe(gulp.dest('./app/data/node_requires')); + +const processRequires = gulp.series(copyRequires, processRequiresTS); + const copyInEditorDocs = () => gulp.src('./docs/docs/ct.*.md') .pipe(gulp.dest('./app/data/node_requires')); @@ -195,7 +210,7 @@ const watchStylus = () => { .on('change', fileChangeNotifier); }; const watchPug = () => { - gulp.watch('./src/pug/*.pug', compilePug) + gulp.watch('./src/pug/**/*.pug', compilePug) .on('change', fileChangeNotifier) .on('error', err => { notifier.notify(makeErrorObj('Pug failure', err)); @@ -203,7 +218,7 @@ const watchPug = () => { }); }; const watchRequires = () => { - gulp.watch('./src/node_requires/**/*', copyRequires) + gulp.watch('./src/node_requires/**/*', processRequires) .on('change', fileChangeNotifier) .on('error', err => { notifier.notify(makeErrorObj('Failure of node_requires', err)); @@ -396,7 +411,7 @@ const build = gulp.parallel([ compilePug, compileStylus, compileScripts, - copyRequires, + processRequires, copyInEditorDocs, icons, bakeTypedefs diff --git a/package-lock.json b/package-lock.json index c799be001..4c32417ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -142,6 +142,11 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" }, + "@types/node": { + "version": "14.11.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.5.tgz", + "integrity": "sha512-jVFzDV6NTbrLMxm4xDSIW/gKnk8rQLF9wAzLWIOg+5nU6ACrIMndeBdXci0FGtqJbP9tQvm6V39eshc96TO2wQ==" + }, "@types/unist": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", @@ -5046,6 +5051,91 @@ } } }, + "gulp-typescript": { + "version": "6.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-6.0.0-alpha.1.tgz", + "integrity": "sha512-KoT0TTfjfT7w3JItHkgFH1T/zK4oXWC+a8xxKfniRfVcA0Fa1bKrIhztYelYmb+95RB80OLMBreknYkdwzdi2Q==", + "requires": { + "ansi-colors": "^4.1.1", + "plugin-error": "^1.0.1", + "source-map": "^0.7.3", + "through2": "^3.0.1", + "vinyl": "^2.2.0", + "vinyl-fs": "^3.0.3" + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, "gulp-util": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", @@ -9676,6 +9766,13 @@ "integrity": "sha512-80fcJLAiUeerg4xPftp+iEEKWUjJjHk9AvcHwJqA8Zw0R4oASdu3kT/plE/Zj19QUTz8KupyOX25zStlNJjS9g==", "requires": { "typescript": "^3.2.1" + }, + "dependencies": { + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==" + } } }, "tslib": { @@ -9720,9 +9817,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typescript": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", + "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==" }, "uc.micro": { "version": "1.0.6", diff --git a/package.json b/package.json index b3b6d6a10..ed1ffba23 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "license": "MIT", "dependencies": { + "@types/node": "^14.11.5", "filemode": "^3.0.0", "fs-extra": "^9.0.1", "globby": "^11.0.1", @@ -36,6 +37,7 @@ "gulp-stylint": "^4.0.2", "gulp-stylus": "^2.7.0", "gulp-svgstore": "^7.0.1", + "gulp-typescript": "^6.0.0-alpha.1", "gulp-zip": "^5.0.1", "jsdoc-x": "^4.1.0", "minimist": "^1.2.5", @@ -47,7 +49,8 @@ "streamqueue": "^1.1.2", "stylint-stylish": "^2.0.0", "stylus": "^0.54.7", - "tsd-jsdoc": "^2.5.0" + "tsd-jsdoc": "^2.5.0", + "typescript": "^4.0.3" }, "devDependencies": { "eslint-plugin-pug": "^1.2.2", diff --git a/src/js/codeEditorHelpers.js b/src/js/codeEditorHelpers.js index 8150dbcd2..1fe6635f1 100644 --- a/src/js/codeEditorHelpers.js +++ b/src/js/codeEditorHelpers.js @@ -222,20 +222,6 @@ }]); }; - const themeMappings = { - Day: 'tomorrow', - Night: 'ambiance', - Horizon: 'horizon', - SpringStream: 'spring', - LucasDracula: 'lucasdracula', - Forest: 'forest', - default: 'tomorrow' - }; - const glob = require('./data/node_requires/glob'); - glob.codeEditorThemeMappings = themeMappings; - window.signals.on('UIThemeChanged', theme => { - monaco.editor.setTheme(themeMappings[theme] ? themeMappings[theme] : themeMappings.default); - }); window.signals.on('codeFontUpdated', () => { const editorWrappers = document.querySelectorAll('.aCodeEditor'); for (const editorWrap of editorWrappers) { @@ -262,9 +248,7 @@ return localStorage.fontFamily || 'Iosevka, monospace'; }, get theme() { - return themeMappings[localStorage.UItheme] ? - themeMappings[localStorage.UItheme] : - themeMappings.default; + return localStorage.UItheme || 'Day'; }, get fontLigatures() { return localStorage.codeLigatures !== 'off'; @@ -301,9 +285,7 @@ textarea.codeEditor = codeEditor; // eslint-disable-next-line id-blacklist codeEditor.tag = textarea; - textarea.classList.add(themeMappings[localStorage.UItheme] ? - themeMappings[localStorage.UItheme] : - themeMappings.default); + textarea.classList.add(localStorage.UItheme || 'Day'); codeEditor.getModel()._options.defaultEOL = monaco.editor.DefaultEndOfLine.LF; diff --git a/src/node_requires/monaco-themes/tomorrow.json b/src/node_requires/monaco-themes/Day.json similarity index 100% rename from src/node_requires/monaco-themes/tomorrow.json rename to src/node_requires/monaco-themes/Day.json diff --git a/src/node_requires/monaco-themes/forest.json b/src/node_requires/monaco-themes/Forest.json similarity index 100% rename from src/node_requires/monaco-themes/forest.json rename to src/node_requires/monaco-themes/Forest.json diff --git a/src/node_requires/monaco-themes/horizon.json b/src/node_requires/monaco-themes/Horizon.json similarity index 100% rename from src/node_requires/monaco-themes/horizon.json rename to src/node_requires/monaco-themes/Horizon.json diff --git a/src/node_requires/monaco-themes/lucasdracula.json b/src/node_requires/monaco-themes/LucasDracula.json similarity index 100% rename from src/node_requires/monaco-themes/lucasdracula.json rename to src/node_requires/monaco-themes/LucasDracula.json diff --git a/src/node_requires/monaco-themes/ambiance.json b/src/node_requires/monaco-themes/Night.json similarity index 100% rename from src/node_requires/monaco-themes/ambiance.json rename to src/node_requires/monaco-themes/Night.json diff --git a/src/node_requires/monaco-themes/spring.json b/src/node_requires/monaco-themes/SpringStream.json similarity index 100% rename from src/node_requires/monaco-themes/spring.json rename to src/node_requires/monaco-themes/SpringStream.json diff --git a/src/node_requires/themes/index.ts b/src/node_requires/themes/index.ts new file mode 100644 index 000000000..39a32e8a0 --- /dev/null +++ b/src/node_requires/themes/index.ts @@ -0,0 +1,81 @@ +const path = require('path'); + +const defaultTheme = 'Day'; +const builtInThemes = [ + 'Day', + 'SpringStream', + 'Forest', + 'Horizon', + 'LucasDracula', + 'Night' +]; +interface ITheme { + name: string; + translated: string; + monacoTheme: object; + css: string; +} + +const registeredThemes: ITheme[] = []; +localStorage.UItheme = localStorage.UItheme || 'Day'; + +const mod = { + registerTheme(name: string): ITheme { + if (mod.getTheme(name)) { + throw new Error(`A theme called ${name} is already registered.`); + } + const monacoTheme = require(path.join('./data/node_requires/monaco-themes', `${name}.json`)); + (window as any).monaco.editor.defineTheme(name, monacoTheme); + const css = `./data/theme${name}.css`; + const theme = { + name, + get translated() { + return (window as any).languageJSON.menu[`theme${name}`] || name; + }, + monacoTheme, + css + }; + registeredThemes.push(theme); + return theme; + }, + getTheme(name: string): ITheme | void { + return registeredThemes.find(t => t.name === name); + }, + loadBuiltInThemes() { + for (const themeName of builtInThemes) { + if (mod.getTheme(themeName)) { + continue; + } + mod.registerTheme(themeName); + } + }, + async switchToTheme(name: string) { + const fs = require('fs-extra'); + try { + const theme = mod.getTheme(name); + if (!theme) { + throw new Error(`A theme called ${name} either does not exist or is not loaded.`); + } + await fs.lstat(theme.css); + const link = (document.getElementById('themeCSS') as HTMLLinkElement); + // Avoid flickering on startup theme reloading + if (link.href !== theme.css) { + link.href = theme.css; + } + (window as any).monaco.editor.setTheme(theme.name); + (window as any).signals.trigger('UIThemeChanged', name); + localStorage.UItheme = name; + } catch (o_O) { + (window as any).alertify.error(`Could not load theme ${name}. Rolling back to the default ${defaultTheme}.`); + await mod.switchToTheme(defaultTheme); + } + }, + async loadTheme() { + mod.switchToTheme(localStorage.UItheme); + }, + getThemeList(): ITheme[] { + return [...registeredThemes]; + } +}; + +module.exports = mod; diff --git a/src/pug/includes/head.pug b/src/pug/includes/head.pug index e917137a7..f24bea03d 100644 --- a/src/pug/includes/head.pug +++ b/src/pug/includes/head.pug @@ -4,9 +4,24 @@ head meta(charset='utf-8') meta(name='description', content='IDE') meta(name='viewport', content='width=device-width, initial-scale=1') - - link(href='data/themeDay.css' id="themeCSS" rel='stylesheet') + style. + #loading { + position: fixed; + font-family: sans-serif; + left: 0; + right: 0; + top: 0; + bottom: 0; + line-height: 86vh; + text-align: center; + z-index: 50; + background: #fff; + color: #333; + font-size: 4vw; + font-weight: 300; + } + link(href='./data/themeDay.css' id="themeCSS" rel='stylesheet') script(type="text/javascript"). localStorage.UItheme = localStorage.UItheme || 'Day'; var theme = localStorage.UItheme; - document.getElementById('themeCSS').href = `data/theme${theme}.css`; + document.getElementById('themeCSS').href = `./data/theme${theme}.css`; diff --git a/src/pug/index.pug b/src/pug/index.pug index 68c469054..417bda188 100644 --- a/src/pug/index.pug +++ b/src/pug/index.pug @@ -3,6 +3,7 @@ html include includes/head.pug body.maximized(data-manage-window) root-tag + div#loading Loading… script. try { require('gulp'); @@ -40,7 +41,6 @@ html if (!window.__dirname) { window.__dirname = global.__dirname; } - div#loading Loading… script(src="data/bundle.js") script(src="data/riotTags.js") script. @@ -58,19 +58,10 @@ html eRequire(['vs/editor/editor.main'], function onMonacoLoad() { window.monaco = global.monaco; - // Custom themes - const tomorrow = require('./data/node_requires/monaco-themes/tomorrow.json'); - const horizon = require('./data/node_requires/monaco-themes/horizon.json'); - const ambiance = require('./data/node_requires/monaco-themes/ambiance.json'); - const spring = require('./data/node_requires/monaco-themes/spring.json'); - const lucasdracula = require('./data/node_requires/monaco-themes/lucasdracula.json'); - const forest = require('./data/node_requires/monaco-themes/forest.json'); - monaco.editor.defineTheme('tomorrow', tomorrow); - monaco.editor.defineTheme('horizon', horizon); - monaco.editor.defineTheme('ambiance', ambiance); - monaco.editor.defineTheme('spring', spring); - monaco.editor.defineTheme('lucasdracula', lucasdracula); - monaco.editor.defineTheme('forest', forest); + const themeManager = require('./data/node_requires/themes'); + themeManager.loadBuiltInThemes(); + // To rollback to a default theme if the set one is inaccessible ⤵ + themeManager.loadTheme(); // Extended typescript tokenizer const typescript = require('./data/node_requires/typescriptTokenizer.js').language; diff --git a/src/riotTags/main-menu.tag b/src/riotTags/main-menu.tag index a2606b156..c8cbf2d16 100644 --- a/src/riotTags/main-menu.tag +++ b/src/riotTags/main-menu.tag @@ -272,12 +272,6 @@ main-menu.flexcol }) .catch(alertify.error); }; - localStorage.UItheme = localStorage.UItheme || 'Day'; - this.switchTheme = theme => { - localStorage.UItheme = theme; - document.getElementById('themeCSS').href = `./data/theme${theme}.css`; - window.signals.trigger('UIThemeChanged', theme); - }; const troubleshootingSubmenu = { items: [{ @@ -333,59 +327,24 @@ main-menu.flexcol }] }; + const themeManager = require('./data/node_requires/themes'); + const themesSubmenu = { + items: themeManager.getThemeList().map(theme => ({ + label: theme.translated, + icon: () => localStorage.UItheme === theme.name && 'check', + click: async () => { + await themeManager.switchToTheme(theme.name); + this.update(); + } + })) + }; const settingsSubmenu = { items: [{ label: window.languageJSON.common.language, submenu: languageSubmenu }, { label: window.languageJSON.menu.theme, - submenu: { - items: [{ - label: window.languageJSON.menu.themeDay, - icon: () => localStorage.UItheme === 'Day' && 'check', - click: () => { - this.switchTheme('Day'); - } - }, { - label: window.languageJSON.menu.themeSpringStream || 'Spring Stream', - icon: () => localStorage.UItheme === 'SpringStream' && 'check', - click: () => { - this.switchTheme('SpringStream'); - } - }, { - type: 'separator' - }, { - label: window.languageJSON.menu.themeSpringStream || 'Forest', - icon: () => localStorage.UItheme === 'Forest' && 'check', - click: () => { - this.switchTheme('Forest'); - } - }, { - label: window.languageJSON.menu.themeNight || 'Night', - icon: () => localStorage.UItheme === 'Night' && 'check', - click: () => { - this.switchTheme('Night'); - } - }, { - label: window.languageJSON.menu.themeHorizon || 'Horizon', - icon: () => localStorage.UItheme === 'Horizon' && 'check', - click: () => { - this.switchTheme('Horizon'); - } - }, { - label: window.languageJSON.menu.themeLucasDracula || 'LucasDracula', - icon: () => localStorage.UItheme === 'LucasDracula' && 'check', - click: () => { - this.switchTheme('LucasDracula'); - } - }, { - label: window.languageJSON.menu.highContrastBlack || 'High Contrast (black)', - icon: () => localStorage.UItheme === 'hcBlack' && 'check', - click: () => { - this.switchTheme('hcBlack'); - } - }] - } + submenu: themesSubmenu }, { label: window.languageJSON.menu.codeFont, submenu: { diff --git a/src/styl/themeHCBlack.styl b/src/styl/themeHCBlack.styl new file mode 100644 index 000000000..623d140cd --- /dev/null +++ b/src/styl/themeHCBlack.styl @@ -0,0 +1,60 @@ +@charset "utf-8" + +foreground = #fff +text = #fff +background = #07060E +backgroundDeeper = #000 +shadows = #000 + +borderPale = #333 +borderBright = #666 + + +/* Frequently used properties */ +trans = + transition 0.35s ease all +transshort = + transition 0.15s ease all +shad = + box-shadow 0 0.1rem 0.2rem rgba(shadows, 0.5) +shadamb = + box-shadow 0 0 0.35rem rgba(shadows, 0.5) + +/* Base fonts for UI */ +fonts = font = 'Open Sans', sans-serif, serif +font-mono = mono = Iosevka, monospace + +br = 0.2rem +iconsize = 1.5rem + +/* Colors used by this theme */ +act = #ffff00 +acttext = act +accent1 = #00ffff +accent2 = #0099ff +error = #ff3333 +red = error +success = #33ff33 +green = success +warning = #ff9900 +orange = warning + +theme = 'HCBlack' +themeDark = true + +@require 'hvost.styl' + +@require '3rdParty/*.styl' +@require './../../app/node_modules/highlight.js/styles/atom-one-dark.css' + +@require 'common.styl' +@require 'inputs.styl' +@require 'typography.styl' +@require 'confetti.styl' +@require 'buildingBlocks.styl' +@require 'tabs.styl' + +@require 'tags/**/*.styl' + +button, .button + border-width 2px diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..f9dd87470 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "umd", + "noImplicitAny": true, + "moduleResolution": "node" + } +} \ No newline at end of file