From 5b1fbe1e2fb1354a5f060f8c8e3794ce756e16a7 Mon Sep 17 00:00:00 2001 From: undergroundwires Date: Sun, 2 Jan 2022 18:20:14 +0100 Subject: [PATCH] Refactor code to comply with ESLint rules Major refactoring using ESLint with rules from AirBnb and Vue. Enable most of the ESLint rules and do necessary linting in the code. Also add more information for rules that are disabled to describe what they are and why they are disabled. Allow logging (`console.log`) in test files, and in development mode (e.g. when working with `npm run serve`), but disable it when environment is production (as pre-configured by Vue). Also add flag (`--mode production`) in `lint:eslint` command so production linting is executed earlier in lifecycle. Disable rules that requires a separate work. Such as ESLint rules that are broken in TypeScript: no-useless-constructor (eslint/eslint#14118) and no-shadow (eslint/eslint#13014). --- .eslintrc.js | 163 ++- babel.config.js | 6 +- package.json | 2 +- postcss.config.js | 6 +- src/application/ApplicationFactory.ts | 23 +- src/application/Common/Array.ts | 28 +- src/application/Common/Enum.ts | 91 +- .../IScriptingLanguageFactory.ts | 2 +- .../ScriptingLanguageFactory.ts | 39 +- src/application/Context/ApplicationContext.ts | 84 +- .../Context/ApplicationContextFactory.ts | 39 +- .../Context/IApplicationContext.ts | 12 +- .../Context/State/CategoryCollectionState.ts | 27 +- .../Context/State/Code/ApplicationCode.ts | 56 +- .../State/Code/Event/CodeChangedEvent.ts | 98 +- .../State/Code/Event/ICodeChangedEvent.ts | 12 +- .../State/Code/Generation/CodeBuilder.ts | 107 +- .../Code/Generation/CodeBuilderFactory.ts | 14 +- .../State/Code/Generation/ICodeBuilder.ts | 14 +- .../Code/Generation/ICodeBuilderFactory.ts | 5 +- .../State/Code/Generation/IUserScript.ts | 4 +- .../Code/Generation/IUserScriptGenerator.ts | 8 +- .../Code/Generation/Languages/BatchBuilder.ts | 19 +- .../Code/Generation/Languages/ShellBuilder.ts | 17 +- .../Code/Generation/UserScriptGenerator.ts | 99 +- .../Context/State/Code/IApplicationCode.ts | 6 +- .../State/Code/Position/CodePosition.ts | 37 +- .../State/Code/Position/ICodePosition.ts | 6 +- .../Context/State/Filter/FilterResult.ts | 28 +- .../Context/State/Filter/IFilterResult.ts | 8 +- .../Context/State/Filter/IUserFilter.ts | 10 +- .../Context/State/Filter/UserFilter.ts | 80 +- .../Context/State/ICategoryCollectionState.ts | 18 +- .../Context/State/Selection/IUserSelection.ts | 28 +- .../Context/State/Selection/SelectedScript.ts | 16 +- .../Context/State/Selection/UserSelection.ts | 226 ++-- .../BrowserOs/BrowserOsDetector.ts | 87 +- .../Environment/BrowserOs/DetectorBuilder.ts | 81 +- .../BrowserOs/IBrowserOsDetector.ts | 2 +- src/application/Environment/Environment.ts | 130 +-- src/application/Environment/IEnvironment.ts | 4 +- src/application/IApplicationFactory.ts | 2 +- src/application/Parser/ApplicationParser.ts | 43 +- .../Parser/CategoryCollectionParser.ts | 56 +- src/application/Parser/CategoryParser.ts | 113 +- src/application/Parser/DocumentationParser.ts | 93 +- .../Parser/ProjectInformationParser.ts | 15 +- .../Script/CategoryCollectionParseContext.ts | 24 +- .../Expressions/Expression/Expression.ts | 93 +- .../Expression/ExpressionEvaluationContext.ts | 17 +- .../Expression/ExpressionPosition.ts | 25 +- .../Expressions/Expression/IExpression.ts | 8 +- .../Expressions/ExpressionsCompiler.ts | 125 +-- .../Expressions/IExpressionsCompiler.ts | 6 +- .../Parser/CompositeExpressionParser.ts | 33 +- .../Expressions/Parser/IExpressionParser.ts | 2 +- .../Parser/Regex/ExpressionRegexBuilder.ts | 97 +- .../Expressions/Parser/Regex/RegexParser.ts | 67 +- .../Compiler/Expressions/Pipes/IPipe.ts | 4 +- .../Expressions/Pipes/IPipelineCompiler.ts | 2 +- .../PipeDefinitions/EscapeDoubleQuotes.ts | 49 +- .../Pipes/PipeDefinitions/InlinePowerShell.ts | 259 ++--- .../Compiler/Expressions/Pipes/PipeFactory.ts | 61 +- .../Expressions/Pipes/PipelineCompiler.ts | 40 +- .../ParameterSubstitutionParser.ts | 44 +- .../Expressions/SyntaxParsers/WithParser.ts | 99 +- .../Call/Argument/FunctionCallArgument.ts | 17 +- .../FunctionCallArgumentCollection.ts | 54 +- .../Call/Argument/IFunctionCallArgument.ts | 4 +- .../IFunctionCallArgumentCollection.ts | 8 +- .../Call/Compiler/FunctionCallCompiler.ts | 206 ++-- .../Function/Call/Compiler/ICompiledCode.ts | 4 +- .../Call/Compiler/IFunctionCallCompiler.ts | 8 +- .../Compiler/Function/Call/FunctionCall.ts | 19 +- .../Function/Call/FunctionCallParser.ts | 42 +- .../Compiler/Function/Call/IFunctionCall.ts | 4 +- .../Compiler/Function/ISharedFunction.ts | 22 +- .../Function/ISharedFunctionCollection.ts | 2 +- .../Function/ISharedFunctionsParser.ts | 2 +- .../Function/Parameter/FunctionParameter.ts | 13 +- .../Parameter/FunctionParameterCollection.ts | 36 +- .../Function/Parameter/IFunctionParameter.ts | 4 +- .../Parameter/IFunctionParameterCollection.ts | 4 +- .../Function/Shared/ParameterNameValidator.ts | 12 +- .../Compiler/Function/SharedFunction.ts | 83 +- .../Function/SharedFunctionCollection.ts | 34 +- .../Function/SharedFunctionsParser.ts | 169 +-- .../Parser/Script/Compiler/IScriptCompiler.ts | 6 +- .../Parser/Script/Compiler/ScriptCompiler.ts | 65 +- .../Script/ICategoryCollectionParseContext.ts | 4 +- src/application/Parser/Script/ScriptParser.ts | 74 +- .../Parser/Script/Syntax/BatchFileSyntax.ts | 10 +- .../Parser/Script/Syntax/ISyntaxFactory.ts | 3 +- .../Parser/Script/Syntax/ShellScriptSyntax.ts | 5 +- .../Parser/Script/Syntax/SyntaxFactory.ts | 14 +- .../ScriptingDefinition/CodeSubstituter.ts | 45 +- .../ScriptingDefinition/ICodeSubstituter.ts | 2 +- .../IScriptingDefinitionParser.ts | 0 .../ScriptingDefinitionParser.ts | 43 +- .../collections/collection.yaml.d.ts | 96 +- src/application/collections/macos.yaml | 4 +- src/domain/Application.ts | 61 +- src/domain/Category.ts | 58 +- src/domain/CategoryCollection.ts | 220 ++-- src/domain/IApplication.ts | 8 +- src/domain/ICategory.ts | 12 +- src/domain/ICategoryCollection.ts | 20 +- src/domain/IDocumentable.ts | 2 +- src/domain/IProjectInformation.ts | 17 +- src/domain/IScript.ts | 10 +- src/domain/IScriptCode.ts | 4 +- src/domain/IScriptingDefinition.ts | 8 +- src/domain/OperatingSystem.ts | 22 +- src/domain/ProjectInformation.ts | 88 +- src/domain/RecommendationLevel.ts | 4 +- src/domain/Script.ts | 34 +- src/domain/ScriptCode.ts | 113 +- src/domain/ScriptingDefinition.ts | 43 +- src/domain/ScriptingLanguage.ts | 4 +- src/infrastructure/Clipboard.ts | 22 +- src/infrastructure/CodeRunner.ts | 82 +- src/infrastructure/Entity/BaseEntity.ts | 14 +- src/infrastructure/Entity/IEntity.ts | 4 +- src/infrastructure/Events/EventSource.ts | 36 +- .../Events/EventSubscriptionCollection.ts | 18 +- src/infrastructure/Events/IEventSource.ts | 6 +- src/infrastructure/Repository/IRepository.ts | 14 +- .../Repository/InMemoryRepository.ts | 87 +- src/infrastructure/SaveFileDialog.ts | 42 +- src/infrastructure/Threading/AsyncLazy.ts | 55 +- src/infrastructure/Threading/AsyncSleep.ts | 7 +- .../bootstrapping/ApplicationBootstrapper.ts | 29 +- .../bootstrapping/IVueBootstrapper.ts | 2 +- .../bootstrapping/Modules/IconBootstrapper.ts | 52 +- .../Modules/TooltipBootstrapper.ts | 8 +- .../bootstrapping/Modules/TreeBootstrapper.ts | 8 +- .../Modules/VModalBootstrapper.ts | 8 +- .../bootstrapping/Modules/VueBootstrapper.ts | 9 +- .../components/Code/CodeButtons/Code.vue | 62 +- .../Code/CodeButtons/IconButton.vue | 88 +- .../Code/CodeButtons/MacOsInstructions.vue | 172 ++-- .../Code/CodeButtons/TheCodeButtons.vue | 76 +- .../components/Code/TheCodeArea.vue | 48 +- .../components/Code/ace-importer.ts | 6 +- .../Scripts/Menu/MenuOptionList.vue | 18 +- .../Scripts/Menu/MenuOptionListItem.vue | 32 +- .../Menu/Selector/SelectionTypeHandler.ts | 115 ++- .../Scripts/Menu/Selector/TheSelector.vue | 106 +- .../components/Scripts/Menu/TheOsChanger.vue | 24 +- .../Scripts/Menu/TheScriptsMenu.vue | 66 +- .../Scripts/Menu/View/TheViewChanger.vue | 67 +- .../components/Scripts/Menu/View/ViewType.ts | 4 +- .../components/Scripts/Slider/Handle.vue | 103 +- .../Scripts/Slider/HorizontalResizeSlider.vue | 81 +- .../components/Scripts/TheScriptArea.vue | 28 +- .../Scripts/View/Cards/CardList.vue | 44 +- .../Scripts/View/Cards/CardListItem.vue | 106 +- .../View/Cards/NonCollapsingDirective.ts | 16 +- .../View/ScriptsTree/ScriptNodeParser.ts | 14 +- .../Scripts/View/ScriptsTree/ScriptsTree.vue | 82 +- .../SelectableTree/INodeSelectedEvent.ts | 4 +- .../SelectableTree/LiquorTree/LiquorTree.d.ts | 133 ++- .../LiquorTree/LiquorTreeOptions.ts | 55 +- .../NodeWrapper/NodePredicateFilter.ts | 20 +- .../NodeWrapper/NodeStateUpdater.ts | 91 +- .../LiquorTree/NodeWrapper/NodeTranslator.ts | 63 +- .../SelectableTree/Node/DocumentationUrls.vue | 66 +- .../ScriptsTree/SelectableTree/Node/INode.ts | 16 +- .../ScriptsTree/SelectableTree/Node/Node.vue | 54 +- .../SelectableTree/Node/RevertToggle.vue | 228 ++-- .../Node/Reverter/CategoryReverter.ts | 46 +- .../SelectableTree/Node/Reverter/IReverter.ts | 4 +- .../Node/Reverter/ReverterFactory.ts | 18 +- .../Node/Reverter/ScriptReverter.ts | 33 +- .../SelectableTree/SelectableTree.vue | 232 +++-- .../Scripts/View/TheScriptsView.vue | 232 +++-- src/presentation/components/Shared/Dialog.vue | 12 +- .../components/Shared/Responsive.vue | 10 +- .../components/Shared/StatefulVue.ts | 45 +- .../components/Shared/Throttle.ts | 80 +- .../components/TheFooter/DownloadUrlList.vue | 13 +- .../TheFooter/DownloadUrlListItem.vue | 71 +- .../components/TheFooter/PrivacyPolicy.vue | 86 +- .../components/TheFooter/TheFooter.vue | 32 +- src/presentation/components/TheHeader.vue | 12 +- src/presentation/components/TheSearchBar.vue | 26 +- .../electron/Update/AutoUpdater.ts | 102 +- .../electron/Update/ManualUpdater.ts | 205 ++-- .../electron/Update/UpdateProgressBar.ts | 155 +-- src/presentation/electron/Update/Updater.ts | 60 +- src/presentation/electron/main.ts | 19 +- src/presentation/main.ts | 4 +- src/presentation/shims-tsx.d.ts | 20 +- src/presentation/shims-vue.d.ts | 5 +- tests/e2e/plugins/index.js | 9 +- tests/e2e/specs/initialization.spec.js | 14 +- tests/e2e/support/index.js | 2 +- .../Parser/ApplicationParser.spec.ts | 14 +- .../NoDeadDocumentationUrls.spec.ts | 73 +- .../StatusChecker/BatchStatusChecker.ts | 100 +- .../ExponentialBackOffRetryHandler.ts | 60 +- .../collections/StatusChecker/FetchFollow.ts | 126 ++- .../collections/StatusChecker/IUrlStatus.ts | 6 +- .../collections/StatusChecker/Requestor.ts | 77 +- .../StatusChecker/UrlPerDomainGrouper.ts | 28 +- .../application/ApplicationFactory.spec.ts | 96 +- .../Common/Array.ComparerTestScenario.ts | 127 +-- tests/unit/application/Common/Array.spec.ts | 117 ++- tests/unit/application/Common/Enum.spec.ts | 204 ++-- .../application/Common/EnumRangeTestRunner.ts | 100 +- .../ScriptingLanguageFactory.spec.ts | 93 +- .../ScriptingLanguageFactoryTestRunner.ts | 73 +- .../Context/ApplicationContext.spec.ts | 463 +++++---- .../Context/ApplicationContextFactory.spec.ts | 127 +-- .../State/CategoryCollectionState.spec.ts | 170 +-- .../State/Code/ApplicationCode.spec.ts | 343 +++--- .../State/Code/Event/CodeChangedEvent.spec.ts | 312 +++--- .../State/Code/Generation/CodeBuilder.spec.ts | 243 ++--- .../Generation/CodeBuilderFactory.spec.ts | 10 +- .../Generation/Languages/BatchBuilder.spec.ts | 103 +- .../Generation/Languages/ShellBuilder.spec.ts | 93 +- .../Generation/UserScriptGenerator.spec.ts | 429 ++++---- .../State/Code/Position/CodePosition.spec.ts | 94 +- .../Context/State/Filter/FilterResult.spec.ts | 74 +- .../Context/State/Filter/UserFilter.spec.ts | 318 +++--- .../State/Selection/SelectedScript.spec.ts | 42 +- .../State/Selection/UserSelection.spec.ts | 728 ++++++------- .../Selection/UserSelectionTestRunner.ts | 137 +-- .../BrowserOs/BrowserOsDetector.spec.ts | 44 +- .../BrowserOs/BrowserOsTestCases.ts | 660 ++++++------ .../Environment/DesktopOsTestCases.ts | 60 +- .../Environment/Environment.spec.ts | 210 ++-- .../Parser/ApplicationParser.spec.ts | 286 +++--- .../Parser/CategoryCollectionParser.spec.ts | 229 +++-- .../application/Parser/CategoryParser.spec.ts | 314 +++--- .../Parser/DocumentationParser.spec.ts | 62 +- .../Parser/ProjectInformationParser.spec.ts | 82 +- .../CategoryCollectionParseContext.spec.ts | 124 +-- .../Expressions/Expression/Expression.spec.ts | 455 ++++---- .../ExpressionEvaluationContext.spec.ts | 105 +- .../Expression/ExpressionPosition.spec.ts | 54 +- .../Expressions/ExpressionsCompiler.spec.ts | 344 +++---- .../Parser/CompositeExpressionParser.spec.ts | 144 +-- .../Regex/ExpressionRegexBuilder.spec.ts | 260 ++--- .../Parser/Regex/RegexParser.spec.ts | 259 ++--- .../EscapeDoubleQuotes.spec.ts | 52 +- .../PipeDefinitions/InlinePowerShell.spec.ts | 873 ++++++++-------- .../Pipes/PipeDefinitions/PipeTestRunner.ts | 22 +- .../Expressions/Pipes/PipeFactory.spec.ts | 194 ++-- .../Pipes/PipelineCompiler.spec.ts | 244 ++--- .../ParameterSubstitutionParser.spec.ts | 118 +-- .../SyntaxParsers/SyntaxParserTestsRunner.ts | 195 ++-- .../SyntaxParsers/WithParser.spec.ts | 340 +++--- .../Argument/FunctionCallArgument.spec.ts | 74 +- .../FunctionCallArgumentCollection.spec.ts | 262 ++--- .../Compiler/FunctionCallCompiler.spec.ts | 972 +++++++++--------- .../Function/Call/FunctionCall.spec.ts | 123 +-- .../Function/Call/FunctionCallParser.spec.ts | 188 ++-- .../Parameter/FunctionParameter.spec.ts | 74 +- .../FunctionParameterCollection.spec.ts | 79 +- .../Compiler/Function/SharedFunction.spec.ts | 461 +++++---- .../Function/SharedFunctionCollection.spec.ts | 158 +-- .../Function/SharedFunctionsParser.spec.ts | 507 ++++----- .../Compiler/ParameterNameTestRunner.ts | 98 +- .../Script/Compiler/ScriptCompiler.spec.ts | 393 +++---- .../Parser/Script/ScriptParser.spec.ts | 341 +++--- .../Script/Syntax/ConcreteSyntaxes.spec.ts | 42 +- .../Script/Syntax/SyntaxFactory.spec.ts | 10 +- .../CodeSubstituter.spec.ts | 169 +-- .../ScriptingDefinitionParser.spec.ts | 185 ++-- .../collections/NoUnintentedInlining.spec.ts | 101 +- .../application/collections/raw-loader.d.ts | 4 +- tests/unit/domain/Application.spec.ts | 212 ++-- tests/unit/domain/Category.spec.ts | 225 ++-- tests/unit/domain/CategoryCollection.spec.ts | 476 ++++----- tests/unit/domain/ProjectInformation.spec.ts | 234 ++--- tests/unit/domain/Script.spec.ts | 265 ++--- tests/unit/domain/ScriptCode.spec.ts | 351 +++---- tests/unit/domain/ScriptingDefinition.spec.ts | 224 ++-- tests/unit/infrastructure/CodeRunner.spec.ts | 436 ++++---- .../infrastructure/Events/EventSource.spec.ts | 153 +-- .../EventSubscriptionCollection.spec.ts | 30 +- .../infrastructure/InMemoryRepository.spec.ts | 213 ++-- .../Threading/AsyncLazy.spec.ts | 91 +- .../Threading/AsyncSleep.spec.ts | 134 +-- .../Selector/SelectionStateTestScenario.ts | 52 +- .../Selector/SelectionTypeHandler.spec.ts | 241 ++--- .../View/Cards/NonCollapsingDirective.spec.ts | 83 +- .../View/Tree/ScriptNodeParser.spec.ts | 203 ++-- .../NodeWrapper/NodePredicateFilter.spec.ts | 100 +- .../NodeWrapper/NodeStateUpdater.spec.ts | 394 +++---- .../NodeWrapper/NodeTranslator.spec.ts | 226 ++-- .../Node/Reverter/CategoryReverter.spec.ts | 186 ++-- .../Node/Reverter/ReverterFactory.spec.ts | 66 +- .../Node/Reverter/ScriptReverter.spec.ts | 150 +-- .../components/Shared/Throttle.spec.ts | 272 ++--- tests/unit/stubs/ApplicationCodeStub.ts | 5 +- tests/unit/stubs/ApplicationStub.ts | 46 +- .../CategoryCollectionParseContextStub.ts | 22 +- .../unit/stubs/CategoryCollectionStateStub.ts | 42 +- tests/unit/stubs/CategoryCollectionStub.ts | 156 +-- tests/unit/stubs/CategoryDataStub.ts | 34 +- tests/unit/stubs/CategoryStub.ts | 96 +- tests/unit/stubs/CodeSubstituterStub.ts | 25 +- tests/unit/stubs/CollectionDataStub.ts | 95 +- tests/unit/stubs/EnumParserStub.ts | 43 +- tests/unit/stubs/EnvironmentStub.ts | 14 +- .../stubs/ExpressionEvaluationContextStub.ts | 25 +- tests/unit/stubs/ExpressionParserStub.ts | 23 +- tests/unit/stubs/ExpressionStub.ts | 64 +- tests/unit/stubs/ExpressionsCompilerStub.ts | 93 +- .../FunctionCallArgumentCollectionStub.ts | 63 +- tests/unit/stubs/FunctionCallArgumentStub.ts | 23 +- tests/unit/stubs/FunctionCallCompilerStub.ts | 60 +- tests/unit/stubs/FunctionCallDataStub.ts | 22 +- tests/unit/stubs/FunctionCallStub.ts | 40 +- tests/unit/stubs/FunctionCodeStub.ts | 23 +- tests/unit/stubs/FunctionDataStub.ts | 112 +- .../stubs/FunctionParameterCollectionStub.ts | 41 +- tests/unit/stubs/FunctionParameterStub.ts | 23 +- tests/unit/stubs/LanguageSyntaxStub.ts | 22 +- tests/unit/stubs/NumericEntityStub.ts | 18 +- .../unit/stubs/ParameterDefinitionDataStub.ts | 23 +- tests/unit/stubs/PipeFactoryStub.ts | 37 +- tests/unit/stubs/PipeStub.ts | 27 +- tests/unit/stubs/PipelineCompilerStub.ts | 11 +- tests/unit/stubs/ProcessEnvironmentStub.ts | 12 +- tests/unit/stubs/ProjectInformationStub.ts | 93 +- tests/unit/stubs/ScriptCodeStub.ts | 22 +- tests/unit/stubs/ScriptCompilerStub.ts | 31 +- tests/unit/stubs/ScriptDataStub.ts | 128 ++- tests/unit/stubs/ScriptStub.ts | 67 +- .../unit/stubs/ScriptingDefinitionDataStub.ts | 43 +- tests/unit/stubs/ScriptingDefinitionStub.ts | 37 +- tests/unit/stubs/SelectedScriptStub.ts | 6 +- .../stubs/SharedFunctionCollectionStub.ts | 33 +- tests/unit/stubs/SharedFunctionStub.ts | 103 +- tests/unit/stubs/SharedFunctionsParserStub.ts | 37 +- tests/unit/stubs/UserFilterStub.ts | 22 +- tests/unit/stubs/UserSelectionStub.ts | 95 +- vue.config.js | 99 +- 341 files changed, 16126 insertions(+), 15101 deletions(-) delete mode 100644 src/application/Parser/ScriptingDefinition/IScriptingDefinitionParser.ts diff --git a/.eslintrc.js b/.eslintrc.js index ef90a058..dd56b077 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,5 @@ +const { rules: baseStyleRules } = require('eslint-config-airbnb-base/rules/style'); + module.exports = { root: true, env: { @@ -22,91 +24,10 @@ module.exports = { ecmaVersion: 'latest', }, rules: { - 'no-console': 'off', // process.env.NODE_ENV === 'production' ? 'warn' : 'off', - 'no-debugger': 'off', // process.env.NODE_ENV === 'production' ? 'warn' : 'off', - - 'linebreak-style': 'off', - 'no-useless-constructor': 'off', - 'import/prefer-default-export': 'off', - 'class-methods-use-this': 'off', - 'no-use-before-define': 'off', - 'no-restricted-syntax': 'off', - 'global-require': 'off', - 'max-len': 'off', - 'import/no-unresolved': 'off', - 'import/no-webpack-loader-syntax': 'off', - 'import/extensions': 'off', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/no-explicit-any': 'off', - 'no-plusplus': 'off', - 'no-mixed-operators': 'off', - 'import/no-extraneous-dependencies': 'off', - '@typescript-eslint/no-empty-function': 'off', - 'no-return-assign': 'off', - 'no-await-in-loop': 'off', - 'no-shadow': 'off', - 'vuejs-accessibility/accessible-emoji': 'off', - 'no-promise-executor-return': 'off', - 'no-new': 'off', - 'no-useless-escape': 'off', - 'prefer-destructuring': 'off', - 'no-param-reassign': 'off', - 'no-irregular-whitespace': 'off', - 'no-undef': 'off', - 'no-underscore-dangle': 'off', - 'vuejs-accessibility/form-control-has-label': 'off', - 'vuejs-accessibility/click-events-have-key-events': 'off', - '@typescript-eslint/ban-ts-comment': 'off', - 'camelcase': 'off', - 'no-restricted-globals': 'off', - 'default-param-last': 'off', - 'no-continue': 'off', - 'vuejs-accessibility/anchor-has-content': 'off', - '@typescript-eslint/no-extra-semi': 'off', - 'no-multi-spaces': 'off', - 'indent': 'off', - 'comma-dangle': 'off', - 'semi': 'off', - 'quotes': 'off', - 'key-spacing': 'off', - 'lines-between-class-members': 'off', - 'import/order': 'off', - 'space-in-parens': 'off', - 'array-bracket-spacing': 'off', - 'object-curly-spacing': 'off', - '@typescript-eslint/no-inferrable-types': 'off', - 'import/no-duplicates': 'off', - 'function-paren-newline': 'off', - 'operator-linebreak': 'off', - 'no-multiple-empty-lines': 'off', - 'object-curly-newline': 'off', - 'object-property-newline': 'off', - 'arrow-body-style': 'off', - 'no-useless-return': 'off', - 'prefer-template': 'off', - 'func-call-spacing': 'off', - 'no-spaced-func': 'off', - 'padded-blocks': 'off', - 'implicit-arrow-linebreak': 'off', - 'function-call-argument-newline': 'off', - 'comma-spacing': 'off', - 'comma-style': 'off', - 'newline-per-chained-call': 'off', - 'no-useless-computed-key': 'off', - 'no-else-return': 'off', - 'quote-props': 'off', - 'no-restricted-properties': 'off', - 'prefer-exponentiation-operator': 'off', - 'semi-spacing': 'off', - 'prefer-object-spread': 'off', - 'import/newline-after-import': 'off', - 'strict': 'off', - 'no-trailing-spaces': 'off', - 'no-confusing-arrow': 'off', - 'eol-last': 'off', - 'import/no-useless-path-segments': 'off', - 'spaced-comment': 'off', - '@typescript-eslint/no-empty-interface': 'off', + ...getOwnRules(), + ...getTurnedOffBrokenRules(), + ...getOpinionatedRuleOverrides(), + ...getTodoRules(), }, overrides: [ { @@ -118,5 +39,77 @@ module.exports = { mocha: true, }, }, + { + files: ['**/tests/**/*.{j,t}s?(x)'], + rules: { + 'no-console': 'off', + }, + }, ], }; + +function getOwnRules() { + return { + 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + 'linebreak-style': ['error', 'unix'], // Also enforced in .editorconfig + 'import/order': [ // Enforce strict import order taking account into aliases + 'error', + { + groups: [ // Enforce more strict order than AirBnb + 'builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'], + pathGroups: [ // Fix manually configured paths being incorrectly grouped as "external" + '@/**', // @/.. + '@tests/**', // @tests/.. (not matching anything after @** because there can be third parties as well) + 'js-yaml-loader!@/**', // E.g. js-yaml-loader!@/.. + ].map((pattern) => ({ pattern, group: 'internal' })), + }, + ], + }; +} + +function getTodoRules() { // Should be worked on separate future commits + return { + 'import/no-extraneous-dependencies': 'off', + // Requires webpack configuration change with import '..yaml' files. + 'import/no-webpack-loader-syntax': 'off', + 'import/extensions': 'off', + 'import/no-unresolved': 'off', + // Accessibility improvements: + 'vuejs-accessibility/form-control-has-label': 'off', + 'vuejs-accessibility/click-events-have-key-events': 'off', + 'vuejs-accessibility/anchor-has-content': 'off', + 'vuejs-accessibility/accessible-emoji': 'off', + }; +} + +function getTurnedOffBrokenRules() { + return { + // Broken in TypeScript + 'no-useless-constructor': 'off', // Cannot interpret TypeScript constructors + 'no-shadow': 'off', // Fails with TypeScript enums + }; +} + +function getOpinionatedRuleOverrides() { + return { + // https://erkinekici.com/articles/linting-trap#no-use-before-define + 'no-use-before-define': 'off', + // https://erkinekici.com/articles/linting-trap#arrow-body-style + 'arrow-body-style': 'off', + // https://erkinekici.com/articles/linting-trap#no-plusplus + 'no-plusplus': 'off', + // https://erkinekici.com/articles/linting-trap#no-param-reassign + 'no-param-reassign': 'off', + // https://erkinekici.com/articles/linting-trap#class-methods-use-this + 'class-methods-use-this': 'off', + // https://erkinekici.com/articles/linting-trap#importprefer-default-export + 'import/prefer-default-export': 'off', + // https://erkinekici.com/articles/linting-trap#disallowing-for-of + // Original: https://github.com/airbnb/javascript/blob/d8cb404da74c302506f91e5928f30cc75109e74d/packages/eslint-config-airbnb-base/rules/style.js#L333-L351 + 'no-restricted-syntax': [ + baseStyleRules['no-restricted-syntax'][0], + ...baseStyleRules['no-restricted-syntax'].slice(1).filter((rule) => rule.selector !== 'ForOfStatement'), + ], + }; +} diff --git a/babel.config.js b/babel.config.js index e9558405..757ff9b1 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,5 +1,5 @@ module.exports = { presets: [ - '@vue/cli-plugin-babel/preset' - ] -} + '@vue/cli-plugin-babel/preset', + ], +}; diff --git a/package.json b/package.json index 8bd60fac..2cb47bd2 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "lint:md": "markdownlint **/*.md --ignore node_modules", "lint:md:consistency": "remark . --frail --use remark-preset-lint-consistent", "lint:md:relative-urls": "remark . --frail --use remark-validate-links", - "lint:eslint": "vue-cli-service lint --no-fix", + "lint:eslint": "vue-cli-service lint --no-fix --mode production", "lint:yaml": "yamllint **/*.yaml --ignore=node_modules/**/*.yaml", "postinstall": "electron-builder install-app-deps", "postuninstall": "electron-builder install-app-deps", diff --git a/postcss.config.js b/postcss.config.js index 961986e2..a47ef4f9 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,5 +1,5 @@ module.exports = { plugins: { - autoprefixer: {} - } -} + autoprefixer: {}, + }, +}; diff --git a/src/application/ApplicationFactory.ts b/src/application/ApplicationFactory.ts index 99a06d45..edfc0657 100644 --- a/src/application/ApplicationFactory.ts +++ b/src/application/ApplicationFactory.ts @@ -7,15 +7,18 @@ export type ApplicationGetter = () => IApplication; const ApplicationGetter: ApplicationGetter = parseApplication; export class ApplicationFactory implements IApplicationFactory { - public static readonly Current: IApplicationFactory = new ApplicationFactory(ApplicationGetter); - private readonly getter: AsyncLazy; - protected constructor(costlyGetter: ApplicationGetter) { - if (!costlyGetter) { - throw new Error('undefined getter'); - } - this.getter = new AsyncLazy(() => Promise.resolve(costlyGetter())); - } - public getApp(): Promise { - return this.getter.getValue(); + public static readonly Current: IApplicationFactory = new ApplicationFactory(ApplicationGetter); + + private readonly getter: AsyncLazy; + + protected constructor(costlyGetter: ApplicationGetter) { + if (!costlyGetter) { + throw new Error('undefined getter'); } + this.getter = new AsyncLazy(() => Promise.resolve(costlyGetter())); + } + + public getApp(): Promise { + return this.getter.getValue(); + } } diff --git a/src/application/Common/Array.ts b/src/application/Common/Array.ts index 079fa41a..c3444578 100644 --- a/src/application/Common/Array.ts +++ b/src/application/Common/Array.ts @@ -1,21 +1,21 @@ // Compares to Array objects for equality, ignoring order export function scrambledEqual(array1: readonly T[], array2: readonly T[]) { - if (!array1) { throw new Error('undefined first array'); } - if (!array2) { throw new Error('undefined second array'); } - const sortedArray1 = sort(array1); - const sortedArray2 = sort(array2); - return sequenceEqual(sortedArray1, sortedArray2); - function sort(array: readonly T[]) { - return array.slice().sort(); - } + if (!array1) { throw new Error('undefined first array'); } + if (!array2) { throw new Error('undefined second array'); } + const sortedArray1 = sort(array1); + const sortedArray2 = sort(array2); + return sequenceEqual(sortedArray1, sortedArray2); + function sort(array: readonly T[]) { + return array.slice().sort(); + } } // Compares to Array objects for equality in same order export function sequenceEqual(array1: readonly T[], array2: readonly T[]) { - if (!array1) { throw new Error('undefined first array'); } - if (!array2) { throw new Error('undefined second array'); } - if (array1.length !== array2.length) { - return false; - } - return array1.every((val, index) => val === array2[index]); + if (!array1) { throw new Error('undefined first array'); } + if (!array2) { throw new Error('undefined second array'); } + if (array1.length !== array2.length) { + return false; + } + return array1.every((val, index) => val === array2[index]); } diff --git a/src/application/Common/Enum.ts b/src/application/Common/Enum.ts index 2042a7ac..ac2bba57 100644 --- a/src/application/Common/Enum.ts +++ b/src/application/Common/Enum.ts @@ -1,54 +1,63 @@ // Because we cannot do "T extends enum" 😞 https://github.com/microsoft/TypeScript/issues/30611 export type EnumType = number | string; -export type EnumVariable = { [key in T]: TEnumValue }; +export type EnumVariable + = { [key in T]: TEnumValue }; export interface IEnumParser { - parseEnum(value: string, propertyName: string): TEnum; -} -export function createEnumParser( - enumVariable: EnumVariable): IEnumParser { - return { - parseEnum: (value, propertyName) => parseEnumValue(value, propertyName, enumVariable), - }; -} -function parseEnumValue( - value: string, - enumName: string, - enumVariable: EnumVariable): TEnumValue { - if (!value) { - throw new Error(`undefined ${enumName}`); - } - if (typeof value !== 'string') { - throw new Error(`unexpected type of ${enumName}: "${typeof value}"`); - } - const casedValue = getEnumNames(enumVariable) - .find((enumValue) => enumValue.toLowerCase() === value.toLowerCase()); - if (!casedValue) { - throw new Error(`unknown ${enumName}: "${value}"`); - } - return enumVariable[casedValue as keyof typeof enumVariable]; + parseEnum(value: string, propertyName: string): TEnum; } -export function getEnumNames( - enumVariable: EnumVariable): string[] { - return Object - .values(enumVariable) - .filter((enumMember) => typeof enumMember === 'string') as string[]; +export function createEnumParser( + enumVariable: EnumVariable, +): IEnumParser { + return { + parseEnum: (value, propertyName) => parseEnumValue(value, propertyName, enumVariable), + }; +} + +function parseEnumValue( + value: string, + enumName: string, + enumVariable: EnumVariable, +): TEnumValue { + if (!value) { + throw new Error(`undefined ${enumName}`); + } + if (typeof value !== 'string') { + throw new Error(`unexpected type of ${enumName}: "${typeof value}"`); + } + const casedValue = getEnumNames(enumVariable) + .find((enumValue) => enumValue.toLowerCase() === value.toLowerCase()); + if (!casedValue) { + throw new Error(`unknown ${enumName}: "${value}"`); + } + return enumVariable[casedValue as keyof typeof enumVariable]; +} + +export function getEnumNames +( + enumVariable: EnumVariable, +): string[] { + return Object + .values(enumVariable) + .filter((enumMember) => typeof enumMember === 'string') as string[]; } export function getEnumValues( - enumVariable: EnumVariable): TEnumValue[] { - return getEnumNames(enumVariable) - .map((level) => enumVariable[level]) as TEnumValue[]; + enumVariable: EnumVariable, +): TEnumValue[] { + return getEnumNames(enumVariable) + .map((level) => enumVariable[level]) as TEnumValue[]; } export function assertInRange( - value: TEnumValue, - enumVariable: EnumVariable) { - if (value === undefined) { - throw new Error('undefined enum value'); - } - if (!(value in enumVariable)) { - throw new RangeError(`enum value "${value}" is out of range`); - } + value: TEnumValue, + enumVariable: EnumVariable, +) { + if (value === undefined) { + throw new Error('undefined enum value'); + } + if (!(value in enumVariable)) { + throw new RangeError(`enum value "${value}" is out of range`); + } } diff --git a/src/application/Common/ScriptingLanguage/IScriptingLanguageFactory.ts b/src/application/Common/ScriptingLanguage/IScriptingLanguageFactory.ts index b82df700..fa18479e 100644 --- a/src/application/Common/ScriptingLanguage/IScriptingLanguageFactory.ts +++ b/src/application/Common/ScriptingLanguage/IScriptingLanguageFactory.ts @@ -1,5 +1,5 @@ import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; export interface IScriptingLanguageFactory { - create(language: ScriptingLanguage): T; + create(language: ScriptingLanguage): T; } diff --git a/src/application/Common/ScriptingLanguage/ScriptingLanguageFactory.ts b/src/application/Common/ScriptingLanguage/ScriptingLanguageFactory.ts index 2b88c767..fb71e0d6 100644 --- a/src/application/Common/ScriptingLanguage/ScriptingLanguageFactory.ts +++ b/src/application/Common/ScriptingLanguage/ScriptingLanguageFactory.ts @@ -1,31 +1,30 @@ import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; -import { IScriptingLanguageFactory } from './IScriptingLanguageFactory'; import { assertInRange } from '@/application/Common/Enum'; +import { IScriptingLanguageFactory } from './IScriptingLanguageFactory'; type Getter = () => T; export abstract class ScriptingLanguageFactory implements IScriptingLanguageFactory { - private readonly getters = new Map>(); + private readonly getters = new Map>(); - public create(language: ScriptingLanguage): T { - assertInRange(language, ScriptingLanguage); - if (!this.getters.has(language)) { - throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`); - } - const getter = this.getters.get(language); - const instance = getter(); - return instance; + public create(language: ScriptingLanguage): T { + assertInRange(language, ScriptingLanguage); + if (!this.getters.has(language)) { + throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`); } + const getter = this.getters.get(language); + const instance = getter(); + return instance; + } - protected registerGetter(language: ScriptingLanguage, getter: Getter) { - assertInRange(language, ScriptingLanguage); - if (!getter) { - throw new Error('undefined getter'); - } - if (this.getters.has(language)) { - throw new Error(`${ScriptingLanguage[language]} is already registered`); - } - this.getters.set(language, getter); + protected registerGetter(language: ScriptingLanguage, getter: Getter) { + assertInRange(language, ScriptingLanguage); + if (!getter) { + throw new Error('undefined getter'); } - + if (this.getters.has(language)) { + throw new Error(`${ScriptingLanguage[language]} is already registered`); + } + this.getters.set(language, getter); + } } diff --git a/src/application/Context/ApplicationContext.ts b/src/application/Context/ApplicationContext.ts index f4c401de..68557136 100644 --- a/src/application/Context/ApplicationContext.ts +++ b/src/application/Context/ApplicationContext.ts @@ -1,60 +1,64 @@ -import { IApplicationContext, IApplicationContextChangedEvent } from './IApplicationContext'; -import { ICategoryCollectionState } from './State/ICategoryCollectionState'; -import { CategoryCollectionState } from './State/CategoryCollectionState'; import { IApplication } from '@/domain/IApplication'; import { OperatingSystem } from '@/domain/OperatingSystem'; import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { EventSource } from '@/infrastructure/Events/EventSource'; import { assertInRange } from '@/application/Common/Enum'; +import { CategoryCollectionState } from './State/CategoryCollectionState'; +import { ICategoryCollectionState } from './State/ICategoryCollectionState'; +import { IApplicationContext, IApplicationContextChangedEvent } from './IApplicationContext'; type StateMachine = Map; export class ApplicationContext implements IApplicationContext { - public readonly contextChanged = new EventSource(); - public collection: ICategoryCollection; - public currentOs: OperatingSystem; + public readonly contextChanged = new EventSource(); - public get state(): ICategoryCollectionState { - return this.states[this.collection.os]; - } + public collection: ICategoryCollection; - private readonly states: StateMachine; - public constructor( - public readonly app: IApplication, - initialContext: OperatingSystem) { - validateApp(app); - assertInRange(initialContext, OperatingSystem); - this.states = initializeStates(app); - this.changeContext(initialContext); - } + public currentOs: OperatingSystem; - public changeContext(os: OperatingSystem): void { - if (this.currentOs === os) { - return; - } - this.collection = this.app.getCollection(os); - if (!this.collection) { - throw new Error(`os "${OperatingSystem[os]}" is not defined in application`); - } - const event: IApplicationContextChangedEvent = { - newState: this.states[os], - oldState: this.states[this.currentOs], - }; - this.contextChanged.notify(event); - this.currentOs = os; + public get state(): ICategoryCollectionState { + return this.states[this.collection.os]; + } + + private readonly states: StateMachine; + + public constructor( + public readonly app: IApplication, + initialContext: OperatingSystem, + ) { + validateApp(app); + assertInRange(initialContext, OperatingSystem); + this.states = initializeStates(app); + this.changeContext(initialContext); + } + + public changeContext(os: OperatingSystem): void { + if (this.currentOs === os) { + return; } + this.collection = this.app.getCollection(os); + if (!this.collection) { + throw new Error(`os "${OperatingSystem[os]}" is not defined in application`); + } + const event: IApplicationContextChangedEvent = { + newState: this.states[os], + oldState: this.states[this.currentOs], + }; + this.contextChanged.notify(event); + this.currentOs = os; + } } function validateApp(app: IApplication) { - if (!app) { - throw new Error('undefined app'); - } + if (!app) { + throw new Error('undefined app'); + } } function initializeStates(app: IApplication): StateMachine { - const machine = new Map(); - for (const collection of app.collections) { - machine[collection.os] = new CategoryCollectionState(collection); - } - return machine; + const machine = new Map(); + for (const collection of app.collections) { + machine[collection.os] = new CategoryCollectionState(collection); + } + return machine; } diff --git a/src/application/Context/ApplicationContextFactory.ts b/src/application/Context/ApplicationContextFactory.ts index a120867f..2fead72e 100644 --- a/src/application/Context/ApplicationContextFactory.ts +++ b/src/application/Context/ApplicationContextFactory.ts @@ -1,31 +1,32 @@ -import { ApplicationContext } from './ApplicationContext'; import { IApplicationContext } from '@/application/Context/IApplicationContext'; import { OperatingSystem } from '@/domain/OperatingSystem'; -import { Environment } from '../Environment/Environment'; import { IApplication } from '@/domain/IApplication'; +import { Environment } from '../Environment/Environment'; import { IEnvironment } from '../Environment/IEnvironment'; import { IApplicationFactory } from '../IApplicationFactory'; import { ApplicationFactory } from '../ApplicationFactory'; +import { ApplicationContext } from './ApplicationContext'; export async function buildContext( - factory: IApplicationFactory = ApplicationFactory.Current, - environment = Environment.CurrentEnvironment): Promise { - if (!factory) { throw new Error('undefined factory'); } - if (!environment) { throw new Error('undefined environment'); } - const app = await factory.getApp(); - const os = getInitialOs(app, environment); - return new ApplicationContext(app, os); + factory: IApplicationFactory = ApplicationFactory.Current, + environment = Environment.CurrentEnvironment, +): Promise { + if (!factory) { throw new Error('undefined factory'); } + if (!environment) { throw new Error('undefined environment'); } + const app = await factory.getApp(); + const os = getInitialOs(app, environment); + return new ApplicationContext(app, os); } function getInitialOs(app: IApplication, environment: IEnvironment): OperatingSystem { - const currentOs = environment.os; - const supportedOsList = app.getSupportedOsList(); - if (supportedOsList.includes(currentOs)) { - return currentOs; - } - supportedOsList.sort((os1, os2) => { - const getPriority = (os: OperatingSystem) => app.getCollection(os).totalScripts; - return getPriority(os2) - getPriority(os1); - }); - return supportedOsList[0]; + const currentOs = environment.os; + const supportedOsList = app.getSupportedOsList(); + if (supportedOsList.includes(currentOs)) { + return currentOs; + } + supportedOsList.sort((os1, os2) => { + const getPriority = (os: OperatingSystem) => app.getCollection(os).totalScripts; + return getPriority(os2) - getPriority(os1); + }); + return supportedOsList[0]; } diff --git a/src/application/Context/IApplicationContext.ts b/src/application/Context/IApplicationContext.ts index 9a8090a3..59bf5892 100644 --- a/src/application/Context/IApplicationContext.ts +++ b/src/application/Context/IApplicationContext.ts @@ -1,12 +1,12 @@ -import { ICategoryCollectionState, IReadOnlyCategoryCollectionState } from './State/ICategoryCollectionState'; import { OperatingSystem } from '@/domain/OperatingSystem'; import { IEventSource } from '@/infrastructure/Events/IEventSource'; import { IApplication } from '@/domain/IApplication'; +import { ICategoryCollectionState, IReadOnlyCategoryCollectionState } from './State/ICategoryCollectionState'; export interface IReadOnlyApplicationContext { - readonly app: IApplication; - readonly state: IReadOnlyCategoryCollectionState; - readonly contextChanged: IEventSource; + readonly app: IApplication; + readonly state: IReadOnlyCategoryCollectionState; + readonly contextChanged: IEventSource; } export interface IApplicationContext extends IReadOnlyApplicationContext { @@ -15,6 +15,6 @@ export interface IApplicationContext extends IReadOnlyApplicationContext { } export interface IApplicationContextChangedEvent { - readonly newState: ICategoryCollectionState; - readonly oldState: ICategoryCollectionState; + readonly newState: ICategoryCollectionState; + readonly oldState: ICategoryCollectionState; } diff --git a/src/application/Context/State/CategoryCollectionState.ts b/src/application/Context/State/CategoryCollectionState.ts index 7638a384..da692d11 100644 --- a/src/application/Context/State/CategoryCollectionState.ts +++ b/src/application/Context/State/CategoryCollectionState.ts @@ -1,3 +1,5 @@ +import { ICategoryCollection } from '@/domain/ICategoryCollection'; +import { OperatingSystem } from '@/domain/OperatingSystem'; import { UserFilter } from './Filter/UserFilter'; import { IUserFilter } from './Filter/IUserFilter'; import { ApplicationCode } from './Code/ApplicationCode'; @@ -5,19 +7,20 @@ import { UserSelection } from './Selection/UserSelection'; import { IUserSelection } from './Selection/IUserSelection'; import { ICategoryCollectionState } from './ICategoryCollectionState'; import { IApplicationCode } from './Code/IApplicationCode'; -import { ICategoryCollection } from '@/domain/ICategoryCollection'; -import { OperatingSystem } from '@/domain/OperatingSystem'; export class CategoryCollectionState implements ICategoryCollectionState { - public readonly os: OperatingSystem; - public readonly code: IApplicationCode; - public readonly selection: IUserSelection; - public readonly filter: IUserFilter; + public readonly os: OperatingSystem; - public constructor(readonly collection: ICategoryCollection) { - this.selection = new UserSelection(collection, []); - this.code = new ApplicationCode(this.selection, collection.scripting); - this.filter = new UserFilter(collection); - this.os = collection.os; - } + public readonly code: IApplicationCode; + + public readonly selection: IUserSelection; + + public readonly filter: IUserFilter; + + public constructor(readonly collection: ICategoryCollection) { + this.selection = new UserSelection(collection, []); + this.code = new ApplicationCode(this.selection, collection.scripting); + this.filter = new UserFilter(collection); + this.os = collection.os; + } } diff --git a/src/application/Context/State/Code/ApplicationCode.ts b/src/application/Context/State/Code/ApplicationCode.ts index f0a4db23..3b517ef1 100644 --- a/src/application/Context/State/Code/ApplicationCode.ts +++ b/src/application/Context/State/Code/ApplicationCode.ts @@ -1,39 +1,41 @@ +import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; +import { IReadOnlyUserSelection } from '@/application/Context/State/Selection/IUserSelection'; +import { EventSource } from '@/infrastructure/Events/EventSource'; +import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; import { CodeChangedEvent } from './Event/CodeChangedEvent'; import { CodePosition } from './Position/CodePosition'; import { ICodeChangedEvent } from './Event/ICodeChangedEvent'; -import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; -import { IReadOnlyUserSelection } from '@/application/Context/State/Selection/IUserSelection'; import { UserScriptGenerator } from './Generation/UserScriptGenerator'; -import { EventSource } from '@/infrastructure/Events/EventSource'; import { IApplicationCode } from './IApplicationCode'; import { IUserScriptGenerator } from './Generation/IUserScriptGenerator'; -import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; export class ApplicationCode implements IApplicationCode { - public readonly changed = new EventSource(); - public current: string; + public readonly changed = new EventSource(); - private scriptPositions = new Map(); + public current: string; - constructor( - userSelection: IReadOnlyUserSelection, - private readonly scriptingDefinition: IScriptingDefinition, - private readonly generator: IUserScriptGenerator = new UserScriptGenerator()) { - if (!userSelection) { throw new Error('userSelection is null or undefined'); } - if (!scriptingDefinition) { throw new Error('scriptingDefinition is null or undefined'); } - if (!generator) { throw new Error('generator is null or undefined'); } - this.setCode(userSelection.selectedScripts); - userSelection.changed.on((scripts) => { - this.setCode(scripts); - }); - } + private scriptPositions = new Map(); - private setCode(scripts: ReadonlyArray): void { - const oldScripts = Array.from(this.scriptPositions.keys()); - const code = this.generator.buildCode(scripts, this.scriptingDefinition); - this.current = code.code; - this.scriptPositions = code.scriptPositions; - const event = new CodeChangedEvent(code.code, oldScripts, code.scriptPositions); - this.changed.notify(event); - } + constructor( + userSelection: IReadOnlyUserSelection, + private readonly scriptingDefinition: IScriptingDefinition, + private readonly generator: IUserScriptGenerator = new UserScriptGenerator(), + ) { + if (!userSelection) { throw new Error('userSelection is null or undefined'); } + if (!scriptingDefinition) { throw new Error('scriptingDefinition is null or undefined'); } + if (!generator) { throw new Error('generator is null or undefined'); } + this.setCode(userSelection.selectedScripts); + userSelection.changed.on((scripts) => { + this.setCode(scripts); + }); + } + + private setCode(scripts: ReadonlyArray): void { + const oldScripts = Array.from(this.scriptPositions.keys()); + const code = this.generator.buildCode(scripts, this.scriptingDefinition); + this.current = code.code; + this.scriptPositions = code.scriptPositions; + const event = new CodeChangedEvent(code.code, oldScripts, code.scriptPositions); + this.changed.notify(event); + } } diff --git a/src/application/Context/State/Code/Event/CodeChangedEvent.ts b/src/application/Context/State/Code/Event/CodeChangedEvent.ts index cffd6c14..d91238fc 100644 --- a/src/application/Context/State/Code/Event/CodeChangedEvent.ts +++ b/src/application/Context/State/Code/Event/CodeChangedEvent.ts @@ -1,64 +1,72 @@ -import { ICodeChangedEvent } from './ICodeChangedEvent'; -import { SelectedScript } from '../../Selection/SelectedScript'; import { IScript } from '@/domain/IScript'; import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition'; +import { SelectedScript } from '../../Selection/SelectedScript'; +import { ICodeChangedEvent } from './ICodeChangedEvent'; export class CodeChangedEvent implements ICodeChangedEvent { - public readonly code: string; - public readonly addedScripts: ReadonlyArray; - public readonly removedScripts: ReadonlyArray; - public readonly changedScripts: ReadonlyArray; + public readonly code: string; - private readonly scripts: Map; + public readonly addedScripts: ReadonlyArray; - constructor( - code: string, - oldScripts: ReadonlyArray, - scripts: Map) { - ensureAllPositionsExist(code, Array.from(scripts.values())); - this.code = code; - const newScripts = Array.from(scripts.keys()); - this.addedScripts = selectIfNotExists(newScripts, oldScripts); - this.removedScripts = selectIfNotExists(oldScripts, newScripts); - this.changedScripts = getChangedScripts(oldScripts, newScripts); - this.scripts = new Map(); - scripts.forEach((position, selection) => { - this.scripts.set(selection.script, position); - }); - } + public readonly removedScripts: ReadonlyArray; - public isEmpty(): boolean { - return this.scripts.size === 0; - } + public readonly changedScripts: ReadonlyArray; - public getScriptPositionInCode(script: IScript): ICodePosition { - return this.scripts.get(script); - } + private readonly scripts: Map; + + constructor( + code: string, + oldScripts: ReadonlyArray, + scripts: Map, + ) { + ensureAllPositionsExist(code, Array.from(scripts.values())); + this.code = code; + const newScripts = Array.from(scripts.keys()); + this.addedScripts = selectIfNotExists(newScripts, oldScripts); + this.removedScripts = selectIfNotExists(oldScripts, newScripts); + this.changedScripts = getChangedScripts(oldScripts, newScripts); + this.scripts = new Map(); + scripts.forEach((position, selection) => { + this.scripts.set(selection.script, position); + }); + } + + public isEmpty(): boolean { + return this.scripts.size === 0; + } + + public getScriptPositionInCode(script: IScript): ICodePosition { + return this.scripts.get(script); + } } function ensureAllPositionsExist(script: string, positions: ReadonlyArray) { - const totalLines = script.split(/\r\n|\r|\n/).length; - for (const position of positions) { - if (position.endLine > totalLines) { - throw new Error(`script end line (${position.endLine}) is out of range.` + - `(total code lines: ${totalLines}`); - } + const totalLines = script.split(/\r\n|\r|\n/).length; + for (const position of positions) { + if (position.endLine > totalLines) { + throw new Error( + `script end line (${position.endLine}) is out of range.` + + `(total code lines: ${totalLines}`, + ); } + } } function getChangedScripts( - oldScripts: ReadonlyArray, - newScripts: ReadonlyArray): ReadonlyArray { - return newScripts - .filter((newScript) => oldScripts.find((oldScript) => oldScript.id === newScript.id - && oldScript.revert !== newScript.revert )) - .map((selection) => selection.script); + oldScripts: ReadonlyArray, + newScripts: ReadonlyArray, +): ReadonlyArray { + return newScripts + .filter((newScript) => oldScripts.find((oldScript) => oldScript.id === newScript.id + && oldScript.revert !== newScript.revert)) + .map((selection) => selection.script); } function selectIfNotExists( - selectableContainer: ReadonlyArray, - test: ReadonlyArray) { - return selectableContainer - .filter((script) => !test.find((oldScript) => oldScript.id === script.id)) - .map((selection) => selection.script); + selectableContainer: ReadonlyArray, + test: ReadonlyArray, +) { + return selectableContainer + .filter((script) => !test.find((oldScript) => oldScript.id === script.id)) + .map((selection) => selection.script); } diff --git a/src/application/Context/State/Code/Event/ICodeChangedEvent.ts b/src/application/Context/State/Code/Event/ICodeChangedEvent.ts index 87780f1a..570ebad8 100644 --- a/src/application/Context/State/Code/Event/ICodeChangedEvent.ts +++ b/src/application/Context/State/Code/Event/ICodeChangedEvent.ts @@ -2,10 +2,10 @@ import { IScript } from '@/domain/IScript'; import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition'; export interface ICodeChangedEvent { - readonly code: string; - addedScripts: ReadonlyArray; - removedScripts: ReadonlyArray; - changedScripts: ReadonlyArray; - isEmpty(): boolean; - getScriptPositionInCode(script: IScript): ICodePosition; + readonly code: string; + addedScripts: ReadonlyArray; + removedScripts: ReadonlyArray; + changedScripts: ReadonlyArray; + isEmpty(): boolean; + getScriptPositionInCode(script: IScript): ICodePosition; } diff --git a/src/application/Context/State/Code/Generation/CodeBuilder.ts b/src/application/Context/State/Code/Generation/CodeBuilder.ts index 601352e1..bf352d3f 100644 --- a/src/application/Context/State/Code/Generation/CodeBuilder.ts +++ b/src/application/Context/State/Code/Generation/CodeBuilder.ts @@ -4,64 +4,67 @@ const NewLine = '\n'; const TotalFunctionSeparatorChars = 58; export abstract class CodeBuilder implements ICodeBuilder { - private readonly lines = new Array(); + private readonly lines = new Array(); - // Returns current line starting from 0 (no lines), or 1 (have single line) - public get currentLine(): number { - return this.lines.length; + // Returns current line starting from 0 (no lines), or 1 (have single line) + public get currentLine(): number { + return this.lines.length; + } + + public appendLine(code?: string): CodeBuilder { + if (!code) { + this.lines.push(''); + return this; } - - public appendLine(code?: string): CodeBuilder { - if (!code) { - this.lines.push(''); - return this; - } - const lines = code.match(/[^\r\n]+/g); - for (const line of lines) { - this.lines.push(line); - } - return this; + const lines = code.match(/[^\r\n]+/g); + for (const line of lines) { + this.lines.push(line); } + return this; + } - public appendTrailingHyphensCommentLine( - totalRepeatHyphens: number = TotalFunctionSeparatorChars): CodeBuilder { - return this.appendCommentLine('-'.repeat(totalRepeatHyphens)); + public appendTrailingHyphensCommentLine( + totalRepeatHyphens: number = TotalFunctionSeparatorChars, + ): CodeBuilder { + return this.appendCommentLine('-'.repeat(totalRepeatHyphens)); + } + + public appendCommentLine(commentLine?: string): CodeBuilder { + this.lines.push(`${this.getCommentDelimiter()} ${commentLine}`); + return this; + } + + public appendFunction(name: string, code: string): CodeBuilder { + if (!name) { throw new Error('name cannot be empty or null'); } + if (!code) { throw new Error('code cannot be empty or null'); } + return this + .appendCommentLineWithHyphensAround(name) + .appendLine(this.writeStandardOut(`--- ${name}`)) + .appendLine(code) + .appendTrailingHyphensCommentLine(); + } + + public appendCommentLineWithHyphensAround( + sectionName: string, + totalRepeatHyphens: number = TotalFunctionSeparatorChars, + ): CodeBuilder { + if (!sectionName) { throw new Error('sectionName cannot be empty or null'); } + if (sectionName.length >= totalRepeatHyphens) { + return this.appendCommentLine(sectionName); } + const firstHyphens = '-'.repeat(Math.floor((totalRepeatHyphens - sectionName.length) / 2)); + const secondHyphens = '-'.repeat(Math.ceil((totalRepeatHyphens - sectionName.length) / 2)); + return this + .appendTrailingHyphensCommentLine() + .appendCommentLine(firstHyphens + sectionName + secondHyphens) + .appendTrailingHyphensCommentLine(TotalFunctionSeparatorChars); + } - public appendCommentLine(commentLine?: string): CodeBuilder { - this.lines.push(`${this.getCommentDelimiter()} ${commentLine}`); - return this; - } + public toString(): string { + return this.lines.join(NewLine); + } - public appendFunction(name: string, code: string): CodeBuilder { - if (!name) { throw new Error('name cannot be empty or null'); } - if (!code) { throw new Error('code cannot be empty or null'); } - return this - .appendCommentLineWithHyphensAround(name) - .appendLine(this.writeStandardOut(`--- ${name}`)) - .appendLine(code) - .appendTrailingHyphensCommentLine(); - } + protected abstract getCommentDelimiter(): string; - public appendCommentLineWithHyphensAround( - sectionName: string, - totalRepeatHyphens: number = TotalFunctionSeparatorChars): CodeBuilder { - if (!sectionName) { throw new Error('sectionName cannot be empty or null'); } - if (sectionName.length >= totalRepeatHyphens) { - return this.appendCommentLine(sectionName); - } - const firstHyphens = '-'.repeat(Math.floor((totalRepeatHyphens - sectionName.length) / 2)); - const secondHyphens = '-'.repeat(Math.ceil((totalRepeatHyphens - sectionName.length) / 2)); - return this - .appendTrailingHyphensCommentLine() - .appendCommentLine(firstHyphens + sectionName + secondHyphens) - .appendTrailingHyphensCommentLine(TotalFunctionSeparatorChars); - } - - public toString(): string { - return this.lines.join(NewLine); - } - - protected abstract getCommentDelimiter(): string; - protected abstract writeStandardOut(text: string): string; + protected abstract writeStandardOut(text: string): string; } diff --git a/src/application/Context/State/Code/Generation/CodeBuilderFactory.ts b/src/application/Context/State/Code/Generation/CodeBuilderFactory.ts index c75b4714..4cb50fdf 100644 --- a/src/application/Context/State/Code/Generation/CodeBuilderFactory.ts +++ b/src/application/Context/State/Code/Generation/CodeBuilderFactory.ts @@ -5,10 +5,12 @@ import { BatchBuilder } from './Languages/BatchBuilder'; import { ShellBuilder } from './Languages/ShellBuilder'; import { ICodeBuilderFactory } from './ICodeBuilderFactory'; -export class CodeBuilderFactory extends ScriptingLanguageFactory implements ICodeBuilderFactory { - constructor() { - super(); - this.registerGetter(ScriptingLanguage.shellscript, () => new ShellBuilder()); - this.registerGetter(ScriptingLanguage.batchfile, () => new BatchBuilder()); - } +export class CodeBuilderFactory + extends ScriptingLanguageFactory + implements ICodeBuilderFactory { + constructor() { + super(); + this.registerGetter(ScriptingLanguage.shellscript, () => new ShellBuilder()); + this.registerGetter(ScriptingLanguage.batchfile, () => new BatchBuilder()); + } } diff --git a/src/application/Context/State/Code/Generation/ICodeBuilder.ts b/src/application/Context/State/Code/Generation/ICodeBuilder.ts index 635838d3..d1a5d140 100644 --- a/src/application/Context/State/Code/Generation/ICodeBuilder.ts +++ b/src/application/Context/State/Code/Generation/ICodeBuilder.ts @@ -1,9 +1,9 @@ export interface ICodeBuilder { - currentLine: number; - appendLine(code?: string): ICodeBuilder; - appendTrailingHyphensCommentLine(totalRepeatHyphens: number): ICodeBuilder; - appendCommentLine(commentLine?: string): ICodeBuilder; - appendCommentLineWithHyphensAround(sectionName: string, totalRepeatHyphens: number): ICodeBuilder; - appendFunction(name: string, code: string): ICodeBuilder; - toString(): string; + currentLine: number; + appendLine(code?: string): ICodeBuilder; + appendTrailingHyphensCommentLine(totalRepeatHyphens: number): ICodeBuilder; + appendCommentLine(commentLine?: string): ICodeBuilder; + appendCommentLineWithHyphensAround(sectionName: string, totalRepeatHyphens: number): ICodeBuilder; + appendFunction(name: string, code: string): ICodeBuilder; + toString(): string; } diff --git a/src/application/Context/State/Code/Generation/ICodeBuilderFactory.ts b/src/application/Context/State/Code/Generation/ICodeBuilderFactory.ts index 15a7441b..de9f1696 100644 --- a/src/application/Context/State/Code/Generation/ICodeBuilderFactory.ts +++ b/src/application/Context/State/Code/Generation/ICodeBuilderFactory.ts @@ -1,5 +1,4 @@ -import { ICodeBuilder } from './ICodeBuilder'; import { IScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/IScriptingLanguageFactory'; +import { ICodeBuilder } from './ICodeBuilder'; -export interface ICodeBuilderFactory extends IScriptingLanguageFactory { -} +export type ICodeBuilderFactory = IScriptingLanguageFactory; diff --git a/src/application/Context/State/Code/Generation/IUserScript.ts b/src/application/Context/State/Code/Generation/IUserScript.ts index 7bbeb05a..596819fd 100644 --- a/src/application/Context/State/Code/Generation/IUserScript.ts +++ b/src/application/Context/State/Code/Generation/IUserScript.ts @@ -2,6 +2,6 @@ import { SelectedScript } from '@/application/Context/State/Selection/SelectedSc import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition'; export interface IUserScript { - code: string; - scriptPositions: Map; + code: string; + scriptPositions: Map; } diff --git a/src/application/Context/State/Code/Generation/IUserScriptGenerator.ts b/src/application/Context/State/Code/Generation/IUserScriptGenerator.ts index 10aee8b5..23e47c90 100644 --- a/src/application/Context/State/Code/Generation/IUserScriptGenerator.ts +++ b/src/application/Context/State/Code/Generation/IUserScriptGenerator.ts @@ -1,9 +1,9 @@ import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; -import { IUserScript } from './IUserScript'; import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; +import { IUserScript } from './IUserScript'; export interface IUserScriptGenerator { - buildCode( - selectedScripts: ReadonlyArray, - scriptingDefinition: IScriptingDefinition): IUserScript; + buildCode( + selectedScripts: ReadonlyArray, + scriptingDefinition: IScriptingDefinition): IUserScript; } diff --git a/src/application/Context/State/Code/Generation/Languages/BatchBuilder.ts b/src/application/Context/State/Code/Generation/Languages/BatchBuilder.ts index bf0def5c..133d77ed 100644 --- a/src/application/Context/State/Code/Generation/Languages/BatchBuilder.ts +++ b/src/application/Context/State/Code/Generation/Languages/BatchBuilder.ts @@ -1,16 +1,17 @@ import { CodeBuilder } from '@/application/Context/State/Code/Generation/CodeBuilder'; export class BatchBuilder extends CodeBuilder { - protected getCommentDelimiter(): string { - return '::'; - } - protected writeStandardOut(text: string): string { - return `echo ${escapeForEcho(text)}`; - } + protected getCommentDelimiter(): string { + return '::'; + } + + protected writeStandardOut(text: string): string { + return `echo ${escapeForEcho(text)}`; + } } function escapeForEcho(text: string) { - return text - .replace(/&/g, '^&') - .replace(/%/g, '%%'); + return text + .replace(/&/g, '^&') + .replace(/%/g, '%%'); } diff --git a/src/application/Context/State/Code/Generation/Languages/ShellBuilder.ts b/src/application/Context/State/Code/Generation/Languages/ShellBuilder.ts index 5c45fac0..1f905823 100644 --- a/src/application/Context/State/Code/Generation/Languages/ShellBuilder.ts +++ b/src/application/Context/State/Code/Generation/Languages/ShellBuilder.ts @@ -1,15 +1,16 @@ import { CodeBuilder } from '@/application/Context/State/Code/Generation/CodeBuilder'; export class ShellBuilder extends CodeBuilder { - protected getCommentDelimiter(): string { - return '#'; - } - protected writeStandardOut(text: string): string { - return `echo '${escapeForEcho(text)}'`; - } + protected getCommentDelimiter(): string { + return '#'; + } + + protected writeStandardOut(text: string): string { + return `echo '${escapeForEcho(text)}'`; + } } function escapeForEcho(text: string) { - return text - .replace(/'/g, '\'\\\'\''); + return text + .replace(/'/g, '\'\\\'\''); } diff --git a/src/application/Context/State/Code/Generation/UserScriptGenerator.ts b/src/application/Context/State/Code/Generation/UserScriptGenerator.ts index e3edd277..e3a4945b 100644 --- a/src/application/Context/State/Code/Generation/UserScriptGenerator.ts +++ b/src/application/Context/State/Code/Generation/UserScriptGenerator.ts @@ -1,71 +1,76 @@ import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; -import { IUserScriptGenerator } from './IUserScriptGenerator'; import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition'; -import { CodePosition } from '../Position/CodePosition'; -import { IUserScript } from './IUserScript'; import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; +import { CodePosition } from '../Position/CodePosition'; +import { IUserScriptGenerator } from './IUserScriptGenerator'; +import { IUserScript } from './IUserScript'; import { ICodeBuilder } from './ICodeBuilder'; import { ICodeBuilderFactory } from './ICodeBuilderFactory'; import { CodeBuilderFactory } from './CodeBuilderFactory'; export class UserScriptGenerator implements IUserScriptGenerator { - constructor(private readonly codeBuilderFactory: ICodeBuilderFactory = new CodeBuilderFactory()) { + constructor(private readonly codeBuilderFactory: ICodeBuilderFactory = new CodeBuilderFactory()) { + } + + public buildCode( + selectedScripts: ReadonlyArray, + scriptingDefinition: IScriptingDefinition, + ): IUserScript { + if (!selectedScripts) { throw new Error('undefined scripts'); } + if (!scriptingDefinition) { throw new Error('undefined definition'); } + let scriptPositions = new Map(); + if (!selectedScripts.length) { + return { code: '', scriptPositions }; } - public buildCode( - selectedScripts: ReadonlyArray, - scriptingDefinition: IScriptingDefinition): IUserScript { - if (!selectedScripts) { throw new Error('undefined scripts'); } - if (!scriptingDefinition) { throw new Error('undefined definition'); } - let scriptPositions = new Map(); - if (!selectedScripts.length) { - return { code: '', scriptPositions }; - } - let builder = this.codeBuilderFactory.create(scriptingDefinition.language); - builder = initializeCode(scriptingDefinition.startCode, builder); - for (const selection of selectedScripts) { - scriptPositions = appendSelection(selection, scriptPositions, builder); - } - const code = finalizeCode(builder, scriptingDefinition.endCode); - return { code, scriptPositions }; + let builder = this.codeBuilderFactory.create(scriptingDefinition.language); + builder = initializeCode(scriptingDefinition.startCode, builder); + for (const selection of selectedScripts) { + scriptPositions = appendSelection(selection, scriptPositions, builder); } + const code = finalizeCode(builder, scriptingDefinition.endCode); + return { code, scriptPositions }; + } } function initializeCode(startCode: string, builder: ICodeBuilder): ICodeBuilder { - if (!startCode) { - return builder; - } - return builder - .appendLine(startCode) - .appendLine(); + if (!startCode) { + return builder; + } + return builder + .appendLine(startCode) + .appendLine(); } function finalizeCode(builder: ICodeBuilder, endCode: string): string { - if (!endCode) { - return builder.toString(); - } - return builder.appendLine() - .appendLine(endCode) - .toString(); + if (!endCode) { + return builder.toString(); + } + return builder.appendLine() + .appendLine(endCode) + .toString(); } function appendSelection( - selection: SelectedScript, - scriptPositions: Map, - builder: ICodeBuilder): Map { - const startPosition = builder.currentLine + 1; // Because first line will be empty to separate scripts - builder = appendCode(selection, builder); - const endPosition = builder.currentLine - 1; - builder.appendLine(); - const position = new CodePosition(startPosition, endPosition); - scriptPositions.set(selection, position); - return scriptPositions; + selection: SelectedScript, + scriptPositions: Map, + builder: ICodeBuilder, +): Map { + // Start from next line because first line will be empty to separate scripts + const startPosition = builder.currentLine + 1; + appendCode(selection, builder); + const endPosition = builder.currentLine - 1; + builder.appendLine(); + const position = new CodePosition(startPosition, endPosition); + scriptPositions.set(selection, position); + return scriptPositions; } function appendCode(selection: SelectedScript, builder: ICodeBuilder): ICodeBuilder { - const name = selection.revert ? `${selection.script.name} (revert)` : selection.script.name; - const scriptCode = selection.revert ? selection.script.code.revert : selection.script.code.execute; - return builder - .appendLine() - .appendFunction(name, scriptCode); + const { script } = selection; + const name = selection.revert ? `${script.name} (revert)` : script.name; + const scriptCode = selection.revert ? script.code.revert : script.code.execute; + return builder + .appendLine() + .appendFunction(name, scriptCode); } diff --git a/src/application/Context/State/Code/IApplicationCode.ts b/src/application/Context/State/Code/IApplicationCode.ts index 555f28b1..10b35d63 100644 --- a/src/application/Context/State/Code/IApplicationCode.ts +++ b/src/application/Context/State/Code/IApplicationCode.ts @@ -1,7 +1,7 @@ -import { ICodeChangedEvent } from './Event/ICodeChangedEvent'; import { IEventSource } from '@/infrastructure/Events/IEventSource'; +import { ICodeChangedEvent } from './Event/ICodeChangedEvent'; export interface IApplicationCode { - readonly changed: IEventSource; - readonly current: string; + readonly changed: IEventSource; + readonly current: string; } diff --git a/src/application/Context/State/Code/Position/CodePosition.ts b/src/application/Context/State/Code/Position/CodePosition.ts index c0921989..aefabe63 100644 --- a/src/application/Context/State/Code/Position/CodePosition.ts +++ b/src/application/Context/State/Code/Position/CodePosition.ts @@ -1,24 +1,25 @@ import { ICodePosition } from './ICodePosition'; export class CodePosition implements ICodePosition { - public get totalLines(): number { - return this.endLine - this.startLine; - } + public get totalLines(): number { + return this.endLine - this.startLine; + } - constructor( - public readonly startLine: number, - public readonly endLine: number) { - if (startLine < 0) { - throw new Error('Code cannot start in a negative line'); - } - if (endLine < 0) { - throw new Error('Code cannot end in a negative line'); - } - if (endLine === startLine) { - throw new Error('Empty code'); - } - if (endLine < startLine) { - throw new Error('End line cannot be less than start line'); - } + constructor( + public readonly startLine: number, + public readonly endLine: number, + ) { + if (startLine < 0) { + throw new Error('Code cannot start in a negative line'); } + if (endLine < 0) { + throw new Error('Code cannot end in a negative line'); + } + if (endLine === startLine) { + throw new Error('Empty code'); + } + if (endLine < startLine) { + throw new Error('End line cannot be less than start line'); + } + } } diff --git a/src/application/Context/State/Code/Position/ICodePosition.ts b/src/application/Context/State/Code/Position/ICodePosition.ts index e3e419aa..e10120b9 100644 --- a/src/application/Context/State/Code/Position/ICodePosition.ts +++ b/src/application/Context/State/Code/Position/ICodePosition.ts @@ -1,5 +1,5 @@ export interface ICodePosition { - readonly startLine: number; - readonly endLine: number; - readonly totalLines: number; + readonly startLine: number; + readonly endLine: number; + readonly totalLines: number; } diff --git a/src/application/Context/State/Filter/FilterResult.ts b/src/application/Context/State/Filter/FilterResult.ts index 127855c2..2bf8e46d 100644 --- a/src/application/Context/State/Filter/FilterResult.ts +++ b/src/application/Context/State/Filter/FilterResult.ts @@ -1,18 +1,20 @@ -import { IFilterResult } from './IFilterResult'; import { IScript } from '@/domain/IScript'; import { ICategory } from '@/domain/ICategory'; +import { IFilterResult } from './IFilterResult'; export class FilterResult implements IFilterResult { - constructor( - public readonly scriptMatches: ReadonlyArray, - public readonly categoryMatches: ReadonlyArray, - public readonly query: string) { - if (!query) { throw new Error('Query is empty or undefined'); } - if (!scriptMatches) { throw new Error('Script matches is undefined'); } - if (!categoryMatches) { throw new Error('Category matches is undefined'); } - } - public hasAnyMatches(): boolean { - return this.scriptMatches.length > 0 - || this.categoryMatches.length > 0; - } + constructor( + public readonly scriptMatches: ReadonlyArray, + public readonly categoryMatches: ReadonlyArray, + public readonly query: string, + ) { + if (!query) { throw new Error('Query is empty or undefined'); } + if (!scriptMatches) { throw new Error('Script matches is undefined'); } + if (!categoryMatches) { throw new Error('Category matches is undefined'); } + } + + public hasAnyMatches(): boolean { + return this.scriptMatches.length > 0 + || this.categoryMatches.length > 0; + } } diff --git a/src/application/Context/State/Filter/IFilterResult.ts b/src/application/Context/State/Filter/IFilterResult.ts index dc8308eb..8391dc82 100644 --- a/src/application/Context/State/Filter/IFilterResult.ts +++ b/src/application/Context/State/Filter/IFilterResult.ts @@ -1,8 +1,8 @@ import { IScript, ICategory } from '@/domain/ICategory'; export interface IFilterResult { - readonly categoryMatches: ReadonlyArray; - readonly scriptMatches: ReadonlyArray; - readonly query: string; - hasAnyMatches(): boolean; + readonly categoryMatches: ReadonlyArray; + readonly scriptMatches: ReadonlyArray; + readonly query: string; + hasAnyMatches(): boolean; } diff --git a/src/application/Context/State/Filter/IUserFilter.ts b/src/application/Context/State/Filter/IUserFilter.ts index 1cec5486..ebe4c127 100644 --- a/src/application/Context/State/Filter/IUserFilter.ts +++ b/src/application/Context/State/Filter/IUserFilter.ts @@ -2,12 +2,12 @@ import { IEventSource } from '@/infrastructure/Events/IEventSource'; import { IFilterResult } from './IFilterResult'; export interface IReadOnlyUserFilter { - readonly currentFilter: IFilterResult | undefined; - readonly filtered: IEventSource; - readonly filterRemoved: IEventSource; + readonly currentFilter: IFilterResult | undefined; + readonly filtered: IEventSource; + readonly filterRemoved: IEventSource; } export interface IUserFilter extends IReadOnlyUserFilter { - setFilter(filter: string): void; - removeFilter(): void; + setFilter(filter: string): void; + removeFilter(): void; } diff --git a/src/application/Context/State/Filter/UserFilter.ts b/src/application/Context/State/Filter/UserFilter.ts index 32c5b2bd..afeebd4e 100644 --- a/src/application/Context/State/Filter/UserFilter.ts +++ b/src/application/Context/State/Filter/UserFilter.ts @@ -1,52 +1,56 @@ import { IScript } from '@/domain/IScript'; +import { EventSource } from '@/infrastructure/Events/EventSource'; +import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { FilterResult } from './FilterResult'; import { IFilterResult } from './IFilterResult'; import { IUserFilter } from './IUserFilter'; -import { EventSource } from '@/infrastructure/Events/EventSource'; -import { ICategoryCollection } from '@/domain/ICategoryCollection'; export class UserFilter implements IUserFilter { - public readonly filtered = new EventSource(); - public readonly filterRemoved = new EventSource(); - public currentFilter: IFilterResult | undefined; + public readonly filtered = new EventSource(); - constructor(private collection: ICategoryCollection) { + public readonly filterRemoved = new EventSource(); + public currentFilter: IFilterResult | undefined; + + constructor(private collection: ICategoryCollection) { + + } + + public setFilter(filter: string): void { + if (!filter) { + throw new Error('Filter must be defined and not empty. Use removeFilter() to remove the filter'); } + const filterLowercase = filter.toLocaleLowerCase(); + const filteredScripts = this.collection.getAllScripts().filter( + (script) => isScriptAMatch(script, filterLowercase), + ); + const filteredCategories = this.collection.getAllCategories().filter( + (category) => category.name.toLowerCase().includes(filterLowercase), + ); + const matches = new FilterResult( + filteredScripts, + filteredCategories, + filter, + ); + this.currentFilter = matches; + this.filtered.notify(matches); + } - public setFilter(filter: string): void { - if (!filter) { - throw new Error('Filter must be defined and not empty. Use removeFilter() to remove the filter'); - } - const filterLowercase = filter.toLocaleLowerCase(); - const filteredScripts = this.collection.getAllScripts().filter( - (script) => isScriptAMatch(script, filterLowercase)); - const filteredCategories = this.collection.getAllCategories().filter( - (category) => category.name.toLowerCase().includes(filterLowercase)); - const matches = new FilterResult( - filteredScripts, - filteredCategories, - filter, - ); - this.currentFilter = matches; - this.filtered.notify(matches); - } - - public removeFilter(): void { - this.currentFilter = undefined; - this.filterRemoved.notify(); - } + public removeFilter(): void { + this.currentFilter = undefined; + this.filterRemoved.notify(); + } } function isScriptAMatch(script: IScript, filterLowercase: string) { - if (script.name.toLowerCase().includes(filterLowercase)) { - return true; - } - if (script.code.execute.toLowerCase().includes(filterLowercase)) { - return true; - } - if (script.code.revert) { - return script.code.revert.toLowerCase().includes(filterLowercase); - } - return false; + if (script.name.toLowerCase().includes(filterLowercase)) { + return true; + } + if (script.code.execute.toLowerCase().includes(filterLowercase)) { + return true; + } + if (script.code.revert) { + return script.code.revert.toLowerCase().includes(filterLowercase); + } + return false; } diff --git a/src/application/Context/State/ICategoryCollectionState.ts b/src/application/Context/State/ICategoryCollectionState.ts index 01664bc8..f07497e4 100644 --- a/src/application/Context/State/ICategoryCollectionState.ts +++ b/src/application/Context/State/ICategoryCollectionState.ts @@ -1,18 +1,18 @@ +import { ICategoryCollection } from '@/domain/ICategoryCollection'; +import { OperatingSystem } from '@/domain/OperatingSystem'; import { IReadOnlyUserFilter, IUserFilter } from './Filter/IUserFilter'; import { IReadOnlyUserSelection, IUserSelection } from './Selection/IUserSelection'; import { IApplicationCode } from './Code/IApplicationCode'; -import { ICategoryCollection } from '@/domain/ICategoryCollection'; -import { OperatingSystem } from '@/domain/OperatingSystem'; export interface IReadOnlyCategoryCollectionState { - readonly code: IApplicationCode; - readonly os: OperatingSystem; - readonly filter: IReadOnlyUserFilter; - readonly selection: IReadOnlyUserSelection; - readonly collection: ICategoryCollection; + readonly code: IApplicationCode; + readonly os: OperatingSystem; + readonly filter: IReadOnlyUserFilter; + readonly selection: IReadOnlyUserSelection; + readonly collection: ICategoryCollection; } export interface ICategoryCollectionState extends IReadOnlyCategoryCollectionState { - readonly filter: IUserFilter; - readonly selection: IUserSelection; + readonly filter: IUserFilter; + readonly selection: IUserSelection; } diff --git a/src/application/Context/State/Selection/IUserSelection.ts b/src/application/Context/State/Selection/IUserSelection.ts index 1c5d7229..b696bada 100644 --- a/src/application/Context/State/Selection/IUserSelection.ts +++ b/src/application/Context/State/Selection/IUserSelection.ts @@ -1,23 +1,23 @@ -import { SelectedScript } from './SelectedScript'; import { IScript } from '@/domain/IScript'; import { ICategory } from '@/domain/ICategory'; import { IEventSource } from '@/infrastructure/Events/IEventSource'; +import { SelectedScript } from './SelectedScript'; export interface IReadOnlyUserSelection { - readonly changed: IEventSource>; - readonly selectedScripts: ReadonlyArray; - isSelected(scriptId: string): boolean; - areAllSelected(category: ICategory): boolean; - isAnySelected(category: ICategory): boolean; + readonly changed: IEventSource>; + readonly selectedScripts: ReadonlyArray; + isSelected(scriptId: string): boolean; + areAllSelected(category: ICategory): boolean; + isAnySelected(category: ICategory): boolean; } export interface IUserSelection extends IReadOnlyUserSelection { - removeAllInCategory(categoryId: number): void; - addOrUpdateAllInCategory(categoryId: number, revert: boolean): void; - addSelectedScript(scriptId: string, revert: boolean): void; - addOrUpdateSelectedScript(scriptId: string, revert: boolean): void; - removeSelectedScript(scriptId: string): void; - selectOnly(scripts: ReadonlyArray): void; - selectAll(): void; - deselectAll(): void; + removeAllInCategory(categoryId: number): void; + addOrUpdateAllInCategory(categoryId: number, revert: boolean): void; + addSelectedScript(scriptId: string, revert: boolean): void; + addOrUpdateSelectedScript(scriptId: string, revert: boolean): void; + removeSelectedScript(scriptId: string): void; + selectOnly(scripts: ReadonlyArray): void; + selectAll(): void; + deselectAll(): void; } diff --git a/src/application/Context/State/Selection/SelectedScript.ts b/src/application/Context/State/Selection/SelectedScript.ts index de2173d8..e3966a56 100644 --- a/src/application/Context/State/Selection/SelectedScript.ts +++ b/src/application/Context/State/Selection/SelectedScript.ts @@ -2,13 +2,13 @@ import { BaseEntity } from '@/infrastructure/Entity/BaseEntity'; import { IScript } from '@/domain/IScript'; export class SelectedScript extends BaseEntity { - constructor( - public readonly script: IScript, - public readonly revert: boolean, - ) { - super(script.id); - if (revert && !script.canRevert()) { - throw new Error('cannot revert an irreversible script'); - } + constructor( + public readonly script: IScript, + public readonly revert: boolean, + ) { + super(script.id); + if (revert && !script.canRevert()) { + throw new Error('cannot revert an irreversible script'); } + } } diff --git a/src/application/Context/State/Selection/UserSelection.ts b/src/application/Context/State/Selection/UserSelection.ts index 1bf64717..d87f152a 100644 --- a/src/application/Context/State/Selection/UserSelection.ts +++ b/src/application/Context/State/Selection/UserSelection.ts @@ -1,141 +1,145 @@ -import { SelectedScript } from './SelectedScript'; -import { IUserSelection } from './IUserSelection'; import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository'; import { IScript } from '@/domain/IScript'; import { EventSource } from '@/infrastructure/Events/EventSource'; import { IRepository } from '@/infrastructure/Repository/IRepository'; import { ICategory } from '@/domain/ICategory'; import { ICategoryCollection } from '@/domain/ICategoryCollection'; +import { IUserSelection } from './IUserSelection'; +import { SelectedScript } from './SelectedScript'; export class UserSelection implements IUserSelection { - public readonly changed = new EventSource>(); - private readonly scripts: IRepository; + public readonly changed = new EventSource>(); - constructor( - private readonly collection: ICategoryCollection, - selectedScripts: ReadonlyArray) { - this.scripts = new InMemoryRepository(); - if (selectedScripts && selectedScripts.length > 0) { - for (const script of selectedScripts) { - this.scripts.addItem(script); - } - } - } + private readonly scripts: IRepository; - public areAllSelected(category: ICategory): boolean { - if (this.selectedScripts.length === 0) { - return false; - } - const scripts = category.getAllScriptsRecursively(); - if (this.selectedScripts.length < scripts.length) { - return false; - } - return scripts.every((script) => this.selectedScripts.some((selected) => selected.id === script.id)); + constructor( + private readonly collection: ICategoryCollection, + selectedScripts: ReadonlyArray, + ) { + this.scripts = new InMemoryRepository(); + if (selectedScripts && selectedScripts.length > 0) { + for (const script of selectedScripts) { + this.scripts.addItem(script); + } } + } - public isAnySelected(category: ICategory): boolean { - if (this.selectedScripts.length === 0) { - return false; - } - return this.selectedScripts.some((s) => category.includes(s.script)); + public areAllSelected(category: ICategory): boolean { + if (this.selectedScripts.length === 0) { + return false; } + const scripts = category.getAllScriptsRecursively(); + if (this.selectedScripts.length < scripts.length) { + return false; + } + return scripts.every( + (script) => this.selectedScripts.some((selected) => selected.id === script.id), + ); + } - public removeAllInCategory(categoryId: number): void { - const category = this.collection.findCategory(categoryId); - const scriptsToRemove = category.getAllScriptsRecursively() - .filter((script) => this.scripts.exists(script.id)); - if (!scriptsToRemove.length) { - return; - } - for (const script of scriptsToRemove) { - this.scripts.removeItem(script.id); - } - this.changed.notify(this.scripts.getItems()); + public isAnySelected(category: ICategory): boolean { + if (this.selectedScripts.length === 0) { + return false; } + return this.selectedScripts.some((s) => category.includes(s.script)); + } - public addOrUpdateAllInCategory(categoryId: number, revert: boolean = false): void { - const category = this.collection.findCategory(categoryId); - const scriptsToAddOrUpdate = category.getAllScriptsRecursively() - .filter((script) => - !this.scripts.exists(script.id) - || this.scripts.getById(script.id).revert !== revert, - ); - if (!scriptsToAddOrUpdate.length) { - return; - } - for (const script of scriptsToAddOrUpdate) { - const selectedScript = new SelectedScript(script, revert); - this.scripts.addOrUpdateItem(selectedScript); - } - this.changed.notify(this.scripts.getItems()); + public removeAllInCategory(categoryId: number): void { + const category = this.collection.findCategory(categoryId); + const scriptsToRemove = category.getAllScriptsRecursively() + .filter((script) => this.scripts.exists(script.id)); + if (!scriptsToRemove.length) { + return; } + for (const script of scriptsToRemove) { + this.scripts.removeItem(script.id); + } + this.changed.notify(this.scripts.getItems()); + } - public addSelectedScript(scriptId: string, revert: boolean): void { - const script = this.collection.findScript(scriptId); - if (!script) { - throw new Error(`Cannot add (id: ${scriptId}) as it is unknown`); - } - const selectedScript = new SelectedScript(script, revert); - this.scripts.addItem(selectedScript); - this.changed.notify(this.scripts.getItems()); + public addOrUpdateAllInCategory(categoryId: number, revert = false): void { + const category = this.collection.findCategory(categoryId); + const scriptsToAddOrUpdate = category.getAllScriptsRecursively() + .filter( + (script) => !this.scripts.exists(script.id) + || this.scripts.getById(script.id).revert !== revert, + ); + if (!scriptsToAddOrUpdate.length) { + return; } + for (const script of scriptsToAddOrUpdate) { + const selectedScript = new SelectedScript(script, revert); + this.scripts.addOrUpdateItem(selectedScript); + } + this.changed.notify(this.scripts.getItems()); + } - public addOrUpdateSelectedScript(scriptId: string, revert: boolean): void { - const script = this.collection.findScript(scriptId); - const selectedScript = new SelectedScript(script, revert); - this.scripts.addOrUpdateItem(selectedScript); - this.changed.notify(this.scripts.getItems()); + public addSelectedScript(scriptId: string, revert: boolean): void { + const script = this.collection.findScript(scriptId); + if (!script) { + throw new Error(`Cannot add (id: ${scriptId}) as it is unknown`); } + const selectedScript = new SelectedScript(script, revert); + this.scripts.addItem(selectedScript); + this.changed.notify(this.scripts.getItems()); + } - public removeSelectedScript(scriptId: string): void { - this.scripts.removeItem(scriptId); - this.changed.notify(this.scripts.getItems()); - } + public addOrUpdateSelectedScript(scriptId: string, revert: boolean): void { + const script = this.collection.findScript(scriptId); + const selectedScript = new SelectedScript(script, revert); + this.scripts.addOrUpdateItem(selectedScript); + this.changed.notify(this.scripts.getItems()); + } - public isSelected(scriptId: string): boolean { - return this.scripts.exists(scriptId); - } + public removeSelectedScript(scriptId: string): void { + this.scripts.removeItem(scriptId); + this.changed.notify(this.scripts.getItems()); + } - /** Get users scripts based on his/her selections */ - public get selectedScripts(): ReadonlyArray { - return this.scripts.getItems(); - } + public isSelected(scriptId: string): boolean { + return this.scripts.exists(scriptId); + } - public selectAll(): void { - for (const script of this.collection.getAllScripts()) { - if (!this.scripts.exists(script.id)) { - const selection = new SelectedScript(script, false); - this.scripts.addItem(selection); - } - } - this.changed.notify(this.scripts.getItems()); - } + /** Get users scripts based on his/her selections */ + public get selectedScripts(): ReadonlyArray { + return this.scripts.getItems(); + } - public deselectAll(): void { - const selectedScriptIds = this.scripts.getItems().map((script) => script.id); - for (const scriptId of selectedScriptIds) { - this.scripts.removeItem(scriptId); - } - this.changed.notify([]); + public selectAll(): void { + for (const script of this.collection.getAllScripts()) { + if (!this.scripts.exists(script.id)) { + const selection = new SelectedScript(script, false); + this.scripts.addItem(selection); + } } + this.changed.notify(this.scripts.getItems()); + } - public selectOnly(scripts: readonly IScript[]): void { - if (!scripts || scripts.length === 0) { - throw new Error('Scripts are empty. Use deselectAll() if you want to deselect everything'); - } - // Unselect from selected scripts - if (this.scripts.length !== 0) { - this.scripts.getItems() - .filter((existing) => !scripts.some((script) => existing.id === script.id)) - .map((script) => script.id) - .forEach((scriptId) => this.scripts.removeItem(scriptId)); - } - // Select from unselected scripts - const unselectedScripts = scripts.filter((script) => !this.scripts.exists(script.id)); - for (const toSelect of unselectedScripts) { - const selection = new SelectedScript(toSelect, false); - this.scripts.addItem(selection); - } - this.changed.notify(this.scripts.getItems()); + public deselectAll(): void { + const selectedScriptIds = this.scripts.getItems().map((script) => script.id); + for (const scriptId of selectedScriptIds) { + this.scripts.removeItem(scriptId); } + this.changed.notify([]); + } + + public selectOnly(scripts: readonly IScript[]): void { + if (!scripts || scripts.length === 0) { + throw new Error('Scripts are empty. Use deselectAll() if you want to deselect everything'); + } + // Unselect from selected scripts + if (this.scripts.length !== 0) { + this.scripts.getItems() + .filter((existing) => !scripts.some((script) => existing.id === script.id)) + .map((script) => script.id) + .forEach((scriptId) => this.scripts.removeItem(scriptId)); + } + // Select from unselected scripts + const unselectedScripts = scripts.filter((script) => !this.scripts.exists(script.id)); + for (const toSelect of unselectedScripts) { + const selection = new SelectedScript(toSelect, false); + this.scripts.addItem(selection); + } + this.changed.notify(this.scripts.getItems()); + } } diff --git a/src/application/Environment/BrowserOs/BrowserOsDetector.ts b/src/application/Environment/BrowserOs/BrowserOsDetector.ts index 8fa5a1f8..3022c3d3 100644 --- a/src/application/Environment/BrowserOs/BrowserOsDetector.ts +++ b/src/application/Environment/BrowserOs/BrowserOsDetector.ts @@ -3,52 +3,55 @@ import { DetectorBuilder } from './DetectorBuilder'; import { IBrowserOsDetector } from './IBrowserOsDetector'; export class BrowserOsDetector implements IBrowserOsDetector { - private readonly detectors = BrowserDetectors; - public detect(userAgent: string): OperatingSystem | undefined { - if (!userAgent) { - return undefined; - } - for (const detector of this.detectors) { - const os = detector.detect(userAgent); - if (os !== undefined) { - return os; - } - } - return undefined; + private readonly detectors = BrowserDetectors; + + public detect(userAgent: string): OperatingSystem | undefined { + if (!userAgent) { + return undefined; } + for (const detector of this.detectors) { + const os = detector.detect(userAgent); + if (os !== undefined) { + return os; + } + } + return undefined; + } } // Reference: https://github.com/keithws/browser-report/blob/master/index.js#L304 -const BrowserDetectors = -[ - define(OperatingSystem.KaiOS, (b) => - b.mustInclude('KAIOS')), - define(OperatingSystem.ChromeOS, (b) => - b.mustInclude('CrOS')), - define(OperatingSystem.BlackBerryOS, (b) => - b.mustInclude('BlackBerry')), - define(OperatingSystem.BlackBerryTabletOS, (b) => - b.mustInclude('RIM Tablet OS')), - define(OperatingSystem.BlackBerry, (b) => - b.mustInclude('BB10')), - define(OperatingSystem.Android, (b) => - b.mustInclude('Android').mustNotInclude('Windows Phone')), - define(OperatingSystem.Android, (b) => - b.mustInclude('Adr').mustNotInclude('Windows Phone')), - define(OperatingSystem.iOS, (b) => - b.mustInclude('like Mac OS X')), - define(OperatingSystem.Linux, (b) => - b.mustInclude('Linux').mustNotInclude('Android').mustNotInclude('Adr')), - define(OperatingSystem.Windows, (b) => - b.mustInclude('Windows').mustNotInclude('Windows Phone')), - define(OperatingSystem.WindowsPhone, (b) => - b.mustInclude('Windows Phone')), - define(OperatingSystem.macOS, (b) => - b.mustInclude('OS X').mustNotInclude('Android').mustNotInclude('like Mac OS X')), +const BrowserDetectors = [ + define(OperatingSystem.KaiOS, (b) => b + .mustInclude('KAIOS')), + define(OperatingSystem.ChromeOS, (b) => b + .mustInclude('CrOS')), + define(OperatingSystem.BlackBerryOS, (b) => b + .mustInclude('BlackBerry')), + define(OperatingSystem.BlackBerryTabletOS, (b) => b + .mustInclude('RIM Tablet OS')), + define(OperatingSystem.BlackBerry, (b) => b + .mustInclude('BB10')), + define(OperatingSystem.Android, (b) => b + .mustInclude('Android').mustNotInclude('Windows Phone')), + define(OperatingSystem.Android, (b) => b + .mustInclude('Adr').mustNotInclude('Windows Phone')), + define(OperatingSystem.iOS, (b) => b + .mustInclude('like Mac OS X')), + define(OperatingSystem.Linux, (b) => b + .mustInclude('Linux').mustNotInclude('Android').mustNotInclude('Adr')), + define(OperatingSystem.Windows, (b) => b + .mustInclude('Windows').mustNotInclude('Windows Phone')), + define(OperatingSystem.WindowsPhone, (b) => b + .mustInclude('Windows Phone')), + define(OperatingSystem.macOS, (b) => b + .mustInclude('OS X').mustNotInclude('Android').mustNotInclude('like Mac OS X')), ]; -function define(os: OperatingSystem, applyRules: (builder: DetectorBuilder) => DetectorBuilder): IBrowserOsDetector { - const builder = new DetectorBuilder(os); - applyRules(builder); - return builder.build(); +function define( + os: OperatingSystem, + applyRules: (builder: DetectorBuilder) => DetectorBuilder, +): IBrowserOsDetector { + const builder = new DetectorBuilder(os); + applyRules(builder); + return builder.build(); } diff --git a/src/application/Environment/BrowserOs/DetectorBuilder.ts b/src/application/Environment/BrowserOs/DetectorBuilder.ts index 3940ec40..ea7e0a37 100644 --- a/src/application/Environment/BrowserOs/DetectorBuilder.ts +++ b/src/application/Environment/BrowserOs/DetectorBuilder.ts @@ -1,53 +1,54 @@ -import { IBrowserOsDetector } from './IBrowserOsDetector'; import { OperatingSystem } from '@/domain/OperatingSystem'; +import { IBrowserOsDetector } from './IBrowserOsDetector'; export class DetectorBuilder { - private readonly existingPartsInUserAgent = new Array(); - private readonly notExistingPartsInUserAgent = new Array(); + private readonly existingPartsInUserAgent = new Array(); - constructor(private readonly os: OperatingSystem) { } + private readonly notExistingPartsInUserAgent = new Array(); - public mustInclude(str: string): DetectorBuilder { - return this.add(str, this.existingPartsInUserAgent); + constructor(private readonly os: OperatingSystem) { } + + public mustInclude(str: string): DetectorBuilder { + return this.add(str, this.existingPartsInUserAgent); + } + + public mustNotInclude(str: string): DetectorBuilder { + return this.add(str, this.notExistingPartsInUserAgent); + } + + public build(): IBrowserOsDetector { + if (!this.existingPartsInUserAgent.length) { + throw new Error('Must include at least a part'); } + return { + detect: (agent) => this.detect(agent), + }; + } - public mustNotInclude(str: string): DetectorBuilder { - return this.add(str, this.notExistingPartsInUserAgent); + private detect(userAgent: string): OperatingSystem { + if (!userAgent) { + throw new Error('User agent is null or undefined'); } + if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) { + return undefined; + } + if (this.notExistingPartsInUserAgent.some((part) => userAgent.includes(part))) { + return undefined; + } + return this.os; + } - public build(): IBrowserOsDetector { - if (!this.existingPartsInUserAgent.length) { - throw new Error('Must include at least a part'); - } - return { - detect: (agent) => this.detect(agent), - }; + private add(part: string, array: string[]): DetectorBuilder { + if (!part) { + throw new Error('part is empty or undefined'); } - - private detect(userAgent: string): OperatingSystem { - if (!userAgent) { - throw new Error('User agent is null or undefined'); - } - if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) { - return undefined; - } - if (this.notExistingPartsInUserAgent.some((part) => userAgent.includes(part))) { - return undefined; - } - return this.os; + if (this.existingPartsInUserAgent.includes(part)) { + throw new Error(`part ${part} is already included as existing part`); } - - private add(part: string, array: string[]): DetectorBuilder { - if (!part) { - throw new Error('part is empty or undefined'); - } - if (this.existingPartsInUserAgent.includes(part)) { - throw new Error(`part ${part} is already included as existing part`); - } - if (this.notExistingPartsInUserAgent.includes(part)) { - throw new Error(`part ${part} is already included as not existing part`); - } - array.push(part); - return this; + if (this.notExistingPartsInUserAgent.includes(part)) { + throw new Error(`part ${part} is already included as not existing part`); } + array.push(part); + return this; + } } diff --git a/src/application/Environment/BrowserOs/IBrowserOsDetector.ts b/src/application/Environment/BrowserOs/IBrowserOsDetector.ts index d09e80ae..368e10f8 100644 --- a/src/application/Environment/BrowserOs/IBrowserOsDetector.ts +++ b/src/application/Environment/BrowserOs/IBrowserOsDetector.ts @@ -1,5 +1,5 @@ import { OperatingSystem } from '@/domain/OperatingSystem'; export interface IBrowserOsDetector { - detect(userAgent: string): OperatingSystem | undefined; + detect(userAgent: string): OperatingSystem | undefined; } diff --git a/src/application/Environment/Environment.ts b/src/application/Environment/Environment.ts index a023f7b8..cb2cf011 100644 --- a/src/application/Environment/Environment.ts +++ b/src/application/Environment/Environment.ts @@ -1,83 +1,89 @@ +import { OperatingSystem } from '@/domain/OperatingSystem'; import { BrowserOsDetector } from './BrowserOs/BrowserOsDetector'; import { IBrowserOsDetector } from './BrowserOs/IBrowserOsDetector'; import { IEnvironment } from './IEnvironment'; -import { OperatingSystem } from '@/domain/OperatingSystem'; -interface IEnvironmentVariables { - readonly window: Window & typeof globalThis; - readonly process: NodeJS.Process; - readonly navigator: Navigator; +export interface IEnvironmentVariables { + readonly window: Window & typeof globalThis; + readonly process: NodeJS.Process; + readonly navigator: Navigator; } export class Environment implements IEnvironment { - public static readonly CurrentEnvironment: IEnvironment = new Environment({ - window, - process: typeof process !== 'undefined' ? process /* electron only */ : undefined, - navigator, - }); - public readonly isDesktop: boolean; - public readonly os: OperatingSystem; - protected constructor( - variables: IEnvironmentVariables, - browserOsDetector: IBrowserOsDetector = new BrowserOsDetector()) { - if (!variables) { - throw new Error('variables is null or empty'); - } - this.isDesktop = isDesktop(variables); - if (this.isDesktop) { - this.os = getDesktopOsType(getProcessPlatform(variables)); - } else { - const userAgent = getUserAgent(variables); - this.os = !userAgent ? undefined : browserOsDetector.detect(userAgent); - } + public static readonly CurrentEnvironment: IEnvironment = new Environment({ + window, + process: typeof process !== 'undefined' ? process /* electron only */ : undefined, + navigator, + }); + + public readonly isDesktop: boolean; + + public readonly os: OperatingSystem; + + protected constructor( + variables: IEnvironmentVariables, + browserOsDetector: IBrowserOsDetector = new BrowserOsDetector(), + ) { + if (!variables) { + throw new Error('variables is null or empty'); } + this.isDesktop = isDesktop(variables); + if (this.isDesktop) { + this.os = getDesktopOsType(getProcessPlatform(variables)); + } else { + const userAgent = getUserAgent(variables); + this.os = !userAgent ? undefined : browserOsDetector.detect(userAgent); + } + } } function getUserAgent(variables: IEnvironmentVariables): string { - if (!variables.window || !variables.window.navigator) { - return undefined; - } - return variables.window.navigator.userAgent; + if (!variables.window || !variables.window.navigator) { + return undefined; + } + return variables.window.navigator.userAgent; } function getProcessPlatform(variables: IEnvironmentVariables): string { - if (!variables.process || !variables.process.platform) { - return undefined; - } - return variables.process.platform; + if (!variables.process || !variables.process.platform) { + return undefined; + } + return variables.process.platform; } function getDesktopOsType(processPlatform: string): OperatingSystem | undefined { - // https://nodejs.org/api/process.html#process_process_platform - if (processPlatform === 'darwin') { - return OperatingSystem.macOS; - } else if (processPlatform === 'win32') { - return OperatingSystem.Windows; - } else if (processPlatform === 'linux') { - return OperatingSystem.Linux; - } - return undefined; + // https://nodejs.org/api/process.html#process_process_platform + switch (processPlatform) { + case 'darwin': + return OperatingSystem.macOS; + case 'win32': + return OperatingSystem.Windows; + case 'linux': + return OperatingSystem.Linux; + default: + return undefined; + } } function isDesktop(variables: IEnvironmentVariables): boolean { - // More: https://github.com/electron/electron/issues/2288 - // Renderer process - if (variables.window - && variables.window.process - && variables.window.process.type === 'renderer') { - return true; - } - // Main process - if (variables.process - && variables.process.versions - && Boolean(variables.process.versions.electron)) { - return true; - } - // Detect the user agent when the `nodeIntegration` option is set to true - if (variables.navigator - && variables.navigator.userAgent - && variables.navigator.userAgent.includes('Electron')) { - return true; - } - return false; + // More: https://github.com/electron/electron/issues/2288 + // Renderer process + if (variables.window + && variables.window.process + && variables.window.process.type === 'renderer') { + return true; + } + // Main process + if (variables.process + && variables.process.versions + && Boolean(variables.process.versions.electron)) { + return true; + } + // Detect the user agent when the `nodeIntegration` option is set to true + if (variables.navigator + && variables.navigator.userAgent + && variables.navigator.userAgent.includes('Electron')) { + return true; + } + return false; } diff --git a/src/application/Environment/IEnvironment.ts b/src/application/Environment/IEnvironment.ts index e2128e2e..1bec8d1b 100644 --- a/src/application/Environment/IEnvironment.ts +++ b/src/application/Environment/IEnvironment.ts @@ -1,6 +1,6 @@ import { OperatingSystem } from '@/domain/OperatingSystem'; export interface IEnvironment { - readonly isDesktop: boolean; - readonly os: OperatingSystem; + readonly isDesktop: boolean; + readonly os: OperatingSystem; } diff --git a/src/application/IApplicationFactory.ts b/src/application/IApplicationFactory.ts index 85c3d69d..25d3d71f 100644 --- a/src/application/IApplicationFactory.ts +++ b/src/application/IApplicationFactory.ts @@ -1,5 +1,5 @@ import { IApplication } from '@/domain/IApplication'; export interface IApplicationFactory { - getApp(): Promise; + getApp(): Promise; } diff --git a/src/application/Parser/ApplicationParser.ts b/src/application/Parser/ApplicationParser.ts index 28f6464e..a3502906 100644 --- a/src/application/Parser/ApplicationParser.ts +++ b/src/application/Parser/ApplicationParser.ts @@ -1,38 +1,41 @@ +import { CollectionData } from 'js-yaml-loader!@/*'; import { IApplication } from '@/domain/IApplication'; import { IProjectInformation } from '@/domain/IProjectInformation'; import { ICategoryCollection } from '@/domain/ICategoryCollection'; -import { parseCategoryCollection } from './CategoryCollectionParser'; import WindowsData from 'js-yaml-loader!@/application/collections/windows.yaml'; import MacOsData from 'js-yaml-loader!@/application/collections/macos.yaml'; -import { CollectionData } from 'js-yaml-loader!@/*'; import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser'; import { Application } from '@/domain/Application'; +import { parseCategoryCollection } from './CategoryCollectionParser'; export function parseApplication( - parser = CategoryCollectionParser, - processEnv: NodeJS.ProcessEnv = process.env, - collectionsData = PreParsedCollections): IApplication { - validateCollectionsData(collectionsData); - const information = parseProjectInformation(processEnv); - const collections = collectionsData.map((collection) => parser(collection, information)); - const app = new Application(information, collections); - return app; + parser = CategoryCollectionParser, + processEnv: NodeJS.ProcessEnv = process.env, + collectionsData = PreParsedCollections, +): IApplication { + validateCollectionsData(collectionsData); + const information = parseProjectInformation(processEnv); + const collections = collectionsData.map((collection) => parser(collection, information)); + const app = new Application(information, collections); + return app; } export type CategoryCollectionParserType = (file: CollectionData, info: IProjectInformation) => ICategoryCollection; -const CategoryCollectionParser: CategoryCollectionParserType - = (file, info) => parseCategoryCollection(file, info); +const CategoryCollectionParser: CategoryCollectionParserType = (file, info) => { + return parseCategoryCollection(file, info); +}; -const PreParsedCollections: readonly CollectionData [] - = [ WindowsData, MacOsData ]; +const PreParsedCollections: readonly CollectionData [] = [ + WindowsData, MacOsData, +]; function validateCollectionsData(collections: readonly CollectionData[]) { - if (!collections.length) { - throw new Error('no collection provided'); - } - if (collections.some((collection) => !collection)) { - throw new Error('undefined collection provided'); - } + if (!collections.length) { + throw new Error('no collection provided'); + } + if (collections.some((collection) => !collection)) { + throw new Error('undefined collection provided'); + } } diff --git a/src/application/Parser/CategoryCollectionParser.ts b/src/application/Parser/CategoryCollectionParser.ts index eaef5153..7c0e6ceb 100644 --- a/src/application/Parser/CategoryCollectionParser.ts +++ b/src/application/Parser/CategoryCollectionParser.ts @@ -1,40 +1,42 @@ -import { Category } from '@/domain/Category'; import { CollectionData } from 'js-yaml-loader!@/*'; -import { parseCategory } from './CategoryParser'; +import { Category } from '@/domain/Category'; import { OperatingSystem } from '@/domain/OperatingSystem'; -import { createEnumParser } from '../Common/Enum'; import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { CategoryCollection } from '@/domain/CategoryCollection'; import { IProjectInformation } from '@/domain/IProjectInformation'; +import { createEnumParser } from '../Common/Enum'; +import { parseCategory } from './CategoryParser'; import { CategoryCollectionParseContext } from './Script/CategoryCollectionParseContext'; import { ScriptingDefinitionParser } from './ScriptingDefinition/ScriptingDefinitionParser'; export function parseCategoryCollection( - content: CollectionData, - info: IProjectInformation, - osParser = createEnumParser(OperatingSystem)): ICategoryCollection { - validate(content); - const scripting = new ScriptingDefinitionParser() - .parse(content.scripting, info); - const context = new CategoryCollectionParseContext(content.functions, scripting); - const categories = new Array(); - for (const action of content.actions) { - const category = parseCategory(action, context); - categories.push(category); - } - const os = osParser.parseEnum(content.os, 'os'); - const collection = new CategoryCollection( - os, - categories, - scripting); - return collection; + content: CollectionData, + info: IProjectInformation, + osParser = createEnumParser(OperatingSystem), +): ICategoryCollection { + validate(content); + const scripting = new ScriptingDefinitionParser() + .parse(content.scripting, info); + const context = new CategoryCollectionParseContext(content.functions, scripting); + const categories = new Array(); + for (const action of content.actions) { + const category = parseCategory(action, context); + categories.push(category); + } + const os = osParser.parseEnum(content.os, 'os'); + const collection = new CategoryCollection( + os, + categories, + scripting, + ); + return collection; } function validate(content: CollectionData): void { - if (!content) { - throw new Error('content is null or undefined'); - } - if (!content.actions || content.actions.length <= 0) { - throw new Error('content does not define any action'); - } + if (!content) { + throw new Error('content is null or undefined'); + } + if (!content.actions || content.actions.length <= 0) { + throw new Error('content does not define any action'); + } } diff --git a/src/application/Parser/CategoryParser.ts b/src/application/Parser/CategoryParser.ts index 2ccaadb4..30939f9e 100644 --- a/src/application/Parser/CategoryParser.ts +++ b/src/application/Parser/CategoryParser.ts @@ -1,71 +1,86 @@ -import { CategoryData, ScriptData, CategoryOrScriptData } from 'js-yaml-loader!@/*'; +import { + CategoryData, ScriptData, CategoryOrScriptData, InstructionHolder, +} from 'js-yaml-loader!@/*'; import { Script } from '@/domain/Script'; import { Category } from '@/domain/Category'; import { parseDocUrls } from './DocumentationParser'; import { ICategoryCollectionParseContext } from './Script/ICategoryCollectionParseContext'; import { parseScript } from './Script/ScriptParser'; -let categoryIdCounter: number = 0; +let categoryIdCounter = 0; interface ICategoryChildren { - subCategories: Category[]; - subScripts: Script[]; + subCategories: Category[]; + subScripts: Script[]; } -export function parseCategory(category: CategoryData, context: ICategoryCollectionParseContext): Category { - if (!context) { throw new Error('undefined context'); } - ensureValid(category); - const children: ICategoryChildren = { - subCategories: new Array(), - subScripts: new Array @@ -27,41 +28,40 @@ export default class IconButton extends Vue { @use "@/presentation/assets/styles/main" as *; .button { - display: flex; - flex-direction: column; - align-items: center; + display: flex; + flex-direction: column; + align-items: center; - background-color: $color-secondary; - color: $color-on-secondary; + background-color: $color-secondary; + color: $color-on-secondary; - border: none; - padding:20px; - transition-duration: 0.4s; - overflow: hidden; - box-shadow: 0 3px 9px $color-primary-darkest; - border-radius: 4px; + border: none; + padding:20px; + transition-duration: 0.4s; + overflow: hidden; + box-shadow: 0 3px 9px $color-primary-darkest; + border-radius: 4px; - cursor: pointer; - width: 10%; - min-width: 90px; - &:hover { - background: $color-surface; - box-shadow: 0px 2px 10px 5px $color-secondary; - } - &:hover>&__text { - display: block; - } - &:hover>&__icon { - display: none; - } - &__text { - display: none; - font-family: $font-artistic; - font-size: 1.5em; - color: $color-primary; - font-weight: 500; - line-height: 1.1; - } + cursor: pointer; + width: 10%; + min-width: 90px; + &:hover { + background: $color-surface; + box-shadow: 0px 2px 10px 5px $color-secondary; } - + &:hover>&__text { + display: block; + } + &:hover>&__icon { + display: none; + } + &__text { + display: none; + font-family: $font-artistic; + font-size: 1.5em; + color: $color-primary; + font-weight: 500; + line-height: 1.1; + } +} diff --git a/src/presentation/components/Code/CodeButtons/MacOsInstructions.vue b/src/presentation/components/Code/CodeButtons/MacOsInstructions.vue index 88a80202..f55eae3b 100644 --- a/src/presentation/components/Code/CodeButtons/MacOsInstructions.vue +++ b/src/presentation/components/Code/CodeButtons/MacOsInstructions.vue @@ -1,98 +1,106 @@ @@ -27,11 +31,11 @@ export default class MenuOptionListItem extends Vue { .enabled { cursor: pointer; &:hover { - font-weight:bold; - text-decoration:underline; - } + font-weight:bold; + text-decoration:underline; + } } .disabled { - color: $color-primary-light; + color: $color-primary-light; } - \ No newline at end of file + diff --git a/src/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler.ts b/src/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler.ts index 456f6a30..90d76f57 100644 --- a/src/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler.ts +++ b/src/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler.ts @@ -5,82 +5,85 @@ import { scrambledEqual } from '@/application/Common/Array'; import { ICategoryCollectionState, IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; export enum SelectionType { - Standard, - Strict, - All, - None, - Custom, + Standard, + Strict, + All, + None, + Custom, } export class SelectionTypeHandler { - constructor(private readonly state: ICategoryCollectionState) { - if (!state) { throw new Error('undefined state'); } + constructor(private readonly state: ICategoryCollectionState) { + if (!state) { throw new Error('undefined state'); } + } + + public selectType(type: SelectionType) { + if (type === SelectionType.Custom) { + throw new Error('cannot select custom type'); } - public selectType(type: SelectionType) { - if (type === SelectionType.Custom) { - throw new Error('cannot select custom type'); - } - const selector = selectors.get(type); - selector.select(this.state); - } - public getCurrentSelectionType(): SelectionType { - for (const [type, selector] of Array.from(selectors.entries())) { - if (selector.isSelected(this.state)) { - return type; - } - } - return SelectionType.Custom; + const selector = selectors.get(type); + selector.select(this.state); + } + + public getCurrentSelectionType(): SelectionType { + for (const [type, selector] of Array.from(selectors.entries())) { + if (selector.isSelected(this.state)) { + return type; + } } + return SelectionType.Custom; + } } interface ISingleTypeHandler { - isSelected: (state: IReadOnlyCategoryCollectionState) => boolean; - select: (state: ICategoryCollectionState) => void; + isSelected: (state: IReadOnlyCategoryCollectionState) => boolean; + select: (state: ICategoryCollectionState) => void; } const selectors = new Map([ - [SelectionType.None, { - select: (state) => - state.selection.deselectAll(), - isSelected: (state) => - state.selection.selectedScripts.length === 0, - }], - [SelectionType.Standard, getRecommendationLevelSelector(RecommendationLevel.Standard)], - [SelectionType.Strict, getRecommendationLevelSelector(RecommendationLevel.Strict)], - [SelectionType.All, { - select: (state) => - state.selection.selectAll(), - isSelected: (state) => - state.selection.selectedScripts.length === state.collection.totalScripts, - }], + [SelectionType.None, { + select: (state) => state.selection.deselectAll(), + isSelected: (state) => state.selection.selectedScripts.length === 0, + }], + [SelectionType.Standard, getRecommendationLevelSelector(RecommendationLevel.Standard)], + [SelectionType.Strict, getRecommendationLevelSelector(RecommendationLevel.Strict)], + [SelectionType.All, { + select: (state) => state.selection.selectAll(), + isSelected: (state) => state.selection.selectedScripts.length === state.collection.totalScripts, + }], ]); function getRecommendationLevelSelector(level: RecommendationLevel): ISingleTypeHandler { - return { - select: (state) => selectOnly(level, state), - isSelected: (state) => hasAllSelectedLevelOf(level, state), - }; + return { + select: (state) => selectOnly(level, state), + isSelected: (state) => hasAllSelectedLevelOf(level, state), + }; } -function hasAllSelectedLevelOf(level: RecommendationLevel, state: IReadOnlyCategoryCollectionState) { - const scripts = state.collection.getScriptsByLevel(level); - const selectedScripts = state.selection.selectedScripts; - return areAllSelected(scripts, selectedScripts); +function hasAllSelectedLevelOf( + level: RecommendationLevel, + state: IReadOnlyCategoryCollectionState, +) { + const scripts = state.collection.getScriptsByLevel(level); + const { selectedScripts } = state.selection; + return areAllSelected(scripts, selectedScripts); } function selectOnly(level: RecommendationLevel, state: ICategoryCollectionState) { - const scripts = state.collection.getScriptsByLevel(level); - state.selection.selectOnly(scripts); + const scripts = state.collection.getScriptsByLevel(level); + state.selection.selectOnly(scripts); } function areAllSelected( - expectedScripts: ReadonlyArray, - selection: ReadonlyArray): boolean { - selection = selection.filter((selected) => !selected.revert); - if (expectedScripts.length < selection.length) { - return false; - } - const selectedScriptIds = selection.map((script) => script.id); - const expectedScriptIds = expectedScripts.map((script) => script.id); - return scrambledEqual(selectedScriptIds, expectedScriptIds); + expectedScripts: ReadonlyArray, + selection: ReadonlyArray, +): boolean { + const selectedScriptIds = selection + .filter((selected) => !selected.revert) + .map((script) => script.id); + if (expectedScripts.length < selectedScriptIds.length) { + return false; + } + const expectedScriptIds = expectedScripts.map((script) => script.id); + return scrambledEqual(selectedScriptIds, expectedScriptIds); } diff --git a/src/presentation/components/Scripts/Menu/Selector/TheSelector.vue b/src/presentation/components/Scripts/Menu/Selector/TheSelector.vue index 2053c220..ec57a60a 100644 --- a/src/presentation/components/Scripts/Menu/Selector/TheSelector.vue +++ b/src/presentation/components/Scripts/Menu/Selector/TheSelector.vue @@ -1,46 +1,46 @@ diff --git a/src/presentation/components/Scripts/Menu/TheOsChanger.vue b/src/presentation/components/Scripts/Menu/TheOsChanger.vue index 8bcd4e13..a6ce435f 100644 --- a/src/presentation/components/Scripts/Menu/TheOsChanger.vue +++ b/src/presentation/components/Scripts/Menu/TheOsChanger.vue @@ -1,11 +1,11 @@ @@ -26,6 +26,7 @@ import MenuOptionListItem from './MenuOptionListItem.vue'; }) export default class TheOsChanger extends StatefulVue { public allOses: Array<{ name: string, os: OperatingSystem }> = []; + public currentOs?: OperatingSystem = null; public async created() { @@ -33,6 +34,7 @@ export default class TheOsChanger extends StatefulVue { this.allOses = app.getSupportedOsList() .map((os) => ({ os, name: renderOsName(os) })); } + public async changeOs(newOs: OperatingSystem) { const context = await this.getCurrentContext(); context.changeContext(newOs); @@ -45,11 +47,11 @@ export default class TheOsChanger extends StatefulVue { } function renderOsName(os: OperatingSystem): string { - switch (os) { - case OperatingSystem.Windows: return 'Windows'; - case OperatingSystem.macOS: return 'macOS'; - default: throw new RangeError(`Cannot render os name: ${OperatingSystem[os]}`); - } + switch (os) { + case OperatingSystem.Windows: return 'Windows'; + case OperatingSystem.macOS: return 'macOS'; + default: throw new RangeError(`Cannot render os name: ${OperatingSystem[os]}`); + } } diff --git a/src/presentation/components/Scripts/Menu/TheScriptsMenu.vue b/src/presentation/components/Scripts/Menu/TheScriptsMenu.vue index b7764b0a..2c11e967 100644 --- a/src/presentation/components/Scripts/Menu/TheScriptsMenu.vue +++ b/src/presentation/components/Scripts/Menu/TheScriptsMenu.vue @@ -1,22 +1,22 @@ @@ -63,18 +61,18 @@ $margin-between-lines: 7px; flex-wrap: wrap; margin-top: -$margin-between-lines; .item { - flex: 1; - white-space: nowrap; - display: flex; - justify-content: center; - align-items: center; - margin: $margin-between-lines 5px 0 5px; - &:first-child { - justify-content: flex-start; - } - &:last-child { - justify-content: flex-end; - } + flex: 1; + white-space: nowrap; + display: flex; + justify-content: center; + align-items: center; + margin: $margin-between-lines 5px 0 5px; + &:first-child { + justify-content: flex-start; + } + &:last-child { + justify-content: flex-end; + } } } diff --git a/src/presentation/components/Scripts/Menu/View/TheViewChanger.vue b/src/presentation/components/Scripts/Menu/View/TheViewChanger.vue index c1206fda..0109109d 100644 --- a/src/presentation/components/Scripts/Menu/View/TheViewChanger.vue +++ b/src/presentation/components/Scripts/Menu/View/TheViewChanger.vue @@ -1,21 +1,21 @@ diff --git a/src/presentation/components/Scripts/Menu/View/ViewType.ts b/src/presentation/components/Scripts/Menu/View/ViewType.ts index 68eebb00..c926c591 100644 --- a/src/presentation/components/Scripts/Menu/View/ViewType.ts +++ b/src/presentation/components/Scripts/Menu/View/ViewType.ts @@ -1,4 +1,4 @@ export enum ViewType { - Cards = 1, - Tree = 0, + Cards = 1, + Tree = 0, } diff --git a/src/presentation/components/Scripts/Slider/Handle.vue b/src/presentation/components/Scripts/Slider/Handle.vue index 665953b5..e35881a4 100644 --- a/src/presentation/components/Scripts/Slider/Handle.vue +++ b/src/presentation/components/Scripts/Slider/Handle.vue @@ -1,15 +1,15 @@ @@ -49,27 +52,27 @@ $color : $color-primary-dark; $color-hover : $color-primary; .handle { - user-select: none; - display: flex; - flex-direction: column; - align-items: center; - &:hover { - .line { - background: $color-hover; - } - .image { - color: $color-hover; - } - } + user-select: none; + display: flex; + flex-direction: column; + align-items: center; + &:hover { .line { - flex: 1; - background: $color; - width: 3px; + background: $color-hover; } - .icon { - color: $color; + .image { + color: $color-hover; } - margin-right: 5px; - margin-left: 5px; + } + .line { + flex: 1; + background: $color; + width: 3px; + } + .icon { + color: $color; + } + margin-right: 5px; + margin-left: 5px; } diff --git a/src/presentation/components/Scripts/Slider/HorizontalResizeSlider.vue b/src/presentation/components/Scripts/Slider/HorizontalResizeSlider.vue index 7380fb27..c7f5015a 100644 --- a/src/presentation/components/Scripts/Slider/HorizontalResizeSlider.vue +++ b/src/presentation/components/Scripts/Slider/HorizontalResizeSlider.vue @@ -1,18 +1,18 @@ @@ -43,27 +46,27 @@ export default class HorizontalResizeSlider extends Vue { @use "@/presentation/assets/styles/main" as *; .slider { - display: flex; - flex-direction: row; + display: flex; + flex-direction: row; + .first { + min-width: var(--first-min-width); + width: var(--first-initial-width); + } + .second { + flex: 1; + min-width: var(--second-min-width); + } + @media screen and (max-width: $media-vertical-view-breakpoint) { + flex-direction: column; .first { - min-width: var(--first-min-width); - width: var(--first-initial-width); + width: auto !important; } .second { - flex: 1; - min-width: var(--second-min-width); + margin-top: var(--vertical-margin); } - @media screen and (max-width: $media-vertical-view-breakpoint) { - flex-direction: column; - .first { - width: auto !important; - } - .second { - margin-top: var(--vertical-margin); - } - .handle { - display: none; - } + .handle { + display: none; } + } } diff --git a/src/presentation/components/Scripts/TheScriptArea.vue b/src/presentation/components/Scripts/TheScriptArea.vue index b5cbb6b9..0f7d12b2 100644 --- a/src/presentation/components/Scripts/TheScriptArea.vue +++ b/src/presentation/components/Scripts/TheScriptArea.vue @@ -1,17 +1,17 @@ diff --git a/src/presentation/components/Scripts/View/Cards/CardList.vue b/src/presentation/components/Scripts/View/Cards/CardList.vue index 95aa225a..026a0840 100644 --- a/src/presentation/components/Scripts/View/Cards/CardList.vue +++ b/src/presentation/components/Scripts/View/Cards/CardList.vue @@ -1,16 +1,21 @@ @@ -112,9 +121,10 @@ function isClickable(element: Element) { flex-flow: row wrap; font-family: $font-main; gap: $card-gap; - /* + /* Padding is used to allow scale animation (growing size) for cards on hover. - It ensures that there's room to grow, so the animation is shown without overflowing with scrollbars. + It ensures that there's room to grow, so the animation is shown without overflowing + with scrollbars. */ padding: 10px; } @@ -125,4 +135,4 @@ function isClickable(element: Element) { font-size: 3.5em; font-family: $font-normal; } - \ No newline at end of file + diff --git a/src/presentation/components/Scripts/View/Cards/CardListItem.vue b/src/presentation/components/Scripts/View/Cards/CardListItem.vue index 406537b9..263908db 100644 --- a/src/presentation/components/Scripts/View/Cards/CardListItem.vue +++ b/src/presentation/components/Scripts/View/Cards/CardListItem.vue @@ -1,38 +1,49 @@ - \ No newline at end of file + diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/INode.ts b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/INode.ts index ddd58391..a322dca4 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/INode.ts +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/INode.ts @@ -1,13 +1,13 @@ export enum NodeType { - Script, - Category, + Script, + Category, } export interface INode { - readonly id: string; - readonly text: string; - readonly isReversible: boolean; - readonly documentationUrls: ReadonlyArray; - readonly children?: ReadonlyArray; - readonly type: NodeType; + readonly id: string; + readonly text: string; + readonly isReversible: boolean; + readonly documentationUrls: ReadonlyArray; + readonly children?: ReadonlyArray; + readonly type: NodeType; } diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Node.vue b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Node.vue index 850a2c69..aa690a8d 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Node.vue +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Node.vue @@ -1,36 +1,34 @@ - - \ No newline at end of file + diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/RevertToggle.vue b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/RevertToggle.vue index 57f76a81..4287a0ae 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/RevertToggle.vue +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/RevertToggle.vue @@ -1,55 +1,55 @@ - - \ No newline at end of file + diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/CategoryReverter.ts b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/CategoryReverter.ts index 996bdb16..9bed8b5d 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/CategoryReverter.ts +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/CategoryReverter.ts @@ -1,30 +1,34 @@ -import { IReverter } from './IReverter'; -import { getCategoryId } from '../../../ScriptNodeParser'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; -import { ScriptReverter } from './ScriptReverter'; import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection'; import { ICategoryCollection } from '@/domain/ICategoryCollection'; +import { getCategoryId } from '../../../ScriptNodeParser'; +import { IReverter } from './IReverter'; +import { ScriptReverter } from './ScriptReverter'; export class CategoryReverter implements IReverter { - private readonly categoryId: number; - private readonly scriptReverters: ReadonlyArray; - constructor(nodeId: string, collection: ICategoryCollection) { - this.categoryId = getCategoryId(nodeId); - this.scriptReverters = getAllSubScriptReverters(this.categoryId, collection); - } - public getState(selectedScripts: ReadonlyArray): boolean { - return this.scriptReverters.every((script) => script.getState(selectedScripts)); - } - public selectWithRevertState(newState: boolean, selection: IUserSelection): void { - selection.addOrUpdateAllInCategory(this.categoryId, newState); - } + private readonly categoryId: number; + + private readonly scriptReverters: ReadonlyArray; + + constructor(nodeId: string, collection: ICategoryCollection) { + this.categoryId = getCategoryId(nodeId); + this.scriptReverters = getAllSubScriptReverters(this.categoryId, collection); + } + + public getState(selectedScripts: ReadonlyArray): boolean { + return this.scriptReverters.every((script) => script.getState(selectedScripts)); + } + + public selectWithRevertState(newState: boolean, selection: IUserSelection): void { + selection.addOrUpdateAllInCategory(this.categoryId, newState); + } } function getAllSubScriptReverters(categoryId: number, collection: ICategoryCollection) { - const category = collection.findCategory(categoryId); - if (!category) { - throw new Error(`Category with id "${categoryId}" does not exist`); - } - const scripts = category.getAllScriptsRecursively(); - return scripts.map((script) => new ScriptReverter(script.id)); + const category = collection.findCategory(categoryId); + if (!category) { + throw new Error(`Category with id "${categoryId}" does not exist`); + } + const scripts = category.getAllScriptsRecursively(); + return scripts.map((script) => new ScriptReverter(script.id)); } diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/IReverter.ts b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/IReverter.ts index ba2fa05e..11b70273 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/IReverter.ts +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/IReverter.ts @@ -2,6 +2,6 @@ import { IUserSelection } from '@/application/Context/State/Selection/IUserSelec import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; export interface IReverter { - getState(selectedScripts: ReadonlyArray): boolean; - selectWithRevertState(newState: boolean, selection: IUserSelection): void; + getState(selectedScripts: ReadonlyArray): boolean; + selectWithRevertState(newState: boolean, selection: IUserSelection): void; } diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/ReverterFactory.ts b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/ReverterFactory.ts index 9c8ada7f..5c1839bd 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/ReverterFactory.ts +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/ReverterFactory.ts @@ -1,16 +1,16 @@ +import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { INode, NodeType } from '../INode'; import { IReverter } from './IReverter'; import { ScriptReverter } from './ScriptReverter'; import { CategoryReverter } from './CategoryReverter'; -import { ICategoryCollection } from '@/domain/ICategoryCollection'; export function getReverter(node: INode, collection: ICategoryCollection): IReverter { - switch (node.type) { - case NodeType.Category: - return new CategoryReverter(node.id, collection); - case NodeType.Script: - return new ScriptReverter(node.id); - default: - throw new Error('Unknown script type'); - } + switch (node.type) { + case NodeType.Category: + return new CategoryReverter(node.id, collection); + case NodeType.Script: + return new ScriptReverter(node.id); + default: + throw new Error('Unknown script type'); + } } diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/ScriptReverter.ts b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/ScriptReverter.ts index 2e6e86ba..fd3d8de6 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/ScriptReverter.ts +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/ScriptReverter.ts @@ -1,21 +1,24 @@ -import { IReverter } from './IReverter'; -import { getScriptId } from '../../../ScriptNodeParser'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection'; +import { getScriptId } from '../../../ScriptNodeParser'; +import { IReverter } from './IReverter'; export class ScriptReverter implements IReverter { - private readonly scriptId: string; - constructor(nodeId: string) { - this.scriptId = getScriptId(nodeId); - } - public getState(selectedScripts: ReadonlyArray): boolean { - const selectedScript = selectedScripts.find((selected) => selected.id === this.scriptId); - if (!selectedScript) { - return false; - } - return selectedScript.revert; - } - public selectWithRevertState(newState: boolean, selection: IUserSelection): void { - selection.addOrUpdateSelectedScript(this.scriptId, newState); + private readonly scriptId: string; + + constructor(nodeId: string) { + this.scriptId = getScriptId(nodeId); + } + + public getState(selectedScripts: ReadonlyArray): boolean { + const selectedScript = selectedScripts.find((selected) => selected.id === this.scriptId); + if (!selectedScript) { + return false; } + return selectedScript.revert; + } + + public selectWithRevertState(newState: boolean, selection: IUserSelection): void { + selection.addOrUpdateSelectedScript(this.scriptId, newState); + } } diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/SelectableTree.vue b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/SelectableTree.vue index c376b5f0..294f93ad 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/SelectableTree.vue +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/SelectableTree.vue @@ -1,137 +1,163 @@ diff --git a/src/presentation/components/Scripts/View/TheScriptsView.vue b/src/presentation/components/Scripts/View/TheScriptsView.vue index 3f915875..5643b452 100644 --- a/src/presentation/components/Scripts/View/TheScriptsView.vue +++ b/src/presentation/components/Scripts/View/TheScriptsView.vue @@ -1,38 +1,41 @@ @@ -100,57 +109,58 @@ export default class TheScriptsView extends StatefulVue { $margin-inner: 4px; .scripts { - margin-top: $margin-inner; - @media screen and (min-width: $media-vertical-view-breakpoint) { // so the current code is always visible - overflow: auto; - max-height: 70vh; - } - .tree { - padding-left: 3%; - padding-top: 15px; - padding-bottom: 15px; - &--searching { - padding-top: 0px; - } + margin-top: $margin-inner; + @media screen and (min-width: $media-vertical-view-breakpoint) { + // so the current code is always visible + overflow: auto; + max-height: 70vh; + } + .tree { + padding-left: 3%; + padding-top: 15px; + padding-bottom: 15px; + &--searching { + padding-top: 0px; } + } } .search { + display: flex; + flex-direction: column; + background-color: $color-primary-darker; + &__query { display: flex; + justify-content: center; + flex-direction: row; + align-items: center; + margin-top: 1em; + color: $color-primary; + &__close-button { + cursor: pointer; + font-size: 1.25em; + margin-left: 0.25rem; + &:hover { + color: $color-primary-dark; + } + } + } + &-no-matches { + display:flex; flex-direction: column; - background-color: $color-primary-darker; - &__query { - display: flex; - justify-content: center; - flex-direction: row; - align-items: center; - margin-top: 1em; - color: $color-primary; - &__close-button { - cursor: pointer; - font-size: 1.25em; - margin-left: 0.25rem; - &:hover { - color: $color-primary-dark; - } - } + word-break:break-word; + text-transform: uppercase; + color: $color-on-primary; + font-size: 1.5em; + padding:10px; + text-align:center; + > div { + padding-bottom:13px; } - &-no-matches { - display:flex; - flex-direction: column; - word-break:break-word; - text-transform: uppercase; - color: $color-on-primary; - font-size: 1.5em; - padding:10px; - text-align:center; - > div { - padding-bottom:13px; - } - a { - color: $color-primary; - } + a { + color: $color-primary; } + } } - \ No newline at end of file + diff --git a/src/presentation/components/Shared/Dialog.vue b/src/presentation/components/Shared/Dialog.vue index 99862b6f..11379983 100644 --- a/src/presentation/components/Shared/Dialog.vue +++ b/src/presentation/components/Shared/Dialog.vue @@ -1,12 +1,12 @@