From 70d3d05ffc0f72dcf71cae323339410e8dff77fa Mon Sep 17 00:00:00 2001 From: Srijan Reddy Date: Tue, 20 Dec 2022 07:01:42 +0530 Subject: [PATCH] Feature: Introduce angular cli and ahead of time compilation (#16073) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ng-cli * Successful build * Angular v14 angular.json * AOT working, old build not running * Add webpack module loader * Fix build after merge from develop * Fix prod build * Remove duplicate css and add css styleUrls * Remove repeated assets.ts code file and fix more css files * Fix ng build and add start commands * Add support for production * Fix logic * Add more fixes to wrong html files * Add a mock hashes file to satisfy angular compiler * New fixes for the template errors * Bug fixes * Fix new changes in develop * Fix new changes in develop * Review changes * Fix be tests * Fix mypy types * Fix errors * Lint and backend test fixes * Fix review comments and add tests * Merge from upstream/develop * Fix unit tests * Fix lint errors * Fix tests * Fix tests * Add a deafult lang direction when no lang dir is present in the url * Fix build errors in latest develop * Move document attribution lang setting logic outside the if case * Fix backend tests * Upgrade angular and ts version * yarn lock changes * Fix backend tests * Partially finished backend tests * Downgrade angular back to 11 and move refactored code to ignore list in ts checks * Fix develop * Add files to deny lists * Fix tests * Fix tests again * Fix lint * Fix lint errors * Fix backend tests * Fix errors in ng build prod * Review comments * Fix upstream/develop * Addressing review comments * Fix typings * Add dist to copied files in e2e tests * Fix template urls * Fix multiple browser module imports * Fix backend tests * Fix backend tests * Fix backemd tests * Fix comments * Address review comments * Fix lint and build issues * Address review comments * Add more comments and fix lint * Add comments for declare global * Re-word the comments * Fix lint issues * feconf.py changes for testing aot on backup server * Revert "feconf.py changes for testing aot on backup server" This reverts commit fbaa34dc182a5cf832ae9bce2b57e419efa309dc. * Fix review comments and infinite loop reload * fix hashes * Change import statement to be relative * Increase budgets * Optimize prev lang direction * Add hashes build step for typescript checks and run_frontend_tests and fix codewowners * Fix lint and add types * Fix lint checks * Fix unit tests and lint errors * 🫡 * 🫣 * 🫠 Co-authored-by: Vojtěch Jelínek Co-authored-by: Vojtěch Jelínek --- .github/CODEOWNERS | 9 +- .github/workflows/e2e_tests.yml | 2 +- .gitignore | 4 + ...plate-style-url-replacer.webpack-loader.js | 105 + angular.json | 133 + app_dev.yaml | 5 + core/controllers/base.py | 27 +- core/controllers/base_test.py | 6 +- core/controllers/oppia_root.py | 23 +- core/controllers/oppia_root_test.py | 86 + core/feconf.py | 4 + .../base-components/base-content.component.ts | 5 +- core/templates/base-components/base.module.ts | 2 + .../base-components/oppia-footer.component.ts | 4 +- core/templates/combined-tests.spec.ts | 4 +- .../hint-and-solution-buttons.component.ts | 3 +- .../social-buttons.component.ts | 2 +- .../checkpoint-celebration-modal.component.ts | 1 + .../ck-editor-copy-toolbar.component.ts | 2 +- .../ck-editor-copy-toolbar.module.ts | 41 + .../alert-message.component.ts | 1 - .../attribution-guide.component.ts | 2 +- .../confirm-or-cancel-modal.component.ts | 8 +- .../top-navigation-bar.component.css | 223 +- .../top-navigation-bar.component.html | 4 +- .../top-navigation-bar.component.ts | 2 +- .../image-uploader.component.html | 4 +- .../image-uploader.component.ts | 2 +- .../schema-based-editor.component.html | 10 +- .../schema-based-float-editor.component.html | 4 +- .../schema-based-int-editor.component.html | 2 +- .../schema-based-unicode-editor.component.ts | 5 +- .../oppia-angular-root.component.spec.ts | 17 +- .../oppia-angular-root.component.ts | 43 +- .../question-editor-save-modal.component.html | 8 +- .../question-editor-save-modal.component.ts | 5 +- ...question-misconception-editor.component.ts | 1 + .../skill-mastery.component.html | 2 +- .../merge-skill-modal.component.html | 1 - .../skill-selector.component.ts | 2 +- .../response-header.component.ts | 2 +- .../solution-editor.component.html | 3 +- .../solution-editor.component.ts | 2 +- .../state-content-editor.component.ts | 2 +- .../state-skill-editor.component.ts | 2 +- .../exploration-summary-tile.component.ts | 1 + .../learner-story-summary-tile.component.ts | 3 +- ...rner-topic-goals-summary-tile.component.ts | 2 +- .../learner-topic-summary-tile.component.ts | 3 +- .../story-summary-tile.component.ts | 3 +- .../summary-tile/summary-tile.module.ts | 44 + .../url-interpolation.service.spec.ts | 14 +- .../utilities/url-interpolation.service.ts | 3 +- core/templates/i18n/i18n.service.spec.ts | 164 +- core/templates/i18n/i18n.service.ts | 59 +- .../about-foundation-page.component.html | 4 +- .../pages/about-page/about-page.component.ts | 3 +- .../admin-blog-admin-common.module.ts | 43 + .../pages/admin-page/admin-page.module.ts | 5 +- .../misc-tab/admin-misc-tab.component.html | 1 - .../navbar/admin-navbar.component.html | 18 +- ...oles-and-actions-visualizer.component.html | 6 +- .../android-page/android-page.component.ts | 3 +- .../blog-admin-page/blog-admin-page.module.ts | 9 +- .../navbar/blog-admin-navbar.component.html | 12 +- .../blog-author-profile-page.module.ts | 9 +- .../blog-dashboard-page.module.ts | 31 +- .../shared-blog-components.module.ts | 80 + .../blog-home-page/blog-home-page.module.ts | 5 +- .../tag-filter/tag-filter.component.html | 3 +- .../blog-post-page/blog-post-page.module.ts | 9 +- .../create-new-classroom-modal.component.ts | 2 +- .../classroom-admin-navbar.component.html | 9 +- .../classroom-page.component.ts | 5 +- .../classroom-page/classroom-page.module.ts | 7 +- .../collection-editor-page.module.ts | 2 + ...tion-editor-pre-publish-modal.component.ts | 2 +- ...ollection-editor-save-modal.component.html | 2 +- .../collection-editor-save-modal.component.ts | 3 +- .../collection-footer.component.ts | 2 +- .../collection-player-page.component.ts | 3 +- core/templates/pages/common-imports.ts | 2 +- ...utor-dashboard-admin-navbar.component.html | 15 +- .../contributor-dashboard-page.module.ts | 9 +- .../translation-modal.component.ts | 2 +- ...ion-suggestion-review-modal.component.html | 2 +- .../opportunities-list-item.component.ts | 2 + .../creator-dashboard-page.component.html | 18 +- .../diagnostic-test-player-page.module.ts | 151 +- .../email-dashboard-result.component.html | 1 - .../error-pages/error-page.component.html | 8 +- .../editor-navbar-breadcrumb.component.ts | 3 +- .../exploration-editor-tab.component.ts | 4 +- .../state-graph-visualization.component.ts | 3 +- .../state-name-editor.component.ts | 2 +- ...customize-interaction-modal.component.html | 1 - .../customize-interaction-modal.component.ts | 2 +- .../exploration-editor-page.component.ts | 4 +- .../exploration-editor-page.module.ts | 10 +- .../exploration-objective-editor.component.ts | 2 +- .../exploration-title-editor.component.html | 2 +- .../exploration-title-editor.component.ts | 2 +- .../history-tab/history-tab.component.html | 5 - .../exploration-metadata-modal.component.ts | 3 +- .../exploration-save-modal.component.ts | 1 + .../welcome-modal.component.html | 2 +- .../param-changes-editor.component.ts | 2 +- .../services/exploration-objective.service.ts | 1 + .../services/exploration-property.service.ts | 6 +- .../services/exploration-title.service.ts | 2 +- .../settings-tab/settings-tab.component.ts | 8 +- .../statistics-tab.component.html | 2 +- .../audio-translation-bar.component.ts | 5 +- .../services/voiceover-recording.service.ts | 2 +- .../exploration-player-page.module.ts | 8 +- ...exploration-player-viewer-common.module.ts | 41 + .../exploration-footer.component.ts | 3 +- .../learner-view-info.component.ts | 3 +- .../progress-nav.component.ts | 1 + .../conversation-skin.component.ts | 5 +- .../end-chapter-check-mark.component.ts | 2 +- .../end-chapter-confetti.component.ts | 2 +- .../input-response-pair.component.ts | 3 +- .../ratings-and-recommendations.component.ts | 4 +- .../supplemental-card.component.ts | 3 +- .../tutor-card.component.html | 3 +- .../tutor-card.component.ts | 3 +- ...lesson-information-card-modal.component.ts | 3 +- .../progress-reminder-modal.component.ts | 3 +- .../facilitator-dashboard-page.component.ts | 1 + core/templates/pages/footer_js_libs.html | 3 + core/templates/pages/header_css_libs.html | 3 + core/templates/pages/header_js_libs.html | 3 + .../community-lessons-tab.component.ts | 7 +- .../goals-tab.component.ts | 7 +- .../home-tab.component.ts | 3 +- .../learner-dashboard-page.component.ts | 2 +- .../add-syllabus-items.component.html | 14 +- .../add-syllabus-items.component.ts | 4 +- .../create-learner-group-page.component.ts | 3 +- .../create-learner-group-page.module.ts | 17 +- .../invite-learners.component.html | 2 +- .../create-group/invite-learners.component.ts | 3 +- .../learner-group-details.component.ts | 3 +- .../edit-learner-group-page.component.ts | 3 +- .../edit-learner-group-page.module.ts | 25 +- ...oup-learner-specific-progress.component.ts | 3 +- ...arner-group-learners-progress.component.ts | 3 +- .../learner-group-overview.component.ts | 3 +- .../learner-group-preferences.component.ts | 3 +- .../learner-group-syllabus.component.ts | 3 +- .../shared-learner-group-component.module.ts | 55 + .../view-learner-group-page.module.ts | 8 +- .../library-page/library-page.component.ts | 3 +- .../search-bar/search-bar.component.html | 8 +- .../search-bar/search-bar.component.ts | 5 +- .../playbook.component.ts | 2 +- .../partnerships-page.component.html | 2 +- .../preferences-page.component.ts | 3 +- .../privacy-page/privacy-page.component.html | 16 +- .../profile-page-navbar.component.ts | 2 +- .../profile-page/profile-page.component.html | 1 - .../profile-page/profile-page.component.ts | 3 +- .../release-coordinator-navbar.component.html | 12 +- .../skill-description-editor.component.html | 2 +- .../delete-misconception-modal.component.ts | 2 +- .../navbar/skill-editor-navbar.component.html | 3 +- .../skill-editor-page.component.ts | 1 + .../skill-preview-tab.component.ts | 3 +- .../splash-page/splash-page.component.ts | 4 +- .../story-viewer-page-root.component.ts | 1 + .../story-viewer-page.component.ts | 3 +- .../subtopic-viewer-page.component.ts | 2 +- ...-skill-and-difficulty-modal.component.html | 5 +- ...ct-skill-and-difficulty-modal.component.ts | 6 + ...ies-select-difficulty-modal.component.html | 2 +- ...ities-select-difficulty-modal.component.ts | 6 + .../topic-editor-save-modal.component.html | 2 +- .../topic-editor-save-modal.component.ts | 1 + .../topic-editor-page.module.ts | 11 +- .../practice-tab/practice-tab.component.ts | 2 +- .../topic-viewer-stories-list.component.ts | 2 +- .../subtopics-list.component.ts | 2 +- .../topic-viewer-page.component.ts | 2 +- .../topic-viewer-page.module.ts | 10 +- .../topic-viewer-player-common.module.ts | 49 + .../skills-list/skills-list.component.html | 2 +- .../volunteer-page.component.html | 4 +- .../volunteer-page.component.ts | 31 +- core/templates/services/UpgradedServices.ts | 2 +- .../i18n-language-code.service.spec.ts | 2 + .../services/i18n-language-code.service.ts | 2 + .../tests/unit-test-utils.ajs.spec.ts | 46 - core/templates/tests/unit-test-utils.ajs.ts | 121 - .../third-party-imports/gif-frames.import.ts | 2 +- .../third-party-imports/guppy.import.ts | 3 +- core/templates/utility/hashes.ts | 33 + core/tests/karma.conf.ts | 5 +- core/tests/test_utils.py | 10 +- ...gebraic-expression-input.component.spec.ts | 11 +- .../oppia-interactive-code-repl.component.ts | 2 +- .../oppia-response-code-repl.component.ts | 5 +- ...short-response-code-repl.component.spec.ts | 2 +- ...ppia-short-response-code-repl.component.ts | 7 +- .../directives/graph-viz.component.html | 2 +- .../oppia-response-graph-input.component.ts | 2 +- ...ppia-response-interactive-map.component.ts | 5 +- ...tive-math-equation-input.component.spec.ts | 11 +- ...numeric-expression-input.component.spec.ts | 11 +- ...-code-reset-confirmation.component.spec.ts | 2 +- ...encil-code-reset-confirmation.component.ts | 4 +- ...ebraic-expression-editor.component.spec.ts | 11 +- .../allowed-variables-editor.component.ts | 6 +- .../templates/image-editor.component.ts | 26 +- .../math-equation-editor.component.spec.ts | 11 +- ...ath-expression-content-editor.component.ts | 6 +- ...umeric-expression-editor.component.spec.ts | 11 +- .../position-of-terms-editor.component.ts | 2 +- .../skill-selector-editor.component.ts | 3 +- .../subtitled-unicode-editor.component.html | 2 +- ...ia-visualization-sorted-tiles.component.ts | 1 + package.json | 13 +- proxy.conf.json | 6 + scripts/build.py | 15 + scripts/check_frontend_test_coverage.py | 4 + scripts/common.py | 23 +- scripts/common_test.py | 104 + scripts/linters/js_ts_linter.py | 2 +- scripts/run_e2e_tests.py | 1 + scripts/run_frontend_tests.py | 4 +- scripts/run_lighthouse_tests.py | 1 + scripts/run_lighthouse_tests_test.py | 16 +- scripts/servers.py | 69 +- scripts/servers_test.py | 57 +- scripts/start.py | 4 + scripts/start_test.py | 41 +- scripts/typescript_checks.py | 82 +- src/environments/environment.prod.ts | 21 + src/environments/environment.ts | 35 + src/index.html | 37 + src/index.prod.html | 37 + src/main.ts | 35 + tsconfig-lint.json | 125 + tsconfig.json | 16 +- typings/custom-window-defs.d.ts | 2 + webpack.common.config.ts | 9 +- webpack.common.macros.ts | 2 +- webpack.dev.config.ts | 2 +- webpack.dev.sourcemap.config.ts | 2 +- webpack.prod.config.ts | 10 +- webpack.prod.sourcemap.config.ts | 2 +- yarn.lock | 7126 ++++++++++++----- 252 files changed, 7283 insertions(+), 3155 deletions(-) create mode 100644 angular-template-style-url-replacer.webpack-loader.js create mode 100644 angular.json create mode 100644 core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.module.ts create mode 100644 core/templates/components/summary-tile/summary-tile.module.ts create mode 100644 core/templates/pages/admin-page/admin-blog-admin-common.module.ts create mode 100644 core/templates/pages/blog-dashboard-page/shared-blog-components.module.ts create mode 100644 core/templates/pages/exploration-player-page/exploration-player-viewer-common.module.ts create mode 100644 core/templates/pages/learner-group-pages/shared-learner-group-component.module.ts create mode 100644 core/templates/pages/topic-viewer-page/topic-viewer-player-common.module.ts delete mode 100644 core/templates/tests/unit-test-utils.ajs.spec.ts create mode 100644 core/templates/utility/hashes.ts create mode 100644 proxy.conf.json create mode 100644 src/environments/environment.prod.ts create mode 100644 src/environments/environment.ts create mode 100644 src/index.html create mode 100644 src/index.prod.html create mode 100644 src/main.ts create mode 100644 tsconfig-lint.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4182b822deeb..4479935607b9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -68,12 +68,16 @@ /core/templates/services/request-interceptor.service*.ts @oppia/angular-migration-reviewers /core/templates/services/UpgradedServices.ts @oppia/angular-migration-reviewers /core/templates/hybrid-router-module-provider*.ts @oppia/angular-migration-reviewers +/core/templates/utility/hashes.ts @oppia/angular-migration-reviewers /core/templates/utility/string-utility*.ts @oppia/angular-migration-reviewers +/proxy.conf.json @oppia/angular-migration-reviewers +/angular-template-style-url-replacer.webpack-loader.js @oppia/angular-migration-reviewers +/angular.json @oppia/angular-migration-reviewers +/src @oppia/frontend-infrastructure-reviewers # TS typing /typings/ @oppia/data-and-stability-reviewers -/tsconfig.json @oppia/data-and-stability-reviewers -/tsconfig-strict.json @oppia/data-and-stability-reviewers +/tsconfig*.json @oppia/data-and-stability-reviewers /scripts/typescript_checks*.py @oppia/data-and-stability-reviewers @@ -573,6 +577,7 @@ /core/templates/components/ck-editor-helpers/ck-editor-copy-content.service*.ts @oppia/interaction-and-rte-reviewers /core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.component.html @oppia/interaction-and-rte-reviewers /core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.component*.ts @oppia/interaction-and-rte-reviewers +/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.module.ts @oppia/interaction-and-rte-reviewers /core/templates/services/autoplayed-videos.service*.ts @oppia/interaction-and-rte-reviewers /core/templates/services/external-save.service*.ts @oppia/angular-migration-reviewers /core/templates/services/external-rte-save.service*.ts @oppia/angular-migration-reviewers diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index 56685545f7dc..7f1d05f9bc73 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -59,7 +59,7 @@ jobs: # We avoid using ../ or absolute paths because unzip treats these as # security issues and will refuse to follow them. run: | - zip -rqy build_files.zip oppia/third_party oppia_tools oppia/build oppia/webpack_bundles oppia/proto_files oppia/app.yaml oppia/assets/hashes.json oppia/proto_files oppia/extensions/classifiers/proto/* oppia/backend_prod_files + zip -rqy build_files.zip oppia/third_party oppia_tools oppia/build oppia/webpack_bundles oppia/proto_files oppia/app.yaml oppia/assets/hashes.json oppia/proto_files oppia/extensions/classifiers/proto/* oppia/backend_prod_files oppia/dist working-directory: /home/runner/work/oppia - name: Upload build files artifact uses: actions/upload-artifact@v3 diff --git a/.gitignore b/.gitignore index b48eaba4f15b..9e67b269c21d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,10 @@ core/templates/prod/* webpack_bundles/* core/tests/.browserstack.env node_modules/* +# .angular is temp folder used by angular to put logs and other artefacts. +.angular/* +# dist is our angular build folder output. +dist/* .coverage* !.coveragerc coverage.xml diff --git a/angular-template-style-url-replacer.webpack-loader.js b/angular-template-style-url-replacer.webpack-loader.js new file mode 100644 index 000000000000..23ba52203417 --- /dev/null +++ b/angular-template-style-url-replacer.webpack-loader.js @@ -0,0 +1,105 @@ +// Copyright 2021 The Oppia Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview This is a webpack loader that replaces templateUrl: './html' + * with template: require('./html'). This is needed for our webpack based + * compilation and not the angular compiler. Angular compiler parses the html + * and converts it to js instructions. For the style urls, the angular compiler + * uses styleUrls while webpack uses imports. Hence, currently we put the + * stylesheet as import and as a styleUrl in the component. Once we have moved + * away from separate rtl css files, we will remove the import statements and + * just keep styleUrls. Until then, for webpack, we need remove styleUrls for + * webpack compilation. + */ + +/** + * The regexes are trying to find the templateUrl from the component decorator + * Eg: + * @Component({ + * selector: 'oppia-base-content', + * templateUrl: './base-content.component.html', + * styleUrls: ['./base-content.component.css'] + * }) + * + * From the above we need to get './base-content.component.html' and + * ['./base-content.component.css']. + * + * After modifications, it will look like: + * @Component({ + * selector: 'oppia-base-content', + * template: require('./base-content.component.html'), + * styleUrls: [] + * }) + * Templates can be found using the regex: + * templateUrl[any number of spaces]:[any number of spaces] + * [any of '"` that starts a string in javascript] + * [match all characters between the quotes][End quotes '"`] + * [any number of spaces] + * [ + * ends with a comma or a closing curly bracket depending or wether there are + * more items in the decorator or not + * ] + */ +const TEMPLATE_URL_REGEX = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*([,}]))/gm; +const STYLES_URL_REGEX = /styleUrls *:(\s*\[[^\]]*?\])/g; +const VALUE_REGEX = /(['`"])((?:[^\\]\\\1|.)*?)\1/g; + +/** + * This function is only used for templateUrl modifications. From a string this + * function extracts the first value inside quotes ('"`). + * Example: For a string like: "templateUrl: './base-content.component.html'," + * The VALUE_REGEX will match "'./base-content.component.html'" and the first + * group is the quote ("'") and the second group is + * ""./base-content.component.html" + * @param {string} str + * @returns Relative url + */ +const replaceStringsWithRequiresStatement = (str) => { + return str.replace(VALUE_REGEX, function(_, __, url) { + return "require('" + url + "')"; + }); +}; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +const loader = (sourceString) => { + // https://webpack.js.org/api/loaders/#thiscacheable + // Cacheable is an interface provided by webpack and is used to speed up + // the build by caching results. + this.cacheable && this.cacheable(); + /** + * The templateURL regex will match something like: + * "templateUrl: './base-content.component.html'," + */ + const newSourceString = sourceString.replace( + TEMPLATE_URL_REGEX, (_, url) => { + return 'template:' + replaceStringsWithRequiresStatement(url); + } + ).replace(STYLES_URL_REGEX, () => { + /** + * For the style urls, the angular compiler + * uses styleUrls while webpack uses imports. Hence, currently we put the + * stylesheet as import and as a styleUrl in the component. Once we have + * moved away from separate rtl css files, we will remove the import + * statements and just keep styleUrls. Until then, for webpack, we need + * remove styleUrls property for webpack compilation. + */ + return 'styleUrl: []'; + }); + + return newSourceString; +}; + + +module.exports = loader; diff --git a/angular.json b/angular.json new file mode 100644 index 000000000000..f35e89db6fd4 --- /dev/null +++ b/angular.json @@ -0,0 +1,133 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "oppia-angular": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "allowedCommonJsDependencies": ["path", "stream", "zlib"], + "outputPath": "dist/oppia-angular", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "core/templates/Polyfills.ts", + "tsConfig": "tsconfig.json", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "core/templates/css/oppia.css", + "core/templates/css/oppia-material.css" + ], + "scripts": [] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "index": { + "input": "src/index.prod.html", + "output": "index.html" + }, + "baseHref": "/dist/oppia-angular-prod/", + "outputPath": "dist/oppia-angular-prod", + "aot": true, + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "2mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb", + "maximumError": "28kb" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "oppia-angular:build" + }, + "configurations": { + "production": { + "browserTarget": "oppia-angular:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "oppia-angular:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "core/templates/Polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "assets": [ + "src/favicon.ico", + "assets" + ], + "styles": [ + ], + "scripts": [] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "tsconfig.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + }, + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "oppia-angular:serve" + }, + "configurations": { + "production": { + "devServerTarget": "oppia-angular:serve:production" + } + } + } + } + } + }, + "defaultProject": "oppia-angular" +} diff --git a/app_dev.yaml b/app_dev.yaml index 4db8887d5302..6acabd1322b8 100644 --- a/app_dev.yaml +++ b/app_dev.yaml @@ -47,6 +47,11 @@ handlers: static_dir: webpack_bundles secure: always expiration: "0" +# /dist is the build output folder for angular. +- url: /dist + static_dir: dist/ + secure: always + expiration: "0" - url: /assets static_dir: assets secure: always diff --git a/core/controllers/base.py b/core/controllers/base.py index 186a55fde199..3ccbe4e1794b 100755 --- a/core/controllers/base.py +++ b/core/controllers/base.py @@ -79,16 +79,26 @@ class ResponseValueDict(TypedDict): @functools.lru_cache(maxsize=128) -def load_template(filename: str) -> str: +def load_template( + filename: str, *, template_is_aot_compiled: bool +) -> str: """Return the HTML file contents at filepath. Args: filename: str. Name of the requested HTML file. + template_is_aot_compiled: bool. Used to determine which bundle to use. Returns: str. The HTML file content. """ - filepath = os.path.join(feconf.FRONTEND_TEMPLATES_DIR, filename) + filepath = os.path.join( + ( + feconf.FRONTEND_AOT_DIR + if template_is_aot_compiled + else feconf.FRONTEND_TEMPLATES_DIR + ), + filename + ) with utils.open_file(filepath, 'r') as f: html_text = f.read() return html_text @@ -647,7 +657,11 @@ def render_downloadable_file( super(webapp2.Response, self.response).write(file.getvalue()) # type: ignore[misc] # pylint: disable=bad-super-call def render_template( - self, filepath: str, iframe_restriction: Optional[str] = 'DENY' + self, + filepath: str, + iframe_restriction: Optional[str] = 'DENY', + *, + template_is_aot_compiled: bool = False ) -> None: """Prepares an HTML response to be sent to the client. @@ -659,6 +673,8 @@ def render_template( DENY: Strictly prevents the template to load in an iframe. SAMEORIGIN: The template can only be displayed in a frame on the same origin as the page itself. + template_is_aot_compiled: bool. False by default. Use + True when the template is compiled by angular AoT compiler. Raises: Exception. Invalid X-Frame-Options. @@ -683,8 +699,9 @@ def render_template( self.response.expires = 'Mon, 01 Jan 1990 00:00:00 GMT' self.response.pragma = 'no-cache' - - self.response.write(load_template(filepath)) + self.response.write(load_template( + filepath, template_is_aot_compiled=template_is_aot_compiled + )) def _render_exception_json_or_html( self, return_type: str, values: ResponseValueDict diff --git a/core/controllers/base_test.py b/core/controllers/base_test.py index 9c4c1a46b93b..cac453e6e1da 100644 --- a/core/controllers/base_test.py +++ b/core/controllers/base_test.py @@ -73,7 +73,11 @@ def test_load_template(self) -> None: with self.swap(feconf, 'FRONTEND_TEMPLATES_DIR', oppia_root_path): self.assertIn( '"Loading | Oppia"', - base.load_template('oppia-root.mainpage.html')) + base.load_template( + 'oppia-root.mainpage.html', + template_is_aot_compiled=False + ) + ) class UniqueTemplateNamesTests(test_utils.GenericTestBase): diff --git a/core/controllers/oppia_root.py b/core/controllers/oppia_root.py index b5d2dcba23c1..ff6b3572ea50 100644 --- a/core/controllers/oppia_root.py +++ b/core/controllers/oppia_root.py @@ -53,4 +53,25 @@ class OppiaLightweightRootPage( @acl_decorators.open_access def get(self, **kwargs: Dict[str, str]) -> None: """Handles GET requests.""" - self.render_template('lightweight-oppia-root.mainpage.html') + # The following logic determines which bundle to return. Currently the + # AoT bundle doesn't support rtl languages yet. So we switch between + # AoT and webpack bundle based on language direction. + # The order of preference to determine the language direction is: + # 1. Cookies + # 2. Url params + # In the case we don't find a language direction from the above two, + # we default to AoT bundle. + # TODO(#16300): Refactor the RTL css generation to add RTL CSS to the + # original CSS files instead of creating a new rtl CSS file + # NOTE: After the aforementioned issue is solved, the AoT bundle will be + # the only bundle that is returned. + if self.request.cookies.get('dir') == 'rtl': + self.render_template('lightweight-oppia-root.mainpage.html') + return + if self.request.cookies.get('dir') == 'ltr': + self.render_template('index.html', template_is_aot_compiled=True) + return + if self.request.get('dir') == 'rtl': + self.render_template('lightweight-oppia-root.mainpage.html') + return + self.render_template('index.html', template_is_aot_compiled=True) diff --git a/core/controllers/oppia_root_test.py b/core/controllers/oppia_root_test.py index 102a61501e8a..9b46c20d1bd7 100644 --- a/core/controllers/oppia_root_test.py +++ b/core/controllers/oppia_root_test.py @@ -33,3 +33,89 @@ def test_oppia_root_page(self) -> None: '') else: response.mustcontain('') + + +class OppiaLightweightRootPageTests(test_utils.GenericTestBase): + + def test_oppia_lightweight_root_page(self) -> None: + response = self.get_html_response('/', expected_status_int=200) + response.mustcontain( + '', + 'Loading | Oppia' + ) + + def test_oppia_lightweight_root_page_with_rtl_lang_param(self) -> None: + response = self.get_html_response('/?dir=rtl', expected_status_int=200) + response.mustcontain( + '', + no='Loading | Oppia' + ) + + def test_oppia_lightweight_root_page_with_ltr_lang_param(self) -> None: + response = self.get_html_response('/?dir=ltr', expected_status_int=200) + response.mustcontain( + '', + 'Loading | Oppia' + ) + + def test_oppia_lightweight_root_page_with_rtl_dir_cookie(self) -> None: + self.testapp.set_cookie('dir', 'rtl') + response = self.get_html_response('/', expected_status_int=200) + response.mustcontain( + '', + no='Loading | Oppia' + ) + + def test_oppia_lightweight_root_page_with_ltr_dir_cookie(self) -> None: + self.testapp.set_cookie('dir', 'ltr') + response = self.get_html_response('/', expected_status_int=200) + response.mustcontain( + '', + 'Loading | Oppia' + ) + + def test_return_bundle_modifier_precedence(self) -> None: + # In case of conflicting cookie and url param values for dir, cookie + # is preferred. + self.testapp.set_cookie('dir', 'ltr') + response = self.get_html_response('/?dir=rtl', expected_status_int=200) + response.mustcontain( + '', + 'Loading | Oppia' + ) + + self.testapp.set_cookie('dir', 'rtl') + response = self.get_html_response('/?dir=ltr', expected_status_int=200) + response.mustcontain( + '', + no='Loading | Oppia' + ) + + def test_invalid_bundle_modifier_values(self) -> None: + # In case of invalid values in cookie but valid query param respect the + # param value for dir. + self.testapp.set_cookie('dir', 'new_hacker_in_the_block') + response = self.get_html_response('/?dir=rtl', expected_status_int=200) + response.mustcontain( + '', + no='Loading | Oppia' + ) + + self.testapp.set_cookie('dir', 'new_hacker_in_the_block') + response = self.get_html_response('/?dir=ltr', expected_status_int=200) + response.mustcontain( + '', + 'Loading | Oppia' + ) + # The bundle modifier precedence guarantees that a valid cookie dir + # value will return the correct bundle. + + # When both modifiers are invalid, default to AoT bundle. + self.testapp.set_cookie('dir', 'new_hacker_in_the_block') + response = self.get_html_response( + '/?dir=is_trying_out', expected_status_int=200 + ) + response.mustcontain( + '', + 'Loading | Oppia' + ) diff --git a/core/feconf.py b/core/feconf.py index b47483b4b94c..ec36d072494b 100644 --- a/core/feconf.py +++ b/core/feconf.py @@ -112,6 +112,10 @@ def check_dev_mode_is_true() -> None: FRONTEND_TEMPLATES_DIR = ( os.path.join('webpack_bundles') if constants.DEV_MODE else os.path.join('build', 'webpack_bundles')) +# To know more about AOT visit https://angular.io/guide/glossary#aot +FRONTEND_AOT_DIR = ( + os.path.join('dist', 'oppia-angular') if constants.DEV_MODE else + os.path.join('dist', 'oppia-angular-prod')) DEPENDENCIES_TEMPLATES_DIR = ( os.path.join(EXTENSIONS_DIR_PREFIX, 'extensions', 'dependencies')) diff --git a/core/templates/base-components/base-content.component.ts b/core/templates/base-components/base-content.component.ts index afd1ddca9b37..be81871eccef 100644 --- a/core/templates/base-components/base-content.component.ts +++ b/core/templates/base-components/base-content.component.ts @@ -31,13 +31,12 @@ import { SidebarStatusService } from 'services/sidebar-status.service'; import { BackgroundMaskService } from 'services/stateful/background-mask.service'; import { I18nLanguageCodeService } from 'services/i18n-language-code.service'; import { NavigationEnd, Router } from '@angular/router'; - import './base-content.component.css'; - @Component({ selector: 'oppia-base-content', - templateUrl: './base-content.component.html' + templateUrl: './base-content.component.html', + styleUrls: ['./base-content.component.css'] }) export class BaseContentComponent { loadingMessage: string = ''; diff --git a/core/templates/base-components/base.module.ts b/core/templates/base-components/base.module.ts index d131e91e227a..95bd8cfb5024 100644 --- a/core/templates/base-components/base.module.ts +++ b/core/templates/base-components/base.module.ts @@ -48,6 +48,7 @@ import { // Miscellaneous. import { SmartRouterModule } from 'hybrid-router-module-provider'; import { OppiaAngularRootComponent } from 'components/oppia-angular-root.component'; +import { NgBootstrapModule } from 'modules/ng-boostrap.module'; @NgModule({ imports: [ @@ -55,6 +56,7 @@ import { OppiaAngularRootComponent } from 'components/oppia-angular-root.compone CookieModule.forChild(), DirectivesModule, I18nModule, + NgBootstrapModule, // TODO(#13443): Remove smart router module provider once all pages are // migrated to angular router. SmartRouterModule, diff --git a/core/templates/base-components/oppia-footer.component.ts b/core/templates/base-components/oppia-footer.component.ts index c453ba8ba1fa..64bc64e1a9a8 100644 --- a/core/templates/base-components/oppia-footer.component.ts +++ b/core/templates/base-components/oppia-footer.component.ts @@ -21,13 +21,13 @@ import { downgradeComponent } from '@angular/upgrade/static'; import { PlatformFeatureService } from 'services/platform-feature.service'; import { AppConstants } from 'app.constants'; - import './oppia-footer.component.css'; @Component({ selector: 'oppia-footer', - templateUrl: './oppia-footer.component.html' + templateUrl: './oppia-footer.component.html', + styleUrls: ['./oppia-footer.component.css'] }) export class OppiaFooterComponent { siteFeedbackFormUrl: string = AppConstants.SITE_FEEDBACK_FORM_URL; diff --git a/core/templates/combined-tests.spec.ts b/core/templates/combined-tests.spec.ts index d9bf586c2ddc..b7524998023a 100644 --- a/core/templates/combined-tests.spec.ts +++ b/core/templates/combined-tests.spec.ts @@ -43,8 +43,8 @@ import { // https://webpack.js.org/guides/dependency-management/#context-module-api // as a reference. interface RequireContext { - context( - directory: string, useSubdirectories: boolean, regExp: RegExp): Context; + context: ( + directory: string, useSubdirectories: boolean, regExp: RegExp) => Context; } interface Context { diff --git a/core/templates/components/button-directives/hint-and-solution-buttons.component.ts b/core/templates/components/button-directives/hint-and-solution-buttons.component.ts index 49447ddd6b2a..f2a0a10a5d8a 100644 --- a/core/templates/components/button-directives/hint-and-solution-buttons.component.ts +++ b/core/templates/components/button-directives/hint-and-solution-buttons.component.ts @@ -34,7 +34,8 @@ import './hint-and-solution-buttons.component.css'; @Component({ selector: 'oppia-hint-and-solution-buttons', - templateUrl: './hint-and-solution-buttons.component.html' + templateUrl: './hint-and-solution-buttons.component.html', + styleUrls: ['./hint-and-solution-buttons.component.css'] }) export class HintAndSolutionButtonsComponent implements OnInit, OnDestroy { directiveSubscriptions = new Subscription(); diff --git a/core/templates/components/button-directives/social-buttons.component.ts b/core/templates/components/button-directives/social-buttons.component.ts index 2a16d468246e..ed73584b452f 100644 --- a/core/templates/components/button-directives/social-buttons.component.ts +++ b/core/templates/components/button-directives/social-buttons.component.ts @@ -26,7 +26,7 @@ import './social-buttons.component.css'; @Component({ selector: 'oppia-social-buttons', templateUrl: './social-buttons.component.html', - styleUrls: [] + styleUrls: ['./social-buttons.component.css'] }) export class SocialButtonsComponent { androidAppButtonIsShown = ( diff --git a/core/templates/components/checkpoint-celebration-modal/checkpoint-celebration-modal.component.ts b/core/templates/components/checkpoint-celebration-modal/checkpoint-celebration-modal.component.ts index 699350bed95a..9514c7a83cb0 100644 --- a/core/templates/components/checkpoint-celebration-modal/checkpoint-celebration-modal.component.ts +++ b/core/templates/components/checkpoint-celebration-modal/checkpoint-celebration-modal.component.ts @@ -44,6 +44,7 @@ const SCREEN_WIDTH_FOR_STANDARD_SIZED_MESSAGE_MODAL_CUTOFF_PX = 1370; @Component({ selector: 'oppia-checkpoint-celebration-modal', templateUrl: './checkpoint-celebration-modal.component.html', + styleUrls: ['./checkpoint-celebration-modal.component.css'] }) export class CheckpointCelebrationModalComponent implements OnInit, OnDestroy { @ViewChild('checkpointCelebrationModalTimer') checkpointTimerTemplateRef!: diff --git a/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.component.ts b/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.component.ts index ce1d6afeff7e..f75071e777eb 100644 --- a/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.component.ts +++ b/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.component.ts @@ -30,7 +30,7 @@ import { CkEditorCopyContentService } from }) export class CkEditorCopyToolbarComponent { constructor( - private ckEditorCopyContentService: CkEditorCopyContentService, + public ckEditorCopyContentService: CkEditorCopyContentService, @Inject(DOCUMENT) private document: Document ) { ckEditorCopyContentService.copyModeActive = false; diff --git a/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.module.ts b/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.module.ts new file mode 100644 index 000000000000..0cac61265997 --- /dev/null +++ b/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.module.ts @@ -0,0 +1,41 @@ +// Copyright 2021 The Oppia Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Module for the CkEditor4 components. + */ + +import 'core-js/es7/reflect'; +import 'zone.js'; + +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { CkEditorCopyToolbarComponent } from './ck-editor-copy-toolbar.component'; + +@NgModule({ + imports: [ + CommonModule, + ], + declarations: [ + CkEditorCopyToolbarComponent + ], + entryComponents: [ + CkEditorCopyToolbarComponent + ], + exports: [ + CkEditorCopyToolbarComponent + ], +}) + +export class OppiaCkEditorCopyToolBarModule { } diff --git a/core/templates/components/common-layout-directives/common-elements/alert-message.component.ts b/core/templates/components/common-layout-directives/common-elements/alert-message.component.ts index 8eb9da984ce2..372cdef1162a 100644 --- a/core/templates/components/common-layout-directives/common-elements/alert-message.component.ts +++ b/core/templates/components/common-layout-directives/common-elements/alert-message.component.ts @@ -20,7 +20,6 @@ import { Component, Input } from '@angular/core'; import { downgradeComponent } from '@angular/upgrade/static'; import { ToastrService } from 'ngx-toastr'; import { AlertsService } from 'services/alerts.service'; -require('ngx-toastr/toastr.css'); export interface MessageObject { type: string; diff --git a/core/templates/components/common-layout-directives/common-elements/attribution-guide.component.ts b/core/templates/components/common-layout-directives/common-elements/attribution-guide.component.ts index a6154504bf4c..34dff15cf189 100644 --- a/core/templates/components/common-layout-directives/common-elements/attribution-guide.component.ts +++ b/core/templates/components/common-layout-directives/common-elements/attribution-guide.component.ts @@ -33,7 +33,7 @@ import './attribution-guide.component.css'; @Component({ selector: 'attribution-guide', templateUrl: './attribution-guide.component.html', - styleUrls: [] + styleUrls: ['./attribution-guide.component.css'] }) export class AttributionGuideComponent implements OnInit { deviceUsedIsMobile: boolean = false; diff --git a/core/templates/components/common-layout-directives/common-elements/confirm-or-cancel-modal.component.ts b/core/templates/components/common-layout-directives/common-elements/confirm-or-cancel-modal.component.ts index f6e4a39d0313..4d1a47a54d36 100644 --- a/core/templates/components/common-layout-directives/common-elements/confirm-or-cancel-modal.component.ts +++ b/core/templates/components/common-layout-directives/common-elements/confirm-or-cancel-modal.component.ts @@ -22,7 +22,13 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; export class ConfirmOrCancelModal { constructor(protected modalInstance: NgbActiveModal) {} - confirm(value: T): void { + /** + * Function called upon an affirmative user action. + * @param value: Value with which the user confirms the action. + * Some actions don't require a value when confirming hence this arg is + * optional. + */ + confirm(value?: T): void { this.modalInstance.close(value); } diff --git a/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.css b/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.css index 41238a8dc87d..40d7954a5ad1 100644 --- a/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.css +++ b/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.css @@ -3,61 +3,61 @@ compilation, here are sme additional rules that can be added to the CSS files: https://rtlcss.com/learn/usage-guide/control-directives . */ -oppia-top-navigation-bar .oppia-navbar-tab:focus { +.oppia-top-navigation-bar .oppia-navbar-tab:focus { background-color: #fff; color: #00645c; } -oppia-top-navigation-bar .arrow-forward { +.oppia-top-navigation-bar .arrow-forward { color: #00645c; font-size: 14px; margin-left: 4px; } -oppia-top-navigation-bar .launch { +.oppia-top-navigation-bar .launch { font-size: 12px; margin-left: 90px; } -oppia-top-navigation-bar .underline:hover, -oppia-top-navigation-bar .underline:focus { +.oppia-top-navigation-bar .underline:hover, +.oppia-top-navigation-bar .underline:focus { border-bottom: 2px solid #333; transition: 50ms; } -oppia-top-navigation-bar .get-involved-dropdown a { +.oppia-top-navigation-bar .get-involved-dropdown a { text-decoration: none; } -oppia-top-navigation-bar .heading a { +.oppia-top-navigation-bar .heading a { color: #212121; } -oppia-top-navigation-bar .nav-item-links a { +.oppia-top-navigation-bar .nav-item-links a { color: #212121; text-decoration: none; } -oppia-top-navigation-bar .learn-dropdown-menu a, -oppia-top-navigation-bar .about-dropdown-menu a, -oppia-top-navigation-bar .get-involved-dropdown a { +.oppia-top-navigation-bar .learn-dropdown-menu a, +.oppia-top-navigation-bar .about-dropdown-menu a, +.oppia-top-navigation-bar .get-involved-dropdown a { text-decoration: none; } -oppia-top-navigation-bar .learn-dropdown-menu .nav-item-top-link a { +.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-top-link a { color: #333; font-family: 'Roboto', sans-serif; font-size: 16px; font-weight: 500; line-height: 18.75px; } -oppia-top-navigation-bar .dropdown-menu .innvolved-item:hover a { +.oppia-top-navigation-bar .dropdown-menu .innvolved-item:hover a { border-bottom: #333 solid 2px; transition: 50ms; } -oppia-top-navigation-bar .get-involved-dropdown { +.oppia-top-navigation-bar .get-involved-dropdown { min-width: 574px; padding: 0; } -oppia-top-navigation-bar .get-involved-dropdown .nav-item { +.oppia-top-navigation-bar .get-involved-dropdown .nav-item { width: 100%; } -oppia-top-navigation-bar .get-involved-dropdown .heading { +.oppia-top-navigation-bar .get-involved-dropdown .heading { color: #333; font-family: 'Roboto', sans-serif; font-size: 16px; @@ -65,23 +65,23 @@ oppia-top-navigation-bar .get-involved-dropdown .heading { line-height: 18.75px; padding: 0; } -oppia-top-navigation-bar .get-involved-dropdown .language { +.oppia-top-navigation-bar .get-involved-dropdown .language { color: #f2994a; font-size: 18px; } -oppia-top-navigation-bar .get-involved-dropdown .volunteer { +.oppia-top-navigation-bar .get-involved-dropdown .volunteer { color: #2d9cdb; font-size: 18px; } -oppia-top-navigation-bar .get-involved-dropdown .contact { +.oppia-top-navigation-bar .get-involved-dropdown .contact { color: #b4bbc3; font-size: 18px; } -oppia-top-navigation-bar .get-involved-dropdown .fav { +.oppia-top-navigation-bar .get-involved-dropdown .fav { color: #eb5757; font-size: 18px; } -oppia-top-navigation-bar .get-involved-dropdown .des { +.oppia-top-navigation-bar .get-involved-dropdown .des { color: #767676; font-family: 'Roboto', sans-serif; font-size: 14px; @@ -91,50 +91,50 @@ oppia-top-navigation-bar .get-involved-dropdown .des { padding-left: 36px; text-overflow: clip; } -oppia-top-navigation-bar .item-border { +.oppia-top-navigation-bar .item-border { border-right: 1px solid rgba(33, 33, 33, 0.102); } -oppia-top-navigation-bar .top-section { +.oppia-top-navigation-bar .top-section { border-bottom: 1px solid rgba(33, 33, 33, 0.102); } -oppia-top-navigation-bar .top-section .heading { +.oppia-top-navigation-bar .top-section .heading { align-items: center; display: flex; } -oppia-top-navigation-bar .bottom-section .heading { +.oppia-top-navigation-bar .bottom-section .heading { align-items: center; display: flex; } -oppia-top-navigation-bar .top-section .heading-text { +.oppia-top-navigation-bar .top-section .heading-text { display: inline-block; margin-left: 7px; } -oppia-top-navigation-bar .bottom-section .heading-text { +.oppia-top-navigation-bar .bottom-section .heading-text { margin-left: 9px; } -oppia-top-navigation-bar .item { +.oppia-top-navigation-bar .item { padding: 32px; width: 287px; } -oppia-top-navigation-bar .item:hover, -oppia-top-navigation-bar .item:focus { +.oppia-top-navigation-bar .item:hover, +.oppia-top-navigation-bar .item:focus { background-color: rgba(33, 33, 33, 0.027); } -oppia-top-navigation-bar .dropdown-menu .signin-dropdown { +.oppia-top-navigation-bar .dropdown-menu .signin-dropdown { border-radius: 1px; height: 41px; width: 200px; } -oppia-top-navigation-bar .top-section .item { +.oppia-top-navigation-bar .top-section .item { padding-bottom: 27px; } -oppia-top-navigation-bar .bottom-section .item { +.oppia-top-navigation-bar .bottom-section .item { padding-top: 27px; } @@ -146,33 +146,33 @@ oppia-top-navigation-bar .bottom-section .item { margin-left: 25px; } -oppia-top-navigation-bar .dropdown-toggle::after { +.oppia-top-navigation-bar .dropdown-toggle::after { margin-left: 0.255em; margin-right: 0.255em; } -oppia-top-navigation-bar .learn-dropdown-menu { +.oppia-top-navigation-bar .learn-dropdown-menu { border-radius: 3px; padding: 0; } -oppia-top-navigation-bar .classroom-enabled { +.oppia-top-navigation-bar .classroom-enabled { min-width: 688px; } -oppia-top-navigation-bar .classroom-disabled { +.oppia-top-navigation-bar .classroom-disabled { min-width: 300px; } -oppia-top-navigation-bar .language-dropdown { +.oppia-top-navigation-bar .language-dropdown { max-height: 60vh; overflow-y: auto; } -oppia-top-navigation-bar .learn { +.oppia-top-navigation-bar .learn { height: 66.5px; } -oppia-top-navigation-bar .learn-dropdown-menu .nav-item-top-link { +.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-top-link { padding: 0; padding-bottom: 8px; } -oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right-head { +.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right-head { color: #333; font-family: 'Roboto', sans-serif; font-size: 12px; @@ -183,7 +183,7 @@ oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right-head { padding-bottom: 8px; text-transform: uppercase; } -oppia-top-navigation-bar .learn-dropdown-menu .nav-item-links { +.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-links { color: #333; font-family: 'Roboto', sans-serif; font-size: 14px; @@ -194,39 +194,39 @@ oppia-top-navigation-bar .learn-dropdown-menu .nav-item-links { text-transform: capitalize; } -oppia-top-navigation-bar .learn-dropdown-menu .nav-item-left { +.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-left { border-right: 1px solid rgba(0, 0, 0, 0.051); padding: 32px; width: 388px; } -oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right { +.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right { min-width: 300px; padding: 32px; } -oppia-top-navigation-bar .learn-dropdown-menu .nav-item-left:hover, -oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right:hover, -oppia-top-navigation-bar .learn-dropdown-menu .nav-item-left:focus, -oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right:focus { +.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-left:hover, +.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right:hover, +.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-left:focus, +.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right:focus { background-color: rgba(33, 33, 33, 0.027); } -oppia-top-navigation-bar .about-dropdown-menu .nav-item a:hover, -oppia-top-navigation-bar .about-dropdown-menu .nav-item a:focus { +.oppia-top-navigation-bar .about-dropdown-menu .nav-item a:hover, +.oppia-top-navigation-bar .about-dropdown-menu .nav-item a:focus { background-color: rgba(33, 33, 33, 0.027); } -oppia-top-navigation-bar .about-dropdown-menu .about-link:hover, -oppia-top-navigation-bar .about-dropdown-menu .about-link:focus { +.oppia-top-navigation-bar .about-dropdown-menu .about-link:hover, +.oppia-top-navigation-bar .about-dropdown-menu .about-link:focus { color: #009688 !important; } -oppia-top-navigation-bar .nav-item-left-head { +.oppia-top-navigation-bar .nav-item-left-head { color: #333; font-family: 'Roboto', sans-serif; font-size: 16px; font-weight: 500; line-height: 18.75px; } -oppia-top-navigation-bar .learn-dropdown-menu .nav-content { +.oppia-top-navigation-bar .learn-dropdown-menu .nav-content { color: #767676; font-family: 'Roboto', sans-serif; font-size: 14px; @@ -234,7 +234,7 @@ oppia-top-navigation-bar .learn-dropdown-menu .nav-content { line-height: 20px; width: 224px; } -oppia-top-navigation-bar .learn-dropdown-menu .nav-item-bottom-link { +.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-bottom-link { color: #00645c; font-family: 'Inter', 'Roboto', sans-serif; font-size: 14px; @@ -243,7 +243,7 @@ oppia-top-navigation-bar .learn-dropdown-menu .nav-item-bottom-link { padding: 0; padding-top: 16px; } -oppia-top-navigation-bar .learn-dropdown-menu .nav-item-bottom-link-logged { +.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-bottom-link-logged { color: #00645c; font-family: 'Inter', 'Roboto', sans-serif; font-size: 14px; @@ -251,7 +251,7 @@ oppia-top-navigation-bar .learn-dropdown-menu .nav-item-bottom-link-logged { line-height: 16.41px; padding: 0; } -oppia-top-navigation-bar .oppia-navbar-nav .dropdown-menu { +.oppia-top-navigation-bar .oppia-navbar-nav .dropdown-menu { border-radius: 3px; border-top-left-radius: 0; border-top-right-radius: 0; @@ -260,15 +260,15 @@ oppia-top-navigation-bar .oppia-navbar-nav .dropdown-menu { text-align: left; } -oppia-top-navigation-bar .language-icon { +.oppia-top-navigation-bar .language-icon { font-size: 20px; vertical-align: text-bottom; } -oppia-top-navigation-bar .language-padding-left { +.oppia-top-navigation-bar .language-padding-left { padding-left: 26px; } -oppia-top-navigation-bar .oppia-nav-right-dropdown { +.oppia-top-navigation-bar .oppia-nav-right-dropdown { background-color: #fff; border-radius: 5px; border-width: 0; @@ -284,44 +284,44 @@ oppia-top-navigation-bar .oppia-nav-right-dropdown { white-space: nowrap; } -oppia-top-navigation-bar .oppia-navbar-brand-name:focus { +.oppia-top-navigation-bar .oppia-navbar-brand-name:focus { outline: 1px dotted #fff; outline: auto 5px -webkit-focus-ring-color; } -oppia-top-navigation-bar .language-element { +.oppia-top-navigation-bar .language-element { border-radius: 0; box-shadow: 0 0; color: #34665c; padding: 10px 0; } -oppia-top-navigation-bar .language-element:hover { +.oppia-top-navigation-bar .language-element:hover { background-color: rgb(52, 102, 92, 0.1); } -oppia-top-navigation-bar .language-element-selected { +.oppia-top-navigation-bar .language-element-selected { background-color: rgb(52, 102, 92, 0.1); } -oppia-top-navigation-bar .oppia-nav-right-dropdown:focus, -oppia-top-navigation-bar .oppia-nav-right-dropdown:hover { +.oppia-top-navigation-bar .oppia-nav-right-dropdown:focus, +.oppia-top-navigation-bar .oppia-nav-right-dropdown:hover { box-shadow: 1px 4px 5px 1px rgba(0,0,0,0.1); } -oppia-top-navigation-bar .oppia-nav-right-dropdown:active { +.oppia-top-navigation-bar .oppia-nav-right-dropdown:active { background-color: #e5e5e5; box-shadow: none; transition-duration: 10ms; } -oppia-top-navigation-bar .oppia-navbar-button-container.dropdown.oppia-navbar-button-container-extra-info { +.oppia-top-navigation-bar .oppia-navbar-button-container.dropdown.oppia-navbar-button-container-extra-info { margin-left: 10px; } -oppia-top-navigation-bar .oppia-navbar-button-container .btn.oppia-navbar-button { +.oppia-top-navigation-bar .oppia-navbar-button-container .btn.oppia-navbar-button { margin-left: 5px; margin-right: 0; } -oppia-top-navigation-bar .oppia-signin-g-icon { +.oppia-top-navigation-bar .oppia-signin-g-icon { float: left; padding: 10px 15px 10px 0; } -oppia-top-navigation-bar .oppia-navbar-dashboard-indicator-text { +.oppia-top-navigation-bar .oppia-navbar-dashboard-indicator-text { bottom: 0; color: white; font-size: 1.12rem; @@ -329,7 +329,7 @@ oppia-top-navigation-bar .oppia-navbar-dashboard-indicator-text { position: absolute; right: 4px; } -oppia-top-navigation-bar .oppia-navbar-role-text { +.oppia-top-navigation-bar .oppia-navbar-role-text { bottom: 0; color: white; font-size: 11px; @@ -337,7 +337,7 @@ oppia-top-navigation-bar .oppia-navbar-role-text { position: absolute; right: 3px; } -oppia-top-navigation-bar .oppia-navbar-dashboard-indicator { +.oppia-top-navigation-bar .oppia-navbar-dashboard-indicator { background-color: #f7a541; border-radius: 20px; height: 15px; @@ -346,16 +346,16 @@ oppia-top-navigation-bar .oppia-navbar-dashboard-indicator { top: 8px; width: 15px; } -oppia-top-navigation-bar .oppia-navbar-menu { +.oppia-top-navigation-bar .oppia-navbar-menu { margin-left: 10px; opacity: 0.9; outline-color: transparent; padding-top: 20px; } -oppia-top-navigation-bar .oppia-navbar-profile { +.oppia-top-navigation-bar .oppia-navbar-profile { float: right; } -oppia-top-navigation-bar .oppia-navbar-role-indicator { +.oppia-top-navigation-bar .oppia-navbar-role-indicator { background-color: #f7a541; border-radius: 20px; bottom: 10px; @@ -364,95 +364,95 @@ oppia-top-navigation-bar .oppia-navbar-role-indicator { right: 25px; width: 15px; } -oppia-top-navigation-bar .oppia-navbar-close-icon { +.oppia-top-navigation-bar .oppia-navbar-close-icon { color: #fff; font-size: 22px; margin-right: 4px; margin-top: -5px; } -oppia-top-navigation-bar .oppia-launch-icon { +.oppia-top-navigation-bar .oppia-launch-icon { color: #009688; font-size: 22px; } -.oppia-user-avatar-icon { +.oppia-top-navigation-bar .oppia-user-avatar-icon { font-size: 36px; } -oppia-top-navigation-bar .oppia-admin-text { +.oppia-top-navigation-bar .oppia-admin-text { right: 4px; } -oppia-top-navigation-bar .oppia-navbar-button-container-extra-info { +.oppia-top-navigation-bar .oppia-navbar-button-container-extra-info { margin-right: 10px; } -oppia-top-navigation-bar .oppia-navbar-dropdown-menu { +.oppia-top-navigation-bar .oppia-navbar-dropdown-menu { margin-right: 15px; padding: 0; } -oppia-top-navigation-bar .oppia-navbar-dropdown { +.oppia-top-navigation-bar .oppia-navbar-dropdown { border: 0; border-top-left-radius: 0; border-top-right-radius: 0; margin-top: 0; } -oppia-top-navigation-bar .oppia-navbar-profile-picture-container .caret { +.oppia-top-navigation-bar .oppia-navbar-profile-picture-container .caret { margin: 4px; margin-left: 10px; } -oppia-top-navigation-bar .oppia-navbar-profile-picture-container { +.oppia-top-navigation-bar .oppia-navbar-profile-picture-container { margin-left: 10px; text-align: right; } -oppia-top-navigation-bar .oppia-navbar-dropdown .nav-link { +.oppia-top-navigation-bar .oppia-navbar-dropdown .nav-link { color: #009688; } -oppia-top-navigation-bar .oppia-navbar-dropdown .nav-link:hover { +.oppia-top-navigation-bar .oppia-navbar-dropdown .nav-link:hover { color: #888; } -oppia-top-navigation-bar .oppia-signin-text { +.oppia-top-navigation-bar .oppia-signin-text { font-family: Roboto, arial, sans-serif; font-size: 14px; font-weight: 500; letter-spacing: .21px; line-height: 41px; } -oppia-top-navigation-bar .oppia-navbar-menu-icon { +.oppia-top-navigation-bar .oppia-navbar-menu-icon { color: #fff; font-size: 22px; margin-top: -5px; } -oppia-top-navigation-bar .nav-mobile-header-text-container { +.oppia-top-navigation-bar .nav-mobile-header-text-container { display: none; } -oppia-top-navigation-bar .oppia-navbar-clickable-dropdown:hover ul.dropdown-menu { +.oppia-top-navigation-bar .oppia-navbar-clickable-dropdown:hover ul.dropdown-menu { display: block; } -oppia-top-navigation-bar .oppia-navbar-menu:hover { +.oppia-top-navigation-bar .oppia-navbar-menu:hover { opacity: 1; } -oppia-top-navigation-bar .oppia-navbar-menu:focus { +.oppia-top-navigation-bar .oppia-navbar-menu:focus { outline: 1px dotted #212121; outline: 5px -webkit-focus-ring-color; } -oppia-top-navigation-bar .oppia-nav-link { +.oppia-top-navigation-bar .oppia-nav-link { padding: 0; width: 190px; } -oppia-top-navigation-bar .oppia-navbar-dropdown-item { +.oppia-top-navigation-bar .oppia-navbar-dropdown-item { display: flex; flex-direction: column; gap: 2px; padding: 30px; } -oppia-top-navigation-bar .oppia-navbar-tab-content-heading { +.oppia-top-navigation-bar .oppia-navbar-tab-content-heading { color: #212121; font-size: 16px; font-weight: 500; padding: 0; } -oppia-top-navigation-bar .oppia-navbar-tab-content-heading:after { +.oppia-top-navigation-bar .oppia-navbar-tab-content-heading:after { border-style: solid; border-width: 0 0 1.59px; bottom: 1px; @@ -462,45 +462,45 @@ oppia-top-navigation-bar .oppia-navbar-tab-content-heading:after { width: 50.5px; } -oppia-top-navigation-bar .oppia-navbar-dropdown-heart-icon { +.oppia-top-navigation-bar .oppia-navbar-dropdown-heart-icon { color: #eb5757; font-size: 22px; padding-right: 6px; } -oppia-top-navigation-bar .oppia-navbar-tab-content-desc { +.oppia-top-navigation-bar .oppia-navbar-tab-content-desc { color: #767676; line-height: 20px; margin-left: 28px; opacity: 0.9; } -oppia-top-navigation-bar .oppia-navbar-dropdown-right { +.oppia-top-navigation-bar .oppia-navbar-dropdown-right { left: 0; min-width: 285px; padding: 0; right: auto; } -oppia-top-navigation-bar .oppia-navbar-nav.oppia-navbar-profile .profile-dropdown:hover { +.oppia-top-navigation-bar .oppia-navbar-nav.oppia-navbar-profile .profile-dropdown:hover { background-color: #fff; } -oppia-top-navigation-bar .oppia-navbar-arrow-right { +.oppia-top-navigation-bar .oppia-navbar-arrow-right { font-size: 12px; padding-left: 8px; padding-top: 1.5px; } -oppia-top-navigation-bar .oppia-tick-mark { +.oppia-top-navigation-bar .oppia-tick-mark { font-size: 15px; margin-right: 5px; padding-left: 5px; } @media screen and (max-width: 880px) { - oppia-top-navigation-bar .oppia-logo-wide { + .oppia-top-navigation-bar .oppia-logo-wide { margin-left: 0; } } @media screen and (max-width: 840px) { - oppia-top-navigation-bar .desktop-view-language-code { + .oppia-top-navigation-bar .desktop-view-language-code { display: none; } } @@ -513,7 +513,7 @@ oppia-top-navigation-bar .oppia-tick-mark { } @media screen and (min-width: 300px) and (max-width: 768px) { - .nav-mobile-header-text-container { + .oppia-top-navigation-bar .nav-mobile-header-text-container { display: inline-block; font-size: 1px; margin-top: 2px; @@ -521,13 +521,13 @@ oppia-top-navigation-bar .oppia-tick-mark { text-overflow: ellipsis; width: 50%; } - oppia-top-navigation-bar .nav-mobile-header-editor { + .oppia-top-navigation-bar .nav-mobile-header-editor { color: #fff; display: block; font-family: Roboto, arial, sans-serif; font-size: 19px; } - oppia-top-navigation-bar .nav-mobile-header-text { + .oppia-top-navigation-bar .nav-mobile-header-text { color: #fff; display: inline-block; font-size: 15px; @@ -537,4 +537,11 @@ oppia-top-navigation-bar .oppia-tick-mark { white-space: nowrap; width: 100%; } + @media screen and (max-width: 600px) { + .oppia-top-navigation-bar .oppia-logo { + left: 0; + margin-left: 60px; + position: absolute; + } + } } diff --git a/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.html b/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.html index d511abd3d6ba..6167fc9028bb 100644 --- a/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.html +++ b/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.html @@ -30,7 +30,7 @@ -