diff --git a/.eslintrc.base.cjs b/.eslintrc.base.cjs index 0213e319fc..13136de1c8 100644 --- a/.eslintrc.base.cjs +++ b/.eslintrc.base.cjs @@ -1,298 +1,143 @@ +// @ts-check let todoTreeKeywordsWarning = ['TODO', 'TODOS', 'TODO WIP', 'FIXME', 'WIP']; let todoTreeKeywordsAll = [...todoTreeKeywordsWarning, 'NOTE', 'NOTES', 'LIST']; -module.exports = { - extends: ['eslint:recommended'], +/** @type {import('eslint').Linter.Config} */ +const eslintConfig = { + /** @see https://eslint.org/docs/latest/rules/ */ + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended' + // 'plugin:@typescript-eslint/recommended-type-checked' + ], env: { node: true, - //NOTE Set to es2022 once VSCode eslint extension updates - // https://github.com/eslint/eslint/pull/15587 - es2021: true, + es2022: true }, + /** @type {Partial} */ rules: { - // [Possible Problems] 'array-callback-return': [ - 1, + 'warn', { - // allowImplicit: false, - checkForEach: true, - }, + // @ts-ignore incorrect type definition + checkForEach: true + } ], - // "constructor-super": 2, - 'for-direction': 1, // Was 2 - 'getter-return': 1, // Was 2 - 'no-async-promise-executor': 1, // Was 2 - 'no-await-in-loop': 1, - 'no-class-assign': 1, // Was 2 - 'no-compare-neg-zero': 1, // Was 2 + 'for-direction': 'warn', + 'getter-return': 'warn', + 'no-async-promise-executor': 'warn', + 'no-class-assign': 'warn', + 'no-compare-neg-zero': 'warn', 'no-cond-assign': [ - 1, // Was 2 - 'always', // Was "except-parens" + 'warn', + 'always' // Was "except-parens" ], // "no-const-assign": 2, - 'no-constant-condition': [ - 1, // Was 2 - { checkLoops: false }, - ], - 'no-constructor-return': 1, - 'no-control-regex': 1, // Was 2 - 'no-debugger': 1, // Was 2 + 'no-constant-condition': ['warn', { checkLoops: false }], + 'no-control-regex': 'warn', + 'no-debugger': 'warn', // "no-dupe-args": 2, - 'no-dupe-class-members': 1, // Was 2 - 'no-dupe-else-if': 1, // Was 2 - 'no-dupe-keys': 1, // Was 2 - 'no-duplicate-case': 1, // Was 2 - 'no-duplicate-imports': 1, - 'no-empty-character-class': 1, // Was 2 - 'no-empty-pattern': 1, // Was 2 - 'no-ex-assign': 1, // Was 2 - 'no-fallthrough': 1, // Was 2 - 'no-func-assign': 1, // Was 2 + 'no-dupe-class-members': 'warn', + 'no-dupe-else-if': 'warn', + 'no-dupe-keys': 'warn', + 'no-duplicate-case': 'warn', + 'no-empty-character-class': 'warn', + 'no-empty-pattern': 'warn', + 'no-ex-assign': 'warn', + 'no-fallthrough': 'warn', + 'no-func-assign': 'warn', // "no-import-assign": 2, 'no-inner-declarations': 0, // Was 2 // "no-invalid-regexp": 2, 'no-irregular-whitespace': [ - 1, // Was 2 + 'warn', { // skipStrings: true, // skipComments: false, // skipRegExps: false, - skipTemplates: true, - }, + skipTemplates: true + } ], - 'no-loss-of-precision': 1, // Was 2 - 'no-misleading-character-class': 1, // Was 2 + 'no-loss-of-precision': 'warn', + 'no-misleading-character-class': 'warn', // "no-new-symbol": 2, // "no-obj-calls": 2, - 'no-promise-executor-return': 1, // "no-prototype-builtins": 2, - 'no-self-assign': 1, // Was 2 - 'no-self-compare': 1, - 'no-setter-return': 1, // Was 2 - 'no-sparse-arrays': 1, // Was 2 - 'no-template-curly-in-string': 1, + 'no-self-assign': 'warn', + 'no-setter-return': 'warn', + 'no-sparse-arrays': 'warn', // "no-this-before-super": 2, - 'no-undef': [2, { typeof: true }], - 'no-unexpected-multiline': 1, // Was 2 - 'no-unmodified-loop-condition': 1, - 'no-unreachable': 1, // Was 2 - 'no-unreachable-loop': 1, - 'no-unsafe-finally': 1, // Was 2 - 'no-unsafe-negation': [ - 1, // Was 2 - { enforceForOrderingRelations: true }, - ], + 'no-unexpected-multiline': 'warn', + 'no-unreachable': 'warn', + 'no-unsafe-finally': 'warn', + // FIXME: Investigate type error + // 'no-unsafe-negation': [ + // "warn", + // { enforceForOrderingRelations: true }, + // ], 'no-unsafe-optional-chaining': [2, { disallowArithmeticOperators: true }], - 'no-unused-private-class-members': 1, - 'no-unused-vars': [ - 1, // Was 2 - { - // vars: "all", - // args: "after-used", - // ignoreRestSiblings: false, - argsIgnorePattern: '^_', - caughtErrors: 'all', // Was "none" - caughtErrorsIgnorePattern: '^_', - }, - ], - 'no-use-before-define': [ - 1, - { - functions: false, - // classes: true, - // variables: true - }, - ], - 'no-useless-backreference': 1, - 'require-await': 1, - 'require-atomic-updates': 1, - 'use-isnan': [ - 1, // Was 2 - { - // enforceForSwitchCase: true, - enforceForIndexOf: true, - }, - ], - 'valid-typeof': [ - 1, // Was 2 - { requireStringLiterals: true }, - ], + // FIXME: Investigate type error + // 'use-isnan': [ + // "warn", + // { + // // enforceForSwitchCase: true, + // enforceForIndexOf: true, + // }, + // ], + 'valid-typeof': ['warn', { requireStringLiterals: true }], // [Suggestions] - 'accessor-pairs': 1, - 'arrow-body-style': 1, - 'block-scoped-var': 1, - camelcase: 1, // "capitalized-comments": 0, // Allow commented code // "class-methods-use-this": 0, - complexity: 1, - 'consistent-return': 1, - 'consistent-this': 1, curly: [ 1, - 'multi-line', // Was "all" + 'multi-line' // Was "all" ], // "default-case": 0, - 'default-case-last': 1, - 'default-param-last': 1, - 'dot-notation': 1, - eqeqeq: 1, - 'func-name-matching': [ - 1, - 'always', // Same - { - considerPropertyDescriptor: true, - // includeCommonJSModuleExports: false - }, - ], - // "func-names": 0, - 'func-style': [ - 1, - 'declaration', // Was "expression" - ], - 'grouped-accessor-pairs': [ - 1, - 'getBeforeSet', // Was "anyOrder" - ], - // "guard-for-in": 0, - // "id-denylist": 0, - // "id-length": 0, - // "id-match": 0, - // "init-declarations": 0, - // "max-classes-per-file": 0, - // "max-depth": 0, - // "max-lines": 0, - // "max-lines-per-function": 0, - // "max-nested-callbacks": 0, - // "max-params": 0, - // "max-statements": 0, - // "multiline-comment-style": 0, - 'new-cap': 1, - // "no-alert": 0, - 'no-array-constructor': 1, - 'no-bitwise': 1, + eqeqeq: 'error', 'no-caller': 2, - 'no-case-declarations': 1, // Was 2 - 'no-confusing-arrow': 1, - // "no-console": 0, - // "no-continue": 0, - // "no-delete-var": 2, - // "no-div-regex": 0, + 'no-case-declarations': 'warn', 'no-else-return': [1, { allowElseIf: false }], - 'no-empty': [ - 1, // Was 2 - { allowEmptyCatch: true }, - ], - 'no-empty-function': 1, - 'no-eq-null': 1, - // "no-eval": 0, - 'no-extend-native': 1, - 'no-extra-bind': 1, - 'no-extra-boolean-cast': [ - 1, // Was 2 - { enforceForLogicalOperands: true }, - ], - 'no-extra-label': 1, - 'no-extra-semi': 1, // Was 2 - 'no-floating-decimal': 1, - // "no-global-assign": 2, - 'no-implicit-coercion': 1, - 'no-implicit-globals': [1, { lexicalBindings: true }], - 'no-implied-eval': 1, - // "no-inline-comments": 0, - 'no-invalid-this': 2, - 'no-iterator': 1, - 'no-label-var': 1, - // "no-labels": 0, - 'no-lone-blocks': 1, - 'no-lonely-if': 1, - 'no-loop-func': 1, - // "no-magic-numbers": 0, - 'no-mixed-operators': 0, - 'no-multi-assign': 1, - 'no-multi-str': 1, - // "no-negated-condition": 0, - // "no-nested-ternary": 0, - 'no-new': 1, - 'no-new-func': 1, - 'no-new-object': 1, - 'no-new-wrappers': 1, - // "no-nonoctal-decimal-escape": 2, - // "no-octal": 2, - 'no-octal-escape': 2, - // "no-param-reassign": 0, - // "no-plusplus": 0, - 'no-proto': 1, - 'no-redeclare': 1, // Was 2 - 'no-regex-spaces': 1, // Was 2 - // "no-restricted-exports": 0, - // "no-restricted-globals": 0, - // "no-restricted-imports": 0, - // "no-restricted-properties": 0, - // "no-restricted-syntax": 0, - 'no-return-assign': 1, - 'no-return-await': 1, - 'no-script-url': 1, - 'no-sequences': 1, - 'no-shadow': [ - 1, + 'no-empty': ['warn', { allowEmptyCatch: true }], + 'no-implicit-globals': [ + 'error', { - builtinGlobals: true, - // hoist: "functions" - }, + lexicalBindings: true + } ], - // "no-shadow-restricted-names": 2, - // "no-ternary": 0, - 'no-throw-literal': 1, - 'no-undef-init': 1, - // "no-undefined": 0, - // "no-underscore-dangle": 0, + 'no-invalid-this': 'error', + 'no-octal-escape': 'error', + 'no-shadow': 'warn', 'no-unneeded-ternary': [ - 1, + 'error', { - defaultAssignment: false, // Use || or ?? instead - }, + defaultAssignment: false // Use || or ?? instead + } ], - 'no-unused-expressions': 1, - 'no-unused-labels': 1, // Was 2 - 'no-useless-call': 1, - 'no-useless-catch': 1, // Was 2 - 'no-useless-computed-key': [1, { enforceForClassMembers: true }], - 'no-useless-concat': 1, - 'no-useless-constructor': 1, - 'no-useless-escape': 1, // Was 2 - 'no-useless-rename': 1, - 'no-useless-return': 1, - 'no-var': 1, + 'no-unused-expressions': 'error', + 'no-unused-labels': 'error', + 'no-useless-call': 'error', + 'no-useless-catch': 'error', + 'no-useless-computed-key': ['error', { enforceForClassMembers: true }], + 'no-useless-concat': 'error', + 'no-useless-constructor': 'warn', + 'no-useless-escape': 'error', + 'no-useless-rename': 'error', + 'no-useless-return': 'error', + 'no-var': 'error', 'no-void': 1, - 'no-warning-comments': [ - 1, - { - terms: todoTreeKeywordsWarning, - // location: "start" - }, - ], - // "no-with": 2, - 'object-shorthand': [ - 1, - 'always', // Same - { - // avoidQuotes: false, - // ignoreConstructors: false, - avoidExplicitReturnArrows: true, - }, - ], + 'no-warning-comments': [1, { terms: todoTreeKeywordsWarning }], + 'object-shorthand': ['warn', 'always', { avoidExplicitReturnArrows: true }], 'one-var': [ 1, - 'never', // Was "always" + 'never' // Was "always" ], 'one-var-declaration-per-line': 1, 'operator-assignment': 1, 'prefer-arrow-callback': 1, // "prefer-const": 0, - // "prefer-destructuring": 0, 'prefer-exponentiation-operator': 1, 'prefer-named-capture-group': 1, 'prefer-numeric-literals': 1, @@ -309,28 +154,25 @@ module.exports = { { keywords: true, // unnecessary: true, - numbers: true, - }, + numbers: true + } ], radix: [ 1, - 'as-needed', // Was "always" + 'as-needed' // Was "always" ], - 'require-await': 1, 'require-unicode-regexp': 1, - 'require-yield': 1, // Was 2 - // "sort-keys": 0, - // "sort-vars": 0, + 'require-yield': 'warn', 'spaced-comment': [ - 1, + 'error', 'always', // Same - { markers: todoTreeKeywordsAll }, + { markers: todoTreeKeywordsAll } ], '@typescript-eslint/consistent-type-imports': [ 1, { fixStyle: 'inline-type-imports', - prefer: 'type-imports', + prefer: 'type-imports' } ], // "strict": 0, // Don't force, though rule configs assume strict errors @@ -341,12 +183,12 @@ module.exports = { // [Layout & Formatting] 'array-bracket-newline': [ 1, - 'consistent', // Was "multiline". Limitation: No consistent + multiline + 'consistent' // Was "multiline". Limitation: No consistent + multiline ], 'array-bracket-spacing': 1, 'array-element-newline': [ 1, - 'consistent', // Was "always". Limitation: No consistent + multiline + 'consistent' // Was "always". Limitation: No consistent + multiline ], 'arrow-parens': 1, 'arrow-spacing': 1, @@ -358,120 +200,46 @@ module.exports = { 'computed-property-spacing': 1, 'dot-location': [ 1, - 'property', // Was "object" + 'property' // Was "object" ], - 'eol-last': 1, + 'eol-last': 'error', 'func-call-spacing': 1, 'function-call-argument-newline': [ 1, - 'consistent', // Was "always". Limitation: No consistent + multiline + 'consistent' // Was "always". Limitation: No consistent + multiline ], 'function-paren-newline': [ 1, - 'consistent', // Was "multiline". Limitation: No consistent + multiline + 'consistent' // Was "multiline". Limitation: No consistent + multiline ], 'generator-star-spacing': [ 1, - 'after', // Was "before" + 'after' // Was "before" ], - 'implicit-arrow-linebreak': 1, - indent: [ - 1, - 'tab', // Was 4 - { - SwitchCase: 1, // Was 0 - // VariableDeclarator: 1, - // outerIIFEBody: 1, - // MemberExpression: 1, - // FunctionDeclaration: { - // parameters: 1, - // body: 1 - // }, - // FunctionExpression: { - // parameters: 1, - // body: 1 - // }, - // StaticBlock: { - // body: 1 - // }, - // CallExpression: { - // arguments: 1, - // }, - // ArrayExpression: 1, - // ObjectExpression: 1, - // ImportDeclaration: 1, - // flatTernaryExpressions: false, - // offsetTernaryExpressions: false, - // ignoreComments: false - }, - ], - 'jsx-quotes': 1, - 'key-spacing': 1, - 'keyword-spacing': 1, - // "line-comment-position": 0, - // 'linebreak-style': [ - // 1, - // 'windows', // Was "unix" - // ], - // "lines-around-comment": 0, - // "lines-between-class-members": 0, - // "max-len": 0, + 'jsx-quotes': 'error', + 'key-spacing': 'error', + 'keyword-spacing': 'error', 'max-statements-per-line': 1, - 'multiline-ternary': [ - 1, - 'always-multiline', // Was "always" - ], - 'new-parens': 1, - 'newline-per-chained-call': [ - 1, - { - ignoreChainWithDepth: 1, // Was 2 - }, - ], - // "no-extra-parens": 0, // Limitation: No exception for ternary conditions - 'no-mixed-spaces-and-tabs': 1, // Was 2 - 'no-multi-spaces': 1, - 'no-multiple-empty-lines': [ - 1, - { - max: 3, // Was 2 - maxEOF: 0, - maxBOF: 0, - }, - ], - // "no-tabs": 0, // Limitation: allowIndentationTabs doesn't allow partial tabs from commenting a block with deeper indentation - 'no-trailing-spaces': 1, + 'no-trailing-spaces': 'error', 'no-whitespace-before-property': 1, 'nonblock-statement-body-position': 1, 'object-curly-newline': [ 1, { multiline: true, - consistent: true, // Same. Only default if no object option - }, + consistent: true // Same. Only default if no object option + } ], 'object-curly-spacing': [ 1, - 'always', // Was "never" + 'always' // Was "never" ], 'object-property-newline': 1, - 'operator-linebreak': [ - 1, - 'before', // Was "after" - ], 'padded-blocks': [ 1, - 'never', // Was "always" + 'never' // Was "always" ], // "padding-line-between-statements": 0, - quotes: [ - 1, - 'double', // Same - { - avoidEscape: true, - // allowTemplateLiterals: false - }, - ], 'rest-spread-spacing': 1, semi: 1, 'semi-spacing': 1, @@ -482,8 +250,8 @@ module.exports = { { anonymous: 'never', named: 'never', - asyncArrow: 'always', - }, + asyncArrow: 'always' + } ], 'space-in-parens': 1, 'space-infix-ops': 1, @@ -491,14 +259,15 @@ module.exports = { 'switch-colon-spacing': 1, 'template-curly-spacing': 1, 'template-tag-spacing': 1, - 'unicode-bom': 1, 'wrap-iife': [ 1, 'inside', // Was "outside" - { functionPrototypeMethods: true }, + { functionPrototypeMethods: true } ], // "wrap-regex": 0, - 'yield-star-spacing': 1, - }, + 'yield-star-spacing': 1 + } }; + +module.exports = eslintConfig; diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..d0f8949183 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "printWidth": 100, + "arrowParens": "always", + "trailingComma": "none" +} diff --git a/devserver/.eslintrc.json b/devserver/.eslintrc.json index f07262d9cb..587364d42b 100644 --- a/devserver/.eslintrc.json +++ b/devserver/.eslintrc.json @@ -1,22 +1,24 @@ { "root": true, "env": { "browser": true, "es2020": true }, - "extends": [ "../.eslintrc.base.cjs" ], + "extends": ["../.eslintrc.base.cjs"], "ignorePatterns": ["dist", ".eslintrc.cjs"], "parser": "@typescript-eslint/parser", "plugins": ["react", "@typescript-eslint"], "rules": { "func-style": 0, "no-empty-function": 0, + + "@typescript-eslint/no-explicit-any": "off", + "no-unused-vars": "off", // disable base rule, as it can report incorrect errors "@typescript-eslint/no-unused-vars": [ - 1, // Was 2 + "error", { - // vars: "all", - // args: "after-used", - // ignoreRestSiblings: false, - "argsIgnorePattern": "^_", - "caughtErrors": "all", // Was "none" - "caughtErrorsIgnorePattern": "^_" + "vars": "all", + "args": "none", + "ignoreRestSiblings": false, + "varsIgnorePattern": "^_", + "argsIgnorePattern": "^_" } ] } diff --git a/devserver/src/components/ControlButton.tsx b/devserver/src/components/ControlButton.tsx index eea41eb958..1506432f5c 100644 --- a/devserver/src/components/ControlButton.tsx +++ b/devserver/src/components/ControlButton.tsx @@ -1,5 +1,5 @@ -import { AnchorButton, Button, Icon, type IconName, Intent } from "@blueprintjs/core"; -import React from "react"; +import { AnchorButton, Button, Icon, Intent, type IconName } from '@blueprintjs/core'; +import React from 'react'; type ButtonOptions = { className: string; @@ -8,7 +8,7 @@ type ButtonOptions = { iconOnRight: boolean; intent: Intent; minimal: boolean; - type?: "submit" | "reset" | "button"; + type?: 'submit' | 'reset' | 'button'; }; type ControlButtonProps = { @@ -20,45 +20,45 @@ type ControlButtonProps = { }; const defaultOptions = { - className: "", - fullWidth: false, - iconOnRight: false, - intent: Intent.NONE, - minimal: true + className: '', + fullWidth: false, + iconOnRight: false, + intent: Intent.NONE, + minimal: true }; const ControlButton: React.FC = ({ - label = "", - icon, - onClick, - options = {}, - isDisabled = false + label = '', + icon, + onClick, + options = {}, + isDisabled = false }) => { - const buttonOptions: ButtonOptions = { - ...defaultOptions, - ...options - }; - const iconElement = icon && ; - // Refer to #2417 and #2422 for why we conditionally - // set the button component. See also: - // https://blueprintjs.com/docs/#core/components/button - const ButtonComponent = isDisabled ? AnchorButton : Button; + const buttonOptions: ButtonOptions = { + ...defaultOptions, + ...options + }; + const iconElement = icon && ; + // Refer to #2417 and #2422 for why we conditionally + // set the button component. See also: + // https://blueprintjs.com/docs/#core/components/button + const ButtonComponent = isDisabled ? AnchorButton : Button; - return ( - - {label} - - ); + return ( + + {label} + + ); }; export default ControlButton; diff --git a/devserver/src/components/Playground.tsx b/devserver/src/components/Playground.tsx index be6589e3b4..3e8e2b7526 100644 --- a/devserver/src/components/Playground.tsx +++ b/devserver/src/components/Playground.tsx @@ -40,7 +40,7 @@ const createContextHelper = () => { return tempContext; }; -const Playground: React.FC<{}> = () => { +const Playground: React.FC = () => { const [dynamicTabs, setDynamicTabs] = React.useState([]); const [selectedTabId, setSelectedTab] = React.useState(testTabContent.id); const [codeContext, setCodeContext] = React.useState(createContextHelper()); diff --git a/devserver/src/components/Workspace.tsx b/devserver/src/components/Workspace.tsx index 36bd146696..6bdc7f21da 100644 --- a/devserver/src/components/Workspace.tsx +++ b/devserver/src/components/Workspace.tsx @@ -1,17 +1,17 @@ -import { FocusStyleManager } from "@blueprintjs/core"; -import { type Enable, Resizable, type ResizeCallback } from "re-resizable"; -import React from "react"; +import { FocusStyleManager } from '@blueprintjs/core'; +import { Resizable, type Enable, type ResizeCallback } from 're-resizable'; +import React from 'react'; -import ControlBar, { type ControlBarProps } from "./controlBar/ControlBar"; -import Editor from "./editor/Editor"; -import Repl, { type ReplProps } from "./repl/Repl"; -import SideContent, { type SideContentProps } from "./sideContent/SideContent"; -import { useDimensions } from "./utils/Hooks"; +import ControlBar, { type ControlBarProps } from './controlBar/ControlBar'; +import Editor from './editor/Editor'; +import Repl, { type ReplProps } from './repl/Repl'; +import SideContent, { type SideContentProps } from './sideContent/SideContent'; +import { useDimensions } from './utils/Hooks'; type DispatchProps = { handleEditorEval: () => void; - handleEditorValueChange: (newValue: string) => void - handlePromptAutocomplete: (row: number, col: number, callback: any) => void + handleEditorValueChange: (newValue: string) => void; + handlePromptAutocomplete: (row: number, col: number, callback: any) => void; }; type StateProps = { @@ -21,9 +21,9 @@ type StateProps = { replProps: ReplProps; sideContentHeight?: number; sideContentIsResizeable?: boolean; - editorValue: string + editorValue: string; - sideContentProps: SideContentProps + sideContentProps: SideContentProps; }; const rightResizeOnly: Enable = { right: true }; @@ -32,113 +32,110 @@ const bottomResizeOnly: Enable = { bottom: true }; export type WorkspaceProps = DispatchProps & StateProps; const Workspace: React.FC = (props) => { - const contentContainerDiv = React.useRef(null); - const editorDividerDiv = React.useRef(null); - const leftParentResizable = React.useRef(null); - const maxDividerHeight = React.useRef(null); - const sideDividerDiv = React.useRef(null); + const contentContainerDiv = React.useRef(null); + const editorDividerDiv = React.useRef(null); + const leftParentResizable = React.useRef(null); + const maxDividerHeight = React.useRef(null); + const sideDividerDiv = React.useRef(null); - const [contentContainerWidth] = useDimensions(contentContainerDiv); + const [contentContainerWidth] = useDimensions(contentContainerDiv); - const [sideContentHeight, setSideContentHeight] = React.useState(undefined); + const [sideContentHeight, setSideContentHeight] = React.useState(undefined); - FocusStyleManager.onlyShowFocusOnTabs(); + FocusStyleManager.onlyShowFocusOnTabs(); - React.useEffect(() => { - if (props.sideContentIsResizeable && maxDividerHeight.current === null) { - maxDividerHeight.current = sideDividerDiv.current!.clientHeight; - } - }); + React.useEffect(() => { + if (props.sideContentIsResizeable && maxDividerHeight.current === null) { + maxDividerHeight.current = sideDividerDiv.current!.clientHeight; + } + }); - /** + /** * Snaps the left-parent resizable to 100% or 0% when percentage width goes * above 95% or below 5% respectively. Also changes the editor divider width * in the case of < 5%. */ - const toggleEditorDividerDisplay: ResizeCallback = (_a, _b, ref) => { - const leftThreshold = 5; - const rightThreshold = 95; - const editorWidthPercentage - = ((ref as HTMLDivElement).clientWidth / contentContainerWidth) * 100; - // update resizable size - if (editorWidthPercentage > rightThreshold) { + const toggleEditorDividerDisplay: ResizeCallback = (_a, _b, ref) => { + const leftThreshold = 5; + const rightThreshold = 95; + const editorWidthPercentage = + ((ref as HTMLDivElement).clientWidth / contentContainerWidth) * 100; + // update resizable size + if (editorWidthPercentage > rightThreshold) { leftParentResizable.current!.updateSize({ - width: "100%", - height: "100%" + width: '100%', + height: '100%' }); - } else if (editorWidthPercentage < leftThreshold) { + } else if (editorWidthPercentage < leftThreshold) { leftParentResizable.current!.updateSize({ - width: "0%", - height: "100%" + width: '0%', + height: '100%' }); - } - }; + } + }; - /** + /** * Hides the side-content-divider div when side-content is resized downwards * so that it's bottom border snaps flush with editor's bottom border */ - const toggleDividerDisplay: ResizeCallback = (_a, _b, ref) => { - maxDividerHeight.current - = sideDividerDiv.current!.clientHeight > maxDividerHeight.current! - ? sideDividerDiv.current!.clientHeight - : maxDividerHeight.current; - const resizableHeight = (ref as HTMLDivElement).clientHeight; - const rightParentHeight = (ref.parentNode as HTMLDivElement).clientHeight; - if (resizableHeight + maxDividerHeight.current! + 2 > rightParentHeight) { - sideDividerDiv.current!.style.display = "none"; - } else { - sideDividerDiv.current!.style.display = "initial"; - } - }; + const toggleDividerDisplay: ResizeCallback = (_a, _b, ref) => { + maxDividerHeight.current = + sideDividerDiv.current!.clientHeight > maxDividerHeight.current! + ? sideDividerDiv.current!.clientHeight + : maxDividerHeight.current; + const resizableHeight = (ref as HTMLDivElement).clientHeight; + const rightParentHeight = (ref.parentNode as HTMLDivElement).clientHeight; + if (resizableHeight + maxDividerHeight.current! + 2 > rightParentHeight) { + sideDividerDiv.current!.style.display = 'none'; + } else { + sideDividerDiv.current!.style.display = 'initial'; + } + }; - return ( -
- -
-
-
- - {}} - handlePromptAutocomplete={props.handlePromptAutocomplete} - handleSendReplInputToOutput={() => {}} - editorValue={props.editorValue} - /> - -
- setSideContentHeight(ref.clientHeight)} - > - -
- - -
-
-
-
- ); + return ( +
+ +
+
+
+ + {}} + handlePromptAutocomplete={props.handlePromptAutocomplete} + handleSendReplInputToOutput={() => {}} + editorValue={props.editorValue} + /> + +
+ setSideContentHeight(ref.clientHeight)} + > + +
+ + +
+
+
+
+ ); }; export default Workspace; diff --git a/devserver/src/components/controlBar/ControlBar.tsx b/devserver/src/components/controlBar/ControlBar.tsx index 52b7175dce..249cacc573 100644 --- a/devserver/src/components/controlBar/ControlBar.tsx +++ b/devserver/src/components/controlBar/ControlBar.tsx @@ -1,6 +1,6 @@ -import { Classes } from "@blueprintjs/core"; -import classNames from "classnames"; -import React, { type JSX } from "react"; +import { Classes } from '@blueprintjs/core'; +import classNames from 'classnames'; +import React, { type JSX } from 'react'; export type ControlBarProps = { editorButtons: Array; @@ -9,29 +9,29 @@ export type ControlBarProps = { }; const ControlBar: React.FC = (props) => { - const editorControl = ( -
- {props.editorButtons} -
- ); + const editorControl = ( +
+ {props.editorButtons} +
+ ); - const flowControl = props.flowButtons && ( -
{props.flowButtons}
- ); + const flowControl = props.flowButtons && ( +
{props.flowButtons}
+ ); - const editingWorkspaceControl = ( -
- {props.editingWorkspaceButtons} -
- ); + const editingWorkspaceControl = ( +
+ {props.editingWorkspaceButtons} +
+ ); - return ( -
- {editorControl} - {flowControl} - {editingWorkspaceControl} -
- ); + return ( +
+ {editorControl} + {flowControl} + {editingWorkspaceControl} +
+ ); }; export default ControlBar; diff --git a/devserver/src/components/controlBar/ControlBarClearButton.tsx b/devserver/src/components/controlBar/ControlBarClearButton.tsx index 95ec26220b..a02f681b10 100644 --- a/devserver/src/components/controlBar/ControlBarClearButton.tsx +++ b/devserver/src/components/controlBar/ControlBarClearButton.tsx @@ -1,15 +1,13 @@ -import { Tooltip2 } from "@blueprintjs/popover2"; -import ControlButton from "../ControlButton"; -import { IconNames } from "@blueprintjs/icons"; +import { IconNames } from '@blueprintjs/icons'; +import { Tooltip2 } from '@blueprintjs/popover2'; +import ControlButton from '../ControlButton'; type Props = { - onClick: () => void -} + onClick: () => void; +}; -export const ControlBarClearButton = (props: Props) => - -; +export const ControlBarClearButton = (props: Props) => ( + + + +); diff --git a/devserver/src/components/controlBar/ControlBarRefreshButton.tsx b/devserver/src/components/controlBar/ControlBarRefreshButton.tsx index a28cc53766..72a14a6222 100644 --- a/devserver/src/components/controlBar/ControlBarRefreshButton.tsx +++ b/devserver/src/components/controlBar/ControlBarRefreshButton.tsx @@ -1,15 +1,13 @@ -import { Tooltip2 } from "@blueprintjs/popover2"; -import ControlButton from "../ControlButton"; -import { IconNames } from "@blueprintjs/icons"; +import { IconNames } from '@blueprintjs/icons'; +import { Tooltip2 } from '@blueprintjs/popover2'; +import ControlButton from '../ControlButton'; type Props = { - onClick: () => void -} + onClick: () => void; +}; -export const ControlBarRefreshButton = (props: Props) => - -; +export const ControlBarRefreshButton = (props: Props) => ( + + + +); diff --git a/devserver/src/components/controlBar/ControlBarRunButton.tsx b/devserver/src/components/controlBar/ControlBarRunButton.tsx index 80dfb6e9ea..df7cadcd17 100644 --- a/devserver/src/components/controlBar/ControlBarRunButton.tsx +++ b/devserver/src/components/controlBar/ControlBarRunButton.tsx @@ -1,9 +1,9 @@ -import { Position } from "@blueprintjs/core"; -import { IconNames } from "@blueprintjs/icons"; -import { Tooltip2 } from "@blueprintjs/popover2"; -import React from "react"; +import { Position } from '@blueprintjs/core'; +import { IconNames } from '@blueprintjs/icons'; +import { Tooltip2 } from '@blueprintjs/popover2'; +import React from 'react'; -import ControlButton from "../ControlButton"; +import ControlButton from '../ControlButton'; type DispatchProps = { handleEditorEval: () => void; @@ -18,18 +18,18 @@ type StateProps = { type ControlButtonRunButtonProps = DispatchProps & StateProps; export const ControlBarRunButton: React.FC = (props) => { - const tooltipContent = "Evaluate the program"; - return ( - - - - ); + const tooltipContent = 'Evaluate the program'; + return ( + + + + ); }; diff --git a/devserver/src/components/editor/Editor.tsx b/devserver/src/components/editor/Editor.tsx index 1896a52f52..d378a0038b 100644 --- a/devserver/src/components/editor/Editor.tsx +++ b/devserver/src/components/editor/Editor.tsx @@ -1,18 +1,18 @@ -import { type Ace, require as acequire } from "ace-builds"; -import "ace-builds/src-noconflict/ext-language_tools"; -import "ace-builds/src-noconflict/ext-searchbox"; -import "ace-builds/src-noconflict/ace"; -import "ace-builds/esm-resolver"; +import { require as acequire, type Ace } from 'ace-builds'; +import 'ace-builds/esm-resolver'; +import 'ace-builds/src-noconflict/ace'; +import 'ace-builds/src-noconflict/ext-language_tools'; +import 'ace-builds/src-noconflict/ext-searchbox'; -import "js-slang/dist/editors/ace/theme/source"; +import 'js-slang/dist/editors/ace/theme/source'; -import React from "react"; -import AceEditor, { type IAceEditorProps } from "react-ace"; -import { HotKeys } from "react-hotkeys"; +import React from 'react'; +import AceEditor, { type IAceEditorProps } from 'react-ace'; +import { HotKeys } from 'react-hotkeys'; -import type { KeyFunction } from "./EditorHotkeys"; +import type { KeyFunction } from './EditorHotkeys'; -import { getModeString, selectMode } from "../utils/AceHelper"; +import { getModeString, selectMode } from '../utils/AceHelper'; export type EditorKeyBindingHandlers = { [key in KeyFunction]?: () => void }; @@ -30,159 +30,157 @@ type DispatchProps = { export type EditorStateProps = { newCursorPosition?: Position; - editorValue: string - handleEditorValueChange: (newCode: string) => void + editorValue: string; + handleEditorValueChange: (newCode: string) => void; }; export type EditorProps = DispatchProps & EditorStateProps; -const makeCompleter = (handlePromptAutocomplete: DispatchProps["handlePromptAutocomplete"]) => ({ - getCompletions( - _editor: Ace.Editor, - _session: Ace.EditSession, - pos: Ace.Point, - prefix: string, - callback: () => void - ) { - // Don't prompt if prefix starts with number - if (prefix && /\d/u.test(prefix.charAt(0))) { - callback(); - return; - } - - // Cursor col is insertion location i.e. last char col + 1 - handlePromptAutocomplete(pos.row + 1, pos.column, callback); - } +const makeCompleter = (handlePromptAutocomplete: DispatchProps['handlePromptAutocomplete']) => ({ + getCompletions( + _editor: Ace.Editor, + _session: Ace.EditSession, + pos: Ace.Point, + prefix: string, + callback: () => void + ) { + // Don't prompt if prefix starts with number + if (prefix && /\d/u.test(prefix.charAt(0))) { + callback(); + return; + } + + // Cursor col is insertion location i.e. last char col + 1 + handlePromptAutocomplete(pos.row + 1, pos.column, callback); + } }); -const moveCursor = (editor: AceEditor["editor"], position: Position) => { - editor.selection.clearSelection(); - editor.moveCursorToPosition(position); - editor.renderer.showCursor(); - editor.renderer.scrollCursorIntoView(position, 0.5); +const moveCursor = (editor: AceEditor['editor'], position: Position) => { + editor.selection.clearSelection(); + editor.moveCursorToPosition(position); + editor.renderer.showCursor(); + editor.renderer.scrollCursorIntoView(position, 0.5); }; /* Override handler, so does not trigger when focus is in editor */ const handlers = { - goGreen() {} + goGreen() {} }; const Editor: React.FC = (props: EditorProps) => { - const reactAceRef: React.MutableRefObject = React.useRef(null); - - // Refs for things that technically shouldn't change... but just in case. - const handlePromptAutocompleteRef = React.useRef(props.handlePromptAutocomplete); - - const editor = reactAceRef.current?.editor; - - // this function defines the Ace language and highlighting mode for the - // given combination of chapter, variant and external library. it CANNOT be - // put in useEffect as it MUST be called before the mode is set on the Ace - // editor, and use(Layout)Effect runs after that happens. - // - // this used to be in useMemo, but selectMode now checks if the mode is - // already defined and doesn't do it, so it is now OK to keep calling this - // unconditionally. - selectMode(); - - React.useLayoutEffect(() => { - if (editor === undefined) { - return; - } - // NOTE: Everything in this function is designed to run exactly ONCE per instance of react-ace. - // The () => ref.current() are designed to use the latest instance only. - - // Start autocompletion - acequire("ace/ext/language_tools") - .setCompleters([ - makeCompleter((...args) => handlePromptAutocompleteRef.current(...args)) - ]); - }, [editor]); - - React.useLayoutEffect(() => { - if (editor === undefined) { - return; - } - const newCursorPosition = props.newCursorPosition; - if (newCursorPosition) { - moveCursor(editor, newCursorPosition); - } - }, [editor, props.newCursorPosition]); - - const aceEditorProps: IAceEditorProps = { - className: "react-ace", - editorProps: { - $blockScrolling: Infinity - }, - fontSize: 17, - height: "100%", - highlightActiveLine: false, - mode: getModeString(), - theme: "source", - value: props.editorValue, - width: "100%", - setOptions: { - enableBasicAutocompletion: true, - enableLiveAutocompletion: true, - fontFamily: "'Inconsolata', 'Consolas', monospace" - }, - // keyboardHandler: props.editorBinding, - onChange(newCode) { - if (reactAceRef.current) props.handleEditorValueChange(newCode); - }, - commands: [ - { - name: "evaluate", - bindKey: { - win: "Shift-Enter", - mac: "Shift-Enter" - }, - exec: props.handleEditorEval - } - // { - // name: 'navigate', - // bindKey: { - // win: 'Ctrl-B', - // mac: 'Command-B' - // } - // }, - // { - // name: 'refactor', - // bindKey: { - // win: 'Ctrl-M', - // mac: 'Command-M' - // } - // }, - // { - // name: 'highlightScope', - // bindKey: { - // win: 'Ctrl-Shift-H', - // mac: 'Command-Shift-H' - // }, - // }, - // { - // name: ' typeInferenceDisplay', - // bindKey: { - // win: 'Ctrl-Shift-M', - // mac: 'Command-Shift-M' - // } - // } - ] - // commands: Object.entries(keyHandlers) - // .filter(([_, exec]) => exec) - // .map(([name, exec]) => ({ name, bindKey: keyBindings[name], exec: exec! })) - }; - - - return ( -
- -
- -
-
-
- ); + const reactAceRef: React.MutableRefObject = React.useRef(null); + + // Refs for things that technically shouldn't change... but just in case. + const handlePromptAutocompleteRef = React.useRef(props.handlePromptAutocomplete); + + const editor = reactAceRef.current?.editor; + + // this function defines the Ace language and highlighting mode for the + // given combination of chapter, variant and external library. it CANNOT be + // put in useEffect as it MUST be called before the mode is set on the Ace + // editor, and use(Layout)Effect runs after that happens. + // + // this used to be in useMemo, but selectMode now checks if the mode is + // already defined and doesn't do it, so it is now OK to keep calling this + // unconditionally. + selectMode(); + + React.useLayoutEffect(() => { + if (editor === undefined) { + return; + } + // NOTE: Everything in this function is designed to run exactly ONCE per instance of react-ace. + // The () => ref.current() are designed to use the latest instance only. + + // Start autocompletion + acequire('ace/ext/language_tools').setCompleters([ + makeCompleter((...args) => handlePromptAutocompleteRef.current(...args)) + ]); + }, [editor]); + + React.useLayoutEffect(() => { + if (editor === undefined) { + return; + } + const newCursorPosition = props.newCursorPosition; + if (newCursorPosition) { + moveCursor(editor, newCursorPosition); + } + }, [editor, props.newCursorPosition]); + + const aceEditorProps: IAceEditorProps = { + className: 'react-ace', + editorProps: { + $blockScrolling: Infinity + }, + fontSize: 17, + height: '100%', + highlightActiveLine: false, + mode: getModeString(), + theme: 'source', + value: props.editorValue, + width: '100%', + setOptions: { + enableBasicAutocompletion: true, + enableLiveAutocompletion: true, + fontFamily: "'Inconsolata', 'Consolas', monospace" + }, + // keyboardHandler: props.editorBinding, + onChange(newCode) { + if (reactAceRef.current) props.handleEditorValueChange(newCode); + }, + commands: [ + { + name: 'evaluate', + bindKey: { + win: 'Shift-Enter', + mac: 'Shift-Enter' + }, + exec: props.handleEditorEval + } + // { + // name: 'navigate', + // bindKey: { + // win: 'Ctrl-B', + // mac: 'Command-B' + // } + // }, + // { + // name: 'refactor', + // bindKey: { + // win: 'Ctrl-M', + // mac: 'Command-M' + // } + // }, + // { + // name: 'highlightScope', + // bindKey: { + // win: 'Ctrl-Shift-H', + // mac: 'Command-Shift-H' + // }, + // }, + // { + // name: ' typeInferenceDisplay', + // bindKey: { + // win: 'Ctrl-Shift-M', + // mac: 'Command-Shift-M' + // } + // } + ] + // commands: Object.entries(keyHandlers) + // .filter(([_, exec]) => exec) + // .map(([name, exec]) => ({ name, bindKey: keyBindings[name], exec: exec! })) + }; + + return ( +
+ +
+ +
+
+
+ ); }; export default Editor; diff --git a/devserver/src/components/editor/EditorHotkeys.ts b/devserver/src/components/editor/EditorHotkeys.ts index 9002f99fee..b649344422 100644 --- a/devserver/src/components/editor/EditorHotkeys.ts +++ b/devserver/src/components/editor/EditorHotkeys.ts @@ -1,24 +1,24 @@ export const keyBindings = { - evaluate: { - win: "Shift-Enter", - mac: "Shift-Enter" - }, - navigate: { - win: "Ctrl-B", - mac: "Command-B" - }, - refactor: { - win: "Ctrl-M", - mac: "Command-M" - }, - highlightScope: { - win: "Ctrl-Shift-H", - mac: "Command-Shift-H" - }, - typeInferenceDisplay: { - win: "Ctrl-Shift-M", - mac: "Command-Shift-M" - } + evaluate: { + win: 'Shift-Enter', + mac: 'Shift-Enter' + }, + navigate: { + win: 'Ctrl-B', + mac: 'Command-B' + }, + refactor: { + win: 'Ctrl-M', + mac: 'Command-M' + }, + highlightScope: { + win: 'Ctrl-Shift-H', + mac: 'Command-Shift-H' + }, + typeInferenceDisplay: { + win: 'Ctrl-Shift-M', + mac: 'Command-Shift-M' + } }; export type KeyFunction = keyof typeof keyBindings; diff --git a/devserver/src/components/repl/Repl.tsx b/devserver/src/components/repl/Repl.tsx index c98a67cf5a..4446b40ab4 100644 --- a/devserver/src/components/repl/Repl.tsx +++ b/devserver/src/components/repl/Repl.tsx @@ -1,61 +1,61 @@ -import { Card, Pre } from "@blueprintjs/core"; -import { parseError } from "js-slang"; -import React from "react"; +import { Card, Pre } from '@blueprintjs/core'; +import { parseError } from 'js-slang'; +import React from 'react'; -import type { InterpreterOutput } from "../../types"; +import type { InterpreterOutput } from '../../types'; type OutputProps = { output: InterpreterOutput; }; const Output: React.FC = (props: OutputProps) => { - switch (props.output.type) { - case "code": - return ( - -
{props.output.value}
-
- ); - case "running": - return ( - -
{props.output.consoleLogs.join("\n")}
-
- ); - case "result": - if (props.output.consoleLogs.length === 0) { - return ( - -
{props.output.value}
-
- ); - } - return ( - -
{props.output.consoleLogs.join("\n")}
-
{props.output.value}
-
- ); + switch (props.output.type) { + case 'code': + return ( + +
{props.output.value}
+
+ ); + case 'running': + return ( + +
{props.output.consoleLogs.join('\n')}
+
+ ); + case 'result': + if (props.output.consoleLogs.length === 0) { + return ( + +
{props.output.value}
+
+ ); + } + return ( + +
{props.output.consoleLogs.join('\n')}
+
{props.output.value}
+
+ ); - case "errors": - if (props.output.consoleLogs.length === 0) { - return ( - -
{parseError(props.output.errors)}
-
- ); - } - return ( - -
{props.output.consoleLogs.join("\n")}
-
-
{parseError(props.output.errors)}
-
- ); + case 'errors': + if (props.output.consoleLogs.length === 0) { + return ( + +
{parseError(props.output.errors)}
+
+ ); + } + return ( + +
{props.output.consoleLogs.join('\n')}
+
+
{parseError(props.output.errors)}
+
+ ); - default: - return ''; - } + default: + return ''; + } }; export type ReplProps = { @@ -67,16 +67,12 @@ export type ReplProps = { }; const Repl: React.FC = (props: ReplProps) => ( -
-
- {props.output === null - ? - : } - {/* {cards.length > 0 ? cards : ()} */} -
-
+
+
+ {props.output === null ? : } + {/* {cards.length > 0 ? cards : ()} */} +
+
); export default Repl; diff --git a/devserver/src/components/sideContent/SideContent.tsx b/devserver/src/components/sideContent/SideContent.tsx index d54e192c7c..3b0b3060fd 100644 --- a/devserver/src/components/sideContent/SideContent.tsx +++ b/devserver/src/components/sideContent/SideContent.tsx @@ -1,7 +1,7 @@ -import { Card, Icon, Tab, type TabProps, Tabs } from "@blueprintjs/core"; -import { Tooltip2 } from "@blueprintjs/popover2"; -import React from "react"; -import type { SideContentTab } from "./types"; +import { Card, Icon, Tab, Tabs, type TabProps } from '@blueprintjs/core'; +import { Tooltip2 } from '@blueprintjs/popover2'; +import React from 'react'; +import type { SideContentTab } from './types'; /** * @property onChange A function that is called whenever the @@ -22,79 +22,85 @@ export type SideContentProps = { renderActiveTabPanelOnly?: boolean; editorWidth?: string; sideContentHeight?: number; - dynamicTabs: SideContentTab[] + dynamicTabs: SideContentTab[]; - selectedTabId: string - alerts: string[] - onChange?: (newId: string, oldId: string) => void + selectedTabId: string; + alerts: string[]; + onChange?: (newId: string, oldId: string) => void; }; const renderTab = ( - tab: SideContentTab, - shouldAlert: boolean, - _editorWidth?: string, - _sideContentHeight?: number + tab: SideContentTab, + shouldAlert: boolean, + _editorWidth?: string, + _sideContentHeight?: number ) => { - const iconSize = 20; - const tabTitle = ( - -
- -
-
- ); - const tabProps: TabProps = { - id: tab.id, - title: tabTitle, - // disabled: tab.disabled, - className: "side-content-tab" - }; + const iconSize = 20; + const tabTitle = ( + +
+ +
+
+ ); + const tabProps: TabProps = { + id: tab.id, + title: tabTitle, + // disabled: tab.disabled, + className: 'side-content-tab' + }; - if (!tab.body) { - return ; - } + if (!tab.body) { + return ; + } - // const tabBody: JSX.Element = workspaceLocation - // ? { - // ...tab.body, - // props: { - // ...tab.body.props, - // workspaceLocation, - // editorWidth, - // sideContentHeight - // } - // } - // : tab.body; - const tabPanel: React.JSX.Element =
{tab.body}
; + // const tabBody: JSX.Element = workspaceLocation + // ? { + // ...tab.body, + // props: { + // ...tab.body.props, + // workspaceLocation, + // editorWidth, + // sideContentHeight + // } + // } + // : tab.body; + const tabPanel: React.JSX.Element =
{tab.body}
; - return ; + return ; }; const SideContent: React.FC = ({ - renderActiveTabPanelOnly, - editorWidth, - sideContentHeight, - dynamicTabs, - selectedTabId, - onChange, - alerts + renderActiveTabPanelOnly, + editorWidth, + sideContentHeight, + dynamicTabs, + selectedTabId, + onChange, + alerts }) => ( -
- -
- { - if (onChange) onChange(newId, oldId); - }} - > - {dynamicTabs.map((tab) => renderTab(tab, alerts.includes(tab.id), editorWidth, sideContentHeight))} - -
-
-
+
+ +
+ { + if (onChange) onChange(newId, oldId); + }} + > + {dynamicTabs.map((tab) => + renderTab(tab, alerts.includes(tab.id), editorWidth, sideContentHeight) + )} + +
+
+
); export default SideContent; diff --git a/devserver/src/components/sideContent/TestTab.tsx b/devserver/src/components/sideContent/TestTab.tsx index 645af3ee46..7f7d4e2d97 100644 --- a/devserver/src/components/sideContent/TestTab.tsx +++ b/devserver/src/components/sideContent/TestTab.tsx @@ -1,26 +1,32 @@ -import { IconNames } from "@blueprintjs/icons"; -import type { SideContentTab } from "./types"; +import { IconNames } from '@blueprintjs/icons'; +import type { SideContentTab } from './types'; -const TestTab = () =>
-

Source Academy Tab Development Server

-

- Run some code that imports modules in the editor on the left. You should see the corresponding module tab spawn.
- Whenever you make changes to the tab, the server should automatically reload and show the changes that you've made
- If that does not happen, you can click the refresh button to manually reload tabs -

-
; +const TestTab = () => ( +
+

Source Academy Tab Development Server

+

+ Run some code that imports modules in the editor on the left. You should see the corresponding + module tab spawn. +
+ Whenever you make changes to the tab, the server should automatically reload and show the + changes that you've made
+ If that does not happen, you can click the refresh button to manually reload tabs +

+
+); const testTabContent: SideContentTab = { - id: "test", - label: "Welcome to the tab development server!", - iconName: IconNames.LabTest, - body: + id: 'test', + label: 'Welcome to the tab development server!', + iconName: IconNames.LabTest, + body: }; export default testTabContent; diff --git a/devserver/src/components/sideContent/types.ts b/devserver/src/components/sideContent/types.ts index 1fc40624f9..b4891a7ce6 100644 --- a/devserver/src/components/sideContent/types.ts +++ b/devserver/src/components/sideContent/types.ts @@ -1,21 +1,21 @@ -import type { IconName } from "@blueprintjs/icons"; -import type { Context } from "js-slang"; -import type { JSX } from "react"; +import type { IconName } from '@blueprintjs/icons'; +import type { Context } from 'js-slang'; +import type { JSX } from 'react'; export type DebuggerContext = { - context: Context -} + context: Context; +}; export type SideContentTab = { - id: string - label: string - iconName: IconName - body: JSX.Element -} + id: string; + label: string; + iconName: IconName; + body: JSX.Element; +}; export type ModuleSideContent = { label: string; - iconName: IconName - toSpawn?: (context: DebuggerContext) => boolean - body: (context: DebuggerContext) => JSX.Element -} + iconName: IconName; + toSpawn?: (context: DebuggerContext) => boolean; + body: (context: DebuggerContext) => JSX.Element; +}; diff --git a/devserver/src/components/sideContent/utils.ts b/devserver/src/components/sideContent/utils.ts index 39827d27fe..22085cd205 100644 --- a/devserver/src/components/sideContent/utils.ts +++ b/devserver/src/components/sideContent/utils.ts @@ -1,21 +1,27 @@ -import type { Context } from "js-slang"; -import manifest from "../../../../modules.json"; -import type { ModuleSideContent, SideContentTab } from "./types"; +import type { Context } from 'js-slang'; +import manifest from '../../../../modules.json'; +import type { ModuleSideContent, SideContentTab } from './types'; const moduleManifest = manifest as Record; export const getDynamicTabs = async (context: Context) => { - const moduleSideContents = await Promise.all(Object.keys(context.moduleContexts) - .flatMap((moduleName) => moduleManifest[moduleName].tabs.map(async (tabName) => { - const { default: rawTab } = await import(`../../../../src/tabs/${tabName}/index.tsx`); - return rawTab as ModuleSideContent; - }))); + const moduleSideContents = await Promise.all( + Object.keys(context.moduleContexts).flatMap((moduleName) => + moduleManifest[moduleName].tabs.map(async (tabName) => { + const { default: rawTab } = await import(`../../../../src/tabs/${tabName}/index.tsx`); + return rawTab as ModuleSideContent; + }) + ) + ); - return moduleSideContents.filter(({ toSpawn }) => !toSpawn || toSpawn({ context })) - .map((tab): SideContentTab => ({ - ...tab, - // In the frontend, module tabs use their labels as IDs - id: tab.label, - body: tab.body({ context }) - })); + return moduleSideContents + .filter(({ toSpawn }) => !toSpawn || toSpawn({ context })) + .map( + (tab): SideContentTab => ({ + ...tab, + // In the frontend, module tabs use their labels as IDs + id: tab.label, + body: tab.body({ context }) + }) + ); }; diff --git a/devserver/src/components/utils/AceHelper.ts b/devserver/src/components/utils/AceHelper.ts index 9ab269131d..6a4da2eb50 100644 --- a/devserver/src/components/utils/AceHelper.ts +++ b/devserver/src/components/utils/AceHelper.ts @@ -1,9 +1,9 @@ /* eslint-disable new-cap */ -import { HighlightRulesSelector, ModeSelector } from "js-slang/dist/editors/ace/modes/source"; -import { Chapter, Variant } from "js-slang/dist/types"; -import ace from "react-ace"; +import { HighlightRulesSelector, ModeSelector } from 'js-slang/dist/editors/ace/modes/source'; +import { Chapter, Variant } from 'js-slang/dist/types'; +import ace from 'react-ace'; -export const getModeString = () => `source${Chapter.SOURCE_4}${Variant.DEFAULT}${""}`; +export const getModeString = () => `source${Chapter.SOURCE_4}${Variant.DEFAULT}${''}`; /** * This _modifies global state_ and defines a new Ace mode globally, if it does not already exist. @@ -11,19 +11,19 @@ export const getModeString = () => `source${Chapter.SOURCE_4}${Variant.DEFAULT}$ * You can call this directly in render functions. */ export const selectMode = () => { - const chapter = Chapter.SOURCE_4; - const variant = Variant.DEFAULT; - const library = ""; + const chapter = Chapter.SOURCE_4; + const variant = Variant.DEFAULT; + const library = ''; - if ( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - typeof ace.define.modules[`ace/mode/${getModeString(chapter, variant, library)}`]?.Mode - === "function" - ) { - return; - } + if ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + typeof ace.define.modules[`ace/mode/${getModeString(chapter, variant, library)}`]?.Mode === + 'function' + ) { + return; + } - HighlightRulesSelector(chapter, variant, library); - ModeSelector(chapter, variant, library); + HighlightRulesSelector(chapter, variant, library); + ModeSelector(chapter, variant, library); }; diff --git a/devserver/src/components/utils/Hooks.ts b/devserver/src/components/utils/Hooks.ts index 57fd8723a1..a2bac84c71 100644 --- a/devserver/src/components/utils/Hooks.ts +++ b/devserver/src/components/utils/Hooks.ts @@ -5,36 +5,37 @@ * @param ref A reference to the underlying HTML element. */ -import React, { type RefObject } from "react"; +import React, { type RefObject } from 'react'; export const useDimensions = (ref: RefObject): [width: number, height: number] => { - const [width, setWidth] = React.useState(0); - const [height, setHeight] = React.useState(0); + const [width, setWidth] = React.useState(0); + const [height, setHeight] = React.useState(0); - const resizeObserver = React.useMemo( - () => new ResizeObserver((entries: ResizeObserverEntry[], _observer: ResizeObserver) => { - if (entries.length !== 1) { - throw new Error( - "Expected only a single HTML element to be observed by the ResizeObserver." - ); - } - const contentRect = entries[0].contentRect; - setWidth(contentRect.width); - setHeight(contentRect.height); - }), - [] - ); + const resizeObserver = React.useMemo( + () => + new ResizeObserver((entries: ResizeObserverEntry[], _observer: ResizeObserver) => { + if (entries.length !== 1) { + throw new Error( + 'Expected only a single HTML element to be observed by the ResizeObserver.' + ); + } + const contentRect = entries[0].contentRect; + setWidth(contentRect.width); + setHeight(contentRect.height); + }), + [] + ); - React.useEffect(() => { - const htmlElement = ref.current; - if (htmlElement === null) { - return undefined; - } - resizeObserver.observe(htmlElement); - return () => { - resizeObserver.disconnect(); - }; - }, [ref, resizeObserver]); + React.useEffect(() => { + const htmlElement = ref.current; + if (htmlElement === null) { + return undefined; + } + resizeObserver.observe(htmlElement); + return () => { + resizeObserver.disconnect(); + }; + }, [ref, resizeObserver]); - return [width, height]; + return [width, height]; }; diff --git a/devserver/src/main.tsx b/devserver/src/main.tsx index ae3e1457ec..26c718f895 100644 --- a/devserver/src/main.tsx +++ b/devserver/src/main.tsx @@ -1,13 +1,16 @@ -import React from "react"; -import ReactDOM from "react-dom"; +import React from 'react'; +import ReactDOM from 'react-dom'; -import "./styles/index.scss"; -import Playground from "./components/Playground"; +import Playground from './components/Playground'; +import './styles/index.scss'; -ReactDOM.render( -
-
- -
-
-
, document.getElementById("root")!); +ReactDOM.render( + +
+
+ +
+
+
, + document.getElementById('root')! +); diff --git a/devserver/src/mockModuleContext.ts b/devserver/src/mockModuleContext.ts index 82505be11f..71996fd265 100644 --- a/devserver/src/mockModuleContext.ts +++ b/devserver/src/mockModuleContext.ts @@ -3,5 +3,5 @@ */ export default { - moduleContexts: {} + moduleContexts: {} }; diff --git a/devserver/src/types.ts b/devserver/src/types.ts index d806c9c967..bee22bdec2 100644 --- a/devserver/src/types.ts +++ b/devserver/src/types.ts @@ -1,4 +1,4 @@ -import type { SourceError } from "js-slang/dist/types"; +import type { SourceError } from 'js-slang/dist/types'; /** * An output while the program is still being run in the interpreter. As a @@ -6,7 +6,7 @@ import type { SourceError } from "js-slang/dist/types"; * have been calls to display (console.log) that need to be printed out. */ export type RunningOutput = { - type: "running"; + type: 'running'; consoleLogs: string[]; }; @@ -16,7 +16,7 @@ export type RunningOutput = { * been entered. */ export type CodeOutput = { - type: "code"; + type: 'code'; value: string; }; @@ -26,7 +26,7 @@ export type CodeOutput = { * but not both. */ export type ResultOutput = { - type: "result"; + type: 'result'; value: any; consoleLogs: string[]; runtime?: number; @@ -39,7 +39,7 @@ export type ResultOutput = { * not both. */ export type ErrorOutput = { - type: "errors"; + type: 'errors'; errors: SourceError[]; consoleLogs: string[]; }; diff --git a/package.json b/package.json index 289ca538ef..22e6ce07a2 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ }, "devDependencies": { "@types/dom-mediacapture-record": "^1.0.11", - "@types/eslint": "^8.4.10", + "@types/eslint": "^8.44.2", "@types/estree": "^1.0.0", "@types/jest": "^27.4.1", "@types/lodash": "^4.14.198", @@ -71,9 +71,7 @@ "console-table-printer": "^2.11.1", "cross-env": "^7.0.3", "esbuild": "^0.18.20", - "eslint": "^8.21.0", - "eslint-config-airbnb": "^19.0.4", - "eslint-config-airbnb-typescript": "^17.0.0", + "eslint": "^8.49.0", "eslint-import-resolver-typescript": "^2.7.1", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jest": "^26.8.1", diff --git a/scripts/src/.eslintrc.cjs b/scripts/src/.eslintrc.cjs index 41c06f2e02..eff8d0999f 100644 --- a/scripts/src/.eslintrc.cjs +++ b/scripts/src/.eslintrc.cjs @@ -3,53 +3,62 @@ module.exports = { // Need react here because otherwise we get undefined rule errors - "plugins": ["import", "react", "simple-import-sort", "@typescript-eslint"], - "extends": ["../../.eslintrc.base.cjs", "airbnb-typescript"], - "ignorePatterns": ["templates/templates/**", '**/__tests__/**', '**/__mocks__/**', "**/jest*", '**/*.*js'], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2022, - "project": "./tsconfig.json", - "tsconfigRootDir": __dirname, + plugins: ['import', 'react', 'simple-import-sort', '@typescript-eslint'], + extends: ['../../.eslintrc.base.cjs'], + ignorePatterns: [ + 'templates/templates/**', + '**/__tests__/**', + '**/__mocks__/**', + '**/jest*', + '**/*.*js' + ], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 2022, + project: './tsconfig.json', + tsconfigRootDir: __dirname }, - "rules": { - "array-callback-return": [2, { "checkForEach": false }], - "func-style": 0, - "import/no-extraneous-dependencies": 0, - "import/extensions": [2, "ignorePackages"], - "no-console": 0, - "no-continue": 0, - "no-param-reassign": 0, - "no-restricted-syntax": 0, - "prefer-const": 0, - "simple-import-sort/imports": [ + rules: { + 'array-callback-return': [2, { checkForEach: false }], + 'func-style': 0, + 'import/no-extraneous-dependencies': 0, + 'import/extensions': [2, 'ignorePackages'], + 'no-console': 0, + 'no-continue': 0, + 'no-param-reassign': 0, + 'no-restricted-syntax': 0, + 'prefer-const': 0, + 'simple-import-sort/imports': [ 1, { - "groups": [ + groups: [ // Packages `react` related packages come first. - ["^react", "^@?\\w"], + ['^react', '^@?\\w'], // Internal packages. - ["^(@|components)(/.*|$)"], + ['^(@|components)(/.*|$)'], // Side effect imports. - ["^\\u0000"], + ['^\\u0000'], // Parent imports. Put `..` last. - ["^\\.\\.(?!/?$)", "^\\.\\./?$"], + ['^\\.\\.(?!/?$)', '^\\.\\./?$'], // Other relative imports. Put same-folder imports and `.` last. - ["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"], + ['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'], // Style imports. - ["^.+\\.?(css)$"] + ['^.+\\.?(css)$'] ] } ] }, - "overrides": [{ - "extends": ["../../.eslintrc.test.cjs", "airbnb-typescript"], - "files": ["**/__tests__/**/*", "**/__mocks__/**/*", "**/jest*"], - env: { - jest: true, + overrides: [ + { + extends: ['../../.eslintrc.test.cjs'], + files: ['**/__tests__/**/*', '**/__mocks__/**/*', '**/jest*'], + env: { + jest: true + } + }, + { + extends: ['../../.eslintrc.base.cjs'], + files: ['**/*.*js'] } - }, { - extends: ["../../.eslintrc.base.cjs"], - files: ["**/*.*js"], - }], -} \ No newline at end of file + ] +}; diff --git a/src/.eslintrc.cjs b/src/.eslintrc.cjs index d643dd5da9..2ac3b95699 100644 --- a/src/.eslintrc.cjs +++ b/src/.eslintrc.cjs @@ -1,133 +1,78 @@ -module.exports = { - "extends": ["../.eslintrc.base.cjs", "airbnb-typescript"], - "ignorePatterns": ["**/__tests__/**", "**/__mocks__/**", "**/*.*js"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "./tsconfig.json", - "tsconfigRootDir": __dirname, +// @ts-check + +/** @type {import('eslint').Linter.Config} */ +const eslintConfig = { + extends: ['../.eslintrc.base.cjs'], + ignorePatterns: ['**/__tests__/**', '**/__mocks__/**', '**/*.*js'], + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: __dirname }, - "plugins": ["import", "react", "jsx-a11y", "@typescript-eslint"], - "rules": { - "func-style": 0, - "indent": [ - 1, - 2, // Was "tabs" - { - "SwitchCase": 1 // Same - // VariableDeclarator: 1, - // outerIIFEBody: 1, - // MemberExpression: 1, - // FunctionDeclaration: { - // parameters: 1, - // body: 1 - // }, - // FunctionExpression: { - // parameters: 1, - // body: 1 - // }, - // StaticBlock: { - // body: 1 - // }, - // CallExpression: { - // arguments: 1, - // }, - // ArrayExpression: 1, - // ObjectExpression: 1, - // ImportDeclaration: 1, - // flatTernaryExpressions: false, - // offsetTernaryExpressions: false, - // ignoreComments: false - } - ], - "quotes": [ - 1, - "single", // Was "double" - { - "avoidEscape": true // Same - // allowTemplateLiterals: false - } - ], + plugins: ['import', 'react', 'jsx-a11y', '@typescript-eslint'], + /** @type {Partial} */ + rules: { + 'func-style': 'off', // [typescript-eslint Extension Rules] - /* NOTE - .eslintrc.base.js has been configured for every rule off the - eslint:recommended config as of V8. - A similar complete config but for all typescript-eslint rules hasn't - been made, instead simply using airbnb-typescript's layers of - extended configs & plugins. - - This section is for reconfiguring the typescript-eslint extension - rules configured by airbnb-typescript that have replaced their eslint - equivalents, to make them match the behaviour in .eslintrc.base.js - */ - "@typescript-eslint/no-unused-vars": [ - 1, // Was 2 - { - // vars: "all", - // args: "after-used", - // ignoreRestSiblings: false, - "argsIgnorePattern": "^_", - "caughtErrors": "all", // Was "none" - "caughtErrorsIgnorePattern": "^_" - } - ], - "@typescript-eslint/no-use-before-define": [ - 1, // Was 2 + '@typescript-eslint/ban-types': [ + 'error', { - "functions": false - // classes: true, - // variables: true, - // enums: true, // TS - // typedefs: true, // TS - // ignoreTypeReferences: true, // TS + extendDefaults: true, + types: { + // TODO: Fix later + Function: false, + '{}': false + } } ], - "@typescript-eslint/default-param-last": 1, // Was 2 - "@typescript-eslint/no-shadow": [ - 1, // Was 2 + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-use-before-define': ['warn', { functions: false }], + '@typescript-eslint/no-namespace': 'off', + 'no-unused-vars': 'off', // disable base rule, as it can report incorrect errors + '@typescript-eslint/no-unused-vars': [ + 'error', { - "builtinGlobals": true - // hoist: "functions", - // ignoreTypeValueShadow: true, // TS - // ignoreFunctionTypeParameterNameValueShadow: true, // TS + vars: 'all', + args: 'none', + ignoreRestSiblings: false, + varsIgnorePattern: '^_', + argsIgnorePattern: '^_' } ], - "@typescript-eslint/lines-between-class-members": 0, // Was 2 - // "@typescript-eslint/consistent-type-imports": 1, - - // [Error → Warn] - /* NOTE - This section is for reducing the severity of rules configured by - airbnb-typescript from 2 to 1, if the problems they point out do not - have the possibility of directly leading to errors - */ + '@typescript-eslint/consistent-type-imports': 'error', // [Other] - "@typescript-eslint/naming-convention": [ - 1, + '@typescript-eslint/naming-convention': [ + 'warn', { - "selector": "variable", + selector: 'variable', // Was ["camelCase", "PascalCase", "UPPER_CASE"]. // Add snake case to let exported module variables match Source - "format": ["camelCase", "PascalCase", "UPPER_CASE", "snake_case"] + format: ['camelCase', 'PascalCase', 'UPPER_CASE', 'snake_case'] }, { - "selector": "function", + selector: 'function', // Was ["camelCase", "PascalCase"]. // Add snake case to let exported module functions match Source - "format": ["camelCase", "PascalCase", "snake_case"] + format: ['camelCase', 'PascalCase', 'snake_case'] }, { - "selector": "typeLike", - "format": ["PascalCase"] + selector: 'typeLike', + format: ['PascalCase'] } ] }, - "overrides": [{ - "extends": ["../.eslintrc.test.cjs"], - "files": ["**/__tests__/**", "**/__mocks__/**"], - }, { - extends: ["../.eslintrc.base.cjs"], - files: ["**/*.*.js"] - }] -} + overrides: [ + { + extends: ['../.eslintrc.test.cjs'], + files: ['**/__tests__/**', '**/__mocks__/**'] + }, + { + extends: ['../.eslintrc.base.cjs'], + files: ['**/*.*.js'] + } + ] +}; + +module.exports = eslintConfig; diff --git a/src/bundles/arcade_2d/audio.ts b/src/bundles/arcade_2d/audio.ts index 43f792f7c7..893d660406 100644 --- a/src/bundles/arcade_2d/audio.ts +++ b/src/bundles/arcade_2d/audio.ts @@ -8,20 +8,22 @@ */ export class AudioClip { private static audioClipCount: number = 0; + // Stores AudioClip index with the URL as a unique key. private static audioClipsIndexMap: Map = new Map(); + // Stores all the created AudioClips private static audioClipsArray: Array = []; + public readonly id: number; private isUpdated: boolean = false; + private shouldPlay: boolean = false; + private shouldLoop: boolean = false; - private constructor( - private url: string, - private volumeLevel: number, - ) { + private constructor(private url: string, private volumeLevel: number) { this.id = AudioClip.audioClipCount++; AudioClip.audioClipsIndexMap.set(url, this.id); AudioClip.audioClipsArray.push(this); @@ -40,24 +42,30 @@ export class AudioClip { } return new AudioClip(url, volumeLevel); } + public getUrl() { return this.url; } + public getVolumeLevel() { return this.volumeLevel; } + public shouldAudioClipLoop() { return this.shouldLoop; } + public shouldAudioClipPlay() { return this.shouldPlay; } + public setShouldAudioClipLoop(loop: boolean) { if (this.shouldLoop !== loop) { this.shouldLoop = loop; this.isUpdated = false; } } + /** * Updates the play/pause state. * @param play When true, the Audio Clip has a playing state. @@ -66,6 +74,7 @@ export class AudioClip { this.shouldPlay = play; this.isUpdated = false; } + /** * Checks if the Audio Clip needs to update. Updates the flag if true. */ @@ -74,9 +83,11 @@ export class AudioClip { this.setAudioClipUpdated(); return prevValue; } + public setAudioClipUpdated() { this.isUpdated = true; } + public static getAudioClipsArray() { return AudioClip.audioClipsArray; } diff --git a/src/bundles/arcade_2d/constants.ts b/src/bundles/arcade_2d/constants.ts index 96b76b991d..4465e34c50 100644 --- a/src/bundles/arcade_2d/constants.ts +++ b/src/bundles/arcade_2d/constants.ts @@ -29,18 +29,19 @@ export const DEFAULT_DEBUG_STATE: boolean = false; export const DEFAULT_TRANSFORM_PROPS: TransformProps = { position: [0, 0], scale: [1, 1], - rotation: 0, + rotation: 0 }; export const DEFAULT_RENDER_PROPS: RenderProps = { color: [255, 255, 255, 255], flip: [false, false], - isVisible: true, + isVisible: true }; export const DEFAULT_INTERACTABLE_PROPS: InteractableProps = { - isHitboxActive: true, + isHitboxActive: true }; // Default values of Phaser scene -export const DEFAULT_PATH_PREFIX: string = 'https://source-academy-assets.s3-ap-southeast-1.amazonaws.com/'; +export const DEFAULT_PATH_PREFIX: string = + 'https://source-academy-assets.s3-ap-southeast-1.amazonaws.com/'; diff --git a/src/bundles/arcade_2d/functions.ts b/src/bundles/arcade_2d/functions.ts index e47236966c..4b7ce3412b 100644 --- a/src/bundles/arcade_2d/functions.ts +++ b/src/bundles/arcade_2d/functions.ts @@ -10,55 +10,53 @@ */ import Phaser from 'phaser'; +import { AudioClip } from './audio'; import { - PhaserScene, - gameState, -} from './phaserScene'; + DEFAULT_DEBUG_STATE, + DEFAULT_FPS, + DEFAULT_HEIGHT, + DEFAULT_INTERACTABLE_PROPS, + DEFAULT_RENDER_PROPS, + DEFAULT_SCALE, + DEFAULT_TRANSFORM_PROPS, + DEFAULT_WIDTH, + MAX_FPS, + MAX_HEIGHT, + MAX_SCALE, + MAX_VOLUME, + MAX_WIDTH, + MIN_FPS, + MIN_HEIGHT, + MIN_SCALE, + MIN_VOLUME, + MIN_WIDTH +} from './constants'; import { + CircleGameObject, GameObject, + InteractableGameObject, + RectangleGameObject, RenderableGameObject, - type ShapeGameObject, SpriteGameObject, TextGameObject, - RectangleGameObject, - CircleGameObject, - TriangleGameObject, InteractableGameObject, + TriangleGameObject, + type ShapeGameObject } from './gameobject'; +import { PhaserScene, gameState } from './phaserScene'; import { - type DisplayText, type BuildGame, - type Sprite, - type UpdateFunction, - type RectangleProps, type CircleProps, - type TriangleProps, + type ColorRGBA, + type DimensionsXY, + type DisplayText, type FlipXY, - type ScaleXY, type PositionXY, - type DimensionsXY, - type ColorRGBA, + type RectangleProps, + type ScaleXY, + type Sprite, + type TriangleProps, + type UpdateFunction } from './types'; -import { - DEFAULT_WIDTH, - DEFAULT_HEIGHT, - DEFAULT_SCALE, - DEFAULT_FPS, - MAX_HEIGHT, - MIN_HEIGHT, - MAX_WIDTH, - MIN_WIDTH, - MAX_SCALE, - MIN_SCALE, - MAX_FPS, - MIN_FPS, - MAX_VOLUME, - MIN_VOLUME, - DEFAULT_DEBUG_STATE, - DEFAULT_TRANSFORM_PROPS, - DEFAULT_RENDER_PROPS, - DEFAULT_INTERACTABLE_PROPS, -} from './constants'; -import { AudioClip } from './audio'; // ============================================================================= // Global Variables @@ -72,7 +70,7 @@ export const config = { fps: DEFAULT_FPS, isDebugEnabled: DEFAULT_DEBUG_STATE, // User update function - userUpdateFunction: (() => {}) as UpdateFunction, + userUpdateFunction: (() => {}) as UpdateFunction }; // ============================================================================= @@ -90,12 +88,20 @@ export const config = { * ``` * @category GameObject */ -export const create_rectangle: (width: number, height: number) => ShapeGameObject = (width: number, height: number) => { +export const create_rectangle: (width: number, height: number) => ShapeGameObject = ( + width: number, + height: number +) => { const rectangle = { width, - height, + height } as RectangleProps; - return new RectangleGameObject(DEFAULT_TRANSFORM_PROPS, DEFAULT_RENDER_PROPS, DEFAULT_INTERACTABLE_PROPS, rectangle); + return new RectangleGameObject( + DEFAULT_TRANSFORM_PROPS, + DEFAULT_RENDER_PROPS, + DEFAULT_INTERACTABLE_PROPS, + rectangle + ); }; /** @@ -110,9 +116,14 @@ export const create_rectangle: (width: number, height: number) => ShapeGameObjec */ export const create_circle: (radius: number) => ShapeGameObject = (radius: number) => { const circle = { - radius, + radius } as CircleProps; - return new CircleGameObject(DEFAULT_TRANSFORM_PROPS, DEFAULT_RENDER_PROPS, DEFAULT_INTERACTABLE_PROPS, circle); + return new CircleGameObject( + DEFAULT_TRANSFORM_PROPS, + DEFAULT_RENDER_PROPS, + DEFAULT_INTERACTABLE_PROPS, + circle + ); }; /** @@ -125,16 +136,24 @@ export const create_circle: (radius: number) => ShapeGameObject = (radius: numbe * ``` * @category GameObject */ -export const create_triangle: (width: number, height: number) => ShapeGameObject = (width: number, height: number) => { +export const create_triangle: (width: number, height: number) => ShapeGameObject = ( + width: number, + height: number +) => { const triangle = { x1: 0, y1: 0, x2: width, y2: 0, x3: width / 2, - y3: height, + y3: height } as TriangleProps; - return new TriangleGameObject(DEFAULT_TRANSFORM_PROPS, DEFAULT_RENDER_PROPS, DEFAULT_INTERACTABLE_PROPS, triangle); + return new TriangleGameObject( + DEFAULT_TRANSFORM_PROPS, + DEFAULT_RENDER_PROPS, + DEFAULT_INTERACTABLE_PROPS, + triangle + ); }; /** @@ -149,9 +168,14 @@ export const create_triangle: (width: number, height: number) => ShapeGameObject */ export const create_text: (text: string) => TextGameObject = (text: string) => { const displayText = { - text, + text } as DisplayText; - return new TextGameObject(DEFAULT_TRANSFORM_PROPS, DEFAULT_RENDER_PROPS, DEFAULT_INTERACTABLE_PROPS, displayText); + return new TextGameObject( + DEFAULT_TRANSFORM_PROPS, + DEFAULT_RENDER_PROPS, + DEFAULT_INTERACTABLE_PROPS, + displayText + ); }; /** @@ -179,9 +203,14 @@ export const create_sprite: (image_url: string) => SpriteGameObject = (image_url throw new Error('image_url must be a string'); } const sprite: Sprite = { - imageUrl: image_url, + imageUrl: image_url } as Sprite; - return new SpriteGameObject(DEFAULT_TRANSFORM_PROPS, DEFAULT_RENDER_PROPS, DEFAULT_INTERACTABLE_PROPS, sprite); + return new SpriteGameObject( + DEFAULT_TRANSFORM_PROPS, + DEFAULT_RENDER_PROPS, + DEFAULT_INTERACTABLE_PROPS, + sprite + ); }; // ============================================================================= @@ -200,12 +229,14 @@ export const create_sprite: (image_url: string) => SpriteGameObject = (image_url * ``` * @category GameObject */ -export const update_position: (gameObject: GameObject, [x, y]: PositionXY) => GameObject -= (gameObject: GameObject, [x, y]: PositionXY) => { +export const update_position: (gameObject: GameObject, [x, y]: PositionXY) => GameObject = ( + gameObject: GameObject, + [x, y]: PositionXY +) => { if (gameObject instanceof GameObject) { gameObject.setTransform({ ...gameObject.getTransform(), - position: [x, y], + position: [x, y] }); return gameObject; } @@ -224,12 +255,14 @@ export const update_position: (gameObject: GameObject, [x, y]: PositionXY) => Ga * ``` * @category GameObject */ -export const update_scale: (gameObject: GameObject, [x, y]: ScaleXY) => GameObject -= (gameObject: GameObject, [x, y]: ScaleXY) => { +export const update_scale: (gameObject: GameObject, [x, y]: ScaleXY) => GameObject = ( + gameObject: GameObject, + [x, y]: ScaleXY +) => { if (gameObject instanceof GameObject) { gameObject.setTransform({ ...gameObject.getTransform(), - scale: [x, y], + scale: [x, y] }); return gameObject; } @@ -248,12 +281,14 @@ export const update_scale: (gameObject: GameObject, [x, y]: ScaleXY) => GameObje * ``` * @category GameObject */ -export const update_rotation: (gameObject: GameObject, radians: number) => GameObject -= (gameObject: GameObject, radians: number) => { +export const update_rotation: (gameObject: GameObject, radians: number) => GameObject = ( + gameObject: GameObject, + radians: number +) => { if (gameObject instanceof GameObject) { gameObject.setTransform({ ...gameObject.getTransform(), - rotation: radians, + rotation: radians }); return gameObject; } @@ -273,15 +308,17 @@ export const update_rotation: (gameObject: GameObject, radians: number) => GameO * ``` * @category GameObject */ -export const update_color: (gameObject: GameObject, color: ColorRGBA) => GameObject -= (gameObject: GameObject, color: ColorRGBA) => { +export const update_color: (gameObject: GameObject, color: ColorRGBA) => GameObject = ( + gameObject: GameObject, + color: ColorRGBA +) => { if (color.length !== 4) { throw new Error('color must be a 4-element array'); } if (gameObject instanceof RenderableGameObject) { gameObject.setRenderState({ ...gameObject.getRenderState(), - color, + color }); return gameObject; } @@ -300,15 +337,17 @@ export const update_color: (gameObject: GameObject, color: ColorRGBA) => GameObj * ``` * @category GameObject */ -export const update_flip: (gameObject: GameObject, flip: FlipXY) => GameObject -= (gameObject: GameObject, flip: FlipXY) => { +export const update_flip: (gameObject: GameObject, flip: FlipXY) => GameObject = ( + gameObject: GameObject, + flip: FlipXY +) => { if (flip.length !== 2) { throw new Error('flip must be a 2-element array'); } if (gameObject instanceof RenderableGameObject) { gameObject.setRenderState({ ...gameObject.getRenderState(), - flip, + flip }); return gameObject; } @@ -328,11 +367,13 @@ export const update_flip: (gameObject: GameObject, flip: FlipXY) => GameObject * ``` * @category GameObject */ -export const update_text: (textGameObject: TextGameObject, text: string) => GameObject -= (textGameObject: TextGameObject, text: string) => { +export const update_text: (textGameObject: TextGameObject, text: string) => GameObject = ( + textGameObject: TextGameObject, + text: string +) => { if (textGameObject instanceof TextGameObject) { textGameObject.setText({ - text, + text } as DisplayText); return textGameObject; } @@ -349,8 +390,7 @@ export const update_text: (textGameObject: TextGameObject, text: string) => Game * ``` * @category GameObject */ -export const update_to_top: (gameObject: GameObject) => GameObject -= (gameObject: GameObject) => { +export const update_to_top: (gameObject: GameObject) => GameObject = (gameObject: GameObject) => { if (gameObject instanceof RenderableGameObject) { gameObject.setBringToTopFlag(); return gameObject; @@ -396,8 +436,7 @@ export const query_id: (gameObject: GameObject) => number = (gameObject: GameObj * ``` * @category GameObject */ -export const query_position: (gameObject: GameObject) => PositionXY -= (gameObject: GameObject) => { +export const query_position: (gameObject: GameObject) => PositionXY = (gameObject: GameObject) => { if (gameObject instanceof GameObject) { return [...gameObject.getTransform().position]; } @@ -416,8 +455,7 @@ export const query_position: (gameObject: GameObject) => PositionXY * ``` * @category GameObject */ -export const query_rotation: (gameObject: GameObject) => number -= (gameObject: GameObject) => { +export const query_rotation: (gameObject: GameObject) => number = (gameObject: GameObject) => { if (gameObject instanceof GameObject) { return gameObject.getTransform().rotation; } @@ -436,8 +474,7 @@ export const query_rotation: (gameObject: GameObject) => number * ``` * @category GameObject */ -export const query_scale: (gameObject: GameObject) => ScaleXY -= (gameObject: GameObject) => { +export const query_scale: (gameObject: GameObject) => ScaleXY = (gameObject: GameObject) => { if (gameObject instanceof GameObject) { return [...gameObject.getTransform().scale]; } @@ -456,8 +493,9 @@ export const query_scale: (gameObject: GameObject) => ScaleXY * ``` * @category GameObject */ -export const query_color: (gameObject: RenderableGameObject) => ColorRGBA -= (gameObject: RenderableGameObject) => { +export const query_color: (gameObject: RenderableGameObject) => ColorRGBA = ( + gameObject: RenderableGameObject +) => { if (gameObject instanceof RenderableGameObject) { return [...gameObject.getColor()]; } @@ -476,8 +514,9 @@ export const query_color: (gameObject: RenderableGameObject) => ColorRGBA * ``` * @category GameObject */ -export const query_flip: (gameObject: RenderableGameObject) => FlipXY -= (gameObject: RenderableGameObject) => { +export const query_flip: (gameObject: RenderableGameObject) => FlipXY = ( + gameObject: RenderableGameObject +) => { if (gameObject instanceof RenderableGameObject) { return [...gameObject.getFlipState()]; } @@ -497,8 +536,9 @@ export const query_flip: (gameObject: RenderableGameObject) => FlipXY * ``` * @category GameObject */ -export const query_text: (textGameObject: TextGameObject) => string -= (textGameObject: TextGameObject) => { +export const query_text: (textGameObject: TextGameObject) => string = ( + textGameObject: TextGameObject +) => { if (textGameObject instanceof TextGameObject) { return textGameObject.getText().text; } @@ -516,8 +556,8 @@ export const query_text: (textGameObject: TextGameObject) => string * position[1]; // y * ``` */ -export const query_pointer_position: () => PositionXY -= () => gameState.pointerProps.pointerPosition; +export const query_pointer_position: () => PositionXY = () => + gameState.pointerProps.pointerPosition; // ============================================================================= // Game configuration @@ -532,8 +572,11 @@ export const query_pointer_position: () => PositionXY * @returns a number within the interval * @hidden */ -const withinRange: (num: number, min: number, max: number) => number -= (num: number, min: number, max: number) => { +const withinRange: (num: number, min: number, max: number) => number = ( + num: number, + min: number, + max: number +) => { if (num > max) { return max; } @@ -613,7 +656,6 @@ export const enable_debug: () => void = () => { config.isDebugEnabled = true; }; - /** * Logs any information passed into it within the `update_loop`. * Displays the information in the top-left corner of the canvas only if debug mode is enabled. @@ -653,7 +695,8 @@ export const debug_log: (info: string) => void = (info: string) => { * ``` * @category Logic */ -export const input_key_down: (key_name: string) => boolean = (key_name: string) => gameState.inputKeysDown.has(key_name); +export const input_key_down: (key_name: string) => boolean = (key_name: string) => + gameState.inputKeysDown.has(key_name); /** * Detects if the left mouse button is pressed down. @@ -668,7 +711,8 @@ export const input_key_down: (key_name: string) => boolean = (key_name: string) * ``` * @category Logic */ -export const input_left_mouse_down: () => boolean = () => gameState.pointerProps.isPointerPrimaryDown; +export const input_left_mouse_down: () => boolean = () => + gameState.pointerProps.isPointerPrimaryDown; /** * Detects if the right mouse button is pressed down. @@ -683,7 +727,8 @@ export const input_left_mouse_down: () => boolean = () => gameState.pointerProps * ``` * @category Logic */ -export const input_right_mouse_down: () => boolean = () => gameState.pointerProps.isPointerSecondaryDown; +export const input_right_mouse_down: () => boolean = () => + gameState.pointerProps.isPointerSecondaryDown; /** * Detects if the (mouse) pointer is over the gameobject. @@ -726,9 +771,14 @@ export const pointer_over_gameobject = (gameObject: GameObject) => { * ``` * @category Logic */ -export const gameobjects_overlap: (gameObject1: InteractableGameObject, gameObject2: InteractableGameObject) => boolean -= (gameObject1: InteractableGameObject, gameObject2: InteractableGameObject) => { - if (gameObject1 instanceof InteractableGameObject && gameObject2 instanceof InteractableGameObject) { +export const gameobjects_overlap: ( + gameObject1: InteractableGameObject, + gameObject2: InteractableGameObject +) => boolean = (gameObject1: InteractableGameObject, gameObject2: InteractableGameObject) => { + if ( + gameObject1 instanceof InteractableGameObject && + gameObject2 instanceof InteractableGameObject + ) { return gameObject1.isOverlapping(gameObject2); } throw new TypeError('Cannot check overlap of non-GameObject'); @@ -785,7 +835,9 @@ export const get_loop_count: () => number = () => gameState.loopCount; * }) * ``` */ -export const update_loop: (update_function: UpdateFunction) => void = (update_function: UpdateFunction) => { +export const update_loop: (update_function: UpdateFunction) => void = ( + update_function: UpdateFunction +) => { // Test for error in user update function // This cannot not check for errors inside a block that is not run. update_function([]); @@ -811,13 +863,13 @@ export const build_game: () => BuildGame = () => { const inputConfig = { keyboard: true, mouse: true, - windowEvents: false, + windowEvents: false }; const fpsConfig = { min: MIN_FPS, target: config.fps, - forceSetTimeOut: true, + forceSetTimeOut: true }; const gameConfig = { @@ -832,12 +884,12 @@ export const build_game: () => BuildGame = () => { scene: PhaserScene, input: inputConfig, fps: fpsConfig, - banner: false, + banner: false }; return { toReplString: () => '[Arcade 2D]', - gameConfig, + gameConfig }; }; @@ -863,8 +915,10 @@ export const build_game: () => BuildGame = () => { * ``` * @category Audio */ -export const create_audio: (audio_url: string, volume_level: number) => AudioClip -= (audio_url: string, volume_level: number) => { +export const create_audio: (audio_url: string, volume_level: number) => AudioClip = ( + audio_url: string, + volume_level: number +) => { if (typeof audio_url !== 'string') { throw new Error('audio_url must be a string'); } diff --git a/src/bundles/arcade_2d/gameobject.ts b/src/bundles/arcade_2d/gameobject.ts index 0b3126337c..a48f441d71 100644 --- a/src/bundles/arcade_2d/gameobject.ts +++ b/src/bundles/arcade_2d/gameobject.ts @@ -2,7 +2,11 @@ * This file contains the bundle's representation of GameObjects. */ import type { ReplResult } from '../../typings/type_helpers'; -import { DEFAULT_INTERACTABLE_PROPS, DEFAULT_RENDER_PROPS, DEFAULT_TRANSFORM_PROPS } from './constants'; +import { + DEFAULT_INTERACTABLE_PROPS, + DEFAULT_RENDER_PROPS, + DEFAULT_TRANSFORM_PROPS +} from './constants'; import type * as types from './types'; // ============================================================================= @@ -14,25 +18,30 @@ import type * as types from './types'; */ export abstract class GameObject implements Transformable, ReplResult { private static gameObjectCount: number = 0; + protected static gameObjectsArray: InteractableGameObject[] = []; // Stores all the created GameObjects + protected isTransformUpdated: boolean = false; + public readonly id: number; - constructor( - private transformProps: types.TransformProps = DEFAULT_TRANSFORM_PROPS, - ) { + constructor(private transformProps: types.TransformProps = DEFAULT_TRANSFORM_PROPS) { this.id = GameObject.gameObjectCount++; } + setTransform(transformProps: types.TransformProps) { this.transformProps = transformProps; this.isTransformUpdated = false; } + getTransform(): types.TransformProps { return this.transformProps; } + hasTransformUpdates(): boolean { return !this.isTransformUpdated; } + setTransformUpdated() { this.isTransformUpdated = true; } @@ -53,11 +62,12 @@ export abstract class GameObject implements Transformable, ReplResult { */ export abstract class RenderableGameObject extends GameObject implements Renderable { protected isRenderUpdated: boolean = false; + private shouldBringToTop: boolean = false; constructor( transformProps: types.TransformProps, - private renderProps: types.RenderProps = DEFAULT_RENDER_PROPS, + private renderProps: types.RenderProps = DEFAULT_RENDER_PROPS ) { super(transformProps); } @@ -72,9 +82,11 @@ export abstract class RenderableGameObject extends GameObject implements Rendera this.renderProps = renderProps; this.isRenderUpdated = false; } + getRenderState(): types.RenderProps { return this.renderProps; } + /** * Sets a flag to render the GameObject infront of other GameObjects. */ @@ -82,19 +94,24 @@ export abstract class RenderableGameObject extends GameObject implements Rendera this.shouldBringToTop = true; this.isRenderUpdated = false; } + hasRenderUpdates(): boolean { return !this.isRenderUpdated; } + setRenderUpdated() { this.isRenderUpdated = true; this.shouldBringToTop = false; } + getColor(): types.ColorRGBA { return this.renderProps.color; } + getFlipState(): types.FlipXY { return this.renderProps.flip; } + getShouldBringToTop(): boolean { return this.shouldBringToTop; } @@ -105,36 +122,45 @@ export abstract class RenderableGameObject extends GameObject implements Rendera */ export abstract class InteractableGameObject extends RenderableGameObject implements Interactable { protected isHitboxUpdated: boolean = false; + protected phaserGameObject: types.PhaserGameObject | undefined; constructor( transformProps: types.TransformProps, renderProps: types.RenderProps, - private interactableProps: types.InteractableProps = DEFAULT_INTERACTABLE_PROPS, + private interactableProps: types.InteractableProps = DEFAULT_INTERACTABLE_PROPS ) { super(transformProps, renderProps); GameObject.gameObjectsArray.push(this); } + setHitboxState(hitboxProps: types.InteractableProps) { this.interactableProps = hitboxProps; this.isHitboxUpdated = false; } + getHitboxState(): types.InteractableProps { return this.interactableProps; } + hasHitboxUpdates(): boolean { return !this.isHitboxUpdated; } + setHitboxUpdated() { this.isHitboxUpdated = true; } + /** * This stores the GameObject within the phaser game, which can only be set after the game has started. * @param phaserGameObject The phaser GameObject reference. */ - setPhaserGameObject(phaserGameObject: Phaser.GameObjects.Shape | Phaser.GameObjects.Sprite | Phaser.GameObjects.Text) { + setPhaserGameObject( + phaserGameObject: Phaser.GameObjects.Shape | Phaser.GameObjects.Sprite | Phaser.GameObjects.Text + ) { this.phaserGameObject = phaserGameObject; } + /** * Checks if two Source GameObjects are overlapping, using their Phaser GameObjects to check. * This method needs to be overriden if the hit area of the Phaser GameObject is not a rectangle. @@ -147,8 +173,11 @@ export abstract class InteractableGameObject extends RenderableGameObject implem } // Use getBounds to check if two objects overlap, checking the shape of the area before checking overlap. // Since getBounds() returns a Rectangle, it will be unable to check the actual intersection of non-rectangular shapes. - // eslint-disable-next-line new-cap - return Phaser.Geom.Intersects.RectangleToRectangle(this.phaserGameObject.getBounds(), other.phaserGameObject.getBounds()); + + return Phaser.Geom.Intersects.RectangleToRectangle( + this.phaserGameObject.getBounds(), + other.phaserGameObject.getBounds() + ); } } @@ -177,10 +206,11 @@ export class RectangleGameObject extends ShapeGameObject { transformProps: types.TransformProps, renderProps: types.RenderProps, interactableProps: types.InteractableProps, - private rectangle: types.RectangleProps, + private rectangle: types.RectangleProps ) { super(transformProps, renderProps, interactableProps); } + /** @override */ getShape(): types.RectangleProps { return this.rectangle; @@ -195,10 +225,11 @@ export class CircleGameObject extends ShapeGameObject { transformProps: types.TransformProps, renderProps: types.RenderProps, interactableProps: types.InteractableProps, - private circle: types.CircleProps, + private circle: types.CircleProps ) { super(transformProps, renderProps, interactableProps); } + /** @override */ getShape(): types.CircleProps { return this.circle; @@ -213,10 +244,11 @@ export class TriangleGameObject extends ShapeGameObject { transformProps: types.TransformProps, renderProps: types.RenderProps, interactableProps: types.InteractableProps, - private triangle: types.TriangleProps, + private triangle: types.TriangleProps ) { super(transformProps, renderProps, interactableProps); } + /** @override */ getShape(): types.TriangleProps { return this.triangle; @@ -231,10 +263,11 @@ export class SpriteGameObject extends InteractableGameObject { transformProps: types.TransformProps, renderProps: types.RenderProps, interactableProps: types.InteractableProps, - private sprite: types.Sprite, + private sprite: types.Sprite ) { super(transformProps, renderProps, interactableProps); } + /** * Gets the sprite displayed by the SpriteGameObject. * @returns The sprite as a Sprite. @@ -258,10 +291,11 @@ export class TextGameObject extends InteractableGameObject { transformProps: types.TransformProps, renderProps: types.RenderProps, interactableProps: types.InteractableProps, - private displayText: types.DisplayText, + private displayText: types.DisplayText ) { super(transformProps, renderProps, interactableProps); } + /** * Sets the text displayed by the GameObject in the canvas. * @param text The text displayed. @@ -270,6 +304,7 @@ export class TextGameObject extends InteractableGameObject { this.setRenderState(this.getRenderState()); this.displayText = text; } + /** * Gets the text displayed by the GameObject in the canvas. * @returns The text displayed. diff --git a/src/bundles/arcade_2d/index.ts b/src/bundles/arcade_2d/index.ts index 90234e3074..f5985c2e9b 100644 --- a/src/bundles/arcade_2d/index.ts +++ b/src/bundles/arcade_2d/index.ts @@ -218,11 +218,24 @@ build_game(); */ export { + build_game, + create_audio, create_circle, create_rectangle, - create_triangle, create_sprite, create_text, + create_triangle, + debug_log, + enable_debug, + gameobjects_overlap, + get_game_time, + get_loop_count, + input_key_down, + input_left_mouse_down, + input_right_mouse_down, + loop_audio, + play_audio, + pointer_over_gameobject, query_color, query_flip, query_id, @@ -231,29 +244,16 @@ export { query_rotation, query_scale, query_text, + set_dimensions, + set_fps, + set_scale, + stop_audio, update_color, update_flip, + update_loop, update_position, update_rotation, update_scale, update_text, - update_to_top, - set_fps, - set_dimensions, - set_scale, - get_game_time, - get_loop_count, - enable_debug, - debug_log, - input_key_down, - input_left_mouse_down, - input_right_mouse_down, - pointer_over_gameobject, - gameobjects_overlap, - update_loop, - build_game, - create_audio, - loop_audio, - stop_audio, - play_audio, + update_to_top } from './functions'; diff --git a/src/bundles/arcade_2d/phaserScene.ts b/src/bundles/arcade_2d/phaserScene.ts index 65ade47158..49ad55e520 100644 --- a/src/bundles/arcade_2d/phaserScene.ts +++ b/src/bundles/arcade_2d/phaserScene.ts @@ -1,25 +1,23 @@ import Phaser from 'phaser'; +import { AudioClip } from './audio'; +import { DEFAULT_PATH_PREFIX } from './constants'; +import { config } from './functions'; import { CircleGameObject, GameObject, - type InteractableGameObject, RectangleGameObject, ShapeGameObject, SpriteGameObject, TextGameObject, TriangleGameObject, + type InteractableGameObject } from './gameobject'; import { - config, -} from './functions'; -import { - type TransformProps, - type PositionXY, type ExceptionError, type PhaserGameObject, + type PositionXY, + type TransformProps } from './types'; -import { AudioClip } from './audio'; -import { DEFAULT_PATH_PREFIX } from './constants'; // Game state information, that changes every frame. export const gameState = { @@ -38,8 +36,8 @@ export const gameState = { isPointerPrimaryDown: false, isPointerSecondaryDown: false, // Stores the IDs of the GameObjects that the pointer is over - pointerOverGameObjectsId: new Set(), - }, + pointerOverGameObjectsId: new Set() + } }; // The game state which the user can modify, through their update function. @@ -53,15 +51,25 @@ export class PhaserScene extends Phaser.Scene { constructor() { super('PhaserScene'); } + private sourceGameObjects: Array = GameObject.getGameObjectsArray(); + private phaserGameObjects: Array = []; + private corsAssetsUrl: Set = new Set(); + private sourceAudioClips: Array = AudioClip.getAudioClipsArray(); + private phaserAudioClips: Array = []; + private shouldRerenderGameObjects: boolean = true; + private delayedKeyUpEvents: Set = new Set(); + private hasRuntimeError: boolean = false; + private debugLogInitialErrorCount: number = 0; + // Handle debug information private debugLogText: Phaser.GameObjects.Text | undefined = undefined; @@ -111,7 +119,8 @@ export class PhaserScene extends Phaser.Scene { if (!config.isDebugEnabled && !this.hasRuntimeError) { gameState.debugLogArray.length = 0; } - this.debugLogText = this.add.text(0, 0, gameState.debugLogArray) + this.debugLogText = this.add + .text(0, 0, gameState.debugLogArray) .setWordWrapWidth(config.scale < 1 ? this.renderer.width * config.scale : this.renderer.width) .setBackgroundColor('black') .setAlpha(0.8) @@ -126,9 +135,12 @@ export class PhaserScene extends Phaser.Scene { // Set the pointer properties gameState.pointerProps = { ...gameState.pointerProps, - pointerPosition: [Math.trunc(this.input.activePointer.x), Math.trunc(this.input.activePointer.y)], + pointerPosition: [ + Math.trunc(this.input.activePointer.x), + Math.trunc(this.input.activePointer.y) + ], isPointerPrimaryDown: this.input.activePointer.primaryDown, - isPointerSecondaryDown: this.input.activePointer.rightButtonDown(), + isPointerSecondaryDown: this.input.activePointer.rightButtonDown() }; this.handleUserDefinedUpdateFunction(); @@ -163,11 +175,9 @@ export class PhaserScene extends Phaser.Scene { // Create TextGameObject if (gameObject instanceof TextGameObject) { const text = gameObject.getText().text; - this.phaserGameObjects.push(this.add.text( - transformProps.position[0], - transformProps.position[1], - text, - )); + this.phaserGameObjects.push( + this.add.text(transformProps.position[0], transformProps.position[1], text) + ); this.phaserGameObjects[gameObject.id].setOrigin(0.5, 0.5); if (gameObject.getHitboxState().isHitboxActive) { this.phaserGameObjects[gameObject.id].setInteractive(); @@ -176,11 +186,9 @@ export class PhaserScene extends Phaser.Scene { // Create SpriteGameObject if (gameObject instanceof SpriteGameObject) { const url = gameObject.getSprite().imageUrl; - this.phaserGameObjects.push(this.add.sprite( - transformProps.position[0], - transformProps.position[1], - url, - )); + this.phaserGameObjects.push( + this.add.sprite(transformProps.position[0], transformProps.position[1], url) + ); if (gameObject.getHitboxState().isHitboxActive) { this.phaserGameObjects[gameObject.id].setInteractive(); } @@ -189,55 +197,48 @@ export class PhaserScene extends Phaser.Scene { if (gameObject instanceof ShapeGameObject) { if (gameObject instanceof RectangleGameObject) { const shape = gameObject.getShape(); - this.phaserGameObjects.push(this.add.rectangle( - transformProps.position[0], - transformProps.position[1], - shape.width, - shape.height, - )); + this.phaserGameObjects.push( + this.add.rectangle( + transformProps.position[0], + transformProps.position[1], + shape.width, + shape.height + ) + ); if (gameObject.getHitboxState().isHitboxActive) { this.phaserGameObjects[gameObject.id].setInteractive(); } } if (gameObject instanceof CircleGameObject) { const shape = gameObject.getShape(); - this.phaserGameObjects.push(this.add.circle( - transformProps.position[0], - transformProps.position[1], - shape.radius, - )); + this.phaserGameObjects.push( + this.add.circle(transformProps.position[0], transformProps.position[1], shape.radius) + ); if (gameObject.getHitboxState().isHitboxActive) { this.phaserGameObjects[gameObject.id].setInteractive( - new Phaser.Geom.Circle( - shape.radius, - shape.radius, - shape.radius, - ), Phaser.Geom.Circle.Contains, + new Phaser.Geom.Circle(shape.radius, shape.radius, shape.radius), + Phaser.Geom.Circle.Contains ); } } if (gameObject instanceof TriangleGameObject) { const shape = gameObject.getShape(); - this.phaserGameObjects.push(this.add.triangle( - transformProps.position[0], - transformProps.position[1], - shape.x1, - shape.y1, - shape.x2, - shape.y2, - shape.x3, - shape.y3, - )); + this.phaserGameObjects.push( + this.add.triangle( + transformProps.position[0], + transformProps.position[1], + shape.x1, + shape.y1, + shape.x2, + shape.y2, + shape.x3, + shape.y3 + ) + ); if (gameObject.getHitboxState().isHitboxActive) { this.phaserGameObjects[gameObject.id].setInteractive( - new Phaser.Geom.Triangle( - shape.x1, - shape.y1, - shape.x2, - shape.y2, - shape.x3, - shape.y3, - ), Phaser.Geom.Triangle.Contains, + new Phaser.Geom.Triangle(shape.x1, shape.y1, shape.x2, shape.y2, shape.x3, shape.y3), + Phaser.Geom.Triangle.Contains ); } } @@ -265,10 +266,12 @@ export class PhaserScene extends Phaser.Scene { private createAudioClips() { try { this.sourceAudioClips.forEach((audioClip: AudioClip) => { - this.phaserAudioClips.push(this.sound.add(audioClip.getUrl(), { - loop: audioClip.shouldAudioClipLoop(), - volume: audioClip.getVolumeLevel(), - })); + this.phaserAudioClips.push( + this.sound.add(audioClip.getUrl(), { + loop: audioClip.shouldAudioClipLoop(), + volume: audioClip.getVolumeLevel() + }) + ); }); } catch (error) { this.hasRuntimeError = true; @@ -294,7 +297,7 @@ export class PhaserScene extends Phaser.Scene { } else { const exceptionError = error as ExceptionError; gameState.debugLogArray.push( - `Line ${exceptionError.location.start.line}: ${exceptionError.error.name}: ${exceptionError.error.message}`, + `Line ${exceptionError.location.start.line}: ${exceptionError.error.name}: ${exceptionError.error.message}` ); } } @@ -308,12 +311,15 @@ export class PhaserScene extends Phaser.Scene { // Update the transform of Phaser GameObject if (gameObject.hasTransformUpdates() || this.shouldRerenderGameObjects) { const transformProps = gameObject.getTransform() as TransformProps; - phaserGameObject.setPosition(transformProps.position[0], transformProps.position[1]) + phaserGameObject + .setPosition(transformProps.position[0], transformProps.position[1]) .setRotation(transformProps.rotation) .setScale(transformProps.scale[0], transformProps.scale[1]); if (gameObject instanceof TriangleGameObject) { // The only shape that requires flipping is the triangle, as the rest are symmetric about their origin. - phaserGameObject.setRotation(transformProps.rotation + (gameObject.getFlipState()[1] ? Math.PI : 0)); + phaserGameObject.setRotation( + transformProps.rotation + (gameObject.getFlipState()[1] ? Math.PI : 0) + ); } gameObject.setTransformUpdated(); } @@ -321,22 +327,25 @@ export class PhaserScene extends Phaser.Scene { // Update the image of Phaser GameObject if (gameObject.hasRenderUpdates() || this.shouldRerenderGameObjects) { const color = gameObject.getColor(); - // eslint-disable-next-line new-cap + const intColor = Phaser.Display.Color.GetColor32(color[0], color[1], color[2], color[3]); const flip = gameObject.getFlipState(); if (gameObject instanceof TextGameObject) { - (phaserGameObject as Phaser.GameObjects.Text).setTint(intColor) + (phaserGameObject as Phaser.GameObjects.Text) + .setTint(intColor) .setAlpha(color[3] / 255) .setFlip(flip[0], flip[1]) .setText(gameObject.getText().text); } else if (gameObject instanceof SpriteGameObject) { - (phaserGameObject as Phaser.GameObjects.Sprite).setTint(intColor) + (phaserGameObject as Phaser.GameObjects.Sprite) + .setTint(intColor) .setAlpha(color[3] / 255) .setFlip(flip[0], flip[1]); } else if (gameObject instanceof ShapeGameObject) { - (phaserGameObject as Phaser.GameObjects.Shape).setFillStyle(intColor, color[3] / 255) - // Phaser.GameObjects.Shape does not have setFlip, so flipping is done with rotations. - // The only shape that requires flipping is the triangle, as the rest are symmetric about their origin. + (phaserGameObject as Phaser.GameObjects.Shape) + .setFillStyle(intColor, color[3] / 255) + // Phaser.GameObjects.Shape does not have setFlip, so flipping is done with rotations. + // The only shape that requires flipping is the triangle, as the rest are symmetric about their origin. .setRotation(gameObject.getTransform().rotation + (flip[1] ? Math.PI : 0)); } // Update the z-index (rendering order), to the top. diff --git a/src/bundles/arcade_2d/types.ts b/src/bundles/arcade_2d/types.ts index dafa6c61e0..2116275571 100644 --- a/src/bundles/arcade_2d/types.ts +++ b/src/bundles/arcade_2d/types.ts @@ -124,4 +124,7 @@ export type ExceptionError = { /** * Represents the Phaser Game Object types that are used. */ -export type PhaserGameObject = Phaser.GameObjects.Sprite | Phaser.GameObjects.Text | Phaser.GameObjects.Shape; +export type PhaserGameObject = + | Phaser.GameObjects.Sprite + | Phaser.GameObjects.Text + | Phaser.GameObjects.Shape; diff --git a/src/bundles/binary_tree/functions.ts b/src/bundles/binary_tree/functions.ts index 1988d3279f..596329b360 100644 --- a/src/bundles/binary_tree/functions.ts +++ b/src/bundles/binary_tree/functions.ts @@ -25,11 +25,7 @@ export function make_empty_tree(): BinaryTree { * @param right Right subtree of the node * @returns A binary tree */ -export function make_tree( - value: any, - left: BinaryTree, - right: BinaryTree, -): BinaryTree { +export function make_tree(value: any, left: BinaryTree, right: BinaryTree): BinaryTree { return [value, [left, [right, null]]]; } @@ -44,18 +40,18 @@ export function make_tree( * @param v Value to be tested * @returns bool */ -export function is_tree( - value: any, -): boolean { - return value === null - || (Array.isArray(value) - && value.length === 2 - && Array.isArray(value[1]) - && value[1].length === 2 - && is_tree(value[1][0]) - && value[1][1].length === 2 - && is_tree(value[1][1][0]) - && value[1][1][1] === null); +export function is_tree(value: any): boolean { + return ( + value === null || + (Array.isArray(value) && + value.length === 2 && + Array.isArray(value[1]) && + value[1].length === 2 && + is_tree(value[1][0]) && + value[1][1].length === 2 && + is_tree(value[1][1][0]) && + value[1][1][1] === null) + ); } /** @@ -69,9 +65,7 @@ export function is_tree( * @param v Value to be tested * @returns bool */ -export function is_empty_tree( - value: any, -): boolean { +export function is_empty_tree(value: any): boolean { return value === null; } @@ -85,15 +79,11 @@ export function is_empty_tree( * @param t BinaryTree to be accessed * @returns Value */ -export function entry( - t: BinaryTree, -): boolean { +export function entry(t: BinaryTree): boolean { if (Array.isArray(t) && t.length === 2) { return t[0]; } - throw new Error( - `function entry expects binary tree, received: ${t}`, - ); + throw new Error(`function entry expects binary tree, received: ${t}`); } /** @@ -106,16 +96,11 @@ export function entry( * @param t BinaryTree to be accessed * @returns BinaryTree */ -export function left_branch( - t: BinaryTree, -): BinaryTree { - if (Array.isArray(t) && t.length === 2 - && Array.isArray(t[1]) && t[1].length === 2) { +export function left_branch(t: BinaryTree): BinaryTree { + if (Array.isArray(t) && t.length === 2 && Array.isArray(t[1]) && t[1].length === 2) { return t[1][0]; } - throw new Error( - `function left_branch expects binary tree, received: ${t}`, - ); + throw new Error(`function left_branch expects binary tree, received: ${t}`); } /** @@ -128,15 +113,16 @@ export function left_branch( * @param t BinaryTree to be accessed * @returns BinaryTree */ -export function right_branch( - t: BinaryTree, -): BinaryTree { - if (Array.isArray(t) && t.length === 2 - && Array.isArray(t[1]) && t[1].length === 2 - && Array.isArray(t[1][1]) && t[1][1].length === 2) { +export function right_branch(t: BinaryTree): BinaryTree { + if ( + Array.isArray(t) && + t.length === 2 && + Array.isArray(t[1]) && + t[1].length === 2 && + Array.isArray(t[1][1]) && + t[1][1].length === 2 + ) { return t[1][1][0]; } - throw new Error( - `function right_branch expects binary tree, received: ${t}`, - ); + throw new Error(`function right_branch expects binary tree, received: ${t}`); } diff --git a/src/bundles/binary_tree/index.ts b/src/bundles/binary_tree/index.ts index 4b9d3c3b8c..b193994101 100644 --- a/src/bundles/binary_tree/index.ts +++ b/src/bundles/binary_tree/index.ts @@ -9,6 +9,11 @@ * @author Loh Xian Ze, Bryan */ export { - entry, is_empty_tree, is_tree, left_branch, - make_empty_tree, make_tree, right_branch, + entry, + is_empty_tree, + is_tree, + left_branch, + make_empty_tree, + make_tree, + right_branch } from './functions'; diff --git a/src/bundles/copy_gc/index.ts b/src/bundles/copy_gc/index.ts index 34aa0812a7..53126b0ec9 100644 --- a/src/bundles/copy_gc/index.ts +++ b/src/bundles/copy_gc/index.ts @@ -65,7 +65,7 @@ function generateMemory(): void { leftDesc: '', rightDesc: '', scan: -1, - free: -1, + free: -1 }; commandHeap.push(obj); @@ -111,7 +111,7 @@ function newCommand( heap, description, firstDesc, - lastDesc, + lastDesc ): void { const newType = type; const newToSpace = toSpace; @@ -142,7 +142,7 @@ function newCommand( leftDesc: newFirstDesc, rightDesc: newLastDesc, scan: -1, - free: -1, + free: -1 }; commandHeap.push(obj); @@ -166,7 +166,7 @@ function newCopy(left, right, heap): void { heap, desc, 'index', - 'free', + 'free' ); } @@ -176,19 +176,7 @@ function endFlip(left, heap): void { const toSpace = commandHeap[length - 1].to; const newSizeLeft = heap[left + SIZE_SLOT]; const desc = 'Flip finished'; - newCommand( - COMMAND.FLIP, - toSpace, - fromSpace, - left, - -1, - newSizeLeft, - 0, - heap, - desc, - 'free', - '', - ); + newCommand(COMMAND.FLIP, toSpace, fromSpace, left, -1, newSizeLeft, 0, heap, desc, 'free', ''); updateFlip(); } @@ -204,19 +192,7 @@ function resetRoots(): void { function startFlip(toSpace, fromSpace, heap): void { const desc = 'Memory is exhausted. Start stop and copy garbage collector.'; - newCommand( - 'Start of Cheneys', - toSpace, - fromSpace, - -1, - -1, - 0, - 0, - heap, - desc, - '', - '', - ); + newCommand('Start of Cheneys', toSpace, fromSpace, -1, -1, 0, 0, heap, desc, '', ''); updateFlip(); } @@ -236,7 +212,7 @@ function newPush(left, right, heap): void { heap, desc, 'last child address slot', - 'new child pushed', + 'new child pushed' ); } @@ -257,7 +233,7 @@ function newPop(res, left, right, heap): void { heap, desc, 'popped memory', - 'last child address slot', + 'last child address slot' ); } @@ -265,19 +241,7 @@ function doneShowRoot(heap): void { const toSpace = 0; const fromSpace = 0; const desc = 'All root nodes are copied'; - newCommand( - 'Copied Roots', - toSpace, - fromSpace, - -1, - -1, - 0, - 0, - heap, - desc, - '', - '', - ); + newCommand('Copied Roots', toSpace, fromSpace, -1, -1, 0, 0, heap, desc, '', ''); } function showRoots(left, heap): void { @@ -297,7 +261,7 @@ function showRoots(left, heap): void { heap, desc, 'roots', - '', + '' ); } @@ -307,19 +271,7 @@ function newAssign(res, left, heap): void { const fromSpace = commandHeap[length - 1].from; const newRes = res; const desc = `Assign memory [${left}] with ${newRes}.`; - newCommand( - COMMAND.ASSIGN, - toSpace, - fromSpace, - left, - -1, - 1, - 1, - heap, - desc, - 'assigned memory', - '', - ); + newCommand(COMMAND.ASSIGN, toSpace, fromSpace, left, -1, 1, 1, heap, desc, 'assigned memory', ''); } function newNew(left, heap): void { @@ -339,7 +291,7 @@ function newNew(left, heap): void { heap, desc, 'new memory allocated', - '', + '' ); } @@ -380,18 +332,13 @@ function scanFlip(left, right, scan, free, heap): void { free: newFree, desc: newDesc, leftDesc: 'scan', - rightDesc: 'free', + rightDesc: 'free' }; commandHeap.push(obj); } -function updateSlotSegment( - tag: number, - size: number, - first: number, - last: number, -): void { +function updateSlotSegment(tag: number, size: number, first: number, last: number): void { if (tag >= 0) { TAG_SLOT = tag; } @@ -422,7 +369,7 @@ function get_flips(): number[] { return flips; } -function get_types(): String[] { +function get_types(): string[] { return typeTag; } @@ -478,10 +425,9 @@ function init() { get_flips, get_slots, get_command, - get_roots, + get_roots }; } - export { init, // initialisation @@ -503,5 +449,5 @@ export { updateRoots, resetRoots, showRoots, - doneShowRoot, + doneShowRoot }; diff --git a/src/bundles/copy_gc/types.ts b/src/bundles/copy_gc/types.ts index 32ae5b2432..c4d4e25f59 100644 --- a/src/bundles/copy_gc/types.ts +++ b/src/bundles/copy_gc/types.ts @@ -12,11 +12,11 @@ export enum COMMAND { ASSIGN = 'Assign', NEW = 'New', SCAN = 'Scan', - INIT = 'Initialize Memory', + INIT = 'Initialize Memory' } export type CommandHeapObject = { - type: String; + type: string; to: number; from: number; heap: number[]; @@ -24,9 +24,9 @@ export type CommandHeapObject = { right: number; sizeLeft: number; sizeRight: number; - desc: String; + desc: string; scan: number; - leftDesc: String; - rightDesc: String; + leftDesc: string; + rightDesc: string; free: number; }; diff --git a/src/bundles/csg/core.ts b/src/bundles/csg/core.ts index 82bef31e44..40285bc5c9 100644 --- a/src/bundles/csg/core.ts +++ b/src/bundles/csg/core.ts @@ -12,13 +12,13 @@ export class Core { } public static getRenderGroupManager(): RenderGroupManager { - let moduleState: CsgModuleState = Core.moduleState as CsgModuleState; + const moduleState: CsgModuleState = Core.moduleState as CsgModuleState; return moduleState.renderGroupManager; } public static nextComponent(): number { - let moduleState: CsgModuleState = Core.moduleState as CsgModuleState; + const moduleState: CsgModuleState = Core.moduleState as CsgModuleState; return moduleState.nextComponent(); } diff --git a/src/bundles/csg/functions.ts b/src/bundles/csg/functions.ts index 911af36dd7..28b08efb7f 100644 --- a/src/bundles/csg/functions.ts +++ b/src/bundles/csg/functions.ts @@ -1,38 +1,27 @@ /* [Imports] */ import { primitives } from '@jscad/modeling'; import { colorize as colorSolid } from '@jscad/modeling/src/colors'; -import { - measureBoundingBox, - type BoundingBox, -} from '@jscad/modeling/src/measurements'; +import { measureBoundingBox, type BoundingBox } from '@jscad/modeling/src/measurements'; import { intersect as _intersect, subtract as _subtract, - union as _union, + union as _union } from '@jscad/modeling/src/operations/booleans'; import { extrudeLinear } from '@jscad/modeling/src/operations/extrusions'; import { serialize } from '@jscad/stl-serializer'; -import { - head, - list, - tail, - type List, - is_list, -} from 'js-slang/dist/stdlib/list'; +import { head, is_list, list, tail, type List } from 'js-slang/dist/stdlib/list'; import save from 'save-file'; +import { degreesToRadians } from '../../common/utilities.js'; import { Core } from './core.js'; import type { Solid } from './jscad/types.js'; import { Group, Shape, + centerPrimitive, hexToColor, type Operable, - type RenderGroup, - centerPrimitive, + type RenderGroup } from './utilities'; -import { degreesToRadians } from '../../common/utilities.js'; - - /* [Main] */ /* NOTE @@ -48,9 +37,9 @@ import { degreesToRadians } from '../../common/utilities.js'; the underlying code is free to operate with arrays. */ export function listToArray(l: List): Operable[] { - let operables: Operable[] = []; + const operables: Operable[] = []; while (l !== null) { - let operable: Operable = head(l); + const operable: Operable = head(l); operables.push(operable); l = tail(l); } @@ -61,8 +50,6 @@ export function arrayToList(array: Operable[]): List { return list(...array); } - - /* [Exports] */ // [Variables - Colors] @@ -192,13 +179,8 @@ export const white: string = '#FFFFFF'; * @category Primitives */ export function cube(hex: string): Shape { - let solid: Solid = primitives.cube({ size: 1 }); - let shape: Shape = new Shape( - colorSolid( - hexToColor(hex), - solid, - ), - ); + const solid: Solid = primitives.cube({ size: 1 }); + const shape: Shape = new Shape(colorSolid(hexToColor(hex), solid)); return centerPrimitive(shape); } @@ -213,13 +195,8 @@ export function cube(hex: string): Shape { * @category Primitives */ export function rounded_cube(hex: string): Shape { - let solid: Solid = primitives.roundedCuboid({ size: [1, 1, 1] }); - let shape: Shape = new Shape( - colorSolid( - hexToColor(hex), - solid, - ), - ); + const solid: Solid = primitives.roundedCuboid({ size: [1, 1, 1] }); + const shape: Shape = new Shape(colorSolid(hexToColor(hex), solid)); return centerPrimitive(shape); } @@ -235,16 +212,11 @@ export function rounded_cube(hex: string): Shape { * @category Primitives */ export function cylinder(hex: string): Shape { - let solid: Solid = primitives.cylinder({ + const solid: Solid = primitives.cylinder({ height: 1, - radius: 0.5, + radius: 0.5 }); - let shape: Shape = new Shape( - colorSolid( - hexToColor(hex), - solid, - ), - ); + const shape: Shape = new Shape(colorSolid(hexToColor(hex), solid)); return centerPrimitive(shape); } @@ -260,16 +232,11 @@ export function cylinder(hex: string): Shape { * @category Primitives */ export function rounded_cylinder(hex: string): Shape { - let solid: Solid = primitives.roundedCylinder({ + const solid: Solid = primitives.roundedCylinder({ height: 1, - radius: 0.5, + radius: 0.5 }); - let shape: Shape = new Shape( - colorSolid( - hexToColor(hex), - solid, - ), - ); + const shape: Shape = new Shape(colorSolid(hexToColor(hex), solid)); return centerPrimitive(shape); } @@ -284,13 +251,8 @@ export function rounded_cylinder(hex: string): Shape { * @category Primitives */ export function sphere(hex: string): Shape { - let solid: Solid = primitives.sphere({ radius: 0.5 }); - let shape: Shape = new Shape( - colorSolid( - hexToColor(hex), - solid, - ), - ); + const solid: Solid = primitives.sphere({ radius: 0.5 }); + const shape: Shape = new Shape(colorSolid(hexToColor(hex), solid)); return centerPrimitive(shape); } @@ -305,13 +267,8 @@ export function sphere(hex: string): Shape { * @category Primitives */ export function geodesic_sphere(hex: string): Shape { - let solid: Solid = primitives.geodesicSphere({ radius: 0.5 }); - let shape: Shape = new Shape( - colorSolid( - hexToColor(hex), - solid, - ), - ); + const solid: Solid = primitives.geodesicSphere({ radius: 0.5 }); + const shape: Shape = new Shape(colorSolid(hexToColor(hex), solid)); return centerPrimitive(shape); } @@ -327,22 +284,17 @@ export function geodesic_sphere(hex: string): Shape { * @category Primitives */ export function pyramid(hex: string): Shape { - let pythagorasSide: number = Math.sqrt(2); // sqrt(1^2 + 1^2) - let radius = pythagorasSide / 2; - let solid: Solid = primitives.cylinderElliptic({ + const pythagorasSide: number = Math.sqrt(2); // sqrt(1^2 + 1^2) + const radius = pythagorasSide / 2; + const solid: Solid = primitives.cylinderElliptic({ height: 1, // Base starting radius startRadius: [radius, radius], // Radius by the time the top is reached endRadius: [0, 0], - segments: 4, + segments: 4 }); - let shape: Shape = new Shape( - colorSolid( - hexToColor(hex), - solid, - ), - ); + let shape: Shape = new Shape(colorSolid(hexToColor(hex), solid)); shape = rotate(shape, 0, 0, degreesToRadians(45)) as Shape; return centerPrimitive(shape); } @@ -359,17 +311,12 @@ export function pyramid(hex: string): Shape { * @category Primitives */ export function cone(hex: string): Shape { - let solid: Solid = primitives.cylinderElliptic({ + const solid: Solid = primitives.cylinderElliptic({ height: 1, startRadius: [0.5, 0.5], - endRadius: [0, 0], + endRadius: [0, 0] }); - let shape: Shape = new Shape( - colorSolid( - hexToColor(hex), - solid, - ), - ); + const shape: Shape = new Shape(colorSolid(hexToColor(hex), solid)); return centerPrimitive(shape); } @@ -385,16 +332,8 @@ export function cone(hex: string): Shape { * @category Primitives */ export function prism(hex: string): Shape { - let solid: Solid = extrudeLinear( - { height: 1 }, - primitives.triangle(), - ); - let shape: Shape = new Shape( - colorSolid( - hexToColor(hex), - solid, - ), - ); + const solid: Solid = extrudeLinear({ height: 1 }, primitives.triangle()); + let shape: Shape = new Shape(colorSolid(hexToColor(hex), solid)); shape = rotate(shape, 0, 0, degreesToRadians(-90)) as Shape; return centerPrimitive(shape); } @@ -410,16 +349,8 @@ export function prism(hex: string): Shape { * @category Primitives */ export function star(hex: string): Shape { - let solid: Solid = extrudeLinear( - { height: 1 }, - primitives.star({ outerRadius: 0.5 }), - ); - let shape: Shape = new Shape( - colorSolid( - hexToColor(hex), - solid, - ), - ); + const solid: Solid = extrudeLinear({ height: 1 }, primitives.star({ outerRadius: 0.5 })); + const shape: Shape = new Shape(colorSolid(hexToColor(hex), solid)); return centerPrimitive(shape); } @@ -435,16 +366,11 @@ export function star(hex: string): Shape { * @category Primitives */ export function torus(hex: string): Shape { - let solid: Solid = primitives.torus({ + const solid: Solid = primitives.torus({ innerRadius: 0.15, - outerRadius: 0.35, + outerRadius: 0.35 }); - let shape: Shape = new Shape( - colorSolid( - hexToColor(hex), - solid, - ), - ); + const shape: Shape = new Shape(colorSolid(hexToColor(hex), solid)); return centerPrimitive(shape); } @@ -464,7 +390,7 @@ export function union(first: Shape, second: Shape): Shape { throw new Error('Failed to union, only Shapes can be operated on'); } - let solid: Solid = _union(first.solid, second.solid); + const solid: Solid = _union(first.solid, second.solid); return new Shape(solid); } @@ -483,7 +409,7 @@ export function subtract(target: Shape, subtractedShape: Shape): Shape { throw new Error('Failed to subtract, only Shapes can be operated on'); } - let solid: Solid = _subtract(target.solid, subtractedShape.solid); + const solid: Solid = _subtract(target.solid, subtractedShape.solid); return new Shape(solid); } @@ -501,7 +427,7 @@ export function intersect(first: Shape, second: Shape): Shape { throw new Error('Failed to intersect, only Shapes can be operated on'); } - let solid: Solid = _intersect(first.solid, second.solid); + const solid: Solid = _intersect(first.solid, second.solid); return new Shape(solid); } @@ -523,7 +449,7 @@ export function translate( operable: Operable, xOffset: number, yOffset: number, - zOffset: number, + zOffset: number ): Operable { return operable.translate([xOffset, yOffset, zOffset]); } @@ -548,7 +474,7 @@ export function rotate( operable: Operable, xAngle: number, yAngle: number, - zAngle: number, + zAngle: number ): Operable { return operable.rotate([xAngle, yAngle, zAngle]); } @@ -573,7 +499,7 @@ export function scale( operable: Operable, xFactor: number, yFactor: number, - zFactor: number, + zFactor: number ): Operable { if (xFactor <= 0 || yFactor <= 0 || zFactor <= 0) { // JSCAD library does not allow factors <= 0 @@ -671,10 +597,8 @@ export function is_group(parameter: unknown): boolean { * * @category Utilities */ -export function bounding_box( - shape: Shape, -): (axis: string, minMax: string) => number { - let bounds: BoundingBox = measureBoundingBox(shape.solid); +export function bounding_box(shape: Shape): (axis: string, minMax: string) => number { + const bounds: BoundingBox = measureBoundingBox(shape.solid); return (axis: string, minMax: string): number => { let j: number; @@ -683,7 +607,7 @@ export function bounding_box( else if (axis === 'z') j = 2; else { throw new Error( - `Bounding box getter function expected "x", "y", or "z" as first parameter, but got ${axis}`, + `Bounding box getter function expected "x", "y", or "z" as first parameter, but got ${axis}` ); } @@ -692,7 +616,7 @@ export function bounding_box( else if (minMax === 'max') i = 1; else { throw new Error( - `Bounding box getter function expected "min" or "max" as second parameter, but got ${minMax}`, + `Bounding box getter function expected "min" or "max" as second parameter, but got ${minMax}` ); } @@ -710,18 +634,14 @@ export function bounding_box( * * @category Utilities */ -export function rgb( - redValue: number, - greenValue: number, - blueValue: number, -): string { +export function rgb(redValue: number, greenValue: number, blueValue: number): string { if ( - redValue < 0 - || redValue > 255 - || greenValue < 0 - || greenValue > 255 - || blueValue < 0 - || blueValue > 255 + redValue < 0 || + redValue > 255 || + greenValue < 0 || + greenValue > 255 || + blueValue < 0 || + blueValue > 255 ) { throw new Error('RGB values must be between 0 and 255 (inclusive)'); } @@ -744,10 +664,7 @@ export async function download_shape_stl(shape: Shape): Promise { throw new Error('Failed to export, only Shapes can be converted to STL'); } - await save( - new Blob(serialize({ binary: true }, shape.solid)), - 'Source Academy CSG Shape.stl', - ); + await save(new Blob(serialize({ binary: true }, shape.solid)), 'Source Academy CSG Shape.stl'); } // [Functions - Rendering] @@ -768,8 +685,7 @@ export function render(operable: Operable): RenderGroup { // Trigger a new render group for use with subsequent renders. // Render group is returned for REPL text only; do not document - return Core.getRenderGroupManager() - .nextRenderGroup(); + return Core.getRenderGroupManager().nextRenderGroup(); } /** @@ -786,8 +702,7 @@ export function render_grid(operable: Operable): RenderGroup { operable.store(); - return Core.getRenderGroupManager() - .nextRenderGroup(true); + return Core.getRenderGroupManager().nextRenderGroup(true); } /** @@ -804,8 +719,7 @@ export function render_axes(operable: Operable): RenderGroup { operable.store(); - return Core.getRenderGroupManager() - .nextRenderGroup(undefined, true); + return Core.getRenderGroupManager().nextRenderGroup(undefined, true); } /** @@ -822,6 +736,5 @@ export function render_grid_axes(operable: Operable): RenderGroup { operable.store(); - return Core.getRenderGroupManager() - .nextRenderGroup(true, true); + return Core.getRenderGroupManager().nextRenderGroup(true, true); } diff --git a/src/bundles/csg/index.ts b/src/bundles/csg/index.ts index 8f58faaed4..98c0626c67 100644 --- a/src/bundles/csg/index.ts +++ b/src/bundles/csg/index.ts @@ -61,7 +61,7 @@ import { Core } from './core.js'; import { CsgModuleState } from './utilities.js'; /* [Main] */ -let moduleState = new CsgModuleState(); +const moduleState = new CsgModuleState(); context.moduleContexts.csg.state = moduleState; // We initialise Core for the first time over on the bundles' end here diff --git a/src/bundles/csg/input_tracker.ts b/src/bundles/csg/input_tracker.ts index 1a67a8aecf..8cbb1f7b0b 100644 --- a/src/bundles/csg/input_tracker.ts +++ b/src/bundles/csg/input_tracker.ts @@ -7,13 +7,9 @@ import { rotate, updateProjection, updateStates, - zoomToFit, + zoomToFit } from './jscad/renderer.js'; -import type { - ControlsState, - GeometryEntity, - PerspectiveCameraState, -} from './jscad/types.js'; +import type { ControlsState, GeometryEntity, PerspectiveCameraState } from './jscad/types.js'; import ListenerTracker from './listener_tracker.js'; /* [Main] */ @@ -26,7 +22,7 @@ enum MousePointer { FORWARD = 4, NONE = -1, - OTHER = 7050, + OTHER = 7050 } /* [Exports] */ @@ -41,11 +37,15 @@ export default class InputTracker { private heldPointer: MousePointer = MousePointer.NONE; private lastX: number | null = null; + private lastY: number | null = null; private rotateX: number = 0; + private rotateY: number = 0; + private panX: number = 0; + private panY: number = 0; private listenerTracker: ListenerTracker; @@ -57,7 +57,7 @@ export default class InputTracker { constructor( private canvas: HTMLCanvasElement, private cameraState: PerspectiveCameraState, - private geometryEntities: GeometryEntity[], + private geometryEntities: GeometryEntity[] ) { this.listenerTracker = new ListenerTracker(canvas); } @@ -91,8 +91,8 @@ export default class InputTracker { private isPointerPan(isShiftKey: boolean): boolean { return ( - this.heldPointer === MousePointer.MIDDLE - || (this.heldPointer === MousePointer.LEFT && isShiftKey) + this.heldPointer === MousePointer.MIDDLE || + (this.heldPointer === MousePointer.LEFT && isShiftKey) ); } @@ -102,13 +102,13 @@ export default class InputTracker { } private tryDynamicResize() { - let { width: oldWidth, height: oldHeight } = this.canvas; + const { width: oldWidth, height: oldHeight } = this.canvas; // Account for display scaling - let canvasBounds: DOMRect = this.canvas.getBoundingClientRect(); - let { devicePixelRatio } = window; - let newWidth: number = Math.floor(canvasBounds.width * devicePixelRatio); - let newHeight: number = Math.floor(canvasBounds.height * devicePixelRatio); + const canvasBounds: DOMRect = this.canvas.getBoundingClientRect(); + const { devicePixelRatio } = window; + const newWidth: number = Math.floor(canvasBounds.width * devicePixelRatio); + const newHeight: number = Math.floor(canvasBounds.height * devicePixelRatio); if (oldWidth === newWidth && oldHeight === newHeight) return; this.frameDirty = true; @@ -132,18 +132,17 @@ export default class InputTracker { if (this.zoomTicks === 0) return; while (this.zoomTicks !== 0) { - let currentTick: number = Math.sign(this.zoomTicks); + const currentTick: number = Math.sign(this.zoomTicks); this.zoomTicks -= currentTick; - let scaledChange: number = currentTick * ZOOM_TICK_SCALE; - let potentialNewScale: number = this.controlsState.scale + scaledChange; - let potentialNewDistance: number - = vec3.distance(this.cameraState.position, this.cameraState.target) - * potentialNewScale; + const scaledChange: number = currentTick * ZOOM_TICK_SCALE; + const potentialNewScale: number = this.controlsState.scale + scaledChange; + const potentialNewDistance: number = + vec3.distance(this.cameraState.position, this.cameraState.target) * potentialNewScale; if ( - potentialNewDistance > this.controlsState.limits.minDistance - && potentialNewDistance < this.controlsState.limits.maxDistance + potentialNewDistance > this.controlsState.limits.minDistance && + potentialNewDistance < this.controlsState.limits.maxDistance ) { this.frameDirty = true; this.controlsState.scale = potentialNewScale; @@ -187,7 +186,7 @@ export default class InputTracker { this.changeZoomTicks(wheelEvent.deltaY); }, // Force wait for our potential preventDefault() - { passive: false }, + { passive: false } ); this.listenerTracker.addListener( @@ -204,59 +203,53 @@ export default class InputTracker { this.canvas.setPointerCapture(pointerEvent.pointerId); }, // Force wait for our potential preventDefault() - { passive: false }, + { passive: false } ); - this.listenerTracker.addListener( - 'pointerup', - (pointerEvent: PointerEvent) => { - this.unsetHeldPointer(); - this.unsetLastCoordinates(); + this.listenerTracker.addListener('pointerup', (pointerEvent: PointerEvent) => { + this.unsetHeldPointer(); + this.unsetLastCoordinates(); - this.canvas.releasePointerCapture(pointerEvent.pointerId); - }, - ); + this.canvas.releasePointerCapture(pointerEvent.pointerId); + }); - this.listenerTracker.addListener( - 'pointermove', - (pointerEvent: PointerEvent) => { - if (this.shouldIgnorePointerMove()) return; - - let currentX = pointerEvent.pageX; - let currentY = pointerEvent.pageY; - - if (this.lastX !== null && this.lastY !== null) { - // If tracked before, use differences to react to input - let differenceX = this.lastX - currentX; - let differenceY = this.lastY - currentY; - - if (this.isPointerPan(pointerEvent.shiftKey)) { - // Drag right (X increases) - // Camera right (still +) - // Viewport left (invert to -) - this.panX += differenceX; - - // Drag down (Y increases) - // Camera down (invert to -) - // Viewport up (still -) - this.panY -= differenceY; - } else { - // Else default to rotate - - // Drag right (X increases) - // Camera angle from origin left (invert to -) - this.rotateX -= differenceX; - - // Drag down (Y increases) - // Camera angle from origin up (still +) - this.rotateY += differenceY; - } + this.listenerTracker.addListener('pointermove', (pointerEvent: PointerEvent) => { + if (this.shouldIgnorePointerMove()) return; + + const currentX = pointerEvent.pageX; + const currentY = pointerEvent.pageY; + + if (this.lastX !== null && this.lastY !== null) { + // If tracked before, use differences to react to input + const differenceX = this.lastX - currentX; + const differenceY = this.lastY - currentY; + + if (this.isPointerPan(pointerEvent.shiftKey)) { + // Drag right (X increases) + // Camera right (still +) + // Viewport left (invert to -) + this.panX += differenceX; + + // Drag down (Y increases) + // Camera down (invert to -) + // Viewport up (still -) + this.panY -= differenceY; + } else { + // Else default to rotate + + // Drag right (X increases) + // Camera angle from origin left (invert to -) + this.rotateX -= differenceX; + + // Drag down (Y increases) + // Camera angle from origin up (still +) + this.rotateY += differenceY; } + } - this.lastX = currentX; - this.lastY = currentY; - }, - ); + this.lastX = currentX; + this.lastY = currentY; + }); } removeListeners() { diff --git a/src/bundles/csg/jscad/renderer.ts b/src/bundles/csg/jscad/renderer.ts index 5742f69aec..13568c36d2 100644 --- a/src/bundles/csg/jscad/renderer.ts +++ b/src/bundles/csg/jscad/renderer.ts @@ -5,8 +5,13 @@ import { controls, drawCommands, entitiesFromSolids, - prepareRender, + prepareRender } from '@jscad/regl-renderer'; +import { + ACE_GUTTER_BACKGROUND_COLOR, + ACE_GUTTER_TEXT_COLOR, + BP_TEXT_COLOR +} from '../../../tabs/common/css_constants.js'; import { DEFAULT_COLOR, GRID_PADDING, @@ -15,7 +20,7 @@ import { ROUND_UP_INTERVAL, SUB_TICKS, X_FACTOR, - Y_FACTOR, + Y_FACTOR } from '../constants.js'; import { hexToAlphaColor, type RenderGroup, type Shape } from '../utilities.js'; import type { @@ -34,29 +39,22 @@ import type { UpdatedStates, WrappedRenderer, WrappedRendererData, - ZoomToFitStates, + ZoomToFitStates } from './types.js'; -import { ACE_GUTTER_BACKGROUND_COLOR, ACE_GUTTER_TEXT_COLOR, BP_TEXT_COLOR } from '../../../tabs/common/css_constants.js'; - - /* [Main] */ -let { orbit } = controls; +const { orbit } = controls; function solidsToGeometryEntities(solids: Solid[]): GeometryEntity[] { - let options: EntitiesFromSolidsOptions = { - color: hexToAlphaColor(DEFAULT_COLOR), + const options: EntitiesFromSolidsOptions = { + color: hexToAlphaColor(DEFAULT_COLOR) }; - return (entitiesFromSolids( - options, - ...solids, - ) as unknown) as GeometryEntity[]; + return entitiesFromSolids(options, ...solids) as unknown as GeometryEntity[]; } function neatGridDistance(rawDistance: number) { - let paddedDistance: number = rawDistance + GRID_PADDING; - let roundedDistance: number - = Math.ceil(paddedDistance / ROUND_UP_INTERVAL) * ROUND_UP_INTERVAL; + const paddedDistance: number = rawDistance + GRID_PADDING; + const roundedDistance: number = Math.ceil(paddedDistance / ROUND_UP_INTERVAL) * ROUND_UP_INTERVAL; return roundedDistance; } @@ -68,12 +66,12 @@ class MultiGridEntity implements MultiGridEntityType { color?: AlphaColor; subColor?: AlphaColor; } = { - drawCmd: 'drawGrid', - show: true, + drawCmd: 'drawGrid', + show: true, - color: hexToAlphaColor(BP_TEXT_COLOR), - subColor: hexToAlphaColor(ACE_GUTTER_TEXT_COLOR), - }; + color: hexToAlphaColor(BP_TEXT_COLOR), + subColor: hexToAlphaColor(ACE_GUTTER_TEXT_COLOR) + }; ticks: [number, number] = [MAIN_TICKS, SUB_TICKS]; @@ -89,41 +87,36 @@ class AxisEntity implements AxisEntityType { drawCmd: 'drawAxis'; show: boolean; } = { - drawCmd: 'drawAxis', - show: true, - }; + drawCmd: 'drawAxis', + show: true + }; alwaysVisible: boolean = false; constructor(public size?: number) {} } -function makeExtraEntities( - renderGroup: RenderGroup, - solids: Solid[], -): Entity[] { - let { hasGrid, hasAxis } = renderGroup; +function makeExtraEntities(renderGroup: RenderGroup, solids: Solid[]): Entity[] { + const { hasGrid, hasAxis } = renderGroup; // Run calculations for grid and/or axis only if needed if (!(hasAxis || hasGrid)) return []; - let boundingBoxes: BoundingBox[] = solids.map( - (solid: Solid): BoundingBox => measureBoundingBox(solid), - ); - let minMaxXys: number[][] = boundingBoxes.map( - (boundingBox: BoundingBox): number[] => { - let minX = boundingBox[0][0]; - let minY = boundingBox[0][1]; - let maxX = boundingBox[1][0]; - let maxY = boundingBox[1][1]; - return [minX, minY, maxX, maxY]; - }, + const boundingBoxes: BoundingBox[] = solids.map( + (solid: Solid): BoundingBox => measureBoundingBox(solid) ); - let xys: number[] = minMaxXys.flat(1); - let distancesFromOrigin: number[] = xys.map(Math.abs); - let furthestDistance: number = Math.max(...distancesFromOrigin); - let neatDistance: number = neatGridDistance(furthestDistance); + const minMaxXys: number[][] = boundingBoxes.map((boundingBox: BoundingBox): number[] => { + const minX = boundingBox[0][0]; + const minY = boundingBox[0][1]; + const maxX = boundingBox[1][0]; + const maxY = boundingBox[1][1]; + return [minX, minY, maxX, maxY]; + }); + const xys: number[] = minMaxXys.flat(1); + const distancesFromOrigin: number[] = xys.map(Math.abs); + const furthestDistance: number = Math.max(...distancesFromOrigin); + const neatDistance: number = neatGridDistance(furthestDistance); - let extraEntities: Entity[] = []; + const extraEntities: Entity[] = []; if (hasGrid) extraEntities.push(new MultiGridEntity(neatDistance * 2)); if (hasAxis) extraEntities.push(new AxisEntity(neatDistance)); return extraEntities; @@ -132,14 +125,12 @@ function makeExtraEntities( /* [Exports] */ export function makeWrappedRendererData( renderGroup: RenderGroup, - cameraState: PerspectiveCameraState, + cameraState: PerspectiveCameraState ): WrappedRendererData { - let solids: Solid[] = renderGroup.shapes.map( - (shape: Shape): Solid => shape.solid, - ); - let geometryEntities: GeometryEntity[] = solidsToGeometryEntities(solids); - let extraEntities: Entity[] = makeExtraEntities(renderGroup, solids); - let allEntities: Entity[] = [...geometryEntities, ...extraEntities]; + const solids: Solid[] = renderGroup.shapes.map((shape: Shape): Solid => shape.solid); + const geometryEntities: GeometryEntity[] = solidsToGeometryEntities(solids); + const extraEntities: Entity[] = makeExtraEntities(renderGroup, solids); + const allEntities: Entity[] = [...geometryEntities, ...extraEntities]; return { entities: allEntities, @@ -148,19 +139,17 @@ export function makeWrappedRendererData( camera: cameraState, rendering: { - background: hexToAlphaColor(ACE_GUTTER_BACKGROUND_COLOR), + background: hexToAlphaColor(ACE_GUTTER_BACKGROUND_COLOR) }, - drawCommands, + drawCommands }; } -export function makeWrappedRenderer( - canvas: HTMLCanvasElement, -): WrappedRenderer { +export function makeWrappedRenderer(canvas: HTMLCanvasElement): WrappedRenderer { return prepareRender({ // Used to initialise Regl from the REGL package constructor - glOptions: { canvas }, + glOptions: { canvas } }); } @@ -174,24 +163,21 @@ export function cloneControlsState(): ControlsState { export function updateProjection( cameraState: PerspectiveCameraState, width: number, - height: number, + height: number ) { // Modify the projection, aspect ratio & viewport. As compared to the general // controls.orbit.update() or even cameras.perspective.update() cameras.perspective.setProjection(cameraState, cameraState, { width, - height, + height }); } -export function updateStates( - cameraState: PerspectiveCameraState, - controlsState: ControlsState, -) { - let states: UpdatedStates = (orbit.update({ +export function updateStates(cameraState: PerspectiveCameraState, controlsState: ControlsState) { + const states: UpdatedStates = orbit.update({ camera: cameraState, - controls: controlsState, - }) as unknown) as UpdatedStates; + controls: controlsState + }) as unknown as UpdatedStates; cameraState.position = states.camera.position; cameraState.view = states.camera.view; @@ -204,13 +190,13 @@ export function updateStates( export function zoomToFit( cameraState: PerspectiveCameraState, controlsState: ControlsState, - geometryEntities: GeometryEntity[], + geometryEntities: GeometryEntity[] ) { - let states: ZoomToFitStates = (orbit.zoomToFit({ + const states: ZoomToFitStates = orbit.zoomToFit({ camera: cameraState, controls: controlsState, - entities: geometryEntities as any, - }) as unknown) as ZoomToFitStates; + entities: geometryEntities as any + }) as unknown as ZoomToFitStates; cameraState.target = states.camera.target; @@ -221,16 +207,16 @@ export function rotate( cameraState: PerspectiveCameraState, controlsState: ControlsState, rotateX: number, - rotateY: number, + rotateY: number ) { - let states: RotateStates = (orbit.rotate( + const states: RotateStates = orbit.rotate( { camera: cameraState, controls: controlsState, - speed: ROTATION_SPEED, + speed: ROTATION_SPEED }, - [rotateX, rotateY], - ) as unknown) as RotateStates; + [rotateX, rotateY] + ) as unknown as RotateStates; controlsState.thetaDelta = states.controls.thetaDelta; controlsState.phiDelta = states.controls.phiDelta; @@ -240,15 +226,15 @@ export function pan( cameraState: PerspectiveCameraState, controlsState: ControlsState, panX: number, - panY: number, + panY: number ) { - let states: PanStates = (orbit.pan( + const states: PanStates = orbit.pan( { camera: cameraState, - controls: controlsState, + controls: controlsState }, - [panX * X_FACTOR, panY * Y_FACTOR], - ) as unknown) as PanStates; + [panX * X_FACTOR, panY * Y_FACTOR] + ) as unknown as PanStates; cameraState.position = states.camera.position; cameraState.target = states.camera.target; diff --git a/src/bundles/csg/jscad/types.ts b/src/bundles/csg/jscad/types.ts index 010a221cc2..de9275e2d3 100644 --- a/src/bundles/csg/jscad/types.ts +++ b/src/bundles/csg/jscad/types.ts @@ -1,11 +1,11 @@ /* [Import] */ import type { RGB, RGBA } from '@jscad/modeling/src/colors/types.js'; import type { Geom3 } from '@jscad/modeling/src/geometries/types.js'; -import { type cameras, type drawCommands, controls } from '@jscad/regl-renderer'; +import { controls, type cameras, type drawCommands } from '@jscad/regl-renderer'; import type makeDrawMultiGrid from '@jscad/regl-renderer/types/rendering/commands/drawGrid/multi'; /* [Main] */ -let { orbit } = controls; +const { orbit } = controls; /* [Exports] */ export type Color = RGB; @@ -158,7 +158,7 @@ export type DrawCommandMakers = Record; export type Mat4 = Float32Array; export type PerspectiveCameraState = Omit< typeof cameras.perspective.cameraState, -'target' | 'position' | 'view' + 'target' | 'position' | 'view' > & { target: Coordinates; @@ -207,16 +207,13 @@ export type WrappedRenderer = (data: WrappedRendererData) => void; /* (Not exhaustive, only defines well the important properties we need) */ -export type ControlsState = Omit< - typeof orbit.controlsState, -'scale' | 'thetaDelta' | 'phiDelta' -> & +export type ControlsState = Omit & typeof orbit.controlsProps & { - scale: number; + scale: number; - thetaDelta: number; - phiDelta: number; -}; + thetaDelta: number; + phiDelta: number; + }; export type Solid = Geom3; diff --git a/src/bundles/csg/listener_tracker.ts b/src/bundles/csg/listener_tracker.ts index 79539fc99b..e09cc00c10 100644 --- a/src/bundles/csg/listener_tracker.ts +++ b/src/bundles/csg/listener_tracker.ts @@ -4,25 +4,18 @@ export default class ListenerTracker { constructor(private element: Element) {} - addListener( - eventType: string, - listener: Function, - options?: AddEventListenerOptions, - ) { + addListener(eventType: string, listener: Function, options?: AddEventListenerOptions) { this.listeners.push([eventType, listener]); this.element.addEventListener( eventType, listener as EventListenerOrEventListenerObject, - options, + options ); } removeListeners() { this.listeners.forEach(([eventType, listener]) => { - this.element.removeEventListener( - eventType, - listener as EventListenerOrEventListenerObject, - ); + this.element.removeEventListener(eventType, listener as EventListenerOrEventListenerObject); }); } } diff --git a/src/bundles/csg/stateful_renderer.ts b/src/bundles/csg/stateful_renderer.ts index ff7bef15a5..f721ff5f27 100644 --- a/src/bundles/csg/stateful_renderer.ts +++ b/src/bundles/csg/stateful_renderer.ts @@ -3,13 +3,13 @@ import InputTracker from './input_tracker.js'; import { cloneCameraState, makeWrappedRenderer, - makeWrappedRendererData, + makeWrappedRendererData } from './jscad/renderer.js'; import type { Entity, PerspectiveCameraState, WrappedRenderer, - WrappedRendererData, + WrappedRendererData } from './jscad/types.js'; import ListenerTracker from './listener_tracker.js'; import type { RenderGroup } from './utilities.js'; @@ -17,6 +17,7 @@ import type { RenderGroup } from './utilities.js'; /* [Exports] */ export default class StatefulRenderer { private isStarted: boolean = false; + private currentRequestId: number | null = null; private cameraState: PerspectiveCameraState = cloneCameraState(); @@ -33,38 +34,32 @@ export default class StatefulRenderer { private componentNumber: number, private loseCallback: Function, - private restoreCallback: Function, + private restoreCallback: Function ) { this.cameraState.position = [1000, 1000, 1500]; this.webGlListenerTracker = new ListenerTracker(canvas); - this.wrappedRendererData = makeWrappedRendererData( - renderGroup, - this.cameraState, - ); + this.wrappedRendererData = makeWrappedRendererData(renderGroup, this.cameraState); this.inputTracker = new InputTracker( canvas, this.cameraState, - this.wrappedRendererData.geometryEntities, + this.wrappedRendererData.geometryEntities ); } private addWebGlListeners() { - this.webGlListenerTracker.addListener( - 'webglcontextlost', - (contextEvent: WebGLContextEvent) => { - // Allow restoration of context - contextEvent.preventDefault(); + this.webGlListenerTracker.addListener('webglcontextlost', (contextEvent: WebGLContextEvent) => { + // Allow restoration of context + contextEvent.preventDefault(); - console.debug(`>>> CONTEXT LOST FOR #${this.componentNumber}`); + console.debug(`>>> CONTEXT LOST FOR #${this.componentNumber}`); - this.loseCallback(); + this.loseCallback(); - this.stop(); - }, - ); + this.stop(); + }); this.webGlListenerTracker.addListener( 'webglcontextrestored', @@ -74,7 +69,7 @@ export default class StatefulRenderer { this.start(); this.restoreCallback(); - }, + } ); } @@ -100,14 +95,12 @@ export default class StatefulRenderer { // Creating the WrappedRenderer already involves REGL. Losing WebGL context // requires repeating this step (ie, with each start()) - let wrappedRenderer: WrappedRenderer = makeWrappedRenderer(this.canvas); + const wrappedRenderer: WrappedRenderer = makeWrappedRenderer(this.canvas); if (firstStart) this.addWebGlListeners(); this.inputTracker.addListeners(); - let frameCallback: FrameRequestCallback = ( - _timestamp: DOMHighResTimeStamp, - ) => { + const frameCallback: FrameRequestCallback = (_timestamp: DOMHighResTimeStamp) => { this.inputTracker.respondToInput(); if (this.inputTracker.frameDirty) { diff --git a/src/bundles/csg/types.ts b/src/bundles/csg/types.ts index b2cf0d266b..75284cc5de 100644 --- a/src/bundles/csg/types.ts +++ b/src/bundles/csg/types.ts @@ -1,19 +1,15 @@ /* [Imports] */ import type { RGB, RGBA } from '@jscad/modeling/src/colors'; import type { Geom3 } from '@jscad/modeling/src/geometries/types'; -import { - cameras, - controls as _controls, - type drawCommands, -} from '@jscad/regl-renderer'; +import { controls as _controls, cameras, type drawCommands } from '@jscad/regl-renderer'; import type makeDrawMultiGrid from '@jscad/regl-renderer/types/rendering/commands/drawGrid/multi'; import type { InitializationOptions } from 'regl'; /* [Main] */ -let orthographicCamera = cameras.orthographic; -let perspectiveCamera = cameras.perspective; +const orthographicCamera = cameras.orthographic; +const perspectiveCamera = cameras.perspective; -let controls = _controls.orbit; +const controls = _controls.orbit; /* [Exports] */ @@ -34,7 +30,7 @@ export type OrthographicCamera = typeof orthographicCamera; export type PerspectiveCameraState = Omit< typeof perspectiveCamera.cameraState, -'target' | 'position' | 'view' + 'target' | 'position' | 'view' > & { target: CoordinatesXYZ; @@ -45,10 +41,7 @@ export type OrthographicCameraState = typeof orthographicCamera.cameraState; export type CameraState = PerspectiveCameraState | OrthographicCameraState; // @jscad\regl-renderer\src\controls\orbitControls.js -export type Controls = Omit< - typeof controls, -'update' | 'zoomToFit' | 'rotate' | 'pan' -> & { +export type Controls = Omit & { update: ControlsUpdate.Function; zoomToFit: ControlsZoomToFit.Function; rotate: ControlsRotate; @@ -124,14 +117,14 @@ export type ControlsPan = ( export type ControlsState = Omit< typeof controls.controlsState, -'scale' | 'thetaDelta' | 'phiDelta' + 'scale' | 'thetaDelta' | 'phiDelta' > & typeof controls.controlsProps & { - scale: number; + scale: number; - thetaDelta: number; - phiDelta: number; -}; + thetaDelta: number; + phiDelta: number; + }; export type Solid = Geom3; @@ -326,18 +319,13 @@ export namespace WrappedRenderer { // drawExps appears abandoned. // Only once passed Regl & an Entity do they return an actual DrawCommand export type PrepareDrawCommands = Record; - export type PrepareDrawCommandFunction = - | typeof drawCommands - | typeof makeDrawMultiGrid; + export type PrepareDrawCommandFunction = typeof drawCommands | typeof makeDrawMultiGrid; } // @jscad\regl-renderer\src\geometry-utils-V2\entitiesFromSolids.js // Converts Solids into Geometries and then into Entities export namespace EntitiesFromSolids { - export type Function = ( - options?: Options, - ...solids: Solid[] - ) => GeometryEntity[]; + export type Function = (options?: Options, ...solids: Solid[]) => GeometryEntity[]; export type Options = { // Default colour for entity rendering if the solid does not have one diff --git a/src/bundles/csg/utilities.ts b/src/bundles/csg/utilities.ts index 82f4a487d7..79023b2331 100644 --- a/src/bundles/csg/utilities.ts +++ b/src/bundles/csg/utilities.ts @@ -1,20 +1,16 @@ /* [Imports] */ -import { - transform as _transform, -} from '@jscad/modeling/src/geometries/geom3'; +import { transform as _transform } from '@jscad/modeling/src/geometries/geom3'; import mat4, { type Mat4 } from '@jscad/modeling/src/maths/mat4'; import { center as _center, rotate as _rotate, scale as _scale, - translate as _translate, + translate as _translate } from '@jscad/modeling/src/operations/transforms'; import type { ReplResult } from '../../typings/type_helpers.js'; import { Core } from './core.js'; import type { AlphaColor, Color, Solid } from './jscad/types.js'; - - /* [Exports] */ export interface Operable { applyTransforms: (newTransforms: Mat4) => Operable; @@ -28,31 +24,21 @@ export interface Operable { export class Group implements Operable, ReplResult { children: Operable[]; - constructor( - _children: Operable[], - public transforms: Mat4 = mat4.create(), - ) { + constructor(_children: Operable[], public transforms: Mat4 = mat4.create()) { // Duplicate the array to avoid modifying the original, maintaining // stateless Operables for the user this.children = [..._children]; } applyTransforms(newTransforms: Mat4): Operable { - let appliedTransforms: Mat4 = mat4.multiply( - mat4.create(), - newTransforms, - this.transforms, - ); + const appliedTransforms: Mat4 = mat4.multiply(mat4.create(), newTransforms, this.transforms); // Return a new object for statelessness - return new Group( - this.children, - appliedTransforms, - ); + return new Group(this.children, appliedTransforms); } store(newTransforms: Mat4 = mat4.create()): void { - let appliedGroup: Group = this.applyTransforms(newTransforms) as Group; + const appliedGroup: Group = this.applyTransforms(newTransforms) as Group; this.children.forEach((child: Operable) => { child.store(appliedGroup.transforms); @@ -62,37 +48,29 @@ export class Group implements Operable, ReplResult { translate(offsets: [number, number, number]): Group { return new Group( this.children, - mat4.multiply( - mat4.create(), - mat4.fromTranslation(mat4.create(), offsets), - this.transforms, - ), + mat4.multiply(mat4.create(), mat4.fromTranslation(mat4.create(), offsets), this.transforms) ); } rotate(angles: [number, number, number]): Group { - let yaw = angles[2]; - let pitch = angles[1]; - let roll = angles[0]; + const yaw = angles[2]; + const pitch = angles[1]; + const roll = angles[0]; return new Group( this.children, mat4.multiply( mat4.create(), mat4.fromTaitBryanRotation(mat4.create(), yaw, pitch, roll), - this.transforms, - ), + this.transforms + ) ); } scale(factors: [number, number, number]): Group { return new Group( this.children, - mat4.multiply( - mat4.create(), - mat4.fromScaling(mat4.create(), factors), - this.transforms, - ), + mat4.multiply(mat4.create(), mat4.fromScaling(mat4.create(), factors), this.transforms) ); } @@ -103,9 +81,7 @@ export class Group implements Operable, ReplResult { ungroup(): Operable[] { // Return all children, but we need to account for this Group's unresolved // transforms by applying them to each child - return this.children.map( - (child: Operable) => child.applyTransforms(this.transforms), - ); + return this.children.map((child: Operable) => child.applyTransforms(this.transforms)); } } @@ -118,10 +94,7 @@ export class Shape implements Operable, ReplResult { } store(newTransforms: Mat4 = mat4.create()): void { - Core.getRenderGroupManager() - .storeShape( - this.applyTransforms(newTransforms) as Shape, - ); + Core.getRenderGroupManager().storeShape(this.applyTransforms(newTransforms) as Shape); } translate(offsets: [number, number, number]): Shape { @@ -143,7 +116,9 @@ export class Shape implements Operable, ReplResult { export class RenderGroup implements ReplResult { render: boolean = false; + hasGrid: boolean = true; + hasAxis: boolean = true; shapes: Shape[] = []; @@ -157,6 +132,7 @@ export class RenderGroup implements ReplResult { export class RenderGroupManager { private canvasTracker: number = 1; + private renderGroups: RenderGroup[] = []; constructor() { @@ -173,11 +149,8 @@ export class RenderGroupManager { } // Returns the old render group - nextRenderGroup( - oldHasGrid: boolean = false, - oldHasAxis: boolean = false, - ): RenderGroup { - let oldRenderGroup: RenderGroup = this.getCurrentRenderGroup(); + nextRenderGroup(oldHasGrid: boolean = false, oldHasAxis: boolean = false): RenderGroup { + const oldRenderGroup: RenderGroup = this.getCurrentRenderGroup(); oldRenderGroup.render = true; oldRenderGroup.hasGrid = oldHasGrid; oldRenderGroup.hasAxis = oldHasAxis; @@ -196,9 +169,7 @@ export class RenderGroupManager { } getGroupsToRender(): RenderGroup[] { - return this.renderGroups.filter( - (renderGroup: RenderGroup) => renderGroup.render, - ); + return this.renderGroups.filter((renderGroup: RenderGroup) => renderGroup.render); } } @@ -219,34 +190,29 @@ export class CsgModuleState { export function centerPrimitive(shape: Shape) { // Move centre of Shape to 0.5, 0.5, 0.5 - let solid: Solid = _center( + const solid: Solid = _center( { - relativeTo: [0.5, 0.5, 0.5], + relativeTo: [0.5, 0.5, 0.5] }, - shape.solid, + shape.solid ); return new Shape(solid); } export function hexToColor(hex: string): Color { - let regex: RegExp - = /^#?(?[\da-f]{2})(?[\da-f]{2})(?[\da-f]{2})$/iu; - let potentialGroups: { [key: string]: string } | undefined - = hex.match(regex)?.groups; + const regex: RegExp = /^#?(?[\da-f]{2})(?[\da-f]{2})(?[\da-f]{2})$/iu; + const potentialGroups: { [key: string]: string } | undefined = hex.match(regex)?.groups; if (potentialGroups === undefined) return [0, 0, 0]; - let groups: { [key: string]: string } = potentialGroups; + const groups: { [key: string]: string } = potentialGroups; return [ parseInt(groups.red, 16) / 0xff, parseInt(groups.green, 16) / 0xff, - parseInt(groups.blue, 16) / 0xff, + parseInt(groups.blue, 16) / 0xff ]; } -export function colorToAlphaColor( - color: Color, - opacity: number = 1, -): AlphaColor { +export function colorToAlphaColor(color: Color, opacity: number = 1): AlphaColor { return [...color, opacity]; } diff --git a/src/bundles/curve/__tests__/curve.ts b/src/bundles/curve/__tests__/curve.ts index 23c931dbe5..4d401868f5 100644 --- a/src/bundles/curve/__tests__/curve.ts +++ b/src/bundles/curve/__tests__/curve.ts @@ -1,21 +1,30 @@ -import { generateCurve, type Curve } from "../curves_webgl"; -import { animate_3D_curve, animate_curve, draw_3D_connected, draw_connected, make_point } from "../functions"; +import { generateCurve, type Curve } from '../curves_webgl'; +import { + animate_3D_curve, + animate_curve, + draw_3D_connected, + draw_connected, + make_point +} from '../functions'; function evalCurve(curve: Curve, numPoints: number) { - generateCurve('none', 'points', numPoints, curve, '2D', false) + generateCurve('none', 'points', numPoints, curve, '2D', false); } test('Ensure that invalid curves error gracefully', () => { - expect(() => evalCurve(t => 1 as any, 200)) - .toThrowErrorMatchingInlineSnapshot(`"Expected curve to return a point, got '1' at t=0"`); -}) + expect(() => evalCurve((t) => 1 as any, 200)).toThrowErrorMatchingInlineSnapshot( + `"Expected curve to return a point, got '1' at t=0"` + ); +}); test('Using 3D render functions with animate_curve should throw errors', () => { - expect(() => animate_curve(1, 60, draw_3D_connected(200), t0 => t1 => make_point(t0, t1))) - .toThrowErrorMatchingInlineSnapshot('"animate_curve cannot be used with 3D draw function!"') -}) + expect(() => + animate_curve(1, 60, draw_3D_connected(200), (t0) => (t1) => make_point(t0, t1)) + ).toThrowErrorMatchingInlineSnapshot('"animate_curve cannot be used with 3D draw function!"'); +}); test('Using 2D render functions with animate_3D_curve should throw errors', () => { - expect(() => animate_3D_curve(1, 60, draw_connected(200), t0 => t1 => make_point(t0, t1))) - .toThrowErrorMatchingInlineSnapshot('"animate_3D_curve cannot be used with 2D draw function!"') -}) + expect(() => + animate_3D_curve(1, 60, draw_connected(200), (t0) => (t1) => make_point(t0, t1)) + ).toThrowErrorMatchingInlineSnapshot('"animate_3D_curve cannot be used with 2D draw function!"'); +}); diff --git a/src/bundles/curve/curves_webgl.ts b/src/bundles/curve/curves_webgl.ts index b15f56bec0..a7c7f2a2d6 100644 --- a/src/bundles/curve/curves_webgl.ts +++ b/src/bundles/curve/curves_webgl.ts @@ -46,11 +46,7 @@ void main() { * @param source - source code of the shader * @returns WebGLShader used to initialize shader program */ -function loadShader( - gl: WebGLRenderingContext, - type: number, - source: string, -): WebGLShader { +function loadShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader { const shader = gl.createShader(type); if (!shader) { throw new Error('WebGLShader not available.'); @@ -71,7 +67,7 @@ function loadShader( function initShaderProgram( gl: WebGLRenderingContext, vsSource: string, - fsSource: string, + fsSource: string ): WebGLProgram { const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource); const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource); @@ -120,7 +116,7 @@ export class Point implements ReplResult { public readonly x: number, public readonly y: number, public readonly z: number, - public readonly color: Color, + public readonly color: Color ) {} public toReplString = () => `(${this.x}, ${this.y}, ${this.z}, Color: ${this.color})`; @@ -143,7 +139,7 @@ export class CurveDrawn implements ReplResult { private readonly space: CurveSpace, private readonly drawCubeArray: number[], private readonly curvePosArray: number[], - private readonly curveColorArray: number[], + private readonly curveColorArray: number[] ) { this.renderingContext = null; this.programs = null; @@ -160,66 +156,48 @@ export class CurveDrawn implements ReplResult { throw new Error('Rendering context cannot be null.'); } const cubeBuffer = this.renderingContext.createBuffer(); - this.renderingContext.bindBuffer( - this.renderingContext.ARRAY_BUFFER, - cubeBuffer, - ); + this.renderingContext.bindBuffer(this.renderingContext.ARRAY_BUFFER, cubeBuffer); this.renderingContext.bufferData( this.renderingContext.ARRAY_BUFFER, new Float32Array(this.drawCubeArray), - this.renderingContext.STATIC_DRAW, + this.renderingContext.STATIC_DRAW ); const curveBuffer = this.renderingContext.createBuffer(); - this.renderingContext.bindBuffer( - this.renderingContext.ARRAY_BUFFER, - curveBuffer, - ); + this.renderingContext.bindBuffer(this.renderingContext.ARRAY_BUFFER, curveBuffer); this.renderingContext.bufferData( this.renderingContext.ARRAY_BUFFER, new Float32Array(this.curvePosArray), - this.renderingContext.STATIC_DRAW, + this.renderingContext.STATIC_DRAW ); const curveColorBuffer = this.renderingContext.createBuffer(); - this.renderingContext.bindBuffer( - this.renderingContext.ARRAY_BUFFER, - curveColorBuffer, - ); + this.renderingContext.bindBuffer(this.renderingContext.ARRAY_BUFFER, curveColorBuffer); this.renderingContext.bufferData( this.renderingContext.ARRAY_BUFFER, new Float32Array(this.curveColorArray), - this.renderingContext.STATIC_DRAW, + this.renderingContext.STATIC_DRAW ); const shaderProgram = initShaderProgram(this.renderingContext, vsS, fsS); this.programs = { program: shaderProgram, attribLocations: { - vertexPosition: this.renderingContext.getAttribLocation( - shaderProgram, - 'aVertexPosition', - ), - vertexColor: this.renderingContext.getAttribLocation( - shaderProgram, - 'aFragColor', - ), + vertexPosition: this.renderingContext.getAttribLocation(shaderProgram, 'aVertexPosition'), + vertexColor: this.renderingContext.getAttribLocation(shaderProgram, 'aFragColor') }, uniformLocations: { projectionMatrix: this.renderingContext.getUniformLocation( shaderProgram, - 'uProjectionMatrix', + 'uProjectionMatrix' ), - modelViewMatrix: this.renderingContext.getUniformLocation( - shaderProgram, - 'uModelViewMatrix', - ), - }, + modelViewMatrix: this.renderingContext.getUniformLocation(shaderProgram, 'uModelViewMatrix') + } }; this.buffersInfo = { cubeBuffer, curveBuffer, - curveColorBuffer, + curveColorBuffer }; }; @@ -234,7 +212,7 @@ export class CurveDrawn implements ReplResult { gl.clearDepth(1.0); // Clear everything gl.enable(gl.DEPTH_TEST); // Enable depth testing gl.depthFunc(gl.LEQUAL); // Near things obscure far things - // eslint-disable-next-line no-bitwise + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); const transMat = mat4.create(); @@ -242,11 +220,7 @@ export class CurveDrawn implements ReplResult { if (this.space === '3D') { const padding = Math.sqrt(1 / 3.1); - mat4.scale( - transMat, - transMat, - vec3.fromValues(padding, padding, padding), - ); + mat4.scale(transMat, transMat, vec3.fromValues(padding, padding, padding)); mat4.translate(transMat, transMat, [0, 0, -5]); mat4.rotate(transMat, transMat, -(Math.PI / 2), [1, 0, 0]); // axis to rotate around X (static) mat4.rotate(transMat, transMat, angle, [0, 0, 1]); // axis to rotate around Z (dynamic) @@ -259,16 +233,8 @@ export class CurveDrawn implements ReplResult { } gl.useProgram(this.programs!.program); - gl.uniformMatrix4fv( - this.programs!.uniformLocations.projectionMatrix, - false, - projMat, - ); - gl.uniformMatrix4fv( - this.programs!.uniformLocations.modelViewMatrix, - false, - transMat, - ); + gl.uniformMatrix4fv(this.programs!.uniformLocations.projectionMatrix, false, projMat); + gl.uniformMatrix4fv(this.programs!.uniformLocations.modelViewMatrix, false, transMat); gl.enableVertexAttribArray(this.programs!.attribLocations.vertexPosition); gl.enableVertexAttribArray(this.programs!.attribLocations.vertexColor); @@ -281,7 +247,7 @@ export class CurveDrawn implements ReplResult { gl.FLOAT, false, 0, - 0, + 0 ); const colors: number[] = []; for (let i = 0; i < 16; i += 1) { @@ -301,7 +267,7 @@ export class CurveDrawn implements ReplResult { gl.FLOAT, false, 0, - 0, + 0 ); gl.bindBuffer(gl.ARRAY_BUFFER, this.buffersInfo!.curveColorBuffer); gl.vertexAttribPointer(0, 4, gl.FLOAT, false, 0, 0); @@ -313,14 +279,14 @@ export class CurveDrawn implements ReplResult { }; } -// eslint-disable-next-line complexity + export function generateCurve( scaleMode: ScaleMode, drawMode: DrawMode, numPoints: number, func: Curve, space: CurveSpace, - isFullView: boolean, + isFullView: boolean ) { const curvePosArray: number[] = []; const curveColorArray: number[] = []; @@ -338,7 +304,9 @@ export function generateCurve( const point = func(i / numPoints); if (!(point instanceof Point)) { - throw new Error(`Expected curve to return a point, got '${stringify(point)}' at t=${i / numPoints}`); + throw new Error( + `Expected curve to return a point, got '${stringify(point)}' at t=${i / numPoints}` + ); } const x = point.x * 2 - 1; @@ -388,11 +356,7 @@ export function generateCurve( } if (scaleMode === 'fit') { - const center = [ - (min_x + max_x) / 2, - (min_y + max_y) / 2, - (min_z + max_z) / 2, - ]; + const center = [(min_x + max_x) / 2, (min_y + max_y) / 2, (min_z + max_z) / 2]; let scale = Math.max(max_x - min_x, max_y - min_y, max_z - min_z); scale = scale === 0 ? 1 : scale; if (space === '3D') { @@ -420,11 +384,7 @@ export function generateCurve( } } } else if (scaleMode === 'stretch') { - const center = [ - (min_x + max_x) / 2, - (min_y + max_y) / 2, - (min_z + max_z) / 2, - ]; + const center = [(min_x + max_x) / 2, (min_y + max_y) / 2, (min_z + max_z) / 2]; const x_scale = max_x === min_x ? 1 : max_x - min_x; const y_scale = max_y === min_y ? 1 : max_y - min_y; const z_scale = max_z === min_z ? 1 : max_z - min_z; @@ -454,12 +414,5 @@ export function generateCurve( } } - return new CurveDrawn( - drawMode, - numPoints, - space, - drawCubeArray, - curvePosArray, - curveColorArray, - ); + return new CurveDrawn(drawMode, numPoints, space, drawCubeArray, curvePosArray, curveColorArray); } diff --git a/src/bundles/curve/functions.ts b/src/bundles/curve/functions.ts index dde6c6bddb..dd6c764d47 100644 --- a/src/bundles/curve/functions.ts +++ b/src/bundles/curve/functions.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import context from 'js-slang/context'; -import { type Curve, type CurveDrawn, generateCurve, Point } from './curves_webgl'; +import { Point, generateCurve, type Curve, type CurveDrawn } from './curves_webgl'; import { AnimatedCurve, type CurveAnimation, @@ -8,30 +8,23 @@ import { type CurveTransformer, type DrawMode, type RenderFunction, - type ScaleMode, + type ScaleMode } from './types'; const drawnCurves: (CurveDrawn | AnimatedCurve)[] = []; context.moduleContexts.curve.state = { - drawnCurves, + drawnCurves }; function createDrawFunction( scaleMode: ScaleMode, drawMode: DrawMode, space: CurveSpace, - isFullView: boolean, + isFullView: boolean ): (numPoints: number) => RenderFunction { return (numPoints: number) => { const func = (curve: Curve) => { - const curveDrawn = generateCurve( - scaleMode, - drawMode, - numPoints, - curve, - space, - isFullView, - ); + const curveDrawn = generateCurve(scaleMode, drawMode, numPoints, curve, space, isFullView); if (!curve.shouldNotAppend) { drawnCurves.push(curveDrawn); @@ -83,12 +76,7 @@ export const draw_connected = createDrawFunction('none', 'lines', '2D', false); * draw_connected_full_view(100)(t => make_point(t, t)); * ``` */ -export const draw_connected_full_view = createDrawFunction( - 'stretch', - 'lines', - '2D', - true, -); +export const draw_connected_full_view = createDrawFunction('stretch', 'lines', '2D', true); /** * Returns a function that turns a given Curve into a Drawing, by sampling the @@ -104,12 +92,7 @@ export const draw_connected_full_view = createDrawFunction( * draw_connected_full_view_proportional(100)(t => make_point(t, t)); * ``` */ -export const draw_connected_full_view_proportional = createDrawFunction( - 'fit', - 'lines', - '2D', - true, -); +export const draw_connected_full_view_proportional = createDrawFunction('fit', 'lines', '2D', true); /** * Returns a function that turns a given Curve into a Drawing, by sampling the @@ -142,12 +125,7 @@ export const draw_points = createDrawFunction('none', 'points', '2D', false); * draw_points_full_view(100)(t => make_point(t, t)); * ``` */ -export const draw_points_full_view = createDrawFunction( - 'stretch', - 'points', - '2D', - true, -); +export const draw_points_full_view = createDrawFunction('stretch', 'points', '2D', true); /** * Returns a function that turns a given Curve into a Drawing, by sampling the @@ -164,12 +142,7 @@ export const draw_points_full_view = createDrawFunction( * draw_points_full_view_proportional(100)(t => make_point(t, t)); * ``` */ -export const draw_points_full_view_proportional = createDrawFunction( - 'fit', - 'points', - '2D', - true, -); +export const draw_points_full_view_proportional = createDrawFunction('fit', 'points', '2D', true); /** * Returns a function that turns a given 3D Curve into a Drawing, by sampling @@ -185,12 +158,7 @@ export const draw_points_full_view_proportional = createDrawFunction( * draw_3D_connected(100)(t => make_3D_point(t, t, t)); * ``` */ -export const draw_3D_connected = createDrawFunction( - 'none', - 'lines', - '3D', - false, -); +export const draw_3D_connected = createDrawFunction('none', 'lines', '3D', false); /** * Returns a function that turns a given 3D Curve into a Drawing, by sampling @@ -206,12 +174,7 @@ export const draw_3D_connected = createDrawFunction( * draw_3D_connected_full_view(100)(t => make_3D_point(t, t, t)); * ``` */ -export const draw_3D_connected_full_view = createDrawFunction( - 'stretch', - 'lines', - '3D', - false, -); +export const draw_3D_connected_full_view = createDrawFunction('stretch', 'lines', '3D', false); /** * Returns a function that turns a given 3D Curve into a Drawing, by sampling @@ -231,7 +194,7 @@ export const draw_3D_connected_full_view_proportional = createDrawFunction( 'fit', 'lines', '3D', - false, + false ); /** @@ -264,12 +227,7 @@ export const draw_3D_points = createDrawFunction('none', 'points', '3D', false); * draw_3D_points_full_view(100)(t => make_3D_point(t, t, t)); * ``` */ -export const draw_3D_points_full_view = createDrawFunction( - 'stretch', - 'points', - '3D', - false, -); +export const draw_3D_points_full_view = createDrawFunction('stretch', 'points', '3D', false); /** * Returns a function that turns a given 3D Curve into a Drawing, by sampling @@ -289,7 +247,7 @@ export const draw_3D_points_full_view_proportional = createDrawFunction( 'fit', 'points', '3D', - false, + false ); /** @@ -339,13 +297,7 @@ export function make_3D_point(x: number, y: number, z: number): Point { * const redPoint = make_color_point(0.5, 0.5, 255, 0, 0); * ``` */ -export function make_color_point( - x: number, - y: number, - r: number, - g: number, - b: number, -): Point { +export function make_color_point(x: number, y: number, r: number, g: number, b: number): Point { return new Point(x, y, 0, [r / 255, g / 255, b / 255, 1]); } @@ -372,7 +324,7 @@ export function make_3D_color_point( z: number, r: number, g: number, - b: number, + b: number ): Point { return new Point(x, y, z, [r / 255, g / 255, b / 255, 1]); } @@ -492,11 +444,7 @@ export function invert(curve: Curve): Curve { * @param z0 z-value * @returns Curve transformation */ -export function translate( - x0: number, - y0: number, - z0: number, -): CurveTransformer { +export function translate(x0: number, y0: number, z0: number): CurveTransformer { return (curve: Curve) => { const transformation = (cf: Curve) => (t: number) => { const a = x0 === undefined ? 0 : x0; @@ -509,7 +457,7 @@ export function translate( c + z_of(ct), r_of(ct), g_of(ct), - b_of(ct), + b_of(ct) ); }; return transformation(curve); @@ -531,16 +479,12 @@ export function translate( export function rotate_around_origin( theta1: number, theta2: number, - theta3: number, + theta3: number ): CurveTransformer { if (theta3 === undefined && theta1 !== undefined && theta2 !== undefined) { // 2 args throw new Error('Expected 1 or 3 arguments, but received 2'); - } else if ( - theta1 !== undefined - && theta2 === undefined - && theta3 === undefined - ) { + } else if (theta1 !== undefined && theta2 === undefined && theta3 === undefined) { // 1 args const cth = Math.cos(theta1); const sth = Math.sin(theta1); @@ -556,7 +500,7 @@ export function rotate_around_origin( z, r_of(ct), g_of(ct), - b_of(ct), + b_of(ct) ); }; return transformation(curve); @@ -574,16 +518,18 @@ export function rotate_around_origin( const coord = [x_of(ct), y_of(ct), z_of(ct)]; const mat = [ [ + // Force prettier to indent cthz * cthy, cthz * sthy * sthx - sthz * cthx, - cthz * sthy * cthx + sthz * sthx, + cthz * sthy * cthx + sthz * sthx ], [ + // Force prettier to indent sthz * cthy, sthz * sthy * sthx + cthz * cthx, - sthz * sthy * cthx - cthz * sthx, + sthz * sthy * cthx - cthz * sthx ], - [-sthy, cthy * sthx, cthy * cthx], + [-sthy, cthy * sthx, cthy * cthx] ]; let xf = 0; let yf = 0; @@ -624,7 +570,7 @@ export function scale(a: number, b: number, c: number): CurveTransformer { c1 * z_of(ct), r_of(ct), g_of(ct), - b_of(ct), + b_of(ct) ); }; return transformation(curve); @@ -656,18 +602,10 @@ export function scale_proportional(s: number): CurveTransformer { */ export function put_in_standard_position(curve: Curve): Curve { const start_point = curve(0); - const curve_started_at_origin = translate( - -x_of(start_point), - -y_of(start_point), - 0, - )(curve); + const curve_started_at_origin = translate(-x_of(start_point), -y_of(start_point), 0)(curve); const new_end_point = curve_started_at_origin(1); const theta = Math.atan2(y_of(new_end_point), x_of(new_end_point)); - const curve_ended_at_x_axis = rotate_around_origin( - 0, - 0, - -theta, - )(curve_started_at_origin); + const curve_ended_at_x_axis = rotate_around_origin(0, 0, -theta)(curve_started_at_origin); const end_point_on_x_axis = x_of(curve_ended_at_x_axis(1)); return scale_proportional(1 / end_point_on_x_axis)(curve_ended_at_x_axis); } @@ -707,8 +645,8 @@ export function connect_ends(curve1: Curve, curve2: Curve): Curve { translate( x_of(endPointOfCurve1) - x_of(startPointOfCurve2), y_of(endPointOfCurve1) - y_of(startPointOfCurve2), - z_of(endPointOfCurve1) - z_of(startPointOfCurve2), - )(curve2), + z_of(endPointOfCurve1) - z_of(startPointOfCurve2) + )(curve2) ); } @@ -772,7 +710,7 @@ export function animate_curve( duration: number, fps: number, drawer: RenderFunction, - func: CurveAnimation, + func: CurveAnimation ): AnimatedCurve { if (drawer.is3D) { throw new Error('animate_curve cannot be used with 3D draw function!'); @@ -795,7 +733,7 @@ export function animate_3D_curve( duration: number, fps: number, drawer: RenderFunction, - func: CurveAnimation, + func: CurveAnimation ): AnimatedCurve { if (!drawer.is3D) { throw new Error('animate_3D_curve cannot be used with 2D draw function!'); diff --git a/src/bundles/curve/index.ts b/src/bundles/curve/index.ts index 4843333285..ceb3bff957 100644 --- a/src/bundles/curve/index.ts +++ b/src/bundles/curve/index.ts @@ -70,5 +70,5 @@ export { unit_line_at, x_of, y_of, - z_of, + z_of } from './functions'; diff --git a/src/bundles/curve/types.ts b/src/bundles/curve/types.ts index fca82eca3a..526066e4f5 100644 --- a/src/bundles/curve/types.ts +++ b/src/bundles/curve/types.ts @@ -3,7 +3,7 @@ import type { ReplResult } from '../../typings/type_helpers'; import type { Curve, CurveDrawn } from './curves_webgl'; export type CurveModuleState = { - drawnCurves: (CurveDrawn | AnimatedCurve)[] + drawnCurves: (CurveDrawn | AnimatedCurve)[]; }; /** A function that takes in CurveFunction and returns a tranformed CurveFunction. */ @@ -23,7 +23,7 @@ export type CurveAnimation = (t: number) => Curve; * a CurveFunction and returns a ShapeDrawn based on its specifications. */ export type RenderFunction = { - is3D: boolean + is3D: boolean; } & ((func: Curve) => CurveDrawn); export class AnimatedCurve extends glAnimation implements ReplResult { @@ -32,7 +32,7 @@ export class AnimatedCurve extends glAnimation implements ReplResult { fps: number, private readonly func: (timestamp: number) => Curve, private readonly drawer: RenderFunction, - public readonly is3D: boolean, + public readonly is3D: boolean ) { super(duration, fps); this.angle = 0; @@ -47,7 +47,7 @@ export class AnimatedCurve extends glAnimation implements ReplResult { draw: (canvas: HTMLCanvasElement) => { curveDrawn.init(canvas); curveDrawn.redraw(this.angle); - }, + } }; } diff --git a/src/bundles/game/functions.ts b/src/bundles/game/functions.ts index 6a3eae63c5..8d52bb87ae 100644 --- a/src/bundles/game/functions.ts +++ b/src/bundles/game/functions.ts @@ -14,19 +14,19 @@ * @author Gokul Rajiv */ -/* eslint-disable consistent-return, @typescript-eslint/default-param-last, @typescript-eslint/no-shadow, @typescript-eslint/no-unused-vars */ + import { + defaultGameParams, type GameObject, type ObjectConfig, type RawContainer, type RawGameElement, type RawGameObject, - type RawInputObject, - defaultGameParams, + type RawInputObject } from './types'; import context from 'js-slang/context'; -import { type List, head, tail, is_pair, accumulate } from 'js-slang/dist/stdlib/list'; +import { accumulate, head, is_pair, tail, type List } from 'js-slang/dist/stdlib/list'; if (!context.moduleContexts.game.state) { context.moduleContexts.game.state = defaultGameParams; @@ -38,13 +38,13 @@ const { preloadSpritesheetMap, remotePath, screenSize, - createAward, + createAward } = context.moduleContexts.game.state; // Listener ObjectTypes enum ListenerTypes { InputPlugin = 'input_plugin', - KeyboardKeyType = 'keyboard_key', + KeyboardKeyType = 'keyboard_key' } const ListnerTypes = Object.values(ListenerTypes); @@ -56,7 +56,7 @@ enum ObjectTypes { RectType = 'rect', EllipseType = 'ellipse', ContainerType = 'container', - AwardType = 'award', + AwardType = 'award' } const ObjTypes = Object.values(ObjectTypes); @@ -77,9 +77,7 @@ const scene = () => mandatory(context.moduleContexts.game.state.scene, 'No scene // ============================================================================= /** @hidden */ -function get_obj( - obj: GameObject, -): RawGameObject | RawInputObject | RawContainer { +function get_obj(obj: GameObject): RawGameObject | RawInputObject | RawContainer { return obj.object!; } @@ -135,13 +133,10 @@ function is_any_type(obj: GameObject, types: string[]): boolean { * @returns typed game object * @hidden */ -function set_type( - object: RawGameObject | RawInputObject | RawContainer, - type: string, -): GameObject { +function set_type(object: RawGameObject | RawInputObject | RawContainer, type: string): GameObject { return { type, - object, + object }; } @@ -183,12 +178,16 @@ export function prepend_remote_url(asset_key: string): string { */ export function create_config(lst: List): ObjectConfig { const config = {}; - accumulate((xs: [any, any], _) => { - if (!is_pair(xs)) { - throw_error('config element is not a pair!'); - } - config[head(xs)] = tail(xs); - }, null, lst); + accumulate( + (xs: [any, any], _) => { + if (!is_pair(xs)) { + throw_error('config element is not a pair!'); + } + config[head(xs)] = tail(xs); + }, + null, + lst + ); return config; } @@ -217,7 +216,7 @@ export function create_text_config( color: string = '#fff', stroke: string = '#fff', stroke_thickness: number = 0, - align: string = 'left', + align: string = 'left' ): ObjectConfig { return { fontFamily: font_family, @@ -225,7 +224,7 @@ export function create_text_config( color, stroke, strokeThickness: stroke_thickness, - align, + align }; } @@ -245,13 +244,13 @@ export function create_interactive_config( draggable: boolean = false, use_hand_cursor: boolean = false, pixel_perfect: boolean = false, - alpha_tolerance: number = 1, + alpha_tolerance: number = 1 ): ObjectConfig { return { draggable, useHandCursor: use_hand_cursor, pixelPerfect: pixel_perfect, - alphaTolerance: alpha_tolerance, + alphaTolerance: alpha_tolerance }; } @@ -277,7 +276,7 @@ export function create_sound_config( detune: number = 0, seek: number = 0, loop: boolean = false, - delay: number = 0, + delay: number = 0 ): ObjectConfig { return { mute, @@ -286,7 +285,7 @@ export function create_sound_config( detune, seek, loop, - delay, + delay }; } @@ -318,7 +317,7 @@ export function create_tween_config( yoyo: boolean = false, loop: number = 0, loop_delay: number = 0, - on_loop: Function = nullFn, + on_loop: Function = nullFn ): ObjectConfig { return { [target_prop]: target_value, @@ -329,7 +328,7 @@ export function create_tween_config( yoyo, loop, loopDelay: loop_delay, - onLoop: on_loop, + onLoop: on_loop }; } @@ -358,7 +357,7 @@ export function create_anim_config( repeat: number = -1, yoyo: boolean = false, show_on_start: boolean = true, - hide_on_complete: boolean = false, + hide_on_complete: boolean = false ): ObjectConfig { return { key: anims_key, @@ -368,7 +367,7 @@ export function create_anim_config( repeat, yoyo, showOnStart: show_on_start, - hideOnComplete: hide_on_complete, + hideOnComplete: hide_on_complete }; } @@ -388,12 +387,12 @@ export function create_anim_config( export function create_anim_frame_config( key: string, duration: number = 0, - visible: boolean = true, + visible: boolean = true ): ObjectConfig { return { key, duration, - visible, + visible }; } @@ -413,9 +412,7 @@ export function create_anim_frame_config( * @param key key associated with spritesheet * @returns animation frame configs */ -export function create_anim_spritesheet_frame_configs( - key: string, -): ObjectConfig[] | undefined { +export function create_anim_spritesheet_frame_configs(key: string): ObjectConfig[] | undefined { if (preloadSpritesheetMap.get(key)) { const configArr = scene().anims.generateFrameNumbers(key, {}); return configArr; @@ -439,14 +436,14 @@ export function create_spritesheet_config( frame_height: number, start_frame: number = 0, margin: number = 0, - spacing: number = 0, + spacing: number = 0 ): ObjectConfig { return { frameWidth: frame_width, frameHeight: frame_height, startFrame: start_frame, margin, - spacing, + spacing }; } @@ -520,11 +517,7 @@ export function load_sound(key: string, url: string) { * @param url path to the sound * @param spritesheet_config config to determines frames within the spritesheet */ -export function load_spritesheet( - key: string, - url: string, - spritesheet_config: ObjectConfig, -) { +export function load_spritesheet(key: string, url: string, spritesheet_config: ObjectConfig) { preloadSpritesheetMap.set(key, [url, spritesheet_config]); } @@ -588,10 +581,7 @@ export function create_anim(anim_config: ObjectConfig): boolean { * @param image image game object * @param anims_key key associated with an animation */ -export function play_anim_on_image( - image: GameObject, - anims_key: string, -): GameObject | undefined { +export function play_anim_on_image(image: GameObject, anims_key: string): GameObject | undefined { if (is_type(image, ObjectTypes.ImageType)) { (get_obj(image) as Phaser.GameObjects.Sprite).play(anims_key); return image; @@ -612,15 +602,8 @@ export function play_anim_on_image( * @param asset_key key to loaded image * @returns image game object */ -export function create_image( - x: number, - y: number, - asset_key: string, -): GameObject | undefined { - if ( - preloadImageMap.get(asset_key) - || preloadSpritesheetMap.get(asset_key) - ) { +export function create_image(x: number, y: number, asset_key: string): GameObject | undefined { + if (preloadImageMap.get(asset_key) || preloadSpritesheetMap.get(asset_key)) { const image = new Phaser.GameObjects.Sprite(scene(), x, y, asset_key); return set_type(image, ObjectTypes.ImageType); } @@ -666,7 +649,7 @@ export function create_text( x: number, y: number, text: string, - config: ObjectConfig = {}, + config: ObjectConfig = {} ): GameObject { const txt = new Phaser.GameObjects.Text(scene(), x, y, text, config); return set_type(txt, ObjectTypes.TextType); @@ -693,17 +676,9 @@ export function create_rect( width: number, height: number, fill: number = 0, - alpha: number = 1, + alpha: number = 1 ): GameObject { - const rect = new Phaser.GameObjects.Rectangle( - scene(), - x, - y, - width, - height, - fill, - alpha, - ); + const rect = new Phaser.GameObjects.Rectangle(scene(), x, y, width, height, fill, alpha); return set_type(rect, ObjectTypes.RectType); } @@ -726,17 +701,9 @@ export function create_ellipse( width: number, height: number, fill: number = 0, - alpha: number = 1, + alpha: number = 1 ): GameObject { - const ellipse = new Phaser.GameObjects.Ellipse( - scene(), - x, - y, - width, - height, - fill, - alpha, - ); + const ellipse = new Phaser.GameObjects.Ellipse(scene(), x, y, width, height, fill, alpha); return set_type(ellipse, ObjectTypes.EllipseType); } @@ -773,20 +740,13 @@ export function create_container(x: number, y: number): GameObject { * @param obj game object to add to the container * @returns container object */ -export function add_to_container( - container: GameObject, - obj: GameObject, -): GameObject | undefined { - if ( - is_type(container, ObjectTypes.ContainerType) - && is_any_type(obj, ObjTypes) - ) { - get_container(container) - .add(get_game_obj(obj)); +export function add_to_container(container: GameObject, obj: GameObject): GameObject | undefined { + if (is_type(container, ObjectTypes.ContainerType) && is_any_type(obj, ObjTypes)) { + get_container(container).add(get_game_obj(obj)); return container; } throw_error( - `${obj} is not of type ${ObjTypes} or ${container} is not of type ${ObjectTypes.ContainerType}`, + `${obj} is not of type ${ObjTypes} or ${container} is not of type ${ObjectTypes.ContainerType}` ); } @@ -801,8 +761,7 @@ export function add_to_container( */ export function destroy_obj(obj: GameObject) { if (is_any_type(obj, ObjTypes)) { - get_game_obj(obj) - .destroy(); + get_game_obj(obj).destroy(); } else { throw_error(`${obj} is not of type ${ObjTypes}`); } @@ -817,14 +776,9 @@ export function destroy_obj(obj: GameObject) { * @param y new display height size * @returns game object itself */ -export function set_display_size( - obj: GameObject, - x: number, - y: number, -): GameObject | undefined { +export function set_display_size(obj: GameObject, x: number, y: number): GameObject | undefined { if (is_any_type(obj, ObjTypes)) { - get_game_obj(obj) - .setDisplaySize(x, y); + get_game_obj(obj).setDisplaySize(x, y); return obj; } throw_error(`${obj} is not of type ${ObjTypes}`); @@ -840,8 +794,7 @@ export function set_display_size( */ export function set_alpha(obj: GameObject, alpha: number): GameObject | undefined { if (is_any_type(obj, ObjTypes)) { - get_game_obj(obj) - .setAlpha(alpha); + get_game_obj(obj).setAlpha(alpha); return obj; } throw_error(`${obj} is not of type ${ObjTypes}`); @@ -860,11 +813,10 @@ export function set_alpha(obj: GameObject, alpha: number): GameObject | undefine */ export function set_interactive( obj: GameObject, - config: ObjectConfig = {}, + config: ObjectConfig = {} ): GameObject | undefined { if (is_any_type(obj, ObjTypes)) { - get_game_obj(obj) - .setInteractive(config); + get_game_obj(obj).setInteractive(config); return obj; } throw_error(`${obj} is not of type ${ObjTypes}`); @@ -880,11 +832,7 @@ export function set_interactive( * @param y new anchor y coordinate, between value 0 to 1. * @returns game object itself */ -export function set_origin( - obj: GameObject, - x: number, - y: number, -): GameObject | undefined { +export function set_origin(obj: GameObject, x: number, y: number): GameObject | undefined { if (is_any_type(obj, ObjTypes)) { (get_game_obj(obj) as RawGameObject).setOrigin(x, y); return obj; @@ -901,14 +849,9 @@ export function set_origin( * @param y new y position * @returns game object itself */ -export function set_position( - obj: GameObject, - x: number, - y: number, -): GameObject | undefined { +export function set_position(obj: GameObject, x: number, y: number): GameObject | undefined { if (obj && is_any_type(obj, ObjTypes)) { - get_game_obj(obj) - .setPosition(x, y); + get_game_obj(obj).setPosition(x, y); return obj; } throw_error(`${obj} is not of type ${ObjTypes}`); @@ -923,14 +866,9 @@ export function set_position( * @param y new y scale * @returns game object itself */ -export function set_scale( - obj: GameObject, - x: number, - y: number, -): GameObject | undefined { +export function set_scale(obj: GameObject, x: number, y: number): GameObject | undefined { if (is_any_type(obj, ObjTypes)) { - get_game_obj(obj) - .setScale(x, y); + get_game_obj(obj).setScale(x, y); return obj; } throw_error(`${obj} is not of type ${ObjTypes}`); @@ -946,8 +884,7 @@ export function set_scale( */ export function set_rotation(obj: GameObject, rad: number): GameObject | undefined { if (is_any_type(obj, ObjTypes)) { - get_game_obj(obj) - .setRotation(rad); + get_game_obj(obj).setRotation(rad); return obj; } throw_error(`${obj} is not of type ${ObjTypes}`); @@ -962,11 +899,7 @@ export function set_rotation(obj: GameObject, rad: number): GameObject | undefin * @param y to flip in the vertical state * @returns game object itself */ -export function set_flip( - obj: GameObject, - x: boolean, - y: boolean, -): GameObject | undefined { +export function set_flip(obj: GameObject, x: boolean, y: boolean): GameObject | undefined { const GameElementType = [ObjectTypes.ImageType, ObjectTypes.TextType]; if (is_any_type(obj, GameElementType)) { (get_obj(obj) as RawGameElement).setFlip(x, y); @@ -985,12 +918,12 @@ export function set_flip( */ export async function add_tween( obj: GameObject, - config: ObjectConfig = {}, + config: ObjectConfig = {} ): Promise { if (is_any_type(obj, ObjTypes)) { scene().tweens.add({ targets: get_game_obj(obj), - ...config, + ...config }); return obj; } @@ -1015,11 +948,10 @@ export async function add_tween( export function add_listener( obj: GameObject, event: string, - callback: Function, + callback: Function ): GameObject | undefined { if (is_any_type(obj, ObjTypes)) { - const listener = get_game_obj(obj) - .addListener(event, callback); + const listener = get_game_obj(obj).addListener(event, callback); return set_type(listener, ListenerTypes.InputPlugin); } throw_error(`${obj} is not of type ${ObjTypes}`); @@ -1044,7 +976,7 @@ export function add_listener( export function add_keyboard_listener( key: string | number, event: string, - callback: Function, + callback: Function ): GameObject { const keyObj = scene().input.keyboard.addKey(key); const keyboardListener = keyObj.addListener(event, callback); @@ -1059,8 +991,7 @@ export function add_keyboard_listener( */ export function remove_listener(listener: GameObject): boolean { if (is_any_type(listener, ListnerTypes)) { - get_input_obj(listener) - .removeAllListeners(); + get_input_obj(listener).removeAllListeners(); return true; } return false; @@ -1107,7 +1038,7 @@ const gameFunctions = [ set_origin, set_position, set_rotation, - set_scale, + set_scale ]; // Inject minArgsNeeded to allow module varargs diff --git a/src/bundles/game/index.ts b/src/bundles/game/index.ts index a4ab431d26..797b81ac2b 100644 --- a/src/bundles/game/index.ts +++ b/src/bundles/game/index.ts @@ -16,8 +16,8 @@ export { add, - add_listener, add_keyboard_listener, + add_listener, add_to_container, add_tween, create_anim, @@ -31,16 +31,16 @@ export { create_image, create_interactive_config, create_rect, + create_sound_config, + create_spritesheet_config, create_text, create_text_config, create_tween_config, - create_sound_config, - create_spritesheet_config, destroy_obj, - get_screen_width, - get_screen_height, - get_screen_display_width, get_screen_display_height, + get_screen_display_width, + get_screen_height, + get_screen_width, load_image, load_sound, load_spritesheet, @@ -55,5 +55,5 @@ export { set_origin, set_position, set_rotation, - set_scale, + set_scale } from './functions'; diff --git a/src/bundles/game/types.ts b/src/bundles/game/types.ts index 68902f7d0f..a49cc7bf0f 100644 --- a/src/bundles/game/types.ts +++ b/src/bundles/game/types.ts @@ -2,21 +2,15 @@ import * as Phaser from 'phaser'; export type ObjectConfig = { [attr: string]: any }; -export type RawGameElement = - | Phaser.GameObjects.Sprite - | Phaser.GameObjects.Text; +export type RawGameElement = Phaser.GameObjects.Sprite | Phaser.GameObjects.Text; -export type RawGameShape = - | Phaser.GameObjects.Rectangle - | Phaser.GameObjects.Ellipse; +export type RawGameShape = Phaser.GameObjects.Rectangle | Phaser.GameObjects.Ellipse; export type RawGameObject = RawGameElement | RawGameShape; export type RawContainer = Phaser.GameObjects.Container; -export type RawInputObject = - | Phaser.Input.InputPlugin - | Phaser.Input.Keyboard.Key; +export type RawInputObject = Phaser.Input.InputPlugin | Phaser.Input.Keyboard.Key; export type GameObject = { type: string; @@ -50,13 +44,14 @@ export const defaultGameParams: GameParams = { lifecycleFuncs: { preload() {}, create() {}, - update() {}, + update() {} }, renderPreview: false, remotePath: (path: string) => sourceAcademyAssets + (path[0] === '/' ? '' : '/') + path, screenSize: { x: 1920, - y: 1080, + y: 1080 }, - createAward: (x: number, y: number, key: string) => new Phaser.GameObjects.Sprite(defaultGameParams.scene!, x, y, key), + createAward: (x: number, y: number, key: string) => + new Phaser.GameObjects.Sprite(defaultGameParams.scene!, x, y, key) }; diff --git a/src/bundles/mark_sweep/index.ts b/src/bundles/mark_sweep/index.ts index 65d879c01c..c1aae0cf0a 100644 --- a/src/bundles/mark_sweep/index.ts +++ b/src/bundles/mark_sweep/index.ts @@ -1,4 +1,4 @@ -import { type MemoryHeaps, type Memory, type Tag, COMMAND, type CommandHeapObject } from './types'; +import { COMMAND, type CommandHeapObject, type Memory, type MemoryHeaps, type Tag } from './types'; // Global Variables let ROW: number = 10; @@ -40,7 +40,7 @@ function generateMemory(): void { desc: 'Memory initially empty.', leftDesc: '', rightDesc: '', - queue: [], + queue: [] }; commandHeap.push(obj); @@ -52,12 +52,7 @@ function updateRoots(array): void { } } -function initialize_memory( - memorySize: number, - nodeSize, - marked, - unmarked, -): void { +function initialize_memory(memorySize: number, nodeSize, marked, unmarked): void { MEMORY_SIZE = memorySize; NODE_SIZE = nodeSize; const excess = MEMORY_SIZE % NODE_SIZE; @@ -91,7 +86,7 @@ function newCommand( description, firstDesc, lastDesc, - queue = [], + queue = [] ): void { const newType = type; const newLeft = left; @@ -121,7 +116,7 @@ function newCommand( desc: newDesc, leftDesc: newFirstDesc, rightDesc: newLastDesc, - queue: newQueue, + queue: newQueue }; commandHeap.push(obj); @@ -130,34 +125,13 @@ function newCommand( function newSweep(left, heap): void { const newSizeLeft = NODE_SIZE; const desc = `Freeing node ${left}`; - newCommand( - COMMAND.SWEEP, - left, - -1, - newSizeLeft, - 0, - heap, - desc, - 'freed node', - '', - ); + newCommand(COMMAND.SWEEP, left, -1, newSizeLeft, 0, heap, desc, 'freed node', ''); } function newMark(left, heap, queue): void { const newSizeLeft = NODE_SIZE; const desc = `Marking node ${left} to be live memory`; - newCommand( - COMMAND.MARK, - left, - -1, - newSizeLeft, - 0, - heap, - desc, - 'marked node', - '', - queue, - ); + newCommand(COMMAND.MARK, left, -1, newSizeLeft, 0, heap, desc, 'marked node', '', queue); } function addRoots(arr): void { @@ -180,17 +154,7 @@ function showRoots(heap): void { function newUpdateSweep(right, heap): void { const desc = `Set node ${right} to freelist`; - newCommand( - COMMAND.RESET, - -1, - right, - 0, - NODE_SIZE, - heap, - desc, - 'free node', - '', - ); + newCommand(COMMAND.RESET, -1, right, 0, NODE_SIZE, heap, desc, 'free node', ''); } function newPush(left, right, heap): void { @@ -204,7 +168,7 @@ function newPush(left, right, heap): void { heap, desc, 'last child address slot', - 'new child pushed', + 'new child pushed' ); } @@ -220,7 +184,7 @@ function newPop(res, left, right, heap): void { heap, desc, 'popped memory', - 'last child address slot', + 'last child address slot' ); } @@ -233,17 +197,7 @@ function newAssign(res, left, heap): void { function newNew(left, heap): void { const newSizeLeft = NODE_SIZE; const desc = `New node starts in [${left}].`; - newCommand( - COMMAND.NEW, - left, - -1, - newSizeLeft, - 0, - heap, - desc, - 'new memory allocated', - '', - ); + newCommand(COMMAND.NEW, left, -1, newSizeLeft, 0, heap, desc, 'new memory allocated', ''); } function newGC(heap): void { @@ -258,12 +212,7 @@ function endGC(heap): void { updateFlip(); } -function updateSlotSegment( - tag: number, - size: number, - first: number, - last: number, -): void { +function updateSlotSegment(tag: number, size: number, first: number, last: number): void { if (tag >= 0) { TAG_SLOT = tag; } @@ -294,7 +243,7 @@ function get_flips(): number[] { return flips; } -function get_types(): String[] { +function get_types(): string[] { return typeTag; } @@ -345,30 +294,30 @@ function init() { get_command, get_unmarked, get_marked, - get_roots, + get_roots }; } export { + addRoots, + allHeap, + endGC, + generateMemory, init, // initialisation initialize_memory, initialize_tag, - generateMemory, - allHeap, - updateSlotSegment, + newAssign, newCommand, + newGC, newMark, - newPush, - newPop, - newAssign, newNew, - newGC, + newPop, + newPush, newSweep, - updateRoots, newUpdateSweep, - showRoots, - endGC, - addRoots, showRoot, + showRoots, + updateRoots, + updateSlotSegment }; diff --git a/src/bundles/mark_sweep/types.ts b/src/bundles/mark_sweep/types.ts index 56c71389dc..a653484391 100644 --- a/src/bundles/mark_sweep/types.ts +++ b/src/bundles/mark_sweep/types.ts @@ -15,18 +15,18 @@ export enum COMMAND { SHOW_MARKED = 'Marked Roots', MARK = 'Mark', SWEEP = 'Sweep', - INIT = 'Initialize Memory', + INIT = 'Initialize Memory' } export type CommandHeapObject = { - type: String; + type: string; heap: number[]; left: number; right: number; sizeLeft: number; sizeRight: number; - desc: String; - leftDesc: String; - rightDesc: String; + desc: string; + leftDesc: string; + rightDesc: string; queue: number[]; }; diff --git a/src/bundles/painter/functions.ts b/src/bundles/painter/functions.ts index d58e5b3864..2d61f5ba12 100644 --- a/src/bundles/painter/functions.ts +++ b/src/bundles/painter/functions.ts @@ -1,10 +1,10 @@ import context from 'js-slang/context'; import Plotly, { type Data, type Layout } from 'plotly.js-dist'; -import { type Frame, LinePlot } from './painter'; +import { LinePlot, type Frame } from './painter'; const drawnPainters: LinePlot[] = []; context.moduleContexts.painter.state = { - drawnPainters, + drawnPainters }; let data: Data = {}; @@ -47,19 +47,23 @@ export function display_painter(painter: (frame: Frame) => void) { painter(frame); data = { x: x_s, - y: y_s, + y: y_s }; drawnPainters.push( - new LinePlot(draw_new_painter, { - ...data, - mode: 'lines', - } as Data, { - xaxis: { visible: true }, - yaxis: { - visible: true, - scaleanchor: 'x', - }, - }), + new LinePlot( + draw_new_painter, + { + ...data, + mode: 'lines' + } as Data, + { + xaxis: { visible: true }, + yaxis: { + visible: true, + scaleanchor: 'x' + } + } + ) ); }; } diff --git a/src/bundles/painter/index.ts b/src/bundles/painter/index.ts index 3e6c83a669..6830409f6f 100644 --- a/src/bundles/painter/index.ts +++ b/src/bundles/painter/index.ts @@ -4,4 +4,4 @@ * @author Sourabh Raj Jaiswal */ -export { draw_line, display_painter } from './functions'; +export { display_painter, draw_line } from './functions'; diff --git a/src/bundles/painter/painter.ts b/src/bundles/painter/painter.ts index b170fa204c..8e00ca5482 100644 --- a/src/bundles/painter/painter.ts +++ b/src/bundles/painter/painter.ts @@ -1,15 +1,19 @@ -import type { ReplResult } from '../../typings/type_helpers'; import type { Data, Layout } from 'plotly.js-dist'; +import type { ReplResult } from '../../typings/type_helpers'; export class LinePlot implements ReplResult { plotlyDrawFn: any; + data: Data; + layout: Partial; + constructor(plotlyDrawFn: any, data: Data, layout: Partial) { this.plotlyDrawFn = plotlyDrawFn; this.data = data; this.layout = layout; } + public toReplString = () => ''; public draw = (divId: string) => { diff --git a/src/bundles/physics_2d/PhysicsObject.ts b/src/bundles/physics_2d/PhysicsObject.ts index e6bbb681eb..e8ced67598 100644 --- a/src/bundles/physics_2d/PhysicsObject.ts +++ b/src/bundles/physics_2d/PhysicsObject.ts @@ -1,26 +1,30 @@ -/* eslint-disable new-cap */ + // We have to disable linting rules since Box2D functions do not // follow the same guidelines as the rest of the codebase. import { - type b2Body, - type b2Shape, - type b2Fixture, b2BodyType, b2CircleShape, b2PolygonShape, b2Vec2, + type b2Body, + type b2Fixture, + type b2Shape } from '@box2d/core'; import { type ReplResult } from '../../typings/type_helpers'; -import { ACCURACY, type Force, type ForceWithPos } from './types'; import { type PhysicsWorld } from './PhysicsWorld'; +import { ACCURACY, type Force, type ForceWithPos } from './types'; export class PhysicsObject implements ReplResult { private body: b2Body; + private shape: b2Shape; + private fixture: b2Fixture; + private forcesCentered: Force[] = []; + private forcesAtAPoint: ForceWithPos[] = []; constructor( @@ -28,19 +32,19 @@ export class PhysicsObject implements ReplResult { rotation: number, shape: b2Shape, isStatic: boolean, - world: PhysicsWorld, + world: PhysicsWorld ) { this.body = world.createBody({ type: isStatic ? b2BodyType.b2_staticBody : b2BodyType.b2_dynamicBody, position, - angle: rotation, + angle: rotation }); this.shape = shape; this.fixture = this.body.CreateFixture({ shape: this.shape, density: 1, - friction: 1, + friction: 1 }); } @@ -100,20 +104,20 @@ export class PhysicsObject implements ReplResult { public addForceAtAPoint(force: Force, pos: b2Vec2) { this.forcesAtAPoint.push({ force, - pos, + pos }); } private applyForcesToCenter(world_time: number) { this.forcesCentered = this.forcesCentered.filter( - (force: Force) => force.start_time + force.duration > world_time, + (force: Force) => force.start_time + force.duration > world_time ); const resForce = this.forcesCentered .filter((force: Force) => force.start_time < world_time) .reduce( (res: b2Vec2, force: Force) => res.Add(force.direction.Scale(force.magnitude)), - new b2Vec2(), + new b2Vec2() ); this.body.ApplyForceToCenter(resForce); @@ -121,15 +125,13 @@ export class PhysicsObject implements ReplResult { private applyForcesAtAPoint(world_time: number) { this.forcesAtAPoint = this.forcesAtAPoint.filter( - (forceWithPos: ForceWithPos) => forceWithPos.force.start_time + forceWithPos.force.duration > world_time, + (forceWithPos: ForceWithPos) => + forceWithPos.force.start_time + forceWithPos.force.duration > world_time ); this.forcesAtAPoint.forEach((forceWithPos) => { const force = forceWithPos.force; - this.body.ApplyForce( - force.direction.Scale(force.magnitude), - forceWithPos.pos, - ); + this.body.ApplyForce(force.direction.Scale(force.magnitude), forceWithPos.pos); }); } @@ -150,19 +152,12 @@ export class PhysicsObject implements ReplResult { } public toReplString = () => ` - Mass: ${this.getMass() - .toFixed(ACCURACY)} - Position: [${this.getPosition().x.toFixed( - ACCURACY, - )},${this.getPosition().y.toFixed(ACCURACY)}] - Velocity: [${this.getVelocity().x.toFixed( - ACCURACY, - )},${this.getVelocity().y.toFixed(ACCURACY)}] + Mass: ${this.getMass().toFixed(ACCURACY)} + Position: [${this.getPosition().x.toFixed(ACCURACY)},${this.getPosition().y.toFixed(ACCURACY)}] + Velocity: [${this.getVelocity().x.toFixed(ACCURACY)},${this.getVelocity().y.toFixed(ACCURACY)}] - Rotation: ${this.getRotation() - .toFixed(ACCURACY)} - AngularVelocity: [${this.getAngularVelocity() - .toFixed(ACCURACY)}]`; + Rotation: ${this.getRotation().toFixed(ACCURACY)} + AngularVelocity: [${this.getAngularVelocity().toFixed(ACCURACY)}]`; public scale_size(scale: number) { if (this.shape instanceof b2CircleShape) { @@ -174,19 +169,18 @@ export class PhysicsObject implements ReplResult { arr.push( new b2Vec2( centroid.x + scale * (vec.x - centroid.x), - centroid.y + scale * (vec.y - centroid.y), - ), + centroid.y + scale * (vec.y - centroid.y) + ) ); }); - this.shape = new b2PolygonShape() - .Set(arr); + this.shape = new b2PolygonShape().Set(arr); } const f: b2Fixture = this.fixture; this.body.DestroyFixture(this.fixture); this.fixture = this.body.CreateFixture({ shape: this.shape, density: f.GetDensity(), - friction: f.GetFriction(), + friction: f.GetFriction() }); } } diff --git a/src/bundles/physics_2d/PhysicsWorld.ts b/src/bundles/physics_2d/PhysicsWorld.ts index 3d2dd825a0..65bd5e7865 100644 --- a/src/bundles/physics_2d/PhysicsWorld.ts +++ b/src/bundles/physics_2d/PhysicsWorld.ts @@ -1,31 +1,34 @@ -/* eslint-disable new-cap */ + // We have to disable linting rules since Box2D functions do not // follow the same guidelines as the rest of the codebase. import { - type b2Body, - type b2Fixture, - type b2BodyDef, b2BodyType, + b2ContactListener, b2PolygonShape, - type b2StepConfig, b2Vec2, b2World, - b2ContactListener, + type b2Body, + type b2BodyDef, type b2Contact, + type b2Fixture, + type b2StepConfig } from '@box2d/core'; import { type PhysicsObject } from './PhysicsObject'; import { Timer } from './types'; export class PhysicsWorld { private b2World: b2World; + private physicsObjects: PhysicsObject[]; + private timer: Timer; + private touchingObjects: Map>; private iterationsConfig: b2StepConfig = { velocityIterations: 8, - positionIterations: 3, + positionIterations: 3 }; constructor() { @@ -71,18 +74,14 @@ export class PhysicsWorld { public makeGround(height: number, friction: number) { const groundBody: b2Body = this.createBody({ type: b2BodyType.b2_staticBody, - position: new b2Vec2(0, height - 10), + position: new b2Vec2(0, height - 10) }); - const groundShape: b2PolygonShape = new b2PolygonShape() - .SetAsBox( - 10000, - 10, - ); + const groundShape: b2PolygonShape = new b2PolygonShape().SetAsBox(10000, 10); groundBody.CreateFixture({ shape: groundShape, density: 1, - friction, + friction }); } @@ -105,8 +104,8 @@ export class PhysicsWorld { return this.b2World; } - public getWorldStatus(): String { - let world_status: String = ` + public getWorldStatus(): string { + let world_status: string = ` World time: ${this.timer.toString()} Objects: diff --git a/src/bundles/physics_2d/functions.ts b/src/bundles/physics_2d/functions.ts index 960bbba40e..b679ac65ab 100644 --- a/src/bundles/physics_2d/functions.ts +++ b/src/bundles/physics_2d/functions.ts @@ -1,4 +1,4 @@ -/* eslint-disable new-cap */ + // We have to disable linting rules since Box2D functions do not // follow the same guidelines as the rest of the codebase. @@ -12,9 +12,9 @@ import context from 'js-slang/context'; import { b2CircleShape, b2PolygonShape } from '@box2d/core'; -import { type Force, Vector2 } from './types'; import { PhysicsObject } from './PhysicsObject'; import { PhysicsWorld } from './PhysicsWorld'; +import { Vector2, type Force } from './types'; // Global Variables @@ -48,17 +48,12 @@ export function make_vector(x: number, y: number): Vector2 { * * @category Dynamics */ -export function make_force( - dir: Vector2, - mag: number, - dur: number, - start: number, -): Force { - let force: Force = { +export function make_force(dir: Vector2, mag: number, dur: number, start: number): Force { + const force: Force = { direction: dir, magnitude: mag, duration: dur, - start_time: start, + start_time: start }; return force; } @@ -81,7 +76,7 @@ export function set_gravity(v: Vector2) { world = new PhysicsWorld(); context.moduleContexts.physics_2d.state = { - world, + world }; world.setGravity(v); } @@ -118,14 +113,7 @@ export function add_wall(pos: Vector2, rot: number, size: Vector2) { } return world.addObject( - new PhysicsObject( - pos, - rot, - new b2PolygonShape() - .SetAsBox(size.x / 2, size.y / 2), - true, - world, - ), + new PhysicsObject(pos, rot, new b2PolygonShape().SetAsBox(size.x / 2, size.y / 2), true, world) ); } @@ -145,7 +133,7 @@ export function add_box_object( rot: number, velc: Vector2, size: Vector2, - isStatic: boolean, + isStatic: boolean ): PhysicsObject { if (!world) { throw NO_WORLD; @@ -153,10 +141,9 @@ export function add_box_object( const newObj: PhysicsObject = new PhysicsObject( pos, rot, - new b2PolygonShape() - .SetAsBox(size.x / 2, size.y / 2), + new b2PolygonShape().SetAsBox(size.x / 2, size.y / 2), isStatic, - world, + world ); newObj.setVelocity(velc); return world.addObject(newObj); @@ -178,7 +165,7 @@ export function add_circle_object( rot: number, velc: Vector2, radius: number, - isStatic: boolean, + isStatic: boolean ): PhysicsObject { if (!world) { throw NO_WORLD; @@ -186,10 +173,9 @@ export function add_circle_object( const newObj: PhysicsObject = new PhysicsObject( pos, rot, - new b2CircleShape() - .Set(new Vector2(), radius), + new b2CircleShape().Set(new Vector2(), radius), isStatic, - world, + world ); newObj.setVelocity(velc); return world.addObject(newObj); @@ -213,7 +199,7 @@ export function add_triangle_object( velc: Vector2, base: number, height: number, - isStatic: boolean, + isStatic: boolean ): PhysicsObject { if (!world) { throw NO_WORLD; @@ -221,14 +207,13 @@ export function add_triangle_object( const newObj: PhysicsObject = new PhysicsObject( pos, rot, - new b2PolygonShape() - .Set([ - new Vector2(-base / 2, -height / 2), - new Vector2(base / 2, -height / 2), - new Vector2(0, height / 2), - ]), + new b2PolygonShape().Set([ + new Vector2(-base / 2, -height / 2), + new Vector2(base / 2, -height / 2), + new Vector2(0, height / 2) + ]), isStatic, - world, + world ); newObj.setVelocity(velc); return world.addObject(newObj); diff --git a/src/bundles/physics_2d/index.ts b/src/bundles/physics_2d/index.ts index 28c0eb0647..62f1a27d0c 100644 --- a/src/bundles/physics_2d/index.ts +++ b/src/bundles/physics_2d/index.ts @@ -48,7 +48,7 @@ * The suggested time step is 1/60 (seconds). * * Visualization of the physics world can also be seen in the display tab. -* + * * The following example simulates a free fall of a circle object. * * ``` @@ -73,41 +73,33 @@ * @author Yu Jiali */ export { - set_gravity, - make_ground, - add_wall, - - make_vector, - make_force, - add_box_object, add_circle_object, add_triangle_object, - + add_vector, + add_wall, + apply_force, + apply_force_to_center, + array_to_vector, + get_angular_velocity, + get_position, + get_rotation, + get_velocity, + impact_start_time, + is_touching, + make_force, + make_ground, + make_vector, + scale_size, + set_angular_velocity, set_density, set_friction, - scale_size, - - get_position, + set_gravity, set_position, - get_rotation, set_rotation, - get_velocity, set_velocity, - get_angular_velocity, - set_angular_velocity, - - apply_force, - apply_force_to_center, - - is_touching, - impact_start_time, - - update_world, simulate_world, - - vector_to_array, - array_to_vector, - add_vector, subtract_vector, + update_world, + vector_to_array } from './functions'; diff --git a/src/bundles/physics_2d/types.ts b/src/bundles/physics_2d/types.ts index 272160c34d..b7e408a5dc 100644 --- a/src/bundles/physics_2d/types.ts +++ b/src/bundles/physics_2d/types.ts @@ -1,4 +1,3 @@ -/* eslint-disable new-cap */ // We have to disable linting rules since Box2D functions do not // follow the same guidelines as the rest of the codebase. diff --git a/src/bundles/pix_n_flix/functions.ts b/src/bundles/pix_n_flix/functions.ts index 0b9e123b33..0023232dae 100644 --- a/src/bundles/pix_n_flix/functions.ts +++ b/src/bundles/pix_n_flix/functions.ts @@ -1,31 +1,30 @@ -/* eslint-disable @typescript-eslint/no-shadow */ import { + InputFeed, + type BundlePacket, type CanvasElement, - type VideoElement, type ErrorLogger, - type StartPacket, + type Filter, + type ImageElement, type Pixel, type Pixels, - type Filter, type Queue, + type StartPacket, type TabsPacket, - type BundlePacket, - InputFeed, - type ImageElement, + type VideoElement } from './types'; import { - DEFAULT_WIDTH, - DEFAULT_HEIGHT, DEFAULT_FPS, + DEFAULT_HEIGHT, + DEFAULT_LOOP, DEFAULT_VOLUME, + DEFAULT_WIDTH, + MAX_FPS, MAX_HEIGHT, - MIN_HEIGHT, MAX_WIDTH, - MIN_WIDTH, - MAX_FPS, MIN_FPS, - DEFAULT_LOOP, + MIN_HEIGHT, + MIN_WIDTH } from './constants'; // Global Variables @@ -111,8 +110,7 @@ function writeToBuffer(buffer: Uint8ClampedArray, data: Pixels) { } if (!ok) { - const warningMessage - = 'You have invalid values for some pixels! Reseting them to default (0)'; + const warningMessage = 'You have invalid values for some pixels! Reseting them to default (0)'; console.warn(warningMessage); errorLogger(warningMessage, false); } @@ -123,12 +121,7 @@ function readFromBuffer(pixelData: Uint8ClampedArray, src: Pixels) { for (let i = 0; i < HEIGHT; i += 1) { for (let j = 0; j < WIDTH; j += 1) { const p = i * WIDTH * 4 + j * 4; - src[i][j] = [ - pixelData[p], - pixelData[p + 1], - pixelData[p + 2], - pixelData[p + 3], - ]; + src[i][j] = [pixelData[p], pixelData[p + 1], pixelData[p + 2], pixelData[p + 3]]; } } } @@ -147,7 +140,7 @@ function drawImage(source: VideoElement | ImageElement): void { (WIDTH - displayWidth) / 2, (HEIGHT - displayHeight) / 2, displayWidth, - displayHeight, + displayHeight ); } else canvasRenderingContext.drawImage(source, 0, 0, WIDTH, HEIGHT); @@ -165,7 +158,7 @@ function drawImage(source: VideoElement | ImageElement): void { if (!e.name) { errorLogger( - 'There is an error with filter function (error shown below). Filter will be reset back to the default. If you are facing an infinite loop error, you can consider increasing the timeout period (clock icon) at the top / reducing the frame dimensions.', + 'There is an error with filter function (error shown below). Filter will be reset back to the default. If you are facing an infinite loop error, you can consider increasing the timeout period (clock icon) at the top / reducing the frame dimensions.' ); errorLogger([e], true); @@ -266,10 +259,8 @@ function loadMedia(): void { .getUserMedia({ video: true }) .then((stream) => { videoElement.srcObject = stream; - videoElement.onloadedmetadata = () => setAspectRatioDimensions( - videoElement.videoWidth, - videoElement.videoHeight, - ); + videoElement.onloadedmetadata = () => + setAspectRatioDimensions(videoElement.videoWidth, videoElement.videoHeight); toRunLateQueue = true; }) .catch((error) => { @@ -319,10 +310,7 @@ function loadAlternative(): void { /** Setting Up imageElement */ imageElement.crossOrigin = 'anonymous'; imageElement.onload = () => { - setAspectRatioDimensions( - imageElement.naturalWidth, - imageElement.naturalHeight, - ); + setAspectRatioDimensions(imageElement.naturalWidth, imageElement.naturalHeight); drawImage(imageElement); }; } @@ -345,11 +333,11 @@ function updateFPS(fps: number): void { function updateDimensions(w: number, h: number): void { // ignore if no change or bad inputs if ( - (w === WIDTH && h === HEIGHT) - || w > MAX_WIDTH - || w < MIN_WIDTH - || h > MAX_HEIGHT - || h < MIN_HEIGHT + (w === WIDTH && h === HEIGHT) || + w > MAX_WIDTH || + w < MIN_WIDTH || + h > MAX_HEIGHT || + h < MIN_HEIGHT ) { return; } @@ -431,7 +419,7 @@ function init( video: VideoElement, canvas: CanvasElement, _errorLogger: ErrorLogger, - _tabsPackage: TabsPacket, + _tabsPackage: TabsPacket ): BundlePacket { imageElement = image; videoElement = video; @@ -453,7 +441,7 @@ function init( WIDTH, FPS, VOLUME, - inputFeed, + inputFeed }; } @@ -468,10 +456,9 @@ function deinit(): void { if (!stream) { return; } - stream.getTracks() - .forEach((track) => { - track.stop(); - }); + stream.getTracks().forEach((track) => { + track.stop(); + }); } // ============================================================================= @@ -490,7 +477,7 @@ export function start(): StartPacket { stopVideo, updateFPS, updateVolume, - updateDimensions, + updateDimensions }; } @@ -548,13 +535,7 @@ export function alpha_of(pixel: Pixel): number { * @param b The blue component as a number between 0 and 255 * @param a The alpha component as a number between 0 and 255 */ -export function set_rgba( - pixel: Pixel, - r: number, - g: number, - b: number, - a: number, -): void { +export function set_rgba(pixel: Pixel, r: number, g: number, b: number, a: number): void { // assigns the r,g,b values to this pixel pixel[0] = r; pixel[1] = g; @@ -659,10 +640,7 @@ export function compose_filter(filter1: Filter, filter2: Filter): Filter { export function pause_at(pause_time: number): void { // prevent negative pause_time lateEnqueue(() => { - setTimeout( - tabsPackage.onClickStill, - pause_time >= 0 ? pause_time : -pause_time, - ); + setTimeout(tabsPackage.onClickStill, pause_time >= 0 ? pause_time : -pause_time); }); } diff --git a/src/bundles/pix_n_flix/types.ts b/src/bundles/pix_n_flix/types.ts index c83e6bec18..6c34376af3 100644 --- a/src/bundles/pix_n_flix/types.ts +++ b/src/bundles/pix_n_flix/types.ts @@ -1,10 +1,7 @@ export type VideoElement = HTMLVideoElement & { srcObject?: MediaStream }; export type ImageElement = HTMLImageElement; export type CanvasElement = HTMLCanvasElement; -export type ErrorLogger = ( - error: string | string[], - isSlangError?: boolean -) => void; +export type ErrorLogger = (error: string | string[], isSlangError?: boolean) => void; export type TabsPacket = { onClickStill: () => void; }; @@ -12,7 +9,7 @@ export enum InputFeed { Camera, ImageURL, VideoURL, - Local, + Local } export type BundlePacket = { diff --git a/src/bundles/plotly/curve_functions.ts b/src/bundles/plotly/curve_functions.ts index e0e0d9add8..005b3f5240 100644 --- a/src/bundles/plotly/curve_functions.ts +++ b/src/bundles/plotly/curve_functions.ts @@ -1,5 +1,5 @@ import Plotly, { type Data, type Layout } from 'plotly.js-dist'; -import { type Curve, CurvePlot, type Point } from './plotly'; +import { CurvePlot, type Curve, type Point } from './plotly'; export function x_of(pt: Point): number { return pt.x; @@ -85,12 +85,12 @@ export function generatePlot( config: Data, layout: Partial, is_colored: boolean, - func: Curve, + func: Curve ): CurvePlot { - let x_s: number[] = []; - let y_s: number[] = []; - let z_s: number[] = []; - let color_s: string[] = []; + const x_s: number[] = []; + const y_s: number[] = []; + const z_s: number[] = []; + const color_s: string[] = []; for (let i = 0; i <= numPoints; i += 1) { const point = func(i / numPoints); x_s.push(x_of(point)); @@ -105,23 +105,23 @@ export function generatePlot( z: z_s, marker: { size: 2, - color: color_s, + color: color_s }, line: { - color: color_s, - }, + color: color_s + } }; return new CurvePlot( draw_new_curve, { ...plotlyData, ...config, - type, + type } as Data, - layout, + layout ); } function draw_new_curve(divId: string, data: Data, layout: Partial) { Plotly.react(divId, [data], layout); -} \ No newline at end of file +} diff --git a/src/bundles/plotly/functions.ts b/src/bundles/plotly/functions.ts index d1e5d1381c..5a4c80dcd5 100644 --- a/src/bundles/plotly/functions.ts +++ b/src/bundles/plotly/functions.ts @@ -5,21 +5,21 @@ import context from 'js-slang/context'; import Plotly, { type Data, type Layout } from 'plotly.js-dist'; +import { type Sound } from '../sound/types'; +import { generatePlot } from './curve_functions'; import { - type Curve, CurvePlot, - type CurvePlotFunction, DrawnPlot, - type ListOfPairs, + type Curve, + type CurvePlotFunction, + type ListOfPairs } from './plotly'; -import { generatePlot } from './curve_functions'; import { get_duration, get_wave, is_sound } from './sound_functions'; -import { type Sound } from '../sound/types'; -let drawnPlots: (DrawnPlot | CurvePlot)[] = []; +const drawnPlots: (DrawnPlot | CurvePlot)[] = []; context.moduleContexts.plotly.state = { - drawnPlots, + drawnPlots }; /** @@ -261,7 +261,7 @@ function draw_new_plot_json(data: any, divId: string) { * @returns The converted data that can be used by the plotly.js function */ function convert_to_plotly_data(data: ListOfPairs): Data { - let convertedData: Data = {}; + const convertedData: Data = {}; if (Array.isArray(data) && data.length === 2) { add_fields_to_data(convertedData, data); } @@ -286,18 +286,11 @@ function createPlotFunction( type: string, config: Data, layout: Partial, - is_colored: boolean = false, + is_colored: boolean = false ): (numPoints: number) => CurvePlotFunction { return (numPoints: number) => { const func = (curveFunction: Curve) => { - const plotDrawn = generatePlot( - type, - numPoints, - config, - layout, - is_colored, - curveFunction, - ); + const plotDrawn = generatePlot(type, numPoints, config, layout, is_colored, curveFunction); drawnPlots.push(plotDrawn); return plotDrawn; @@ -322,16 +315,16 @@ function createPlotFunction( export const draw_connected_2d = createPlotFunction( 'scattergl', { - mode: 'lines', + mode: 'lines' }, { xaxis: { visible: false }, yaxis: { visible: false, - scaleanchor: 'x', - }, + scaleanchor: 'x' + } }, - true, + true ); /** @@ -346,12 +339,7 @@ export const draw_connected_2d = createPlotFunction( * draw_connected_3d(100)(t => make_point(t, t)); * ``` */ -export const draw_connected_3d = createPlotFunction( - 'scatter3d', - { mode: 'lines' }, - {}, - true, -); +export const draw_connected_3d = createPlotFunction('scatter3d', { mode: 'lines' }, {}, true); /** * Returns a function that turns a given Curve into a Drawing, by sampling the @@ -372,10 +360,10 @@ export const draw_points_2d = createPlotFunction( xaxis: { visible: false }, yaxis: { visible: false, - scaleanchor: 'x', - }, + scaleanchor: 'x' + } }, - true, + true ); /** @@ -390,11 +378,7 @@ export const draw_points_2d = createPlotFunction( * ``` * draw_points_3d(100)(t => make_point(t, t)); */ -export const draw_points_3d = createPlotFunction( - 'scatter3d', - { mode: 'markers' }, - {}, -); +export const draw_points_3d = createPlotFunction('scatter3d', { mode: 'markers' }, {}); /** * Visualizes the sound on a 2d line graph @@ -403,9 +387,7 @@ export const draw_points_3d = createPlotFunction( export const draw_sound_2d = (sound: Sound) => { const FS: number = 44100; // Output sample rate if (!is_sound(sound)) { - throw new Error( - `draw_sound_2d is expecting sound, but encountered ${sound}`, - ); + throw new Error(`draw_sound_2d is expecting sound, but encountered ${sound}`); // If a sound is already displayed, terminate execution. } else if (get_duration(sound) < 0) { throw new Error('draw_sound_2d: duration of sound is negative'); @@ -423,8 +405,8 @@ export const draw_sound_2d = (sound: Sound) => { channel[i] = wave(i / FS); } - let x_s: number[] = []; - let y_s: number[] = []; + const x_s: number[] = []; + const y_s: number[] = []; for (let i = 0; i < channel.length; i += 1) { x_s.push(time_stamps[i]); @@ -433,7 +415,7 @@ export const draw_sound_2d = (sound: Sound) => { const plotlyData: Data = { x: x_s, - y: y_s, + y: y_s }; const plot = new CurvePlot( draw_new_curve, @@ -441,7 +423,7 @@ export const draw_sound_2d = (sound: Sound) => { ...plotlyData, type: 'scattergl', mode: 'lines', - line: { width: 0.5 }, + line: { width: 0.5 } } as Data, { xaxis: { @@ -449,15 +431,15 @@ export const draw_sound_2d = (sound: Sound) => { title: 'Time', anchor: 'y', position: 0, - rangeslider: { visible: true }, + rangeslider: { visible: true } }, yaxis: { type: 'linear', - visible: false, + visible: false }, bargap: 0.2, - barmode: 'stack', - }, + barmode: 'stack' + } ); if (drawnPlots) drawnPlots.push(plot); } diff --git a/src/bundles/plotly/index.ts b/src/bundles/plotly/index.ts index bb1ca049be..19e984f703 100644 --- a/src/bundles/plotly/index.ts +++ b/src/bundles/plotly/index.ts @@ -4,11 +4,11 @@ */ export { - new_plot, - new_plot_json, draw_connected_2d, draw_connected_3d, draw_points_2d, draw_points_3d, draw_sound_2d, -} from './functions'; \ No newline at end of file + new_plot, + new_plot_json +} from './functions'; diff --git a/src/bundles/plotly/plotly.ts b/src/bundles/plotly/plotly.ts index 3ad00f06bb..9e14978169 100644 --- a/src/bundles/plotly/plotly.ts +++ b/src/bundles/plotly/plotly.ts @@ -1,13 +1,15 @@ +import type { Pair } from 'js-slang/dist/stdlib/list'; import { type Data, type Layout } from 'plotly.js-dist'; import { type ReplResult } from '../../typings/type_helpers'; -import type { Pair } from 'js-slang/dist/stdlib/list'; /** * Represents plots with a draw method attached */ export class DrawnPlot implements ReplResult { drawFn: any; + data: ListOfPairs; + constructor(drawFn: any, data: ListOfPairs) { this.drawFn = drawFn; this.data = data; @@ -22,13 +24,17 @@ export class DrawnPlot implements ReplResult { export class CurvePlot implements ReplResult { plotlyDrawFn: any; + data: Data; + layout: Partial; + constructor(plotlyDrawFn: any, data: Data, layout: Partial) { this.plotlyDrawFn = plotlyDrawFn; this.data = data; this.layout = layout; } + public toReplString = () => ''; public draw = (divId: string) => { @@ -52,11 +58,11 @@ export class Point implements ReplResult { public readonly x: number, public readonly y: number, public readonly z: number, - public readonly color: Color, + public readonly color: Color ) {} public toReplString = () => `(${this.x}, ${this.y}, ${this.z}, Color: ${this.color})`; } export type Wave = (...t: any) => number; -export type Sound = Pair; \ No newline at end of file +export type Sound = Pair; diff --git a/src/bundles/plotly/sound_functions.ts b/src/bundles/plotly/sound_functions.ts index 6d727eef44..52e9229bb2 100644 --- a/src/bundles/plotly/sound_functions.ts +++ b/src/bundles/plotly/sound_functions.ts @@ -1,16 +1,10 @@ -import { - head, - tail, - is_pair, -} from 'js-slang/dist/stdlib/list'; +import { head, is_pair, tail } from 'js-slang/dist/stdlib/list'; import { type Sound, type Wave } from '../sound/types'; + export function is_sound(x: any): x is Sound { - return ( - is_pair(x) - && typeof get_wave(x) === 'function' - && typeof get_duration(x) === 'number' - ); + return is_pair(x) && typeof get_wave(x) === 'function' && typeof get_duration(x) === 'number'; } + /** * Accesses the wave function of a given Sound. * @@ -21,6 +15,7 @@ export function is_sound(x: any): x is Sound { export function get_wave(sound: Sound): Wave { return head(sound); } + /** * Accesses the duration of a given Sound. * diff --git a/src/bundles/remote_execution/ev3/index.ts b/src/bundles/remote_execution/ev3/index.ts index ed04443260..a0cffaa721 100644 --- a/src/bundles/remote_execution/ev3/index.ts +++ b/src/bundles/remote_execution/ev3/index.ts @@ -47,9 +47,9 @@ const ev3DeviceType = { 'ev3_ledRightGreen', 'ev3_ledRightRed', 'ev3_ledGetBrightness', - 'ev3_ledSetBrightness', + 'ev3_ledSetBrightness' ], - languageChapter: Chapter.SOURCE_3, + languageChapter: Chapter.SOURCE_3 }; export default ev3DeviceType; diff --git a/src/bundles/repeat/__tests__/index.ts b/src/bundles/repeat/__tests__/index.ts index e4e5537636..89b34fc881 100644 --- a/src/bundles/repeat/__tests__/index.ts +++ b/src/bundles/repeat/__tests__/index.ts @@ -1,17 +1,14 @@ -import { repeat, twice, thrice } from '../functions'; +import { repeat, thrice, twice } from '../functions'; // Test functions test('repeat works correctly and repeats function n times', () => { - expect(repeat((x: number) => x + 1, 5)(1)) - .toBe(6); + expect(repeat((x: number) => x + 1, 5)(1)).toBe(6); }); test('twice works correctly and repeats function twice', () => { - expect(twice((x: number) => x + 1)(1)) - .toBe(3); + expect(twice((x: number) => x + 1)(1)).toBe(3); }); test('thrice works correctly and repeats function thrice', () => { - expect(thrice((x: number) => x + 1)(1)) - .toBe(4); + expect(thrice((x: number) => x + 1)(1)).toBe(4); }); diff --git a/src/bundles/repeat/index.ts b/src/bundles/repeat/index.ts index 8e1023ae46..2275398b95 100644 --- a/src/bundles/repeat/index.ts +++ b/src/bundles/repeat/index.ts @@ -4,4 +4,4 @@ * @author Tang Xin Kye, Marcus */ -export { repeat, twice, thrice } from './functions'; +export { repeat, thrice, twice } from './functions'; diff --git a/src/bundles/repl/config.ts b/src/bundles/repl/config.ts index 9512fcdd8a..13768c7a78 100644 --- a/src/bundles/repl/config.ts +++ b/src/bundles/repl/config.ts @@ -4,7 +4,7 @@ export const COLOR_ERROR_MESSAGE = 'red'; export const FONT_MESSAGE = { fontFamily: 'Inconsolata, Consolas, monospace', fontSize: '16px', - fontWeight: 'normal', + fontWeight: 'normal' }; export const DEFAULT_EDITOR_HEIGHT = 375; export const MINIMUM_EDITOR_HEIGHT = 40; diff --git a/src/bundles/repl/functions.ts b/src/bundles/repl/functions.ts index 914ceea70b..2f3dd746f9 100644 --- a/src/bundles/repl/functions.ts +++ b/src/bundles/repl/functions.ts @@ -5,8 +5,8 @@ */ import context from 'js-slang/context'; -import { ProgrammableRepl } from './programmable_repl'; import { COLOR_REPL_DISPLAY_DEFAULT } from './config'; +import { ProgrammableRepl } from './programmable_repl'; const INSTANCE = new ProgrammableRepl(); context.moduleContexts.repl.state = INSTANCE; @@ -25,16 +25,17 @@ context.moduleContexts.repl.state = INSTANCE; */ export function set_evaluator(evalFunc: Function) { if (!(evalFunc instanceof Function)) { - const typeName = typeof (evalFunc); - throw new Error(`Wrong parameter type "${typeName}' in function "set_evaluator". It supposed to be a function and it's the entrance function of your metacircular evaulator.`); + const typeName = typeof evalFunc; + throw new Error( + `Wrong parameter type "${typeName}' in function "set_evaluator". It supposed to be a function and it's the entrance function of your metacircular evaulator.` + ); } INSTANCE.evalFunction = evalFunc; return { - toReplString: () => '', + toReplString: () => '' }; } - /** * Display message in Programmable Repl Tab * If you give a pair as the parameter, it will use the given pair to generate rich text and use rich text display mode to display the string in Programmable Repl Tab with undefined return value (see module description for more information). @@ -73,15 +74,14 @@ export function set_evaluator(evalFunc: Function) { * @param {content} the content you want to display * @category Main */ -export function repl_display(content: any) : any { +export function repl_display(content: any): any { if (INSTANCE.richDisplayInternal(content) === 'not_rich_text_pair') { - INSTANCE.pushOutputString(content.toString(), COLOR_REPL_DISPLAY_DEFAULT, 'plaintext');// students may set the value of the parameter "str" to types other than a string (for example "repl_display(1)" ). So here I need to first convert the parameter "str" into a string before preceding. + INSTANCE.pushOutputString(content.toString(), COLOR_REPL_DISPLAY_DEFAULT, 'plaintext'); // students may set the value of the parameter "str" to types other than a string (for example "repl_display(1)" ). So here I need to first convert the parameter "str" into a string before preceding. return content; } return undefined; } - /** * Set Programmable Repl editor background image with a customized image URL * @param {img_url} the url to the new background image @@ -89,12 +89,11 @@ export function repl_display(content: any) : any { * * @category Main */ -export function set_background_image(img_url: string, background_color_alpha: number) : void { +export function set_background_image(img_url: string, background_color_alpha: number): void { INSTANCE.customizedEditorProps.backgroundImageUrl = img_url; INSTANCE.customizedEditorProps.backgroundColorAlpha = background_color_alpha; } - /** * Set Programmable Repl editor font size * @param {font_size_px} font size (in pixel) @@ -102,7 +101,7 @@ export function set_background_image(img_url: string, background_color_alpha: nu * @category Main */ export function set_font_size(font_size_px: number) { - INSTANCE.customizedEditorProps.fontSize = parseInt(font_size_px.toString());// The TypeScript type checker will throw an error as "parseInt" in TypeScript only accepts one string as parameter. + INSTANCE.customizedEditorProps.fontSize = parseInt(font_size_px.toString()); // The TypeScript type checker will throw an error as "parseInt" in TypeScript only accepts one string as parameter. } /** @@ -112,8 +111,10 @@ export function set_font_size(font_size_px: number) { * * @category Main */ -export function default_js_slang(_program: string) : any { - throw new Error('Invaild Call: Function "default_js_slang" can not be directly called by user\'s code in editor. You should use it as the parameter of the function "set_evaluator"'); +export function default_js_slang(_program: string): any { + throw new Error( + 'Invaild Call: Function "default_js_slang" can not be directly called by user\'s code in editor. You should use it as the parameter of the function "set_evaluator"' + ); // When the function is normally called by set_evaluator function, safeKey is set to "document.body", which has a type "Element". // Students can not create objects and use HTML Elements in Source due to limitations and rules in Source, so they can't set the safeKey to a HTML Element, thus they can't use this function in Source. } diff --git a/src/bundles/repl/index.ts b/src/bundles/repl/index.ts index 456cccf5fb..02cab63339 100644 --- a/src/bundles/repl/index.ts +++ b/src/bundles/repl/index.ts @@ -35,12 +35,12 @@ * * @module repl * @author Wang Zihan -*/ + */ export { - set_evaluator, + default_js_slang, repl_display, set_background_image, - set_font_size, - default_js_slang, + set_evaluator, + set_font_size } from './functions'; diff --git a/src/bundles/repl/programmable_repl.ts b/src/bundles/repl/programmable_repl.ts index 77fa179729..cce0f3ad97 100644 --- a/src/bundles/repl/programmable_repl.ts +++ b/src/bundles/repl/programmable_repl.ts @@ -4,33 +4,37 @@ * @author Wang Zihan */ - +import { runFilesInContext, type IOptions } from 'js-slang'; import context from 'js-slang/context'; +import { COLOR_ERROR_MESSAGE, COLOR_RUN_CODE_RESULT, DEFAULT_EDITOR_HEIGHT } from './config'; import { default_js_slang } from './functions'; -import { runFilesInContext, type IOptions } from 'js-slang'; -import { COLOR_RUN_CODE_RESULT, COLOR_ERROR_MESSAGE, DEFAULT_EDITOR_HEIGHT } from './config'; export class ProgrammableRepl { public evalFunction: Function; + public userCodeInEditor: string; + public outputStrings: any[]; + private _editorInstance; + private _tabReactComponent: any; + // I store editorHeight value separately in here although it is already stored in the module's Tab React component state because I need to keep the editor height // when the Tab component is re-mounted due to the user drags the area between the module's Tab and Source Academy's original REPL to resize the module's Tab height. - public editorHeight : number; + public editorHeight: number; public customizedEditorProps = { backgroundImageUrl: 'no-background-image', backgroundColorAlpha: 1, - fontSize: 17, + fontSize: 17 }; constructor() { this.evalFunction = (_placeholder) => this.easterEggFunction(); this.userCodeInEditor = this.getSavedEditorContent(); this.outputStrings = []; - this._editorInstance = null;// To be set when calling "SetEditorInstance" in the ProgrammableRepl Tab React Component render function. + this._editorInstance = null; // To be set when calling "SetEditorInstance" in the ProgrammableRepl Tab React Component render function. this.editorHeight = DEFAULT_EDITOR_HEIGHT; developmentLog(this); } @@ -52,14 +56,20 @@ export class ProgrammableRepl { developmentLog(exception); // If the exception has a start line of -1 and an undefined error property, then this exception is most likely to be "incorrect number of arguments" caused by incorrect number of parameters in the evaluator entry function provided by students with set_evaluator. if (exception.location.start.line === -1 && exception.error === undefined) { - this.pushOutputString('Error: Unable to use your evaluator to run the code. Does your evaluator entry function contain and only contain exactly one parameter?', COLOR_ERROR_MESSAGE); + this.pushOutputString( + 'Error: Unable to use your evaluator to run the code. Does your evaluator entry function contain and only contain exactly one parameter?', + COLOR_ERROR_MESSAGE + ); } else { - this.pushOutputString(`Line ${exception.location.start.line.toString()}: ${exception.error?.message}`, COLOR_ERROR_MESSAGE); + this.pushOutputString( + `Line ${exception.location.start.line.toString()}: ${exception.error?.message}`, + COLOR_ERROR_MESSAGE + ); } this.reRenderTab(); return; } - if (typeof (retVal) === 'string') { + if (typeof retVal === 'string') { retVal = `"${retVal}"`; } // Here must use plain text output mode because retVal contains strings from the users. @@ -73,11 +83,11 @@ export class ProgrammableRepl { } // Rich text output method allow output strings to have html tags and css styles. - pushOutputString(content : string, textColor : string, outputMethod : string = 'plaintext') { - let tmp = { + pushOutputString(content: string, textColor: string, outputMethod: string = 'plaintext') { + const tmp = { content: content === undefined ? 'undefined' : content === null ? 'null' : content, color: textColor, - outputMethod, + outputMethod }; this.outputStrings.push(tmp); } @@ -90,7 +100,9 @@ export class ProgrammableRepl { developmentLog(breakpointLine); }); - this._editorInstance.setOptions({ fontSize: `${this.customizedEditorProps.fontSize.toString()}pt` }); + this._editorInstance.setOptions({ + fontSize: `${this.customizedEditorProps.fontSize.toString()}pt` + }); } richDisplayInternal(pair_rich_text) { @@ -99,54 +111,66 @@ export class ProgrammableRepl { const tail = (pair) => pair[1]; const is_pair = (obj) => obj instanceof Array && obj.length === 2; if (!is_pair(pair_rich_text)) return 'not_rich_text_pair'; - function checkColorStringValidity(htmlColor:string) { + function checkColorStringValidity(htmlColor: string) { if (htmlColor.length !== 7) return false; if (htmlColor[0] !== '#') return false; for (let i = 1; i < 7; i++) { const char = htmlColor[i]; developmentLog(` ${char}`); - if (!((char >= '0' && char <= '9') || (char >= 'A' && char <= 'F') || (char >= 'a' && char <= 'f'))) { + if ( + !( + (char >= '0' && char <= '9') || + (char >= 'A' && char <= 'F') || + (char >= 'a' && char <= 'f') + ) + ) { return false; } } return true; } function recursiveHelper(thisInstance, param): string { - if (typeof (param) === 'string') { + if (typeof param === 'string') { // There MUST be a safe check on users' strings, because users may insert something that can be interpreted as executable JavaScript code when outputing rich text. const safeCheckResult = thisInstance.userStringSafeCheck(param); if (safeCheckResult !== 'safe') { - throw new Error(`For safety matters, the character/word ${safeCheckResult} is not allowed in rich text output. Please remove it or use plain text output mode and try again.`); + throw new Error( + `For safety matters, the character/word ${safeCheckResult} is not allowed in rich text output. Please remove it or use plain text output mode and try again.` + ); } developmentLog(head(param)); return `">${param}`; } if (!is_pair(param)) { - throw new Error(`Unexpected data type ${typeof (param)} when processing rich text. It should be a pair.`); + throw new Error( + `Unexpected data type ${typeof param} when processing rich text. It should be a pair.` + ); } else { - const pairStyleToCssStyle : { [pairStyle : string] : string } = { + const pairStyleToCssStyle: { [pairStyle: string]: string } = { bold: 'font-weight:bold;', italic: 'font-style:italic;', small: 'font-size: 14px;', medium: 'font-size: 20px;', large: 'font-size: 25px;', gigantic: 'font-size: 50px;', - underline: 'text-decoration: underline;', + underline: 'text-decoration: underline;' }; - if (typeof (tail(param)) !== 'string') { - throw new Error(`The tail in style pair should always be a string, but got ${typeof (tail(param))}.`); + if (typeof tail(param) !== 'string') { + throw new Error( + `The tail in style pair should always be a string, but got ${typeof tail(param)}.` + ); } let style = ''; - if (tail(param) - .substring(0, 3) === 'clr') { + if (tail(param).substring(0, 3) === 'clr') { let prefix = ''; if (tail(param)[3] === 't') prefix = 'color:'; else if (tail(param)[3] === 'b') prefix = 'background-color:'; else throw new Error('Error when decoding rich text color data'); - const colorHex = tail(param) - .substring(4); + const colorHex = tail(param).substring(4); if (!checkColorStringValidity(colorHex)) { - throw new Error(`Invalid html color string ${colorHex}. It should start with # and followed by 6 characters representing a hex number.`); + throw new Error( + `Invalid html color string ${colorHex}. It should start with # and followed by 6 characters representing a hex number.` + ); } style = `${prefix + colorHex};`; } else { @@ -159,15 +183,26 @@ export class ProgrammableRepl { } } this.pushOutputString(`', 'script', 'javascript', 'eval', 'document', 'window', 'console', 'location']; - for (let word of forbiddenWords) { + const forbiddenWords = [ + '\\', + '<', + '>', + 'script', + 'javascript', + 'eval', + 'document', + 'window', + 'console', + 'location' + ]; + for (const word of forbiddenWords) { if (tmp.indexOf(word) !== -1) { return word; } @@ -182,63 +217,78 @@ export class ProgrammableRepl { runInJsSlang(code: string): string { developmentLog('js-slang context:'); // console.log(context); - const options : Partial = { + const options: Partial = { originalMaxExecTime: 1000, scheduler: 'preemptive', stepLimit: 1000, throwInfiniteLoops: true, - useSubst: false, + useSubst: false }; context.prelude = 'const display=(x)=>repl_display(x);'; context.errors = []; // Here if I don't manually clear the "errors" array in context, the remaining errors from the last evaluation will stop the function "preprocessFileImports" in preprocessor.ts of js-slang thus stop the whole evaluation. - const sourceFile : Record = { - '/ReplModuleUserCode.js': code, + const sourceFile: Record = { + '/ReplModuleUserCode.js': code }; - runFilesInContext(sourceFile, '/ReplModuleUserCode.js', context, options) - .then((evalResult) => { - if (evalResult.status === 'suspended' || evalResult.status === 'suspended-ec-eval') { - throw new Error('This should not happen'); - } - if (evalResult.status !== 'error') { - this.pushOutputString('js-slang program finished with value:', COLOR_RUN_CODE_RESULT); - // Here must use plain text output mode because evalResult.value contains strings from the users. - this.pushOutputString(evalResult.value === undefined ? 'undefined' : evalResult.value.toString(), COLOR_RUN_CODE_RESULT); - } else { - const errors = context.errors; - console.log(errors); - const errorCount = errors.length; - for (let i = 0; i < errorCount; i++) { - const error = errors[i]; - if (error.explain() - .indexOf('Name repl_display not declared.') !== -1) { - this.pushOutputString('[Error] It seems that you haven\'t import the function "repl_display" correctly when calling "set_evaluator" in Source Academy\'s main editor.', COLOR_ERROR_MESSAGE); - } else this.pushOutputString(`Line ${error.location.start.line}: ${error.type} Error: ${error.explain()} (${error.elaborate()})`, COLOR_ERROR_MESSAGE); + runFilesInContext(sourceFile, '/ReplModuleUserCode.js', context, options).then((evalResult) => { + if (evalResult.status === 'suspended' || evalResult.status === 'suspended-ec-eval') { + throw new Error('This should not happen'); + } + if (evalResult.status !== 'error') { + this.pushOutputString('js-slang program finished with value:', COLOR_RUN_CODE_RESULT); + // Here must use plain text output mode because evalResult.value contains strings from the users. + this.pushOutputString( + evalResult.value === undefined ? 'undefined' : evalResult.value.toString(), + COLOR_RUN_CODE_RESULT + ); + } else { + const errors = context.errors; + console.log(errors); + const errorCount = errors.length; + for (let i = 0; i < errorCount; i++) { + const error = errors[i]; + if (error.explain().indexOf('Name repl_display not declared.') !== -1) { + this.pushOutputString( + '[Error] It seems that you haven\'t import the function "repl_display" correctly when calling "set_evaluator" in Source Academy\'s main editor.', + COLOR_ERROR_MESSAGE + ); + } else { + this.pushOutputString( + `Line ${error.location.start.line}: ${ + error.type + } Error: ${error.explain()} (${error.elaborate()})`, + COLOR_ERROR_MESSAGE + ); } } - this.reRenderTab(); - }); + } + this.reRenderTab(); + }); return 'Async run in js-slang'; } - setTabReactComponentInstance(tab : any) { + setTabReactComponentInstance(tab: any) { this._tabReactComponent = tab; } private reRenderTab() { - this._tabReactComponent.setState({});// Forces the tab React Component to re-render using setState + this._tabReactComponent.setState({}); // Forces the tab React Component to re-render using setState } saveEditorContent() { localStorage.setItem('programmable_repl_saved_editor_code', this.userCodeInEditor.toString()); this.pushOutputString('Saved', 'lightgreen'); - this.pushOutputString('The saved code is stored locally in your browser. You may lose the saved code if you clear browser data or use another device.', 'gray', 'richtext'); + this.pushOutputString( + "The saved code is stored locally in your browser. You may lose the saved code if you clear browser data or use another device.", + 'gray', + 'richtext' + ); this.reRenderTab(); } private getSavedEditorContent() { - let savedContent = localStorage.getItem('programmable_repl_saved_editor_code'); + const savedContent = localStorage.getItem('programmable_repl_saved_editor_code'); if (savedContent === null) return ''; return savedContent; } @@ -246,11 +296,12 @@ export class ProgrammableRepl { // Small Easter Egg that doesn't affect module functionality and normal user experience :) // Please don't modify these text! Thanks! :) private easterEggFunction() { - this.pushOutputString('[Author (Wang Zihan)] ❤I love Keqing and Ganyu.❤', 'pink', 'richtext'); - this.pushOutputString('Showing my love to my favorite girls through a SA module, is that the so-called "romance of a programmer"?', 'gray', 'richtext'); - this.pushOutputString('❤❤❤❤❤', 'pink'); this.pushOutputString('
', 'white', 'richtext'); - this.pushOutputString('If you see this, please check whether you have called set_evaluator function with the correct parameter before using the Programmable Repl Tab.', 'yellow', 'richtext'); + this.pushOutputString( + "If you see this, please check whether you have called set_evaluator function with the correct parameter before using the Programmable Repl Tab.", + 'yellow', + 'richtext' + ); return 'Easter Egg!'; } } diff --git a/src/bundles/rune/display.ts b/src/bundles/rune/display.ts index d3e7723d21..19c20526cc 100644 --- a/src/bundles/rune/display.ts +++ b/src/bundles/rune/display.ts @@ -1,6 +1,6 @@ import context from 'js-slang/context'; import { AnaglyphRune, HollusionRune } from './functions'; -import { type DrawnRune, AnimatedRune, type Rune, NormalRune, type RuneAnimation } from './rune'; +import { AnimatedRune, NormalRune, type DrawnRune, type Rune, type RuneAnimation } from './rune'; import { throwIfNotRune } from './runes_ops'; // ============================================================================= @@ -9,7 +9,7 @@ import { throwIfNotRune } from './runes_ops'; const drawnRunes: (DrawnRune | AnimatedRune)[] = []; context.moduleContexts.rune.state = { - drawnRunes, + drawnRunes }; /** @@ -76,11 +76,7 @@ export function hollusion(rune: Rune): Rune { * * @category Main */ -export function animate_rune( - duration: number, - fps: number, - func: RuneAnimation, -) { +export function animate_rune(duration: number, fps: number, func: RuneAnimation) { const anim = new AnimatedRune(duration, fps, (n) => { const rune = func(n); throwIfNotRune(animate_rune.name, rune); @@ -99,11 +95,7 @@ export function animate_rune( * * @category Main */ -export function animate_anaglyph( - duration: number, - fps: number, - func: RuneAnimation, -) { +export function animate_anaglyph(duration: number, fps: number, func: RuneAnimation) { const anim = new AnimatedRune(duration, fps, (n) => { const rune = func(n); throwIfNotRune(animate_anaglyph.name, rune); diff --git a/src/bundles/rune/functions.ts b/src/bundles/rune/functions.ts index eea4c67a15..ab0ab65e79 100644 --- a/src/bundles/rune/functions.ts +++ b/src/bundles/rune/functions.ts @@ -1,36 +1,31 @@ import { mat4, vec3 } from 'gl-matrix'; +import { DrawnRune, Rune, drawRunesToFrameBuffer, type AnimatedRune } from './rune'; import { - Rune, - DrawnRune, - drawRunesToFrameBuffer, - type AnimatedRune, -} from './rune'; -import { - getSquare, + addColorFromHex, + colorPalette, getBlank, - getRcross, - getSail, - getTriangle, - getCorner, - getNova, getCircle, + getCorner, getHeart, + getNova, getPentagram, + getRcross, getRibbon, - throwIfNotRune, - addColorFromHex, - colorPalette, + getSail, + getSquare, + getTriangle, hexToColor, + throwIfNotRune } from './runes_ops'; import { - type FrameBufferWithTexture, getWebGlFromCanvas, initFramebufferObject, initShaderProgram, + type FrameBufferWithTexture } from './runes_webgl'; export type RuneModuleState = { - drawnRunes: (DrawnRune | AnimatedRune)[] + drawnRunes: (DrawnRune | AnimatedRune)[]; }; // ============================================================================= @@ -143,11 +138,7 @@ export function from_url(imageUrl: string): Rune { * * @category Main */ -export function scale_independent( - ratio_x: number, - ratio_y: number, - rune: Rune, -): Rune { +export function scale_independent(ratio_x: number, ratio_y: number, rune: Rune): Rune { throwIfNotRune(scale_independent.name, rune); const scaleVec = vec3.fromValues(ratio_x, ratio_y, 1); const scaleMat = mat4.create(); @@ -157,7 +148,7 @@ export function scale_independent( mat4.multiply(wrapperMat, scaleMat, wrapperMat); return Rune.of({ subRunes: [rune], - transformMatrix: wrapperMat, + transformMatrix: wrapperMat }); } @@ -193,7 +184,7 @@ export function translate(x: number, y: number, rune: Rune): Rune { mat4.multiply(wrapperMat, translateMat, wrapperMat); return Rune.of({ subRunes: [rune], - transformMatrix: wrapperMat, + transformMatrix: wrapperMat }); } @@ -217,7 +208,7 @@ export function rotate(rad: number, rune: Rune): Rune { mat4.multiply(wrapperMat, rotateMat, wrapperMat); return Rune.of({ subRunes: [rune], - transformMatrix: wrapperMat, + transformMatrix: wrapperMat }); } @@ -245,7 +236,7 @@ export function stack_frac(frac: number, rune1: Rune, rune2: Rune): Rune { const upper = translate(0, -(1 - frac), scale_independent(1, frac, rune1)); const lower = translate(0, frac, scale_independent(1, 1 - frac, rune2)); return Rune.of({ - subRunes: [upper, lower], + subRunes: [upper, lower] }); } @@ -346,7 +337,7 @@ export function beside_frac(frac: number, rune1: Rune, rune2: Rune): Rune { const left = translate(-(1 - frac), 0, scale_independent(frac, 1, rune1)); const right = translate(frac, 0, scale_independent(1 - frac, 1, rune2)); return Rune.of({ - subRunes: [left, right], + subRunes: [left, right] }); } @@ -407,7 +398,7 @@ export function make_cross(rune: Rune): Rune { throwIfNotRune(make_cross.name, rune); return stack( beside(quarter_turn_right(rune), rotate(Math.PI, rune)), - beside(rune, rotate(Math.PI / 2, rune)), + beside(rune, rotate(Math.PI / 2, rune)) ); } @@ -421,11 +412,7 @@ export function make_cross(rune: Rune): Rune { * * @category Main */ -export function repeat_pattern( - n: number, - pattern: (a: Rune) => Rune, - initial: Rune, -): Rune { +export function repeat_pattern(n: number, pattern: (a: Rune) => Rune, initial: Rune): Rune { if (n === 0) { return initial; } @@ -472,7 +459,7 @@ export function overlay_frac(frac: number, rune1: Rune, rune2: Rune): Rune { mat4.scale(frontMat, frontMat, vec3.fromValues(1, 1, useFrac)); const front = Rune.of({ subRunes: [rune1], - transformMatrix: frontMat, + transformMatrix: frontMat }); const backMat = mat4.create(); @@ -481,11 +468,11 @@ export function overlay_frac(frac: number, rune1: Rune, rune2: Rune): Rune { mat4.scale(backMat, backMat, vec3.fromValues(1, 1, 1 - useFrac)); const back = Rune.of({ subRunes: [rune2], - transformMatrix: backMat, + transformMatrix: backMat }); return Rune.of({ - subRunes: [front, back], // render front first to avoid redrawing + subRunes: [front, back] // render front first to avoid redrawing }); } @@ -526,7 +513,7 @@ export function color(rune: Rune, r: number, g: number, b: number): Rune { const colorVector = [r, g, b, 1]; return Rune.of({ colors: new Float32Array(colorVector), - subRunes: [rune], + subRunes: [rune] }); } @@ -541,13 +528,11 @@ export function color(rune: Rune, r: number, g: number, b: number): Rune { */ export function random_color(rune: Rune): Rune { throwIfNotRune(random_color.name, rune); - const randomColor = hexToColor( - colorPalette[Math.floor(Math.random() * colorPalette.length)], - ); + const randomColor = hexToColor(colorPalette[Math.floor(Math.random() * colorPalette.length)]); return Rune.of({ colors: new Float32Array(randomColor), - subRunes: [rune], + subRunes: [rune] }); } @@ -728,14 +713,14 @@ export class AnaglyphRune extends DrawnRune { leftCameraMatrix, vec3.fromValues(-halfEyeDistance, 0, 0), vec3.fromValues(0, 0, -0.4), - vec3.fromValues(0, 1, 0), + vec3.fromValues(0, 1, 0) ); const rightCameraMatrix = mat4.create(); mat4.lookAt( rightCameraMatrix, vec3.fromValues(halfEyeDistance, 0, 0), vec3.fromValues(0, 0, -0.4), - vec3.fromValues(0, 1, 0), + vec3.fromValues(0, 1, 0) ); // left/right eye images are drawn into respective framebuffers @@ -747,7 +732,7 @@ export class AnaglyphRune extends DrawnRune { leftCameraMatrix, new Float32Array([1, 0, 0, 1]), leftBuffer.framebuffer, - true, + true ); drawRunesToFrameBuffer( gl, @@ -755,7 +740,7 @@ export class AnaglyphRune extends DrawnRune { rightCameraMatrix, new Float32Array([0, 1, 1, 1]), rightBuffer.framebuffer, - true, + true ); // prepare to draw to screen by setting framebuffer to null @@ -764,15 +749,12 @@ export class AnaglyphRune extends DrawnRune { const shaderProgram = initShaderProgram( gl, AnaglyphRune.anaglyphVertexShader, - AnaglyphRune.anaglyphFragmentShader, + AnaglyphRune.anaglyphFragmentShader ); gl.useProgram(shaderProgram); const reduPt = gl.getUniformLocation(shaderProgram, 'u_sampler_red'); const cyanuPt = gl.getUniformLocation(shaderProgram, 'u_sampler_cyan'); - const vertexPositionPointer = gl.getAttribLocation( - shaderProgram, - 'a_position', - ); + const vertexPositionPointer = gl.getAttribLocation(shaderProgram, 'a_position'); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, leftBuffer.texture); @@ -849,7 +831,7 @@ export class HollusionRune extends DrawnRune { cameraMatrix, vec3.fromValues(xshift, 0, 0), vec3.fromValues(0, 0, -0.4), - vec3.fromValues(0, 1, 0), + vec3.fromValues(0, 1, 0) ); drawRunesToFrameBuffer( @@ -858,7 +840,7 @@ export class HollusionRune extends DrawnRune { cameraMatrix, new Float32Array([1, 1, 1, 1]), fb.framebuffer, - true, + true ); return fb; }; @@ -871,14 +853,11 @@ export class HollusionRune extends DrawnRune { const copyShaderProgram = initShaderProgram( gl, HollusionRune.copyVertexShader, - HollusionRune.copyFragmentShader, + HollusionRune.copyFragmentShader ); gl.useProgram(copyShaderProgram); const texturePt = gl.getUniformLocation(copyShaderProgram, 'uTexture'); - const vertexPositionPointer = gl.getAttribLocation( - copyShaderProgram, - 'a_position', - ); + const vertexPositionPointer = gl.getAttribLocation(copyShaderProgram, 'a_position'); gl.bindFramebuffer(gl.FRAMEBUFFER, null); const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); @@ -892,11 +871,10 @@ export class HollusionRune extends DrawnRune { lastTime = timeInMs; - const framePos - = Math.floor(timeInMs / (period / frameCount)) % frameCount; + const framePos = Math.floor(timeInMs / (period / frameCount)) % frameCount; const fbObject = frameBuffer[framePos]; gl.clearColor(1.0, 1.0, 1.0, 1.0); // Set clear color to white, fully opaque - // eslint-disable-next-line no-bitwise + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear the viewport gl.activeTexture(gl.TEXTURE0); diff --git a/src/bundles/rune/index.ts b/src/bundles/rune/index.ts index 24729248e2..59f7bf1620 100644 --- a/src/bundles/rune/index.ts +++ b/src/bundles/rune/index.ts @@ -42,13 +42,13 @@ export { scale_independent, square, stack, - stackn, stack_frac, + stackn, translate, triangle, turn_upside_down, white, - yellow, + yellow } from './functions'; export { @@ -57,5 +57,5 @@ export { animate_rune, hollusion, hollusion_magnitude, - show, + show } from './display'; diff --git a/src/bundles/rune/rune.ts b/src/bundles/rune/rune.ts index d1ab043a97..04e432b1ea 100644 --- a/src/bundles/rune/rune.ts +++ b/src/bundles/rune/rune.ts @@ -1,5 +1,5 @@ import { mat4 } from 'gl-matrix'; -import { type AnimFrame, glAnimation } from '../../typings/anim_types'; +import { glAnimation, type AnimFrame } from '../../typings/anim_types'; import type { ReplResult } from '../../typings/type_helpers'; import { getWebGlFromCanvas, initShaderProgram } from './runes_webgl'; @@ -64,17 +64,18 @@ export class Rune { public transformMatrix: mat4, public subRunes: Rune[], public texture: HTMLImageElement | null, - public hollusionDistance: number, + public hollusionDistance: number ) {} - public copy = () => new Rune( - this.vertices, - this.colors, - mat4.clone(this.transformMatrix), - this.subRunes, - this.texture, - this.hollusionDistance, - ); + public copy = () => + new Rune( + this.vertices, + this.colors, + mat4.clone(this.transformMatrix), + this.subRunes, + this.texture, + this.hollusionDistance + ); /** * Flatten the subrunes to return a list of runes @@ -92,7 +93,7 @@ export class Rune { mat4.multiply( subRuneCopy.transformMatrix, runeToExpand.transformMatrix, - subRuneCopy.transformMatrix, + subRuneCopy.transformMatrix ); subRuneCopy.hollusionDistance = runeToExpand.hollusionDistance; if (runeToExpand.colors !== null) { @@ -116,9 +117,10 @@ export class Rune { subRunes?: Rune[]; texture?: HTMLImageElement | null; hollusionDistance?: number; - } = {}, + } = {} ) => { - const paramGetter = (name: string, defaultValue: () => any) => (params[name] === undefined ? defaultValue() : params[name]); + const paramGetter = (name: string, defaultValue: () => any) => + params[name] === undefined ? defaultValue() : params[name]; return new Rune( paramGetter('vertices', () => new Float32Array()), @@ -126,7 +128,7 @@ export class Rune { paramGetter('transformMatrix', mat4.create), paramGetter('subRunes', () => []), paramGetter('texture', () => null), - paramGetter('hollusionDistance', () => 0.1), + paramGetter('hollusionDistance', () => 0.1) ); }; @@ -145,54 +147,26 @@ export function drawRunesToFrameBuffer( cameraMatrix: mat4, colorFilter: Float32Array, framebuffer: WebGLFramebuffer | null = null, - depthSwitch: boolean = false, + depthSwitch: boolean = false ) { // step 1: initiate the WebGLRenderingContext gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); // step 2: initiate the shaderProgram - const shaderProgram = initShaderProgram( - gl, - normalVertexShader, - normalFragmentShader, - ); + const shaderProgram = initShaderProgram(gl, normalVertexShader, normalFragmentShader); gl.useProgram(shaderProgram); if (gl === null) { throw Error('Rendering Context not initialized for drawRune.'); } // create pointers to the data-entries of the shader program - const vertexPositionPointer = gl.getAttribLocation( - shaderProgram, - 'aVertexPosition', - ); - const vertexColorPointer = gl.getUniformLocation( - shaderProgram, - 'uVertexColor', - ); - const vertexColorFilterPt = gl.getUniformLocation( - shaderProgram, - 'uColorFilter', - ); - const projectionMatrixPointer = gl.getUniformLocation( - shaderProgram, - 'uProjectionMatrix', - ); - const cameraMatrixPointer = gl.getUniformLocation( - shaderProgram, - 'uCameraMatrix', - ); - const modelViewMatrixPointer = gl.getUniformLocation( - shaderProgram, - 'uModelViewMatrix', - ); - const textureSwitchPointer = gl.getUniformLocation( - shaderProgram, - 'uRenderWithTexture', - ); - const depthSwitchPointer = gl.getUniformLocation( - shaderProgram, - 'uRenderWithDepthColor', - ); + const vertexPositionPointer = gl.getAttribLocation(shaderProgram, 'aVertexPosition'); + const vertexColorPointer = gl.getUniformLocation(shaderProgram, 'uVertexColor'); + const vertexColorFilterPt = gl.getUniformLocation(shaderProgram, 'uColorFilter'); + const projectionMatrixPointer = gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'); + const cameraMatrixPointer = gl.getUniformLocation(shaderProgram, 'uCameraMatrix'); + const modelViewMatrixPointer = gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'); + const textureSwitchPointer = gl.getUniformLocation(shaderProgram, 'uRenderWithTexture'); + const depthSwitchPointer = gl.getUniformLocation(shaderProgram, 'uRenderWithDepthColor'); const texturePointer = gl.getUniformLocation(shaderProgram, 'uTexture'); // load depth @@ -217,7 +191,6 @@ export function drawRunesToFrameBuffer( const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); function isPowerOf2(value) { - // eslint-disable-next-line no-bitwise return (value & (value - 1)) === 0; } // Because images have to be downloaded over the internet @@ -242,18 +215,11 @@ export function drawRunesToFrameBuffer( border, srcFormat, srcType, - pixel, + pixel ); gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D( - gl.TEXTURE_2D, - level, - internalFormat, - srcFormat, - srcType, - image, - ); + gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, srcFormat, srcType, image); // WebGL1 has different requirements for power of 2 images // vs non power of 2 images so check if the image is a @@ -282,10 +248,7 @@ export function drawRunesToFrameBuffer( // load color/texture if (rune.texture === null) { - gl.uniform4fv( - vertexColorPointer, - rune.colors || new Float32Array([0, 0, 0, 1]), - ); + gl.uniform4fv(vertexColorPointer, rune.colors || new Float32Array([0, 0, 0, 1])); gl.uniform1i(textureSwitchPointer, 0); } else { const texture = loadTexture(rune.texture); @@ -356,10 +319,7 @@ export abstract class DrawnRune implements ReplResult { } `; - constructor( - protected readonly rune: Rune, - public readonly isHollusion: boolean, - ) {} + constructor(protected readonly rune: Rune, public readonly isHollusion: boolean) {} public toReplString = () => ''; @@ -384,7 +344,7 @@ export class NormalRune extends DrawnRune { cameraMatrix, new Float32Array([1, 1, 1, 1]), null, - true, + true ); }; } @@ -393,18 +353,14 @@ export class NormalRune extends DrawnRune { export type RuneAnimation = (time: number) => Rune; export class AnimatedRune extends glAnimation implements ReplResult { - constructor( - duration: number, - fps: number, - private readonly func: (frame: number) => DrawnRune, - ) { + constructor(duration: number, fps: number, private readonly func: (frame: number) => DrawnRune) { super(duration, fps); } public getFrame(num: number): AnimFrame { const rune = this.func(num); return { - draw: rune.draw, + draw: rune.draw }; } diff --git a/src/bundles/rune/runes_ops.ts b/src/bundles/rune/runes_ops.ts index ae12f23e47..e9cfeae7fc 100644 --- a/src/bundles/rune/runes_ops.ts +++ b/src/bundles/rune/runes_ops.ts @@ -36,7 +36,7 @@ export const getSquare: () => Rune = () => { return Rune.of({ vertices: new Float32Array(vertexList), - colors: new Float32Array(colorList), + colors: new Float32Array(colorList) }); }; @@ -77,7 +77,7 @@ export const getRcross: () => Rune = () => { return Rune.of({ vertices: new Float32Array(vertexList), - colors: new Float32Array(colorList), + colors: new Float32Array(colorList) }); }; @@ -96,7 +96,7 @@ export const getSail: () => Rune = () => { return Rune.of({ vertices: new Float32Array(vertexList), - colors: new Float32Array(colorList), + colors: new Float32Array(colorList) }); }; @@ -115,7 +115,7 @@ export const getTriangle: () => Rune = () => { return Rune.of({ vertices: new Float32Array(vertexList), - colors: new Float32Array(colorList), + colors: new Float32Array(colorList) }); }; @@ -134,7 +134,7 @@ export const getCorner: () => Rune = () => { return Rune.of({ vertices: new Float32Array(vertexList), - colors: new Float32Array(colorList), + colors: new Float32Array(colorList) }); }; @@ -158,7 +158,7 @@ export const getNova: () => Rune = () => { return Rune.of({ vertices: new Float32Array(vertexList), - colors: new Float32Array(colorList), + colors: new Float32Array(colorList) }); }; @@ -180,7 +180,7 @@ export const getCircle: () => Rune = () => { return Rune.of({ vertices: new Float32Array(vertexList), - colors: new Float32Array(colorList), + colors: new Float32Array(colorList) }); }; @@ -206,13 +206,13 @@ export const getHeart: () => Rune = () => { (Math.cos(angle1) * r + rightCenterX) * scaleX, Math.sin(angle1) * r + rightCenterY, 0, - 1, + 1 ); vertexList.push( (Math.cos(angle2) * r + rightCenterX) * scaleX, Math.sin(angle2) * r + rightCenterY, 0, - 1, + 1 ); vertexList.push(0, -1, 0, 1); } @@ -226,13 +226,13 @@ export const getHeart: () => Rune = () => { (Math.cos(angle1) * r + leftCenterX) * scaleX, Math.sin(angle1) * r + leftCenterY, 0, - 1, + 1 ); vertexList.push( (Math.cos(angle2) * r + leftCenterX) * scaleX, Math.sin(angle2) * r + leftCenterY, 0, - 1, + 1 ); vertexList.push(0, -1, 0, 1); } @@ -241,7 +241,7 @@ export const getHeart: () => Rune = () => { return Rune.of({ vertices: new Float32Array(vertexList), - colors: new Float32Array(colorList), + colors: new Float32Array(colorList) }); }; @@ -275,7 +275,7 @@ export const getPentagram: () => Rune = () => { return Rune.of({ vertices: new Float32Array(vertexList), - colors: new Float32Array(colorList), + colors: new Float32Array(colorList) }); }; @@ -293,17 +293,12 @@ export const getRibbon: () => Rune = () => { const vertices: number[][] = []; for (let i = 0; i < thetaMax; i += unit) { - vertices.push([ - (i / thetaMax) * Math.cos(i), - (i / thetaMax) * Math.sin(i), - 0, - 1, - ]); + vertices.push([(i / thetaMax) * Math.cos(i), (i / thetaMax) * Math.sin(i), 0, 1]); vertices.push([ Math.abs(Math.cos(i) * thickness) + (i / thetaMax) * Math.cos(i), Math.abs(Math.sin(i) * thickness) + (i / thetaMax) * Math.sin(i), 0, - 1, + 1 ]); } for (let i = 0; i < vertices.length - 2; i += 1) { @@ -316,7 +311,7 @@ export const getRibbon: () => Rune = () => { return Rune.of({ vertices: new Float32Array(vertexList), - colors: new Float32Array(colorList), + colors: new Float32Array(colorList) }); }; @@ -334,13 +329,11 @@ export const colorPalette = [ '#4CAF50', '#FFEB3B', '#FF9800', - '#795548', + '#795548' ]; export function hexToColor(hex: string): number[] { - const result = /^#?(?[a-f\d]{2})(?[a-f\d]{2})(?[a-f\d]{2})$/iu.exec( - hex, - ); + const result = /^#?(?[a-f\d]{2})(?[a-f\d]{2})(?[a-f\d]{2})$/iu.exec(hex); if (result === null || result.length < 4) { return [0, 0, 0]; } @@ -348,7 +341,7 @@ export function hexToColor(hex: string): number[] { parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255, - 1, + 1 ]; } @@ -356,6 +349,6 @@ export function addColorFromHex(rune: Rune, hex: string) { throwIfNotRune(addColorFromHex.name, rune); return Rune.of({ subRunes: [rune], - colors: new Float32Array(hexToColor(hex)), + colors: new Float32Array(hexToColor(hex)) }); } diff --git a/src/bundles/rune/runes_webgl.ts b/src/bundles/rune/runes_webgl.ts index 1b57d7b660..6e4dd3ef78 100644 --- a/src/bundles/rune/runes_webgl.ts +++ b/src/bundles/rune/runes_webgl.ts @@ -16,11 +16,7 @@ export type FrameBufferWithTexture = { * @param source - Source code of the shader * @returns WebGLShader used to initialize shader program */ -function loadShader( - gl: WebGLRenderingContext, - type: number, - source: string, -): WebGLShader { +function loadShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader { const shader = gl.createShader(type); if (!shader) { throw new Error('WebGLShader not available.'); @@ -46,7 +42,7 @@ function loadShader( export function initShaderProgram( gl: WebGLRenderingContext, vsSource: string, - fsSource: string, + fsSource: string ): WebGLProgram { const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource); const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource); @@ -65,9 +61,7 @@ export function initShaderProgram( * @param canvas WebGLRenderingContext * @returns */ -export function getWebGlFromCanvas( - canvas: HTMLCanvasElement, -): WebGLRenderingContext { +export function getWebGlFromCanvas(canvas: HTMLCanvasElement): WebGLRenderingContext { const gl: WebGLRenderingContext | null = canvas.getContext('webgl'); if (!gl) { throw Error('Unable to initialize WebGL.'); @@ -75,7 +69,7 @@ export function getWebGlFromCanvas( gl.clearColor(1.0, 1.0, 1.0, 1.0); // Set clear color to white, fully opaque gl.enable(gl.DEPTH_TEST); // Enable depth testing gl.depthFunc(gl.LESS); // Near things obscure far things (this is default setting can omit) - // eslint-disable-next-line no-bitwise + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear the viewport return gl; } @@ -85,9 +79,7 @@ export function getWebGlFromCanvas( * @param gl WebGLRenderingContext * @returns FrameBufferWithTexture */ -export function initFramebufferObject( - gl: WebGLRenderingContext, -): FrameBufferWithTexture { +export function initFramebufferObject(gl: WebGLRenderingContext): FrameBufferWithTexture { // create a framebuffer object const framebuffer = gl.createFramebuffer(); if (!framebuffer) { @@ -109,7 +101,7 @@ export function initFramebufferObject( 0, gl.RGBA, gl.UNSIGNED_BYTE, - null, + null ); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); @@ -125,25 +117,14 @@ export function initFramebufferObject( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, gl.drawingBufferWidth, - gl.drawingBufferHeight, + gl.drawingBufferHeight ); // set the texture object to the framebuffer object gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); // bind to target - gl.framebufferTexture2D( - gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0, - gl.TEXTURE_2D, - texture, - 0, - ); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); // set the renderbuffer object to the framebuffer object - gl.framebufferRenderbuffer( - gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.RENDERBUFFER, - depthBuffer, - ); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer); // check whether the framebuffer is configured correctly const e = gl.checkFramebufferStatus(gl.FRAMEBUFFER); @@ -158,6 +139,6 @@ export function initFramebufferObject( return { framebuffer, - texture, + texture }; } diff --git a/src/bundles/rune_in_words/index.ts b/src/bundles/rune_in_words/index.ts index e0a061b50d..6760855650 100644 --- a/src/bundles/rune_in_words/index.ts +++ b/src/bundles/rune_in_words/index.ts @@ -45,11 +45,11 @@ export { show, square, stack, - stackn, stack_frac, + stackn, translate, triangle, turn_upside_down, white, - yellow, + yellow } from './functions'; diff --git a/src/bundles/rune_in_words/runes_ops.ts b/src/bundles/rune_in_words/runes_ops.ts index 26d75ee321..51e31dac07 100644 --- a/src/bundles/rune_in_words/runes_ops.ts +++ b/src/bundles/rune_in_words/runes_ops.ts @@ -52,7 +52,7 @@ export const colorPalette = [ '#4CAF50', '#FFEB3B', '#FF9800', - '#795548', + '#795548' ]; export function addColorFromHex(rune, hex) { diff --git a/src/bundles/scrabble/functions.ts b/src/bundles/scrabble/functions.ts index df3ae75a69..c9559dce82 100644 --- a/src/bundles/scrabble/functions.ts +++ b/src/bundles/scrabble/functions.ts @@ -5,7 +5,6 @@ * @module scrabble */ - /** * `scrabble_words` is an array of strings, each representing * an allowed word in Scrabble. diff --git a/src/bundles/sound/__tests__/sound.test.ts b/src/bundles/sound/__tests__/sound.test.ts index 865055ae42..733bfba382 100644 --- a/src/bundles/sound/__tests__/sound.test.ts +++ b/src/bundles/sound/__tests__/sound.test.ts @@ -1,38 +1,41 @@ -import { make_sound, play, play_in_tab } from '../functions' +import { make_sound, play, play_in_tab } from '../functions'; describe('Test make_sound', () => { test('Should error gracefully when duration is negative', () => { - expect(() => make_sound(() => 0, -1)) - .toThrowErrorMatchingInlineSnapshot('"Sound duration must be greater than or equal to 0"') - }) + expect(() => make_sound(() => 0, -1)).toThrowErrorMatchingInlineSnapshot( + '"Sound duration must be greater than or equal to 0"' + ); + }); test('Should not error when duration is zero', () => { - expect(() => make_sound(() => 0, 0)).not.toThrow() - }) -}) + expect(() => make_sound(() => 0, 0)).not.toThrow(); + }); +}); describe('Test play', () => { test('Should error gracefully when duration is negative', () => { - const sound = [t => 0, -1]; - expect(() => play(sound as any)) - .toThrowErrorMatchingInlineSnapshot('"play: duration of sound is negative"') - }) + const sound = [(t) => 0, -1]; + expect(() => play(sound as any)).toThrowErrorMatchingInlineSnapshot( + '"play: duration of sound is negative"' + ); + }); test('Should not error when duration is zero', () => { - const sound = make_sound(t => 0, 0); - expect(() => play(sound)).not.toThrow() - }) -}) + const sound = make_sound((t) => 0, 0); + expect(() => play(sound)).not.toThrow(); + }); +}); describe('Test play_in_tab', () => { test('Should error gracefully when duration is negative', () => { - const sound = [t => 0, -1]; - expect(() => play_in_tab(sound as any)) - .toThrowErrorMatchingInlineSnapshot('"play_in_tab: duration of sound is negative"') - }) + const sound = [(t) => 0, -1]; + expect(() => play_in_tab(sound as any)).toThrowErrorMatchingInlineSnapshot( + '"play_in_tab: duration of sound is negative"' + ); + }); test('Should not error when duration is zero', () => { - const sound = make_sound(t => 0, 0); - expect(() => play_in_tab(sound)).not.toThrow() - }) -}) \ No newline at end of file + const sound = make_sound((t) => 0, 0); + expect(() => play_in_tab(sound)).not.toThrow(); + }); +}); diff --git a/src/bundles/sound/functions.ts b/src/bundles/sound/functions.ts index ba29d19ef5..be2fdb43c6 100644 --- a/src/bundles/sound/functions.ts +++ b/src/bundles/sound/functions.ts @@ -1,24 +1,18 @@ -/* eslint-disable new-cap, @typescript-eslint/naming-convention */ -import type { - Wave, - Sound, - SoundProducer, - SoundTransformer, - AudioPlayed, -} from './types'; +/* eslint-disable @typescript-eslint/naming-convention */ +import context from 'js-slang/context'; import { - pair, + accumulate, head, - tail, - list, - length, is_null, is_pair, - accumulate, - type List, + length, + list, + pair, + tail, + type List } from 'js-slang/dist/stdlib/list'; import { RIFFWAVE } from './riffwave'; -import context from 'js-slang/context'; +import type { AudioPlayed, Sound, SoundProducer, SoundTransformer, Wave } from './types'; // Global Constants and Variables const FS: number = 44100; // Output sample rate @@ -26,7 +20,7 @@ const fourier_expansion_level: number = 5; // fourier expansion level const audioPlayed: AudioPlayed[] = []; context.moduleContexts.sound.state = { - audioPlayed, + audioPlayed }; // Singular audio context for all playback functions @@ -66,9 +60,7 @@ let recorded_sound: Sound | undefined; // to record a sound function check_permission() { if (permission === undefined) { - throw new Error( - 'Call init_record(); to obtain permission to use microphone', - ); + throw new Error('Call init_record(); to obtain permission to use microphone'); } else if (permission === false) { throw new Error(`Permission has been denied.\n Re-start browser and call init_record();\n @@ -104,7 +96,6 @@ function play_recording_signal() { play(sine_sound(1200, recording_signal_ms / 1000)); } -// eslint-disable-next-line @typescript-eslint/no-shadow function process(data) { const audioContext = new AudioContext(); const blob = new Blob(data); @@ -117,8 +108,7 @@ function process(data) { // Converts input microphone sound (blob) into array format. function convertToArrayBuffer(blob: Blob): Promise { const url = URL.createObjectURL(blob); - return fetch(url) - .then((response) => response.arrayBuffer()); + return fetch(url).then((response) => response.arrayBuffer()); } function save(audioBuffer: AudioBuffer) { @@ -142,9 +132,7 @@ function save(audioBuffer: AudioBuffer) { * @returns string "obtaining recording permission" */ export function init_record(): string { - navigator.mediaDevices - .getUserMedia({ audio: true }) - .then(rememberStream, setPermissionToFalse); + navigator.mediaDevices.getUserMedia({ audio: true }).then(rememberStream, setPermissionToFalse); return 'obtaining recording permission'; } @@ -288,11 +276,7 @@ export function get_duration(sound: Sound): number { * @example is_sound(make_sound(t => 0, 2)); // Returns true */ export function is_sound(x: any): x is Sound { - return ( - is_pair(x) - && typeof get_wave(x) === 'function' - && typeof get_duration(x) === 'number' - ); + return is_pair(x) && typeof get_wave(x) === 'function' && typeof get_duration(x) === 'number'; } /** @@ -374,7 +358,7 @@ export function play_in_tab(sound: Sound): Sound { const soundToPlay = { toReplString: () => '', - dataUri: riffwave.dataURI, + dataUri: riffwave.dataURI }; audioPlayed.push(soundToPlay); return sound; @@ -392,9 +376,7 @@ export function play_in_tab(sound: Sound): Sound { export function play(sound: Sound): Sound { // Type-check sound if (!is_sound(sound)) { - throw new Error( - `${play.name} is expecting sound, but encountered ${sound}`, - ); + throw new Error(`${play.name} is expecting sound, but encountered ${sound}`); } else if (get_duration(sound) < 0) { throw new Error(`${play.name}: duration of sound is negative`); } else if (get_duration(sound) === 0) { @@ -406,11 +388,7 @@ export function play(sound: Sound): Sound { } // Create mono buffer - const theBuffer = audioplayer.createBuffer( - 1, - Math.ceil(FS * get_duration(sound)), - FS, - ); + const theBuffer = audioplayer.createBuffer(1, Math.ceil(FS * get_duration(sound)), FS); const channel = theBuffer.getChannelData(0); let temp: number; @@ -510,10 +488,7 @@ export function square_sound(f: number, duration: number): Sound { } return answer; } - return make_sound( - (t) => (4 / Math.PI) * fourier_expansion_square(t), - duration, - ); + return make_sound((t) => (4 / Math.PI) * fourier_expansion_square(t), duration); } /** @@ -528,16 +503,11 @@ export function triangle_sound(freq: number, duration: number): Sound { function fourier_expansion_triangle(t: number) { let answer = 0; for (let i = 0; i < fourier_expansion_level; i += 1) { - answer - += ((-1) ** i * Math.sin((2 * i + 1) * t * freq * Math.PI * 2)) - / (2 * i + 1) ** 2; + answer += ((-1) ** i * Math.sin((2 * i + 1) * t * freq * Math.PI * 2)) / (2 * i + 1) ** 2; } return answer; } - return make_sound( - (t) => (8 / Math.PI / Math.PI) * fourier_expansion_triangle(t), - duration, - ); + return make_sound((t) => (8 / Math.PI / Math.PI) * fourier_expansion_triangle(t), duration); } /** @@ -556,10 +526,7 @@ export function sawtooth_sound(freq: number, duration: number): Sound { } return answer; } - return make_sound( - (t) => 1 / 2 - (1 / Math.PI) * fourier_expansion_sawtooth(t), - duration, - ); + return make_sound((t) => 1 / 2 - (1 / Math.PI) * fourier_expansion_sawtooth(t), duration); } // Composition Operators @@ -636,7 +603,7 @@ export function adsr( attack_ratio: number, decay_ratio: number, sustain_level: number, - release_ratio: number, + release_ratio: number ): SoundTransformer { return (sound) => { const wave = get_wave(sound); @@ -650,19 +617,14 @@ export function adsr( } if (x < attack_time + decay_time) { return ( - ((1 - sustain_level) * linear_decay(decay_time)(x - attack_time) - + sustain_level) - * wave(x) + ((1 - sustain_level) * linear_decay(decay_time)(x - attack_time) + sustain_level) * + wave(x) ); } if (x < duration - release_time) { return wave(x) * sustain_level; } - return ( - wave(x) - * sustain_level - * linear_decay(release_time)(x - (duration - release_time)) - ); + return wave(x) * sustain_level * linear_decay(release_time)(x - (duration - release_time)); }, duration); }; } @@ -687,7 +649,7 @@ export function stacking_adsr( waveform: SoundProducer, base_frequency: number, duration: number, - envelopes: List, + envelopes: List ): Sound { function zip(lst: List, n: number) { if (is_null(lst)) { @@ -700,8 +662,8 @@ export function stacking_adsr( accumulate( (x: any, y: any) => pair(tail(x)(waveform(base_frequency * head(x), duration)), y), null, - zip(envelopes, 1), - ), + zip(envelopes, 1) + ) ); } @@ -719,15 +681,9 @@ export function stacking_adsr( * @return function which takes in a Sound and returns a Sound * @example phase_mod(440, 5, 1)(sine_sound(220, 5)); */ -export function phase_mod( - freq: number, - duration: number, - amount: number, -): SoundTransformer { - return (modulator: Sound) => make_sound( - (t) => Math.sin(2 * Math.PI * t * freq + amount * get_wave(modulator)(t)), - duration, - ); +export function phase_mod(freq: number, duration: number, amount: number): SoundTransformer { + return (modulator: Sound) => + make_sound((t) => Math.sin(2 * Math.PI * t * freq + amount * get_wave(modulator)(t)), duration); } // MIDI conversion functions @@ -837,8 +793,8 @@ export function bell(note: number, duration: number): Sound { adsr(0, 0.6, 0, 0.05), adsr(0, 0.6618, 0, 0.05), adsr(0, 0.7618, 0, 0.05), - adsr(0, 0.9071, 0, 0.05), - ), + adsr(0, 0.9071, 0, 0.05) + ) ); } @@ -855,7 +811,7 @@ export function cello(note: number, duration: number): Sound { square_sound, midi_note_to_frequency(note), duration, - list(adsr(0.05, 0, 1, 0.1), adsr(0.05, 0, 1, 0.15), adsr(0, 0, 0.2, 0.15)), + list(adsr(0.05, 0, 1, 0.1), adsr(0.05, 0, 1, 0.15), adsr(0, 0, 0.2, 0.15)) ); } @@ -872,7 +828,7 @@ export function piano(note: number, duration: number): Sound { triangle_sound, midi_note_to_frequency(note), duration, - list(adsr(0, 0.515, 0, 0.05), adsr(0, 0.32, 0, 0.05), adsr(0, 0.2, 0, 0.05)), + list(adsr(0, 0.515, 0, 0.05), adsr(0, 0.32, 0, 0.05), adsr(0, 0.2, 0, 0.05)) ); } @@ -889,7 +845,7 @@ export function trombone(note: number, duration: number): Sound { square_sound, midi_note_to_frequency(note), duration, - list(adsr(0.2, 0, 1, 0.1), adsr(0.3236, 0.6, 0, 0.1)), + list(adsr(0.2, 0, 1, 0.1), adsr(0.3236, 0.6, 0, 0.1)) ); } @@ -910,7 +866,7 @@ export function violin(note: number, duration: number): Sound { adsr(0.35, 0, 1, 0.15), adsr(0.35, 0, 1, 0.15), adsr(0.45, 0, 1, 0.15), - adsr(0.45, 0, 1, 0.15), - ), + adsr(0.45, 0, 1, 0.15) + ) ); } diff --git a/src/bundles/sound/index.ts b/src/bundles/sound/index.ts index c456f65f13..c593ec447b 100644 --- a/src/bundles/sound/index.ts +++ b/src/bundles/sound/index.ts @@ -45,9 +45,9 @@ export { noise_sound, phase_mod, piano, + play, // Play-related play_in_tab, - play, play_wave, record, record_for, @@ -60,5 +60,5 @@ export { stop, triangle_sound, trombone, - violin, + violin } from './functions'; diff --git a/src/bundles/sound/riffwave.ts b/src/bundles/sound/riffwave.ts index 70a1af7516..425f803c8f 100644 --- a/src/bundles/sound/riffwave.ts +++ b/src/bundles/sound/riffwave.ts @@ -54,7 +54,7 @@ var FastBase64 = { dst += '='; } return dst; - }, // end Encode + } // end Encode }; FastBase64.Init(); @@ -78,7 +78,7 @@ export function RIFFWAVE(this: any, data) { blockAlign: 0, // 32 2 NumChannels*BitsPerSample/8 bitsPerSample: 8, // 34 2 8 bits = 8, 16 bits = 16 subChunk2Id: [0x64, 0x61, 0x74, 0x61], // 36 4 "data" = 0x64617461 - subChunk2Size: 0, // 40 4 data size = NumSamples*NumChannels*BitsPerSample/8 + subChunk2Size: 0 // 40 4 data size = NumSamples*NumChannels*BitsPerSample/8 }; function u32ToArray(i) { @@ -102,11 +102,9 @@ export function RIFFWAVE(this: any, data) { this.Make = function (data: any) { if (data instanceof Array) this.data = data; - this.header.blockAlign = - (this.header.numChannels * this.header.bitsPerSample) >> 3; + this.header.blockAlign = (this.header.numChannels * this.header.bitsPerSample) >> 3; this.header.byteRate = this.header.blockAlign * this.sampleRate; - this.header.subChunk2Size = - this.data.length * (this.header.bitsPerSample >> 3); + this.header.subChunk2Size = this.data.length * (this.header.bitsPerSample >> 3); this.header.chunkSize = 36 + this.header.subChunk2Size; this.wav = this.header.chunkId.concat( diff --git a/src/bundles/sound/types.ts b/src/bundles/sound/types.ts index 6691438b1a..5bb246878d 100644 --- a/src/bundles/sound/types.ts +++ b/src/bundles/sound/types.ts @@ -4,15 +4,12 @@ export type Wave = (...t: any) => number; export type Sound = Pair; export type SoundProducer = (...t: any) => Sound; export type SoundTransformer = (s: Sound) => Sound; -export type ErrorLogger = ( - error: string | string[], - isSlangError?: boolean -) => void; +export type ErrorLogger = (error: string | string[], isSlangError?: boolean) => void; export type AudioPlayed = { toReplString: () => string; dataUri: string; }; export type SoundModuleState = { - audioPlayed: AudioPlayed[] + audioPlayed: AudioPlayed[]; }; diff --git a/src/bundles/sound_matrix/functions.ts b/src/bundles/sound_matrix/functions.ts index 102d2d6d93..4413261f6e 100644 --- a/src/bundles/sound_matrix/functions.ts +++ b/src/bundles/sound_matrix/functions.ts @@ -10,14 +10,14 @@ */ /* eslint-disable @typescript-eslint/no-unused-vars */ -import type { List } from './types'; import { list_to_vector, vector_to_list } from './list'; +import type { List } from './types'; export const ToneMatrix = { initialise_matrix, clear_matrix, randomise_matrix, - bindMatrixButtons, + bindMatrixButtons }; let $tone_matrix: HTMLCanvasElement; // canvas container for tone matrix @@ -48,12 +48,8 @@ let timeout_objects: number[] = []; // set_timeout_renamed return type // given the x, y coordinates of a "click" event // return the row and column numbers of the clicked square in an array function x_y_to_row_column(x: number, y: number): number[] { - const row = Math.floor( - (y - margin_length) / (square_side_length + distance_between_squares), - ); - const column = Math.floor( - (x - margin_length) / (square_side_length + distance_between_squares), - ); + const row = Math.floor((y - margin_length) / (square_side_length + distance_between_squares)); + const column = Math.floor((x - margin_length) / (square_side_length + distance_between_squares)); return [row, column]; } @@ -64,9 +60,7 @@ function row_to_y(row: number): number { // given the column number of a square, return the topmost coordinate function column_to_x(column: number): number { - return ( - margin_length + column * (square_side_length + distance_between_squares) - ); + return margin_length + column * (square_side_length + distance_between_squares); } // return a list representing a particular row @@ -99,12 +93,7 @@ function set_color(row: number, column: number, color: string): void { const ctx = $tone_matrix.getContext('2d') as CanvasRenderingContext2D; ctx.fillStyle = color; - ctx.fillRect( - column_to_x(column), - row_to_y(row), - square_side_length, - square_side_length, - ); + ctx.fillRect(column_to_x(column), row_to_y(row), square_side_length, square_side_length); } // highlight a given square @@ -113,11 +102,7 @@ function highlight_color(row: number, column: number, color: string): void { } // given the square that we are supposed to highlight, color the neighboring squares -function set_adjacent_color_1( - row: number, - column: number, - color: string, -): void { +function set_adjacent_color_1(row: number, column: number, color: string): void { if (!is_on(row, column - 1)) { set_color(row, column - 1, color); } @@ -136,11 +121,7 @@ function set_adjacent_color_1( } // given the square that we are supposed to highlight, color the squares 2 units from it -function set_adjacent_color_2( - row: number, - column: number, - color: string, -): void { +function set_adjacent_color_2(row: number, column: number, color: string): void { if (!is_on(row, column - 2)) { set_color(row, column - 2, color); } @@ -244,7 +225,7 @@ function bind_events_to_rect(c) { set_color(row, column, color_off); } }, - false, + false ); } @@ -341,9 +322,7 @@ ToneMatrix.bindMatrixButtons = bindMatrixButtons; // return the current state of the matrix, represented by a list of lists of bits export function get_matrix(): List { if (!matrix) { - throw new Error( - 'Please activate the tone matrix first by clicking on the tab!', - ); + throw new Error('Please activate the tone matrix first by clicking on the tab!'); } const matrix_list = matrix.slice(0); const result: List[] = []; @@ -378,9 +357,7 @@ export function set_timeout(f, t) { const timeoutObj = set_time_out_renamed(f, t); timeout_objects.push(timeoutObj); } else { - throw new Error( - 'set_timeout(f, t) expects a function and a number respectively.', - ); + throw new Error('set_timeout(f, t) expects a function and a number respectively.'); } } diff --git a/src/bundles/sound_matrix/index.ts b/src/bundles/sound_matrix/index.ts index 0651ea9b91..95bc3e522e 100644 --- a/src/bundles/sound_matrix/index.ts +++ b/src/bundles/sound_matrix/index.ts @@ -8,8 +8,8 @@ export { // Constructor/Accessors/Typecheck ToneMatrix, - get_matrix, - clear_matrix, - set_timeout, clear_all_timeout, + clear_matrix, + get_matrix, + set_timeout } from './functions'; diff --git a/src/bundles/sound_matrix/list.ts b/src/bundles/sound_matrix/list.ts index 7d9d3f7837..1be02e6ef5 100644 --- a/src/bundles/sound_matrix/list.ts +++ b/src/bundles/sound_matrix/list.ts @@ -1,4 +1,4 @@ -/* eslint-disable no-else-return, no-lonely-if, operator-assignment, prefer-template */ +/* eslint-disable no-else-return, operator-assignment, prefer-template */ // list.js: Supporting lists in the Scheme style, using pairs made // up of two-element JavaScript array (vector) @@ -38,9 +38,7 @@ export function head(xs): any { if (is_pair(xs)) { return xs[0]; } else { - throw new Error( - 'head(xs) expects a pair as argument xs, but encountered ' + xs, - ); + throw new Error('head(xs) expects a pair as argument xs, but encountered ' + xs); } } @@ -51,9 +49,7 @@ export function tail(xs) { if (is_pair(xs)) { return xs[1]; } else { - throw new Error( - 'tail(xs) expects a pair as argument xs, but encountered ' + xs, - ); + throw new Error('tail(xs) expects a pair as argument xs, but encountered ' + xs); } } @@ -142,9 +138,7 @@ export function map(f, xs) { export function build_list(n, fun) { if (typeof n !== 'number' || n < 0 || Math.floor(n) !== n) { throw new Error( - 'build_list(n, fun) expects a positive integer as ' - + 'argument n, but encountered ' - + n, + 'build_list(n, fun) expects a positive integer as argument n, but encountered ' + n ); } @@ -170,9 +164,7 @@ export function build_list(n, fun) { // tslint:disable-next-line:ban-types export function for_each(fun, xs) { if (!is_list(xs)) { - throw new Error( - 'for_each expects a list as argument xs, but encountered ' + xs, - ); + throw new Error('for_each expects a list as argument xs, but encountered ' + xs); } for (; !is_null(xs); xs = tail(xs)) { fun(head(xs)); @@ -184,9 +176,7 @@ export function for_each(fun, xs) { // reverse throws an exception if the argument is not a list. export function reverse(xs) { if (!is_list(xs)) { - throw new Error( - 'reverse(xs) expects a list as argument xs, but encountered ' + xs, - ); + throw new Error('reverse(xs) expects a list as argument xs, but encountered ' + xs); } let result: any = null; for (; !is_null(xs); xs = tail(xs)) { @@ -296,14 +286,12 @@ export function filter(pred, xs) { export function enum_list(start, end) { if (typeof start !== 'number') { throw new Error( - 'enum_list(start, end) expects a number as argument start, but encountered ' - + start, + 'enum_list(start, end) expects a number as argument start, but encountered ' + start ); } if (typeof end !== 'number') { throw new Error( - 'enum_list(start, end) expects a number as argument start, but encountered ' - + end, + 'enum_list(start, end) expects a number as argument start, but encountered ' + end ); } if (start > end) { @@ -317,8 +305,7 @@ export function enum_list(start, end) { export function list_ref(xs, n) { if (typeof n !== 'number' || n < 0 || Math.floor(n) !== n) { throw new Error( - 'list_ref(xs, n) expects a positive integer as argument n, but encountered ' - + n, + 'list_ref(xs, n) expects a positive integer as argument n, but encountered ' + n ); } for (; n > 0; --n) { @@ -351,9 +338,7 @@ export function set_head(xs, x) { xs[0] = x; return undefined; } else { - throw new Error( - 'set_head(xs,x) expects a pair as argument xs, but encountered ' + xs, - ); + throw new Error('set_head(xs,x) expects a pair as argument xs, but encountered ' + xs); } } @@ -365,8 +350,6 @@ export function set_tail(xs, x) { xs[1] = x; return undefined; } else { - throw new Error( - 'set_tail(xs,x) expects a pair as argument xs, but encountered ' + xs, - ); + throw new Error('set_tail(xs,x) expects a pair as argument xs, but encountered ' + xs); } } diff --git a/src/bundles/stereo_sound/functions.ts b/src/bundles/stereo_sound/functions.ts index 42f3b7a84d..8b82b9b13b 100644 --- a/src/bundles/stereo_sound/functions.ts +++ b/src/bundles/stereo_sound/functions.ts @@ -1,4 +1,5 @@ -/* eslint-disable new-cap, @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/naming-convention */ +import context from 'js-slang/context'; import { accumulate, head, @@ -8,17 +9,10 @@ import { list, pair, tail, - type List, + type List } from 'js-slang/dist/stdlib/list'; import { RIFFWAVE } from './riffwave'; -import type { - AudioPlayed, - Sound, - SoundProducer, - SoundTransformer, - Wave, -} from './types'; -import context from 'js-slang/context'; +import type { AudioPlayed, Sound, SoundProducer, SoundTransformer, Wave } from './types'; // Global Constants and Variables @@ -27,7 +21,7 @@ const fourier_expansion_level: number = 5; // fourier expansion level const audioPlayed: AudioPlayed[] = []; context.moduleContexts.stereo_sound.state = { - audioPlayed, + audioPlayed }; // Singular audio context for all playback functions @@ -67,9 +61,7 @@ let recorded_sound: Sound | undefined; // to record a sound function check_permission() { if (permission === undefined) { - throw new Error( - 'Call init_record(); to obtain permission to use microphone', - ); + throw new Error('Call init_record(); to obtain permission to use microphone'); } else if (permission === false) { throw new Error(`Permission has been denied.\n Re-start browser and call init_record();\n @@ -103,7 +95,6 @@ function play_recording_signal() { play(sine_sound(1200, recording_signal_duration_ms / 1000)); } -// eslint-disable-next-line @typescript-eslint/no-shadow function process(data: any[] | undefined) { const audioContext = new AudioContext(); const blob = new Blob(data); @@ -116,8 +107,7 @@ function process(data: any[] | undefined) { // Converts input microphone sound (blob) into array format. function convertToArrayBuffer(blob: Blob): Promise { const url = URL.createObjectURL(blob); - return fetch(url) - .then((response) => response.arrayBuffer()); + return fetch(url).then((response) => response.arrayBuffer()); } function save(audioBuffer: AudioBuffer) { @@ -141,9 +131,7 @@ function save(audioBuffer: AudioBuffer) { * @returns string "obtaining recording permission" */ export function init_record(): string { - navigator.mediaDevices - .getUserMedia({ audio: true }) - .then(rememberStream, setPermissionToFalse); + navigator.mediaDevices.getUserMedia({ audio: true }).then(rememberStream, setPermissionToFalse); return 'obtaining recording permission'; } @@ -241,17 +229,13 @@ export function record_for(duration: number, buffer: number): () => Sound { * @return resulting stereo Sound * @example const s = make_stereo_sound(t => math_sin(2 * math_PI * 440 * t), t => math_sin(2 * math_PI * 300 * t), 5); */ -export function make_stereo_sound( - left_wave: Wave, - right_wave: Wave, - duration: number, -): Sound { +export function make_stereo_sound(left_wave: Wave, right_wave: Wave, duration: number): Sound { return pair( pair( (t: number) => (t >= duration ? 0 : left_wave(t)), - (t: number) => (t >= duration ? 0 : right_wave(t)), + (t: number) => (t >= duration ? 0 : right_wave(t)) ), - duration, + duration ); } @@ -312,10 +296,10 @@ export function get_duration(sound: Sound): number { */ export function is_sound(x: any): boolean { return ( - is_pair(x) - && typeof get_left_wave(x) === 'function' - && typeof get_right_wave(x) === 'function' - && typeof get_duration(x) === 'number' + is_pair(x) && + typeof get_left_wave(x) === 'function' && + typeof get_right_wave(x) === 'function' && + typeof get_duration(x) === 'number' ); } @@ -341,11 +325,7 @@ export function play_wave(wave: Wave, duration: number): Sound { * @return the given Sound * @example play_waves(t => math_sin(t * 3000), t => math_sin(t * 6000), 5); */ -export function play_waves( - wave1: Wave, - wave2: Wave, - duration: number, -): Sound { +export function play_waves(wave1: Wave, wave2: Wave, duration: number): Sound { return play(make_stereo_sound(wave1, wave2, duration)); } @@ -397,10 +377,7 @@ export function play_in_tab(sound: Sound): Sound { } // smoothen out sudden cut-outs - if ( - channel[2 * i] === 0 - && Math.abs(channel[2 * i] - Lprev_value) > 0.01 - ) { + if (channel[2 * i] === 0 && Math.abs(channel[2 * i] - Lprev_value) > 0.01) { channel[2 * i] = Lprev_value * 0.999; } @@ -417,10 +394,7 @@ export function play_in_tab(sound: Sound): Sound { } // smoothen out sudden cut-outs - if ( - channel[2 * i + 1] === 0 - && Math.abs(channel[2 * i] - Rprev_value) > 0.01 - ) { + if (channel[2 * i + 1] === 0 && Math.abs(channel[2 * i] - Rprev_value) > 0.01) { channel[2 * i + 1] = Rprev_value * 0.999; } @@ -440,7 +414,7 @@ export function play_in_tab(sound: Sound): Sound { const audio = { toReplString: () => '', - dataUri: riffwave.dataURI, + dataUri: riffwave.dataURI }; audioPlayed.push(audio); @@ -495,10 +469,7 @@ export function play(sound: Sound): Sound { } // smoothen out sudden cut-outs - if ( - channel[2 * i] === 0 - && Math.abs(channel[2 * i] - Lprev_value) > 0.01 - ) { + if (channel[2 * i] === 0 && Math.abs(channel[2 * i] - Lprev_value) > 0.01) { channel[2 * i] = Lprev_value * 0.999; } @@ -515,10 +486,7 @@ export function play(sound: Sound): Sound { } // smoothen out sudden cut-outs - if ( - channel[2 * i + 1] === 0 - && Math.abs(channel[2 * i] - Rprev_value) > 0.01 - ) { + if (channel[2 * i + 1] === 0 && Math.abs(channel[2 * i] - Rprev_value) > 0.01) { channel[2 * i + 1] = Rprev_value * 0.999; } @@ -593,7 +561,7 @@ export function pan(amount: number): SoundTransformer { return make_stereo_sound( (t) => ((1 - amount) / 2) * get_left_wave(sound)(t), (t) => ((1 + amount) / 2) * get_right_wave(sound)(t), - get_duration(sound), + get_duration(sound) ); }; } @@ -623,7 +591,7 @@ export function pan_mod(modulator: Sound): SoundTransformer { return make_stereo_sound( (t) => ((1 - amount(t)) / 2) * get_left_wave(sound)(t), (t) => ((1 + amount(t)) / 2) * get_right_wave(sound)(t), - get_duration(sound), + get_duration(sound) ); }; } @@ -680,10 +648,7 @@ export function square_sound(f: number, duration: number): Sound { } return answer; } - return make_sound( - (t) => (4 / Math.PI) * fourier_expansion_square(t), - duration, - ); + return make_sound((t) => (4 / Math.PI) * fourier_expansion_square(t), duration); } /** @@ -698,16 +663,11 @@ export function triangle_sound(freq: number, duration: number): Sound { function fourier_expansion_triangle(t: number) { let answer = 0; for (let i = 0; i < fourier_expansion_level; i += 1) { - answer - += ((-1) ** i * Math.sin((2 * i + 1) * t * freq * Math.PI * 2)) - / (2 * i + 1) ** 2; + answer += ((-1) ** i * Math.sin((2 * i + 1) * t * freq * Math.PI * 2)) / (2 * i + 1) ** 2; } return answer; } - return make_sound( - (t) => (8 / Math.PI / Math.PI) * fourier_expansion_triangle(t), - duration, - ); + return make_sound((t) => (8 / Math.PI / Math.PI) * fourier_expansion_triangle(t), duration); } /** @@ -726,10 +686,7 @@ export function sawtooth_sound(freq: number, duration: number): Sound { } return answer; } - return make_sound( - (t) => 1 / 2 - (1 / Math.PI) * fourier_expansion_sawtooth(t), - duration, - ); + return make_sound((t) => 1 / 2 - (1 / Math.PI) * fourier_expansion_sawtooth(t), duration); } // Composition Operators @@ -784,11 +741,7 @@ export function simultaneously(list_of_sounds: List): Sound { return make_stereo_sound(new_left, new_right, new_dur); } - const unnormed = accumulate( - stereo_simul_two, - silence_sound(0), - list_of_sounds, - ); + const unnormed = accumulate(stereo_simul_two, silence_sound(0), list_of_sounds); const sounds_length = length(list_of_sounds); const normalised_left = (t: number) => head(head(unnormed))(t) / sounds_length; const normalised_right = (t: number) => tail(head(unnormed))(t) / sounds_length; @@ -815,7 +768,7 @@ export function adsr( attack_ratio: number, decay_ratio: number, sustain_level: number, - release_ratio: number, + release_ratio: number ): SoundTransformer { return (sound) => { const Lwave = get_left_wave(sound); @@ -832,19 +785,14 @@ export function adsr( } if (x < attack_time + decay_time) { return ( - ((1 - sustain_level) * linear_decay(decay_time)(x - attack_time) - + sustain_level) - * wave(x) + ((1 - sustain_level) * linear_decay(decay_time)(x - attack_time) + sustain_level) * + wave(x) ); } if (x < duration - release_time) { return wave(x) * sustain_level; } - return ( - wave(x) - * sustain_level - * linear_decay(release_time)(x - (duration - release_time)) - ); + return wave(x) * sustain_level * linear_decay(release_time)(x - (duration - release_time)); }; } return make_stereo_sound(adsrHelper(Lwave), adsrHelper(Rwave), duration); @@ -871,7 +819,7 @@ export function stacking_adsr( waveform: SoundProducer, base_frequency: number, duration: number, - envelopes: List, + envelopes: List ): Sound { function zip(lst: List, n: number) { if (is_null(lst)) { @@ -884,8 +832,8 @@ export function stacking_adsr( accumulate( (x: any, y: any) => pair(tail(x)(waveform(base_frequency * head(x), duration)), y), null, - zip(envelopes, 1), - ), + zip(envelopes, 1) + ) ); } @@ -903,18 +851,13 @@ export function stacking_adsr( * @return function which takes in a Sound and returns a Sound * @example phase_mod(440, 5, 1)(sine_sound(220, 5)); */ -export function phase_mod( - freq: number, - duration: number, - amount: number, -): SoundTransformer { - return (modulator: Sound) => make_stereo_sound( - (t) => Math.sin(2 * Math.PI * t * freq + amount * get_left_wave(modulator)(t)), - (t) => Math.sin( - 2 * Math.PI * t * freq + amount * get_right_wave(modulator)(t), - ), - duration, - ); +export function phase_mod(freq: number, duration: number, amount: number): SoundTransformer { + return (modulator: Sound) => + make_stereo_sound( + (t) => Math.sin(2 * Math.PI * t * freq + amount * get_left_wave(modulator)(t)), + (t) => Math.sin(2 * Math.PI * t * freq + amount * get_right_wave(modulator)(t)), + duration + ); } // MIDI conversion functions @@ -1024,8 +967,8 @@ export function bell(note: number, duration: number): Sound { adsr(0, 0.6, 0, 0.05), adsr(0, 0.6618, 0, 0.05), adsr(0, 0.7618, 0, 0.05), - adsr(0, 0.9071, 0, 0.05), - ), + adsr(0, 0.9071, 0, 0.05) + ) ); } @@ -1042,7 +985,7 @@ export function cello(note: number, duration: number): Sound { square_sound, midi_note_to_frequency(note), duration, - list(adsr(0.05, 0, 1, 0.1), adsr(0.05, 0, 1, 0.15), adsr(0, 0, 0.2, 0.15)), + list(adsr(0.05, 0, 1, 0.1), adsr(0.05, 0, 1, 0.15), adsr(0, 0, 0.2, 0.15)) ); } @@ -1059,7 +1002,7 @@ export function piano(note: number, duration: number): Sound { triangle_sound, midi_note_to_frequency(note), duration, - list(adsr(0, 0.515, 0, 0.05), adsr(0, 0.32, 0, 0.05), adsr(0, 0.2, 0, 0.05)), + list(adsr(0, 0.515, 0, 0.05), adsr(0, 0.32, 0, 0.05), adsr(0, 0.2, 0, 0.05)) ); } @@ -1076,7 +1019,7 @@ export function trombone(note: number, duration: number): Sound { square_sound, midi_note_to_frequency(note), duration, - list(adsr(0.2, 0, 1, 0.1), adsr(0.3236, 0.6, 0, 0.1)), + list(adsr(0.2, 0, 1, 0.1), adsr(0.3236, 0.6, 0, 0.1)) ); } @@ -1097,7 +1040,7 @@ export function violin(note: number, duration: number): Sound { adsr(0.35, 0, 1, 0.15), adsr(0.35, 0, 1, 0.15), adsr(0.45, 0, 1, 0.15), - adsr(0.45, 0, 1, 0.15), - ), + adsr(0.45, 0, 1, 0.15) + ) ); } diff --git a/src/bundles/stereo_sound/riffwave.ts b/src/bundles/stereo_sound/riffwave.ts index 70a1af7516..425f803c8f 100644 --- a/src/bundles/stereo_sound/riffwave.ts +++ b/src/bundles/stereo_sound/riffwave.ts @@ -54,7 +54,7 @@ var FastBase64 = { dst += '='; } return dst; - }, // end Encode + } // end Encode }; FastBase64.Init(); @@ -78,7 +78,7 @@ export function RIFFWAVE(this: any, data) { blockAlign: 0, // 32 2 NumChannels*BitsPerSample/8 bitsPerSample: 8, // 34 2 8 bits = 8, 16 bits = 16 subChunk2Id: [0x64, 0x61, 0x74, 0x61], // 36 4 "data" = 0x64617461 - subChunk2Size: 0, // 40 4 data size = NumSamples*NumChannels*BitsPerSample/8 + subChunk2Size: 0 // 40 4 data size = NumSamples*NumChannels*BitsPerSample/8 }; function u32ToArray(i) { @@ -102,11 +102,9 @@ export function RIFFWAVE(this: any, data) { this.Make = function (data: any) { if (data instanceof Array) this.data = data; - this.header.blockAlign = - (this.header.numChannels * this.header.bitsPerSample) >> 3; + this.header.blockAlign = (this.header.numChannels * this.header.bitsPerSample) >> 3; this.header.byteRate = this.header.blockAlign * this.sampleRate; - this.header.subChunk2Size = - this.data.length * (this.header.bitsPerSample >> 3); + this.header.subChunk2Size = this.data.length * (this.header.bitsPerSample >> 3); this.header.chunkSize = 36 + this.header.subChunk2Size; this.wav = this.header.chunkId.concat( diff --git a/src/bundles/stereo_sound/types.ts b/src/bundles/stereo_sound/types.ts index 30d2036de5..c5caa97f40 100644 --- a/src/bundles/stereo_sound/types.ts +++ b/src/bundles/stereo_sound/types.ts @@ -4,10 +4,7 @@ export type Wave = (...t: any) => number; export type Sound = Pair, number>; export type SoundProducer = (...t: any) => Sound; export type SoundTransformer = (s: Sound) => Sound; -export type ErrorLogger = ( - error: string | string[], - isSlangError?: boolean -) => void; +export type ErrorLogger = (error: string | string[], isSlangError?: boolean) => void; export type AudioPlayed = { toReplString: () => string; dataUri: string; diff --git a/src/bundles/unity_academy/UnityAcademy.tsx b/src/bundles/unity_academy/UnityAcademy.tsx index ae12804a68..9c5419be1d 100644 --- a/src/bundles/unity_academy/UnityAcademy.tsx +++ b/src/bundles/unity_academy/UnityAcademy.tsx @@ -5,74 +5,76 @@ * @author Wang Zihan */ -import { UNITY_ACADEMY_BACKEND_URL, BUILD_NAME } from './config'; -import React from 'react'; -import ReactDOM from 'react-dom'; import { Button } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import { Vector3, normalizeVector, zeroVector, pointDistance } from './UnityAcademyMaths'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Vector3, normalizeVector, pointDistance, zeroVector } from './UnityAcademyMaths'; +import { BUILD_NAME, UNITY_ACADEMY_BACKEND_URL } from './config'; type Transform = { - position : Vector3; - rotation : Vector3; - scale : Vector3; + position: Vector3; + rotation: Vector3; + scale: Vector3; }; type StudentGameObject = { - startMethod : Function | null; - updateMethod : Function | null; - onCollisionEnterMethod : Function | null; - onCollisionStayMethod : Function | null; - onCollisionExitMethod : Function | null; - transform : Transform; - rigidbody : RigidbodyData | null; - audioSource : AudioSourceData | null; - customProperties : any; - isDestroyed : boolean; // [set by interop] + startMethod: Function | null; + updateMethod: Function | null; + onCollisionEnterMethod: Function | null; + onCollisionStayMethod: Function | null; + onCollisionExitMethod: Function | null; + transform: Transform; + rigidbody: RigidbodyData | null; + audioSource: AudioSourceData | null; + customProperties: any; + isDestroyed: boolean; // [set by interop] }; type InputData = { - keyboardInputInfo : { [key : string] : number }; + keyboardInputInfo: { [key: string]: number }; }; type RigidbodyData = { - velocity : Vector3; - angularVelocity : Vector3; - mass : number; - useGravity : boolean; - drag : number; - angularDrag : number; + velocity: Vector3; + angularVelocity: Vector3; + mass: number; + useGravity: boolean; + drag: number; + angularDrag: number; }; type AudioSourceData = { - audioClipIdentifier : AudioClipIdentifier; - playSpeed : number; - playProgress : number; - volume : number; - isLooping : boolean; - isPlaying : boolean; + audioClipIdentifier: AudioClipIdentifier; + playSpeed: number; + playProgress: number; + volume: number; + isLooping: boolean; + isPlaying: boolean; }; +declare const createUnityInstance: Function; // This function comes from {BUILD_NAME}.loader.js in Unity Academy Application (For Example: ua-frontend-prod.loader.js) -declare const createUnityInstance : Function; // This function comes from {BUILD_NAME}.loader.js in Unity Academy Application (For Example: ua-frontend-prod.loader.js) - -export function getInstance() : UnityAcademyJsInteropContext { +export function getInstance(): UnityAcademyJsInteropContext { return (window as any).unityAcademyContext as UnityAcademyJsInteropContext; } type AudioClipInternalName = string; -export class AudioClipIdentifier { // A wrapper class to store identifier string and prevent users from using arbitrary string for idenfitier - audioClipInternalName : AudioClipInternalName; - constructor(audioClipInternalName : string) { +export class AudioClipIdentifier { + // A wrapper class to store identifier string and prevent users from using arbitrary string for idenfitier + audioClipInternalName: AudioClipInternalName; + + constructor(audioClipInternalName: string) { this.audioClipInternalName = audioClipInternalName; } } +export class GameObjectIdentifier { + // A wrapper class to store identifier string and prevent users from using arbitrary string for idenfitier + gameObjectIdentifier: string; -export class GameObjectIdentifier { // A wrapper class to store identifier string and prevent users from using arbitrary string for idenfitier - gameObjectIdentifier : string; - constructor(gameObjectIdentifier : string) { + constructor(gameObjectIdentifier: string) { this.gameObjectIdentifier = gameObjectIdentifier; } } @@ -90,58 +92,74 @@ class UnityComponent extends React.Component { render() { const moduleInstance = getInstance(); return ( - //
-
-
+
-

Preparing to load Unity Academy...

-
-
- +
+ zIndex: '-1' + }} + > +

+ Preparing to load Unity Academy... +

+
+
+
@@ -149,13 +167,10 @@ class UnityComponent extends React.Component { } componentDidMount() { - getInstance() - .firstTimeLoadUnityApplication(); + getInstance().firstTimeLoadUnityApplication(); } } - - const UNITY_CONFIG = { loaderUrl: `${UNITY_ACADEMY_BACKEND_URL}frontend/${BUILD_NAME}.loader.js`, dataUrl: `${UNITY_ACADEMY_BACKEND_URL}frontend/${BUILD_NAME}.data.gz`, @@ -164,28 +179,43 @@ const UNITY_CONFIG = { streamingAssetsUrl: `${UNITY_ACADEMY_BACKEND_URL}webgl_assetbundles`, companyName: 'Wang Zihan @ NUS SoC 2026', productName: 'Unity Academy (Source Academy Embedding Version)', - productVersion: 'See \'About\' in the embedded frontend.', + productVersion: "See 'About' in the embedded frontend." }; - class UnityAcademyJsInteropContext { // private unityConfig : any; - public unityInstance : any; - private unityContainerElement : HTMLElement | null; - private studentGameObjectStorage : { [gameObjectIdentifier : string] : StudentGameObject }; // [get by interop] + public unityInstance: any; + + private unityContainerElement: HTMLElement | null; + + private studentGameObjectStorage: { [gameObjectIdentifier: string]: StudentGameObject }; // [get by interop] + private prefabInfo: any; + private gameObjectIdentifierSerialCounter = 0; - private studentActionQueue : any; // [get / clear by interop] + + private studentActionQueue: any; // [get / clear by interop] + private deltaTime = 0; // [set by interop] - private input : InputData; // [set by interop] 0 = key idle, 1 = on key down, 2 = holding key, 3 = on key up - public gameObjectIdentifierWrapperClass : any; // [get by interop] For interop to create the class instance with the correct type when calling users' Start and Update functions. Only the object with this class type can pass checkGameObjectIdentifierParameter in functions.ts - private targetFrameRate : number; + + private input: InputData; // [set by interop] 0 = key idle, 1 = on key down, 2 = holding key, 3 = on key up + + public gameObjectIdentifierWrapperClass: any; // [get by interop] For interop to create the class instance with the correct type when calling users' Start and Update functions. Only the object with this class type can pass checkGameObjectIdentifierParameter in functions.ts + + private targetFrameRate: number; + private unityInstanceState; // [set by interop] - private guiData : any[]; // [get / clear by interop] + + private guiData: any[]; // [get / clear by interop] + public dimensionMode; - private isShowingUnityAcademy : boolean; // [get by interop] - private latestUserAgreementVersion : string; - private audioClipStorage : AudioClipInternalName[]; + + private isShowingUnityAcademy: boolean; // [get by interop] + + private latestUserAgreementVersion: string; + + private audioClipStorage: AudioClipInternalName[]; + private audioClipIdentifierSerialCounter = 0; constructor() { @@ -202,7 +232,7 @@ class UnityAcademyJsInteropContext { this.audioClipStorage = []; this.guiData = []; this.input = { - keyboardInputInfo: {}, + keyboardInputInfo: {} }; this.targetFrameRate = 30; @@ -269,7 +299,6 @@ class UnityAcademyJsInteropContext { this.createUnityAcademyInstance(); } - setShowUnityComponent(resolution: number) { const toShow = resolution > 0; this.isShowingUnityAcademy = toShow; @@ -297,7 +326,11 @@ class UnityAcademyJsInteropContext { terminate() { if (this.unityInstance === null) return; - if (!confirm('Do you really hope to terminate the current Unity Academy instance? If so, everything need to reload when you use Unity Academy again.')) { + if ( + !confirm( + 'Do you really hope to terminate the current Unity Academy instance? If so, everything need to reload when you use Unity Academy again.' + ) + ) { return; } const quitFunctionName = 'Quit'; @@ -305,10 +338,13 @@ class UnityAcademyJsInteropContext { this.unityInstance = null; this.resetModuleData(); this.setShowUnityComponent(0); - const canvasContext = (document.querySelector('#unity-canvas') as HTMLCanvasElement)!.getContext('webgl2'); + const canvasContext = (document.querySelector( + '#unity-canvas' + ) as HTMLCanvasElement)!.getContext('webgl2'); canvasContext!.clearColor(0, 0, 0, 0); canvasContext!.clear(canvasContext!.COLOR_BUFFER_BIT); - document.querySelector('#unity_load_info')!.innerHTML = 'Unity Academy app has been terminated. Please rerun your program with init_unity_academy_3d or init_unity_academy_2d for re-initialization.'; + document.querySelector('#unity_load_info')!.innerHTML = + 'Unity Academy app has been terminated. Please rerun your program with init_unity_academy_3d or init_unity_academy_2d for re-initialization.'; } reset() { @@ -335,7 +371,7 @@ class UnityAcademyJsInteropContext { return this.unityInstanceState === 'Ready'; } - private getLatestUserAgreementVersion() : void { + private getLatestUserAgreementVersion(): void { const jsonUrl = `${UNITY_ACADEMY_BACKEND_URL}user_agreement.json`; const xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { @@ -347,9 +383,15 @@ class UnityAcademyJsInteropContext { xhr.send(); } - getUserAgreementStatus() : string { - const agreedUserAgreementVersion = localStorage.getItem('unity_academy_agreed_user_agreement_version'); - if (agreedUserAgreementVersion === null || agreedUserAgreementVersion === 'unagreed' || agreedUserAgreementVersion === 'unknown') { + getUserAgreementStatus(): string { + const agreedUserAgreementVersion = localStorage.getItem( + 'unity_academy_agreed_user_agreement_version' + ); + if ( + agreedUserAgreementVersion === null || + agreedUserAgreementVersion === 'unagreed' || + agreedUserAgreementVersion === 'unknown' + ) { return 'unagreed'; } if (this.latestUserAgreementVersion === 'unknown') { @@ -361,15 +403,18 @@ class UnityAcademyJsInteropContext { return 'agreed'; } - setUserAgreementStatus(agree : boolean) : void { + setUserAgreementStatus(agree: boolean): void { if (agree) { - localStorage.setItem('unity_academy_agreed_user_agreement_version', this.latestUserAgreementVersion); + localStorage.setItem( + 'unity_academy_agreed_user_agreement_version', + this.latestUserAgreementVersion + ); } else { localStorage.setItem('unity_academy_agreed_user_agreement_version', 'unagreed'); } } - instantiateInternal(prefabName : string) : GameObjectIdentifier { + instantiateInternal(prefabName: string): GameObjectIdentifier { let prefabExists = false; const len = this.prefabInfo.prefab_info.length; for (let i = 0; i < len; i++) { @@ -379,7 +424,9 @@ class UnityAcademyJsInteropContext { } } if (!prefabExists) { - throw new Error(`Unknown prefab name: '${prefabName}'. Please refer to this prefab list at [ ${UNITY_ACADEMY_BACKEND_URL}webgl_assetbundles/prefab_info.html ] for all available prefab names.`); + throw new Error( + `Unknown prefab name: '${prefabName}'. Please refer to this prefab list at [ ${UNITY_ACADEMY_BACKEND_URL}webgl_assetbundles/prefab_info.html ] for all available prefab names.` + ); } const gameObjectIdentifier = `${prefabName}_${this.gameObjectIdentifierSerialCounter}`; this.gameObjectIdentifierSerialCounter++; @@ -388,7 +435,7 @@ class UnityAcademyJsInteropContext { return new GameObjectIdentifier(gameObjectIdentifier); } - instantiate2DSpriteUrlInternal(sourceImageUrl : string) : GameObjectIdentifier { + instantiate2DSpriteUrlInternal(sourceImageUrl: string): GameObjectIdentifier { // Use percent-encoding "%7C" to replace all '|' characters as '|' is used as the data separator in student action strings in Unity Academy Embedded Frontend. sourceImageUrl = sourceImageUrl.replaceAll('|', '%7C'); const gameObjectIdentifier = `2DSprite_${this.gameObjectIdentifierSerialCounter}`; @@ -398,7 +445,7 @@ class UnityAcademyJsInteropContext { return new GameObjectIdentifier(gameObjectIdentifier); } - instantiateEmptyGameObjectInternal() : GameObjectIdentifier { + instantiateEmptyGameObjectInternal(): GameObjectIdentifier { const gameObjectIdentifier = `EmptyGameObject_${this.gameObjectIdentifierSerialCounter}`; this.gameObjectIdentifierSerialCounter++; this.makeGameObjectDataStorage(gameObjectIdentifier); @@ -406,7 +453,7 @@ class UnityAcademyJsInteropContext { return new GameObjectIdentifier(gameObjectIdentifier); } - instantiateAudioSourceInternal(audioClipIdentifier : AudioClipIdentifier) { + instantiateAudioSourceInternal(audioClipIdentifier: AudioClipIdentifier) { const gameObjectIdentifier = `AudioSource_${this.gameObjectIdentifierSerialCounter}`; this.gameObjectIdentifierSerialCounter++; this.makeGameObjectDataStorage(gameObjectIdentifier); @@ -416,17 +463,19 @@ class UnityAcademyJsInteropContext { playProgress: 0, volume: 1, isLooping: false, - isPlaying: false, + isPlaying: false }; - this.dispatchStudentAction(`instantiateAudioSourceGameObject|${gameObjectIdentifier}|${audioClipIdentifier.audioClipInternalName}`); + this.dispatchStudentAction( + `instantiateAudioSourceGameObject|${gameObjectIdentifier}|${audioClipIdentifier.audioClipInternalName}` + ); return new GameObjectIdentifier(gameObjectIdentifier); } - destroyGameObjectInternal(gameObjectIdentifier : GameObjectIdentifier) : void { + destroyGameObjectInternal(gameObjectIdentifier: GameObjectIdentifier): void { this.dispatchStudentAction(`destroyGameObject|${gameObjectIdentifier.gameObjectIdentifier}`); } - private makeGameObjectDataStorage(gameObjectIdentifier : string) { + private makeGameObjectDataStorage(gameObjectIdentifier: string) { this.studentGameObjectStorage[gameObjectIdentifier] = { startMethod: null, updateMethod: null, @@ -436,16 +485,16 @@ class UnityAcademyJsInteropContext { transform: { position: zeroVector(), rotation: zeroVector(), - scale: new Vector3(1, 1, 1), + scale: new Vector3(1, 1, 1) }, rigidbody: null, audioSource: null, customProperties: {}, - isDestroyed: false, + isDestroyed: false }; } - getStudentGameObject(gameObjectIdentifier : GameObjectIdentifier) : StudentGameObject { + getStudentGameObject(gameObjectIdentifier: GameObjectIdentifier): StudentGameObject { const retVal = this.studentGameObjectStorage[gameObjectIdentifier.gameObjectIdentifier]; if (retVal === undefined) { throw new Error(`Could not find GameObject with identifier ${gameObjectIdentifier}`); @@ -453,97 +502,136 @@ class UnityAcademyJsInteropContext { return retVal; } - setStartInternal(gameObjectIdentifier : GameObjectIdentifier, startFunction : Function) : void { + setStartInternal(gameObjectIdentifier: GameObjectIdentifier, startFunction: Function): void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); gameObject.startMethod = startFunction; } - setUpdateInternal(gameObjectIdentifier : GameObjectIdentifier, updateFunction : Function) : void { + setUpdateInternal(gameObjectIdentifier: GameObjectIdentifier, updateFunction: Function): void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); gameObject.updateMethod = updateFunction; } - private dispatchStudentAction(action) : void { + private dispatchStudentAction(action): void { this.studentActionQueue[this.studentActionQueue.length] = action; } - getGameObjectIdentifierForPrimitiveGameObject(name : string) : GameObjectIdentifier { + getGameObjectIdentifierForPrimitiveGameObject(name: string): GameObjectIdentifier { const propName = 'gameObjectIdentifierWrapperClass'; return new this[propName](name); } - getGameObjectTransformProp(propName : string, gameObjectIdentifier : GameObjectIdentifier) : Vector3 { + getGameObjectTransformProp( + propName: string, + gameObjectIdentifier: GameObjectIdentifier + ): Vector3 { const gameObject = this.getStudentGameObject(gameObjectIdentifier); - return new Vector3(gameObject.transform[propName].x, gameObject.transform[propName].y, gameObject.transform[propName].z); + return new Vector3( + gameObject.transform[propName].x, + gameObject.transform[propName].y, + gameObject.transform[propName].z + ); } - setGameObjectTransformProp(propName : string, gameObjectIdentifier : GameObjectIdentifier, newValue : Vector3) : void { + setGameObjectTransformProp( + propName: string, + gameObjectIdentifier: GameObjectIdentifier, + newValue: Vector3 + ): void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); gameObject.transform[propName].x = newValue.x; gameObject.transform[propName].y = newValue.y; gameObject.transform[propName].z = newValue.z; } - getDeltaTime() : number { + getDeltaTime(): number { return this.deltaTime; } - translateWorldInternal(gameObjectIdentifier : GameObjectIdentifier, deltaPosition : Vector3) : void { + translateWorldInternal(gameObjectIdentifier: GameObjectIdentifier, deltaPosition: Vector3): void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); gameObject.transform.position.x += deltaPosition.x; gameObject.transform.position.y += deltaPosition.y; gameObject.transform.position.z += deltaPosition.z; } - - translateLocalInternal(gameObjectIdentifier : GameObjectIdentifier, deltaPosition : Vector3) : void { + translateLocalInternal(gameObjectIdentifier: GameObjectIdentifier, deltaPosition: Vector3): void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); const rotation = gameObject.transform.rotation; // Some methematical stuff here for calcuating the actual world position displacement from local translate vector and current Euler rotation. - const rx = rotation.x * Math.PI / 180; - const ry = rotation.y * Math.PI / 180; - const rz = rotation.z * Math.PI / 180; + const rx = (rotation.x * Math.PI) / 180; + const ry = (rotation.y * Math.PI) / 180; + const rz = (rotation.z * Math.PI) / 180; const cos = Math.cos; const sin = Math.sin; - const rotationMatrix - = [[cos(ry) * cos(rz), -cos(ry) * sin(rz), sin(ry)], - [cos(rx) * sin(rz) + sin(rx) * sin(ry) * cos(rz), cos(rx) * cos(rz) - sin(rx) * sin(ry) * sin(rz), -sin(rx) * cos(ry)], - [sin(rx) * sin(rz) - cos(rx) * sin(ry) * cos(rz), cos(rx) * sin(ry) * sin(rz) + sin(rx) * cos(rz), cos(rx) * cos(ry)]]; + const rotationMatrix = [ + [cos(ry) * cos(rz), -cos(ry) * sin(rz), sin(ry)], + [ + cos(rx) * sin(rz) + sin(rx) * sin(ry) * cos(rz), + cos(rx) * cos(rz) - sin(rx) * sin(ry) * sin(rz), + -sin(rx) * cos(ry) + ], + [ + sin(rx) * sin(rz) - cos(rx) * sin(ry) * cos(rz), + cos(rx) * sin(ry) * sin(rz) + sin(rx) * cos(rz), + cos(rx) * cos(ry) + ] + ]; const finalWorldTranslateVector = [ - rotationMatrix[0][0] * deltaPosition.x + rotationMatrix[0][1] * deltaPosition.y + rotationMatrix[0][2] * deltaPosition.z, - rotationMatrix[1][0] * deltaPosition.x + rotationMatrix[1][1] * deltaPosition.y + rotationMatrix[1][2] * deltaPosition.z, - rotationMatrix[2][0] * deltaPosition.x + rotationMatrix[2][1] * deltaPosition.y + rotationMatrix[2][2] * deltaPosition.z, + rotationMatrix[0][0] * deltaPosition.x + + rotationMatrix[0][1] * deltaPosition.y + + rotationMatrix[0][2] * deltaPosition.z, + rotationMatrix[1][0] * deltaPosition.x + + rotationMatrix[1][1] * deltaPosition.y + + rotationMatrix[1][2] * deltaPosition.z, + rotationMatrix[2][0] * deltaPosition.x + + rotationMatrix[2][1] * deltaPosition.y + + rotationMatrix[2][2] * deltaPosition.z ]; gameObject.transform.position.x += finalWorldTranslateVector[0]; gameObject.transform.position.y += finalWorldTranslateVector[1]; gameObject.transform.position.z += finalWorldTranslateVector[2]; } - lookAtPositionInternal(gameObjectIdentifier : GameObjectIdentifier, position : Vector3) : void { + lookAtPositionInternal(gameObjectIdentifier: GameObjectIdentifier, position: Vector3): void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); - const deltaVector = normalizeVector(new Vector3(position.x - gameObject.transform.position.x, position.y - gameObject.transform.position.y, position.z - gameObject.transform.position.z)); + const deltaVector = normalizeVector( + new Vector3( + position.x - gameObject.transform.position.x, + position.y - gameObject.transform.position.y, + position.z - gameObject.transform.position.z + ) + ); const eulerX = Math.asin(-deltaVector.y); const eulerY = Math.atan2(deltaVector.x, deltaVector.z); - gameObject.transform.rotation.x = eulerX * 180 / Math.PI; - gameObject.transform.rotation.y = eulerY * 180 / Math.PI; + gameObject.transform.rotation.x = (eulerX * 180) / Math.PI; + gameObject.transform.rotation.y = (eulerY * 180) / Math.PI; gameObject.transform.rotation.z = 0; } - gameObjectDistanceInternal(gameObjectIdentifier_A : GameObjectIdentifier, gameObjectIdentifier_B : GameObjectIdentifier) : number { + gameObjectDistanceInternal( + gameObjectIdentifier_A: GameObjectIdentifier, + gameObjectIdentifier_B: GameObjectIdentifier + ): number { const gameObjectA = this.getStudentGameObject(gameObjectIdentifier_A); const gameObjectB = this.getStudentGameObject(gameObjectIdentifier_B); return pointDistance(gameObjectA.transform.position, gameObjectB.transform.position); } - rotateWorldInternal(gameObjectIdentifier : GameObjectIdentifier, angles : Vector3) : void { + rotateWorldInternal(gameObjectIdentifier: GameObjectIdentifier, angles: Vector3): void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); gameObject.transform.rotation.x += angles.x; gameObject.transform.rotation.y += angles.y; gameObject.transform.rotation.z += angles.z; } - copyTransformPropertiesInternal(propName : string, from : GameObjectIdentifier, to : GameObjectIdentifier, deltaValues : Vector3) : void { + copyTransformPropertiesInternal( + propName: string, + from: GameObjectIdentifier, + to: GameObjectIdentifier, + deltaValues: Vector3 + ): void { const fromGameObject = this.getStudentGameObject(from); const toGameObject = this.getStudentGameObject(to); const deltaX = deltaValues.x; @@ -554,20 +642,24 @@ class UnityAcademyJsInteropContext { if (Math.abs(deltaZ) !== 999999) toGameObject.transform[propName].z = fromGameObject.transform[propName].z + deltaZ; } - getKeyState(keyCode : string) : number { + getKeyState(keyCode: string): number { return this.input.keyboardInputInfo[keyCode]; } - playAnimatorStateInternal(gameObjectIdentifier : GameObjectIdentifier, animatorStateName : string) { + playAnimatorStateInternal(gameObjectIdentifier: GameObjectIdentifier, animatorStateName: string) { this.getStudentGameObject(gameObjectIdentifier); // Just to check whether the game object identifier is valid or not. - this.dispatchStudentAction(`playAnimatorState|${gameObjectIdentifier.gameObjectIdentifier}|${animatorStateName}`); + this.dispatchStudentAction( + `playAnimatorState|${gameObjectIdentifier.gameObjectIdentifier}|${animatorStateName}` + ); } - applyRigidbodyInternal(gameObjectIdentifier : GameObjectIdentifier) { + applyRigidbodyInternal(gameObjectIdentifier: GameObjectIdentifier) { console.log(`Applying rigidbody to GameObject ${gameObjectIdentifier.gameObjectIdentifier}`); const gameObject = this.getStudentGameObject(gameObjectIdentifier); if (gameObject.rigidbody !== null) { - throw new Error(`Trying to duplicately apply rigidbody on GameObject ${gameObjectIdentifier.gameObjectIdentifier}`); + throw new Error( + `Trying to duplicately apply rigidbody on GameObject ${gameObjectIdentifier.gameObjectIdentifier}` + ); } gameObject.rigidbody = { velocity: zeroVector(), @@ -575,23 +667,34 @@ class UnityAcademyJsInteropContext { mass: 1, useGravity: true, drag: 0, - angularDrag: 0.05, + angularDrag: 0.05 }; this.dispatchStudentAction(`applyRigidbody|${gameObjectIdentifier.gameObjectIdentifier}`); } - private getRigidbody(gameObject: StudentGameObject) : RigidbodyData { - if (gameObject.rigidbody === null) throw new Error('You must call apply_rigidbody on the game object before using this physics function!'); + private getRigidbody(gameObject: StudentGameObject): RigidbodyData { + if (gameObject.rigidbody === null) { + throw new Error( + 'You must call apply_rigidbody on the game object before using this physics function!' + ); + } return gameObject.rigidbody; } - getRigidbodyVelocityVector3Prop(propName : string, gameObjectIdentifier : GameObjectIdentifier) : Vector3 { + getRigidbodyVelocityVector3Prop( + propName: string, + gameObjectIdentifier: GameObjectIdentifier + ): Vector3 { const gameObject = this.getStudentGameObject(gameObjectIdentifier); const rigidbody = this.getRigidbody(gameObject); return new Vector3(rigidbody[propName].x, rigidbody[propName].y, rigidbody[propName].z); } - setRigidbodyVelocityVector3Prop(propName : string, gameObjectIdentifier : GameObjectIdentifier, newValue : Vector3) : void { + setRigidbodyVelocityVector3Prop( + propName: string, + gameObjectIdentifier: GameObjectIdentifier, + newValue: Vector3 + ): void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); const rigidbody = this.getRigidbody(gameObject); rigidbody[propName].x = newValue.x; @@ -599,48 +702,58 @@ class UnityAcademyJsInteropContext { rigidbody[propName].z = newValue.z; } - getRigidbodyNumericalProp(propName : string, gameObjectIdentifier : GameObjectIdentifier) : number { + getRigidbodyNumericalProp(propName: string, gameObjectIdentifier: GameObjectIdentifier): number { const gameObject = this.getStudentGameObject(gameObjectIdentifier); const rigidbody = this.getRigidbody(gameObject); return rigidbody[propName]; } - setRigidbodyNumericalProp(propName : string, gameObjectIdentifier : GameObjectIdentifier, value : number) : void { + setRigidbodyNumericalProp( + propName: string, + gameObjectIdentifier: GameObjectIdentifier, + value: number + ): void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); const rigidbody = this.getRigidbody(gameObject); rigidbody[propName] = value; } - setUseGravityInternal(gameObjectIdentifier : GameObjectIdentifier, useGravity : boolean) : void { + setUseGravityInternal(gameObjectIdentifier: GameObjectIdentifier, useGravity: boolean): void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); const rigidbody = this.getRigidbody(gameObject); rigidbody.useGravity = useGravity; } - addImpulseForceInternal(gameObjectIdentifier : GameObjectIdentifier, force : Vector3) : void { - this.dispatchStudentAction(`addImpulseForce|${gameObjectIdentifier.gameObjectIdentifier}|${force.x.toString()}|${force.y.toString()}|${force.z.toString()}`); + addImpulseForceInternal(gameObjectIdentifier: GameObjectIdentifier, force: Vector3): void { + this.dispatchStudentAction( + `addImpulseForce|${ + gameObjectIdentifier.gameObjectIdentifier + }|${force.x.toString()}|${force.y.toString()}|${force.z.toString()}` + ); } - removeColliderComponentsInternal(gameObjectIdentifier : GameObjectIdentifier) : void { - this.dispatchStudentAction(`removeColliderComponents|${gameObjectIdentifier.gameObjectIdentifier}`); + removeColliderComponentsInternal(gameObjectIdentifier: GameObjectIdentifier): void { + this.dispatchStudentAction( + `removeColliderComponents|${gameObjectIdentifier.gameObjectIdentifier}` + ); } - setOnCollisionEnterInternal(gameObjectIdentifier : GameObjectIdentifier, eventFunction : Function) { + setOnCollisionEnterInternal(gameObjectIdentifier: GameObjectIdentifier, eventFunction: Function) { const gameObject = this.getStudentGameObject(gameObjectIdentifier); gameObject.onCollisionEnterMethod = eventFunction; } - setOnCollisionStayInternal(gameObjectIdentifier : GameObjectIdentifier, eventFunction : Function) { + setOnCollisionStayInternal(gameObjectIdentifier: GameObjectIdentifier, eventFunction: Function) { const gameObject = this.getStudentGameObject(gameObjectIdentifier); gameObject.onCollisionStayMethod = eventFunction; } - setOnCollisionExitInternal(gameObjectIdentifier : GameObjectIdentifier, eventFunction : Function) { + setOnCollisionExitInternal(gameObjectIdentifier: GameObjectIdentifier, eventFunction: Function) { const gameObject = this.getStudentGameObject(gameObjectIdentifier); gameObject.onCollisionExitMethod = eventFunction; } - requestForMainCameraControlInternal() : GameObjectIdentifier { + requestForMainCameraControlInternal(): GameObjectIdentifier { const name = 'MainCamera'; if (this.studentGameObjectStorage[name] !== undefined) { return this.getGameObjectIdentifierForPrimitiveGameObject('MainCamera'); @@ -650,7 +763,7 @@ class UnityAcademyJsInteropContext { return this.getGameObjectIdentifierForPrimitiveGameObject('MainCamera'); } - onGUI_Label(content : string, x : number, y : number) : void { + onGUI_Label(content: string, x: number, y: number): void { // Temporarily use "<%7C>" to replace all '|' characters as '|' is used as the data separator in GUI data in Unity Academy Embedded Frontend. // In Unity Academy Embedded Frontend, "<%7C>" will be replaced back to '|' when displaying the text in GUI content = content.replaceAll('|', '<%7C>'); @@ -658,12 +771,19 @@ class UnityAcademyJsInteropContext { type: 'label', content, x, - y, + y }; this.guiData.push(newLabel); } - onGUI_Button(text : string, x: number, y : number, width : number, height : number, onClick : Function) : void { + onGUI_Button( + text: string, + x: number, + y: number, + width: number, + height: number, + onClick: Function + ): void { // Temporarily use "<%7C>" to replace all '|' characters as '|' is used as the data separator in GUI data in Unity Academy Embedded Frontend. // In Unity Academy Embedded Frontend, "<%7C>" will be replaced back to '|' when displaying the text in GUI text = text.replaceAll('|', '<%7C>'); @@ -674,20 +794,22 @@ class UnityAcademyJsInteropContext { y, width, height, - onClick, + onClick }; this.guiData.push(newButton); } - loadAudioClipInternal(audioClipUrl : string, audioType : string) : AudioClipIdentifier { + loadAudioClipInternal(audioClipUrl: string, audioType: string): AudioClipIdentifier { const audioClipInternalName = `AudioClip_${this.audioClipIdentifierSerialCounter}`; this.audioClipIdentifierSerialCounter++; this.audioClipStorage[this.audioClipStorage.length] = audioClipInternalName; - this.dispatchStudentAction(`loadAudioClip|${audioClipUrl}|${audioType}|${audioClipInternalName}`); + this.dispatchStudentAction( + `loadAudioClip|${audioClipUrl}|${audioType}|${audioClipInternalName}` + ); return new AudioClipIdentifier(audioClipInternalName); } - private getAudioSourceData(gameObjectIdentifier : GameObjectIdentifier) : AudioSourceData { + private getAudioSourceData(gameObjectIdentifier: GameObjectIdentifier): AudioSourceData { const gameObject = this.getStudentGameObject(gameObjectIdentifier); const retVal = gameObject.audioSource; if (retVal === null) { @@ -696,36 +818,40 @@ class UnityAcademyJsInteropContext { return retVal; } - setAudioSourceProp(propName : string, audioSrc : GameObjectIdentifier, value : any) : void { + setAudioSourceProp(propName: string, audioSrc: GameObjectIdentifier, value: any): void { const audioSourceData = this.getAudioSourceData(audioSrc); audioSourceData[propName] = value; } - getAudioSourceProp(propName : string, audioSrc : GameObjectIdentifier) : any { + getAudioSourceProp(propName: string, audioSrc: GameObjectIdentifier): any { const audioSourceData = this.getAudioSourceData(audioSrc); return audioSourceData[propName]; } - studentLogger(contentStr : string, severity : string) { + studentLogger(contentStr: string, severity: string) { // Temporarily use "<%7C>" to replace all '|' characters as '|' is used as the data separator in student action strings in Unity Academy Embedded Frontend. // In Unity Academy Embedded Frontend, "<%7C>" will be replaced back to '|' when displaying the log message. contentStr = contentStr.replaceAll('|', '<%7C>'); this.dispatchStudentAction(`studentLogger|${severity}|${contentStr}`); } - setTargetFrameRate(newTargetFrameRate : number) : void { + setTargetFrameRate(newTargetFrameRate: number): void { newTargetFrameRate = Math.floor(newTargetFrameRate); if (newTargetFrameRate < 15) return; if (newTargetFrameRate > 120) return; this.targetFrameRate = newTargetFrameRate; } - setCustomPropertyInternal(gameObjectIdentifier : GameObjectIdentifier, propName : string, value : any) : void { + setCustomPropertyInternal( + gameObjectIdentifier: GameObjectIdentifier, + propName: string, + value: any + ): void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); gameObject.customProperties[propName] = value; } - getCustomPropertyInternal(gameObjectIdentifier : GameObjectIdentifier, propName : string) : any { + getCustomPropertyInternal(gameObjectIdentifier: GameObjectIdentifier, propName: string): any { const gameObject = this.getStudentGameObject(gameObjectIdentifier); return gameObject.customProperties[propName]; } @@ -735,11 +861,13 @@ class UnityAcademyJsInteropContext { } } -export function initializeModule(dimensionMode : string) { +export function initializeModule(dimensionMode: string) { let instance = getInstance(); if (instance !== undefined) { if (!instance.isUnityInstanceReady()) { - throw new Error('Unity Academy Embedded Frontend is not ready to accept a new Source program now, please try again later. If you just successfully ran your code before but haven\'t open Unity Academy Embedded Frontend before running your code again, please try open the frontend first. If this error persists or you can not open Unity Academy Embedded Frontend, please try to refresh your browser\'s page.'); + throw new Error( + "Unity Academy Embedded Frontend is not ready to accept a new Source program now, please try again later. If you just successfully ran your code before but haven't open Unity Academy Embedded Frontend before running your code again, please try open the frontend first. If this error persists or you can not open Unity Academy Embedded Frontend, please try to refresh your browser's page." + ); } if (instance.unityInstance === null) { instance.reloadUnityAcademyInstanceAfterTermination(); diff --git a/src/bundles/unity_academy/UnityAcademyMaths.ts b/src/bundles/unity_academy/UnityAcademyMaths.ts index 84f09bf482..5ce8256d02 100644 --- a/src/bundles/unity_academy/UnityAcademyMaths.ts +++ b/src/bundles/unity_academy/UnityAcademyMaths.ts @@ -6,8 +6,11 @@ export class Vector3 { x = 0; + y = 0; + z = 0; + constructor(x, y, z) { this.x = x; this.y = y; @@ -19,53 +22,65 @@ export class Vector3 { } } -export function checkVector3Parameter(parameter : any) : void { - if (typeof (parameter) !== 'object') { - throw new Error(`The given parameter is not a valid 3D vector! Wrong parameter type: ${typeof (parameter)}`); +export function checkVector3Parameter(parameter: any): void { + if (typeof parameter !== 'object') { + throw new Error( + `The given parameter is not a valid 3D vector! Wrong parameter type: ${typeof parameter}` + ); } - if (typeof (parameter.x) !== 'number' || typeof (parameter.y) !== 'number' || typeof (parameter.z) !== 'number') { + if ( + typeof parameter.x !== 'number' || + typeof parameter.y !== 'number' || + typeof parameter.z !== 'number' + ) { throw new Error('The given parameter is not a valid 3D vector!'); } } -export function makeVector3D(x : number, y : number, z : number) : Vector3 { +export function makeVector3D(x: number, y: number, z: number): Vector3 { return new Vector3(x, y, z); } -export function scaleVector(vector : Vector3, factor : number) : Vector3 { +export function scaleVector(vector: Vector3, factor: number): Vector3 { return new Vector3(vector.x * factor, vector.y * factor, vector.z * factor); } -export function addVectors(vectorA : Vector3, vectorB : Vector3) : Vector3 { +export function addVectors(vectorA: Vector3, vectorB: Vector3): Vector3 { return new Vector3(vectorA.x + vectorB.x, vectorA.y + vectorB.y, vectorA.z + vectorB.z); } -export function vectorDifference(vectorA : Vector3, vectorB : Vector3) : Vector3 { +export function vectorDifference(vectorA: Vector3, vectorB: Vector3): Vector3 { return new Vector3(vectorA.x - vectorB.x, vectorA.y - vectorB.y, vectorA.z - vectorB.z); } -export function dotProduct(vectorA : Vector3, vectorB : Vector3) : number { +export function dotProduct(vectorA: Vector3, vectorB: Vector3): number { return vectorA.x * vectorB.x + vectorA.y * vectorB.y + vectorA.z * vectorB.z; } -export function crossProduct(vectorA : Vector3, vectorB : Vector3) : Vector3 { - return new Vector3(vectorA.y * vectorB.z - vectorB.y * vectorA.z, vectorB.x * vectorA.z - vectorA.x * vectorB.z, vectorA.x * vectorB.y - vectorB.x * vectorA.y); +export function crossProduct(vectorA: Vector3, vectorB: Vector3): Vector3 { + return new Vector3( + vectorA.y * vectorB.z - vectorB.y * vectorA.z, + vectorB.x * vectorA.z - vectorA.x * vectorB.z, + vectorA.x * vectorB.y - vectorB.x * vectorA.y + ); } -export function vectorMagnitude(vector : Vector3) : number { +export function vectorMagnitude(vector: Vector3): number { return Math.sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z); } -export function normalizeVector(vector : Vector3) : Vector3 { +export function normalizeVector(vector: Vector3): Vector3 { const magnitude = vectorMagnitude(vector); if (magnitude === 0) return new Vector3(0, 0, 0); // If the parameter is a zero vector, then return a new zero vector. return new Vector3(vector.x / magnitude, vector.y / magnitude, vector.z / magnitude); } -export function zeroVector() : Vector3 { +export function zeroVector(): Vector3 { return new Vector3(0, 0, 0); } -export function pointDistance(pointA : Vector3, pointB : Vector3) : number { - return Math.sqrt((pointB.x - pointA.x) ** 2 + (pointB.y - pointA.y) ** 2 + (pointB.z - pointA.z) ** 2); +export function pointDistance(pointA: Vector3, pointB: Vector3): number { + return Math.sqrt( + (pointB.x - pointA.x) ** 2 + (pointB.y - pointA.y) ** 2 + (pointB.z - pointA.z) ** 2 + ); } diff --git a/src/bundles/unity_academy/functions.ts b/src/bundles/unity_academy/functions.ts index dd059201fe..8f60ea3163 100644 --- a/src/bundles/unity_academy/functions.ts +++ b/src/bundles/unity_academy/functions.ts @@ -4,13 +4,27 @@ * @author Wang Zihan */ -import { initializeModule, getInstance, type GameObjectIdentifier, type AudioClipIdentifier } from './UnityAcademy'; import { - type Vector3, checkVector3Parameter, makeVector3D, scaleVector, addVectors, vectorDifference, dotProduct, - crossProduct, normalizeVector, vectorMagnitude, zeroVector, pointDistance, + getInstance, + initializeModule, + type AudioClipIdentifier, + type GameObjectIdentifier +} from './UnityAcademy'; +import { + addVectors, + checkVector3Parameter, + crossProduct, + dotProduct, + makeVector3D, + normalizeVector, + pointDistance, + scaleVector, + vectorDifference, + vectorMagnitude, + zeroVector, + type Vector3 } from './UnityAcademyMaths'; - /** * Load and initialize Unity Academy WebGL player and set it to 2D mode. All other functions (except Maths functions) in this module requires calling this function or `init_unity_academy_3d` first. * @@ -19,7 +33,7 @@ import { * @category Application Initialization * @category Outside Lifecycle */ -export function init_unity_academy_2d() : void { +export function init_unity_academy_2d(): void { initializeModule('2d'); } @@ -31,45 +45,51 @@ export function init_unity_academy_2d() : void { * @category Application Initialization * @category Outside Lifecycle */ -export function init_unity_academy_3d() : void { +export function init_unity_academy_3d(): void { initializeModule('3d'); } function checkUnityAcademyExistence() { if (getInstance() === undefined) { - throw new Error('Unity module is not initialized, please call init_unity_academy_3d / init_unity_academy_2d first before calling this function'); + throw new Error( + 'Unity module is not initialized, please call init_unity_academy_3d / init_unity_academy_2d first before calling this function' + ); } } -function checkIs2DMode() : void { +function checkIs2DMode(): void { if (getInstance().dimensionMode !== '2d') throw new Error('You are calling a "2D mode only" function in non-2d mode.'); } -function checkIs3DMode() : void { +function checkIs3DMode(): void { if (getInstance().dimensionMode !== '3d') throw new Error('You are calling a "3D mode only" function in non-3d mode.'); } -function checkGameObjectIdentifierParameter(gameObjectIdentifier : any) { +function checkGameObjectIdentifierParameter(gameObjectIdentifier: any) { // Here I can not just do "gameObjectIdentifier instanceof GameObjectIdentifier". // Because if I do that, when students re-run their code on the same Unity instance, (gameObjectIdentifier instanceof GameObjectIdentifier) will always evaluate to false // even when students provide the parameter with the correct type. const instance = getInstance(); if (!(gameObjectIdentifier instanceof instance.gameObjectIdentifierWrapperClass)) { - throw new Error(`Type "${(typeof (gameObjectIdentifier)).toString()}" can not be used as game object identifier!`); + throw new Error( + `Type "${(typeof gameObjectIdentifier).toString()}" can not be used as game object identifier!` + ); } if (instance.getStudentGameObject(gameObjectIdentifier).isDestroyed) { throw new Error('Trying to use a GameObject that is already destroyed.'); } } -function checkParameterType(parameter : any, expectedType : string, numberAllowInfinity = false) { - const actualType = typeof (parameter); +function checkParameterType(parameter: any, expectedType: string, numberAllowInfinity = false) { + const actualType = typeof parameter; if (actualType !== expectedType) { throw new Error(`Wrong parameter type: expected ${expectedType}, but got ${actualType}`); } if (actualType.toString() === 'number') { if (!numberAllowInfinity && (parameter === Infinity || parameter === -Infinity)) { - throw new Error('Wrong parameter type: expected a finite number, but got Infinity or -Infinity'); + throw new Error( + 'Wrong parameter type: expected a finite number, but got Infinity or -Infinity' + ); } } } @@ -82,10 +102,16 @@ function checkParameterType(parameter : any, expectedType : string, numberAllowI * @return Returns true if the two GameObject identifiers refers to the same GameObject and false otherwise. * @category Common */ -export function same_gameobject(first : GameObjectIdentifier, second : GameObjectIdentifier) : boolean { +export function same_gameobject( + first: GameObjectIdentifier, + second: GameObjectIdentifier +): boolean { checkUnityAcademyExistence(); const instance = getInstance(); - if (!(first instanceof instance.gameObjectIdentifierWrapperClass) || !(second instanceof instance.gameObjectIdentifierWrapperClass)) { + if ( + !(first instanceof instance.gameObjectIdentifierWrapperClass) || + !(second instanceof instance.gameObjectIdentifierWrapperClass) + ) { return false; } return first.gameObjectIdentifier === second.gameObjectIdentifier; @@ -99,12 +125,14 @@ export function same_gameobject(first : GameObjectIdentifier, second : GameObjec * @category Common * @category Outside Lifecycle */ -export function set_start(gameObjectIdentifier : GameObjectIdentifier, startFunction : Function) : void { +export function set_start( + gameObjectIdentifier: GameObjectIdentifier, + startFunction: Function +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkParameterType(startFunction, 'function'); - getInstance() - .setStartInternal(gameObjectIdentifier, startFunction); + getInstance().setStartInternal(gameObjectIdentifier, startFunction); } /** @@ -116,15 +144,16 @@ export function set_start(gameObjectIdentifier : GameObjectIdentifier, startFunc * @category Common * @category Outside Lifecycle */ -export function set_update(gameObjectIdentifier : GameObjectIdentifier, updateFunction : Function) : void { +export function set_update( + gameObjectIdentifier: GameObjectIdentifier, + updateFunction: Function +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkParameterType(updateFunction, 'function'); - getInstance() - .setUpdateInternal(gameObjectIdentifier, updateFunction); + getInstance().setUpdateInternal(gameObjectIdentifier, updateFunction); } - /** * Creates a new GameObject from an existing Prefab * @@ -140,12 +169,11 @@ export function set_update(gameObjectIdentifier : GameObjectIdentifier, updateFu * @category Common * @category Outside Lifecycle */ -export function instantiate(prefab_name : string) : GameObjectIdentifier { +export function instantiate(prefab_name: string): GameObjectIdentifier { checkUnityAcademyExistence(); checkIs3DMode(); checkParameterType(prefab_name, 'string'); - return getInstance() - .instantiateInternal(prefab_name); + return getInstance().instantiateInternal(prefab_name); } /** @@ -163,12 +191,11 @@ export function instantiate(prefab_name : string) : GameObjectIdentifier { * @category Common * @category Outside Lifecycle */ -export function instantiate_sprite(sourceImageUrl : string) { +export function instantiate_sprite(sourceImageUrl: string) { checkUnityAcademyExistence(); checkIs2DMode(); checkParameterType(sourceImageUrl, 'string'); - return getInstance() - .instantiate2DSpriteUrlInternal(sourceImageUrl); + return getInstance().instantiate2DSpriteUrlInternal(sourceImageUrl); } /** @@ -183,10 +210,9 @@ export function instantiate_sprite(sourceImageUrl : string) { * @category Common * @category Outside Lifecycle */ -export function instantiate_empty() : GameObjectIdentifier { +export function instantiate_empty(): GameObjectIdentifier { checkUnityAcademyExistence(); - return getInstance() - .instantiateEmptyGameObjectInternal(); + return getInstance().instantiateEmptyGameObjectInternal(); } /** @@ -210,8 +236,7 @@ export function instantiate_empty() : GameObjectIdentifier { */ export function delta_time() { checkUnityAcademyExistence(); - return getInstance() - .getDeltaTime(); + return getInstance().getDeltaTime(); } /** @@ -224,11 +249,10 @@ export function delta_time() { * @param gameObjectIdentifier The identifier for the GameObject that you want to destroy. * @category Common */ -export function destroy(gameObjectIdentifier : GameObjectIdentifier) : void { +export function destroy(gameObjectIdentifier: GameObjectIdentifier): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - getInstance() - .destroyGameObjectInternal(gameObjectIdentifier); + getInstance().destroyGameObjectInternal(gameObjectIdentifier); } /** @@ -238,11 +262,10 @@ export function destroy(gameObjectIdentifier : GameObjectIdentifier) : void { * * @category Transform */ -export function get_position(gameObjectIdentifier : GameObjectIdentifier) : Vector3 { +export function get_position(gameObjectIdentifier: GameObjectIdentifier): Vector3 { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - return getInstance() - .getGameObjectTransformProp('position', gameObjectIdentifier); + return getInstance().getGameObjectTransformProp('position', gameObjectIdentifier); } /** @@ -252,12 +275,11 @@ export function get_position(gameObjectIdentifier : GameObjectIdentifier) : Vect * * @category Transform */ -export function set_position(gameObjectIdentifier : GameObjectIdentifier, position : Vector3) : void { +export function set_position(gameObjectIdentifier: GameObjectIdentifier, position: Vector3): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkVector3Parameter(position); - return getInstance() - .setGameObjectTransformProp('position', gameObjectIdentifier, position); + return getInstance().setGameObjectTransformProp('position', gameObjectIdentifier, position); } /** @@ -267,11 +289,10 @@ export function set_position(gameObjectIdentifier : GameObjectIdentifier, positi * * @category Transform */ -export function get_rotation_euler(gameObjectIdentifier : GameObjectIdentifier) : Vector3 { +export function get_rotation_euler(gameObjectIdentifier: GameObjectIdentifier): Vector3 { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - return getInstance() - .getGameObjectTransformProp('rotation', gameObjectIdentifier); + return getInstance().getGameObjectTransformProp('rotation', gameObjectIdentifier); } /** @@ -281,12 +302,14 @@ export function get_rotation_euler(gameObjectIdentifier : GameObjectIdentifier) * * @category Transform */ -export function set_rotation_euler(gameObjectIdentifier : GameObjectIdentifier, rotation : Vector3) : void { +export function set_rotation_euler( + gameObjectIdentifier: GameObjectIdentifier, + rotation: Vector3 +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkVector3Parameter(rotation); - return getInstance() - .setGameObjectTransformProp('rotation', gameObjectIdentifier, rotation); + return getInstance().setGameObjectTransformProp('rotation', gameObjectIdentifier, rotation); } /** @@ -298,11 +321,10 @@ export function set_rotation_euler(gameObjectIdentifier : GameObjectIdentifier, * * @category Transform */ -export function get_scale(gameObjectIdentifier : GameObjectIdentifier) : Vector3 { +export function get_scale(gameObjectIdentifier: GameObjectIdentifier): Vector3 { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - return getInstance() - .getGameObjectTransformProp('scale', gameObjectIdentifier); + return getInstance().getGameObjectTransformProp('scale', gameObjectIdentifier); } /** @@ -317,12 +339,11 @@ export function get_scale(gameObjectIdentifier : GameObjectIdentifier) : Vector3 * * @category Transform */ -export function set_scale(gameObjectIdentifier : GameObjectIdentifier, scale : Vector3) : void { +export function set_scale(gameObjectIdentifier: GameObjectIdentifier, scale: Vector3): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkVector3Parameter(scale); - return getInstance() - .setGameObjectTransformProp('scale', gameObjectIdentifier, scale); + return getInstance().setGameObjectTransformProp('scale', gameObjectIdentifier, scale); } /** @@ -333,12 +354,14 @@ export function set_scale(gameObjectIdentifier : GameObjectIdentifier, scale : V * * @category Transform */ -export function translate_world(gameObjectIdentifier : GameObjectIdentifier, deltaPosition : Vector3) : void { +export function translate_world( + gameObjectIdentifier: GameObjectIdentifier, + deltaPosition: Vector3 +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkVector3Parameter(deltaPosition); - return getInstance() - .translateWorldInternal(gameObjectIdentifier, deltaPosition); + return getInstance().translateWorldInternal(gameObjectIdentifier, deltaPosition); } /** @@ -353,12 +376,14 @@ export function translate_world(gameObjectIdentifier : GameObjectIdentifier, del * * @category Transform */ -export function translate_local(gameObjectIdentifier : GameObjectIdentifier, deltaPosition : Vector3) : void { +export function translate_local( + gameObjectIdentifier: GameObjectIdentifier, + deltaPosition: Vector3 +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkVector3Parameter(deltaPosition); - return getInstance() - .translateLocalInternal(gameObjectIdentifier, deltaPosition); + return getInstance().translateLocalInternal(gameObjectIdentifier, deltaPosition); } /** @@ -369,12 +394,11 @@ export function translate_local(gameObjectIdentifier : GameObjectIdentifier, del * * @category Transform */ -export function rotate_world(gameObjectIdentifier : GameObjectIdentifier, angles : Vector3) : void { +export function rotate_world(gameObjectIdentifier: GameObjectIdentifier, angles: Vector3): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkVector3Parameter(angles); - return getInstance() - .rotateWorldInternal(gameObjectIdentifier, angles); + return getInstance().rotateWorldInternal(gameObjectIdentifier, angles); } /** @@ -388,13 +412,16 @@ export function rotate_world(gameObjectIdentifier : GameObjectIdentifier, angles * * @category Transform */ -export function copy_position(from : GameObjectIdentifier, to : GameObjectIdentifier, deltaPosition : Vector3) : void { +export function copy_position( + from: GameObjectIdentifier, + to: GameObjectIdentifier, + deltaPosition: Vector3 +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(from); checkGameObjectIdentifierParameter(to); checkVector3Parameter(deltaPosition); - return getInstance() - .copyTransformPropertiesInternal('position', from, to, deltaPosition); + return getInstance().copyTransformPropertiesInternal('position', from, to, deltaPosition); } /** @@ -408,13 +435,16 @@ export function copy_position(from : GameObjectIdentifier, to : GameObjectIdenti * * @category Transform */ -export function copy_rotation(from : GameObjectIdentifier, to : GameObjectIdentifier, deltaRotation : Vector3) : void { +export function copy_rotation( + from: GameObjectIdentifier, + to: GameObjectIdentifier, + deltaRotation: Vector3 +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(from); checkGameObjectIdentifierParameter(to); checkVector3Parameter(deltaRotation); - return getInstance() - .copyTransformPropertiesInternal('rotation', from, to, deltaRotation); + return getInstance().copyTransformPropertiesInternal('rotation', from, to, deltaRotation); } /** @@ -428,13 +458,16 @@ export function copy_rotation(from : GameObjectIdentifier, to : GameObjectIdenti * * @category Transform */ -export function copy_scale(from : GameObjectIdentifier, to : GameObjectIdentifier, deltaScale : Vector3) : void { +export function copy_scale( + from: GameObjectIdentifier, + to: GameObjectIdentifier, + deltaScale: Vector3 +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(from); checkGameObjectIdentifierParameter(to); checkVector3Parameter(deltaScale); - return getInstance() - .copyTransformPropertiesInternal('scale', from, to, deltaScale); + return getInstance().copyTransformPropertiesInternal('scale', from, to, deltaScale); } /** @@ -449,12 +482,11 @@ export function copy_scale(from : GameObjectIdentifier, to : GameObjectIdentifie * * @category Transform */ -export function look_at(gameObjectIdentifier : GameObjectIdentifier, position : Vector3) : void { +export function look_at(gameObjectIdentifier: GameObjectIdentifier, position: Vector3): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkVector3Parameter(position); - getInstance() - .lookAtPositionInternal(gameObjectIdentifier, position); + getInstance().lookAtPositionInternal(gameObjectIdentifier, position); } /** @@ -466,22 +498,37 @@ export function look_at(gameObjectIdentifier : GameObjectIdentifier, position : * @return The value of the distance between these two GameObjects * @category Transform */ -export function gameobject_distance(gameObjectIdentifier_A : GameObjectIdentifier, gameObjectIdentifier_B : GameObjectIdentifier) : number { +export function gameobject_distance( + gameObjectIdentifier_A: GameObjectIdentifier, + gameObjectIdentifier_B: GameObjectIdentifier +): number { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier_A); checkGameObjectIdentifierParameter(gameObjectIdentifier_B); - return getInstance() - .gameObjectDistanceInternal(gameObjectIdentifier_A, gameObjectIdentifier_B); -} - -function checkKeyCodeValidityAndToLowerCase(keyCode : string) : string { - if (typeof (keyCode) !== 'string') throw new Error(`Key code must be a string! Given type: ${typeof (keyCode)}`); - if (keyCode === 'LeftMouseBtn' || keyCode === 'RightMouseBtn' || keyCode === 'MiddleMouseBtn' || keyCode === 'Space' || keyCode === 'LeftShift' || keyCode === 'RightShift') return keyCode; + return getInstance().gameObjectDistanceInternal(gameObjectIdentifier_A, gameObjectIdentifier_B); +} + +function checkKeyCodeValidityAndToLowerCase(keyCode: string): string { + if (typeof keyCode !== 'string') throw new Error(`Key code must be a string! Given type: ${typeof keyCode}`); + if ( + keyCode === 'LeftMouseBtn' || + keyCode === 'RightMouseBtn' || + keyCode === 'MiddleMouseBtn' || + keyCode === 'Space' || + keyCode === 'LeftShift' || + keyCode === 'RightShift' + ) return keyCode; keyCode = keyCode.toLowerCase(); - if (keyCode.length !== 1) throw new Error(`Key code must be either a string of length 1 or one among 'LeftMouseBtn', 'RightMouseBtn', 'MiddleMouseBtn', 'Space', 'LeftShift' or 'RightShift'! Given length: ${keyCode.length}`); + if (keyCode.length !== 1) { + throw new Error( + `Key code must be either a string of length 1 or one among 'LeftMouseBtn', 'RightMouseBtn', 'MiddleMouseBtn', 'Space', 'LeftShift' or 'RightShift'! Given length: ${keyCode.length}` + ); + } const char = keyCode.charAt(0); if (!((char >= 'a' && char <= 'z') || (char >= '0' && char <= '9'))) { - throw new Error(`Key code must be either a letter between A-Z or a-z or 0-9 or one among 'LeftMouseBtn', 'RightMouseBtn', 'MiddleMouseBtn', 'Space', 'LeftShift' or 'RightShift'! Given: ${keyCode}`); + throw new Error( + `Key code must be either a letter between A-Z or a-z or 0-9 or one among 'LeftMouseBtn', 'RightMouseBtn', 'MiddleMouseBtn', 'Space', 'LeftShift' or 'RightShift'! Given: ${keyCode}` + ); } return keyCode; } @@ -495,11 +542,10 @@ function checkKeyCodeValidityAndToLowerCase(keyCode : string) : string { * @param keyCode The key to detact input for. * @category Input */ -export function get_key_down(keyCode : string) : boolean { +export function get_key_down(keyCode: string): boolean { checkUnityAcademyExistence(); keyCode = checkKeyCodeValidityAndToLowerCase(keyCode); - return getInstance() - .getKeyState(keyCode) === 1; + return getInstance().getKeyState(keyCode) === 1; } /** @@ -511,15 +557,13 @@ export function get_key_down(keyCode : string) : boolean { * @param keyCode The key to detact input for. * @category Input */ -export function get_key(keyCode : string) : boolean { +export function get_key(keyCode: string): boolean { checkUnityAcademyExistence(); keyCode = checkKeyCodeValidityAndToLowerCase(keyCode); - const keyState = getInstance() - .getKeyState(keyCode); + const keyState = getInstance().getKeyState(keyCode); return keyState === 1 || keyState === 2 || keyState === 3; } - /** * When user releases a pressed key on the keyboard or mouse button, this function will return true only at the frame when the key is just released up and return false otherwise. * @@ -529,11 +573,10 @@ export function get_key(keyCode : string) : boolean { * @param keyCode The key to detact input for. * @category Input */ -export function get_key_up(keyCode : string) : boolean { +export function get_key_up(keyCode: string): boolean { checkUnityAcademyExistence(); keyCode = checkKeyCodeValidityAndToLowerCase(keyCode); - return getInstance() - .getKeyState(keyCode) === 3; + return getInstance().getKeyState(keyCode) === 3; } /** @@ -547,13 +590,15 @@ export function get_key_up(keyCode : string) : boolean { * @param animatorStateName The name for the animator state to play. * @category Common */ -export function play_animator_state(gameObjectIdentifier : GameObjectIdentifier, animatorStateName : string) : void { +export function play_animator_state( + gameObjectIdentifier: GameObjectIdentifier, + animatorStateName: string +): void { checkUnityAcademyExistence(); checkIs3DMode(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkParameterType(animatorStateName, 'string'); - getInstance() - .playAnimatorStateInternal(gameObjectIdentifier, animatorStateName); + getInstance().playAnimatorStateInternal(gameObjectIdentifier, animatorStateName); } /** @@ -568,11 +613,10 @@ export function play_animator_state(gameObjectIdentifier : GameObjectIdentifier, * @param gameObjectIdentifier The identifier for the GameObject that you want to apply rigidbody on. * @category Physics - Rigidbody */ -export function apply_rigidbody(gameObjectIdentifier : GameObjectIdentifier) : void { +export function apply_rigidbody(gameObjectIdentifier: GameObjectIdentifier): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - getInstance() - .applyRigidbodyInternal(gameObjectIdentifier); + getInstance().applyRigidbodyInternal(gameObjectIdentifier); } /** @@ -584,11 +628,10 @@ export function apply_rigidbody(gameObjectIdentifier : GameObjectIdentifier) : v * @return The mass of the rigidbody attached on the GameObject * @category Physics - Rigidbody */ -export function get_mass(gameObjectIdentifier : GameObjectIdentifier) : number { +export function get_mass(gameObjectIdentifier: GameObjectIdentifier): number { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - return getInstance() - .getRigidbodyNumericalProp('mass', gameObjectIdentifier); + return getInstance().getRigidbodyNumericalProp('mass', gameObjectIdentifier); } /** @@ -600,12 +643,11 @@ export function get_mass(gameObjectIdentifier : GameObjectIdentifier) : number { * @param mass The value for the new mass. * @category Physics - Rigidbody */ -export function set_mass(gameObjectIdentifier : GameObjectIdentifier, mass : number) : void { +export function set_mass(gameObjectIdentifier: GameObjectIdentifier, mass: number): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkParameterType(mass, 'number'); - getInstance() - .setRigidbodyNumericalProp('mass', gameObjectIdentifier, mass); + getInstance().setRigidbodyNumericalProp('mass', gameObjectIdentifier, mass); } /** @@ -617,11 +659,10 @@ export function set_mass(gameObjectIdentifier : GameObjectIdentifier, mass : num * @return the velocity at this moment represented in a Vector3. * @category Physics - Rigidbody */ -export function get_velocity(gameObjectIdentifier : GameObjectIdentifier) : Vector3 { +export function get_velocity(gameObjectIdentifier: GameObjectIdentifier): Vector3 { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - return getInstance() - .getRigidbodyVelocityVector3Prop('velocity', gameObjectIdentifier); + return getInstance().getRigidbodyVelocityVector3Prop('velocity', gameObjectIdentifier); } /** @@ -633,12 +674,11 @@ export function get_velocity(gameObjectIdentifier : GameObjectIdentifier) : Vect * @param velocity The new velocity for the rigidbody attached on the GameObject. * @category Physics - Rigidbody */ -export function set_velocity(gameObjectIdentifier : GameObjectIdentifier, velocity : Vector3) : void { +export function set_velocity(gameObjectIdentifier: GameObjectIdentifier, velocity: Vector3): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkVector3Parameter(velocity); - getInstance() - .setRigidbodyVelocityVector3Prop('velocity', gameObjectIdentifier, velocity); + getInstance().setRigidbodyVelocityVector3Prop('velocity', gameObjectIdentifier, velocity); } /** @@ -652,11 +692,10 @@ export function set_velocity(gameObjectIdentifier : GameObjectIdentifier, veloci * @return the angular velocity at this moment represented in a Vector3. * @category Physics - Rigidbody */ -export function get_angular_velocity(gameObjectIdentifier : GameObjectIdentifier) : Vector3 { +export function get_angular_velocity(gameObjectIdentifier: GameObjectIdentifier): Vector3 { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - return getInstance() - .getRigidbodyVelocityVector3Prop('angularVelocity', gameObjectIdentifier); + return getInstance().getRigidbodyVelocityVector3Prop('angularVelocity', gameObjectIdentifier); } /** @@ -670,12 +709,18 @@ export function get_angular_velocity(gameObjectIdentifier : GameObjectIdentifier * @param angularVelocity The new angular velocity for the rigidbody attached on the GameObject. * @category Physics - Rigidbody */ -export function set_angular_velocity(gameObjectIdentifier : GameObjectIdentifier, angularVelocity : Vector3) : void { +export function set_angular_velocity( + gameObjectIdentifier: GameObjectIdentifier, + angularVelocity: Vector3 +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkVector3Parameter(angularVelocity); - getInstance() - .setRigidbodyVelocityVector3Prop('angularVelocity', gameObjectIdentifier, angularVelocity); + getInstance().setRigidbodyVelocityVector3Prop( + 'angularVelocity', + gameObjectIdentifier, + angularVelocity + ); } /** @@ -689,12 +734,11 @@ export function set_angular_velocity(gameObjectIdentifier : GameObjectIdentifier * @param value The value of the new drag. * @category Physics - Rigidbody */ -export function set_drag(gameObjectIdentifier : GameObjectIdentifier, value: number) : void { +export function set_drag(gameObjectIdentifier: GameObjectIdentifier, value: number): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkParameterType(value, 'number'); - getInstance() - .setRigidbodyNumericalProp('drag', gameObjectIdentifier, value); + getInstance().setRigidbodyNumericalProp('drag', gameObjectIdentifier, value); } /** @@ -708,12 +752,11 @@ export function set_drag(gameObjectIdentifier : GameObjectIdentifier, value: num * @param value The value of the new angular drag. * @category Physics - Rigidbody */ -export function set_angular_drag(gameObjectIdentifier : GameObjectIdentifier, value: number) : void { +export function set_angular_drag(gameObjectIdentifier: GameObjectIdentifier, value: number): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkParameterType(value, 'number'); - getInstance() - .setRigidbodyNumericalProp('angularDrag', gameObjectIdentifier, value); + getInstance().setRigidbodyNumericalProp('angularDrag', gameObjectIdentifier, value); } /** @@ -725,15 +768,16 @@ export function set_angular_drag(gameObjectIdentifier : GameObjectIdentifier, va * @param {useGravity} Set to true if you want gravity to be applied on this rigidbody, false otherwise. * @category Physics - Rigidbody */ -export function set_use_gravity(gameObjectIdentifier : GameObjectIdentifier, useGravity : boolean) : void { +export function set_use_gravity( + gameObjectIdentifier: GameObjectIdentifier, + useGravity: boolean +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkParameterType(useGravity, 'boolean'); - getInstance() - .setUseGravityInternal(gameObjectIdentifier, useGravity); + getInstance().setUseGravityInternal(gameObjectIdentifier, useGravity); } - /** * Add an impulse force on the Rigidbody attached on the GameObject, **using its mass**. * @@ -743,12 +787,14 @@ export function set_use_gravity(gameObjectIdentifier : GameObjectIdentifier, use * @param The force vector. * @category Physics - Rigidbody */ -export function add_impulse_force(gameObjectIdentifier : GameObjectIdentifier, force : Vector3) : void { +export function add_impulse_force( + gameObjectIdentifier: GameObjectIdentifier, + force: Vector3 +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkVector3Parameter(force); - getInstance() - .addImpulseForceInternal(gameObjectIdentifier, force); + getInstance().addImpulseForceInternal(gameObjectIdentifier, force); } /** @@ -761,11 +807,10 @@ export function add_impulse_force(gameObjectIdentifier : GameObjectIdentifier, f * @param gameObjectIdentifier The identifier for the GameObject that you want to remove colliders for. * @category Physics - Collision */ -export function remove_collider_components(gameObjectIdentifier : GameObjectIdentifier) : void { +export function remove_collider_components(gameObjectIdentifier: GameObjectIdentifier): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - getInstance() - .removeColliderComponentsInternal(gameObjectIdentifier); + getInstance().removeColliderComponentsInternal(gameObjectIdentifier); } /** @@ -788,12 +833,14 @@ export function remove_collider_components(gameObjectIdentifier : GameObjectIden * @category Physics - Collision * @category Outside Lifecycle */ -export function on_collision_enter(gameObjectIdentifier : GameObjectIdentifier, eventFunction : Function) : void { +export function on_collision_enter( + gameObjectIdentifier: GameObjectIdentifier, + eventFunction: Function +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkParameterType(eventFunction, 'function'); - getInstance() - .setOnCollisionEnterInternal(gameObjectIdentifier, eventFunction); + getInstance().setOnCollisionEnterInternal(gameObjectIdentifier, eventFunction); } /** @@ -816,12 +863,14 @@ export function on_collision_enter(gameObjectIdentifier : GameObjectIdentifier, * @category Physics - Collision * @category Outside Lifecycle */ -export function on_collision_stay(gameObjectIdentifier : GameObjectIdentifier, eventFunction : Function) : void { +export function on_collision_stay( + gameObjectIdentifier: GameObjectIdentifier, + eventFunction: Function +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkParameterType(eventFunction, 'function'); - getInstance() - .setOnCollisionStayInternal(gameObjectIdentifier, eventFunction); + getInstance().setOnCollisionStayInternal(gameObjectIdentifier, eventFunction); } /** @@ -844,15 +893,16 @@ export function on_collision_stay(gameObjectIdentifier : GameObjectIdentifier, e * @category Physics - Collision * @category Outside Lifecycle */ -export function on_collision_exit(gameObjectIdentifier : GameObjectIdentifier, eventFunction : Function) : void { +export function on_collision_exit( + gameObjectIdentifier: GameObjectIdentifier, + eventFunction: Function +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkParameterType(eventFunction, 'function'); - getInstance() - .setOnCollisionExitInternal(gameObjectIdentifier, eventFunction); + getInstance().setOnCollisionExitInternal(gameObjectIdentifier, eventFunction); } - /** * Draw a text (string) on the screen with given **screen space position** in the current frame. * @@ -867,16 +917,14 @@ export function on_collision_exit(gameObjectIdentifier : GameObjectIdentifier, e * @param y The y coordinate of the text (in screen position). * @category Graphical User Interface */ -export function gui_label(text : string, x : number, y : number) : void { +export function gui_label(text: string, x: number, y: number): void { checkUnityAcademyExistence(); checkParameterType(text, 'string'); checkParameterType(x, 'number'); checkParameterType(y, 'number'); - getInstance() - .onGUI_Label(text, x, y); + getInstance().onGUI_Label(text, x, y); } - /** * Make a button on the screen with given **screen space position** in the current frame. When user clicks the button, the `onClick` function will be called. * @@ -916,7 +964,14 @@ export function gui_label(text : string, x : number, y : number) : void { * @param onClick The function that will be called when user clicks the button on screen. * @category Graphical User Interface */ -export function gui_button(text : string, x: number, y : number, width : number, height : number, onClick : Function) : void { +export function gui_button( + text: string, + x: number, + y: number, + width: number, + height: number, + onClick: Function +): void { checkUnityAcademyExistence(); checkParameterType(text, 'string'); checkParameterType(x, 'number'); @@ -924,8 +979,7 @@ export function gui_button(text : string, x: number, y : number, width : number, checkParameterType(width, 'number'); checkParameterType(height, 'number'); checkParameterType(onClick, 'function'); - getInstance() - .onGUI_Button(text, x, y, width, height, onClick); + getInstance().onGUI_Button(text, x, y, width, height, onClick); } /** @@ -941,13 +995,11 @@ export function gui_button(text : string, x: number, y : number, width : number, * @category Camera * @category Outside Lifecycle */ -export function get_main_camera_following_target() : GameObjectIdentifier { +export function get_main_camera_following_target(): GameObjectIdentifier { checkUnityAcademyExistence(); - return getInstance() - .getGameObjectIdentifierForPrimitiveGameObject('MainCameraFollowingTarget'); + return getInstance().getGameObjectIdentifierForPrimitiveGameObject('MainCameraFollowingTarget'); } - /** * Request for main camera control and get a GameObject identifier that can directly be used to control the main camera's position and rotation. * @@ -959,10 +1011,9 @@ export function get_main_camera_following_target() : GameObjectIdentifier { * @category Camera * @category Outside Lifecycle */ -export function request_for_main_camera_control() : GameObjectIdentifier { +export function request_for_main_camera_control(): GameObjectIdentifier { checkUnityAcademyExistence(); - return getInstance() - .requestForMainCameraControlInternal(); + return getInstance().requestForMainCameraControlInternal(); } /** @@ -974,12 +1025,15 @@ export function request_for_main_camera_control() : GameObjectIdentifier { * * @category Common */ -export function set_custom_prop(gameObjectIdentifier : GameObjectIdentifier, propName : string, value : any) : void { +export function set_custom_prop( + gameObjectIdentifier: GameObjectIdentifier, + propName: string, + value: any +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkParameterType(propName, 'string'); - getInstance() - .setCustomPropertyInternal(gameObjectIdentifier, propName, value); + getInstance().setCustomPropertyInternal(gameObjectIdentifier, propName, value); } /** @@ -992,12 +1046,11 @@ export function set_custom_prop(gameObjectIdentifier : GameObjectIdentifier, pro * * @category Common */ -export function get_custom_prop(gameObjectIdentifier : GameObjectIdentifier, propName : string) : any { +export function get_custom_prop(gameObjectIdentifier: GameObjectIdentifier, propName: string): any { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); checkParameterType(propName, 'string'); - return getInstance() - .getCustomPropertyInternal(gameObjectIdentifier, propName); + return getInstance().getCustomPropertyInternal(gameObjectIdentifier, propName); } /** @@ -1010,7 +1063,7 @@ export function get_custom_prop(gameObjectIdentifier : GameObjectIdentifier, pro * * @category Maths */ -export function vector3(x : number, y : number, z : number) : Vector3 { +export function vector3(x: number, y: number, z: number): Vector3 { checkParameterType(x, 'number'); checkParameterType(y, 'number'); checkParameterType(z, 'number'); @@ -1025,7 +1078,7 @@ export function vector3(x : number, y : number, z : number) : Vector3 { * * @category Maths */ -export function get_x(vector : Vector3) : number { +export function get_x(vector: Vector3): number { checkVector3Parameter(vector); return vector.x; } @@ -1038,7 +1091,7 @@ export function get_x(vector : Vector3) : number { * * @category Maths */ -export function get_y(vector : Vector3) : number { +export function get_y(vector: Vector3): number { checkVector3Parameter(vector); return vector.y; } @@ -1051,7 +1104,7 @@ export function get_y(vector : Vector3) : number { * * @category Maths */ -export function get_z(vector : Vector3) : number { +export function get_z(vector: Vector3): number { checkVector3Parameter(vector); return vector.z; } @@ -1064,7 +1117,7 @@ export function get_z(vector : Vector3) : number { * * @category Maths */ -export function scale_vector(vector : Vector3, factor : number) : Vector3 { +export function scale_vector(vector: Vector3, factor: number): Vector3 { checkVector3Parameter(vector); checkParameterType(factor, 'number'); return scaleVector(vector, factor); @@ -1078,7 +1131,7 @@ export function scale_vector(vector : Vector3, factor : number) : Vector3 { * * @category Maths */ -export function add_vectors(vectorA : Vector3, vectorB : Vector3) : Vector3 { +export function add_vectors(vectorA: Vector3, vectorB: Vector3): Vector3 { checkVector3Parameter(vectorA); checkVector3Parameter(vectorB); return addVectors(vectorA, vectorB); @@ -1092,7 +1145,7 @@ export function add_vectors(vectorA : Vector3, vectorB : Vector3) : Vector3 { * * @category Maths */ -export function vector_difference(vectorA : Vector3, vectorB : Vector3) : Vector3 { +export function vector_difference(vectorA: Vector3, vectorB: Vector3): Vector3 { checkVector3Parameter(vectorA); checkVector3Parameter(vectorB); return vectorDifference(vectorA, vectorB); @@ -1106,7 +1159,7 @@ export function vector_difference(vectorA : Vector3, vectorB : Vector3) : Vector * * @category Maths */ -export function dot(vectorA : Vector3, vectorB : Vector3) : number { +export function dot(vectorA: Vector3, vectorB: Vector3): number { checkVector3Parameter(vectorA); checkVector3Parameter(vectorB); return dotProduct(vectorA, vectorB); @@ -1120,7 +1173,7 @@ export function dot(vectorA : Vector3, vectorB : Vector3) : number { * * @category Maths */ -export function cross(vectorA : Vector3, vectorB : Vector3) : Vector3 { +export function cross(vectorA: Vector3, vectorB: Vector3): Vector3 { checkVector3Parameter(vectorA); checkVector3Parameter(vectorB); return crossProduct(vectorA, vectorB); @@ -1133,7 +1186,7 @@ export function cross(vectorA : Vector3, vectorB : Vector3) : Vector3 { * * @category Maths */ -export function normalize(vector : Vector3) : Vector3 { +export function normalize(vector: Vector3): Vector3 { checkVector3Parameter(vector); return normalizeVector(vector); } @@ -1145,7 +1198,7 @@ export function normalize(vector : Vector3) : Vector3 { * * @category Maths */ -export function magnitude(vector : Vector3) : number { +export function magnitude(vector: Vector3): number { checkVector3Parameter(vector); return vectorMagnitude(vector); } @@ -1156,7 +1209,7 @@ export function magnitude(vector : Vector3) : number { * * @category Maths */ -export function zero_vector() : Vector3 { +export function zero_vector(): Vector3 { return zeroVector(); } @@ -1170,14 +1223,12 @@ export function zero_vector() : Vector3 { * * @category Maths */ -export function point_distance(pointA : Vector3, pointB : Vector3) : number { +export function point_distance(pointA: Vector3, pointB: Vector3): number { checkVector3Parameter(pointA); checkVector3Parameter(pointB); return pointDistance(pointA, pointB); } - - /** * * Documentation TODO @@ -1185,11 +1236,10 @@ export function point_distance(pointA : Vector3, pointB : Vector3) : number { * @category Sound / Audio * @category Outside Lifecycle */ -export function load_audio_clip_mp3(audioUrl: string) : AudioClipIdentifier { +export function load_audio_clip_mp3(audioUrl: string): AudioClipIdentifier { checkUnityAcademyExistence(); checkParameterType(audioUrl, 'string'); - return getInstance() - .loadAudioClipInternal(audioUrl, 'mp3'); + return getInstance().loadAudioClipInternal(audioUrl, 'mp3'); } /** @@ -1199,11 +1249,10 @@ export function load_audio_clip_mp3(audioUrl: string) : AudioClipIdentifier { * @category Sound / Audio * @category Outside Lifecycle */ -export function load_audio_clip_ogg(audioUrl: string) : AudioClipIdentifier { +export function load_audio_clip_ogg(audioUrl: string): AudioClipIdentifier { checkUnityAcademyExistence(); checkParameterType(audioUrl, 'string'); - return getInstance() - .loadAudioClipInternal(audioUrl, 'ogg'); + return getInstance().loadAudioClipInternal(audioUrl, 'ogg'); } /** @@ -1213,11 +1262,10 @@ export function load_audio_clip_ogg(audioUrl: string) : AudioClipIdentifier { * @category Sound / Audio * @category Outside Lifecycle */ -export function load_audio_clip_wav(audioUrl: string) : AudioClipIdentifier { +export function load_audio_clip_wav(audioUrl: string): AudioClipIdentifier { checkUnityAcademyExistence(); checkParameterType(audioUrl, 'string'); - return getInstance() - .loadAudioClipInternal(audioUrl, 'wav'); + return getInstance().loadAudioClipInternal(audioUrl, 'wav'); } /** @@ -1233,11 +1281,10 @@ export function load_audio_clip_wav(audioUrl: string) : AudioClipIdentifier { * @category Sound / Audio * @category Outside Lifecycle */ -export function instantiate_audio_source(audioClip : AudioClipIdentifier) : GameObjectIdentifier { +export function instantiate_audio_source(audioClip: AudioClipIdentifier): GameObjectIdentifier { // todo: check audio clip identifier type checkUnityAcademyExistence(); - return getInstance() - .instantiateAudioSourceInternal(audioClip); + return getInstance().instantiateAudioSourceInternal(audioClip); } /** @@ -1248,11 +1295,10 @@ export function instantiate_audio_source(audioClip : AudioClipIdentifier) : Game * * @category Sound / Audio */ -export function play_audio(audioSrc : GameObjectIdentifier) : void { +export function play_audio(audioSrc: GameObjectIdentifier): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(audioSrc); - getInstance() - .setAudioSourceProp('isPlaying', audioSrc, true); + getInstance().setAudioSourceProp('isPlaying', audioSrc, true); } /** @@ -1263,11 +1309,10 @@ export function play_audio(audioSrc : GameObjectIdentifier) : void { * * @category Sound / Audio */ -export function pause_audio(audioSrc : GameObjectIdentifier) : void { +export function pause_audio(audioSrc: GameObjectIdentifier): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(audioSrc); - getInstance() - .setAudioSourceProp('isPlaying', audioSrc, false); + getInstance().setAudioSourceProp('isPlaying', audioSrc, false); } /** @@ -1279,12 +1324,11 @@ export function pause_audio(audioSrc : GameObjectIdentifier) : void { * * @category Sound / Audio */ -export function set_audio_play_speed(audioSrc : GameObjectIdentifier, speed : number) : void { +export function set_audio_play_speed(audioSrc: GameObjectIdentifier, speed: number): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(audioSrc); checkParameterType(speed, 'number'); - getInstance() - .setAudioSourceProp('playSpeed', audioSrc, speed); + getInstance().setAudioSourceProp('playSpeed', audioSrc, speed); } /** @@ -1296,11 +1340,10 @@ export function set_audio_play_speed(audioSrc : GameObjectIdentifier, speed : nu * * @category Sound / Audio */ -export function get_audio_play_progress(audioSrc : GameObjectIdentifier) : number { +export function get_audio_play_progress(audioSrc: GameObjectIdentifier): number { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(audioSrc); - return getInstance() - .getAudioSourceProp('playProgress', audioSrc); + return getInstance().getAudioSourceProp('playProgress', audioSrc); } /** @@ -1312,59 +1355,57 @@ export function get_audio_play_progress(audioSrc : GameObjectIdentifier) : numbe * * @category Sound / Audio */ -export function set_audio_play_progress(audioSrc : GameObjectIdentifier, progress : number) : void { +export function set_audio_play_progress(audioSrc: GameObjectIdentifier, progress: number): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(audioSrc); checkParameterType(progress, 'number'); - getInstance() - .setAudioSourceProp('playProgress', audioSrc, progress); + getInstance().setAudioSourceProp('playProgress', audioSrc, progress); } /** * * @category Sound / Audio */ -export function change_audio_clip(audioSrc : GameObjectIdentifier, newAudioClip : AudioClipIdentifier) : void { +export function change_audio_clip( + audioSrc: GameObjectIdentifier, + newAudioClip: AudioClipIdentifier +): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(audioSrc); // todo: check audio clip identifier type - getInstance() - .setAudioSourceProp('audioClipIdentifier', audioSrc, newAudioClip); + getInstance().setAudioSourceProp('audioClipIdentifier', audioSrc, newAudioClip); } /** * * @category Sound / Audio */ -export function set_audio_looping(audioSrc : GameObjectIdentifier, looping : boolean) : void { +export function set_audio_looping(audioSrc: GameObjectIdentifier, looping: boolean): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(audioSrc); checkParameterType(looping, 'boolean'); - getInstance() - .setAudioSourceProp('isLooping', audioSrc, looping); + getInstance().setAudioSourceProp('isLooping', audioSrc, looping); } /** * * @category Sound / Audio */ -export function set_audio_volume(audioSrc : GameObjectIdentifier, volume : number) : void { +export function set_audio_volume(audioSrc: GameObjectIdentifier, volume: number): void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(audioSrc); checkParameterType(volume, 'number'); - getInstance() - .setAudioSourceProp('volume', audioSrc, volume); + getInstance().setAudioSourceProp('volume', audioSrc, volume); } /** * * @category Sound / Audio */ -export function is_audio_playing(audioSrc : GameObjectIdentifier) : boolean { +export function is_audio_playing(audioSrc: GameObjectIdentifier): boolean { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(audioSrc); - return getInstance() - .getAudioSourceProp('isPlaying', audioSrc); + return getInstance().getAudioSourceProp('isPlaying', audioSrc); } /** @@ -1378,11 +1419,10 @@ export function is_audio_playing(audioSrc : GameObjectIdentifier) : boolean { * @category Common * @category Outside Lifecycle */ -export function debug_log(content : any) : void { +export function debug_log(content: any): void { checkUnityAcademyExistence(); const contentStr = content.toString(); - getInstance() - .studentLogger(contentStr, 'log'); + getInstance().studentLogger(contentStr, 'log'); } /** @@ -1396,11 +1436,10 @@ export function debug_log(content : any) : void { * @category Common * @category Outside Lifecycle */ -export function debug_logwarning(content : any) : void { +export function debug_logwarning(content: any): void { checkUnityAcademyExistence(); const contentStr = content.toString(); - getInstance() - .studentLogger(contentStr, 'warning'); + getInstance().studentLogger(contentStr, 'warning'); } /** @@ -1416,11 +1455,10 @@ export function debug_logwarning(content : any) : void { * @category Common * @category Outside Lifecycle */ -export function debug_logerror(content : any) : void { +export function debug_logerror(content: any): void { checkUnityAcademyExistence(); const contentStr = content.toString(); - getInstance() - .studentLogger(contentStr, 'error'); + getInstance().studentLogger(contentStr, 'error'); } /** @@ -1433,7 +1471,11 @@ export function debug_logerror(content : any) : void { * * @category Sound / Audio */ -export function set_audio_listener_position(positionX: number, positionY: number, positionZ: number) { +export function set_audio_listener_position( + positionX: number, + positionY: number, + positionZ: number +) { // todo: check audio clip identifier type checkUnityAcademyExistence(); checkParameterType(positionX, 'number'); @@ -1452,7 +1494,14 @@ export function set_audio_listener_position(positionX: number, positionY: number * * @category Sound / Audio */ -export function play_audio_clip_3d_sound(audioClip : AudioClipIdentifier, volume: number, loop: boolean, positionX: number, positionY: number, positionZ: number) { +export function play_audio_clip_3d_sound( + audioClip: AudioClipIdentifier, + volume: number, + loop: boolean, + positionX: number, + positionY: number, + positionZ: number +) { // todo: check audio clip identifier type checkUnityAcademyExistence(); checkParameterType(volume, 'number'); diff --git a/src/bundles/wasm/index.ts b/src/bundles/wasm/index.ts index 3ba88ac3f5..6051362ede 100644 --- a/src/bundles/wasm/index.ts +++ b/src/bundles/wasm/index.ts @@ -81,7 +81,4 @@ * @module wasm * @author Kim Yongbeom */ -export { - wcompile, - wrun, -} from './wabt'; +export { wcompile, wrun } from './wabt'; diff --git a/src/bundles/wasm/wabt.ts b/src/bundles/wasm/wabt.ts index a108fd1cfb..00c1c0ec2b 100644 --- a/src/bundles/wasm/wabt.ts +++ b/src/bundles/wasm/wabt.ts @@ -1,5 +1,5 @@ -import { compile } from 'source-academy-wabt'; import { objectToLinkedList } from 'source-academy-utils'; +import { compile } from 'source-academy-wabt'; /** * Compile a (hopefully valid) WebAssembly Text module to binary. diff --git a/src/tabs/ArcadeTwod/index.tsx b/src/tabs/ArcadeTwod/index.tsx index ebf476d16f..268d647654 100644 --- a/src/tabs/ArcadeTwod/index.tsx +++ b/src/tabs/ArcadeTwod/index.tsx @@ -1,8 +1,8 @@ -import React from 'react'; -import Phaser from 'phaser'; -import { type DebuggerContext } from '../../typings/type_helpers'; import { Button, ButtonGroup } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; +import Phaser from 'phaser'; +import React from 'react'; +import { type DebuggerContext } from '../../typings/type_helpers'; /** * Game display tab for user-created games made with the Arcade2D module. @@ -52,7 +52,7 @@ class A2dUiButtons extends React.Component { constructor(props) { super(props); this.state = { - isPaused: false, + isPaused: false }; } @@ -84,20 +84,23 @@ class GameTab extends React.Component { constructor(props) { super(props); this.state = { - game: undefined, + game: undefined }; } componentDidMount() { // Only mount the component when the Arcade2D tab is active - if (document.querySelector('[id="bp4-tab-panel_side-content-tabs_Arcade2D Tab"]')?.ariaHidden === 'true') { + if ( + document.querySelector('[id="bp4-tab-panel_side-content-tabs_Arcade2D Tab"]')?.ariaHidden === + 'true' + ) { return; } // Config will exist since it is checked in toSpawn const config = this.props.context.result?.value?.gameConfig; this.setState({ - game: new Phaser.Game(config), + game: new Phaser.Game(config) }); } @@ -132,7 +135,7 @@ class GameTab extends React.Component { display: 'flex', alignItems: 'center', justifyContent: 'center', - flexDirection: 'column', + flexDirection: 'column' }} >
@@ -176,5 +179,5 @@ export default { * displayed in the side contents panel. * @see https://blueprintjs.com/docs/#icons */ - iconName: IconNames.SHAPES, + iconName: IconNames.SHAPES }; diff --git a/src/tabs/CopyGc/index.tsx b/src/tabs/CopyGc/index.tsx index 9db02657d3..6f65bb0077 100644 --- a/src/tabs/CopyGc/index.tsx +++ b/src/tabs/CopyGc/index.tsx @@ -1,7 +1,7 @@ +import { Icon, Slider } from '@blueprintjs/core'; import React from 'react'; -import { Slider, Icon } from '@blueprintjs/core'; -import { ThemeColor } from './style'; import { COMMAND } from '../../bundles/copy_gc/types'; +import { ThemeColor } from './style'; type Props = { children?: never; @@ -17,15 +17,15 @@ type State = { tags: number[]; heap: number[]; commandHeap: any[]; - command: String; + command: string; flips: number[]; toMemoryMatrix: number[][]; fromMemoryMatrix: number[][]; firstChild: number; lastChild: number; - description: String; - leftDesc: String; - rightDesc: String; + description: string; + leftDesc: string; + rightDesc: string; running: boolean; }; @@ -50,17 +50,13 @@ class CopyGC extends React.Component { description: '', rightDesc: '', leftDesc: '', - running: false, + running: false }; } componentDidMount() { const { debuggerContext } = this.props; - if ( - debuggerContext - && debuggerContext.result - && debuggerContext.result.value - ) { + if (debuggerContext && debuggerContext.result && debuggerContext.result.value) { this.initialize_state(); } } @@ -114,7 +110,7 @@ class CopyGC extends React.Component { description, leftDesc, rightDesc, - running, + running }; }); }; @@ -131,7 +127,7 @@ class CopyGC extends React.Component { lastChild: commandHeap[newValue].right, description: commandHeap[newValue].desc, leftDesc: commandHeap[newValue].leftDesc, - rightDesc: commandHeap[newValue].rightDesc, + rightDesc: commandHeap[newValue].rightDesc }; }); }; @@ -152,7 +148,7 @@ class CopyGC extends React.Component { lastChild: commandHeap[value].right, description: commandHeap[value].desc, leftDesc: commandHeap[value].leftDesc, - rightDesc: commandHeap[value].rightDesc, + rightDesc: commandHeap[value].rightDesc }; }); } @@ -173,7 +169,7 @@ class CopyGC extends React.Component { lastChild: commandHeap[value].right, description: commandHeap[value].desc, leftDesc: commandHeap[value].leftDesc, - rightDesc: commandHeap[value].rightDesc, + rightDesc: commandHeap[value].rightDesc }; }); } @@ -248,8 +244,7 @@ class CopyGC extends React.Component {

- This is a visualiser for stop and copy garbage collector. Check - the guide{' '} + This is a visualiser for stop and copy garbage collector. Check the guide{' '} here @@ -261,41 +256,37 @@ class CopyGC extends React.Component { style={{ display: 'flex', flexDirection: 'row', - marginTop: 10, + marginTop: 10 }} > - {state.leftDesc - ? ( -

- - {state.leftDesc} -
- ) - : ( - false - )} - {state.rightDesc - ? ( -
- - {state.rightDesc} -
- ) - : ( - false - )} + {state.leftDesc ? ( +
+ + {state.leftDesc} +
+ ) : ( + false + )} + {state.rightDesc ? ( +
+ + {state.rightDesc} +
+ ) : ( + false + )}

@@ -323,31 +314,33 @@ class CopyGC extends React.Component {

{state.toSpace === 0 ? 'To Space' : 'From Space'}

- {toMemoryMatrix - && toMemoryMatrix.length > 0 - && toMemoryMatrix.map((item, row) => ( -
+ {toMemoryMatrix && + toMemoryMatrix.length > 0 && + toMemoryMatrix.map((item, row) => ( +
{row * state.column} - {item - && item.length > 0 - && item.map((content) => { + {item && + item.length > 0 && + item.map((content) => { const color = this.getMemoryColor(content); const bgColor = this.getBackgroundColor(content); return (
@@ -360,54 +353,56 @@ class CopyGC extends React.Component {

{state.toSpace > 0 ? 'To Space' : 'From Space'}

- {fromMemoryMatrix - && fromMemoryMatrix.length > 0 - && fromMemoryMatrix.map((item, row) => ( -
- - {row * state.column + state.memorySize / 2} - + {fromMemoryMatrix && + fromMemoryMatrix.length > 0 && + fromMemoryMatrix.map((item, row) => ( +
+ {row * state.column + state.memorySize / 2} {item && item.length > 0 ? item.map((content) => { - const color = this.getMemoryColor(content); - const bgColor = this.getBackgroundColor(content); - return ( -
- -
- ); - }) + > + +
+ ); + }) : false}
))}
-
+
defined @@ -417,7 +412,7 @@ class CopyGC extends React.Component { width={10} height={10} style={{ - backgroundColor: ThemeColor.PINK, + backgroundColor: ThemeColor.PINK }} /> tag @@ -427,7 +422,7 @@ class CopyGC extends React.Component { width={10} height={10} style={{ - backgroundColor: ThemeColor.GREY, + backgroundColor: ThemeColor.GREY }} /> empty or undefined @@ -440,8 +435,7 @@ class CopyGC extends React.Component { return (

- This is a visualiser for stop and copy garbage collector. Check the - guide{' '} + This is a visualiser for stop and copy garbage collector. Check the guide{' '} here @@ -457,5 +451,5 @@ export default { toSpawn: () => true, body: (debuggerContext: any) => , label: 'Copying Garbage Collector', - iconName: 'duplicate', + iconName: 'duplicate' }; diff --git a/src/tabs/CopyGc/style.tsx b/src/tabs/CopyGc/style.tsx index aa890ff501..c1fddd6f1c 100644 --- a/src/tabs/CopyGc/style.tsx +++ b/src/tabs/CopyGc/style.tsx @@ -5,9 +5,9 @@ export enum ThemeColor { GREEN = '#42a870', YELLOW = '#f0d60e', RED = 'red', - BLACK = 'black', + BLACK = 'black' } export const FONT = { - SMALL: 10, + SMALL: 10 }; diff --git a/src/tabs/Csg/canvas_holder.tsx b/src/tabs/Csg/canvas_holder.tsx index 6ede5a2b47..254a5c3c75 100644 --- a/src/tabs/Csg/canvas_holder.tsx +++ b/src/tabs/Csg/canvas_holder.tsx @@ -5,17 +5,18 @@ import React from 'react'; import { Core } from '../../bundles/csg/core.js'; import StatefulRenderer from '../../bundles/csg/stateful_renderer.js'; import type { RenderGroup } from '../../bundles/csg/utilities.js'; +import { + BP_CARD_BORDER_RADIUS, + BP_TAB_BUTTON_MARGIN, + BP_TAB_PANEL_MARGIN, + BP_TEXT_MARGIN, + CANVAS_MAX_WIDTH +} from '../common/css_constants.js'; import HoverControlHint from './hover_control_hint'; import type { CanvasHolderProps, CanvasHolderState } from './types'; -import { BP_CARD_BORDER_RADIUS, BP_TAB_BUTTON_MARGIN, BP_TAB_PANEL_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '../common/css_constants.js'; - - /* [Main] */ -export default class CanvasHolder extends React.Component< -CanvasHolderProps, -CanvasHolderState -> { +export default class CanvasHolder extends React.Component { private readonly canvasReference: React.RefObject = React.createRef(); private statefulRenderer: StatefulRenderer | null = null; @@ -24,20 +25,18 @@ CanvasHolderState super(props); this.state = { - isContextLost: false, + isContextLost: false }; } componentDidMount() { console.debug(`>>> MOUNT #${this.props.componentNumber}`); - let { current: canvas } = this.canvasReference; + const { current: canvas } = this.canvasReference; if (canvas === null) return; - let renderGroups: RenderGroup[] = Core - .getRenderGroupManager() - .getGroupsToRender(); - let lastRenderGroup: RenderGroup = renderGroups.at(-1) as RenderGroup; + const renderGroups: RenderGroup[] = Core.getRenderGroupManager().getGroupsToRender(); + const lastRenderGroup: RenderGroup = renderGroups.at(-1) as RenderGroup; this.statefulRenderer = new StatefulRenderer( canvas, @@ -45,7 +44,7 @@ CanvasHolderState this.props.componentNumber, () => this.setState({ isContextLost: true }), - () => this.setState({ isContextLost: false }), + () => this.setState({ isContextLost: false }) ); this.statefulRenderer.start(true); } @@ -61,105 +60,98 @@ CanvasHolderState // canvasReference via the ref attribute, for imperatively modifying the // canvas render() { - return <> -

-
- - - - - -
- + return ( + <>
- + + + + + +
+ +
+ > + +
-
-
-

- WebGL Context Lost -

- -

- Your GPU is probably busy. Waiting for browser to re-establish connection... -

-
- ; +

+ WebGL Context Lost +

+ +

+ Your GPU is probably busy. Waiting for browser to re-establish connection... +

+
+ + ); } } diff --git a/src/tabs/Csg/hover_control_hint.tsx b/src/tabs/Csg/hover_control_hint.tsx index ab051ca324..63ba5e0965 100644 --- a/src/tabs/Csg/hover_control_hint.tsx +++ b/src/tabs/Csg/hover_control_hint.tsx @@ -8,26 +8,21 @@ import type { HintProps } from './types'; /* [Main] */ export default class HoverControlHint extends React.Component { render() { - return
- - - -
; + + + +
+ ); } } diff --git a/src/tabs/Csg/index.tsx b/src/tabs/Csg/index.tsx index b4f89c63af..410e711362 100644 --- a/src/tabs/Csg/index.tsx +++ b/src/tabs/Csg/index.tsx @@ -1,39 +1,30 @@ /* [Imports] */ -import type { ReactElement } from 'react'; import { IconNames } from '@blueprintjs/icons'; +import type { ReactElement } from 'react'; import { Core } from '../../bundles/csg/core.js'; -import { - type CsgModuleState, -} from '../../bundles/csg/utilities.js'; +import { type CsgModuleState } from '../../bundles/csg/utilities.js'; import { type DebuggerContext } from '../../typings/type_helpers'; import CanvasHolder from './canvas_holder'; - - /* [Exports] */ export default { // Called by the frontend to decide whether to spawn the CSG tab toSpawn(debuggerContext: DebuggerContext): boolean { - let moduleState: CsgModuleState = debuggerContext.context.moduleContexts.csg.state; + const moduleState: CsgModuleState = debuggerContext.context.moduleContexts.csg.state; // toSpawn() is checked before the frontend calls body() if needed, so we // initialise Core for the first time over on the tabs' end here Core.initialize(moduleState); - return Core.getRenderGroupManager() - .shouldRender(); + return Core.getRenderGroupManager().shouldRender(); }, // Called by the frontend to know what to render in the CSG tab body(_debuggerContext: DebuggerContext): ReactElement { - return ( - - ); + return ; }, // BlueprintJS icon name iconName: IconNames.SHAPES, // Icon tooltip in sidebar - label: 'CSG Tab', + label: 'CSG Tab' }; diff --git a/src/tabs/Csg/types.ts b/src/tabs/Csg/types.ts index dc71142984..1a4b313225 100644 --- a/src/tabs/Csg/types.ts +++ b/src/tabs/Csg/types.ts @@ -1,8 +1,6 @@ /* [Imports] */ import { type IconName } from '@blueprintjs/icons'; - - /* [Exports] */ // React Component Props for the CSG canvas holder diff --git a/src/tabs/Curve/__tests__/Curve.tsx b/src/tabs/Curve/__tests__/Curve.tsx index 03f31ac2a5..23aca0d0ba 100644 --- a/src/tabs/Curve/__tests__/Curve.tsx +++ b/src/tabs/Curve/__tests__/Curve.tsx @@ -1,18 +1,27 @@ -import { CurveTab } from ".." -import { animate_3D_curve, animate_curve, draw_3D_connected, draw_connected } from "../../../bundles/curve" -import type { CurveModuleState } from "../../../bundles/curve/types" -import { mockDebuggerContext } from "../../common/testUtils" +import { CurveTab } from '..'; +import { + animate_3D_curve, + animate_curve, + draw_3D_connected, + draw_connected +} from '../../../bundles/curve'; +import type { CurveModuleState } from '../../../bundles/curve/types'; +import { mockDebuggerContext } from '../../common/testUtils'; test('Curve animations error gracefully', () => { - const badAnimation = animate_curve(1, 60, draw_connected(200), t => 1 as any) - const mockContext = mockDebuggerContext({ drawnCurves: [badAnimation] }, 'curve'); - expect() - .toMatchSnapshot() -}) + const badAnimation = animate_curve(1, 60, draw_connected(200), (t) => 1 as any); + const mockContext = mockDebuggerContext( + { drawnCurves: [badAnimation] }, + 'curve' + ); + expect().toMatchSnapshot(); +}); test('Curve 3D animations error gracefully', () => { - const badAnimation = animate_3D_curve(1, 60, draw_3D_connected(200), t => 1 as any) - const mockContext = mockDebuggerContext({ drawnCurves: [badAnimation] }, 'curve'); - expect() - .toMatchSnapshot() -}) \ No newline at end of file + const badAnimation = animate_3D_curve(1, 60, draw_3D_connected(200), (t) => 1 as any); + const mockContext = mockDebuggerContext( + { drawnCurves: [badAnimation] }, + 'curve' + ); + expect().toMatchSnapshot(); +}); diff --git a/src/tabs/Curve/animation_canvas_3d_curve.tsx b/src/tabs/Curve/animation_canvas_3d_curve.tsx index c78988e947..347123b331 100644 --- a/src/tabs/Curve/animation_canvas_3d_curve.tsx +++ b/src/tabs/Curve/animation_canvas_3d_curve.tsx @@ -4,10 +4,10 @@ import { Tooltip2 } from '@blueprintjs/popover2'; import React from 'react'; import { type AnimatedCurve } from '../../bundles/curve/types'; import AutoLoopSwitch from '../common/AutoLoopSwitch'; -import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '../common/css_constants'; +import ButtonComponent from '../common/ButtonComponent'; import PlayButton from '../common/PlayButton'; import WebGLCanvas from '../common/WebglCanvas'; -import ButtonComponent from '../common/ButtonComponent'; +import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '../common/css_constants'; type Props = { animation: AnimatedCurve; @@ -38,10 +38,7 @@ type State = { * * Uses WebGLCanvas internally. */ -export default class AnimationCanvas3dCurve extends React.Component< -Props, -State -> { +export default class AnimationCanvas3dCurve extends React.Component { private canvas: HTMLCanvasElement | null; /** @@ -69,7 +66,7 @@ State isPlaying: false, wasPlaying: false, isAutoLooping: true, - displayAngle: 0, + displayAngle: 0 }; this.canvas = null; @@ -89,9 +86,7 @@ State private drawFrame = () => { try { if (this.canvas) { - const frame = this.props.animation.getFrame( - this.state.animTimestamp / 1000, - ); + const frame = this.props.animation.getFrame(this.state.animTimestamp / 1000); frame.draw(this.canvas); } } catch (error) { @@ -100,7 +95,7 @@ State } this.setState({ isPlaying: false, - errored: error, + errored: error }); } }; @@ -139,19 +134,19 @@ State // If auto loop is active, restart the animation this.setState( { - animTimestamp: 0, + animTimestamp: 0 }, - this.reqFrame, + this.reqFrame ); } else { // Otherwise, stop the animation this.setState( { - isPlaying: false, + isPlaying: false }, () => { this.callbackTimestamp = null; - }, + } ); } } else { @@ -159,9 +154,9 @@ State this.drawFrame(); this.setState( (prev) => ({ - animTimestamp: prev.animTimestamp + currentFrame, + animTimestamp: prev.animTimestamp + currentFrame }), - this.reqFrame, + this.reqFrame ); } }; @@ -173,18 +168,18 @@ State if (this.state.isPlaying) { this.setState( { - isPlaying: false, + isPlaying: false }, () => { this.callbackTimestamp = null; - }, + } ); } else { this.setState( { - isPlaying: true, + isPlaying: true }, - this.reqFrame, + this.reqFrame ); } }; @@ -193,17 +188,14 @@ State * Reset button click handler */ private onResetButtonClick = () => { - this.setState( - { animTimestamp: 0 }, - () => { - if (this.state.isPlaying) { - // Force stop - this.onPlayButtonClick(); - } + this.setState({ animTimestamp: 0 }, () => { + if (this.state.isPlaying) { + // Force stop + this.onPlayButtonClick(); + } - this.drawFrame(); - }, - ); + this.drawFrame(); + }); }; /** @@ -216,9 +208,9 @@ State (prev) => ({ wasPlaying: prev.isPlaying, isPlaying: false, - animTimestamp: newValue, + animTimestamp: newValue }), - this.drawFrame, + this.drawFrame ); }; @@ -228,7 +220,7 @@ State private onTimeSliderRelease = () => { this.setState( (prev) => ({ - isPlaying: prev.wasPlaying, + isPlaying: prev.wasPlaying }), () => { if (!this.state.isPlaying) { @@ -236,20 +228,20 @@ State } else { this.reqFrame(); } - }, + } ); }; private onAngleSliderChange = (newAngle: number) => { this.setState( { - displayAngle: newAngle, + displayAngle: newAngle }, () => { this.props.animation.angle = newAngle; if (this.state.isPlaying) this.reqFrame(); else this.drawFrame(); - }, + } ); }; @@ -258,142 +250,141 @@ State */ private onSwitchChange = () => { this.setState((prev) => ({ - isAutoLooping: !prev.isAutoLooping, + isAutoLooping: !prev.isAutoLooping })); }; public render() { - return
+ return (
- - - - - -
- - + + + + +
- + + + +
+
-
-
-
- {this.state.errored - ? ( -
-
+ {this.state.errored ? ( +
- -
+
+ flexDirection: 'row', + alignItems: 'center' + }} + > + +

An error occurred while running your animation!

Here's the details:

- + {this.state.errored.toString()} -
) - : ( +
+ ) : ( { this.canvas = r; }} /> )} +
-
; + ); } } diff --git a/src/tabs/Curve/canvas_3d_curve.tsx b/src/tabs/Curve/canvas_3d_curve.tsx index eb892c8079..1067d59086 100644 --- a/src/tabs/Curve/canvas_3d_curve.tsx +++ b/src/tabs/Curve/canvas_3d_curve.tsx @@ -1,10 +1,10 @@ import { Slider } from '@blueprintjs/core'; import React from 'react'; import type { CurveDrawn } from '../../bundles/curve/curves_webgl'; -import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '../common/css_constants'; +import { degreesToRadians } from '../../common/utilities'; import PlayButton from '../common/PlayButton'; import WebGLCanvas from '../common/WebglCanvas'; -import { degreesToRadians } from '../../common/utilities'; +import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '../common/css_constants'; type State = { /** @@ -40,7 +40,7 @@ export default class Canvas3dCurve extends React.Component { this.canvas = null; this.state = { displayAngle: 0, - isRotating: false, + isRotating: false }; } @@ -54,13 +54,13 @@ export default class Canvas3dCurve extends React.Component { this.setState( { displayAngle: newValue, - isRotating: false, + isRotating: false }, () => { if (this.canvas) { this.props.curve.redraw(degreesToRadians(newValue)); } - }, + } ); }; @@ -73,13 +73,13 @@ export default class Canvas3dCurve extends React.Component { this.setState( (prevState) => ({ - isRotating: !prevState.isRotating, + isRotating: !prevState.isRotating }), () => { if (this.state.isRotating) { this.autoRotate(); } - }, + } ); }; @@ -91,13 +91,12 @@ export default class Canvas3dCurve extends React.Component { this.setState( (prevState) => ({ ...prevState, - displayAngle: - prevState.displayAngle >= 360 ? 0 : prevState.displayAngle + 2, + displayAngle: prevState.displayAngle >= 360 ? 0 : prevState.displayAngle + 2 }), () => { this.props.curve.redraw(degreesToRadians(this.state.displayAngle)); window.requestAnimationFrame(this.autoRotate); - }, + } ); } }; @@ -110,7 +109,7 @@ export default class Canvas3dCurve extends React.Component { if (this.canvas) { this.props.curve.redraw(degreesToRadians(angle)); } - }, + } ); }; @@ -122,72 +121,69 @@ export default class Canvas3dCurve extends React.Component { } public render() { - return
+ return (
- - - + paddingTop: BP_TEXT_MARGIN, + paddingBottom: BP_TEXT_MARGIN + }} + > + + + +
-
-
- { - this.canvas = r; +
+ > + { + this.canvas = r; + }} + /> +
-
; + ); } } diff --git a/src/tabs/Curve/index.tsx b/src/tabs/Curve/index.tsx index 2954e870ee..62ceadabbe 100644 --- a/src/tabs/Curve/index.tsx +++ b/src/tabs/Curve/index.tsx @@ -1,11 +1,11 @@ import type { CurveModuleState } from '../../bundles/curve/types'; import { glAnimation } from '../../typings/anim_types'; -import MultiItemDisplay from '../common/MultItemDisplay'; import { getModuleState, type DebuggerContext, type ModuleTab } from '../../typings/type_helpers'; -import Curve3DAnimationCanvas from './animation_canvas_3d_curve'; -import CurveCanvas3D from './canvas_3d_curve'; import AnimationCanvas from '../common/AnimationCanvas'; +import MultiItemDisplay from '../common/MultItemDisplay'; import WebGLCanvas from '../common/WebglCanvas'; +import Curve3DAnimationCanvas from './animation_canvas_3d_curve'; +import CurveCanvas3D from './canvas_3d_curve'; export const CurveTab: ModuleTab = ({ context }) => { const { drawnCurves } = getModuleState(context, 'curve'); @@ -13,29 +13,25 @@ export const CurveTab: ModuleTab = ({ context }) => { const elemKey = i.toString(); if (glAnimation.isAnimation(curve)) { - return curve.is3D - ? ( - - ) - : ( - - ); - } - return curve.is3D() - ? ( - - ) - : ( - { - if (r) { - curve.init(r); - curve.redraw(0); - } - }} - key={elemKey} - /> + return curve.is3D ? ( + + ) : ( + ); + } + return curve.is3D() ? ( + + ) : ( + { + if (r) { + curve.init(r); + curve.redraw(0); + } + }} + key={elemKey} + /> + ); }); return ; @@ -50,5 +46,5 @@ export default { return ; }, label: 'Curves Tab', - iconName: 'media', // See https://blueprintjs.com/docs/#icons for more options + iconName: 'media' // See https://blueprintjs.com/docs/#icons for more options }; diff --git a/src/tabs/Game/constants.ts b/src/tabs/Game/constants.ts index a6afdeac56..b925b3adec 100644 --- a/src/tabs/Game/constants.ts +++ b/src/tabs/Game/constants.ts @@ -1,5 +1,5 @@ export enum Links { gameUserGuide = 'https://github.com/source-academy/modules/wiki/%5Bgame%5D-User-Guide', gameDeveloperDocumentation = 'https://github.com/source-academy/modules/wiki/%5Bgame%5D-Developer-Documentation', - gameAPIDocumentation = 'https://source-academy.github.io/modules/documentation/modules/game.html', + gameAPIDocumentation = 'https://source-academy.github.io/modules/documentation/modules/game.html' } diff --git a/src/tabs/Game/index.tsx b/src/tabs/Game/index.tsx index fcaa4f154e..2c9dac5f69 100644 --- a/src/tabs/Game/index.tsx +++ b/src/tabs/Game/index.tsx @@ -11,16 +11,12 @@ class Game extends React.PureComponent { public render() { return (
- Info: You need to visit the game to see the effect of your program. - Remember to save your work first! + Info: You need to visit the game to see the effect of your program. Remember to save your + work first!

You may find the game module{' '} - + documentation{' '} and{' '} @@ -37,5 +33,5 @@ export default { toSpawn: () => true, body: (debuggerContext: any) => , label: 'Game Info Tab', - iconName: 'info-sign', + iconName: 'info-sign' }; diff --git a/src/tabs/MarkSweep/index.tsx b/src/tabs/MarkSweep/index.tsx index db803a7f93..e18da8e125 100644 --- a/src/tabs/MarkSweep/index.tsx +++ b/src/tabs/MarkSweep/index.tsx @@ -1,5 +1,5 @@ +import { Icon, Slider } from '@blueprintjs/core'; import React from 'react'; -import { Slider, Icon } from '@blueprintjs/core'; import { ThemeColor } from './style'; type Props = { @@ -14,14 +14,14 @@ type State = { tags: number[]; heap: number[]; commandHeap: any[]; - command: String; + command: string; flips: number[]; memoryMatrix: number[][]; firstChild: number; lastChild: number; - description: String; - leftDesc: String; - rightDesc: String; + description: string; + leftDesc: string; + rightDesc: string; unmarked: number; marked: number; queue: number[]; @@ -50,17 +50,13 @@ class MarkSweep extends React.Component { unmarked: 0, marked: 1, queue: [], - running: false, + running: false }; } componentDidMount() { const { debuggerContext } = this.props; - if ( - debuggerContext - && debuggerContext.result - && debuggerContext.result.value - ) { + if (debuggerContext && debuggerContext.result && debuggerContext.result.value) { this.initialize_state(); } } @@ -112,7 +108,7 @@ class MarkSweep extends React.Component { unmarked, marked, queue, - running: true, + running: true })); }; @@ -132,7 +128,7 @@ class MarkSweep extends React.Component { description: commandHeap[value].desc, leftDesc: commandHeap[value].leftDesc, rightDesc: commandHeap[value].rightDesc, - queue: commandHeap[value].queue, + queue: commandHeap[value].queue }; }); } @@ -153,7 +149,7 @@ class MarkSweep extends React.Component { description: commandHeap[value].desc, leftDesc: commandHeap[value].leftDesc, rightDesc: commandHeap[value].rightDesc, - queue: commandHeap[value].queue, + queue: commandHeap[value].queue }; }); } @@ -171,15 +167,15 @@ class MarkSweep extends React.Component { description: commandHeap[newValue].desc, leftDesc: commandHeap[newValue].leftDesc, rightDesc: commandHeap[newValue].rightDesc, - queue: commandHeap[newValue].queue, + queue: commandHeap[newValue].queue }; }); }; private getlengthFunction = () => { const { debuggerContext } = this.props; - const commandHeap - = debuggerContext && debuggerContext.result.value + const commandHeap = + debuggerContext && debuggerContext.result.value ? debuggerContext.result.value.get_command() : []; return commandHeap.length; @@ -193,9 +189,7 @@ class MarkSweep extends React.Component { private getMemoryColor = (indexValue) => { const { heap, marked, unmarked, command } = this.state; const { debuggerContext } = this.props; - const roots = debuggerContext.result.value - ? debuggerContext.result.value.get_roots() - : []; + const roots = debuggerContext.result.value ? debuggerContext.result.value.get_roots() : []; const value = heap ? heap[indexValue] : 0; let color = ''; @@ -224,9 +218,7 @@ class MarkSweep extends React.Component { const { lastChild } = this.state; const { commandHeap, value, command } = this.state; const { debuggerContext } = this.props; - const roots = debuggerContext.result.value - ? debuggerContext.result.value.get_roots() - : []; + const roots = debuggerContext.result.value ? debuggerContext.result.value.get_roots() : []; const size1 = commandHeap[value].sizeLeft; const size2 = commandHeap[value].sizeRight; let color = ''; @@ -257,8 +249,7 @@ class MarkSweep extends React.Component {

- This is a visualiser for mark and sweep garbage collector. Check - the guide{' '} + This is a visualiser for mark and sweep garbage collector. Check the guide{' '} here @@ -270,7 +261,7 @@ class MarkSweep extends React.Component { style={{ display: 'flex', flexDirection: 'row', - marginTop: 10, + marginTop: 10 }} > {state.leftDesc && ( @@ -279,28 +270,26 @@ class MarkSweep extends React.Component { width={10} height={10} style={{ - backgroundColor: ThemeColor.GREEN, + backgroundColor: ThemeColor.GREEN }} /> {state.leftDesc}

)} - {state.rightDesc - ? ( -
- - {state.rightDesc} -
- ) - : ( - false - )} + {state.rightDesc ? ( +
+ + {state.rightDesc} +
+ ) : ( + false + )}

@@ -327,31 +316,33 @@ class MarkSweep extends React.Component {

- {memoryMatrix - && memoryMatrix.length > 0 - && memoryMatrix.map((item, row) => ( -
+ {memoryMatrix && + memoryMatrix.length > 0 && + memoryMatrix.map((item, row) => ( +
{row * state.column} - {item - && item.length > 0 - && item.map((content) => { + {item && + item.length > 0 && + item.map((content) => { const color = this.getMemoryColor(content); const bgColor = this.getBackgroundColor(content); return (
@@ -377,7 +368,7 @@ class MarkSweep extends React.Component { style={{ display: 'flex', flexDirection: 'row', - marginTop: 10, + marginTop: 10 }} >
@@ -385,7 +376,7 @@ class MarkSweep extends React.Component { width={10} height={10} style={{ - backgroundColor: ThemeColor.BLUE, + backgroundColor: ThemeColor.BLUE }} /> defined @@ -395,7 +386,7 @@ class MarkSweep extends React.Component { width={10} height={10} style={{ - backgroundColor: ThemeColor.PINK, + backgroundColor: ThemeColor.PINK }} /> tag @@ -405,7 +396,7 @@ class MarkSweep extends React.Component { width={10} height={10} style={{ - backgroundColor: ThemeColor.GREY, + backgroundColor: ThemeColor.GREY }} /> empty or undefined @@ -415,7 +406,7 @@ class MarkSweep extends React.Component { style={{ display: 'flex', flexDirection: 'row', - marginTop: 10, + marginTop: 10 }} >
@@ -426,7 +417,7 @@ class MarkSweep extends React.Component { width={10} height={10} style={{ - backgroundColor: 'red', + backgroundColor: 'red' }} /> marked @@ -436,7 +427,7 @@ class MarkSweep extends React.Component { width={10} height={10} style={{ - backgroundColor: 'black', + backgroundColor: 'black' }} /> unmarked @@ -450,8 +441,7 @@ class MarkSweep extends React.Component { return (

- This is a visualiser for mark and sweep garbage collector. Check the - guide{' '} + This is a visualiser for mark and sweep garbage collector. Check the guide{' '} here @@ -465,9 +455,7 @@ class MarkSweep extends React.Component { export default { toSpawn: () => true, - body: (debuggerContext: any) => ( - - ), + body: (debuggerContext: any) => , label: 'Mark Sweep Garbage Collector', - iconName: 'heat-grid', + iconName: 'heat-grid' }; diff --git a/src/tabs/Painter/index.tsx b/src/tabs/Painter/index.tsx index 1b50dadd7d..1edd98aecd 100644 --- a/src/tabs/Painter/index.tsx +++ b/src/tabs/Painter/index.tsx @@ -4,14 +4,14 @@ import type { DebuggerContext } from '../../typings/type_helpers'; import Modal from '../common/ModalDiv'; type Props = { - children?: never - className?: string - debuggerContext: any + children?: never; + className?: string; + debuggerContext: any; }; type State = { - modalOpen: boolean - selectedPainter: any | null + modalOpen: boolean; + selectedPainter: any | null; }; class Painter extends React.Component { @@ -19,19 +19,27 @@ class Painter extends React.Component { super(props); this.state = { modalOpen: false, - selectedPainter: null, + selectedPainter: null }; } handleOpen = (selectedPainter: LinePlot) => { this.setState({ modalOpen: true, - selectedPainter, + selectedPainter }); }; public render() { - const { context: { moduleContexts: { painter: { state: { drawnPainters } } } } } = this.props.debuggerContext; + const { + context: { + moduleContexts: { + painter: { + state: { drawnPainters } + } + } + } + } = this.props.debuggerContext; return (

@@ -50,28 +58,25 @@ class Painter extends React.Component { }} style={{ height: '20rem', - width: '20rem', + width: '20rem' }} >
- { - drawnPainters.map((drawnPainter: any, id:number) => { - const divId = `plotDiv${id}`; - return ( - <> -
this.handleOpen(drawnPainter)}>Click here to open Modal
-
{ - console.log(drawnPainter); - drawnPainter.draw(divId); - }} - >
- - ); - }) - } - + {drawnPainters.map((drawnPainter: any, id: number) => { + const divId = `plotDiv${id}`; + return ( + <> +
this.handleOpen(drawnPainter)}>Click here to open Modal
+
{ + console.log(drawnPainter); + drawnPainter.draw(divId); + }} + >
+ + ); + })}
); } @@ -85,5 +90,5 @@ export default { }, body: (debuggerContext: any) => , label: 'Painter Test Tab', - iconName: 'scatter-plot', + iconName: 'scatter-plot' }; diff --git a/src/tabs/Pixnflix/index.tsx b/src/tabs/Pixnflix/index.tsx index 366275928c..257742d43e 100644 --- a/src/tabs/Pixnflix/index.tsx +++ b/src/tabs/Pixnflix/index.tsx @@ -1,6 +1,6 @@ import { Button, ButtonGroup, Divider, NumericInput } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -// eslint-disable-next-line @typescript-eslint/no-shadow + import React, { type ChangeEvent, type DragEvent } from 'react'; import { DEFAULT_FPS, @@ -12,13 +12,13 @@ import { MAX_WIDTH, MIN_FPS, MIN_HEIGHT, - MIN_WIDTH, + MIN_WIDTH } from '../../bundles/pix_n_flix/constants'; import { + InputFeed, type BundlePacket, type ErrorLogger, - InputFeed, - type TabsPacket, + type TabsPacket } from '../../bundles/pix_n_flix/types'; type Props = { @@ -31,7 +31,7 @@ enum VideoMode { Video, Still, Accepting, - Image, + Image } type State = { @@ -77,7 +77,7 @@ class PixNFlix extends React.Component { FPS: DEFAULT_FPS, volume: DEFAULT_VOLUME, hasAudio: false, - mode: VideoMode.Video, + mode: VideoMode.Video }; const { debuggerContext } = this.props; this.pixNFlix = debuggerContext.result.value; @@ -108,8 +108,8 @@ class PixNFlix extends React.Component { this.$canvas, this.printError, { - onClickStill: this.onClickStill, - }, + onClickStill: this.onClickStill + } ); let mode: VideoMode = VideoMode.Video; if (inputFeed === InputFeed.Local) { @@ -123,7 +123,7 @@ class PixNFlix extends React.Component { FPS, volume: VOLUME, hasAudio: inputFeed === InputFeed.VideoURL, - mode, + mode }); } }; @@ -153,9 +153,9 @@ class PixNFlix extends React.Component { } else if (mode === VideoMode.Video) { this.setState( () => ({ - mode: VideoMode.Still, + mode: VideoMode.Still }), - this.handleStopVideo, + this.handleStopVideo ); } }; @@ -165,9 +165,9 @@ class PixNFlix extends React.Component { if (mode === VideoMode.Still) { this.setState( () => ({ - mode: VideoMode.Video, + mode: VideoMode.Video }), - this.handleStartVideo, + this.handleStartVideo ); } }; @@ -185,7 +185,7 @@ class PixNFlix extends React.Component { public handleFPSChange = (fps: number) => { if (fps >= MIN_FPS && fps <= MAX_FPS) { this.setState({ - FPS: fps, + FPS: fps }); if (this.isPixNFlix()) { this.pixNFlix.updateFPS(fps); @@ -194,15 +194,10 @@ class PixNFlix extends React.Component { }; public handleUpdateDimensions = (w: number, h: number) => { - if ( - w >= MIN_WIDTH - && w <= MAX_WIDTH - && h >= MIN_HEIGHT - && h <= MAX_HEIGHT - ) { + if (w >= MIN_WIDTH && w <= MAX_WIDTH && h >= MIN_HEIGHT && h <= MAX_HEIGHT) { this.setState({ width: w, - height: h, + height: h }); if (this.isPixNFlix()) { this.pixNFlix.updateDimensions(w, h); @@ -217,7 +212,7 @@ class PixNFlix extends React.Component { this.$video.src = URL.createObjectURL(file); this.setState({ hasAudio: true, - mode: VideoMode.Video, + mode: VideoMode.Video }); this.handleStartVideo(); } @@ -225,7 +220,7 @@ class PixNFlix extends React.Component { if (this.$image && mode === VideoMode.Accepting) { this.$image.src = URL.createObjectURL(file); this.setState({ - mode: VideoMode.Image, + mode: VideoMode.Image }); } } @@ -251,7 +246,7 @@ class PixNFlix extends React.Component { e.preventDefault(); const volume = parseFloat(e.target.value); this.setState({ - volume, + volume }); this.pixNFlix.updateVolume(volume); }; @@ -264,9 +259,7 @@ class PixNFlix extends React.Component { */ private isPixNFlix() { return ( - this.pixNFlix - && this.pixNFlix.toReplString - && this.pixNFlix.toReplString() === '[Pix N Flix]' + this.pixNFlix && this.pixNFlix.toReplString && this.pixNFlix.toReplString() === '[Pix N Flix]' ); } @@ -276,11 +269,7 @@ class PixNFlix extends React.Component { const videoIsActive = mode === VideoMode.Video; const isAccepting = mode === VideoMode.Accepting; return ( -
+
{

-
+
Volume: {

- Note: Is video lagging? Switch to 'still image' or adjust - FPS rate! + Note: Is video lagging? Switch to 'still image' or adjust FPS rate!

@@ -420,9 +406,7 @@ class PixNFlix extends React.Component { export default { toSpawn: () => true, - body: (debuggerContext: any) => ( - - ), + body: (debuggerContext: any) => , label: 'PixNFlix Live Feed', - iconName: 'mobile-video', + iconName: 'mobile-video' }; diff --git a/src/tabs/Plotly/index.tsx b/src/tabs/Plotly/index.tsx index b925b787d4..868feda5ee 100644 --- a/src/tabs/Plotly/index.tsx +++ b/src/tabs/Plotly/index.tsx @@ -4,14 +4,14 @@ import { type DebuggerContext } from '../../typings/type_helpers'; import Modal from '../common/ModalDiv'; type Props = { - children?: never - className?: string - debuggerContext: any + children?: never; + className?: string; + debuggerContext: any; }; type State = { - modalOpen: boolean - selectedPlot: any | null + modalOpen: boolean; + selectedPlot: any | null; }; class Plotly extends React.Component { @@ -19,19 +19,27 @@ class Plotly extends React.Component { super(props); this.state = { modalOpen: false, - selectedPlot: null, + selectedPlot: null }; } handleOpen = (selectedPlot: DrawnPlot) => { this.setState({ modalOpen: true, - selectedPlot, + selectedPlot }); }; public render() { - const { context: { moduleContexts: { plotly: { state: { drawnPlots } } } } } = this.props.debuggerContext; + const { + context: { + moduleContexts: { + plotly: { + state: { drawnPlots } + } + } + } + } = this.props.debuggerContext; return (
@@ -51,27 +59,27 @@ class Plotly extends React.Component { style={{ height: '80vh' }} >
- { - drawnPlots.map((drawnPlot: any, id:number) => { - const divId = `plotDiv${id}`; - return ( -
{ + const divId = `plotDiv${id}`; + return ( +
-
this.handleOpen(drawnPlot)}>Click here to open Modal
-
{ - drawnPlot.draw(divId); - }} - >
-
- ); - }) - } - + marginBottom: '5vh' + }} + key={divId} + > +
this.handleOpen(drawnPlot)}>Click here to open Modal
+
{ + drawnPlot.draw(divId); + }} + >
+
+ ); + })}
); } @@ -84,5 +92,5 @@ export default { }, body: (debuggerContext: any) => , label: 'Plotly Test Tab', - iconName: 'scatter-plot', -}; \ No newline at end of file + iconName: 'scatter-plot' +}; diff --git a/src/tabs/Repeat/index.tsx b/src/tabs/Repeat/index.tsx index 6159a09943..8323190d40 100644 --- a/src/tabs/Repeat/index.tsx +++ b/src/tabs/Repeat/index.tsx @@ -16,5 +16,5 @@ export default { toSpawn: () => true, body: (debuggerContext: any) => , label: 'Repeat Test Tab', - iconName: 'build', + iconName: 'build' }; diff --git a/src/tabs/Repl/index.tsx b/src/tabs/Repl/index.tsx index df66ec9d1c..d0ec3d995f 100644 --- a/src/tabs/Repl/index.tsx +++ b/src/tabs/Repl/index.tsx @@ -4,89 +4,114 @@ * @author Wang Zihan */ -import React from 'react'; -import type { DebuggerContext } from '../../typings/type_helpers'; import { Button } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import type { ProgrammableRepl } from '../../bundles/repl/programmable_repl'; +import React from 'react'; import { FONT_MESSAGE, MINIMUM_EDITOR_HEIGHT } from '../../bundles/repl/config'; +import type { ProgrammableRepl } from '../../bundles/repl/programmable_repl'; +import type { DebuggerContext } from '../../typings/type_helpers'; // If I use import for AceEditor it will cause runtime error and crash Source Academy when spawning tab in the new module building system. -// import AceEditor from 'react-ace'; +// eslint-disable-next-line @typescript-eslint/no-var-requires const AceEditor = require('react-ace').default; +import 'ace-builds/src-noconflict/ext-language_tools'; import 'ace-builds/src-noconflict/mode-javascript'; import 'ace-builds/src-noconflict/theme-twilight'; -import 'ace-builds/src-noconflict/ext-language_tools'; type Props = { programmableReplInstance: ProgrammableRepl; }; type State = { - editorHeight: number, - isDraggingDragBar: boolean, + editorHeight: number; + isDraggingDragBar: boolean; }; const BOX_PADDING_VALUE = 4; class ProgrammableReplGUI extends React.Component { - public replInstance : ProgrammableRepl; + public replInstance: ProgrammableRepl; + private editorAreaRect; + private editorInstance; + constructor(data: Props) { super(data); this.replInstance = data.programmableReplInstance; this.replInstance.setTabReactComponentInstance(this); this.state = { editorHeight: this.replInstance.editorHeight, - isDraggingDragBar: false, + isDraggingDragBar: false }; } + private dragBarOnMouseDown = (e) => { e.preventDefault(); this.setState({ isDraggingDragBar: true }); }; + private onMouseMove = (e) => { if (this.state.isDraggingDragBar) { - const height = Math.max(e.clientY - this.editorAreaRect.top - BOX_PADDING_VALUE * 2, MINIMUM_EDITOR_HEIGHT); + const height = Math.max( + e.clientY - this.editorAreaRect.top - BOX_PADDING_VALUE * 2, + MINIMUM_EDITOR_HEIGHT + ); this.replInstance.editorHeight = height; this.setState({ editorHeight: height }); this.editorInstance.resize(); } }; + private onMouseUp = (_e) => { this.setState({ isDraggingDragBar: false }); }; + componentDidMount() { document.addEventListener('mousemove', this.onMouseMove); document.addEventListener('mouseup', this.onMouseUp); } + componentWillUnmount() { document.removeEventListener('mousemove', this.onMouseMove); document.removeEventListener('mouseup', this.onMouseUp); } + public render() { const { editorHeight } = this.state; - const outputDivs : JSX.Element[] = []; + const outputDivs: JSX.Element[] = []; const outputStringCount = this.replInstance.outputStrings.length; for (let i = 0; i < outputStringCount; i++) { const str = this.replInstance.outputStrings[i]; if (str.outputMethod === 'richtext') { if (str.color === '') { - outputDivs.push(
); + outputDivs.push( +
+ ); } else { - outputDivs.push(
); + outputDivs.push( +
+ ); } } else if (str.color === '') { - outputDivs.push(
{ str.content }
); + outputDivs.push(
{str.content}
); } else { - outputDivs.push(
{ str.content }
); + outputDivs.push( +
+ {str.content} +
+ ); } } return ( @@ -95,48 +120,60 @@ class ProgrammableReplGUI extends React.Component { className="programmable-repl-button" icon={IconNames.PLAY} active={true} - onClick={() => this.replInstance.runCode()}// Note: Here if I directly use "this.replInstance.RunCode" instead using this lambda function, the "this" reference will become undefined and lead to a runtime error when user clicks the "Run" button + onClick={() => this.replInstance.runCode()} // Note: Here if I directly use "this.replInstance.RunCode" instead using this lambda function, the "this" reference will become undefined and lead to a runtime error when user clicks the "Run" button text="Run" /> @@ -110,5 +103,5 @@ export default { * displayed in the side contents panel. * @see https://blueprintjs.com/docs/#icons */ - iconName: 'music', + iconName: 'music' }; diff --git a/src/tabs/StereoSound/index.tsx b/src/tabs/StereoSound/index.tsx index 000b173af1..942681117e 100644 --- a/src/tabs/StereoSound/index.tsx +++ b/src/tabs/StereoSound/index.tsx @@ -12,19 +12,14 @@ const SoundTab: ModuleTab = ({ context }) => { const { audioPlayed } = getModuleState(context, 'sound'); const elements = audioPlayed.map((audio) => ( -