├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── CHANGELOG.md ├── Jenkinsfile ├── LICENSE ├── README.md ├── Semantic-UI-2.5.0 ├── LICENSE.md ├── README.md ├── RELEASE-NOTES.md └── dist │ ├── components │ ├── accordion.css │ ├── accordion.js │ ├── accordion.min.css │ ├── accordion.min.js │ ├── ad.css │ ├── ad.min.css │ ├── api.js │ ├── api.min.js │ ├── breadcrumb.css │ ├── breadcrumb.min.css │ ├── button.css │ ├── button.min.css │ ├── card.css │ ├── card.min.css │ ├── checkbox.css │ ├── checkbox.js │ ├── checkbox.min.css │ ├── checkbox.min.js │ ├── comment.css │ ├── comment.min.css │ ├── container.css │ ├── container.min.css │ ├── dimmer.css │ ├── dimmer.js │ ├── dimmer.min.css │ ├── dimmer.min.js │ ├── divider.css │ ├── divider.min.css │ ├── dropdown.css │ ├── dropdown.js │ ├── dropdown.min.css │ ├── dropdown.min.js │ ├── embed.css │ ├── embed.js │ ├── embed.min.css │ ├── embed.min.js │ ├── feed.css │ ├── feed.min.css │ ├── flag.css │ ├── flag.min.css │ ├── form.css │ ├── form.js │ ├── form.min.css │ ├── form.min.js │ ├── grid.css │ ├── grid.min.css │ ├── header.css │ ├── header.min.css │ ├── icon.css │ ├── icon.min.css │ ├── image.css │ ├── image.min.css │ ├── input.css │ ├── input.min.css │ ├── item.css │ ├── item.min.css │ ├── label.css │ ├── label.min.css │ ├── list.css │ ├── list.min.css │ ├── loader.css │ ├── loader.min.css │ ├── menu.css │ ├── menu.min.css │ ├── message.css │ ├── message.min.css │ ├── modal.css │ ├── modal.js │ ├── modal.min.css │ ├── modal.min.js │ ├── nag.css │ ├── nag.js │ ├── nag.min.css │ ├── nag.min.js │ ├── placeholder.css │ ├── placeholder.min.css │ ├── popup.css │ ├── popup.js │ ├── popup.min.css │ ├── popup.min.js │ ├── progress.css │ ├── progress.js │ ├── progress.min.css │ ├── progress.min.js │ ├── rail.css │ ├── rail.min.css │ ├── rating.css │ ├── rating.js │ ├── rating.min.css │ ├── rating.min.js │ ├── reset.css │ ├── reset.min.css │ ├── reveal.css │ ├── reveal.min.css │ ├── search.css │ ├── search.js │ ├── search.min.css │ ├── search.min.js │ ├── segment.css │ ├── segment.min.css │ ├── shape.css │ ├── shape.js │ ├── shape.min.css │ ├── shape.min.js │ ├── sidebar.css │ ├── sidebar.js │ ├── sidebar.min.css │ ├── sidebar.min.js │ ├── site.css │ ├── site.js │ ├── site.min.css │ ├── site.min.js │ ├── statistic.css │ ├── statistic.min.css │ ├── step.css │ ├── step.min.css │ ├── sticky.css │ ├── sticky.js │ ├── sticky.min.css │ ├── sticky.min.js │ ├── tab.css │ ├── tab.js │ ├── tab.min.css │ ├── tab.min.js │ ├── table.css │ ├── table.min.css │ ├── transition.css │ ├── transition.js │ ├── transition.min.css │ ├── transition.min.js │ ├── video.css │ ├── video.js │ ├── video.min.css │ ├── video.min.js │ ├── visibility.js │ └── visibility.min.js │ ├── semantic.css │ ├── semantic.js │ ├── semantic.min.css │ ├── semantic.min.js │ └── themes │ ├── basic │ └── assets │ │ └── fonts │ │ ├── icons.eot │ │ ├── icons.svg │ │ ├── icons.ttf │ │ └── icons.woff │ ├── default │ └── assets │ │ ├── fonts │ │ ├── brand-icons.eot │ │ ├── brand-icons.svg │ │ ├── brand-icons.ttf │ │ ├── brand-icons.woff │ │ ├── brand-icons.woff2 │ │ ├── icons.eot │ │ ├── icons.otf │ │ ├── icons.svg │ │ ├── icons.ttf │ │ ├── icons.woff │ │ ├── icons.woff2 │ │ ├── outline-icons.eot │ │ ├── outline-icons.svg │ │ ├── outline-icons.ttf │ │ ├── outline-icons.woff │ │ └── outline-icons.woff2 │ │ └── images │ │ └── flags.png │ ├── github │ └── assets │ │ └── fonts │ │ ├── octicons-local.ttf │ │ ├── octicons.svg │ │ ├── octicons.ttf │ │ └── octicons.woff │ └── material │ └── assets │ └── fonts │ ├── icons.eot │ ├── icons.svg │ ├── icons.ttf │ ├── icons.woff │ └── icons.woff2 ├── angular.json ├── art ├── nepal_select_icon.svg └── osmType_icons │ ├── Mf_Relation.svg │ ├── Mf_area.svg │ ├── Mf_closed_way.svg │ ├── Mf_node.svg │ ├── Mf_way.png │ ├── Mf_way.svg │ ├── README.txt │ └── osmtype_choose.svg ├── karma-jenkins.conf.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── pipeline_config.groovy ├── scripts ├── .example.env ├── deploy-to-dashboard-prod.sh ├── deploy-to-dashboard-test.sh └── prompt_user_exit_or_continue.sh ├── sonar-project.properties ├── src ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── oqt │ │ ├── metadata-indicator.response.mock.ts │ │ ├── metadata-topics.response.mock.ts │ │ ├── oqt-api-metadata-provider.service.mock.ts │ │ ├── oqt-api-metadata-provider.service.spec.ts │ │ ├── oqt-api-metadata-provider.service.ts │ │ ├── oqt-api-metadata.response.mock.ts │ │ ├── oqt-api.service.mock.ts │ │ ├── oqt-api.service.spec.ts │ │ ├── oqt-api.service.ts │ │ ├── oqt.module.ts │ │ ├── query-form │ │ │ └── oqt-api-query-form │ │ │ │ ├── attribute-completeness-attributes │ │ │ │ ├── attribute-completeness-attributes.component.css │ │ │ │ ├── attribute-completeness-attributes.component.html │ │ │ │ ├── attribute-completeness-attributes.component.spec.ts │ │ │ │ └── attribute-completeness-attributes.component.ts │ │ │ │ ├── oqt-api-query-form.component.css │ │ │ │ ├── oqt-api-query-form.component.html │ │ │ │ ├── oqt-api-query-form.component.spec.ts │ │ │ │ ├── oqt-api-query-form.component.ts │ │ │ │ ├── simple-indicator │ │ │ │ ├── simple-indicator.component.css │ │ │ │ ├── simple-indicator.component.html │ │ │ │ ├── simple-indicator.component.spec.ts │ │ │ │ └── simple-indicator.component.ts │ │ │ │ └── thematical-accuracy-indicator │ │ │ │ ├── thematical-accuracy-indicator.component.html │ │ │ │ ├── thematical-accuracy-indicator.component.spec.ts │ │ │ │ └── thematical-accuracy-indicator.component.ts │ │ ├── result │ │ │ ├── indicator-result │ │ │ │ ├── indicator-result.component.css │ │ │ │ ├── indicator-result.component.html │ │ │ │ ├── indicator-result.component.spec.ts │ │ │ │ └── indicator-result.component.ts │ │ │ ├── indicator.response.mock.ts │ │ │ ├── oqt-result.component.css │ │ │ ├── oqt-result.component.html │ │ │ ├── oqt-result.component.spec.ts │ │ │ └── oqt-result.component.ts │ │ └── types │ │ │ ├── AttributionJSON.d.ts │ │ │ ├── BaseResponseJSON.d.ts │ │ │ ├── ErrorResponseJSON.d.ts │ │ │ ├── MetadataResponseJSON.d.ts │ │ │ └── types.d.ts │ ├── oshdb │ │ ├── ohsome-api-metadata-provider.service.mock.ts │ │ ├── ohsome-api-metadata-provider.service.spec.ts │ │ ├── ohsome-api-metadata-provider.service.ts │ │ ├── ohsome-api-metadata.response.mock.ts │ │ ├── ohsome-api.service.spec.ts │ │ ├── ohsome-api.service.ts │ │ ├── oshdb.module.ts │ │ ├── query-form │ │ │ ├── ohsome-api-query-form │ │ │ │ ├── ohsome-api-query-form.component.css │ │ │ │ ├── ohsome-api-query-form.component.html │ │ │ │ ├── ohsome-api-query-form.component.spec.ts │ │ │ │ └── ohsome-api-query-form.component.ts │ │ │ └── time-period-picker-input │ │ │ │ ├── time-period-picker-input.component.css │ │ │ │ ├── time-period-picker-input.component.html │ │ │ │ └── time-period-picker-input.component.ts │ │ └── result │ │ │ ├── result.component.css │ │ │ ├── result.component.html │ │ │ ├── result.component.spec.ts │ │ │ ├── result.component.ts │ │ │ ├── result.utils.ts │ │ │ ├── simple-chart │ │ │ ├── simple-chart.component.css │ │ │ ├── simple-chart.component.html │ │ │ ├── simple-chart.component.spec.ts │ │ │ └── simple-chart.component.ts │ │ │ ├── simple-groupby-result │ │ │ ├── simple-groupby-result.component.css │ │ │ ├── simple-groupby-result.component.html │ │ │ ├── simple-groupby-result.component.spec.ts │ │ │ ├── simple-groupby-result.component.ts │ │ │ └── simple-groupby-result.mockdata.ts │ │ │ └── simple-result │ │ │ ├── simple-result.component.css │ │ │ ├── simple-result.component.html │ │ │ ├── simple-result.component.spec.ts │ │ │ ├── simple-result.component.ts │ │ │ └── simple-result.mockdata.ts │ ├── query-panel │ │ ├── query-panel.component.css │ │ ├── query-panel.component.html │ │ ├── query-panel.component.spec.ts │ │ └── query-panel.component.ts │ ├── result-panel │ │ ├── result-list.directive.spec.ts │ │ ├── result-list.directive.ts │ │ ├── result-panel.component.css │ │ ├── result-panel.component.html │ │ ├── result-panel.component.spec.ts │ │ └── result-panel.component.ts │ ├── shared │ │ ├── components │ │ │ ├── boundary-input │ │ │ │ ├── boundary-input.component.css │ │ │ │ ├── boundary-input.component.html │ │ │ │ └── boundary-input.component.ts │ │ │ ├── boundary-select-input │ │ │ │ ├── Leaflet.TileLayer.WmsSelect.ts │ │ │ │ ├── boundary-select-input.component.css │ │ │ │ ├── boundary-select-input.component.html │ │ │ │ └── boundary-select-input.component.ts │ │ │ ├── plotly-chart │ │ │ │ ├── plotly-chart.component.css │ │ │ │ ├── plotly-chart.component.html │ │ │ │ ├── plotly-chart.component.spec.ts │ │ │ │ ├── plotly-chart.component.ts │ │ │ │ └── plotly-gauge-data.mock.ts │ │ │ ├── prism-editor │ │ │ │ ├── prism-editor.component.html │ │ │ │ ├── prism-editor.component.spec.ts │ │ │ │ └── prism-editor.component.ts │ │ │ └── sui-dropdown │ │ │ │ ├── sui-multi-select-search-dropdown.component.html │ │ │ │ ├── sui-multi-select-search-dropdown.component.spec.ts │ │ │ │ └── sui-multi-select-search-dropdown.component.ts │ │ ├── directives │ │ │ └── validation │ │ │ │ ├── at-least-one-checkbox-checked.directive.spec.ts │ │ │ │ └── at-least-one-checkbox-checked.directive.ts │ │ ├── shared-types.d.ts │ │ └── shared.module.ts │ └── singelton-services │ │ ├── data.service.spec.ts │ │ ├── data.service.ts │ │ ├── osm-boundary-provider.service.spec.ts │ │ ├── osm-boundary-provider.service.ts │ │ ├── url-hash-params-provider.service.spec.ts │ │ └── url-hash-params-provider.service.ts ├── assets │ ├── .gitkeep │ ├── fonts │ │ ├── STIXTwoMath-Regular.woff2 │ │ ├── lmmonoltcond10-regular-webfont.woff │ │ └── lmmonoprop10-regular-webfont.woff │ └── images │ │ ├── HeiGIT_Logo_only.svg │ │ ├── ohsome-quality-analyst_without_fonts.svg │ │ └── ohsome_narrow.svg ├── environments │ ├── environment.idai.ts │ ├── environment.prod.ts │ ├── environment.test.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── prism-language-ohsome-filter.ts ├── styles.css └── utils.ts ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "plugins": [ 7 | "unused-imports" 8 | ], 9 | "overrides": [ 10 | { 11 | "files": [ 12 | "*.ts" 13 | ], 14 | "extends": [ 15 | "eslint:recommended", 16 | "plugin:@typescript-eslint/recommended", 17 | "plugin:@angular-eslint/recommended", 18 | "plugin:@angular-eslint/template/process-inline-templates" 19 | ], 20 | "rules": { 21 | "@angular-eslint/directive-selector": [ 22 | "error", 23 | { 24 | "type": "attribute", 25 | "prefix": "app", 26 | "style": "camelCase" 27 | } 28 | ], 29 | "@angular-eslint/component-selector": [ 30 | "error", 31 | { 32 | "type": "element", 33 | "prefix": "app", 34 | "style": "kebab-case" 35 | } 36 | ], 37 | "unused-imports/no-unused-imports": "error" 38 | } 39 | }, 40 | { 41 | "files": [ 42 | "*.html" 43 | ], 44 | "extends": [ 45 | "plugin:@angular-eslint/template/recommended" 46 | ], 47 | "rules": {} 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # compiled output 8 | /dist 9 | /tmp 10 | /out-tsc 11 | .angular 12 | 13 | # dependencies 14 | /node_modules 15 | 16 | # IDEs and editors 17 | /.idea 18 | .project 19 | .classpath 20 | .c9/ 21 | *.launch 22 | .settings/ 23 | *.sublime-workspace 24 | 25 | # IDE - VSCode 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | 32 | # misc 33 | /.sass-cache 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | npm-debug.log 38 | testem.log 39 | /typings 40 | 41 | # e2e 42 | /e2e/*.js 43 | /e2e/*.map 44 | 45 | # System Files 46 | .DS_Store 47 | Thumbs.db 48 | 49 | # nx cache 50 | /.nx/cache/ 51 | 52 | # folder related to deployment 53 | .prod.env 54 | .test.env 55 | /temp-test-repo/ 56 | /temp-prod-repo/ 57 | 58 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label 'worker' 4 | } 5 | options { 6 | timeout(time: 30, unit: 'MINUTES') 7 | } 8 | 9 | environment { 10 | REPO_NAME = sh(returnStdout: true, script: 'basename `git remote get-url origin` .git').trim() 11 | VERSION = sh(returnStdout: true, script: 'grep -Po "\\"version\\": \\"\\K([^\\"]+)" package.json').trim() 12 | LATEST_AUTHOR = sh(returnStdout: true, script: 'git show -s --pretty=%an').trim() 13 | LATEST_COMMIT_ID = sh(returnStdout: true, script: 'git describe --tags --long --always').trim() 14 | PATH = "${WORKSPACE}/node_modules/.bin:${env.PATH}" 15 | } 16 | 17 | stages { 18 | stage ('Install') { 19 | steps { 20 | script { 21 | echo REPO_NAME 22 | echo LATEST_AUTHOR 23 | echo LATEST_COMMIT_ID 24 | 25 | echo env.BRANCH_NAME 26 | echo env.BUILD_NUMBER 27 | echo env.TAG_NAME 28 | } 29 | nodejs('NodeJS 18') { 30 | sh 'npm install' 31 | } 32 | } 33 | post { 34 | failure { 35 | basicsend("*${REPO_NAME}*-build nr. ${env.BUILD_NUMBER} *failed* to install packages, check your packages.json file (<${env.BUILD_URL}|Open Build in Jenkins>). Latest commit from ${LATEST_AUTHOR}.") 36 | } 37 | } 38 | } 39 | 40 | stage ('Build') { 41 | steps { 42 | nodejs('NodeJS 18') { 43 | sh 'npm run build:prod' 44 | } 45 | } 46 | post { 47 | failure { 48 | rocket_buildfail() 49 | } 50 | } 51 | } 52 | 53 | stage ('Test') { 54 | steps { 55 | nodejs('NodeJS 18') { 56 | sh 'ng test --karma-config karma-jenkins.conf.js --code-coverage' 57 | } 58 | } 59 | post { 60 | failure { 61 | rocket_testfail() 62 | } 63 | } 64 | } 65 | 66 | stage ('Reports and Statistics') { 67 | steps { 68 | script { 69 | def scannerHome = tool 'SonarScanner 4'; 70 | withSonarQubeEnv('sonarcloud GIScience/ohsome') { 71 | SONAR_CLI_PARAMETER = "-Dsonar.projectVersion=${VERSION} " 72 | if (env.CHANGE_ID) { 73 | SONAR_CLI_PARAMETER += 74 | "-Dsonar.pullrequest.key=${env.CHANGE_ID} " + 75 | "-Dsonar.pullrequest.branch=${env.CHANGE_BRANCH} " + 76 | "-Dsonar.pullrequest.base=${env.CHANGE_TARGET}" 77 | } else { 78 | SONAR_CLI_PARAMETER += "-Dsonar.branch.name=${env.BRANCH_NAME}" 79 | } 80 | nodejs('NodeJS 18') { 81 | sh "${scannerHome}/bin/sonar-scanner " + SONAR_CLI_PARAMETER 82 | } 83 | } 84 | } 85 | } 86 | post { 87 | failure { 88 | rocket_reportfail() 89 | } 90 | } 91 | } 92 | stage('Wrapping Up') { 93 | steps { 94 | encourage() 95 | status_change() 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ohsome dashboard 2 | 3 | [![Build Status](https://jenkins.heigit.org/buildStatus/icon?job=ohsome-dashboard/main)](https://jenkins.heigit.org/job/ohsome-dashboard/job/main/) 4 | [![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=GIScience_ohsome-dashboard&metric=alert_status)](https://sonarcloud.io/dashboard?id=GIScience_ohsome-dashboard) 5 | [![status: active](https://github.com/GIScience/badges/raw/master/status/active.svg)](https://github.com/GIScience/badges#active) 6 | [![LICENSE](https://img.shields.io/github/license/GIScience/ohsome-dashboard)](LICENSE) 7 | [![Website](https://img.shields.io/website?url=https%3A%2F%2Fdashboard.ohsome.org)](https://dashboard.ohsome.org) 8 | 9 | Try out the online version here: https://dashboard.ohsome.org/ 10 | 11 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.2.0. 12 | 13 | ## Setup 14 | 15 | Use node v18+ 16 | 17 | The following steps describe possible setups using `npm`. 18 | 19 | ### Simple Setup 20 | 21 | If you only want to change things in the `dashboard` code: 22 | ```bash 23 | export DASHBOARD_FOLDER=$(pwd)/ohsome-dashboard # modify to your desired dashboard source folder 24 | git clone ssh://git@github.com:GIScience/ohsome-dashboard.git $DASHBOARD_FOLDER 25 | 26 | # eventually switch to a development branch 27 | npm install 28 | 29 | npm start 30 | ``` 31 | 32 | ### Advanced Setup 33 | 34 | If you need to make changes in dashboard code and ohsome-js-utils at the same time: 35 | ```bash 36 | export DASHBOARD_FOLDER=$(pwd)/ohsome-dashboard # modify to your desired dashboard source folder 37 | export OHSOME_UTILS_FOLDER=$(pwd)/ohsome-js-utils # modify to your desired ohsome-js-utils source folder 38 | git clone ssh://git@github.com:GIScience/ohsome-dashboard.git $DASHBOARD_FOLDER 39 | git clone ssh://git@github.com:GIScience/ohsome-js-utils.git $OHSOME_UTILS_FOLDER 40 | 41 | cd $OHSOME_UTILS_FOLDER/dist 42 | npm link 43 | cd $DASHBOARD_FOLDER 44 | 45 | # eventually switch to a development branch 46 | npm install 47 | npm link "@giscience/ohsome-js-utils" 48 | 49 | npm start 50 | ``` 51 | 52 | ## Development server 53 | 54 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 55 | 56 | ## Code scaffolding 57 | 58 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 59 | 60 | ## Build 61 | 62 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod --env=prod` flag for a production build. 63 | 64 | ## Linting 65 | We are using `eslint`. To configure rules change `.eslintrc.json`. 66 | 67 | Run `ng lint` to find out about code style problems. 68 | 69 | ## Running unit tests 70 | 71 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 72 | 73 | ## Running end-to-end tests 74 | 75 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 76 | Run `ng e2e` to choose an e2e platform that angular will install for you. 77 | 78 | ## Further help 79 | 80 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 81 | -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/ad.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Ad 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2013 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */.ui.ad{display:block;overflow:hidden;margin:1em 0}.ui.ad:first-child{margin:0}.ui.ad:last-child{margin:0}.ui.ad iframe{margin:0;padding:0;border:none;overflow:hidden}.ui.leaderboard.ad{width:728px;height:90px}.ui[class*="medium rectangle"].ad{width:300px;height:250px}.ui[class*="large rectangle"].ad{width:336px;height:280px}.ui[class*="half page"].ad{width:300px;height:600px}.ui.square.ad{width:250px;height:250px}.ui[class*="small square"].ad{width:200px;height:200px}.ui[class*="small rectangle"].ad{width:180px;height:150px}.ui[class*="vertical rectangle"].ad{width:240px;height:400px}.ui.button.ad{width:120px;height:90px}.ui[class*="square button"].ad{width:125px;height:125px}.ui[class*="small button"].ad{width:120px;height:60px}.ui.skyscraper.ad{width:120px;height:600px}.ui[class*="wide skyscraper"].ad{width:160px}.ui.banner.ad{width:468px;height:60px}.ui[class*="vertical banner"].ad{width:120px;height:240px}.ui[class*="top banner"].ad{width:930px;height:180px}.ui[class*="half banner"].ad{width:234px;height:60px}.ui[class*="large leaderboard"].ad{width:970px;height:90px}.ui.billboard.ad{width:970px;height:250px}.ui.panorama.ad{width:980px;height:120px}.ui.netboard.ad{width:580px;height:400px}.ui[class*="large mobile banner"].ad{width:320px;height:100px}.ui[class*="mobile leaderboard"].ad{width:320px;height:50px}.ui.mobile.ad{display:none}@media only screen and (max-width:767px){.ui.mobile.ad{display:block}}.ui.centered.ad{margin-left:auto;margin-right:auto}.ui.test.ad{position:relative;background:#545454}.ui.test.ad:after{position:absolute;top:50%;left:50%;width:100%;text-align:center;transform:translateX(-50%) translateY(-50%);content:'Ad';color:#fff;font-size:1em;font-weight:700}.ui.mobile.test.ad:after{font-size:.85714286em}.ui.test.ad[data-text]:after{content:attr(data-text)} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/breadcrumb.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Breadcrumb 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */ 10 | 11 | 12 | /******************************* 13 | Breadcrumb 14 | *******************************/ 15 | 16 | .ui.breadcrumb { 17 | line-height: 1; 18 | display: inline-block; 19 | margin: 0em 0em; 20 | vertical-align: middle; 21 | } 22 | .ui.breadcrumb:first-child { 23 | margin-top: 0em; 24 | } 25 | .ui.breadcrumb:last-child { 26 | margin-bottom: 0em; 27 | } 28 | 29 | 30 | /******************************* 31 | Content 32 | *******************************/ 33 | 34 | 35 | /* Divider */ 36 | .ui.breadcrumb .divider { 37 | display: inline-block; 38 | opacity: 0.7; 39 | margin: 0em 0.21428571rem 0em; 40 | font-size: 0.92857143em; 41 | color: rgba(0, 0, 0, 0.4); 42 | vertical-align: baseline; 43 | } 44 | 45 | /* Link */ 46 | .ui.breadcrumb a { 47 | color: #4183C4; 48 | } 49 | .ui.breadcrumb a:hover { 50 | color: #1e70bf; 51 | } 52 | 53 | /* Icon Divider */ 54 | .ui.breadcrumb .icon.divider { 55 | font-size: 0.85714286em; 56 | vertical-align: baseline; 57 | } 58 | 59 | /* Section */ 60 | .ui.breadcrumb a.section { 61 | cursor: pointer; 62 | } 63 | .ui.breadcrumb .section { 64 | display: inline-block; 65 | margin: 0em; 66 | padding: 0em; 67 | } 68 | 69 | /* Loose Coupling */ 70 | .ui.breadcrumb.segment { 71 | display: inline-block; 72 | padding: 0.78571429em 1em; 73 | } 74 | 75 | 76 | /******************************* 77 | States 78 | *******************************/ 79 | 80 | .ui.breadcrumb .active.section { 81 | font-weight: bold; 82 | } 83 | 84 | 85 | /******************************* 86 | Variations 87 | *******************************/ 88 | 89 | .ui.mini.breadcrumb { 90 | font-size: 0.78571429rem; 91 | } 92 | .ui.tiny.breadcrumb { 93 | font-size: 0.85714286rem; 94 | } 95 | .ui.small.breadcrumb { 96 | font-size: 0.92857143rem; 97 | } 98 | .ui.breadcrumb { 99 | font-size: 1rem; 100 | } 101 | .ui.large.breadcrumb { 102 | font-size: 1.14285714rem; 103 | } 104 | .ui.big.breadcrumb { 105 | font-size: 1.28571429rem; 106 | } 107 | .ui.huge.breadcrumb { 108 | font-size: 1.42857143rem; 109 | } 110 | .ui.massive.breadcrumb { 111 | font-size: 1.71428571rem; 112 | } 113 | 114 | 115 | /******************************* 116 | Theme Overrides 117 | *******************************/ 118 | 119 | 120 | 121 | /******************************* 122 | Site Overrides 123 | *******************************/ 124 | 125 | -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/breadcrumb.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Breadcrumb 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.breadcrumb{line-height:1;display:inline-block;margin:0 0;vertical-align:middle}.ui.breadcrumb:first-child{margin-top:0}.ui.breadcrumb:last-child{margin-bottom:0}.ui.breadcrumb .divider{display:inline-block;opacity:.7;margin:0 .21428571rem 0;font-size:.92857143em;color:rgba(0,0,0,.4);vertical-align:baseline}.ui.breadcrumb a{color:#4183c4}.ui.breadcrumb a:hover{color:#1e70bf}.ui.breadcrumb .icon.divider{font-size:.85714286em;vertical-align:baseline}.ui.breadcrumb a.section{cursor:pointer}.ui.breadcrumb .section{display:inline-block;margin:0;padding:0}.ui.breadcrumb.segment{display:inline-block;padding:.78571429em 1em}.ui.breadcrumb .active.section{font-weight:700}.ui.mini.breadcrumb{font-size:.78571429rem}.ui.tiny.breadcrumb{font-size:.85714286rem}.ui.small.breadcrumb{font-size:.92857143rem}.ui.breadcrumb{font-size:1rem}.ui.large.breadcrumb{font-size:1.14285714rem}.ui.big.breadcrumb{font-size:1.28571429rem}.ui.huge.breadcrumb{font-size:1.42857143rem}.ui.massive.breadcrumb{font-size:1.71428571rem} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/comment.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Comment 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.comments{margin:1.5em 0;max-width:650px}.ui.comments:first-child{margin-top:0}.ui.comments:last-child{margin-bottom:0}.ui.comments .comment{position:relative;background:0 0;margin:.5em 0 0;padding:.5em 0 0;border:none;border-top:none;line-height:1.2}.ui.comments .comment:first-child{margin-top:0;padding-top:0}.ui.comments .comment .comments{margin:0 0 .5em .5em;padding:1em 0 1em 1em}.ui.comments .comment .comments:before{position:absolute;top:0;left:0}.ui.comments .comment .comments .comment{border:none;border-top:none;background:0 0}.ui.comments .comment .avatar{display:block;width:2.5em;height:auto;float:left;margin:.2em 0 0}.ui.comments .comment .avatar img,.ui.comments .comment img.avatar{display:block;margin:0 auto;width:100%;height:100%;border-radius:.25rem}.ui.comments .comment>.content{display:block}.ui.comments .comment>.avatar~.content{margin-left:3.5em}.ui.comments .comment .author{font-size:1em;color:rgba(0,0,0,.87);font-weight:700}.ui.comments .comment a.author{cursor:pointer}.ui.comments .comment a.author:hover{color:#1e70bf}.ui.comments .comment .metadata{display:inline-block;margin-left:.5em;color:rgba(0,0,0,.4);font-size:.875em}.ui.comments .comment .metadata>*{display:inline-block;margin:0 .5em 0 0}.ui.comments .comment .metadata>:last-child{margin-right:0}.ui.comments .comment .text{margin:.25em 0 .5em;font-size:1em;word-wrap:break-word;color:rgba(0,0,0,.87);line-height:1.3}.ui.comments .comment .actions{font-size:.875em}.ui.comments .comment .actions a{cursor:pointer;display:inline-block;margin:0 .75em 0 0;color:rgba(0,0,0,.4)}.ui.comments .comment .actions a:last-child{margin-right:0}.ui.comments .comment .actions a.active,.ui.comments .comment .actions a:hover{color:rgba(0,0,0,.8)}.ui.comments>.reply.form{margin-top:1em}.ui.comments .comment .reply.form{width:100%;margin-top:1em}.ui.comments .reply.form textarea{font-size:1em;height:12em}.ui.collapsed.comments,.ui.comments .collapsed.comment,.ui.comments .collapsed.comments{display:none}.ui.threaded.comments .comment .comments{margin:-1.5em 0 -1em 1.25em;padding:3em 0 2em 2.25em;box-shadow:-1px 0 0 rgba(34,36,38,.15)}.ui.minimal.comments .comment .actions{opacity:0;position:absolute;top:0;right:0;left:auto;transition:opacity .2s ease;transition-delay:.1s}.ui.minimal.comments .comment>.content:hover>.actions{opacity:1}.ui.mini.comments{font-size:.78571429rem}.ui.tiny.comments{font-size:.85714286rem}.ui.small.comments{font-size:.92857143rem}.ui.comments{font-size:1rem}.ui.large.comments{font-size:1.14285714rem}.ui.big.comments{font-size:1.28571429rem}.ui.huge.comments{font-size:1.42857143rem}.ui.massive.comments{font-size:1.71428571rem} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/container.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Container 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */ 10 | 11 | 12 | /******************************* 13 | Container 14 | *******************************/ 15 | 16 | 17 | /* All Sizes */ 18 | .ui.container { 19 | display: block; 20 | max-width: 100% !important; 21 | } 22 | 23 | /* Mobile */ 24 | @media only screen and (max-width: 767px) { 25 | .ui.container { 26 | width: auto !important; 27 | margin-left: 1em !important; 28 | margin-right: 1em !important; 29 | } 30 | .ui.grid.container { 31 | width: auto !important; 32 | } 33 | .ui.relaxed.grid.container { 34 | width: auto !important; 35 | } 36 | .ui.very.relaxed.grid.container { 37 | width: auto !important; 38 | } 39 | } 40 | 41 | /* Tablet */ 42 | @media only screen and (min-width: 768px) and (max-width: 991px) { 43 | .ui.container { 44 | width: 723px; 45 | margin-left: auto !important; 46 | margin-right: auto !important; 47 | } 48 | .ui.grid.container { 49 | width: calc( 723px + 2rem ) !important; 50 | } 51 | .ui.relaxed.grid.container { 52 | width: calc( 723px + 3rem ) !important; 53 | } 54 | .ui.very.relaxed.grid.container { 55 | width: calc( 723px + 5rem ) !important; 56 | } 57 | } 58 | 59 | /* Small Monitor */ 60 | @media only screen and (min-width: 992px) and (max-width: 1199px) { 61 | .ui.container { 62 | width: 933px; 63 | margin-left: auto !important; 64 | margin-right: auto !important; 65 | } 66 | .ui.grid.container { 67 | width: calc( 933px + 2rem ) !important; 68 | } 69 | .ui.relaxed.grid.container { 70 | width: calc( 933px + 3rem ) !important; 71 | } 72 | .ui.very.relaxed.grid.container { 73 | width: calc( 933px + 5rem ) !important; 74 | } 75 | } 76 | 77 | /* Large Monitor */ 78 | @media only screen and (min-width: 1200px) { 79 | .ui.container { 80 | width: 1127px; 81 | margin-left: auto !important; 82 | margin-right: auto !important; 83 | } 84 | .ui.grid.container { 85 | width: calc( 1127px + 2rem ) !important; 86 | } 87 | .ui.relaxed.grid.container { 88 | width: calc( 1127px + 3rem ) !important; 89 | } 90 | .ui.very.relaxed.grid.container { 91 | width: calc( 1127px + 5rem ) !important; 92 | } 93 | } 94 | 95 | 96 | /******************************* 97 | Types 98 | *******************************/ 99 | 100 | 101 | /* Text Container */ 102 | .ui.text.container { 103 | font-family: 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif; 104 | max-width: 700px !important; 105 | line-height: 1.5; 106 | } 107 | .ui.text.container { 108 | font-size: 1.14285714rem; 109 | } 110 | 111 | /* Fluid */ 112 | .ui.fluid.container { 113 | width: 100%; 114 | } 115 | 116 | 117 | /******************************* 118 | Variations 119 | *******************************/ 120 | 121 | .ui[class*="left aligned"].container { 122 | text-align: left; 123 | } 124 | .ui[class*="center aligned"].container { 125 | text-align: center; 126 | } 127 | .ui[class*="right aligned"].container { 128 | text-align: right; 129 | } 130 | .ui.justified.container { 131 | text-align: justify; 132 | -webkit-hyphens: auto; 133 | -ms-hyphens: auto; 134 | hyphens: auto; 135 | } 136 | 137 | 138 | /******************************* 139 | Theme Overrides 140 | *******************************/ 141 | 142 | 143 | 144 | /******************************* 145 | Site Overrides 146 | *******************************/ 147 | 148 | -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/container.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Container 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.container{display:block;max-width:100%!important}@media only screen and (max-width:767px){.ui.container{width:auto!important;margin-left:1em!important;margin-right:1em!important}.ui.grid.container{width:auto!important}.ui.relaxed.grid.container{width:auto!important}.ui.very.relaxed.grid.container{width:auto!important}}@media only screen and (min-width:768px) and (max-width:991px){.ui.container{width:723px;margin-left:auto!important;margin-right:auto!important}.ui.grid.container{width:calc(723px + 2rem)!important}.ui.relaxed.grid.container{width:calc(723px + 3rem)!important}.ui.very.relaxed.grid.container{width:calc(723px + 5rem)!important}}@media only screen and (min-width:992px) and (max-width:1199px){.ui.container{width:933px;margin-left:auto!important;margin-right:auto!important}.ui.grid.container{width:calc(933px + 2rem)!important}.ui.relaxed.grid.container{width:calc(933px + 3rem)!important}.ui.very.relaxed.grid.container{width:calc(933px + 5rem)!important}}@media only screen and (min-width:1200px){.ui.container{width:1127px;margin-left:auto!important;margin-right:auto!important}.ui.grid.container{width:calc(1127px + 2rem)!important}.ui.relaxed.grid.container{width:calc(1127px + 3rem)!important}.ui.very.relaxed.grid.container{width:calc(1127px + 5rem)!important}}.ui.text.container{font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;max-width:700px!important;line-height:1.5}.ui.text.container{font-size:1.14285714rem}.ui.fluid.container{width:100%}.ui[class*="left aligned"].container{text-align:left}.ui[class*="center aligned"].container{text-align:center}.ui[class*="right aligned"].container{text-align:right}.ui.justified.container{text-align:justify;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/dimmer.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Dimmer 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.dimmable:not(body){position:relative}.ui.dimmer{display:none;position:absolute;top:0!important;left:0!important;width:100%;height:100%;text-align:center;vertical-align:middle;padding:1em;background-color:rgba(0,0,0,.85);opacity:0;line-height:1;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.5s;animation-duration:.5s;transition:background-color .5s linear;flex-direction:column;align-items:center;justify-content:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;will-change:opacity;z-index:1000}.ui.dimmer>.content{-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;color:#fff}.ui.segment>.ui.dimmer{border-radius:inherit!important}.ui.dimmer:not(.inverted)::-webkit-scrollbar-track{background:rgba(255,255,255,.1)}.ui.dimmer:not(.inverted)::-webkit-scrollbar-thumb{background:rgba(255,255,255,.25)}.ui.dimmer:not(.inverted)::-webkit-scrollbar-thumb:window-inactive{background:rgba(255,255,255,.15)}.ui.dimmer:not(.inverted)::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.35)}.animating.dimmable:not(body),.dimmed.dimmable:not(body){overflow:hidden}.dimmed.dimmable>.ui.animating.dimmer,.dimmed.dimmable>.ui.visible.dimmer,.ui.active.dimmer{display:flex;opacity:1}.ui.disabled.dimmer{width:0!important;height:0!important}.dimmed.dimmable>.ui.animating.legacy.dimmer,.dimmed.dimmable>.ui.visible.legacy.dimmer,.ui.active.legacy.dimmer{display:block}.ui[class*="top aligned"].dimmer{justify-content:flex-start}.ui[class*="bottom aligned"].dimmer{justify-content:flex-end}.ui.page.dimmer{position:fixed;transform-style:'';perspective:2000px;transform-origin:center center}body.animating.in.dimmable,body.dimmed.dimmable{overflow:hidden}body.dimmable>.dimmer{position:fixed}.blurring.dimmable>:not(.dimmer){-webkit-filter:blur(0) grayscale(0);filter:blur(0) grayscale(0);transition:.8s -webkit-filter ease;transition:.8s filter ease;transition:.8s filter ease,.8s -webkit-filter ease}.blurring.dimmed.dimmable>:not(.dimmer){-webkit-filter:blur(5px) grayscale(.7);filter:blur(5px) grayscale(.7)}.blurring.dimmable>.dimmer{background-color:rgba(0,0,0,.6)}.blurring.dimmable>.inverted.dimmer{background-color:rgba(255,255,255,.6)}.ui.dimmer>.top.aligned.content>*{vertical-align:top}.ui.dimmer>.bottom.aligned.content>*{vertical-align:bottom}.ui.inverted.dimmer{background-color:rgba(255,255,255,.85)}.ui.inverted.dimmer>.content>*{color:#fff}.ui.simple.dimmer{display:block;overflow:hidden;opacity:1;width:0%;height:0%;z-index:-100;background-color:rgba(0,0,0,0)}.dimmed.dimmable>.ui.simple.dimmer{overflow:visible;opacity:1;width:100%;height:100%;background-color:rgba(0,0,0,.85);z-index:1}.ui.simple.inverted.dimmer{background-color:rgba(255,255,255,0)}.dimmed.dimmable>.ui.simple.inverted.dimmer{background-color:rgba(255,255,255,.85)} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/embed.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Video 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */ 10 | 11 | 12 | /******************************* 13 | Types 14 | *******************************/ 15 | 16 | .ui.embed { 17 | position: relative; 18 | max-width: 100%; 19 | height: 0px; 20 | overflow: hidden; 21 | background: #DCDDDE; 22 | padding-bottom: 56.25%; 23 | } 24 | 25 | /*----------------- 26 | Embedded Content 27 | ------------------*/ 28 | 29 | .ui.embed iframe, 30 | .ui.embed embed, 31 | .ui.embed object { 32 | position: absolute; 33 | border: none; 34 | width: 100%; 35 | height: 100%; 36 | top: 0px; 37 | left: 0px; 38 | margin: 0em; 39 | padding: 0em; 40 | } 41 | 42 | /*----------------- 43 | Embed 44 | ------------------*/ 45 | 46 | .ui.embed > .embed { 47 | display: none; 48 | } 49 | 50 | /*-------------- 51 | Placeholder 52 | ---------------*/ 53 | 54 | .ui.embed > .placeholder { 55 | position: absolute; 56 | cursor: pointer; 57 | top: 0px; 58 | left: 0px; 59 | display: block; 60 | width: 100%; 61 | height: 100%; 62 | background-color: radial-gradient(transparent 45%, rgba(0, 0, 0, 0.3)); 63 | } 64 | 65 | /*-------------- 66 | Icon 67 | ---------------*/ 68 | 69 | .ui.embed > .icon { 70 | cursor: pointer; 71 | position: absolute; 72 | top: 0px; 73 | left: 0px; 74 | width: 100%; 75 | height: 100%; 76 | z-index: 2; 77 | } 78 | .ui.embed > .icon:after { 79 | position: absolute; 80 | top: 0%; 81 | left: 0%; 82 | width: 100%; 83 | height: 100%; 84 | z-index: 3; 85 | content: ''; 86 | background: radial-gradient(transparent 45%, rgba(0, 0, 0, 0.3)); 87 | opacity: 0.5; 88 | transition: opacity 0.5s ease; 89 | } 90 | .ui.embed > .icon:before { 91 | position: absolute; 92 | top: 50%; 93 | left: 50%; 94 | z-index: 4; 95 | transform: translateX(-50%) translateY(-50%); 96 | color: #FFFFFF; 97 | font-size: 6rem; 98 | text-shadow: 0px 2px 10px rgba(34, 36, 38, 0.2); 99 | transition: opacity 0.5s ease, color 0.5s ease; 100 | z-index: 10; 101 | } 102 | 103 | 104 | /******************************* 105 | States 106 | *******************************/ 107 | 108 | 109 | /*-------------- 110 | Hover 111 | ---------------*/ 112 | 113 | .ui.embed .icon:hover:after { 114 | background: radial-gradient(transparent 45%, rgba(0, 0, 0, 0.3)); 115 | opacity: 1; 116 | } 117 | .ui.embed .icon:hover:before { 118 | color: #FFFFFF; 119 | } 120 | 121 | /*-------------- 122 | Active 123 | ---------------*/ 124 | 125 | .ui.active.embed > .icon, 126 | .ui.active.embed > .placeholder { 127 | display: none; 128 | } 129 | .ui.active.embed > .embed { 130 | display: block; 131 | } 132 | 133 | 134 | /******************************* 135 | Video Overrides 136 | *******************************/ 137 | 138 | 139 | 140 | /******************************* 141 | Site Overrides 142 | *******************************/ 143 | 144 | 145 | 146 | /******************************* 147 | Variations 148 | *******************************/ 149 | 150 | .ui.square.embed { 151 | padding-bottom: 100%; 152 | } 153 | .ui[class*="4:3"].embed { 154 | padding-bottom: 75%; 155 | } 156 | .ui[class*="16:9"].embed { 157 | padding-bottom: 56.25%; 158 | } 159 | .ui[class*="21:9"].embed { 160 | padding-bottom: 42.85714286%; 161 | } 162 | -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/embed.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Video 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.embed{position:relative;max-width:100%;height:0;overflow:hidden;background:#dcddde;padding-bottom:56.25%}.ui.embed embed,.ui.embed iframe,.ui.embed object{position:absolute;border:none;width:100%;height:100%;top:0;left:0;margin:0;padding:0}.ui.embed>.embed{display:none}.ui.embed>.placeholder{position:absolute;cursor:pointer;top:0;left:0;display:block;width:100%;height:100%;background-color:radial-gradient(transparent 45%,rgba(0,0,0,.3))}.ui.embed>.icon{cursor:pointer;position:absolute;top:0;left:0;width:100%;height:100%;z-index:2}.ui.embed>.icon:after{position:absolute;top:0;left:0;width:100%;height:100%;z-index:3;content:'';background:radial-gradient(transparent 45%,rgba(0,0,0,.3));opacity:.5;transition:opacity .5s ease}.ui.embed>.icon:before{position:absolute;top:50%;left:50%;z-index:4;transform:translateX(-50%) translateY(-50%);color:#fff;font-size:6rem;text-shadow:0 2px 10px rgba(34,36,38,.2);transition:opacity .5s ease,color .5s ease;z-index:10}.ui.embed .icon:hover:after{background:radial-gradient(transparent 45%,rgba(0,0,0,.3));opacity:1}.ui.embed .icon:hover:before{color:#fff}.ui.active.embed>.icon,.ui.active.embed>.placeholder{display:none}.ui.active.embed>.embed{display:block}.ui.square.embed{padding-bottom:100%}.ui[class*="4:3"].embed{padding-bottom:75%}.ui[class*="16:9"].embed{padding-bottom:56.25%}.ui[class*="21:9"].embed{padding-bottom:42.85714286%} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/feed.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Feed 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.feed{margin:1em 0}.ui.feed:first-child{margin-top:0}.ui.feed:last-child{margin-bottom:0}.ui.feed>.event{display:flex;flex-direction:row;width:100%;padding:.21428571rem 0;margin:0;background:0 0;border-top:none}.ui.feed>.event:first-child{border-top:0;padding-top:0}.ui.feed>.event:last-child{padding-bottom:0}.ui.feed>.event>.label{display:block;flex:0 0 auto;width:2.5em;height:auto;align-self:stretch;text-align:left}.ui.feed>.event>.label .icon{opacity:1;font-size:1.5em;width:100%;padding:.25em;background:0 0;border:none;border-radius:none;color:rgba(0,0,0,.6)}.ui.feed>.event>.label img{width:100%;height:auto;border-radius:500rem}.ui.feed>.event>.label+.content{margin:.5em 0 .35714286em 1.14285714em}.ui.feed>.event>.content{display:block;flex:1 1 auto;align-self:stretch;text-align:left;word-wrap:break-word}.ui.feed>.event:last-child>.content{padding-bottom:0}.ui.feed>.event>.content a{cursor:pointer}.ui.feed>.event>.content .date{margin:-.5rem 0 0;padding:0;font-weight:400;font-size:1em;font-style:normal;color:rgba(0,0,0,.4)}.ui.feed>.event>.content .summary{margin:0;font-size:1em;font-weight:700;color:rgba(0,0,0,.87)}.ui.feed>.event>.content .summary img{display:inline-block;width:auto;height:10em;margin:-.25em .25em 0 0;border-radius:.25em;vertical-align:middle}.ui.feed>.event>.content .user{display:inline-block;font-weight:700;margin-right:0;vertical-align:baseline}.ui.feed>.event>.content .user img{margin:-.25em .25em 0 0;width:auto;height:10em;vertical-align:middle}.ui.feed>.event>.content .summary>.date{display:inline-block;float:none;font-weight:400;font-size:.85714286em;font-style:normal;margin:0 0 0 .5em;padding:0;color:rgba(0,0,0,.4)}.ui.feed>.event>.content .extra{margin:.5em 0 0;background:0 0;padding:0;color:rgba(0,0,0,.87)}.ui.feed>.event>.content .extra.images img{display:inline-block;margin:0 .25em 0 0;width:6em}.ui.feed>.event>.content .extra.text{padding:0;border-left:none;font-size:1em;max-width:500px;line-height:1.4285em}.ui.feed>.event>.content .meta{display:inline-block;font-size:.85714286em;margin:.5em 0 0;background:0 0;border:none;border-radius:0;box-shadow:none;padding:0;color:rgba(0,0,0,.6)}.ui.feed>.event>.content .meta>*{position:relative;margin-left:.75em}.ui.feed>.event>.content .meta>:after{content:'';color:rgba(0,0,0,.2);top:0;left:-1em;opacity:1;position:absolute;vertical-align:top}.ui.feed>.event>.content .meta .like{color:'';transition:.2s color ease}.ui.feed>.event>.content .meta .like:hover .icon{color:#ff2733}.ui.feed>.event>.content .meta .active.like .icon{color:#ef404a}.ui.feed>.event>.content .meta>:first-child{margin-left:0}.ui.feed>.event>.content .meta>:first-child::after{display:none}.ui.feed>.event>.content .meta a,.ui.feed>.event>.content .meta>.icon{cursor:pointer;opacity:1;color:rgba(0,0,0,.5);transition:color .1s ease}.ui.feed>.event>.content .meta a:hover,.ui.feed>.event>.content .meta a:hover .icon,.ui.feed>.event>.content .meta>.icon:hover{color:rgba(0,0,0,.95)}.ui.small.feed{font-size:.92857143rem}.ui.feed{font-size:1rem}.ui.large.feed{font-size:1.14285714rem} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/image.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Image 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.image{position:relative;display:inline-block;vertical-align:middle;max-width:100%;background-color:transparent}img.ui.image{display:block}.ui.image img,.ui.image svg{display:block;max-width:100%;height:auto}.ui.hidden.image,.ui.hidden.images{display:none}.ui.hidden.transition.image,.ui.hidden.transition.images{display:block;visibility:hidden}.ui.images>.hidden.transition{display:inline-block;visibility:hidden}.ui.disabled.image,.ui.disabled.images{cursor:default;opacity:.45}.ui.inline.image,.ui.inline.image img,.ui.inline.image svg{display:inline-block}.ui.top.aligned.image,.ui.top.aligned.image img,.ui.top.aligned.image svg,.ui.top.aligned.images .image{display:inline-block;vertical-align:top}.ui.middle.aligned.image,.ui.middle.aligned.image img,.ui.middle.aligned.image svg,.ui.middle.aligned.images .image{display:inline-block;vertical-align:middle}.ui.bottom.aligned.image,.ui.bottom.aligned.image img,.ui.bottom.aligned.image svg,.ui.bottom.aligned.images .image{display:inline-block;vertical-align:bottom}.ui.rounded.image,.ui.rounded.image>*,.ui.rounded.images .image,.ui.rounded.images .image>*{border-radius:.3125em}.ui.bordered.image img,.ui.bordered.image svg,.ui.bordered.images .image,.ui.bordered.images img,.ui.bordered.images svg,img.ui.bordered.image{border:1px solid rgba(0,0,0,.1)}.ui.circular.image,.ui.circular.images{overflow:hidden}.ui.circular.image,.ui.circular.image>*,.ui.circular.images .image,.ui.circular.images .image>*{border-radius:500rem}.ui.fluid.image,.ui.fluid.image img,.ui.fluid.image svg,.ui.fluid.images,.ui.fluid.images img,.ui.fluid.images svg{display:block;width:100%;height:auto}.ui.avatar.image,.ui.avatar.image img,.ui.avatar.image svg,.ui.avatar.images .image,.ui.avatar.images img,.ui.avatar.images svg{margin-right:.25em;display:inline-block;width:2em;height:2em;border-radius:500rem}.ui.spaced.image{display:inline-block!important;margin-left:.5em;margin-right:.5em}.ui[class*="left spaced"].image{margin-left:.5em;margin-right:0}.ui[class*="right spaced"].image{margin-left:0;margin-right:.5em}.ui.floated.image,.ui.floated.images{float:left;margin-right:1em;margin-bottom:1em}.ui.right.floated.image,.ui.right.floated.images{float:right;margin-right:0;margin-bottom:1em;margin-left:1em}.ui.floated.image:last-child,.ui.floated.images:last-child{margin-bottom:0}.ui.centered.image,.ui.centered.images{margin-left:auto;margin-right:auto}.ui.mini.image,.ui.mini.images .image,.ui.mini.images img,.ui.mini.images svg{width:35px;height:auto;font-size:.78571429rem}.ui.tiny.image,.ui.tiny.images .image,.ui.tiny.images img,.ui.tiny.images svg{width:80px;height:auto;font-size:.85714286rem}.ui.small.image,.ui.small.images .image,.ui.small.images img,.ui.small.images svg{width:150px;height:auto;font-size:.92857143rem}.ui.medium.image,.ui.medium.images .image,.ui.medium.images img,.ui.medium.images svg{width:300px;height:auto;font-size:1rem}.ui.large.image,.ui.large.images .image,.ui.large.images img,.ui.large.images svg{width:450px;height:auto;font-size:1.14285714rem}.ui.big.image,.ui.big.images .image,.ui.big.images img,.ui.big.images svg{width:600px;height:auto;font-size:1.28571429rem}.ui.huge.image,.ui.huge.images .image,.ui.huge.images img,.ui.huge.images svg{width:800px;height:auto;font-size:1.42857143rem}.ui.massive.image,.ui.massive.images .image,.ui.massive.images img,.ui.massive.images svg{width:960px;height:auto;font-size:1.71428571rem}.ui.images{font-size:0;margin:0 -.25rem 0}.ui.images .image,.ui.images>img,.ui.images>svg{display:inline-block;margin:0 .25rem .5rem} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/nag.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Nag 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */ 10 | 11 | 12 | /******************************* 13 | Nag 14 | *******************************/ 15 | 16 | .ui.nag { 17 | display: none; 18 | opacity: 0.95; 19 | position: relative; 20 | top: 0em; 21 | left: 0px; 22 | z-index: 999; 23 | min-height: 0em; 24 | width: 100%; 25 | margin: 0em; 26 | padding: 0.75em 1em; 27 | background: #555555; 28 | box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.2); 29 | font-size: 1rem; 30 | text-align: center; 31 | color: rgba(0, 0, 0, 0.87); 32 | border-radius: 0em 0em 0.28571429rem 0.28571429rem; 33 | transition: 0.2s background ease; 34 | } 35 | a.ui.nag { 36 | cursor: pointer; 37 | } 38 | .ui.nag > .title { 39 | display: inline-block; 40 | margin: 0em 0.5em; 41 | color: #FFFFFF; 42 | } 43 | .ui.nag > .close.icon { 44 | cursor: pointer; 45 | opacity: 0.4; 46 | position: absolute; 47 | top: 50%; 48 | right: 1em; 49 | font-size: 1em; 50 | margin: -0.5em 0em 0em; 51 | color: #FFFFFF; 52 | transition: opacity 0.2s ease; 53 | } 54 | 55 | 56 | /******************************* 57 | States 58 | *******************************/ 59 | 60 | 61 | /* Hover */ 62 | .ui.nag:hover { 63 | background: #555555; 64 | opacity: 1; 65 | } 66 | .ui.nag .close:hover { 67 | opacity: 1; 68 | } 69 | 70 | 71 | /******************************* 72 | Variations 73 | *******************************/ 74 | 75 | 76 | /*-------------- 77 | Static 78 | ---------------*/ 79 | 80 | .ui.overlay.nag { 81 | position: absolute; 82 | display: block; 83 | } 84 | 85 | /*-------------- 86 | Fixed 87 | ---------------*/ 88 | 89 | .ui.fixed.nag { 90 | position: fixed; 91 | } 92 | 93 | /*-------------- 94 | Bottom 95 | ---------------*/ 96 | 97 | .ui.bottom.nags, 98 | .ui.bottom.nag { 99 | border-radius: 0.28571429rem 0.28571429rem 0em 0em; 100 | top: auto; 101 | bottom: 0em; 102 | } 103 | 104 | /*-------------- 105 | White 106 | ---------------*/ 107 | 108 | .ui.inverted.nags .nag, 109 | .ui.inverted.nag { 110 | background-color: #F3F4F5; 111 | color: rgba(0, 0, 0, 0.85); 112 | } 113 | .ui.inverted.nags .nag .close, 114 | .ui.inverted.nags .nag .title, 115 | .ui.inverted.nag .close, 116 | .ui.inverted.nag .title { 117 | color: rgba(0, 0, 0, 0.4); 118 | } 119 | 120 | 121 | /******************************* 122 | Groups 123 | *******************************/ 124 | 125 | .ui.nags .nag { 126 | border-radius: 0em !important; 127 | } 128 | .ui.nags .nag:last-child { 129 | border-radius: 0em 0em 0.28571429rem 0.28571429rem; 130 | } 131 | .ui.bottom.nags .nag:last-child { 132 | border-radius: 0.28571429rem 0.28571429rem 0em 0em; 133 | } 134 | 135 | 136 | /******************************* 137 | Theme Overrides 138 | *******************************/ 139 | 140 | 141 | 142 | /******************************* 143 | User Overrides 144 | *******************************/ 145 | 146 | -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/nag.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Nag 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.nag{display:none;opacity:.95;position:relative;top:0;left:0;z-index:999;min-height:0;width:100%;margin:0;padding:.75em 1em;background:#555;box-shadow:0 1px 2px 0 rgba(0,0,0,.2);font-size:1rem;text-align:center;color:rgba(0,0,0,.87);border-radius:0 0 .28571429rem .28571429rem;transition:.2s background ease}a.ui.nag{cursor:pointer}.ui.nag>.title{display:inline-block;margin:0 .5em;color:#fff}.ui.nag>.close.icon{cursor:pointer;opacity:.4;position:absolute;top:50%;right:1em;font-size:1em;margin:-.5em 0 0;color:#fff;transition:opacity .2s ease}.ui.nag:hover{background:#555;opacity:1}.ui.nag .close:hover{opacity:1}.ui.overlay.nag{position:absolute;display:block}.ui.fixed.nag{position:fixed}.ui.bottom.nag,.ui.bottom.nags{border-radius:.28571429rem .28571429rem 0 0;top:auto;bottom:0}.ui.inverted.nag,.ui.inverted.nags .nag{background-color:#f3f4f5;color:rgba(0,0,0,.85)}.ui.inverted.nag .close,.ui.inverted.nag .title,.ui.inverted.nags .nag .close,.ui.inverted.nags .nag .title{color:rgba(0,0,0,.4)}.ui.nags .nag{border-radius:0!important}.ui.nags .nag:last-child{border-radius:0 0 .28571429rem .28571429rem}.ui.bottom.nags .nag:last-child{border-radius:.28571429rem .28571429rem 0 0} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/placeholder.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Loader 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.placeholder{position:static;overflow:hidden;-webkit-animation:placeholderShimmer 2s linear;animation:placeholderShimmer 2s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;background-color:#fff;background-image:linear-gradient(to right,rgba(0,0,0,.08) 0,rgba(0,0,0,.15) 15%,rgba(0,0,0,.08) 30%);background-size:1200px 100%;max-width:30rem}@-webkit-keyframes placeholderShimmer{0%{background-position:-1200px 0}100%{background-position:1200px 0}}@keyframes placeholderShimmer{0%{background-position:-1200px 0}100%{background-position:1200px 0}}.ui.placeholder+.ui.placeholder{margin-top:2rem}.ui.placeholder+.ui.placeholder{-webkit-animation-delay:.15s;animation-delay:.15s}.ui.placeholder+.ui.placeholder+.ui.placeholder{-webkit-animation-delay:.3s;animation-delay:.3s}.ui.placeholder+.ui.placeholder+.ui.placeholder+.ui.placeholder{-webkit-animation-delay:.45s;animation-delay:.45s}.ui.placeholder+.ui.placeholder+.ui.placeholder+.ui.placeholder+.ui.placeholder{-webkit-animation-delay:.6s;animation-delay:.6s}.ui.placeholder,.ui.placeholder .image.header:after,.ui.placeholder .line,.ui.placeholder .line:after,.ui.placeholder>:before{background-color:#fff}.ui.placeholder .image:not(.header):not(.ui){height:100px}.ui.placeholder .square.image:not(.header){height:0;overflow:hidden;padding-top:100%}.ui.placeholder .rectangular.image:not(.header){height:0;overflow:hidden;padding-top:75%}.ui.placeholder .line{position:relative;height:.85714286em}.ui.placeholder .line:after,.ui.placeholder .line:before{top:100%;position:absolute;content:'';background-color:inherit}.ui.placeholder .line:before{left:0}.ui.placeholder .line:after{right:0}.ui.placeholder .line{margin-bottom:.5em}.ui.placeholder .line:after,.ui.placeholder .line:before{height:.5em}.ui.placeholder .line:not(:first-child){margin-top:.5em}.ui.placeholder .header{position:relative;overflow:hidden}.ui.placeholder .line:nth-child(1):after{width:0%}.ui.placeholder .line:nth-child(2):after{width:50%}.ui.placeholder .line:nth-child(3):after{width:10%}.ui.placeholder .line:nth-child(4):after{width:35%}.ui.placeholder .line:nth-child(5):after{width:65%}.ui.placeholder .header .line{margin-bottom:.64285714em}.ui.placeholder .header .line:after,.ui.placeholder .header .line:before{height:.64285714em}.ui.placeholder .header .line:not(:first-child){margin-top:.64285714em}.ui.placeholder .header .line:after{width:20%}.ui.placeholder .header .line:nth-child(2):after{width:60%}.ui.placeholder .image.header .line{margin-left:3em}.ui.placeholder .image.header .line:before{width:.71428571rem}.ui.placeholder .image.header:after{display:block;height:.85714286em;content:'';margin-left:3em}.ui.placeholder .header .line:first-child,.ui.placeholder .image .line:first-child,.ui.placeholder .paragraph .line:first-child{height:.01px}.ui.placeholder .header:not(:first-child):before,.ui.placeholder .image:not(:first-child):before,.ui.placeholder .paragraph:not(:first-child):before{height:1.42857143em;content:'';display:block}.ui.inverted.placeholder{background-image:linear-gradient(to right,rgba(255,255,255,.08) 0,rgba(255,255,255,.14) 15%,rgba(255,255,255,.08) 30%)}.ui.inverted.placeholder,.ui.inverted.placeholder .image.header:after,.ui.inverted.placeholder .line,.ui.inverted.placeholder .line:after,.ui.inverted.placeholder>:before{background-color:#1b1c1d}.ui.placeholder .full.line.line.line:after{width:0%}.ui.placeholder .very.long.line.line.line:after{width:10%}.ui.placeholder .long.line.line.line:after{width:35%}.ui.placeholder .medium.line.line.line:after{width:50%}.ui.placeholder .short.line.line.line:after{width:65%}.ui.placeholder .very.short.line.line.line:after{width:80%}.ui.fluid.placeholder{max-width:none} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/rail.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Rail 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */ 10 | 11 | 12 | /******************************* 13 | Rails 14 | *******************************/ 15 | 16 | .ui.rail { 17 | position: absolute; 18 | top: 0%; 19 | width: 300px; 20 | height: 100%; 21 | } 22 | .ui.left.rail { 23 | left: auto; 24 | right: 100%; 25 | padding: 0em 2rem 0em 0em; 26 | margin: 0em 2rem 0em 0em; 27 | } 28 | .ui.right.rail { 29 | left: 100%; 30 | right: auto; 31 | padding: 0em 0em 0em 2rem; 32 | margin: 0em 0em 0em 2rem; 33 | } 34 | 35 | 36 | /******************************* 37 | Variations 38 | *******************************/ 39 | 40 | 41 | /*-------------- 42 | Internal 43 | ---------------*/ 44 | 45 | .ui.left.internal.rail { 46 | left: 0%; 47 | right: auto; 48 | padding: 0em 0em 0em 2rem; 49 | margin: 0em 0em 0em 2rem; 50 | } 51 | .ui.right.internal.rail { 52 | left: auto; 53 | right: 0%; 54 | padding: 0em 2rem 0em 0em; 55 | margin: 0em 2rem 0em 0em; 56 | } 57 | 58 | /*-------------- 59 | Dividing 60 | ---------------*/ 61 | 62 | .ui.dividing.rail { 63 | width: 302.5px; 64 | } 65 | .ui.left.dividing.rail { 66 | padding: 0em 2.5rem 0em 0em; 67 | margin: 0em 2.5rem 0em 0em; 68 | border-right: 1px solid rgba(34, 36, 38, 0.15); 69 | } 70 | .ui.right.dividing.rail { 71 | border-left: 1px solid rgba(34, 36, 38, 0.15); 72 | padding: 0em 0em 0em 2.5rem; 73 | margin: 0em 0em 0em 2.5rem; 74 | } 75 | 76 | /*-------------- 77 | Distance 78 | ---------------*/ 79 | 80 | .ui.close.rail { 81 | width: calc( 300px + 1em ); 82 | } 83 | .ui.close.left.rail { 84 | padding: 0em 1em 0em 0em; 85 | margin: 0em 1em 0em 0em; 86 | } 87 | .ui.close.right.rail { 88 | padding: 0em 0em 0em 1em; 89 | margin: 0em 0em 0em 1em; 90 | } 91 | .ui.very.close.rail { 92 | width: calc( 300px + 0.5em ); 93 | } 94 | .ui.very.close.left.rail { 95 | padding: 0em 0.5em 0em 0em; 96 | margin: 0em 0.5em 0em 0em; 97 | } 98 | .ui.very.close.right.rail { 99 | padding: 0em 0em 0em 0.5em; 100 | margin: 0em 0em 0em 0.5em; 101 | } 102 | 103 | /*-------------- 104 | Attached 105 | ---------------*/ 106 | 107 | .ui.attached.left.rail, 108 | .ui.attached.right.rail { 109 | padding: 0em; 110 | margin: 0em; 111 | } 112 | 113 | /*-------------- 114 | Sizing 115 | ---------------*/ 116 | 117 | .ui.mini.rail { 118 | font-size: 0.78571429rem; 119 | } 120 | .ui.tiny.rail { 121 | font-size: 0.85714286rem; 122 | } 123 | .ui.small.rail { 124 | font-size: 0.92857143rem; 125 | } 126 | .ui.rail { 127 | font-size: 1rem; 128 | } 129 | .ui.large.rail { 130 | font-size: 1.14285714rem; 131 | } 132 | .ui.big.rail { 133 | font-size: 1.28571429rem; 134 | } 135 | .ui.huge.rail { 136 | font-size: 1.42857143rem; 137 | } 138 | .ui.massive.rail { 139 | font-size: 1.71428571rem; 140 | } 141 | 142 | 143 | /******************************* 144 | Theme Overrides 145 | *******************************/ 146 | 147 | 148 | 149 | /******************************* 150 | Site Overrides 151 | *******************************/ 152 | 153 | -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/rail.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Rail 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.rail{position:absolute;top:0;width:300px;height:100%}.ui.left.rail{left:auto;right:100%;padding:0 2rem 0 0;margin:0 2rem 0 0}.ui.right.rail{left:100%;right:auto;padding:0 0 0 2rem;margin:0 0 0 2rem}.ui.left.internal.rail{left:0;right:auto;padding:0 0 0 2rem;margin:0 0 0 2rem}.ui.right.internal.rail{left:auto;right:0;padding:0 2rem 0 0;margin:0 2rem 0 0}.ui.dividing.rail{width:302.5px}.ui.left.dividing.rail{padding:0 2.5rem 0 0;margin:0 2.5rem 0 0;border-right:1px solid rgba(34,36,38,.15)}.ui.right.dividing.rail{border-left:1px solid rgba(34,36,38,.15);padding:0 0 0 2.5rem;margin:0 0 0 2.5rem}.ui.close.rail{width:calc(300px + 1em)}.ui.close.left.rail{padding:0 1em 0 0;margin:0 1em 0 0}.ui.close.right.rail{padding:0 0 0 1em;margin:0 0 0 1em}.ui.very.close.rail{width:calc(300px + .5em)}.ui.very.close.left.rail{padding:0 .5em 0 0;margin:0 .5em 0 0}.ui.very.close.right.rail{padding:0 0 0 .5em;margin:0 0 0 .5em}.ui.attached.left.rail,.ui.attached.right.rail{padding:0;margin:0}.ui.mini.rail{font-size:.78571429rem}.ui.tiny.rail{font-size:.85714286rem}.ui.small.rail{font-size:.92857143rem}.ui.rail{font-size:1rem}.ui.large.rail{font-size:1.14285714rem}.ui.big.rail{font-size:1.28571429rem}.ui.huge.rail{font-size:1.42857143rem}.ui.massive.rail{font-size:1.71428571rem} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/reset.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Reset 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */*,:after,:before{box-sizing:inherit}html{box-sizing:border-box}input[type=email],input[type=password],input[type=search],input[type=text]{-webkit-appearance:none;-moz-appearance:none}/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/shape.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Shape 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */ 10 | 11 | 12 | /******************************* 13 | Shape 14 | *******************************/ 15 | 16 | .ui.shape { 17 | position: relative; 18 | vertical-align: top; 19 | display: inline-block; 20 | perspective: 2000px; 21 | transition: transform 0.6s ease-in-out, left 0.6s ease-in-out, width 0.6s ease-in-out, height 0.6s ease-in-out; 22 | } 23 | .ui.shape .sides { 24 | transform-style: preserve-3d; 25 | } 26 | .ui.shape .side { 27 | opacity: 1; 28 | width: 100%; 29 | margin: 0em !important; 30 | -webkit-backface-visibility: hidden; 31 | backface-visibility: hidden; 32 | } 33 | .ui.shape .side { 34 | display: none; 35 | } 36 | .ui.shape .side * { 37 | -webkit-backface-visibility: visible !important; 38 | backface-visibility: visible !important; 39 | } 40 | 41 | 42 | /******************************* 43 | Types 44 | *******************************/ 45 | 46 | .ui.cube.shape .side { 47 | min-width: 15em; 48 | height: 15em; 49 | padding: 2em; 50 | background-color: #E6E6E6; 51 | color: rgba(0, 0, 0, 0.87); 52 | box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.3); 53 | } 54 | .ui.cube.shape .side > .content { 55 | width: 100%; 56 | height: 100%; 57 | display: table; 58 | text-align: center; 59 | -webkit-user-select: text; 60 | -moz-user-select: text; 61 | -ms-user-select: text; 62 | user-select: text; 63 | } 64 | .ui.cube.shape .side > .content > div { 65 | display: table-cell; 66 | vertical-align: middle; 67 | font-size: 2em; 68 | } 69 | 70 | 71 | /******************************* 72 | Variations 73 | *******************************/ 74 | 75 | .ui.text.shape.animating .sides { 76 | position: static; 77 | } 78 | .ui.text.shape .side { 79 | white-space: nowrap; 80 | } 81 | .ui.text.shape .side > * { 82 | white-space: normal; 83 | } 84 | 85 | 86 | /******************************* 87 | States 88 | *******************************/ 89 | 90 | 91 | /*-------------- 92 | Loading 93 | ---------------*/ 94 | 95 | .ui.loading.shape { 96 | position: absolute; 97 | top: -9999px; 98 | left: -9999px; 99 | } 100 | 101 | /*-------------- 102 | Animating 103 | ---------------*/ 104 | 105 | .ui.shape .animating.side { 106 | position: absolute; 107 | top: 0px; 108 | left: 0px; 109 | display: block; 110 | z-index: 100; 111 | } 112 | .ui.shape .hidden.side { 113 | opacity: 0.6; 114 | } 115 | 116 | /*-------------- 117 | CSS 118 | ---------------*/ 119 | 120 | .ui.shape.animating .sides { 121 | position: absolute; 122 | } 123 | .ui.shape.animating .sides { 124 | transition: transform 0.6s ease-in-out, left 0.6s ease-in-out, width 0.6s ease-in-out, height 0.6s ease-in-out; 125 | } 126 | .ui.shape.animating .side { 127 | transition: opacity 0.6s ease-in-out; 128 | } 129 | 130 | /*-------------- 131 | Active 132 | ---------------*/ 133 | 134 | .ui.shape .active.side { 135 | display: block; 136 | } 137 | 138 | 139 | /******************************* 140 | Theme Overrides 141 | *******************************/ 142 | 143 | 144 | 145 | /******************************* 146 | User Overrides 147 | *******************************/ 148 | 149 | -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/shape.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Shape 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.shape{position:relative;vertical-align:top;display:inline-block;perspective:2000px;transition:transform .6s ease-in-out,left .6s ease-in-out,width .6s ease-in-out,height .6s ease-in-out}.ui.shape .sides{transform-style:preserve-3d}.ui.shape .side{opacity:1;width:100%;margin:0!important;-webkit-backface-visibility:hidden;backface-visibility:hidden}.ui.shape .side{display:none}.ui.shape .side *{-webkit-backface-visibility:visible!important;backface-visibility:visible!important}.ui.cube.shape .side{min-width:15em;height:15em;padding:2em;background-color:#e6e6e6;color:rgba(0,0,0,.87);box-shadow:0 0 2px rgba(0,0,0,.3)}.ui.cube.shape .side>.content{width:100%;height:100%;display:table;text-align:center;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.ui.cube.shape .side>.content>div{display:table-cell;vertical-align:middle;font-size:2em}.ui.text.shape.animating .sides{position:static}.ui.text.shape .side{white-space:nowrap}.ui.text.shape .side>*{white-space:normal}.ui.loading.shape{position:absolute;top:-9999px;left:-9999px}.ui.shape .animating.side{position:absolute;top:0;left:0;display:block;z-index:100}.ui.shape .hidden.side{opacity:.6}.ui.shape.animating .sides{position:absolute}.ui.shape.animating .sides{transition:transform .6s ease-in-out,left .6s ease-in-out,width .6s ease-in-out,height .6s ease-in-out}.ui.shape.animating .side{transition:opacity .6s ease-in-out}.ui.shape .active.side{display:block} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/site.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Site 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic&subset=latin);body,html{height:100%}html{font-size:14px}body{margin:0;padding:0;overflow-x:hidden;min-width:320px;background:#fff;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-size:14px;line-height:1.4285em;color:rgba(0,0,0,.87);font-smoothing:antialiased}h1,h2,h3,h4,h5{font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;line-height:1.28571429em;margin:calc(2rem - .14285714em) 0 1rem;font-weight:700;padding:0}h1{min-height:1rem;font-size:2rem}h2{font-size:1.71428571rem}h3{font-size:1.28571429rem}h4{font-size:1.07142857rem}h5{font-size:1rem}h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child{margin-top:0}h1:last-child,h2:last-child,h3:last-child,h4:last-child,h5:last-child{margin-bottom:0}p{margin:0 0 1em;line-height:1.4285em}p:first-child{margin-top:0}p:last-child{margin-bottom:0}a{color:#4183c4;text-decoration:none}a:hover{color:#1e70bf;text-decoration:none}::-webkit-selection{background-color:#cce2ff;color:rgba(0,0,0,.87)}::-moz-selection{background-color:#cce2ff;color:rgba(0,0,0,.87)}::selection{background-color:#cce2ff;color:rgba(0,0,0,.87)}input::-webkit-selection,textarea::-webkit-selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.87)}input::-moz-selection,textarea::-moz-selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.87)}input::-moz-selection,textarea::-moz-selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.87)}input::selection,textarea::selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.87)}body ::-webkit-scrollbar{-webkit-appearance:none;width:10px;height:10px}body ::-webkit-scrollbar-track{background:rgba(0,0,0,.1);border-radius:0}body ::-webkit-scrollbar-thumb{cursor:pointer;border-radius:5px;background:rgba(0,0,0,.25);-webkit-transition:color .2s ease;transition:color .2s ease}body ::-webkit-scrollbar-thumb:window-inactive{background:rgba(0,0,0,.15)}body ::-webkit-scrollbar-thumb:hover{background:rgba(128,135,139,.8)}body .ui.inverted::-webkit-scrollbar-track{background:rgba(255,255,255,.1)}body .ui.inverted::-webkit-scrollbar-thumb{background:rgba(255,255,255,.25)}body .ui.inverted::-webkit-scrollbar-thumb:window-inactive{background:rgba(255,255,255,.15)}body .ui.inverted::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.35)} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/sticky.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Sticky 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */ 10 | 11 | 12 | /******************************* 13 | Sticky 14 | *******************************/ 15 | 16 | .ui.sticky { 17 | position: static; 18 | transition: none; 19 | z-index: 800; 20 | } 21 | 22 | 23 | /******************************* 24 | States 25 | *******************************/ 26 | 27 | 28 | /* Bound */ 29 | .ui.sticky.bound { 30 | position: absolute; 31 | left: auto; 32 | right: auto; 33 | } 34 | 35 | /* Fixed */ 36 | .ui.sticky.fixed { 37 | position: fixed; 38 | left: auto; 39 | right: auto; 40 | } 41 | 42 | /* Bound/Fixed Position */ 43 | .ui.sticky.bound.top, 44 | .ui.sticky.fixed.top { 45 | top: 0px; 46 | bottom: auto; 47 | } 48 | .ui.sticky.bound.bottom, 49 | .ui.sticky.fixed.bottom { 50 | top: auto; 51 | bottom: 0px; 52 | } 53 | 54 | 55 | /******************************* 56 | Types 57 | *******************************/ 58 | 59 | .ui.native.sticky { 60 | position: -webkit-sticky; 61 | position: -moz-sticky; 62 | position: -ms-sticky; 63 | position: -o-sticky; 64 | position: sticky; 65 | } 66 | 67 | 68 | /******************************* 69 | Theme Overrides 70 | *******************************/ 71 | 72 | 73 | 74 | /******************************* 75 | Site Overrides 76 | *******************************/ 77 | 78 | -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/sticky.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Sticky 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.sticky{position:static;transition:none;z-index:800}.ui.sticky.bound{position:absolute;left:auto;right:auto}.ui.sticky.fixed{position:fixed;left:auto;right:auto}.ui.sticky.bound.top,.ui.sticky.fixed.top{top:0;bottom:auto}.ui.sticky.bound.bottom,.ui.sticky.fixed.bottom{top:auto;bottom:0}.ui.native.sticky{position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;position:sticky} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/tab.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Tab 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */ 10 | 11 | 12 | /******************************* 13 | UI Tabs 14 | *******************************/ 15 | 16 | .ui.tab { 17 | display: none; 18 | } 19 | 20 | 21 | /******************************* 22 | States 23 | *******************************/ 24 | 25 | 26 | /*-------------------- 27 | Active 28 | ---------------------*/ 29 | 30 | .ui.tab.active, 31 | .ui.tab.open { 32 | display: block; 33 | } 34 | 35 | /*-------------------- 36 | Loading 37 | ---------------------*/ 38 | 39 | .ui.tab.loading { 40 | position: relative; 41 | overflow: hidden; 42 | display: block; 43 | min-height: 250px; 44 | } 45 | .ui.tab.loading * { 46 | position: relative !important; 47 | left: -10000px !important; 48 | } 49 | .ui.tab.loading:before, 50 | .ui.tab.loading.segment:before { 51 | position: absolute; 52 | content: ''; 53 | top: 100px; 54 | left: 50%; 55 | margin: -1.25em 0em 0em -1.25em; 56 | width: 2.5em; 57 | height: 2.5em; 58 | border-radius: 500rem; 59 | border: 0.2em solid rgba(0, 0, 0, 0.1); 60 | } 61 | .ui.tab.loading:after, 62 | .ui.tab.loading.segment:after { 63 | position: absolute; 64 | content: ''; 65 | top: 100px; 66 | left: 50%; 67 | margin: -1.25em 0em 0em -1.25em; 68 | width: 2.5em; 69 | height: 2.5em; 70 | -webkit-animation: button-spin 0.6s linear; 71 | animation: button-spin 0.6s linear; 72 | -webkit-animation-iteration-count: infinite; 73 | animation-iteration-count: infinite; 74 | border-radius: 500rem; 75 | border-color: #767676 transparent transparent; 76 | border-style: solid; 77 | border-width: 0.2em; 78 | box-shadow: 0px 0px 0px 1px transparent; 79 | } 80 | 81 | 82 | /******************************* 83 | Tab Overrides 84 | *******************************/ 85 | 86 | 87 | 88 | /******************************* 89 | User Overrides 90 | *******************************/ 91 | 92 | -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/tab.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Tab 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.tab{display:none}.ui.tab.active,.ui.tab.open{display:block}.ui.tab.loading{position:relative;overflow:hidden;display:block;min-height:250px}.ui.tab.loading *{position:relative!important;left:-10000px!important}.ui.tab.loading.segment:before,.ui.tab.loading:before{position:absolute;content:'';top:100px;left:50%;margin:-1.25em 0 0 -1.25em;width:2.5em;height:2.5em;border-radius:500rem;border:.2em solid rgba(0,0,0,.1)}.ui.tab.loading.segment:after,.ui.tab.loading:after{position:absolute;content:'';top:100px;left:50%;margin:-1.25em 0 0 -1.25em;width:2.5em;height:2.5em;-webkit-animation:button-spin .6s linear;animation:button-spin .6s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;border-radius:500rem;border-color:#767676 transparent transparent;border-style:solid;border-width:.2em;box-shadow:0 0 0 1px transparent} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/video.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.0.0 - Video 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */ 11 | 12 | 13 | /******************************* 14 | Video 15 | *******************************/ 16 | 17 | .ui.video { 18 | background-color: #dddddd; 19 | position: relative; 20 | max-width: 100%; 21 | padding-bottom: 56.25%; 22 | height: 0px; 23 | overflow: hidden; 24 | } 25 | 26 | /*-------------- 27 | Content 28 | ---------------*/ 29 | 30 | 31 | /* Placeholder Image */ 32 | .ui.video .placeholder { 33 | background-color: #333333; 34 | } 35 | 36 | /* Play Icon Overlay */ 37 | .ui.video .play { 38 | cursor: pointer; 39 | position: absolute; 40 | top: 0px; 41 | left: 0px; 42 | z-index: 10; 43 | width: 100%; 44 | height: 100%; 45 | background: transparent; 46 | -webkit-transition: background 0.2s ease; 47 | transition: background 0.2s ease; 48 | } 49 | .ui.video .play.icon:before { 50 | position: absolute; 51 | top: 50%; 52 | left: 50%; 53 | z-index: 11; 54 | -webkit-transform: translateX(-50%) translateY(-50%); 55 | -ms-transform: translateX(-50%) translateY(-50%); 56 | transform: translateX(-50%) translateY(-50%); 57 | color: rgba(255, 255, 255, 0.7); 58 | font-size: 7rem; 59 | text-shadow: 2px 2px 0px rgba(0, 0, 0, 0.15); 60 | -webkit-transition: color 0.2s ease; 61 | transition: color 0.2s ease; 62 | } 63 | .ui.video .placeholder { 64 | position: absolute; 65 | top: 0px; 66 | left: 0px; 67 | display: block; 68 | width: 100%; 69 | height: 100%; 70 | } 71 | 72 | /* IFrame Embed */ 73 | .ui.video .embed iframe, 74 | .ui.video .embed embed, 75 | .ui.video .embed object { 76 | position: absolute; 77 | border: none; 78 | width: 100%; 79 | height: 100%; 80 | top: 0px; 81 | left: 0px; 82 | margin: 0em; 83 | padding: 0em; 84 | } 85 | 86 | 87 | /******************************* 88 | States 89 | *******************************/ 90 | 91 | 92 | /*-------------- 93 | Hover 94 | ---------------*/ 95 | 96 | .ui.video .play:hover { 97 | background: rgba(0, 0, 0, 0); 98 | } 99 | .ui.video .play:hover:before { 100 | color: #ffffff; 101 | } 102 | 103 | /*-------------- 104 | Active 105 | ---------------*/ 106 | 107 | .ui.active.video .play, 108 | .ui.active.video .placeholder { 109 | display: none; 110 | } 111 | .ui.active.video .embed { 112 | display: inline; 113 | } 114 | 115 | 116 | /******************************* 117 | Video Overrides 118 | *******************************/ 119 | 120 | 121 | 122 | /******************************* 123 | Site Overrides 124 | *******************************/ 125 | 126 | -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/components/video.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.0.0 - Video 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Copyright 2014 Contributors 7 | * Released under the MIT license 8 | * http://opensource.org/licenses/MIT 9 | * 10 | */.ui.video{background-color:#ddd;position:relative;max-width:100%;padding-bottom:56.25%;height:0;overflow:hidden}.ui.video .placeholder{background-color:#333}.ui.video .play{cursor:pointer;position:absolute;top:0;left:0;z-index:10;width:100%;height:100%;background:0 0;-webkit-transition:background .2s ease;transition:background .2s ease}.ui.video .play.icon:before{position:absolute;top:50%;left:50%;z-index:11;-webkit-transform:translateX(-50%)translateY(-50%);-ms-transform:translateX(-50%)translateY(-50%);transform:translateX(-50%)translateY(-50%);color:rgba(255,255,255,.7);font-size:7rem;text-shadow:2px 2px 0 rgba(0,0,0,.15);-webkit-transition:color .2s ease;transition:color .2s ease}.ui.video .placeholder{position:absolute;top:0;left:0;display:block;width:100%;height:100%}.ui.video .embed embed,.ui.video .embed iframe,.ui.video .embed object{position:absolute;border:none;width:100%;height:100%;top:0;left:0;margin:0;padding:0}.ui.video .play:hover{background:0 0}.ui.video .play:hover:before{color:#fff}.ui.active.video .placeholder,.ui.active.video .play{display:none}.ui.active.video .embed{display:inline} -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/basic/assets/fonts/icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/basic/assets/fonts/icons.eot -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/basic/assets/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/basic/assets/fonts/icons.ttf -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/basic/assets/fonts/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/basic/assets/fonts/icons.woff -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/fonts/brand-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/fonts/brand-icons.eot -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/fonts/brand-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/fonts/brand-icons.ttf -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/fonts/brand-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/fonts/brand-icons.woff -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/fonts/brand-icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/fonts/brand-icons.woff2 -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/fonts/icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/fonts/icons.eot -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/fonts/icons.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/fonts/icons.otf -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/fonts/icons.ttf -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/fonts/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/fonts/icons.woff -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/fonts/icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/fonts/icons.woff2 -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/fonts/outline-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/fonts/outline-icons.eot -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/fonts/outline-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/fonts/outline-icons.ttf -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/fonts/outline-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/fonts/outline-icons.woff -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/fonts/outline-icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/fonts/outline-icons.woff2 -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/default/assets/images/flags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/default/assets/images/flags.png -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/github/assets/fonts/octicons-local.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/github/assets/fonts/octicons-local.ttf -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/github/assets/fonts/octicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/github/assets/fonts/octicons.ttf -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/github/assets/fonts/octicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/github/assets/fonts/octicons.woff -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/material/assets/fonts/icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/material/assets/fonts/icons.eot -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/material/assets/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/material/assets/fonts/icons.ttf -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/material/assets/fonts/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/material/assets/fonts/icons.woff -------------------------------------------------------------------------------- /Semantic-UI-2.5.0/dist/themes/material/assets/fonts/icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/Semantic-UI-2.5.0/dist/themes/material/assets/fonts/icons.woff2 -------------------------------------------------------------------------------- /art/osmType_icons/Mf_area.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | image/svg+xml 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /art/osmType_icons/Mf_node.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | image/svg+xml 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /art/osmType_icons/Mf_way.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/art/osmType_icons/Mf_way.png -------------------------------------------------------------------------------- /art/osmType_icons/README.txt: -------------------------------------------------------------------------------- 1 | These icons are taken from the OpenStreetMap Wiki. 2 | Please see: https://wiki.openstreetmap.org/wiki/File:.svg -------------------------------------------------------------------------------- /karma-jenkins.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | singleRun: true, 7 | basePath: '', 8 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 9 | plugins: [ 10 | require('karma-jasmine'), 11 | require('karma-chrome-launcher'), 12 | require('karma-jasmine-html-reporter'), 13 | require('karma-coverage'), 14 | require('@angular-devkit/build-angular/plugins/karma') 15 | ], 16 | files: [ 17 | "src/styles.css", 18 | "Semantic-UI-2.5.0/dist/semantic.css", 19 | "node_modules/semantic-ui-calendar/dist/calendar.css", 20 | "node_modules/leaflet/dist/leaflet.css", 21 | "node_modules/@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css", 22 | "node_modules/prismjs/themes/prism.css", 23 | "node_modules/prismjs/components/prism-core.js", 24 | "node_modules/jquery/dist/jquery.js", 25 | "Semantic-UI-2.5.0/dist/semantic.js", 26 | { pattern: 'Semantic-UI-2.5.0/dist/themes/default/assets/**/*.ttf', watched: false, included: false, served: true }, 27 | { pattern: 'Semantic-UI-2.5.0/dist/themes/default/assets/**/*.woff', watched: false, included: false, served: true }, 28 | { pattern: 'Semantic-UI-2.5.0/dist/themes/default/assets/**/*.woff2', watched: false, included: false, served: true }, 29 | ], 30 | client: { 31 | jasmine: { 32 | // you can add configuration options for Jasmine here 33 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 34 | // for example, you can disable the random execution with `random: false` 35 | // or set a specific seed with `seed: 4321` 36 | }, 37 | clearContext: false // leave Jasmine Spec Runner output visible in browser 38 | }, 39 | colors: false, 40 | jasmineHtmlReporter: { 41 | suppressAll: true // removes the duplicated traces 42 | }, 43 | coverageReporter: { 44 | dir: require('path').join(__dirname, './coverage'), 45 | subdir: '.', 46 | type: 'lcovonly' 47 | }, 48 | reporters: ['progress', 'kjhtml'], 49 | browsers: ['ChromiumHeadless'], 50 | restartOnFileChange: false 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | files: [ 16 | "src/styles.css", 17 | "Semantic-UI-2.5.0/dist/semantic.css", 18 | "node_modules/semantic-ui-calendar/dist/calendar.css", 19 | "node_modules/leaflet/dist/leaflet.css", 20 | "node_modules/@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css", 21 | "node_modules/prismjs/themes/prism.css", 22 | "node_modules/prismjs/components/prism-core.js", 23 | "node_modules/jquery/dist/jquery.js", 24 | "Semantic-UI-2.5.0/dist/semantic.js", 25 | { pattern: 'Semantic-UI-2.5.0/dist/themes/default/assets/**/*.ttf', watched: false, included: false, served: true }, 26 | { pattern: 'Semantic-UI-2.5.0/dist/themes/default/assets/**/*.woff', watched: false, included: false, served: true }, 27 | { pattern: 'Semantic-UI-2.5.0/dist/themes/default/assets/**/*.woff2', watched: false, included: false, served: true }, 28 | ], 29 | client: { 30 | jasmine: { 31 | // you can add configuration options for Jasmine here 32 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 33 | // for example, you can disable the random execution with `random: false` 34 | // or set a specific seed with `seed: 4321` 35 | }, 36 | }, 37 | jasmineHtmlReporter: { 38 | suppressAll: true // removes the duplicated traces 39 | }, 40 | coverageReporter: { 41 | dir: require('path').join(__dirname, './coverage/dashboard'), 42 | subdir: '.', 43 | reporters: [ 44 | { type: 'html' }, 45 | { type: 'text-summary' } 46 | ] 47 | }, 48 | reporters: ['progress', 'kjhtml'], 49 | browsers: ['Chrome'], 50 | restartOnFileChange: true 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@giscience/dashboard", 3 | "version": "1.5.5", 4 | "license": "GPL-3.0-only", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "build:prod": "ng build --configuration=production", 9 | "build:test": "ng build --configuration=test", 10 | "build-idai": "ng build --configuration=idai", 11 | "test": "ng test", 12 | "test:coverage": "ng test --code-coverage --watch=false", 13 | "test-chrome": "ng test --browsers=Chrome", 14 | "test-chromium": "ng test --browsers=Chromium", 15 | "test-chromium-headless": "ng test --browsers=ChromiumHeadless --watch=false", 16 | "lint": "ng lint", 17 | "lint:fix": "ng lint --fix", 18 | "e2e": "ng e2e" 19 | }, 20 | "dependencies": { 21 | "@angular/animations": "^19.2.3", 22 | "@angular/common": "^19.2.3", 23 | "@angular/compiler": "^19.2.3", 24 | "@angular/core": "^19.2.3", 25 | "@angular/forms": "^19.2.3", 26 | "@angular/platform-browser": "^19.2.3", 27 | "@angular/platform-browser-dynamic": "^19.2.3", 28 | "@angular/router": "^19.2.3", 29 | "@geoman-io/leaflet-geoman-free": "^2.17.0", 30 | "@giscience/ohsome-js-utils": "^0.6.5", 31 | "@turf/area": "7.2.0", 32 | "@turf/bbox": "^7.2.0", 33 | "@turf/bbox-polygon": "^7.2.0", 34 | "@turf/boolean-point-in-polygon": "^7.2.0", 35 | "@turf/centroid": "7.2.0", 36 | "@turf/envelope": "7.2.0", 37 | "@turf/helpers": "^7.2.0", 38 | "@turf/invariant": "^7.2.0", 39 | "@turf/mask": "7.2.0", 40 | "@turf/meta": "^7.2.0", 41 | "@turf/union": "^7.2.0", 42 | "chart.js": "^2.7.1", 43 | "jquery": "^3.7.1", 44 | "leaflet": "^1.9.4", 45 | "moment": "^2.30.1", 46 | "ngx-pipes": "^3.2.0", 47 | "plotly.js-dist-min": "^2.35.3", 48 | "prism-code-editor": "^3.4.0", 49 | "prismjs": "^1.30.0", 50 | "reproject": "^1.2.7", 51 | "rxjs": "^7.8.2", 52 | "semantic-ui-calendar": "^0.0.8", 53 | "tslib": "^2.5.0" 54 | }, 55 | "devDependencies": { 56 | "@angular-devkit/build-angular": "^19.2.5", 57 | "@angular-eslint/builder": "19.3.0", 58 | "@angular-eslint/eslint-plugin": "19.3.0", 59 | "@angular-eslint/eslint-plugin-template": "19.3.0", 60 | "@angular-eslint/schematics": "19.3.0", 61 | "@angular-eslint/template-parser": "19.3.0", 62 | "@angular/cli": "^19.2.5", 63 | "@angular/compiler-cli": "^19.2.3", 64 | "@types/chart.js": "^2.9.39", 65 | "@types/geojson": "7946.0.14", 66 | "@types/jasmine": "~4.3.6", 67 | "@types/leaflet": "^1.9.4", 68 | "@types/node": "~18.14.1", 69 | "@types/plotly.js-dist-min": "^2.3.4", 70 | "@types/prismjs": "^1.26.5", 71 | "@typescript-eslint/eslint-plugin": "^8.28.0", 72 | "@typescript-eslint/parser": "^8.28.0", 73 | "eslint": "^9.23.0", 74 | "eslint-plugin-unused-imports": "^4.1.4", 75 | "jasmine-core": "~4.5.0", 76 | "karma": "~6.4.4", 77 | "karma-chrome-launcher": "~3.1.0", 78 | "karma-coverage": "~2.2.0", 79 | "karma-jasmine": "~5.1.0", 80 | "karma-jasmine-html-reporter": "~2.0.0", 81 | "ts-node": "~10.9.2", 82 | "typescript": "~5.8.2" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /pipeline_config.groovy: -------------------------------------------------------------------------------- 1 | libraries{ 2 | npm 3 | rocketchat 4 | misc 5 | } -------------------------------------------------------------------------------- /scripts/.example.env: -------------------------------------------------------------------------------- 1 | # Please create a copy of this file and name it .prod.env or .test.env 2 | # Set the variables to be able to deploy the ohsome-dashboard on a remote server 3 | 4 | # do not quote the values in .env files 5 | # see https://gist.github.com/mihow/9c7f559807069a03e302605691f85572?permalink_comment_id=3898844#gistcomment-3898844 6 | 7 | # info of the remote server where the deployment server should be pulled 8 | REMOTE_HOST=some_hostname 9 | REMOTE_USER=the_username_to_log_into_remote_host 10 | REMOTE_PATH=/var/www/html/wherever_you_want_to_deploy_it/ 11 | 12 | # the deployment repository which stores the built code to be deployed 13 | TARGET_REPO=ssh://some.gitlab.or.github.repo.url 14 | 15 | # relative path from the repository root, may also be empty if the whole content of the repo should be exchanged 16 | TARGET_REPO_FOLDER=this/is/the/folder/where/the/built/dashboard/will/be/copied/to 17 | -------------------------------------------------------------------------------- /scripts/prompt_user_exit_or_continue.sh: -------------------------------------------------------------------------------- 1 | prompt_user() { 2 | local question="$1" # Capture the first argument as the question 3 | while true; do 4 | read -p "$question (y/n): " choice 5 | case "$choice" in 6 | y|Y ) 7 | echo "You chose Yes." 8 | echo 9 | return 0 # Exit the function and proceed 10 | ;; 11 | n|N ) 12 | echo "You chose No. Exiting." 13 | exit 0 # Exit the script 14 | ;; 15 | * ) 16 | echo "Invalid input. Please enter 'y' or 'n'." 17 | ;; 18 | esac 19 | done 20 | } 21 | 22 | # Example usage 23 | #prompt_user "Do you want to continue?" 24 | 25 | # import in other script 26 | 27 | ## Get the directory of the current script 28 | #SCRIPT_DIR="$(dirname "$0")" 29 | # 30 | ## import user prompt 31 | #source "$SCRIPT_DIR/prompt_user_exit_or_continue.sh" 32 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.organization=giscience 2 | sonar.projectKey=GIScience_ohsome-dashboard 3 | sonar.projectName=ohsome dashboard 4 | 5 | # settings for pull requests 6 | sonar.pullrequest.provider=github 7 | sonar.pullrequest.github.repository=GIScience/ohsome-dashboard 8 | sonar.pullrequest.github.endpoint=https://api.github.com/ 9 | 10 | sonar.exclusions=Semantic-UI-2.2.13/**,prism/** 11 | 12 | # settings for code coverage 13 | sonar.javascript.lcov.reportPaths=coverage/lcov.info 14 | sonar.coverage.exclusions=**/*.spec.ts 15 | 16 | # settings for code duplication 17 | sonar.cpd.exclusions=**/*mock*.ts 18 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | #main { 2 | padding-top: 60px; 3 | } 4 | 5 | .menu img { 6 | height: 32px; 7 | } 8 | 9 | .footer img { 10 | max-height: 90px; 11 | width: 80%; 12 | margin: 0 auto; 13 | } 14 | 15 | /* for mobile < 768px */ 16 | @media screen and (width < 768px ){ 17 | #header-message-item {display: none} 18 | } 19 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 13 | 20 | 21 |
22 | 23 | 24 |
25 | 26 | 27 |
About
28 | 72 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | import {AppComponent} from './app.component'; 3 | import {OshdbModule} from './oshdb/oshdb.module'; 4 | import {OhsomeApiMetadataProviderService} from './oshdb/ohsome-api-metadata-provider.service'; 5 | import OhsomeApiMetadataProviderServiceMock from './oshdb/ohsome-api-metadata-provider.service.mock'; 6 | import {QueryPanelComponent} from './query-panel/query-panel.component'; 7 | import {ResultPanelComponent} from './result-panel/result-panel.component'; 8 | import {ResultListDirective} from './result-panel/result-list.directive'; 9 | import {SharedModule} from './shared/shared.module'; 10 | import {OqtModule} from './oqt/oqt.module'; 11 | import {BrowserModule} from '@angular/platform-browser'; 12 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 13 | import {OqtApiMetadataProviderService} from './oqt/oqt-api-metadata-provider.service'; 14 | import OqtApiMetadataProviderServiceMock from './oqt/oqt-api-metadata-provider.service.mock'; 15 | 16 | describe('AppComponent', () => { 17 | 18 | beforeEach(async () => { 19 | await TestBed.configureTestingModule({ 20 | declarations: [ 21 | AppComponent, 22 | QueryPanelComponent, 23 | ResultPanelComponent, 24 | ResultListDirective, 25 | ], 26 | imports: [BrowserModule, 27 | SharedModule, 28 | OshdbModule, 29 | OqtModule], 30 | providers: [ 31 | { provide: OhsomeApiMetadataProviderService, useValue: OhsomeApiMetadataProviderServiceMock }, 32 | { provide: OqtApiMetadataProviderService, useValue: OqtApiMetadataProviderServiceMock }, 33 | provideHttpClient(withInterceptorsFromDi()) 34 | ] 35 | }).compileComponents(); 36 | }); 37 | 38 | it('should create the app', () => { 39 | const fixture = TestBed.createComponent(AppComponent); 40 | const app = fixture.componentInstance; 41 | expect(app).toBeTruthy(); 42 | }); 43 | 44 | it(`should have as title 'ohsome dashboard'`, () => { 45 | const fixture = TestBed.createComponent(AppComponent); 46 | const app = fixture.componentInstance; 47 | expect(app.title).toEqual('ohsome dashboard'); 48 | }); 49 | 50 | // it('should render title', () => { 51 | // const fixture = TestBed.createComponent(AppComponent); 52 | // fixture.detectChanges(); 53 | // const compiled = fixture.nativeElement as HTMLElement; 54 | // expect(compiled.querySelector('.content span')?.textContent).toContain('ng15-dashboard app is running!'); 55 | // }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import {AfterViewInit, Component} from '@angular/core'; 2 | import {OhsomeApiMetadataProviderService} from './oshdb/ohsome-api-metadata-provider.service'; 3 | declare let $; 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.css'], 9 | standalone: false 10 | }) 11 | export class AppComponent implements AfterViewInit{ 12 | title = 'ohsome dashboard'; 13 | public hasAnnouncement: boolean; 14 | public announcement: string; 15 | public currentYear: string = new Date().getFullYear().toString(); 16 | protected readonly window = window; 17 | 18 | constructor(ohsomeApiMetadataProviderService: OhsomeApiMetadataProviderService) { 19 | this.hasAnnouncement = ohsomeApiMetadataProviderService.hasOhsomeApiAnnouncement(); 20 | this.announcement = ohsomeApiMetadataProviderService.getOhsomeApiAnnouncement(); 21 | } 22 | 23 | ngAfterViewInit(): void { 24 | this.handleAnnouncementClose() 25 | } 26 | 27 | private handleAnnouncementClose() { 28 | $('#announcement .message .close') 29 | .on('click', function() { 30 | $('#announcement') 31 | .transition('fade') 32 | ; 33 | }) 34 | ; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/app/oqt/metadata-indicator.response.mock.ts: -------------------------------------------------------------------------------- 1 | export const oqtMetadataIndicatorsResponseMock = { 2 | 'api-version': '0.14.1', 3 | 'attribution': { 4 | 'url': 'https://github.com/GIScience/ohsome-quality-analyst/blob/main/data/COPYRIGHTS.md' 5 | }, 6 | 'result': [ 7 | { 8 | 'key': 'mapping-saturation', 9 | 'name': 'Mapping Saturation', 10 | 'description': 'Calculate if mapping has saturated.\nHigh saturation has been reached if the growth of the fitted curve is minimal.', 11 | 'quality-dimension': ['completeness'], 12 | 'label-description': { 13 | 'red': 'No saturation identified (Saturation ≤ 30%).', 14 | 'yellow': 'Saturation is in progress (30% < Saturation ≤ 97%).', 15 | 'green': 'High saturation has been reached (97% < Saturation ≤ 100%).', 16 | 'undefined': 'Saturation could not be calculated.' 17 | } 18 | }, 19 | { 20 | 'key': 'currentness', 21 | 'name': 'Currentness', 22 | 'description': 'Calculate the currentness.', 23 | 'quality-dimension': ['currentness'], 24 | 'label-description': { 25 | 'red': 'No saturation identified (Saturation ≤ 30%).', 26 | 'yellow': 'Saturation is in progress (30% < Saturation ≤ 97%).', 27 | 'green': 'High saturation has been reached (97% < Saturation ≤ 100%).', 28 | 'undefined': 'Saturation could not be calculated.' 29 | } 30 | }, 31 | { 32 | 'key': 'attribute-completeness', 33 | 'name': 'Attribute Completeness', 34 | 'description': 'Calculate the currentness.', 35 | 'quality-dimension': ['completeness'], 36 | 'label-description': { 37 | 'red': 'No saturation identified (Saturation ≤ 30%).', 38 | 'yellow': 'Saturation is in progress (30% < Saturation ≤ 97%).', 39 | 'green': 'High saturation has been reached (97% < Saturation ≤ 100%).', 40 | 'undefined': 'Saturation could not be calculated.' 41 | } 42 | }, 43 | { 44 | 'key': 'poi-density', 45 | 'name': 'POI Density', 46 | 'description': 'Calculate the POI Density.', 47 | 'quality-dimension': ['completeness'], 48 | 'label-description': { 49 | 'red': 'No saturation identified (Saturation ≤ 30%).', 50 | 'yellow': 'Saturation is in progress (30% < Saturation ≤ 97%).', 51 | 'green': 'High saturation has been reached (97% < Saturation ≤ 100%).', 52 | 'undefined': 'Saturation could not be calculated.' 53 | } 54 | }, 55 | ] 56 | }; 57 | -------------------------------------------------------------------------------- /src/app/oqt/oqt-api-metadata-provider.service.mock.ts: -------------------------------------------------------------------------------- 1 | import createSpyObj = jasmine.createSpyObj; 2 | import {of} from 'rxjs'; 3 | import {oqtApiMetadataResponseMock, oqtAttributesResponseMock} from './oqt-api-metadata.response.mock'; 4 | import {OqtApiMetadataProviderService} from './oqt-api-metadata-provider.service'; 5 | 6 | const OqtApiMetadataProviderServiceMock: jasmine.SpyObj = createSpyObj( 7 | 'MetadataProviderService', 8 | [ 9 | 'loadOqtApiMetadata', 10 | 'getOqtApiMetadata', 11 | 'getAttributes', 12 | 'getAttributeName', 13 | 'getAttributeDescription', 14 | 'getAttributeFilter', 15 | ]); 16 | 17 | OqtApiMetadataProviderServiceMock.loadOqtApiMetadata.and.returnValue(of(oqtApiMetadataResponseMock)) 18 | OqtApiMetadataProviderServiceMock.getOqtApiMetadata.and.returnValue(oqtApiMetadataResponseMock); 19 | OqtApiMetadataProviderServiceMock.getAttributes.and.returnValue(oqtAttributesResponseMock); 20 | 21 | export default OqtApiMetadataProviderServiceMock; 22 | 23 | -------------------------------------------------------------------------------- /src/app/oqt/oqt-api.service.mock.ts: -------------------------------------------------------------------------------- 1 | import {OqtApiService} from './oqt-api.service'; 2 | import createSpyObj = jasmine.createSpyObj; 3 | 4 | const OqtApiServiceMock: jasmine.SpyObj = createSpyObj( 5 | 'OqtApiService', 6 | [ 7 | 'get', 8 | 'post', 9 | 'getMetadata', 10 | 'getIndicator', 11 | 'getIndicatorCoverage', 12 | 'getAttributes', 13 | ]); 14 | 15 | export default OqtApiServiceMock; 16 | -------------------------------------------------------------------------------- /src/app/oqt/oqt-api.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | 3 | import {OqtApiService} from './oqt-api.service'; 4 | import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; 5 | import {environment} from '../../environments/environment'; 6 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 7 | 8 | 9 | describe('OqtApiServiceService', () => { 10 | let service: OqtApiService; 11 | let httpMock: HttpTestingController; 12 | 13 | beforeEach(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [], 16 | providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] 17 | }); 18 | service = TestBed.inject(OqtApiService); 19 | httpMock = TestBed.inject(HttpTestingController); 20 | }); 21 | 22 | afterEach(() => { 23 | httpMock.verify(); 24 | }); 25 | 26 | it('should be created', () => { 27 | expect(service).toBeTruthy(); 28 | }); 29 | 30 | it('should perform a GET request to ohsome quality api', () => { 31 | service.get('test').subscribe(() => {}); 32 | const req = httpMock.expectOne(`${environment.oqtApiRootUrl}/test`); 33 | expect(req.request.method).toEqual('GET'); 34 | }) 35 | 36 | it('should perform a GET request with params to ohsome quality api', () => { 37 | service.get('test', 'p1=v1&p2=v2').subscribe(() => {}); 38 | const req = httpMock.expectOne(`${environment.oqtApiRootUrl}/test?p1=v1&p2=v2`); 39 | expect(req.request.method).toEqual('GET'); 40 | }) 41 | 42 | it('should perform a POST request to ohsome quality api', () => { 43 | service.post('test', {param:'value'}).subscribe(() => { 44 | }); 45 | const req = httpMock.expectOne(`${environment.oqtApiRootUrl}/test`); 46 | expect(req.request.method).toEqual('POST'); 47 | expect(req.request.body).toEqual({param:'value'}); 48 | }); 49 | 50 | it('should perform a GET request to /metadata endpoint with project param', () => { 51 | service.getMetadata().subscribe(() => {}); 52 | const req = httpMock.expectOne(`${environment.oqtApiRootUrl}/metadata?project=${service.OQT_API_PROJECT}`); 53 | expect(req.request.method).toEqual('GET'); 54 | expect(req.request.params.get("project")).toEqual(service.OQT_API_PROJECT); 55 | }); 56 | 57 | it('should perform a GET request to /metadata/indicators/${indicatorKey}/coverage', () => { 58 | const indicatorKey = "road-comparison"; 59 | const inverse = false; 60 | service.getIndicatorCoverage(indicatorKey, inverse).subscribe(() => { 61 | }); 62 | const req = httpMock.expectOne(`${environment.oqtApiRootUrl}/metadata/indicators/${indicatorKey}/coverage?inverse=${inverse}`); 63 | expect(req.request.method).toEqual('GET'); 64 | }); 65 | 66 | it('should perform a GET request to /metadata/attributes', () => { 67 | service.getAttributes().subscribe(() => { 68 | }); 69 | const req = httpMock.expectOne(`${environment.oqtApiRootUrl}/metadata/attributes`); 70 | expect(req.request.method).toEqual('GET'); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /src/app/oqt/oqt-api.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; 3 | import {Observable, /*of*/} from 'rxjs'; 4 | import {IndicatorResponseJSON} from './types/types'; 5 | import {AttributeResponseJSON} from './types/types'; 6 | import {environment} from '../../environments/environment'; 7 | import {BaseResponseJSON} from './types/BaseResponseJSON'; 8 | import {MetadataResponseJSON} from './types/MetadataResponseJSON'; 9 | import {FeatureCollection, MultiPolygon, Polygon} from 'geojson'; 10 | // import {oqtApiMetadataResponseMock} from './oqt-api-metadata.response.mock'; 11 | // import {indicatorResponseMock} from './result/indicator.response.mock'; 12 | 13 | const OQT_API_ROOT_URL = environment.oqtApiRootUrl; 14 | 15 | @Injectable({ 16 | providedIn: 'root' 17 | }) 18 | export class OqtApiService { 19 | 20 | OQT_API_PROJECT: string; 21 | 22 | constructor(private http: HttpClient) { 23 | const project = null; //urlHashParamsProviderService.getHashURLSearchParams().get("project"); 24 | this.OQT_API_PROJECT = project || environment.oqtApiProject || 'core'; 25 | } 26 | 27 | get(urlPath: string, queryParams = ''): Observable { 28 | return this.http.get(OQT_API_ROOT_URL + '/' + urlPath, 29 | { 30 | params: new HttpParams({fromString: queryParams}), 31 | responseType: 'json' 32 | }); 33 | } 34 | 35 | post(urlPath: string, body?: object | null): Observable { 36 | return this.http.post(OQT_API_ROOT_URL + '/' + urlPath, 37 | body, 38 | {headers: new HttpHeaders({'Content-Type': 'application/json', 'accept': 'application/json'})}); 39 | } 40 | 41 | getMetadata(): Observable { 42 | // return of(oqtApiMetadataResponseMock); 43 | return this.get('metadata', `project=${this.OQT_API_PROJECT}`) as Observable; 44 | } 45 | 46 | getIndicator(indicatorKey, body): Observable { 47 | // return of(indicatorResponseMock); 48 | const path = `indicators/${indicatorKey}`; 49 | return this.post(path, body); 50 | } 51 | 52 | getIndicatorCoverage(indicatorKey: string, inverse: boolean = false): Observable> { 53 | const path = `metadata/indicators/${indicatorKey}/coverage`; 54 | return this.get(path, `inverse=${inverse}`) as Observable>; 55 | } 56 | 57 | getAttributes(): Observable{ 58 | const path = `metadata/attributes`; 59 | return this.get(path) as Observable; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app/oqt/oqt.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {OqtApiMetadataProviderService} from './oqt-api-metadata-provider.service'; 3 | import {OqtApiService} from './oqt-api.service'; 4 | import {OqtResultComponent} from './result/oqt-result.component'; 5 | import {IndicatorResultComponent} from './result/indicator-result/indicator-result.component'; 6 | import {SharedModule} from '../shared/shared.module'; 7 | import {OqtApiQueryFormComponent} from './query-form/oqt-api-query-form/oqt-api-query-form.component'; 8 | import {SimpleIndicatorComponent} from './query-form/oqt-api-query-form/simple-indicator/simple-indicator.component'; 9 | import { 10 | AttributeCompletenessAttributesComponent 11 | } from './query-form/oqt-api-query-form/attribute-completeness-attributes/attribute-completeness-attributes.component'; 12 | import {PrismEditorComponent} from '../shared/components/prism-editor/prism-editor.component'; 13 | import {SuiMultiSelectSearchDropdownComponent} from '../shared/components/sui-dropdown/sui-multi-select-search-dropdown.component'; 14 | import { 15 | ThematicalAccuracyIndicatorComponent 16 | } from "./query-form/oqt-api-query-form/thematical-accuracy-indicator/thematical-accuracy-indicator.component"; 17 | 18 | 19 | @NgModule({ 20 | declarations: [ 21 | OqtApiQueryFormComponent, 22 | SimpleIndicatorComponent, 23 | AttributeCompletenessAttributesComponent, 24 | ThematicalAccuracyIndicatorComponent, 25 | OqtResultComponent, 26 | IndicatorResultComponent, 27 | ], 28 | imports: [ 29 | SharedModule, 30 | PrismEditorComponent, 31 | SuiMultiSelectSearchDropdownComponent, 32 | ], 33 | providers: [ 34 | OqtApiMetadataProviderService, 35 | OqtApiService 36 | ], 37 | exports: [ 38 | OqtApiQueryFormComponent, 39 | SimpleIndicatorComponent, 40 | AttributeCompletenessAttributesComponent, 41 | OqtResultComponent 42 | ] 43 | }) 44 | export class OqtModule { 45 | } 46 | -------------------------------------------------------------------------------- /src/app/oqt/query-form/oqt-api-query-form/attribute-completeness-attributes/attribute-completeness-attributes.component.css: -------------------------------------------------------------------------------- 1 | ::ng-deep i.icon.delete::before { 2 | padding: 5px 11px 5px 5px; 3 | } 4 | 5 | ::ng-deep .editable-attributes .label { 6 | padding-right: 0 !important; 7 | } 8 | 9 | /* dropdown search with pre defined attributes + EDIT button*/ 10 | .editable-attributes { 11 | display: flex; 12 | align-items: flex-start; 13 | } 14 | 15 | .editable-attributes > div.field { 16 | flex: 1 1 auto; 17 | } 18 | 19 | .editable-attributes > button { 20 | flex: 0 0 auto; 21 | margin-left: 1rem; 22 | } 23 | 24 | .editable-attributes #custom-filter-wrapper-element{ 25 | border: 1px solid rgba(34, 36, 38, 0.15); 26 | border-radius: 0.28571429rem; 27 | padding: 0.22619048em 0 0.22619048em 0.35714286em; 28 | & .label { 29 | line-height: 1; 30 | padding: 5px 0.785714em 5px 0.785714em; 31 | margin: 0.14285714rem 0.28571429rem 0.14285714rem 0; 32 | display: flex !important; 33 | width: fit-content; 34 | & div i.icon.delete { 35 | padding-right: 20px; 36 | } 37 | } 38 | } 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/app/oqt/query-form/oqt-api-query-form/oqt-api-query-form.component.css: -------------------------------------------------------------------------------- 1 | #topicFilter { 2 | display: block; 3 | min-height: 5rem; 4 | height: 6rem; 5 | max-height: 40rem; 6 | overflow: auto; 7 | resize: vertical; 8 | width: 100%; 9 | white-space: pre-wrap; 10 | border: 1px solid lightgrey; 11 | background-color: #f9f9f9; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/oqt/query-form/oqt-api-query-form/oqt-api-query-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | 3 | import {OqtApiQueryFormComponent} from './oqt-api-query-form.component'; 4 | import {FormsModule, NgForm} from '@angular/forms'; 5 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 6 | import {OqtApiMetadataProviderService} from '../../oqt-api-metadata-provider.service'; 7 | import OqtApiMetadataProviderServiceMock from '../../oqt-api-metadata-provider.service.mock'; 8 | import {OqtModule} from '../../oqt.module'; 9 | 10 | describe('OqtApiQueryFormComponent', () => { 11 | let component: OqtApiQueryFormComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(async () => { 15 | await TestBed.configureTestingModule({ 16 | declarations: [OqtApiQueryFormComponent], 17 | imports: [FormsModule, OqtModule], 18 | providers: [ 19 | NgForm, 20 | { provide: OqtApiMetadataProviderService, useValue: OqtApiMetadataProviderServiceMock }, 21 | provideHttpClient(withInterceptorsFromDi()) 22 | ] 23 | }) 24 | .compileComponents(); 25 | 26 | fixture = TestBed.createComponent(OqtApiQueryFormComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should create', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/app/oqt/query-form/oqt-api-query-form/simple-indicator/simple-indicator.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/src/app/oqt/query-form/oqt-api-query-form/simple-indicator/simple-indicator.component.css -------------------------------------------------------------------------------- /src/app/oqt/query-form/oqt-api-query-form/simple-indicator/simple-indicator.component.html: -------------------------------------------------------------------------------- 1 |
2 | 4 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /src/app/oqt/query-form/oqt-api-query-form/simple-indicator/simple-indicator.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | 3 | import {SimpleIndicatorComponent} from './simple-indicator.component'; 4 | import {NgForm} from '@angular/forms'; 5 | import {OqtModule} from '../../../oqt.module'; 6 | 7 | describe('SimpleIndicatorComponent', () => { 8 | let component: SimpleIndicatorComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async () => { 12 | await TestBed.configureTestingModule({ 13 | imports: [OqtModule], 14 | declarations: [SimpleIndicatorComponent], 15 | providers: [ 16 | NgForm, 17 | ] 18 | 19 | }) 20 | .compileComponents(); 21 | 22 | fixture = TestBed.createComponent(SimpleIndicatorComponent); 23 | component = fixture.componentInstance; 24 | component.indicator = { 25 | "name": "Attribute Completeness", 26 | "description": "Derive the ratio of OSM features compared to features which match additional expected tags (e.g. amenity=hospital vs amenity=hospital and wheelchair=yes).", 27 | "projects": ["bkg"], 28 | "qualityDimension": "completeness", 29 | "key": "attribute-completeness", 30 | "checked": true 31 | } 32 | component.qualityDimension = component.indicator.qualityDimension; 33 | 34 | fixture.detectChanges(); 35 | }); 36 | 37 | it('should create', () => { 38 | expect(component).toBeTruthy(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/app/oqt/query-form/oqt-api-query-form/simple-indicator/simple-indicator.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, EventEmitter, Input, Output} from '@angular/core'; 2 | import {Checkbox, Indicator} from '../../../types/types'; 3 | import {ControlContainer, NgForm} from '@angular/forms'; 4 | 5 | @Component({ 6 | selector: 'app-simple-indicator', 7 | templateUrl: './simple-indicator.component.html', 8 | styleUrl: './simple-indicator.component.css', 9 | viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], 10 | standalone: false 11 | }) 12 | export class SimpleIndicatorComponent { 13 | @Input() indicator!: Checkbox; 14 | @Input() qualityDimension!: string; 15 | @Output() indicatorToggle: EventEmitter<{indicator: Indicator, state: boolean}> = new EventEmitter<{indicator: Indicator, state: boolean}>(); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/oqt/query-form/oqt-api-query-form/thematical-accuracy-indicator/thematical-accuracy-indicator.component.html: -------------------------------------------------------------------------------- 1 |
Select a specific land cover class to check. Leave empty for all classes:
2 | 3 |
4 | 10 | 11 |
12 | -------------------------------------------------------------------------------- /src/app/oqt/query-form/oqt-api-query-form/thematical-accuracy-indicator/thematical-accuracy-indicator.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | import {ThematicalAccuracyIndicatorComponent} from './thematical-accuracy-indicator.component'; 3 | import {OqtModule} from '../../../oqt.module'; 4 | import {OqtApiMetadataProviderService} from '../../../oqt-api-metadata-provider.service'; 5 | import OqtApiMetadataProviderServiceMock from '../../../oqt-api-metadata-provider.service.mock'; 6 | import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http'; 7 | import {NgForm} from '@angular/forms'; 8 | import { provideAppInitializer } from '@angular/core'; 9 | import {preparePrismToRenderOhsomeFilterLangauge} from '../../../../app.module'; 10 | import {PrismEditorComponent} from '../../../../shared/components/prism-editor/prism-editor.component'; 11 | 12 | 13 | describe('ThematicalAccuracyIndicatorComponent', () => { 14 | let component: ThematicalAccuracyIndicatorComponent; 15 | let fixture: ComponentFixture; 16 | 17 | beforeEach(async () => { 18 | await TestBed.configureTestingModule({ 19 | imports: [OqtModule, PrismEditorComponent], 20 | providers: [ 21 | NgForm, 22 | {provide: OqtApiMetadataProviderService, useValue: OqtApiMetadataProviderServiceMock}, 23 | provideAppInitializer(() => { 24 | const initializerFn = (preparePrismToRenderOhsomeFilterLangauge)(); 25 | return initializerFn(); 26 | }), 27 | provideHttpClient(withInterceptorsFromDi()) 28 | ] 29 | }) 30 | .compileComponents(); 31 | 32 | fixture = TestBed.createComponent(ThematicalAccuracyIndicatorComponent); 33 | component = fixture.componentInstance; 34 | component.indicatorKey = "land-cover-thematic-accuracy"; 35 | component.hashParams = new URLSearchParams("land-cover-thematic-accuracy--corine_land_cover_class=11"); 36 | component.selectedCorineClassIds = '11'; 37 | fixture.detectChanges(); 38 | }); 39 | 40 | it('should create', () => { 41 | expect(component).toBeTruthy(); 42 | }); 43 | 44 | 45 | describe('getCorineLandCoverClassFromUrlHashParams', () => { 46 | const hashParamsCases = [ 47 | { 48 | description: 'Valid corine land cover class', 49 | topicKey: 'lulc', 50 | hashParams: new URLSearchParams('land-cover-thematic-accuracy--corine_land_cover_class=11'), 51 | expected: '11' 52 | }, 53 | { 54 | description: 'Invalid corine land cover class', 55 | topicKey: 'lulc', 56 | hashParams: new URLSearchParams('land-cover-thematic-accuracy--corine_land_cover_class=19'), 57 | expected: '' 58 | }, 59 | { 60 | description: 'No corine land cover class selected', 61 | topicKey: 'building-lulc', 62 | hashParams: new URLSearchParams('land-cover-thematic-accuracy--corine_land_cover_class='), 63 | expected: '' 64 | } 65 | ]; 66 | 67 | hashParamsCases.forEach((hashParamsCase) => { 68 | it(hashParamsCase.description, () => { 69 | const result = component.getCorineLandCoverClassFromUrlHashParams(hashParamsCase.hashParams); 70 | expect(result).toEqual(hashParamsCase.expected); 71 | }) 72 | }) 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /src/app/oqt/query-form/oqt-api-query-form/thematical-accuracy-indicator/thematical-accuracy-indicator.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | Input, 4 | OnInit, 5 | } from '@angular/core'; 6 | import { ControlContainer, NgForm } from '@angular/forms'; 7 | 8 | 9 | @Component({ 10 | selector: 'app-thematical-accuracy-indicator', 11 | templateUrl: './thematical-accuracy-indicator.component.html', 12 | viewProviders: [{provide: ControlContainer, useExisting: NgForm}], 13 | 14 | standalone: false, 15 | }) 16 | export class ThematicalAccuracyIndicatorComponent implements OnInit { 17 | @Input({required: true}) indicatorKey!: string; 18 | @Input() selectOptions!: { label: string; value: number }[]; 19 | @Input() hashParams!: URLSearchParams; 20 | selectedCorineClassIds: string = ""; 21 | 22 | corineLandCoverClassMapLevel1 = { 23 | "1": {name: "Artificial surfaces"}, 24 | "2": {name: "Agricultural areas"}, 25 | "3": {name: "Forest and semi-natural areas"}, 26 | "4": {name: "Wetlands"}, 27 | "5": {name: "Water bodies"}, 28 | } 29 | 30 | corineLandCoverClassMapLevel2 = { 31 | "11": {name: "Urban fabric", class: 1}, 32 | "12": {name: "Industrial, commercial and transport units", class: 1}, 33 | "13": {name: "Mine, dump and construction sites", class: 1}, 34 | "14": {name: "Artificial non-agricultural vegetated areas", class: 1}, 35 | "21": {name: "Arable land", class: 2}, 36 | "22": {name: "Permanent crops", class: 2}, 37 | "23": {name: "Pastures", class: 2}, 38 | "24": {name: "Heterogeneous agricultural areas", class: 2}, 39 | "31": {name: "Forest", class: 3}, 40 | "32": {name: "Shrubs and/or herbaceous vegetation associations", class: 3}, 41 | "33": {name: "Open spaces with little or no vegetation", class: 3}, 42 | "41": {name: "Inland wetlands", class: 4}, 43 | "42": {name: "Coastal wetlands", class: 4}, 44 | "51": {name: "Inland waters", class: 5}, 45 | "52": {name: "Marine waters", class: 5}, 46 | }; 47 | 48 | 49 | 50 | corineLandCoverClassDropdownOptions = { 51 | fullTextSearch: 'exact', 52 | clearable: true, 53 | }; 54 | 55 | getCorineLandCoverClassFromUrlHashParams(hashParams: URLSearchParams): string { 56 | 57 | console.log("hashParams", hashParams); 58 | 59 | // 1. extract the corine land cover class from URL 60 | const corineLandCoverClassFromUrl: string | null = hashParams.get('land-cover-thematic-accuracy--corine_land_cover_class'); 61 | 62 | // 2.undefined should return an empty string 63 | if (corineLandCoverClassFromUrl == null) { 64 | return ""; 65 | } 66 | // 3. if the corine class is not in the map, return an empty string 67 | if (!Object.keys(this.corineLandCoverClassMapLevel2).includes(corineLandCoverClassFromUrl)) { 68 | return ""; 69 | } 70 | 71 | return corineLandCoverClassFromUrl; 72 | } 73 | ngOnInit() { 74 | this.selectedCorineClassIds = this.getCorineLandCoverClassFromUrlHashParams(this.hashParams); 75 | 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/app/oqt/result/indicator-result/indicator-result.component.css: -------------------------------------------------------------------------------- 1 | .grow { 2 | flex-grow: 1; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/oqt/result/indicator-result/indicator-result.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
{{displayQualityLabel}}
6 |

{{indicatorName}}

7 |
8 | 9 |
10 | 11 | 12 | 13 |
14 | 15 |
16 |

Sorry, we were unable to process your query!

17 |

{{error.type}}

18 |
{{detail.msg}}
19 |
20 |
21 | 22 |
23 | -------------------------------------------------------------------------------- /src/app/oqt/result/oqt-result.component.css: -------------------------------------------------------------------------------- 1 | #header #leftMenu { 2 | margin-left: 1rem; 3 | overflow: hidden; 4 | } 5 | 6 | #header #boundaryLabelsItem { 7 | min-width: 0; 8 | flex: 1 1 auto; 9 | align-items: baseline; 10 | line-height: inherit; 11 | } 12 | 13 | #header #boundaryLabelsText { 14 | text-overflow: ellipsis; 15 | overflow-x: hidden; 16 | white-space: nowrap; 17 | } 18 | 19 | .result { 20 | margin-bottom: 1rem !important; 21 | background-color: whitesmoke; 22 | } 23 | 24 | .ui.circular.label.topic { 25 | font-family: 'latin_modern_mono_prop10Rg', monospace; 26 | background-color: #E8E8E8 !important; 27 | color: rgba(0, 0, 0, 0.6) !important; 28 | margin: 0 0.5rem !important; 29 | text-transform: none !important; 30 | } 31 | 32 | app-indicator-result { 33 | display: flex; 34 | flex-grow: 1; 35 | } 36 | 37 | .indicator-container { 38 | display: flex !important; 39 | min-height: 100px; 40 | } 41 | -------------------------------------------------------------------------------- /src/app/oqt/result/oqt-result.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35 | 36 | 37 |
38 | 39 | 40 | 41 |
42 |
43 |
45 | 46 |
47 |
48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /src/app/oqt/types/AttributionJSON.d.ts: -------------------------------------------------------------------------------- 1 | interface AttributionJSON { 2 | url: string; 3 | text?: string; 4 | } 5 | 6 | export {AttributionJSON}; 7 | -------------------------------------------------------------------------------- /src/app/oqt/types/BaseResponseJSON.d.ts: -------------------------------------------------------------------------------- 1 | import {AttributionJSON} from './AttributionJSON'; 2 | 3 | interface BaseResponseJSON { 4 | apiVersion: string; 5 | attribution: AttributionJSON; 6 | } 7 | 8 | export {BaseResponseJSON}; 9 | -------------------------------------------------------------------------------- /src/app/oqt/types/ErrorResponseJSON.d.ts: -------------------------------------------------------------------------------- 1 | export interface ErrorResponseJSON { 2 | apiVersion: string; 3 | type: string; 4 | detail: { msg: string }[]; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/oqt/types/MetadataResponseJSON.d.ts: -------------------------------------------------------------------------------- 1 | import {BaseResponseJSON} from './BaseResponseJSON'; 2 | import { 3 | RawAttributeMetadata, 4 | RawIndicatorMetadata, 5 | RawProjectMetadata, 6 | RawQualityDimensionMetadata, 7 | RawReportMetadata, 8 | RawTopicMetadata 9 | } from './types'; 10 | 11 | export interface MetadataResponseJSON extends BaseResponseJSON { 12 | result: MetadataResultJSON; 13 | } 14 | 15 | interface MetadataResultJSON { 16 | attributes: Record<[topicKey: string], Record<[attributeKey:string], RawAttributeMetadata>>, 17 | topics: { [topicKey: string]: RawTopicMetadata }, 18 | indicators: { [indicatorKey: string]: RawIndicatorMetadata }, 19 | reports?: {[reportKey:string]: RawReportMetadata}, 20 | qualityDimensions: {[qualityDimensionKey: string]: RawQualityDimensionMetadata}, 21 | projects: {[projectKey:string]: RawProjectMetadata} 22 | } 23 | -------------------------------------------------------------------------------- /src/app/oqt/types/types.d.ts: -------------------------------------------------------------------------------- 1 | import {Feature, MultiPolygon, Polygon} from 'geojson'; 2 | import * as Plotly from 'plotly.js-dist-min'; 3 | import BaseResponseJSON from './BaseResponseJSON'; 4 | import {KeyValue} from '@angular/common'; 5 | 6 | /** 7 | * topic as of oqt v0.14.2 /metadata 8 | */ 9 | interface RawTopicMetadata { 10 | name: string; 11 | description: string; 12 | endpoint: string; 13 | aggregationType: string; 14 | projects: string[]; 15 | filter: string; 16 | ratioFilter?: string | null; 17 | source: string | null; 18 | indicators: string[]; 19 | } 20 | 21 | interface OqtAttribute { 22 | filter: string; 23 | name: string; 24 | description: string; 25 | } 26 | 27 | interface Topic extends RawTopicMetadata { 28 | key: string; 29 | qualityDimensions?: { [qualityDimensionKey: string]: Checkbox[] }; 30 | } 31 | 32 | 33 | interface RawIndicatorMetadata { 34 | name: string; 35 | description: string; 36 | projects: string[]; 37 | qualityDimension: string; 38 | } 39 | 40 | interface RawQualityDimensionMetadata { 41 | name: string; 42 | description: string; 43 | source: string | null; 44 | } 45 | 46 | interface RawProjectMetadata { 47 | name: string; 48 | description: string; 49 | } 50 | 51 | interface Indicator extends RawIndicatorMetadata { 52 | key: string; 53 | } 54 | 55 | // parameter information from the formValues 56 | type Params = { [paramName: string]: string | string[] | boolean } | null 57 | type IndicatorParams = KeyValue; 58 | 59 | interface RawReportMetadata { 60 | name: string; 61 | description: string; 62 | project: string; 63 | } 64 | 65 | interface RawAttributeMetadata { 66 | name: string, 67 | description: string; 68 | filter: string; 69 | } 70 | 71 | 72 | type Checkbox = T & { 73 | checked: boolean 74 | } 75 | 76 | 77 | type IndicatorResponseGeoJSON = BaseResponseJSON & Feature; 78 | type IndicatorResponseJSON = BaseResponseJSON & { result: IndicatorProperties[] } 79 | type AttributeResponseJSON = BaseResponseJSON & { result: Record> } 80 | 81 | type IndicatorLabel = 'green' | 'yellow' | 'red' | 'undefined'; 82 | 83 | interface IndicatorProperties { 84 | metadata: { name: string; description: string; projects: string[]; qualityDimension: string }, 85 | topic: { key: string; name: string; description: string; projects: string[] }, 86 | result: { 87 | description: string; 88 | timestampOQT: string; 89 | timestampOSM: string; 90 | value: number | null; 91 | label: IndicatorLabel; 92 | class: number | null; 93 | figure: Plotly.PlotlyDataLayoutConfig | null; 94 | } 95 | } 96 | 97 | 98 | export { 99 | RawTopicMetadata, 100 | RawIndicatorMetadata, 101 | RawReportMetadata, 102 | RawQualityDimensionMetadata, 103 | RawProjectMetadata, 104 | RawAttributeMetadata, 105 | Topic, 106 | Indicator, 107 | Checkbox, 108 | IndicatorResponseGeoJSON, 109 | IndicatorResponseJSON, 110 | IndicatorLabel, 111 | IndicatorProperties, 112 | OqtAttribute, 113 | AttributeResponseJSON, 114 | IndicatorParams, 115 | Params 116 | }; 117 | -------------------------------------------------------------------------------- /src/app/oshdb/ohsome-api-metadata-provider.service.mock.ts: -------------------------------------------------------------------------------- 1 | import createSpyObj = jasmine.createSpyObj; 2 | import {of} from 'rxjs'; 3 | import {ohsomeApiMetadataResponse} from './ohsome-api-metadata.response.mock'; 4 | 5 | const OhsomeApiMetadataProviderServiceMock = createSpyObj( 6 | 'MetadataProviderService', 7 | [ 8 | 'loadOhsomeMetaData', 9 | 'getOhsomeMetadataResponse', 10 | 'hasOhsomeApiAnnouncement', 11 | 'getOhsomeApiAnnouncement' 12 | ]); 13 | 14 | OhsomeApiMetadataProviderServiceMock.loadOhsomeMetaData.and.returnValue(of(ohsomeApiMetadataResponse)) 15 | OhsomeApiMetadataProviderServiceMock.getOhsomeMetadataResponse.and.returnValue(ohsomeApiMetadataResponse); 16 | OhsomeApiMetadataProviderServiceMock.hasOhsomeApiAnnouncement.and.returnValue(true); 17 | OhsomeApiMetadataProviderServiceMock.getOhsomeApiAnnouncement.and.returnValue({"Announce": "Hello World"}); 18 | 19 | 20 | export default OhsomeApiMetadataProviderServiceMock; 21 | -------------------------------------------------------------------------------- /src/app/oshdb/ohsome-api-metadata-provider.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {OhsomeApi} from '@giscience/ohsome-js-utils'; 3 | import {OhsomeApiService} from './ohsome-api.service'; 4 | import {catchError, Observable, of, retry, tap, throwError} from 'rxjs'; 5 | import MetadataResponse = OhsomeApi.v1.response.MetadataResponse; 6 | 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class OhsomeApiMetadataProviderService { 12 | private ohsomeMetadataResponse: MetadataResponse; 13 | private ohsomeApiAnnouncement = ''; 14 | public ohsomeApiAvailable = false; 15 | 16 | constructor(private ohsomeApiService: OhsomeApiService) { 17 | } 18 | 19 | public getOhsomeMetadataResponse(): MetadataResponse | undefined { 20 | return this.ohsomeMetadataResponse; 21 | } 22 | 23 | public hasOhsomeApiAnnouncement(): boolean { 24 | return this.ohsomeApiAnnouncement.trim() !== ''; 25 | } 26 | 27 | public getOhsomeApiAnnouncement(): string { 28 | return this.ohsomeApiAnnouncement; 29 | } 30 | 31 | loadOhsomeMetadata(): Observable { 32 | // return of(ohsomeApiMetadataResponse) 33 | return this.ohsomeApiService.get('metadata') 34 | .pipe( 35 | retry({count: 2, delay: 2000, resetOnSuccess: true}), 36 | tap((response) => { 37 | // add custom logic here 38 | if (MetadataResponse.isMetadataResponseJSON(response)) { 39 | this.ohsomeMetadataResponse = new MetadataResponse(response); 40 | this.ohsomeApiAvailable = true; 41 | } else { 42 | this.ohsomeApiAvailable = false; 43 | } 44 | }), 45 | catchError((error) => { 46 | this.ohsomeApiAvailable = false; 47 | if (error.status === 0) { 48 | // A client-side or network error occurred. Handle it accordingly. 49 | console.error('An error occurred:', error.error); 50 | } else { 51 | // The backend returned an unsuccessful response code. 52 | // The response body may contain clues as to what went wrong. 53 | console.error( 54 | `Backend returned code ${error.status}, body was: `, error.error); 55 | } 56 | // Return an observable with a user-facing error message. 57 | return throwError( 58 | () => new Error('The ohsome API did not respond with a metadata response as expected.') 59 | ); 60 | }) 61 | ) 62 | } 63 | 64 | loadOhsomeApiAnnouncement() { 65 | return this.ohsomeApiService.getOhsomeApiAnnouncement() 66 | .pipe( 67 | tap(response => { 68 | this.ohsomeApiAnnouncement = response['Announce'] || ''; 69 | }), 70 | catchError((err) => { 71 | console.log(err.error); 72 | // if we cannot retrieve the announcement, we continue with an empty message 73 | return of({"Announce": ''}) 74 | }) 75 | ) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/app/oshdb/ohsome-api-metadata.response.mock.ts: -------------------------------------------------------------------------------- 1 | export const ohsomeApiMetadataResponse = { 2 | "attribution" : { 3 | "url" : "https://ohsome.org/copyrights", 4 | "text" : "© OpenStreetMap contributors" 5 | }, 6 | "apiVersion" : "1.9.0", 7 | "timeout" : 600.0, 8 | "extractRegion" : { 9 | "spatialExtent" : { 10 | "type" : "Polygon", 11 | "coordinates" : [ [ [ -180.0, -90.0 ], [ 180.0, -90.0 ], [ 180.0, 90.0 ], [ -180.0, 90.0 ], [ -180.0, -90.0 ] ] ] 12 | }, 13 | "temporalExtent" : { 14 | "fromTimestamp" : "2007-10-08T00:00:00Z", 15 | "toTimestamp" : "2023-03-10T15:00Z" 16 | }, 17 | "replicationSequenceNumber" : 91952 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/app/oshdb/ohsome-api.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | 3 | import {OhsomeApiService} from './ohsome-api.service'; 4 | import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; 5 | import {environment} from '../../environments/environment'; 6 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 7 | 8 | let service: OhsomeApiService; 9 | let httpMock: HttpTestingController; 10 | 11 | describe('OhsomeApiService', () => { 12 | beforeEach(() => { 13 | TestBed.configureTestingModule({ 14 | imports: [], 15 | providers: [OhsomeApiService, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] 16 | }); 17 | 18 | service = TestBed.inject(OhsomeApiService); 19 | httpMock = TestBed.inject(HttpTestingController); 20 | }); 21 | 22 | afterEach(() => { 23 | httpMock.verify(); 24 | }); 25 | 26 | it('should be created', () => { 27 | expect(service).toBeTruthy(); 28 | }); 29 | 30 | it('should perform a GET request to ohsome API to a specified endpoint', () => { 31 | service.get('test').subscribe(() => { 32 | }); 33 | const req = httpMock.expectOne(`${environment.oshdbRestApiRootUrl}/test`); 34 | expect(req.request.method).toEqual('GET'); 35 | }); 36 | 37 | it('should perform a GET request to ohsome API with query params', () => { 38 | service.get('test2', 'param=value¶m2=value2').subscribe(() => { 39 | }); 40 | const req = httpMock.expectOne(`${environment.oshdbRestApiRootUrl}/test2?param=value¶m2=value2`); 41 | expect(req.request.method).toEqual('GET'); 42 | }); 43 | 44 | it('should perform a POST request to ohsome API', () => { 45 | service.post('test', 'param=value').subscribe(() => { 46 | }); 47 | const req = httpMock.expectOne(`${environment.oshdbRestApiRootUrl}/test`); 48 | expect(req.request.method).toEqual('POST'); 49 | expect(req.request.body).toEqual('param=value'); 50 | }); 51 | 52 | it('should perform a GET request to the ohsome dashboard status page', () => { 53 | service.getOhsomeApiAnnouncement().subscribe(() => { 54 | }); 55 | const req = httpMock.expectOne(environment.announcementUrl); 56 | expect(req.request.method).toEqual('GET'); 57 | }); 58 | 59 | }); 60 | -------------------------------------------------------------------------------- /src/app/oshdb/ohsome-api.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; 3 | import {environment} from '../../environments/environment'; 4 | import {Observable} from 'rxjs'; 5 | import {OhsomeApi} from '@giscience/ohsome-js-utils'; 6 | import ResponseJSON = OhsomeApi.v1.format.ResponseJSON; 7 | 8 | const OHSOME_API_ROOT_URL = environment.oshdbRestApiRootUrl; 9 | const OHSOME_API_ANNOUNCEMENT_URL = environment.announcementUrl; 10 | 11 | @Injectable() 12 | export class OhsomeApiService { 13 | constructor(private http: HttpClient) { 14 | } 15 | 16 | get(urlPath: string, queryParams = ''): Observable { 17 | return this.http.get(OHSOME_API_ROOT_URL + '/' + urlPath, 18 | { 19 | params: new HttpParams({fromString: queryParams}), 20 | responseType: 'json' 21 | }); 22 | } 23 | 24 | post(urlPath: string, queryParams?: string): Observable { 25 | return this.http.post(OHSOME_API_ROOT_URL + '/' + urlPath, 26 | queryParams, 27 | {headers: new HttpHeaders({'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'})}); 28 | } 29 | 30 | getOhsomeApiAnnouncement() { 31 | return this.http.get(OHSOME_API_ANNOUNCEMENT_URL); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/oshdb/oshdb.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {ResultComponent} from './result/result.component'; 3 | import {OhsomeApiService} from './ohsome-api.service'; 4 | import {SimpleChartComponent} from './result/simple-chart/simple-chart.component'; 5 | import {SimpleGroupbyResultComponent} from './result/simple-groupby-result/simple-groupby-result.component'; 6 | import {SimpleResultComponent} from './result/simple-result/simple-result.component'; 7 | import {NgDatePipesModule} from 'ngx-pipes'; 8 | import {TimePeriodPickerInputComponent} from './query-form/time-period-picker-input/time-period-picker-input.component'; 9 | import {OhsomeApiQueryFormComponent} from './query-form/ohsome-api-query-form/ohsome-api-query-form.component'; 10 | import {SharedModule} from '../shared/shared.module'; 11 | 12 | 13 | @NgModule({ 14 | imports: [ 15 | SharedModule, 16 | NgDatePipesModule, 17 | ], 18 | declarations: [ 19 | ResultComponent, 20 | SimpleChartComponent, 21 | SimpleGroupbyResultComponent, 22 | SimpleResultComponent, 23 | TimePeriodPickerInputComponent, 24 | OhsomeApiQueryFormComponent, 25 | ], 26 | exports: [ 27 | OhsomeApiQueryFormComponent, 28 | ResultComponent, 29 | SimpleChartComponent, 30 | SimpleGroupbyResultComponent, 31 | SimpleResultComponent, 32 | ], 33 | providers: [ 34 | OhsomeApiService, 35 | ] 36 | }) 37 | export class OshdbModule { 38 | } 39 | -------------------------------------------------------------------------------- /src/app/oshdb/query-form/ohsome-api-query-form/ohsome-api-query-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | 3 | import {OhsomeApiQueryFormComponent} from './ohsome-api-query-form.component'; 4 | import {OhsomeApiMetadataProviderService} from '../../ohsome-api-metadata-provider.service'; 5 | import OhsomeApiMetadataProviderServiceMock from '../../ohsome-api-metadata-provider.service.mock'; 6 | import {OshdbModule} from '../../oshdb.module'; 7 | import {NgForm} from '@angular/forms'; 8 | 9 | describe('OhsomeApiQueryFormComponent', () => { 10 | let component: OhsomeApiQueryFormComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async () => { 14 | await TestBed.configureTestingModule({ 15 | imports: [OshdbModule], 16 | declarations: [OhsomeApiQueryFormComponent], 17 | providers: [ 18 | NgForm, 19 | {provide: OhsomeApiMetadataProviderService, useValue: OhsomeApiMetadataProviderServiceMock} 20 | ] 21 | }) 22 | .compileComponents(); 23 | 24 | fixture = TestBed.createComponent(OhsomeApiQueryFormComponent); 25 | component = fixture.componentInstance; 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/app/oshdb/query-form/time-period-picker-input/time-period-picker-input.component.css: -------------------------------------------------------------------------------- 1 | .bottom.attached.fields { 2 | margin-bottom: 0; 3 | } 4 | 5 | #startfield,#endfield{ 6 | width: unset; 7 | flex-grow: 1; 8 | flex-shrink: 1; 9 | flex-basis: 0; 10 | /*min-width: 33.333%;*/ 11 | /*transition: all 0.5s ease !important; 12 | -webkit-transition: all 0.5s ease !important;*/ 13 | } 14 | 15 | input[name=start],input[name=end]{ 16 | transition: all 0.5s ease !important; 17 | -webkit-transition: all 0.5s ease !important; 18 | padding: 0.67em 0.9em !important; 19 | } 20 | 21 | input[name=start]:focus,input[name=end]:focus{ 22 | width: 13em !important; 23 | } 24 | 25 | input[name=start]::placeholder { 26 | color: black; 27 | opacity: 1; 28 | } 29 | -------------------------------------------------------------------------------- /src/app/oshdb/query-form/time-period-picker-input/time-period-picker-input.component.html: -------------------------------------------------------------------------------- 1 |

Time Interval

2 |
3 |
4 | 5 |
6 |
7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 |
15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 | 25 |
26 |
27 | 28 | 31 |
32 |
33 |
34 |
35 | Be aware of these warnings! 36 |
37 | Ignoring these may lead to unexpected results. 38 |
    39 |
  • End time must not be lower than start time.
  • 40 |
  • Period is too small for this large timerange.
  • 41 |
42 |
43 | -------------------------------------------------------------------------------- /src/app/oshdb/result/result.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 60 | 61 |
62 | 63 |
64 |
{{error | json}}
65 |
66 |
67 |

Empty result.

68 |
69 | 70 | 71 | 74 | 77 |
78 | -------------------------------------------------------------------------------- /src/app/oshdb/result/result.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | 3 | import {ResultComponent} from './result.component'; 4 | import {OshdbModule} from '../oshdb.module'; 5 | import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http'; 6 | import {OhsomeApiMetadataProviderService} from '../ohsome-api-metadata-provider.service'; 7 | import OhsomeApiMetadataProviderServiceMock from '../ohsome-api-metadata-provider.service.mock'; 8 | 9 | describe('ResultComponent', () => { 10 | let component: ResultComponent; 11 | let fixture: ComponentFixture; 12 | 13 | const formValuesMock = { 14 | 'keys': 'natural', 15 | 'types': ['node'] 16 | } 17 | 18 | beforeEach(async () => { 19 | await TestBed.configureTestingModule({ 20 | declarations: [ResultComponent], 21 | imports: [ 22 | OshdbModule 23 | ], 24 | providers: [ 25 | {provide: OhsomeApiMetadataProviderService, useValue: OhsomeApiMetadataProviderServiceMock}, 26 | provideHttpClient(withInterceptorsFromDi())] 27 | }) 28 | .compileComponents(); 29 | 30 | fixture = TestBed.createComponent(ResultComponent); 31 | 32 | component = fixture.componentInstance; 33 | component.formValues = formValuesMock; 34 | component.boundaryType = 'bpoly'; 35 | fixture.detectChanges(); 36 | 37 | const testContainer = document.createElement('div'); 38 | testContainer.id = 'test-container'; 39 | testContainer.appendChild(fixture.nativeElement); 40 | document.body.appendChild(testContainer); 41 | }); 42 | 43 | afterEach(() => { 44 | document.getElementById('test-container')?.remove(); 45 | TestBed.resetTestingModule(); 46 | }); 47 | 48 | it('should create', () => { 49 | expect(component).toBeTruthy(); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /src/app/oshdb/result/result.utils.ts: -------------------------------------------------------------------------------- 1 | import {GrowthRateCssClass} from '../../shared/shared-types'; 2 | 3 | /** 4 | * Calculates a growth rate in percent. Value between 0..1. 5 | * growthRate = ((current - past) / past) 6 | * @param {number} past 7 | * @param {number} current 8 | * @returns {number} growth rate 9 | */ 10 | function growthRate(past: number, current: number) { 11 | if (past === 0) { 12 | return null; 13 | } 14 | return ((current - past) / past); 15 | } 16 | 17 | function shareOf(part: number, whole: number) { 18 | if (whole === 0) { 19 | return null; 20 | } 21 | return (part / whole); 22 | } 23 | 24 | function percentFormatter(percent: number | null): string { 25 | if (percent == null) { 26 | return '-- %'; 27 | } 28 | 29 | percent *= 100; 30 | 31 | let decimals = 0; 32 | let smallSign = ''; 33 | 34 | if (Math.abs(percent) < 10) { 35 | decimals = 2; 36 | if (Math.abs(percent) < 0.01 && Math.abs(percent) > 0) { 37 | smallSign = (percent > 0) ? '< ' : '> -'; 38 | return smallSign + '0.01'; 39 | } 40 | } else if (Math.abs(percent) < 100) { 41 | decimals = 1; 42 | } 43 | 44 | return percent.toFixed(decimals) + ' %'; 45 | } 46 | 47 | function computeGrowthRateCssClass(gRate: number | null): GrowthRateCssClass { 48 | 49 | if (gRate === null || gRate === undefined) { 50 | return ''; 51 | } else if (gRate > 0) { 52 | return 'up'; 53 | } else if (gRate < 0) { 54 | return 'down'; 55 | } else { 56 | // (gRate === 0) 57 | return 'right'; 58 | } 59 | } 60 | 61 | export { 62 | shareOf, 63 | percentFormatter, 64 | growthRate, 65 | computeGrowthRateCssClass 66 | }; 67 | -------------------------------------------------------------------------------- /src/app/oshdb/result/simple-chart/simple-chart.component.css: -------------------------------------------------------------------------------- 1 | div { 2 | width: 100%; 3 | height: calc(50vh); 4 | min-height: 150px; 5 | } 6 | 7 | @media (orientation: portrait) { 8 | div { 9 | width: 100%; 10 | height: 25vh; 11 | min-height: 150px; 12 | } 13 | } 14 | 15 | canvas { 16 | width: 100%; 17 | height: 100%; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/oshdb/result/simple-chart/simple-chart.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{chart}} 4 |
5 | -------------------------------------------------------------------------------- /src/app/oshdb/result/simple-chart/simple-chart.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import {SimpleChartComponent} from './simple-chart.component'; 4 | 5 | describe('SimpleChartComponent', () => { 6 | let component: SimpleChartComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SimpleChartComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SimpleChartComponent); 18 | component = fixture.componentInstance; 19 | // fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/oshdb/result/simple-chart/simple-chart.component.ts: -------------------------------------------------------------------------------- 1 | import {AfterViewInit, Component, ElementRef, Input} from '@angular/core'; 2 | import {Chart} from 'chart.js'; 3 | 4 | declare let $: any; 5 | 6 | @Component({ 7 | selector: 'app-simple-chart', 8 | templateUrl: './simple-chart.component.html', 9 | styleUrls: ['./simple-chart.component.css'], 10 | standalone: false 11 | }) 12 | export class SimpleChartComponent implements AfterViewInit { 13 | public chart: any; 14 | 15 | @Input() type: string; 16 | @Input() chartJsData: any; 17 | @Input() chartJsOptions: any; 18 | @Input() chartJsPlugins: any; 19 | 20 | constructor(private elemRef: ElementRef) { } 21 | 22 | ngAfterViewInit(){// 23 | this.createChart(); 24 | } 25 | 26 | createChart() { 27 | this.chart = new Chart(this.elemRef.nativeElement.querySelector('canvas'), { 28 | type: this.type, 29 | data: this.chartJsData, 30 | options: this.chartJsOptions || Chart.defaults['doughnut'], 31 | plugins: this.chartJsPlugins 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/oshdb/result/simple-groupby-result/simple-groupby-result.component.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 550px) { 2 | thead,th,td { 3 | font-size: 0.7em; 4 | } 5 | } 6 | 7 | @media screen and (min-width: 551px) and (max-width: 991px) { 8 | thead { 9 | font-size: 0.7em; 10 | } 11 | } 12 | 13 | @media screen and (min-width: 992px) and (max-width: 1200px) { 14 | thead { 15 | font-size: 0.9em; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/oshdb/result/simple-groupby-result/simple-groupby-result.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 7 |
8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 30 | 31 | 32 |
NAME{{moment(chartJsDataStart[0].x).format('YYYY-MM-DD')}}{{moment(chartJsDataEnd[0].x).format('YYYY-MM-DD')}}GROWTH RATE
{{chartJsData.datasets[i].label}}{{kFormatter(chartJsDataStart[i].y, unit)}}{{kFormatter(chartJsDataEnd[i].y, unit)}} 28 | {{percentFormatter(growthRate(chartJsDataStart[i].y, chartJsDataEnd[i].y))}} 29 |
33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /src/app/oshdb/result/simple-groupby-result/simple-groupby-result.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 2 | 3 | import {SimpleGroupbyResultComponent} from './simple-groupby-result.component'; 4 | import {SimpleChartComponent} from '../simple-chart/simple-chart.component'; 5 | import {simpleGroupbyResultMockInputs} from './simple-groupby-result.mockdata'; 6 | import {OhsomeApi} from '@giscience/ohsome-js-utils'; 7 | import GroupByResponse = OhsomeApi.v1.response.GroupByResponse; 8 | 9 | describe('SimpleGroupbyResultComponent', () => { 10 | let component: SimpleGroupbyResultComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(waitForAsync(() => { 14 | TestBed.configureTestingModule({ 15 | declarations: [SimpleGroupbyResultComponent, SimpleChartComponent] 16 | }) 17 | .compileComponents(); 18 | })); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(SimpleGroupbyResultComponent); 22 | component = fixture.componentInstance; 23 | component.formValues = simpleGroupbyResultMockInputs.formValuesMock; 24 | component.response = new GroupByResponse(simpleGroupbyResultMockInputs.ohsomeApiSimpleGroupByResult); 25 | // fixture.detectChanges(); 26 | }); 27 | 28 | it('should create', () => { 29 | expect(component).toBeTruthy(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/app/oshdb/result/simple-groupby-result/simple-groupby-result.mockdata.ts: -------------------------------------------------------------------------------- 1 | const simpleGroupbyResultMockInputs = { 2 | formValuesMock: { 3 | 'keys': 'natural', 4 | 'types': ['node'], 5 | 'measure': 'count', 6 | 'groupBy': 'boundary' 7 | }, 8 | ohsomeApiSimpleGroupByResult: { 9 | "attribution": { 10 | "url": "https://ohsome.org/copyrights", 11 | "text": "© OpenStreetMap contributors" 12 | }, 13 | "apiVersion": "1.9.0", 14 | "groupByResult": [ 15 | { 16 | "groupByObject": "boundary1", 17 | "result": [ 18 | { 19 | "timestamp": "2014-01-01T00:00:00Z", 20 | "value": 42 21 | }, 22 | { 23 | "timestamp": "2015-01-01T00:00:00Z", 24 | "value": 42 25 | }, 26 | { 27 | "timestamp": "2016-01-01T00:00:00Z", 28 | "value": 43 29 | }, 30 | { 31 | "timestamp": "2017-01-01T00:00:00Z", 32 | "value": 43 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | } 39 | 40 | export { 41 | simpleGroupbyResultMockInputs 42 | } 43 | -------------------------------------------------------------------------------- /src/app/oshdb/result/simple-result/simple-result.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/src/app/oshdb/result/simple-result/simple-result.component.css -------------------------------------------------------------------------------- /src/app/oshdb/result/simple-result/simple-result.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 7 |
8 | 9 | 10 |
11 |
13 |
14 |
15 |
{{kFormatter(chartJsDataStart[0].y, unit)}}
16 |
{{moment(chartJsDataStart[0].x).format('YYYY-MM-DD')}}
17 |
18 |
19 |
{{kFormatter(chartJsDataEnd[0].y, unit)}}
20 |
{{moment(chartJsDataEnd[0].x).format('YYYY-MM-DD')}}
21 |
22 |
23 |
{{percentFormatter(growthRate)}}
26 |
growth rate
27 |
28 |
29 |
30 |
31 |
32 |
33 | -------------------------------------------------------------------------------- /src/app/oshdb/result/simple-result/simple-result.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 2 | 3 | import {SimpleResultComponent} from './simple-result.component'; 4 | import {SimpleChartComponent} from '../simple-chart/simple-chart.component'; 5 | import {ResultComponent} from '../result.component'; 6 | import {simpleResultMockInputs} from './simple-result.mockdata'; 7 | 8 | describe('SimpleResultComponent', () => { 9 | let component: SimpleResultComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(waitForAsync(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [SimpleResultComponent, SimpleChartComponent] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(SimpleResultComponent); 21 | component = fixture.componentInstance; 22 | component.unit = simpleResultMockInputs.unit; 23 | component.chartJsData = ResultComponent.prototype.createChartJsData(simpleResultMockInputs.ohsomeApiSimpleResult); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /src/app/oshdb/result/simple-result/simple-result.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, OnInit} from '@angular/core'; 2 | import moment from 'moment'; 3 | import {computeGrowthRateCssClass, growthRate, percentFormatter} from '../result.utils'; 4 | import {ChartData} from 'chart.js'; 5 | 6 | @Component({ 7 | selector: 'app-simple-result', 8 | templateUrl: './simple-result.component.html', 9 | styleUrls: ['./simple-result.component.css'], 10 | standalone: false 11 | }) 12 | export class SimpleResultComponent implements OnInit { 13 | public moment = moment; 14 | 15 | @Input() chartJsData: Required; 16 | @Input() chartJsOptions: any; 17 | @Input() chartJsPlugins: any; 18 | @Input() unit: any; 19 | 20 | public simpleRequestColor = '#6EB0E0'; 21 | public chartJsDataStart: any; 22 | public chartJsDataEnd: any; 23 | public UNITS = { 24 | '': { 25 | units: ['', 'k'], 26 | factor: 1000 27 | }, 28 | 'meter': { 29 | units: ['m', 'km'], 30 | factor: 1000 31 | }, 32 | 'm': { 33 | units: ['m', 'km'], 34 | factor: 1000 35 | }, 36 | 'm²': { 37 | units: ['㎡', '㎢'], 38 | factor: 1000000 39 | } 40 | }; 41 | public growthRate: null | number; 42 | 43 | ngOnInit() { 44 | this.chartJsDataStart = this.getChartJsDataAtIndex(0); 45 | this.chartJsDataEnd = this.getChartJsDataAtIndex(this.chartJsData.datasets[0].data!.length - 1); 46 | this.growthRate = growthRate(this.chartJsDataStart[0].y, this.chartJsDataEnd[0].y); 47 | } 48 | 49 | public kFormatter(meter: number, unit: string): string { 50 | let value = meter; 51 | let unitString = this.UNITS[unit].units[0]; // 'm'; 52 | const unitFactor = this.UNITS[unit].factor; 53 | let decimals = 1; 54 | if (meter >= unitFactor) { 55 | value = meter / unitFactor; 56 | unitString = this.UNITS[unit].units[1]; // 'km'; 57 | } 58 | if (meter >= 100 * unitFactor) { 59 | decimals = 0; 60 | } 61 | return value.toFixed(decimals) + ' ' + unitString; 62 | } 63 | 64 | public getChartJsDataAtIndex(index: number) { 65 | const dataAt: any[] = []; 66 | 67 | this.chartJsData.datasets.forEach( 68 | (dataset) => dataAt.push(dataset.data![index]) 69 | ); 70 | 71 | return dataAt; 72 | } 73 | 74 | public computeGrowthRateCssClass = computeGrowthRateCssClass; 75 | public percentFormatter = percentFormatter; 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/app/oshdb/result/simple-result/simple-result.mockdata.ts: -------------------------------------------------------------------------------- 1 | const simpleResultMockInputs = { 2 | unit : '', 3 | ohsomeApiSimpleResult: { 4 | 'attribution': { 5 | 'url': 'https://ohsome.org/copyrights', 6 | 'text': '© OpenStreetMap contributors' 7 | }, 8 | 'apiVersion': '1.9.0', 9 | 'result': [ 10 | { 11 | 'timestamp': '2014-01-01T00:00:00Z', 12 | 'value': 42 13 | }, 14 | { 15 | 'timestamp': '2015-01-01T00:00:00Z', 16 | 'value': 42 17 | }, 18 | { 19 | 'timestamp': '2016-01-01T00:00:00Z', 20 | 'value': 43 21 | }, 22 | { 23 | 'timestamp': '2017-01-01T00:00:00Z', 24 | 'value': 43 25 | } 26 | ] 27 | } 28 | } 29 | 30 | export { 31 | simpleResultMockInputs 32 | } 33 | -------------------------------------------------------------------------------- /src/app/query-panel/query-panel.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 2 | 3 | import {QueryPanelComponent} from './query-panel.component'; 4 | import {OhsomeApiMetadataProviderService} from '../oshdb/ohsome-api-metadata-provider.service'; 5 | import OhsomeApiMetadataProviderServiceMock from '../oshdb/ohsome-api-metadata-provider.service.mock'; 6 | import {OshdbModule} from '../oshdb/oshdb.module'; 7 | import {SharedModule} from '../shared/shared.module'; 8 | import {BrowserModule} from '@angular/platform-browser'; 9 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 10 | import {OqtModule} from '../oqt/oqt.module'; 11 | 12 | describe('QueryPanelComponent', () => { 13 | let component: QueryPanelComponent; 14 | let fixture: ComponentFixture; 15 | 16 | beforeEach(waitForAsync(() => { 17 | TestBed.configureTestingModule({ 18 | declarations: [QueryPanelComponent], 19 | imports: [BrowserModule, 20 | SharedModule, 21 | OshdbModule, 22 | OqtModule], 23 | providers: [ 24 | { provide: OhsomeApiMetadataProviderService, useValue: OhsomeApiMetadataProviderServiceMock }, 25 | provideHttpClient(withInterceptorsFromDi()) 26 | ] 27 | }) 28 | .compileComponents(); 29 | })); 30 | 31 | beforeEach(() => { 32 | fixture = TestBed.createComponent(QueryPanelComponent); 33 | fixture.detectChanges(); 34 | component = fixture.componentInstance; 35 | }); 36 | 37 | it('should create', () => { 38 | expect(component).toBeTruthy(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/app/result-panel/result-list.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import {ResultListDirective} from './result-list.directive'; 2 | import {TestBed} from '@angular/core/testing'; 3 | import {ResultPanelComponent} from './result-panel.component'; 4 | import {DataService} from '../singelton-services/data.service'; 5 | 6 | 7 | describe('ResultListDirective', () => { 8 | beforeEach(async () => { 9 | await TestBed.configureTestingModule({ 10 | declarations: [ 11 | ResultPanelComponent 12 | ], 13 | providers:[ 14 | DataService 15 | ] 16 | }).compileComponents(); 17 | 18 | }); 19 | it('should create an instance', () => { 20 | const fixtureResultPanelComponent = TestBed.createComponent(ResultPanelComponent); 21 | const comp = fixtureResultPanelComponent.nativeElement; 22 | 23 | const service = TestBed.inject(DataService); 24 | 25 | const directive = new ResultListDirective(comp,service); 26 | expect(directive).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/result-panel/result-list.directive.ts: -------------------------------------------------------------------------------- 1 | import {ComponentRef, Directive, OnInit, ViewContainerRef} from '@angular/core'; 2 | import {ResultComponent} from '../oshdb/result/result.component'; 3 | import {DataService} from '../singelton-services/data.service'; 4 | import {OqtResultComponent} from '../oqt/result/oqt-result.component'; 5 | 6 | @Directive({ 7 | selector: '[appResultList]', 8 | standalone: false 9 | }) 10 | export class ResultListDirective implements OnInit { 11 | private resultItem: ComponentRef; 12 | 13 | constructor(private container: ViewContainerRef, 14 | private dataService: DataService) { 15 | } 16 | 17 | get length() { 18 | return this.container.length; 19 | } 20 | 21 | ngOnInit() { 22 | this.dataService.currentFormValues.subscribe(result => { 23 | const backend = result.formValues.backend; 24 | switch (backend) { 25 | case 'ohsomeApi': 26 | this.createResultComponent(result); 27 | break; 28 | case 'oqtApi': 29 | this.createOqtComponent(result); 30 | break; 31 | } 32 | 33 | }); 34 | } 35 | 36 | createResultComponent(result) { 37 | this.resultItem = this.container.createComponent(ResultComponent, {index: 0}); 38 | this.resultItem.instance.formValues = result.formValues; 39 | this.resultItem.instance.boundaryType = result.boundaryType; 40 | this.resultItem.instance.componentRef = this.resultItem; 41 | } 42 | 43 | private createOqtComponent(result) { 44 | const oqtResultItem = this.container.createComponent(OqtResultComponent, {index: 0}); 45 | oqtResultItem.instance.formValues = result.formValues; 46 | oqtResultItem.instance.boundaryType = result.boundaryType; 47 | oqtResultItem.instance.componentRef = oqtResultItem; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/result-panel/result-panel.component.css: -------------------------------------------------------------------------------- 1 | .result-panel-container { 2 | margin-top: 3rem; 3 | margin-bottom: 4rem; 4 | } 5 | 6 | .ui.header > .icon { 7 | display: inline-block; 8 | } 9 | 10 | .message { 11 | justify-content: center; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/result-panel/result-panel.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Result Log

3 |
Results will be shown here!
4 | 5 |
6 | 7 | -------------------------------------------------------------------------------- /src/app/result-panel/result-panel.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import {ResultPanelComponent} from './result-panel.component'; 4 | import {ResultListDirective} from './result-list.directive'; 5 | import {DataService} from '../singelton-services/data.service'; 6 | 7 | describe('ResultPanelComponent', () => { 8 | let component: ResultPanelComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ ResultPanelComponent, ResultListDirective ], 14 | providers:[DataService] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(ResultPanelComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/result-panel/result-panel.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, ViewChild} from '@angular/core'; 2 | import {ResultListDirective} from './result-list.directive'; 3 | 4 | @Component({ 5 | selector: 'app-result-panel', 6 | templateUrl: './result-panel.component.html', 7 | styleUrls: ['./result-panel.component.css'], 8 | standalone: false 9 | }) 10 | export class ResultPanelComponent { 11 | 12 | @ViewChild(ResultListDirective, { static: true }) 13 | resultList: ResultListDirective; 14 | 15 | get numResults() { 16 | return this.resultList.length; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/app/shared/components/boundary-input/boundary-input.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | position: relative; 3 | min-height: 400px; 4 | } 5 | 6 | .field { 7 | height: 100%; 8 | display: flex; 9 | flex-direction: column; 10 | } 11 | 12 | 13 | #boundaryMap { 14 | z-index: 0; 15 | flex: 1; 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/app/shared/components/boundary-input/boundary-input.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 |
7 | 8 | -------------------------------------------------------------------------------- /src/app/shared/components/boundary-select-input/boundary-select-input.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | position: relative; 3 | min-height: 400px; 4 | } 5 | 6 | .field { 7 | height: 100%; 8 | display: flex; 9 | flex-direction: column; 10 | } 11 | 12 | 13 | #boundaryMap { 14 | z-index: 0; 15 | flex: 1; 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/app/shared/components/boundary-select-input/boundary-select-input.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 |
7 | 8 | -------------------------------------------------------------------------------- /src/app/shared/components/plotly-chart/plotly-chart.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/src/app/shared/components/plotly-chart/plotly-chart.component.css -------------------------------------------------------------------------------- /src/app/shared/components/plotly-chart/plotly-chart.component.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /src/app/shared/components/plotly-chart/plotly-chart.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PlotlyChartComponent } from './plotly-chart.component'; 4 | 5 | describe('PlotlyChartComponent', () => { 6 | let component: PlotlyChartComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ PlotlyChartComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(PlotlyChartComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/shared/components/plotly-chart/plotly-chart.component.ts: -------------------------------------------------------------------------------- 1 | import {AfterViewInit, Component, ElementRef, Input, ViewChild} from '@angular/core'; 2 | import * as Plotly from 'plotly.js-dist-min'; 3 | 4 | @Component({ 5 | selector: 'app-plotly-chart', 6 | templateUrl: './plotly-chart.component.html', 7 | styleUrls: ['./plotly-chart.component.css'], 8 | standalone: false 9 | }) 10 | export class PlotlyChartComponent implements AfterViewInit{ 11 | @Input() plotlyDataLayoutConfig: Plotly.PlotlyDataLayoutConfig; 12 | @ViewChild('chart', {static: false}) chartDiv: ElementRef; 13 | 14 | ngAfterViewInit(): void { 15 | if (!this.plotlyDataLayoutConfig) return; 16 | 17 | const {data, layout, config} = this.plotlyDataLayoutConfig; 18 | 19 | Plotly.newPlot(this.chartDiv.nativeElement,data, layout, config); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app/shared/components/plotly-chart/plotly-gauge-data.mock.ts: -------------------------------------------------------------------------------- 1 | import {Data} from 'plotly.js-dist-min'; 2 | 3 | 4 | export const gaugeData: Data[] = [ 5 | { 6 | // domain: { x: [0, 0.5], y: [0, 1]}, 7 | // domain: {row:0,column:0}, 8 | value: 88.6 / 100, 9 | number: {valueformat: '0.1%'}, 10 | // title: {text: 'Mapping Saturation'}, 11 | type: 'indicator', 12 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 13 | // @ts-ignore 14 | mode: 'gauge+number+percent', 15 | gauge: { 16 | 'bar': {'color': 'transparent'}, 17 | threshold: { 18 | value: 87, 19 | 'thickness': 1.5, 20 | line: {width: 3, color: 'blue'} 21 | } 22 | , 23 | axis: { 24 | ticksuffix: '%', 25 | range: [null, 100] 26 | }, 27 | steps: [ 28 | {range: [0, 33.3], color: 'tomato'}, 29 | {range: [33.3, 97], color: 'gold'}, 30 | {range: [97, 100], color: 'darkseagreen'} 31 | ] 32 | } 33 | } 34 | ]; 35 | -------------------------------------------------------------------------------- /src/app/shared/components/prism-editor/prism-editor.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | -------------------------------------------------------------------------------- /src/app/shared/components/prism-editor/prism-editor.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PrismEditorComponent } from './prism-editor.component'; 4 | 5 | describe('PrismEditorComponent', () => { 6 | let component: PrismEditorComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [PrismEditorComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(PrismEditorComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/shared/components/prism-editor/prism-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ElementRef, 4 | EventEmitter, 5 | Input, 6 | OnChanges, 7 | OnInit, 8 | Output, 9 | SimpleChanges, 10 | ViewChild 11 | } from '@angular/core'; 12 | import {basicEditor, SetupOptions,} from 'prism-code-editor/setups'; 13 | import {languages} from 'prism-code-editor/prism'; 14 | import {PRISM_LANGUAGE_OHSOME_FILTER} from '../../../../prism-language-ohsome-filter'; 15 | 16 | @Component({ 17 | selector: 'app-prism-editor', 18 | imports: [], 19 | templateUrl: './prism-editor.component.html' 20 | }) 21 | export class PrismEditorComponent implements OnInit, OnChanges { 22 | @Input() value: string; 23 | @Input() readonly: boolean = false 24 | @Output() valueChange = new EventEmitter(); 25 | @ViewChild('prismEditorElement', {static: true}) prismEditorElement: ElementRef; 26 | editor; 27 | 28 | 29 | ngOnInit(): void { 30 | // default options for prism editor with ohsome-filter language 31 | languages['ohsome-filter'] = PRISM_LANGUAGE_OHSOME_FILTER; 32 | const setupOptions: SetupOptions = { 33 | theme: 'prism', 34 | value: this.value, 35 | language: 'ohsome-filter', 36 | onUpdate: (value) => { 37 | this.valueChange.emit(value); 38 | } 39 | }; 40 | 41 | if (this.readonly) { 42 | // readonly does not need event handler 43 | delete setupOptions.onUpdate; 44 | setupOptions.readOnly = true; 45 | } 46 | 47 | this.editor = basicEditor(this.prismEditorElement.nativeElement, setupOptions); 48 | } 49 | 50 | ngOnChanges(changes: SimpleChanges): void { 51 | if (changes['value'] && !changes['value'].firstChange) { 52 | this.editor.setOptions({value: changes['value'].currentValue}); 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/app/shared/components/sui-dropdown/sui-multi-select-search-dropdown.component.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /src/app/shared/components/sui-dropdown/sui-multi-select-search-dropdown.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | 3 | import {SuiMultiSelectSearchDropdownComponent} from './sui-multi-select-search-dropdown.component'; 4 | 5 | describe('SuiDropdownComponent', () => { 6 | let component: SuiMultiSelectSearchDropdownComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [SuiMultiSelectSearchDropdownComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(SuiMultiSelectSearchDropdownComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | 24 | it('should update the value when writeValue is called', async () => { 25 | spyOn(component, 'updateDropdown') 26 | 27 | component.writeValue(['test value']); 28 | 29 | expect(component.value).toEqual(['test value']); 30 | expect(component.updateDropdown).toHaveBeenCalled(); 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /src/app/shared/components/sui-dropdown/sui-multi-select-search-dropdown.component.ts: -------------------------------------------------------------------------------- 1 | import {AfterViewInit, Component, ElementRef, forwardRef, Input, NgZone, signal, ViewChild} from '@angular/core'; 2 | import { 3 | ControlValueAccessor, 4 | NG_VALIDATORS, 5 | NG_VALUE_ACCESSOR, 6 | ReactiveFormsModule, 7 | ValidationErrors 8 | } from '@angular/forms'; 9 | import {KeyValue, NgForOf} from '@angular/common'; 10 | 11 | 12 | declare const $; 13 | 14 | @Component({ 15 | selector: 'app-sui-multi-select-search-dropdown', 16 | imports: [ 17 | ReactiveFormsModule, 18 | NgForOf 19 | ], 20 | templateUrl: './sui-multi-select-search-dropdown.component.html', 21 | providers: [ 22 | { 23 | provide: NG_VALUE_ACCESSOR, 24 | useExisting: forwardRef(() => SuiMultiSelectSearchDropdownComponent), 25 | multi: true 26 | }, 27 | { 28 | provide: NG_VALIDATORS, 29 | useExisting: forwardRef(() => SuiMultiSelectSearchDropdownComponent), 30 | multi: true 31 | } 32 | ] 33 | }) 34 | export class SuiMultiSelectSearchDropdownComponent implements ControlValueAccessor, AfterViewInit { 35 | 36 | @ViewChild("dropdown", {static: false}) dropdown: ElementRef; 37 | // see https://semantic-ui.com/modules/dropdown.html#/settings for properties that you can set in options 38 | @Input() options: object = {}; 39 | @Input() selectOptions!: Array>; 40 | @Input() multiple: boolean = false; 41 | required = signal(false); 42 | 43 | constructor(private readonly ngZone: NgZone) { 44 | } 45 | 46 | // selectedAttributeKeys 47 | value: string[]; 48 | 49 | // initial state 50 | touched = false; 51 | 52 | disabled = false; 53 | 54 | // CVA handler 55 | onChange = (value: typeof this.value) => { 56 | console.log("ONCHANGE", value); 57 | }; 58 | 59 | onTouched = () => { 60 | }; 61 | 62 | 63 | // CVA methods 64 | 65 | // notify the parent, when the user changes the value 66 | registerOnChange(onChange: (value: typeof this.value) => void) { 67 | this.onChange = onChange; 68 | } 69 | 70 | registerOnTouched(onTouched: () => void) { 71 | this.onTouched = onTouched; 72 | } 73 | 74 | setDisabledState(isDisabled: boolean) { 75 | this.disabled = isDisabled; 76 | } 77 | 78 | // form writes value to this component 79 | writeValue(value: string[]): void { 80 | this.value = value; 81 | this.updateDropdown(value); 82 | } 83 | 84 | // TODO implement this onBlur 85 | // markAsTouched() { 86 | // if (!this.touched) { 87 | // this.onTouched(); 88 | // this.touched = true; 89 | // } 90 | // } 91 | 92 | ngAfterViewInit(): void { 93 | this.initDropdown(); 94 | } 95 | 96 | 97 | initDropdown(): void { 98 | 99 | const options: object = {...this.options,...{ 100 | onChange: (value: string[]) => { 101 | if (value != undefined) { 102 | this.onChange(value); 103 | } 104 | } 105 | }}; 106 | 107 | this.ngZone.runOutsideAngular(() => { 108 | $(this.dropdown.nativeElement) 109 | .dropdown(options); 110 | }) 111 | 112 | } 113 | 114 | updateDropdown(value: string[]) { 115 | this.ngZone.runOutsideAngular(() => { 116 | $(this.dropdown.nativeElement).dropdown('clear'); 117 | setTimeout(() => { 118 | $(this.dropdown.nativeElement).dropdown('set exactly', value); 119 | }) 120 | }) 121 | } 122 | 123 | // Validator implementation, so you can set "required" attribute on the HTMLElement 124 | validate(): ValidationErrors | null { 125 | if (this.required() && !this.value) { 126 | return {required: true}; 127 | } 128 | return null; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/app/shared/directives/validation/at-least-one-checkbox-checked.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { AtLeastOneCheckboxCheckedDirective } from './at-least-one-checkbox-checked.directive'; 2 | 3 | describe('AtLeastOneCheckboxCheckedDirective', () => { 4 | it('should create an instance', () => { 5 | const directive = new AtLeastOneCheckboxCheckedDirective(); 6 | expect(directive).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/app/shared/directives/validation/at-least-one-checkbox-checked.directive.ts: -------------------------------------------------------------------------------- 1 | import {Directive} from '@angular/core'; 2 | import {FormGroup, NG_VALIDATORS, ValidationErrors, Validator} from '@angular/forms'; 3 | 4 | @Directive({ 5 | selector: '[appAtLeastOneOqtIndicatorCheckboxChecked]', 6 | providers: [{ provide: NG_VALIDATORS, useExisting: AtLeastOneCheckboxCheckedDirective, multi: true }], 7 | standalone: false 8 | }) 9 | export class AtLeastOneCheckboxCheckedDirective implements Validator { 10 | 11 | validate(control: FormGroup): ValidationErrors | null { 12 | 13 | //use the checkbox names in the indicatorGroup to query if checkbox formcontrols are selected (value = true) 14 | 15 | const checkedCheckboxes: NodeListOf = document.querySelectorAll('#indicatorGroup input[type=checkbox]'); 16 | const names: string[] = []; 17 | checkedCheckboxes.forEach(value => { 18 | if(value?.dataset?.['indicatorKey'] ){ 19 | names.push(value.dataset['indicatorKey']) 20 | } 21 | }); 22 | 23 | const controls = control.controls; 24 | 25 | const isValid = names.reduce((previousValue, currentValue) => previousValue || !!controls[currentValue]?.value, false); 26 | 27 | return ((control.get('backend')?.value === 'ohsomeApi') || isValid) ? null : { 28 | type: 'ValidationError', 29 | message: 'At least one checkbox must be checked!' 30 | }; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/app/shared/shared-types.d.ts: -------------------------------------------------------------------------------- 1 | import {Feature, FeatureCollection, MultiPolygon, Polygon} from 'geojson'; 2 | import * as L from 'leaflet'; 3 | 4 | /** 5 | * Defines the arrow icon at the growth rate stat 6 | */ 7 | type GrowthRateCssClass = 'up' | 'down' | 'right' | ''; 8 | 9 | interface Userlayer { 10 | name: string; 11 | title: string; 12 | data: Polygon | MultiPolygon | Feature | FeatureCollection; 13 | style?: L.PathOptions 14 | } 15 | 16 | // determines which map will be instantiated 17 | type BoundaryType = 'admin' | 'bbox' | 'bcircle' | 'bpoly'; 18 | 19 | // determines which drawing interaction will be available when instantiating the BoundaryInputComponent 20 | type BoundaryInputComponentInteractionType = 'bbox' | 'bcircle' | 'bpoly'; 21 | interface BoundaryInputComponentOptions { 22 | label?: string | boolean; 23 | center: L.LatLngExpression; 24 | zoom: number; 25 | maxBounds?: L.LatLngBoundsExpression; 26 | minZoom?: number; 27 | maxZoom?: number; 28 | maskPoly?: Polygon | MultiPolygon | Feature | FeatureCollection; 29 | userDefinedPolygonLayers?: Userlayer[] 30 | } 31 | 32 | // removes the optional (?) and nullish values from types or interfaces 33 | type RequiredAndDefined = { 34 | [P in keyof T]-?: Exclude 35 | }; 36 | 37 | export { 38 | GrowthRateCssClass, 39 | BoundaryType, 40 | Userlayer, 41 | BoundaryInputComponentInteractionType, 42 | BoundaryInputComponentOptions, 43 | RequiredAndDefined 44 | } 45 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {FormsModule} from '@angular/forms'; 4 | import {AtLeastOneCheckboxCheckedDirective} from './directives/validation/at-least-one-checkbox-checked.directive'; 5 | import {PlotlyChartComponent} from './components/plotly-chart/plotly-chart.component'; 6 | import {BoundarySelectInputComponent} from './components/boundary-select-input/boundary-select-input.component'; 7 | import {BoundaryInputComponent} from './components/boundary-input/boundary-input.component'; 8 | 9 | 10 | @NgModule({ 11 | declarations: [ 12 | AtLeastOneCheckboxCheckedDirective, 13 | PlotlyChartComponent, 14 | BoundarySelectInputComponent, 15 | BoundaryInputComponent, 16 | ], 17 | imports: [ 18 | CommonModule 19 | ], 20 | exports: [ 21 | CommonModule, 22 | FormsModule, 23 | AtLeastOneCheckboxCheckedDirective, 24 | PlotlyChartComponent, 25 | BoundarySelectInputComponent, 26 | BoundaryInputComponent, 27 | ] 28 | }) 29 | export class SharedModule { } 30 | -------------------------------------------------------------------------------- /src/app/singelton-services/data.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed, inject} from '@angular/core/testing'; 2 | 3 | import {DataService} from './data.service'; 4 | 5 | describe('DataService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [DataService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([DataService], (service: DataService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/singelton-services/data.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Observable, Subject} from 'rxjs'; 3 | import {BoundaryType} from '../shared/shared-types'; 4 | 5 | type FormData = { formValues: any, boundaryType: BoundaryType }; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class DataService { 11 | private formValueSource = new Subject(); 12 | public currentFormValues: Observable = this.formValueSource.asObservable(); 13 | 14 | pushFormValues(formValues, boundaryType: BoundaryType) { 15 | this.formValueSource.next({ 16 | formValues, 17 | boundaryType 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/singelton-services/osm-boundary-provider.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { OsmBoundaryProviderService } from './osm-boundary-provider.service'; 4 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 5 | 6 | describe('OsmBoundaryServiceService', () => { 7 | let service: OsmBoundaryProviderService; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [], 12 | providers: [provideHttpClient(withInterceptorsFromDi())] 13 | }); 14 | service = TestBed.inject(OsmBoundaryProviderService); 15 | }); 16 | 17 | it('should be created', () => { 18 | expect(service).toBeTruthy(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/app/singelton-services/osm-boundary-provider.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {environment} from '../../environments/environment'; 3 | import { HttpClient } from '@angular/common/http'; 4 | import {FeatureCollection} from 'geojson'; 5 | import {map, Observable, of} from 'rxjs'; 6 | 7 | const OHSOME_BOUNDARY_WFS_URL = environment.ohsomeBoundaryWFSUrl; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class OsmBoundaryProviderService { 13 | 14 | constructor(private http: HttpClient) { 15 | } 16 | 17 | getOsmBoundariesByIds(ids: number[] | undefined): Observable { 18 | console.log('GET BOUNDARIES', ids); 19 | if (ids == undefined) { 20 | return of(''); 21 | } 22 | 23 | const idList = ids.join(','); 24 | const url = `${OHSOME_BOUNDARY_WFS_URL}&CQL_FILTER="id" IN (${idList})`; 25 | return this.http.get(url).pipe(map(featureCollection => { 26 | featureCollection.features.forEach( (feature, index)=> { 27 | feature['id'] += '-_-' + (feature.properties?.['display_name'] || `area${index}`).replace(/ /g, '__'); 28 | }); 29 | return JSON.stringify(featureCollection) 30 | })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/singelton-services/url-hash-params-provider.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { UrlHashParamsProviderService } from './url-hash-params-provider.service'; 4 | 5 | describe('UrlHashParamsProviderService', () => { 6 | let service: UrlHashParamsProviderService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(UrlHashParamsProviderService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/singelton-services/url-hash-params-provider.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class UrlHashParamsProviderService { 7 | private currentHashParams: URLSearchParams; 8 | 9 | updateHashParamsStoreFromUrl () { 10 | this.currentHashParams = new URLSearchParams(window.location.hash.slice(1));//.toLowerCase()); 11 | } 12 | 13 | getHashURLSearchParams() { 14 | if (!this.currentHashParams) this.updateHashParamsStoreFromUrl(); 15 | return new URLSearchParams(this.currentHashParams); 16 | } 17 | 18 | updateHashParams(paramsObject){ 19 | this.currentHashParams = new URLSearchParams(paramsObject); 20 | window.location.hash = this.currentHashParams.toString(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/fonts/STIXTwoMath-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/src/assets/fonts/STIXTwoMath-Regular.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/lmmonoltcond10-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/src/assets/fonts/lmmonoltcond10-regular-webfont.woff -------------------------------------------------------------------------------- /src/assets/fonts/lmmonoprop10-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/src/assets/fonts/lmmonoprop10-regular-webfont.woff -------------------------------------------------------------------------------- /src/environments/environment.idai.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | 4 | oshdbRestApiRootUrl : 'https://api.ohsome.org/v1', 5 | zoomLevel : 5, 6 | mapCenter : {lat: -19.0, lng: 37.0}, 7 | mapCenterFromPoly : false, 8 | boundaryType : 'bpoly', 9 | bpolys : 'Cyclone Idai Region:35.2880804,-20.7920609,35.0134222,-20.1075181,' + 10 | '39.4518933,-17.1617754,38.6389050,-15.4324902,' + 11 | '35.2441186,-13.3682218,30.1025170,-15.0084424,' + 12 | '31.7284936,-22.0652577,35.2880804,-20.7920609', // idai hotosm project polygon 13 | selectedFilter : `building=* and building!=no and geometry:polygon`, 14 | selectedKey : 'building', 15 | selectedValue : '', 16 | selectedTypes : ['way'], 17 | startDate : '2019-03-01', 18 | endDate : '', 19 | period : 'P1D', 20 | viewUpdateTime : true, 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | 4 | //ohsomeAPI (osm history stats from OSHDB) 5 | oshdbRestApiRootUrl : 'https://api.ohsome.org/v1', 6 | 7 | //oqtApi (ohsome quality analyst) 8 | oqtApiRootUrl: 'https://api.quality.ohsome.org/v1', 9 | // core, experimental, all, ... 10 | oqtApiProject: 'core', 11 | 12 | //the WFS endpoint which will get appended a CQL_FILTER="id" IN (number, number, ...) param and should return a GeoJSON 13 | ohsomeBoundaryWFSUrl: 'https://maps.heigit.org/ohsome/wfs?service=wfs&request=GetFeature&typeNames=ohsome:admin_world_water&outputFormat=application/json&version=2.0.0&srsName=EPSG:4326', 14 | ohsomeBoundaryWMSUrl: 'https://maps.heigit.org/ohsome/service/wms', 15 | ohsomeBoundaryWMSLayer: 'ohsome:admin_world_water', 16 | 17 | // url to fetch an announcement text 18 | announcementUrl: 'https://dashboard.ohsome.org/statuspage', 19 | 20 | //map options 21 | zoomLevel : 2, 22 | mapCenter : {lat: 0.0, lng: 0.0}, 23 | mapCenterFromPoly : false, 24 | 25 | //ohsomeAPI options 26 | selectedFilter : `natural=tree and type:node`, 27 | selectedKey : 'natural', 28 | selectedValue : 'tree', 29 | selectedTypes : ['node'], 30 | period : 'P1M', 31 | }; 32 | -------------------------------------------------------------------------------- /src/environments/environment.test.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | 4 | //ohsomeAPI (osm history stats from OSHDB) 5 | oshdbRestApiRootUrl : 'https://api.ohsome.org/v1', 6 | 7 | //oqtApi (ohsome quality analyst) 8 | oqtApiRootUrl: 'https://api.quality.ohsome.org/v1-test/', 9 | 10 | // core, experimental, all, ... 11 | oqtApiProject: 'bkg', 12 | 13 | //the WFS endpoint which will get appended a CQL_FILTER="id" IN (number, number, ...) param and should return a GeoJSON 14 | ohsomeBoundaryWFSUrl: 'https://maps.heigit.org/ohsome/wfs?service=wfs&request=GetFeature&typeNames=ohsome:admin_world_water&outputFormat=application/json&version=2.0.0&srsName=EPSG:4326', 15 | ohsomeBoundaryWMSUrl: 'https://maps.heigit.org/ohsome/service/wms', 16 | ohsomeBoundaryWMSLayer: 'ohsome:admin_world_water', 17 | 18 | // url to fetch an announcement text 19 | announcementUrl: 'https://dashboard.ohsome.org/statuspage', 20 | 21 | //map options 22 | zoomLevel : 2, 23 | mapCenter : {lat: 0.0, lng: 0.0}, 24 | mapCenterFromPoly : false, 25 | 26 | //ohsomeAPI options 27 | selectedFilter : `natural=tree and type:node`, 28 | selectedKey : 'natural', 29 | selectedValue : 'tree', 30 | selectedTypes : ['node'], 31 | period : 'P1M', 32 | }; 33 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --configuration=production` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `angular.json`. 5 | 6 | export const environment = { 7 | production: false, 8 | 9 | //ohsomeAPI (osm history stats from OSHDB) 10 | oshdbRestApiRootUrl : 'https://api.ohsome.org/v1', 11 | 12 | //oqtApi (ohsome quality analyst) 13 | // oqtApiRootUrl: 'https://api.quality.ohsome.org/v1', 14 | oqtApiRootUrl: 'https://api.quality.ohsome.org/v1-test', 15 | // oqtApiRootUrl: 'http://127.0.0.1:8080', 16 | // core, experimental, all, ... 17 | oqtApiProject: 'bkg', 18 | 19 | //the WFS endpoint which will get appended a CQL_FILTER="id" IN (number, number, ...) param and should return a GeoJSON 20 | ohsomeBoundaryWFSUrl: 'https://maps.heigit.org/ohsome/wfs?service=wfs&request=GetFeature&typeNames=ohsome:admin_world_water&outputFormat=application/json&version=2.0.0&srsName=EPSG:4326', 21 | ohsomeBoundaryWMSUrl: 'https://maps.heigit.org/ohsome/service/wms', 22 | ohsomeBoundaryWMSLayer: 'ohsome:admin_world_water', 23 | 24 | // url to fetch an announcement text 25 | announcementUrl: 'https://dashboard.ohsome.org/statuspage', 26 | 27 | //map options 28 | zoomLevel : 10, 29 | mapCenter : {lat: 49.41, lng: 8.68}, 30 | mapCenterFromPoly : false, 31 | // boundaryType : 'admin', 32 | // bpolys : 'Cyclone Idai Region:35.2880804,-20.7920609,35.0134222,-20.1075181,' + 33 | // '39.4518933,-17.1617754,38.6389050,-15.4324902,' + 34 | // '35.2441186,-13.3682218,30.1025170,-15.0084424,' + 35 | // '31.7284936,-22.0652577,35.2880804,-20.7920609', // idai hotosm project polygon 36 | 37 | //ohsomeAPI options 38 | selectedFilter : `building=* and building!=no and geometry:polygon`, 39 | selectedKey : 'historic', 40 | selectedValue : '', 41 | selectedTypes : ['node', 'way'], 42 | // startDate : '2019-03-01', 43 | // endDate : '2020-03-01', 44 | period : 'P1M', 45 | viewUpdateTime : false, 46 | }; 47 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIScience/ohsome-dashboard/af9fe0280389325c709b5fb94c8dc262048702de/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ohsome - dashboard 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app/app.module'; 4 | 5 | 6 | platformBrowserDynamic().bootstrapModule(AppModule) 7 | .catch(err => console.error(err)); 8 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | html { 3 | scroll-behavior: smooth; 4 | } 5 | 6 | @font-face { 7 | font-family: 'latin_modern_mono_light_c10Rg'; 8 | src: url("assets/fonts/lmmonoltcond10-regular-webfont.woff"); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'latin_modern_mono_prop10Rg'; 15 | src: url("assets/fonts/lmmonoprop10-regular-webfont.woff"); 16 | font-weight: normal; 17 | font-style: normal; 18 | } 19 | 20 | @font-face { 21 | font-family: 'stix_two_math_regular'; 22 | src: url("assets/fonts/STIXTwoMath-Regular.woff2"); 23 | font-weight: normal; 24 | font-style: normal; 25 | } 26 | 27 | math { 28 | font-family: stix_two_math_regular, latin_modern_mono_light_c10Rg, serif; 29 | font-weight: bold; 30 | } 31 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import {environment} from './environments/environment'; 3 | 4 | export default class Utils { 5 | static sanitizeLabel(label) { 6 | if (label == undefined) { return ''} 7 | if (label.includes('-_-')) { 8 | label = label.split('-_-')[1]; 9 | } 10 | return label.replace(/__/g, ' '); 11 | } 12 | 13 | static loadEnv(name, defaultValue) { 14 | if (name in environment && environment[name] !== '') { 15 | return environment[name]; 16 | } else { 17 | return defaultValue; 18 | } 19 | } 20 | 21 | static setObjectProperty(obj = {}, path, val) { 22 | const keys = path.split('.') 23 | const last = keys.pop() 24 | keys.reduce((o, k) => o[k] ??= {}, obj)[last] = val 25 | } 26 | 27 | static getFromParamsOrDefault(params: URLSearchParams, key: string, def: string): string { 28 | if (!params.has(key)) { 29 | return def; 30 | } else { 31 | return params.get(key) || ''; 32 | } 33 | } 34 | 35 | // helper function which calculates a matching start date for a given end date and peridod 36 | // such that: start + n*period ~= end 37 | static calculateStartDateFromEndAndPeriod(endDate: string, period: string, minDate: string | undefined | null): string { 38 | if (!minDate) return ''; 39 | const min = moment(minDate); 40 | const time = moment(endDate); 41 | const p = moment.duration(period); 42 | const multiplePeriod = moment.duration(); 43 | do { 44 | multiplePeriod.add(p); 45 | } while (moment(time).subtract(multiplePeriod) > min); 46 | multiplePeriod.subtract(p); 47 | return moment(time).subtract(multiplePeriod).toISOString(); 48 | } 49 | 50 | static async wait(ms:number): Promise { 51 | return new Promise((resolve) => setTimeout(resolve, ms)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "esModuleInterop": true, 9 | "strict": true, 10 | "noImplicitOverride": true, 11 | "noImplicitAny": false, 12 | "noPropertyAccessFromIndexSignature": true, 13 | "noImplicitReturns": false, 14 | "noFallthroughCasesInSwitch": true, 15 | "sourceMap": true, 16 | "strictPropertyInitialization": false, 17 | "declaration": false, 18 | "experimentalDecorators": true, 19 | "moduleResolution": "bundler", 20 | "importHelpers": true, 21 | "skipLibCheck": true, 22 | "target": "ES2022", 23 | "module": "ES2022", 24 | "useDefineForClassFields": false, 25 | "lib": [ 26 | "ES2022", 27 | "dom" 28 | ], 29 | "typeRoots": ["node_modules/@types"] 30 | }, 31 | "angularCompilerOptions": { 32 | "enableI18nLegacyMessageIdFormat": false, 33 | "strictInjectionParameters": true, 34 | "strictInputAccessModifiers": true, 35 | "strictTemplates": true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | --------------------------------------------------------------------------------