From 33167a30249f8f1900933addeb297cf7de67d0f0 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Fri, 13 Jan 2023 19:52:00 -0500 Subject: [PATCH] Tsify main (#4520) --- lib/common-utils.ts | 2 +- lib/compilers/hook.ts | 3 +- lib/options-handler.ts | 3 +- package-lock.json | 35 ++- package.json | 1 + static/analytics.ts | 3 +- static/components.interfaces.ts | 10 +- static/global.ts | 4 +- static/hub.ts | 4 +- static/local.ts | 2 +- static/{main.js => main.ts} | 382 ++++++++++++++++---------------- static/options.interfaces.ts | 19 +- static/panes/editor.ts | 3 +- static/settings.ts | 11 +- static/themes.ts | 3 +- types/languages.interfaces.ts | 4 + webpack.config.esm.js | 2 +- 17 files changed, 268 insertions(+), 223 deletions(-) rename static/{main.js => main.ts} (65%) diff --git a/lib/common-utils.ts b/lib/common-utils.ts index 76aa049cf5a..d7795d3c5d2 100644 --- a/lib/common-utils.ts +++ b/lib/common-utils.ts @@ -29,6 +29,6 @@ export function isString(x: any): x is string { // Object.keys is typed as returning :string[] for some reason // This util is for cases where the key is a union of a few possible keys and we // want the resulting array properly typed. -export function keys(o: Record): K[] { +export function keys(o: Partial>): K[] { return Object.keys(o) as K[]; } diff --git a/lib/compilers/hook.ts b/lib/compilers/hook.ts index 10443c18924..3e6c08773cc 100644 --- a/lib/compilers/hook.ts +++ b/lib/compilers/hook.ts @@ -24,12 +24,11 @@ import path from 'path'; +import {ParsedAsmResultLine} from '../../types/asmresult/asmresult.interfaces'; import {CompilationResult, ExecutionOptions} from '../../types/compilation/compilation.interfaces'; import {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces'; import {BaseCompiler} from '../base-compiler'; -import {AsmResultSource, ParsedAsmResultLine} from '../../types/asmresult/asmresult.interfaces'; - export class HookCompiler extends BaseCompiler { static get key(): string { return 'hook'; diff --git a/lib/options-handler.ts b/lib/options-handler.ts index b6cff811e93..5cb0d9e756a 100755 --- a/lib/options-handler.ts +++ b/lib/options-handler.ts @@ -54,6 +54,7 @@ export type OptionHandlerArguments = { suppressConsoleLog: boolean; }; +// TODO: Is this the same as Options in static/options.interfaces.ts? type OptionsType = { googleAnalyticsAccount: string; googleAnalyticsEnabled: boolean; @@ -188,7 +189,7 @@ export class ClientOptionsHandler { sentryEnvironment: ceProps('sentryEnvironment') || defArgs.env[0], release: defArgs.releaseBuildNumber || defArgs.gitReleaseName, gitReleaseCommit: defArgs.gitReleaseName || '', - cookieDomainRe: cookieDomainRe, + cookieDomainRe, localStoragePrefix: ceProps('localStoragePrefix'), cvCompilerCountMax: ceProps('cvCompilerCountMax', 6), defaultFontScale: ceProps('defaultFontScale', 14), diff --git a/package-lock.json b/package-lock.json index 1c9c3f57f70..26ef17e106c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@sentry/node": "^7.28.1", "@types/body-parser": "^1.19.2", "@types/file-saver": "^2.0.5", + "@types/js-cookie": "^3.0.2", "@types/http-proxy": "^1.17.9", "@types/request": "^2.48.8", "aws-sdk": "^2.1048.0", @@ -1850,6 +1851,11 @@ "@types/sizzle": "*" } }, + "node_modules/@types/js-cookie": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.2.tgz", + "integrity": "sha512-6+0ekgfusHftJNYpihfkMu8BWdeHs9EOJuGcSofErjstGPfPGEu9yTu4t460lTzzAMl2cM5zngQJqPMHbbnvYA==" + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -8505,9 +8511,9 @@ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", + "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -14255,9 +14261,9 @@ } }, "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -16865,6 +16871,11 @@ "@types/sizzle": "*" } }, + "@types/js-cookie": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.2.tgz", + "integrity": "sha512-6+0ekgfusHftJNYpihfkMu8BWdeHs9EOJuGcSofErjstGPfPGEu9yTu4t460lTzzAMl2cM5zngQJqPMHbbnvYA==" + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -21775,9 +21786,9 @@ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", + "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==", "dev": true }, "jsonfile": { @@ -26079,9 +26090,9 @@ }, "dependencies": { "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { "minimist": "^1.2.0" diff --git a/package.json b/package.json index 7b5383e0326..f6609b5f9fe 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@sentry/node": "^7.28.1", "@types/body-parser": "^1.19.2", "@types/file-saver": "^2.0.5", + "@types/js-cookie": "^3.0.2", "@types/http-proxy": "^1.17.9", "@types/request": "^2.48.8", "aws-sdk": "^2.1048.0", diff --git a/static/analytics.ts b/static/analytics.ts index 7bf417a7b33..3a734869ee1 100644 --- a/static/analytics.ts +++ b/static/analytics.ts @@ -93,5 +93,4 @@ class GAProxy { } } -const ga = new GAProxy(); -export {ga}; +export const ga = new GAProxy(); diff --git a/static/components.interfaces.ts b/static/components.interfaces.ts index 0dce4e9ec3e..8cb144f2a58 100644 --- a/static/components.interfaces.ts +++ b/static/components.interfaces.ts @@ -60,12 +60,12 @@ export interface ComponentConfig { componentState: S; } -type StateWithLanguage = {lang: string}; +export type StateWithLanguage = {lang: string}; // TODO(#4490 The War of The Types) We should normalize state types -type StateWithEditor = {source: string | number}; -type StateWithTree = {tree: number}; -type StateWithId = {id: number}; -type EmptyState = Record; +export type StateWithEditor = {source: string | number}; +export type StateWithTree = {tree: number}; +export type StateWithId = {id: number}; +export type EmptyState = Record; export type EmptyCompilerState = StateWithLanguage & StateWithEditor; export type PopulatedCompilerState = StateWithEditor & { diff --git a/static/global.ts b/static/global.ts index 2b1127fd1c9..003704572db 100644 --- a/static/global.ts +++ b/static/global.ts @@ -25,7 +25,7 @@ import {IFrontendTesting} from './tests/frontend-testing.interfaces'; import {Options} from './options.interfaces'; -type CompilerExplorerOptions = Record & Options; +export type CompilerExplorerOptions = Record & Options; declare global { export interface Window { @@ -36,6 +36,8 @@ declare global { ga: any; GoogleAnalyticsObject: any; hasUIBeenReset: boolean; + PRODUCTION: boolean; + onSponsorClick: (sponsorUrl: string) => void; } } diff --git a/static/hub.ts b/static/hub.ts index ba56fc92468..d9964745a34 100644 --- a/static/hub.ts +++ b/static/hub.ts @@ -100,9 +100,9 @@ export class Hub { public subdomainLangId: string | undefined; public defaultLangId: string; - public constructor(public readonly layout: GoldenLayout, subLangId: string, defaultLangId: string) { + public constructor(public readonly layout: GoldenLayout, subLangId: string | undefined, defaultLangId: string) { this.lastOpenedLangId = null; - this.subdomainLangId = subLangId || undefined; + this.subdomainLangId = subLangId; this.defaultLangId = defaultLangId; this.compilerService = new CompilerService(this.layout.eventHub); diff --git a/static/local.ts b/static/local.ts index 7c722bc5fbb..5cfe73bd999 100644 --- a/static/local.ts +++ b/static/local.ts @@ -26,7 +26,7 @@ import {options} from './options'; const prefix = options.localStoragePrefix ?? ''; -export function get(key: string, ifNotPresent: string): string { +export function get(key: string, ifNotPresent: T): string | T { try { return window.localStorage.getItem(prefix + key) ?? ifNotPresent; } catch (e) { diff --git a/static/main.js b/static/main.ts similarity index 65% rename from static/main.js rename to static/main.ts index 3d8293a6a77..9efd057d62a 100644 --- a/static/main.js +++ b/static/main.ts @@ -22,42 +22,52 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. -'use strict'; - // setup analytics before anything else so we can capture any future errors in sentry -var analytics = require('./analytics').ga; - -require('whatwg-fetch'); -// eslint-disable-next-line requirejs/no-js-extension -require('popper.js'); -require('bootstrap'); - -var Sharing = require('./sharing').Sharing; -var _ = require('underscore'); -var $ = require('jquery'); -var GoldenLayout = require('golden-layout'); -var Components = require('./components'); -var url = require('./url'); -var clipboard = require('clipboard'); -var Hub = require('./hub').Hub; -var Sentry = require('@sentry/browser'); -var Settings = require('./settings').Settings; -var local = require('./local'); -var Alert = require('./widgets/alert').Alert; -var themer = require('./themes'); -var motd = require('./motd'); -var jsCookie = require('js-cookie'); -var SimpleCook = require('./widgets/simplecook').SimpleCook; -var HistoryWidget = require('./widgets/history-widget').HistoryWidget; -var History = require('./history'); -var Presentation = require('./presentation').Presentation; -var setupSiteTemplateWidgetButton = require('./widgets/site-templates-widget').setupSiteTemplateWidgetButton; - -var logos = require.context('../views/resources/logos', false, /\.(png|svg)$/); - -var siteTemplateScreenshots = require.context('../views/resources/template_screenshots', false, /\.png$/); - -if (!window.PRODUCTION) { +import {ga as analytics} from './analytics'; + +import 'whatwg-fetch'; +import 'popper.js'; // eslint-disable-line requirejs/no-js-extension +import 'bootstrap'; + +import $ from 'jquery'; +import _ from 'underscore'; + +import GoldenLayout from 'golden-layout'; +import JsCookie from 'js-cookie'; +import clipboard from 'clipboard'; +import * as Sentry from '@sentry/browser'; + +// We re-assign this +let jsCookie = JsCookie; + +import {Sharing} from './sharing'; +import * as Components from './components'; +import * as url from './url'; +import {Hub} from './hub'; +import {Settings, SiteSettings} from './settings'; +import * as local from './local'; +import {Alert} from './widgets/alert'; +import * as themer from './themes'; +import * as motd from './motd'; +import {SimpleCook} from './widgets/simplecook'; +import {HistoryWidget} from './widgets/history-widget'; +import * as History from './history'; +import {Presentation} from './presentation'; +import {setupSiteTemplateWidgetButton} from './widgets/site-templates-widget'; +import {options} from './options'; +import {unwrap} from './assert'; + +import {Language, LanguageKey} from '../types/languages.interfaces'; +import {CompilerExplorerOptions} from './global'; +import {ComponentConfig, EmptyCompilerState, StateWithId, StateWithLanguage} from './components.interfaces'; + +import * as utils from '../lib/common-utils'; + +const logos = require.context('../views/resources/logos', false, /\.(png|svg)$/); + +const siteTemplateScreenshots = require.context('../views/resources/template_screenshots', false, /\.png$/); + +if (!window.PRODUCTION && !options.embedded) { require('./tests/_all'); } @@ -70,23 +80,23 @@ require('./styles/explorer.scss'); // Check to see if the current unload is a UI reset. // Forgive me the global usage here -var hasUIBeenReset = false; -var simpleCooks = new SimpleCook(); -var historyWidget = new HistoryWidget(); +let hasUIBeenReset = false; +const simpleCooks = new SimpleCook(); +const historyWidget = new HistoryWidget(); -var policyDocuments = { +const policyDocuments = { cookies: require('./generated/cookies.pug').default, privacy: require('./generated/privacy.pug').default, }; -function setupSettings(hub) { - var eventHub = hub.layout.eventHub; - var defaultSettings = { +function setupSettings(hub: Hub) { + const eventHub = hub.layout.eventHub; + const defaultSettings = { defaultLanguage: hub.defaultLangId, }; - var currentSettings = JSON.parse(local.get('settings', null)) || defaultSettings; + let currentSettings: SiteSettings = JSON.parse(local.get('settings', 'null')) || defaultSettings; - function onChange(newSettings) { + function onChange(newSettings: SiteSettings) { if (currentSettings.theme !== newSettings.theme) { analytics.proxy('send', { hitType: 'event', @@ -109,18 +119,18 @@ function setupSettings(hub) { new themer.Themer(eventHub, currentSettings); - eventHub.on('requestSettings', function () { + eventHub.on('requestSettings', () => { eventHub.emit('settingsChange', currentSettings); }); - var SettingsObject = new Settings(hub, $('#settings'), currentSettings, onChange, hub.subdomainLangId); - eventHub.on('modifySettings', function (newSettings) { + const SettingsObject = new Settings(hub, $('#settings'), currentSettings, onChange, hub.subdomainLangId); + eventHub.on('modifySettings', (newSettings: Partial) => { SettingsObject.setSettings(_.extend(currentSettings, newSettings)); }); return currentSettings; } -function hasCookieConsented(options) { +function hasCookieConsented(options: CompilerExplorerOptions) { return jsCookie.get(options.policies.cookies.key) === policyDocuments.cookies.hash; } @@ -128,20 +138,20 @@ function isMobileViewer() { return window.compilerExplorerOptions.mobileViewer; } -function calcLocaleChangedDate(policyModal) { - var timestamp = policyModal.find('#changed-date'); - timestamp.text(new Date(timestamp.attr('datetime')).toLocaleString()); +function calcLocaleChangedDate(policyModal: JQuery) { + const timestamp = policyModal.find('#changed-date'); + timestamp.text(new Date(unwrap(timestamp.attr('datetime'))).toLocaleString()); } -function setupButtons(options, hub) { - var eventHub = hub.createEventHub(); - var alertSystem = new Alert(); +function setupButtons(options: CompilerExplorerOptions, hub: Hub) { + const eventHub = hub.createEventHub(); + const alertSystem = new Alert(); // I'd like for this to be the only function used, but it gets messy to pass the callback function around, // so we instead trigger a click here when we want it to open with this effect. Sorry! if (options.policies.privacy.enabled) { - $('#privacy').on('click', function (event, data) { - var modal = alertSystem.alert( + $('#privacy').on('click', (event, data) => { + const modal = alertSystem.alert( data && data.title ? data.title : 'Privacy policy', policyDocuments.privacy.text ); @@ -157,7 +167,7 @@ function setupButtons(options, hub) { } if (options.policies.cookies.enabled) { - var getCookieTitle = function () { + const getCookieTitle = () => { return ( 'Cookies & related technologies policy

Current consent status: { analytics.proxy('send', { hitType: 'event', eventCategory: 'Sponsors', @@ -692,11 +704,9 @@ function start() { }; if (options.pageloadUrl) { - setTimeout(function () { - var visibleIcons = $('.ces-icon:visible') - .map(function (index, value) { - return value.dataset.statsid; - }) + setTimeout(() => { + const visibleIcons = $('.ces-icon:visible') + .map((_, value) => value.dataset.statsid) .get() .join(','); $.post(options.pageloadUrl + '?icons=' + encodeURIComponent(visibleIcons)); diff --git a/static/options.interfaces.ts b/static/options.interfaces.ts index 6be53f7e84b..c76b4fea9b7 100644 --- a/static/options.interfaces.ts +++ b/static/options.interfaces.ts @@ -49,10 +49,11 @@ export type Libs = Record; export type LibsPerRemote = Record; +// TODO: Is this the same as OptionsType in lib/options-handler.ts? export type Options = { libs: Libs; remoteLibs: LibsPerRemote; - languages: Record; + languages: Partial>; compilers: CompilerInfo[]; defaultCompiler: Record; defaultLibs: Record; @@ -62,6 +63,22 @@ export type Options = { sentryEnvironment?: string; compileOptions: Record; tools: Record>; + slides?: any[]; + cookieDomainRe: string; + motdUrl: string; + pageloadUrl: string; + mobileViewer: boolean; + readOnly: boolean; + policies: { + cookies: { + enabled: boolean; + key: string; + }; + privacy: { + enabled: boolean; + key: string; + }; + }; supportsExecute: boolean; supportsLibraryCodeFilter: boolean; cvCompilerCountMax: number; diff --git a/static/panes/editor.ts b/static/panes/editor.ts index 64d7a4b9ba6..066610b1f6c 100644 --- a/static/panes/editor.ts +++ b/static/panes/editor.ts @@ -196,7 +196,6 @@ export class Editor extends MonacoPane void, - private subLangId: string | null + private onChange: (siteSettings: SiteSettings) => void, + private subLangId: string | undefined ) { this.eventHub = hub.createEventHub(); this.settings = settings; @@ -324,7 +325,7 @@ export class Settings { const defaultLanguageData = Object.keys(langs).map(lang => { return {label: langs[lang].id, desc: langs[lang].name}; }); - addSelector('.defaultLanguage', 'defaultLanguage', defaultLanguageData, defLang); + addSelector('.defaultLanguage', 'defaultLanguage', defaultLanguageData, defLang as LanguageKey); if (this.subLangId) { defaultLanguageSelector diff --git a/static/themes.ts b/static/themes.ts index cf57da670db..7ba0c8e595b 100644 --- a/static/themes.ts +++ b/static/themes.ts @@ -25,6 +25,7 @@ import $ from 'jquery'; import {editor} from 'monaco-editor'; import {SiteSettings} from './settings'; +import GoldenLayout from 'golden-layout'; export type Themes = 'default' | 'dark' | 'darkplus' | 'system'; @@ -124,7 +125,7 @@ editor.defineTheme('ce-dark-plus', { export class Themer { private currentTheme: Theme | null = null; - constructor(private eventHub: any, initialSettings: SiteSettings) { + constructor(private eventHub: GoldenLayout.EventEmitter, initialSettings: SiteSettings) { this.onSettingsChange(initialSettings); this.eventHub.on('settingsChange', this.onSettingsChange, this); diff --git a/types/languages.interfaces.ts b/types/languages.interfaces.ts index cf0cf3cf9e2..a08a5de26dc 100644 --- a/types/languages.interfaces.ts +++ b/types/languages.interfaces.ts @@ -92,6 +92,10 @@ export interface Language { logoUrl: string | null; /** Path in /views/resources/logos to the logo of the language for dark mode use */ logoUrlDark: string | null; + /** Data from webpack */ + logoData?: any; + /** Data from webpack */ + logoDataDark?: any; /** Example code to show in the language's editor */ example: string; previewFilter: RegExp | null; diff --git a/webpack.config.esm.js b/webpack.config.esm.js index 05566d18197..68cd5a38c74 100644 --- a/webpack.config.esm.js +++ b/webpack.config.esm.js @@ -103,7 +103,7 @@ if (isDev) { export default { mode: isDev ? 'development' : 'production', entry: { - main: './static/main.js', + main: './static/main.ts', noscript: './static/noscript.ts', }, output: {