├── .browserslistrc ├── .editorconfig ├── .eslintrc.js ├── .github └── workflows │ ├── docker-ci-push.yml │ └── docker-ci-tag.yml ├── .gitignore ├── .npmrc ├── CITATION.cff ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── babel.config.js ├── docker └── Dockerfile ├── jsconfig.json ├── lib └── xlsx-0.20.3.tgz ├── package-lock.json ├── package.json ├── public ├── img │ ├── 403.svg │ ├── 404.svg │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── funders │ │ ├── bbsrc.svg │ │ ├── bold.svg │ │ ├── cimmyt.svg │ │ ├── crop-trust.svg │ │ ├── crp-maize.svg │ │ ├── crp-wheat.svg │ │ ├── cwr.svg │ │ ├── defra.svg │ │ ├── divseek.png │ │ ├── eu.svg │ │ ├── hutton.svg │ │ ├── ibh.svg │ │ ├── innovate-uk.svg │ │ ├── iwyp.svg │ │ ├── jhiss.svg │ │ ├── norad.svg │ │ ├── norway.svg │ │ ├── norwegian-ministry-of-foreign-affairs.svg │ │ ├── resas.svg │ │ ├── sader.svg │ │ ├── scottish-government.svg │ │ ├── sefari.svg │ │ ├── templeton.svg │ │ ├── ukri.svg │ │ └── uod.svg │ ├── germinate-silhouette.svg │ ├── germinate-square-name.svg │ ├── germinate-square.svg │ ├── germinate-sticker.svg │ ├── germinate-text.svg │ ├── hutton-black.svg │ ├── hutton.svg │ ├── ics-sdg-black.svg │ ├── ics-sdg.svg │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── mstile-70x70.png │ ├── other │ │ └── lokalise.svg │ ├── safari-pinned-tab.svg │ ├── site.webmanifest │ ├── team │ │ ├── iain-milne.jpg │ │ ├── paul-shaw.jpg │ │ └── sebastian-raubach.jpg │ └── tools │ │ ├── curlywhirly.png │ │ ├── excel.svg │ │ ├── flapjack.png │ │ ├── gridscore.svg │ │ ├── helium.png │ │ ├── helium.svg │ │ ├── strudel.png │ │ └── tassel.png └── index.html ├── src ├── App.vue ├── assets │ ├── dark-mode.scss │ └── img │ │ ├── data-collection.svg │ │ ├── germplasm-selection.svg │ │ ├── helium.png │ │ ├── image-icon-aperture.svg │ │ ├── image-icon-camera.svg │ │ ├── image-icon-date.svg │ │ ├── image-icon-exposure.svg │ │ ├── image-icon-flash-no.svg │ │ ├── image-icon-flash.svg │ │ ├── image-icon-focal-length.svg │ │ ├── image-icon-gps.svg │ │ ├── image-icon-iso.svg │ │ ├── image-icon-lens.svg │ │ ├── layout-design.svg │ │ └── story-banner.svg ├── auth.js ├── components │ ├── TrialFieldbookImportComponent.vue │ ├── admin │ │ ├── DatasetGroupPermissions.vue │ │ ├── DatasetPermissions.vue │ │ ├── DatasetUserPermissions.vue │ │ ├── UserGroupMembers.vue │ │ └── UserGroups.vue │ ├── charts │ │ ├── AlleleFrequencyChart.vue │ │ ├── BalloonChart.vue │ │ ├── BarChart.vue │ │ ├── BaseChart.vue │ │ ├── ChoroplethChart.vue │ │ ├── ClimateBoxplotChart.vue │ │ ├── ComparisonChart.vue │ │ ├── HeatmapChart.vue │ │ ├── MapChart.vue │ │ ├── MatrixChart.vue │ │ ├── ParallelCoordinatesChart.vue │ │ ├── PedigreeChart.vue │ │ ├── RadarChart.vue │ │ ├── ScatterChart.vue │ │ ├── TaxonomySankey.vue │ │ ├── TaxonomySunburst.vue │ │ ├── TaxonomyTreemap.vue │ │ ├── TimelineChart.vue │ │ └── TraitBoxplotChart.vue │ ├── dataimport │ │ └── DataImportJobs.vue │ ├── dropdowns │ │ ├── LocaleDropdown.vue │ │ └── UserSettingsDropdown.vue │ ├── export │ │ ├── BoxplotSelection.vue │ │ ├── ClimateExportChartSelection.vue │ │ ├── CrossDataTypeSelection.vue │ │ ├── DatasetOverview.vue │ │ ├── ExportDownloadSelection.vue │ │ ├── ExportGroupSelection.vue │ │ ├── ExportSelection.vue │ │ ├── GenotypeExportSelection.vue │ │ ├── IndividualDatasetWidget.vue │ │ ├── MapExportSelection.vue │ │ ├── TraitComparisonSelection.vue │ │ ├── TraitExportChartSelection.vue │ │ ├── TraitExportTimelineSelection.vue │ │ └── TraitRadarChartSelection.vue │ ├── germplasm │ │ ├── GermplasmDownload.vue │ │ ├── GermplasmTableComponent.vue │ │ ├── GermplasmTraitStats.vue │ │ ├── Mcpd.vue │ │ └── SpecificPassport.vue │ ├── icons │ │ ├── HeliumIcon.vue │ │ ├── MdiIcon.vue │ │ └── SidebarIcon.vue │ ├── images │ │ ├── ImageCarousel.vue │ │ ├── ImageGallery.vue │ │ ├── ImageNode.vue │ │ └── ImageTags.vue │ ├── institution │ │ └── Institution.vue │ ├── map │ │ ├── GermplasmLocationMap.vue │ │ ├── LocationMap.vue │ │ ├── ShapefileGeotiffMap.vue │ │ └── TrialsLocationMap.vue │ ├── modals │ │ ├── AddAboutPartnerModal.vue │ │ ├── AddNewsModal.vue │ │ ├── AddProjectModal.vue │ │ ├── AddStoryModal.vue │ │ ├── AddStoryStepModal.vue │ │ ├── CarouselEditModal.vue │ │ ├── ChangelogModal.vue │ │ ├── ClimateOverlayModal.vue │ │ ├── CustomChartColorModal.vue │ │ ├── DatasetEditModal.vue │ │ ├── EditTagModal.vue │ │ ├── ExperimentCreationModal.vue │ │ ├── FileResourceModal.vue │ │ ├── FileResourceTypeModal.vue │ │ ├── GenotypeExportModal.vue │ │ ├── GetTokenModal.vue │ │ ├── GroupEditAddModal.vue │ │ ├── GroupUploadModal.vue │ │ ├── ImageExifModal.vue │ │ ├── ImageUploadModal.vue │ │ ├── InstitutionModal.vue │ │ ├── LicenseCreationModal.vue │ │ ├── LicenseModal.vue │ │ ├── LocationCountrySelectionModal.vue │ │ ├── LocationSelectionModal.vue │ │ ├── PublicationsModal.vue │ │ ├── ReferenceModal.vue │ │ ├── RegistrationModal.vue │ │ ├── TraitEditModal.vue │ │ └── YesNoCancelModal.vue │ ├── news │ │ └── NewsSection.vue │ ├── structure │ │ ├── PageHeader.vue │ │ ├── SidebarAsyncJobs.vue │ │ ├── SidebarComponent.vue │ │ ├── SidebarLogoComponent.vue │ │ └── StoryNavbar.vue │ ├── tables │ │ ├── BaseTable.vue │ │ ├── ClimateDataTable.vue │ │ ├── ClimateTable.vue │ │ ├── CollaboratorTable.vue │ │ ├── CommentTable.vue │ │ ├── DatasetAttributeTable.vue │ │ ├── DatasetTable.vue │ │ ├── EntityTable.vue │ │ ├── ExperimentTable.vue │ │ ├── FileresourceTable.vue │ │ ├── GenotypeDatasetTable.vue │ │ ├── GermplasmAttributeTable.vue │ │ ├── GermplasmTable.vue │ │ ├── GermplasmTableFilter.vue │ │ ├── GroupTable.vue │ │ ├── ImageTable.vue │ │ ├── InstitutionDatasetTable.vue │ │ ├── InstitutionTable.vue │ │ ├── LocationTable.vue │ │ ├── MapDefinitionTable.vue │ │ ├── MapTable.vue │ │ ├── MarkedItems.vue │ │ ├── MarkerTable.vue │ │ ├── PedigreeDefinitionTable.vue │ │ ├── PedigreeTable.vue │ │ ├── ProjectTable.vue │ │ ├── PublicationTable.vue │ │ ├── StoryTable.vue │ │ ├── TableFilter.vue │ │ ├── TraitAttributeTable.vue │ │ ├── TraitTable.vue │ │ ├── TrialsDataTable.vue │ │ ├── UploadStatusTable.vue │ │ ├── UserFeedbackTable.vue │ │ ├── UserGroupTable.vue │ │ ├── UserTable.vue │ │ └── details │ │ │ ├── AttributeDetails.vue │ │ │ ├── CollaboratorDetails.vue │ │ │ └── InstitutionDetails.vue │ └── util │ │ ├── BannerCard.vue │ │ ├── Collapse.vue │ │ ├── ColorGradient.vue │ │ ├── DataStoryWidget.vue │ │ ├── DatasetMetadataDownload copy.vue │ │ ├── DatasetMetadataDownload.vue │ │ ├── DatasetsWithUnacceptedLicense.vue │ │ ├── DropFilePreview.vue │ │ ├── FeedbackButton.vue │ │ ├── HtmlTemplateEditor.vue │ │ ├── Links.vue │ │ ├── LoginBackground.vue │ │ ├── MarkerLookup.vue │ │ ├── PasswordInput.vue │ │ ├── PublicationCard.vue │ │ ├── PublicationsWidget.vue │ │ ├── RecentItems.vue │ │ ├── Scale.vue │ │ ├── ScreenshotComponent.vue │ │ ├── SearchableSelect.vue │ │ ├── SignInForm.vue │ │ ├── StoryCard.vue │ │ ├── Tour.vue │ │ ├── TraitCategories.vue │ │ ├── TrialGermplasmLookup.vue │ │ └── UploadWidget.vue ├── const │ ├── database-columns.js │ └── table-props.js ├── main.js ├── mixins │ ├── api │ │ ├── auth.js │ │ ├── base.js │ │ ├── climate.js │ │ ├── dataset.js │ │ ├── genotype.js │ │ ├── germplasm.js │ │ ├── group.js │ │ ├── location.js │ │ ├── misc.js │ │ ├── project.js │ │ ├── stats.js │ │ ├── trait.js │ │ └── usergroup.js │ ├── colors.js │ ├── formatting.js │ ├── image.js │ ├── pages.js │ ├── search.js │ ├── types.js │ └── util.js ├── plugins │ ├── browser-detect.js │ ├── changelog │ │ └── index.json │ ├── charts │ │ ├── plotly-allelefreq-chart.js │ │ ├── plotly-bar-chart.js │ │ ├── plotly-map-chart.js │ │ ├── plotly-scatter-matrix.js │ │ ├── plotly-scatter-plot.js │ │ ├── plotly-sunburst-chart.js │ │ └── plotly-treemap-chart.js │ ├── debounce.js │ ├── i18n.js │ └── i18n │ │ ├── de_DE.json │ │ └── en_GB.json ├── router │ └── index.js ├── store │ └── index.js └── views │ ├── AboutView.vue │ ├── Cookies.vue │ ├── DashboardContainer.vue │ ├── DatasetSelector.vue │ ├── Genesys.vue │ ├── GroupUpload.vue │ ├── Groups.vue │ ├── HomeView.vue │ ├── Images.vue │ ├── Login.vue │ ├── MarkedItemsView.vue │ ├── ProjectDetails.vue │ ├── Projects.vue │ ├── Publications.vue │ ├── Search.vue │ ├── Setup.vue │ ├── Stories.vue │ ├── about │ ├── AboutGerminate.vue │ ├── AboutProject.vue │ └── ExportFormats.vue │ ├── admin │ ├── GerminateSettings.vue │ ├── UserFeedback.vue │ └── UserPermissions.vue │ ├── data │ ├── DataResources.vue │ ├── DataStatistics.vue │ ├── DataUploader.vue │ ├── Datasets.vue │ ├── ExperimentDetails.vue │ ├── Experiments.vue │ ├── climate │ │ ├── ClimateDetails.vue │ │ └── Climates.vue │ ├── export │ │ ├── AlleleFrequencyExport.vue │ │ ├── ClimateExport.vue │ │ ├── CrossDataTypeComparison.vue │ │ ├── GenotypeExport.vue │ │ └── TrialsExport.vue │ ├── genotype │ │ ├── Maps.vue │ │ ├── Marker.vue │ │ └── Markers.vue │ ├── geography │ │ ├── GeographicSearch.vue │ │ └── Locations.vue │ ├── germplasm │ │ ├── Germplasm.vue │ │ ├── GermplasmMatch.vue │ │ ├── GermplasmUnifier.vue │ │ └── Passport.vue │ └── trials │ │ ├── TraitDetails.vue │ │ ├── Traits.vue │ │ └── TrialCreation.vue │ └── error │ ├── Page403.vue │ └── Page404.vue └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | parserOptions: { 11 | parser: '@babel/eslint-parser' 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'vue/no-mutating-props': 'off', 17 | 'vue/multi-word-component-names': 0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/docker-ci-push.yml: -------------------------------------------------------------------------------- 1 | name: Docker CI Push 2 | 3 | # Docker CI builds on master branch push. 4 | # Only builds AMD build to increase development speed. 5 | # Tagged releases will include ARM builds. 6 | 7 | on: 8 | push: 9 | branches: [ master ] 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: read 18 | packages: write 19 | 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v2 23 | 24 | # - name: Set up QEMU 25 | # uses: docker/setup-qemu-action@v1 26 | 27 | # - name: Set up Docker Buildx 28 | # uses: docker/setup-buildx-action@v1 29 | 30 | - name: Log into Dockerhub 31 | if: github.event_name != 'pull_request' 32 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 33 | with: 34 | username: ${{ secrets.DOCKERHUB_USERNAME }} 35 | password: ${{ secrets.DOCKERHUB_TOKEN }} 36 | 37 | # Extract metadata (tags, labels) for Docker 38 | # https://github.com/docker/metadata-action 39 | - name: Extract Docker metadata 40 | id: meta 41 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 42 | with: 43 | images: cropgeeks/germinate 44 | tags: | 45 | type=raw,enable=${{ github.ref == 'refs/heads/master' }},value=development 46 | # Build and push Docker image with Buildx (don't push on PR) 47 | # https://github.com/docker/build-push-action 48 | - name: Build and push Docker image 49 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 50 | with: 51 | context: docker 52 | # platforms: linux/amd64,linux/arm64 53 | platforms: linux/amd64 54 | push: ${{ github.event_name != 'pull_request' }} 55 | tags: ${{ steps.meta.outputs.tags }} 56 | labels: ${{ steps.meta.outputs.labels }} -------------------------------------------------------------------------------- /.github/workflows/docker-ci-tag.yml: -------------------------------------------------------------------------------- 1 | name: Docker CI Tag 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | push: 10 | # Publish semver tags as releases. 11 | tags: [ 'v*.*.*' ] 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | packages: write 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v2 24 | 25 | - name: Set up QEMU 26 | uses: docker/setup-qemu-action@v1 27 | 28 | - name: Set up Docker Buildx 29 | uses: docker/setup-buildx-action@v1 30 | 31 | - name: Log into Dockerhub 32 | if: github.event_name != 'pull_request' 33 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 34 | with: 35 | username: ${{ secrets.DOCKERHUB_USERNAME }} 36 | password: ${{ secrets.DOCKERHUB_TOKEN }} 37 | 38 | - name: Get the version 39 | id: get_version 40 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 41 | 42 | # Extract metadata (tags, labels) for Docker 43 | # https://github.com/docker/metadata-action 44 | - name: Extract Docker metadata 45 | id: meta 46 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 47 | with: 48 | images: cropgeeks/germinate 49 | tags: | 50 | type=semver,pattern=release-{{version}} 51 | # Build and push Docker image with Buildx (don't push on PR) 52 | # https://github.com/docker/build-push-action 53 | - name: Build and push Docker image 54 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 55 | with: 56 | build-args: BRANCH=${{ steps.get_version.outputs.VERSION }} 57 | context: docker 58 | platforms: linux/amd64,linux/arm64 59 | push: ${{ github.event_name != 'pull_request' }} 60 | tags: ${{ steps.meta.outputs.tags }} 61 | labels: ${{ steps.meta.outputs.labels }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /coverage 5 | 6 | /tests/e2e/reports/ 7 | selenium-debug.log 8 | 9 | # local env files 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw* 26 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset', 4 | [ 5 | '@babel/preset-env', { 6 | useBuiltIns: 'usage', 7 | corejs: 3 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22.11-alpine3.20 AS builder 2 | 3 | LABEL maintainer="sebastian.raubach@hutton.ac.uk" 4 | 5 | ARG BRANCH=master 6 | 7 | # Force docker to not cache the next line 8 | ADD https://api.github.com/repos/germinateplatform/germinate-vue/git/refs/heads/master version.json 9 | # Clone the Germinate server code and client code 10 | RUN echo "Pulling GitHub branch: $BRANCH" 11 | RUN apk add --no-cache git && \ 12 | git clone -b "$BRANCH" --single-branch --depth 1 https://github.com/germinateplatform/germinate-server.git /opt/germinate-server && \ 13 | git clone -b "$BRANCH" --single-branch --depth 1 https://github.com/germinateplatform/germinate-vue.git /opt/germinate-client 14 | 15 | # Build the client code 16 | WORKDIR /opt/germinate-client 17 | RUN rm -f .env && \ 18 | echo "VUE_APP_BASE_URL=./api/" > .env && \ 19 | apk add --no-cache python3 build-base gcc wget && \ 20 | npm i --legacy-peer-deps && \ 21 | npm run build && \ 22 | mkdir /opt/germinate-server/client/ && \ 23 | cp -a /opt/germinate-client/dist/. /opt/germinate-server/client/ 24 | 25 | # Download Gradle and build the server code 26 | RUN apk add --no-cache openjdk21 && \ 27 | wget https://services.gradle.org/distributions/gradle-8.8-bin.zip -P /tmp/ && \ 28 | unzip /tmp/gradle-8.8-bin.zip -d /opt/ && \ 29 | echo "data.directory.external=/data/germinate" > /opt/germinate-server/config.properties && \ 30 | echo "project.name=germinate" >> /opt/germinate-server/gradle.properties && \ 31 | /opt/gradle-8.8/bin/gradle -p /opt/germinate-server war 32 | 33 | 34 | FROM tomcat:10.1-jdk21 35 | 36 | LABEL maintainer="sebastian.raubach@hutton.ac.uk" 37 | 38 | RUN apt-get update && \ 39 | apt-get --yes --force-yes install imagemagick unzip zip && \ 40 | # Obscuring server info 41 | cd ${CATALINA_HOME}/lib && \ 42 | mkdir -p org/apache/catalina/util/ && \ 43 | unzip -j catalina.jar org/apache/catalina/util/ServerInfo.properties -d org/apache/catalina/util/ && \ 44 | sed -i 's/server.info=.*/server.info=Apache Tomcat/g' org/apache/catalina/util/ServerInfo.properties && \ 45 | zip -ur catalina.jar org/apache/catalina/util/ServerInfo.properties && \ 46 | rm -rf org && cd ${CATALINA_HOME} && \ 47 | # Add a default error page mapping to hide the exception message 48 | sed -i 's/<\/web-app>/ \n java.lang.Throwable<\/exception-type>\n \/dev\/null<\/location>\n <\/error-page>\n<\/web-app>/g' conf/web.xml && \ 49 | sed -i 's/<\/Host>/ \n <\/Host>/g' conf/server.xml 50 | 51 | RUN mkdir -p /usr/local/tomcat/webapps && \ 52 | rm -rf /usr/local/tomcat/webapps/ROOT 53 | 54 | COPY --from=builder /opt/germinate-server/build/libs/germinate-*.war /usr/local/tomcat/webapps/ROOT.war 55 | 56 | WORKDIR /usr/local/tomcat/ 57 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": [ 9 | "src/*" 10 | ] 11 | }, 12 | "lib": [ 13 | "esnext", 14 | "dom", 15 | "dom.iterable", 16 | "scripthost" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/xlsx-0.20.3.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/lib/xlsx-0.20.3.tgz -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "germinate", 3 | "version": "4.8.4", 4 | "description": "Germinate is an open source plant database infrastructure and application programming platform on which complex data from genetic resource collections can be stored, queried and visualized", 5 | "author": "Sebastian Raubach", 6 | "scripts": { 7 | "serve": "vue-cli-service serve", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "@adapttive/vue-markdown": "^4.0.2", 13 | "@citation-js/core": "^0.7.1", 14 | "@citation-js/plugin-csl": "^0.7.2", 15 | "@citation-js/plugin-doi": "^0.7.2", 16 | "@mdi/js": "^7.4.47", 17 | "axios": "^1.6.2", 18 | "bootstrap": "~4.6.1", 19 | "bootstrap-vue": "~2.22.0", 20 | "bootswatch": "~4.6.1", 21 | "buffer": "^6.0.3", 22 | "compressorjs": "^1.1.1", 23 | "cropperjs": "^1.5.12", 24 | "d3-dsv": "~3.0.1", 25 | "d3-selection": "~3.0.0", 26 | "flag-icons": "~6.2.0", 27 | "html2canvas": "^1.4.1", 28 | "i18n-iso-countries": "~7.4.0", 29 | "leaflet": "^1.9.4", 30 | "leaflet-draw": "~1.0.4", 31 | "leaflet.heat": "~0.2.0", 32 | "leaflet.markercluster": "~1.5.3", 33 | "leaflet.sync": "~0.2.4", 34 | "path-browserify": "~1.0.1", 35 | "plotly.js": "^2.31.1", 36 | "semver": "^7.3.8", 37 | "shpjs": "^4.0.4", 38 | "simplex-noise": "~2.4.0", 39 | "tiny-emitter": "~2.1.0", 40 | "vis-data": "^7.1.4", 41 | "vis-network": "^9.1.2", 42 | "vue": "^2.7.16", 43 | "vue-cool-lightbox": "~2.7.1", 44 | "vue-gtag": "~1.16.1", 45 | "vue-i18n": "~8.27.1", 46 | "vue-plausible": "~1.3.1", 47 | "vue-router": "~3.5.1", 48 | "vue-sidebar-menu": "~4.8.1", 49 | "vue-typeahead-bootstrap": "~2.12.0", 50 | "vue-upload-component": "~2.8.22", 51 | "vue2-editor": "^2.10.3", 52 | "vue2-leaflet": "~2.7.1", 53 | "vuedraggable": "^2.24.3", 54 | "vuex": "~3.6.2", 55 | "vuex-persistedstate": "~4.1.0", 56 | "zxcvbn": "~4.4.2", 57 | "xlsx": "./lib/xlsx-0.20.3.tgz" 58 | }, 59 | "devDependencies": { 60 | "@babel/core": "~7.12.16", 61 | "@babel/eslint-parser": "~7.12.16", 62 | "@vue/cli-plugin-babel": "^5.0.8", 63 | "@vue/cli-plugin-eslint": "^5.0.8", 64 | "@vue/cli-plugin-router": "^5.0.8", 65 | "@vue/cli-plugin-vuex": "^5.0.8", 66 | "@vue/cli-service": "^5.0.8", 67 | "@vue/eslint-config-standard": "~6.1.0", 68 | "eslint": "~7.32.0", 69 | "eslint-plugin-import": "~2.25.3", 70 | "eslint-plugin-node": "~11.1.0", 71 | "eslint-plugin-promise": "~5.1.0", 72 | "eslint-plugin-vue": "~8.0.3", 73 | "node-polyfill-webpack-plugin": "^2.0.1", 74 | "querystring-es3": "~0.2.1", 75 | "sass": "~1.32.7", 76 | "sass-loader": "~12.0.0", 77 | "vue-template-compiler": "~2.6.14", 78 | "webpack-bundle-analyzer": "^4.6.1" 79 | }, 80 | "copyright": "Copyright 2024 The James Hutton Institute", 81 | "homepage": "https://ics.hutton.ac.uk/get-germinate", 82 | "license": "Apache-2.0", 83 | "repository": { 84 | "type": "git", 85 | "url": "https://github.com/germinateplatform/germinate-vue.git" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /public/img/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #2d89ef 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/img/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/favicon.ico -------------------------------------------------------------------------------- /public/img/funders/divseek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/funders/divseek.png -------------------------------------------------------------------------------- /public/img/funders/norway.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /public/img/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/mstile-144x144.png -------------------------------------------------------------------------------- /public/img/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/mstile-310x150.png -------------------------------------------------------------------------------- /public/img/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/mstile-310x310.png -------------------------------------------------------------------------------- /public/img/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/mstile-70x70.png -------------------------------------------------------------------------------- /public/img/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /public/img/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /public/img/team/iain-milne.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/team/iain-milne.jpg -------------------------------------------------------------------------------- /public/img/team/paul-shaw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/team/paul-shaw.jpg -------------------------------------------------------------------------------- /public/img/team/sebastian-raubach.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/team/sebastian-raubach.jpg -------------------------------------------------------------------------------- /public/img/tools/curlywhirly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/tools/curlywhirly.png -------------------------------------------------------------------------------- /public/img/tools/flapjack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/tools/flapjack.png -------------------------------------------------------------------------------- /public/img/tools/helium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/tools/helium.png -------------------------------------------------------------------------------- /public/img/tools/strudel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/tools/strudel.png -------------------------------------------------------------------------------- /public/img/tools/tassel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/public/img/tools/tassel.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Germinate - The generic plant genetic resources database 18 | 19 | 20 | 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/assets/img/helium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/germinateplatform/germinate-vue/8bf88f44d8aab85c9bea8b37e9a23fec453fc2e6/src/assets/img/helium.png -------------------------------------------------------------------------------- /src/assets/img/image-icon-aperture.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/image-icon-camera.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/image-icon-exposure.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/image-icon-flash-no.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/image-icon-flash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/image-icon-focal-length.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/image-icon-gps.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/image-icon-iso.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/auth.js: -------------------------------------------------------------------------------- 1 | import store from './store' 2 | 3 | export default { 4 | loggedIn () { 5 | const token = store.getters.storeToken 6 | return token !== null && this.tokenStillValid() 7 | }, 8 | tokenStillValid () { 9 | const token = store.getters.storeToken 10 | if (token) { 11 | return new Date().getTime() - token.createdOn <= token.lifetime 12 | } else { 13 | return false 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/admin/DatasetPermissions.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 59 | 60 | 63 | -------------------------------------------------------------------------------- /src/components/admin/DatasetUserPermissions.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 76 | 77 | 80 | -------------------------------------------------------------------------------- /src/components/admin/UserGroupMembers.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 70 | 71 | 74 | -------------------------------------------------------------------------------- /src/components/dropdowns/LocaleDropdown.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 80 | 81 | 83 | -------------------------------------------------------------------------------- /src/components/export/BoxplotSelection.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 95 | 96 | 99 | -------------------------------------------------------------------------------- /src/components/export/DatasetOverview.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /src/components/export/ExportDownloadSelection.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 94 | 95 | 97 | -------------------------------------------------------------------------------- /src/components/export/IndividualDatasetWidget.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 73 | 74 | 76 | -------------------------------------------------------------------------------- /src/components/germplasm/GermplasmTableComponent.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 66 | 67 | 70 | -------------------------------------------------------------------------------- /src/components/icons/MdiIcon.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 50 | 51 | 56 | -------------------------------------------------------------------------------- /src/components/icons/SidebarIcon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /src/components/images/ImageCarousel.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 93 | 94 | 108 | -------------------------------------------------------------------------------- /src/components/images/ImageTags.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 72 | 73 | 76 | -------------------------------------------------------------------------------- /src/components/map/TrialsLocationMap.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 95 | 96 | 99 | -------------------------------------------------------------------------------- /src/components/modals/AddAboutPartnerModal.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 93 | 94 | 97 | -------------------------------------------------------------------------------- /src/components/modals/AddStoryStepModal.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 97 | 98 | 101 | -------------------------------------------------------------------------------- /src/components/modals/ClimateOverlayModal.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 65 | 66 | 68 | -------------------------------------------------------------------------------- /src/components/modals/CustomChartColorModal.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 97 | 98 | 101 | -------------------------------------------------------------------------------- /src/components/modals/EditTagModal.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 70 | 71 | 73 | -------------------------------------------------------------------------------- /src/components/modals/ExperimentCreationModal.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 100 | 101 | 103 | -------------------------------------------------------------------------------- /src/components/modals/FileResourceTypeModal.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 69 | 70 | 73 | -------------------------------------------------------------------------------- /src/components/modals/GenotypeExportModal.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 49 | -------------------------------------------------------------------------------- /src/components/modals/GetTokenModal.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 47 | 48 | 51 | -------------------------------------------------------------------------------- /src/components/modals/GroupEditAddModal.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 66 | 67 | 70 | -------------------------------------------------------------------------------- /src/components/modals/GroupUploadModal.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /src/components/modals/ImageUploadModal.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 55 | 56 | 59 | -------------------------------------------------------------------------------- /src/components/modals/InstitutionModal.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 45 | 46 | 49 | -------------------------------------------------------------------------------- /src/components/modals/LocationCountrySelectionModal.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 73 | 74 | 77 | -------------------------------------------------------------------------------- /src/components/modals/LocationSelectionModal.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 64 | 65 | 68 | -------------------------------------------------------------------------------- /src/components/modals/PublicationsModal.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 38 | 39 | 41 | -------------------------------------------------------------------------------- /src/components/modals/ReferenceModal.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 58 | 59 | 61 | -------------------------------------------------------------------------------- /src/components/modals/TraitEditModal.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 78 | 79 | 87 | -------------------------------------------------------------------------------- /src/components/modals/YesNoCancelModal.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 64 | 65 | 67 | -------------------------------------------------------------------------------- /src/components/structure/SidebarLogoComponent.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | 16 | 24 | -------------------------------------------------------------------------------- /src/components/tables/MapTable.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 99 | 100 | 102 | -------------------------------------------------------------------------------- /src/components/tables/MarkedItems.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 88 | 89 | 91 | -------------------------------------------------------------------------------- /src/components/tables/MarkerTable.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 105 | 106 | 108 | -------------------------------------------------------------------------------- /src/components/tables/UserTable.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 116 | 117 | 123 | -------------------------------------------------------------------------------- /src/components/tables/details/CollaboratorDetails.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /src/components/tables/details/InstitutionDetails.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 44 | 45 | 48 | -------------------------------------------------------------------------------- /src/components/util/BannerCard.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 90 | 91 | 97 | -------------------------------------------------------------------------------- /src/components/util/Collapse.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 100 | 101 | 112 | -------------------------------------------------------------------------------- /src/components/util/DatasetMetadataDownload copy.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 69 | 70 | 73 | -------------------------------------------------------------------------------- /src/components/util/DatasetMetadataDownload.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 69 | 70 | 73 | -------------------------------------------------------------------------------- /src/components/util/DatasetsWithUnacceptedLicense.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 58 | 59 | 62 | -------------------------------------------------------------------------------- /src/components/util/DropFilePreview.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 61 | 62 | 65 | -------------------------------------------------------------------------------- /src/components/util/Links.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 64 | 65 | 68 | -------------------------------------------------------------------------------- /src/components/util/MarkerLookup.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 80 | 81 | 84 | -------------------------------------------------------------------------------- /src/components/util/PasswordInput.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 95 | 96 | 106 | -------------------------------------------------------------------------------- /src/components/util/SignInForm.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 84 | -------------------------------------------------------------------------------- /src/components/util/TraitCategories.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 52 | 53 | 56 | -------------------------------------------------------------------------------- /src/components/util/TrialGermplasmLookup.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 101 | 102 | 105 | -------------------------------------------------------------------------------- /src/const/table-props.js: -------------------------------------------------------------------------------- 1 | export default { 2 | BASE: { 3 | filterOn: { 4 | type: Array, 5 | default: null 6 | }, 7 | getData: { 8 | type: Function, 9 | default: () => { 10 | return { 11 | data: [], 12 | count: 0 13 | } 14 | } 15 | }, 16 | storeUrlParameters: { 17 | type: Boolean, 18 | default: true 19 | } 20 | }, 21 | DOWNLOAD: { 22 | downloadTable: { 23 | type: Function, 24 | default: null 25 | } 26 | }, 27 | IDS: { 28 | getIds: { 29 | type: Function, 30 | default: () => { 31 | return { 32 | data: [], 33 | count: 0 34 | } 35 | } 36 | } 37 | }, 38 | ACTIONS: { 39 | tableActions: { 40 | type: Array, 41 | default: () => null 42 | } 43 | }, 44 | FULL: { 45 | downloadTable: { 46 | type: Function, 47 | default: null 48 | }, 49 | filterOn: { 50 | type: Array, 51 | default: null 52 | }, 53 | getData: { 54 | type: Function, 55 | default: () => { 56 | return { 57 | data: [], 58 | count: 0 59 | } 60 | } 61 | }, 62 | getIds: { 63 | type: Function, 64 | default: () => { 65 | return { 66 | data: [], 67 | count: 0 68 | } 69 | } 70 | }, 71 | tableActions: { 72 | type: Array, 73 | default: () => null 74 | }, 75 | storeUrlParameters: { 76 | type: Boolean, 77 | default: true 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { BadgePlugin, ButtonGroupPlugin, FormFilePlugin, ButtonPlugin, CardPlugin, CarouselPlugin, FormCheckboxPlugin, FormDatepickerPlugin, FormGroupPlugin, FormInputPlugin, FormPlugin, FormRadioPlugin, FormSelectPlugin, FormTagsPlugin, FormTextareaPlugin, ImagePlugin, InputGroupPlugin, LayoutPlugin, ListGroupPlugin, ModalPlugin, NavbarPlugin, PaginationPlugin, PopoverPlugin, ProgressPlugin, SidebarPlugin, SpinnerPlugin, TablePlugin, TabsPlugin, TooltipPlugin, VBScrollspyPlugin, ToastPlugin, CalendarPlugin, AlertPlugin } from 'bootstrap-vue' 2 | 3 | import Vue from 'vue' 4 | import App from '@/App.vue' 5 | import router from '@/router' 6 | import store from '@/store' 7 | import { i18n } from '@/plugins/i18n.js' 8 | 9 | import VueGtag from 'vue-gtag' 10 | 11 | Vue.config.productionTip = false 12 | 13 | Vue.use(BadgePlugin) 14 | Vue.use(ButtonGroupPlugin) 15 | Vue.use(ButtonPlugin) 16 | Vue.use(CardPlugin) 17 | Vue.use(CarouselPlugin) 18 | Vue.use(CalendarPlugin) 19 | Vue.use(FormCheckboxPlugin) 20 | Vue.use(FormDatepickerPlugin) 21 | Vue.use(FormFilePlugin) 22 | Vue.use(FormGroupPlugin) 23 | Vue.use(FormPlugin) 24 | Vue.use(FormInputPlugin) 25 | Vue.use(FormRadioPlugin) 26 | Vue.use(FormSelectPlugin) 27 | Vue.use(FormTagsPlugin) 28 | Vue.use(FormTextareaPlugin) 29 | Vue.use(ImagePlugin) 30 | Vue.use(InputGroupPlugin) 31 | Vue.use(LayoutPlugin, { breakpoints: ['xs', 'sm', 'md', 'lg', 'xl', 'xxl', 'xxxl'] }) 32 | Vue.use(ListGroupPlugin) 33 | Vue.use(ModalPlugin) 34 | Vue.use(NavbarPlugin) 35 | Vue.use(PaginationPlugin) 36 | Vue.use(PopoverPlugin) 37 | Vue.use(ProgressPlugin) 38 | Vue.use(SidebarPlugin) 39 | Vue.use(SpinnerPlugin) 40 | Vue.use(TablePlugin) 41 | Vue.use(TabsPlugin) 42 | Vue.use(TooltipPlugin) 43 | Vue.use(ToastPlugin) 44 | Vue.use(VBScrollspyPlugin) 45 | Vue.use(AlertPlugin) 46 | 47 | Vue.use(VueGtag, { 48 | bootstrap: false, 49 | enabled: false 50 | }, router) 51 | 52 | // Set base URL 53 | let baseUrl = './api/' 54 | if (process.env.VUE_APP_BASE_URL) { 55 | baseUrl = process.env.VUE_APP_BASE_URL 56 | } 57 | 58 | store.commit('ON_APP_STATE_CHANGED_MUTATION', process.env.NODE_ENV) 59 | store.commit('ON_BASE_URL_CHANGED_MUTATION', baseUrl) 60 | 61 | new Vue({ 62 | router, 63 | store, 64 | i18n, 65 | render: h => h(App) 66 | }).$mount('#app') 67 | -------------------------------------------------------------------------------- /src/mixins/api/auth.js: -------------------------------------------------------------------------------- 1 | import { authAxios } from '@/mixins/api/base' 2 | 3 | const USER_TYPE_ADMINISTRATOR = 'Administrator' 4 | const USER_TYPE_DATA_CURATOR = 'Data Curator' 5 | const USER_TYPE_REGULAR_USER = 'Regular User' 6 | 7 | /** 8 | * Checks whether the given user type is at least the given minimum user type 9 | * @param {String} userType The user type to check 10 | * @param {String} atLeast The user type to check against 11 | */ 12 | const userIsAtLeast = (userType, atLeast) => { 13 | switch (atLeast) { 14 | case USER_TYPE_ADMINISTRATOR: 15 | return userType === USER_TYPE_ADMINISTRATOR 16 | case USER_TYPE_DATA_CURATOR: 17 | return userType === USER_TYPE_ADMINISTRATOR || userType === USER_TYPE_DATA_CURATOR 18 | case USER_TYPE_REGULAR_USER: 19 | return userType === USER_TYPE_ADMINISTRATOR || userType === USER_TYPE_DATA_CURATOR || userType === USER_TYPE_REGULAR_USER 20 | } 21 | 22 | return false 23 | } 24 | 25 | /** 26 | * Deletes the current json token 27 | * 28 | * @param {Object} user The user object containing the username and the token as the password 29 | * @param {function=} onSuccess Called on success 30 | * @param {function=} onError Called on failure 31 | * @returns A Promise 32 | */ 33 | const apiDeleteToken = (user, onSuccess, onError) => authAxios({ url: 'token', method: 'DELETE', data: user, success: onSuccess, error: onError }) 34 | 35 | /** 36 | * Requests a token given the user details 37 | * 38 | * @param {Object} user The user object containing the username and password 39 | * @param {function=} onSuccess Called on success 40 | * @param {function=} onError Called on failure 41 | * @returns A Promise 42 | */ 43 | const apiPostToken = (user, onSuccess, onError) => authAxios({ url: 'token', method: 'POST', data: user, success: onSuccess, error: onError }) 44 | 45 | const apiSetupCheckGatekeeper = (gkConfig, onSuccess, onError) => authAxios({ url: 'setup/check/gatekeeper', method: 'POST', data: gkConfig, success: onSuccess, error: onError }) 46 | 47 | const apiSetupCheckDatabase = (dbConfig, onSuccess, onError) => authAxios({ url: 'setup/check/database', method: 'POST', data: dbConfig, success: onSuccess, error: onError }) 48 | 49 | const apiSetupStore = (data, onSuccess, onError) => authAxios({ url: 'setup/store', method: 'POST', data: data, success: onSuccess, error: onError }) 50 | 51 | export { 52 | userIsAtLeast, 53 | apiDeleteToken, 54 | apiPostToken, 55 | apiSetupCheckGatekeeper, 56 | apiSetupCheckDatabase, 57 | apiSetupStore, 58 | USER_TYPE_ADMINISTRATOR, 59 | USER_TYPE_DATA_CURATOR, 60 | USER_TYPE_REGULAR_USER 61 | } 62 | -------------------------------------------------------------------------------- /src/mixins/api/climate.js: -------------------------------------------------------------------------------- 1 | import { authAxios } from '@/mixins/api/base' 2 | 3 | const apiPostDatasetClimates = (datasetIds, onSuccess, onError) => { 4 | const queryData = { 5 | datasetIds: datasetIds 6 | } 7 | return authAxios({ url: 'dataset/climate', method: 'POST', data: queryData, success: onSuccess, error: onError }) 8 | } 9 | 10 | const apiPostClimateTable = (queryData, onSuccess, onError) => { 11 | queryData.page -= 1 12 | return authAxios({ url: 'climate/table', method: 'POST', data: queryData, success: onSuccess, error: onError }) 13 | } 14 | 15 | const apiPostClimates = (queryData, onSuccess, onError) => { 16 | return authAxios({ url: 'climate/table', method: 'POST', data: queryData, success: onSuccess, error: onError }) 17 | } 18 | 19 | const apiPostClimateDataTable = (queryData, onSuccess, onError) => { 20 | queryData.page -= 1 21 | return authAxios({ url: 'dataset/data/climate/table', method: 'POST', data: queryData, success: onSuccess, error: onError }) 22 | } 23 | 24 | const apiPostClimateDataTableIds = (queryData, onSuccess, onError) => { 25 | delete queryData.orderBy 26 | delete queryData.ascending 27 | return authAxios({ url: 'dataset/data/climate/table/ids', method: 'POST', data: queryData, success: onSuccess, error: onError }) 28 | } 29 | 30 | const apiPostClimateDatasetTable = (climateId, queryData, onSuccess, onError) => { 31 | queryData.page -= 1 32 | return authAxios({ url: `climate/${climateId}/dataset`, method: 'POST', data: queryData, success: onSuccess, error: onError }) 33 | } 34 | 35 | export { 36 | apiPostDatasetClimates, 37 | apiPostClimateTable, 38 | apiPostClimates, 39 | apiPostClimateDataTable, 40 | apiPostClimateDataTableIds, 41 | apiPostClimateDatasetTable 42 | } 43 | -------------------------------------------------------------------------------- /src/mixins/api/group.js: -------------------------------------------------------------------------------- 1 | import { MAX_JAVA_INTEGER, authAxios } from '@/mixins/api/base' 2 | 3 | const apiPatchGroup = (group, onSuccess, onError) => authAxios({ url: `group/${group.id}`, data: group, method: 'PATCH', success: onSuccess, error: onError }) 4 | 5 | const apiPutGroup = (group, onSuccess, onError) => authAxios({ url: 'group', data: group, method: 'PUT', success: onSuccess, error: onError }) 6 | 7 | const apiDeleteGroup = (groupId, onSuccess, onError) => authAxios({ url: `group/${groupId}`, method: 'DELETE', success: onSuccess, error: onError }) 8 | 9 | const apiPatchGroupMembers = (groupId, groupType, groupModification, onSuccess, onError) => authAxios({ url: `group/${groupId}/${groupType}`, method: 'PATCH', data: groupModification, success: onSuccess, error: onError }) 10 | 11 | const apiGetGroupTypes = (onSuccess, onError) => authAxios({ url: `grouptype?limit=${MAX_JAVA_INTEGER}`, success: onSuccess, error: onError }) 12 | 13 | const apiPostGroupTable = (queryData, onSuccess, onError) => { 14 | queryData.page -= 1 15 | return authAxios({ url: 'group/table', method: 'POST', data: queryData, success: onSuccess, error: onError }) 16 | } 17 | 18 | const apiPostDatasetGroups = (queryData, onSuccess, onError) => authAxios({ url: 'dataset/group', method: 'POST', data: queryData, success: onSuccess, error: onError }) 19 | 20 | const apiPostPublicationGroupTable = (publicationId, queryData, onSuccess, onError) => { 21 | queryData.page -= 1 22 | return authAxios({ url: `publication/${publicationId}/group`, method: 'POST', data: queryData, success: onSuccess, error: onError }) 23 | } 24 | 25 | export { 26 | apiPatchGroup, 27 | apiPutGroup, 28 | apiDeleteGroup, 29 | apiPatchGroupMembers, 30 | apiGetGroupTypes, 31 | apiPostGroupTable, 32 | apiPostDatasetGroups, 33 | apiPostPublicationGroupTable 34 | } 35 | -------------------------------------------------------------------------------- /src/mixins/api/location.js: -------------------------------------------------------------------------------- 1 | import { authAxios } from '@/mixins/api/base' 2 | 3 | const apiPostLocationTable = (queryData, onSuccess, onError) => { 4 | queryData.page -= 1 5 | return authAxios({ url: 'location/table', method: 'POST', data: queryData, success: onSuccess, error: onError }) 6 | } 7 | 8 | const apiPostLocationDistanceTable = (queryData, onSuccess, onError) => { 9 | queryData.page -= 1 10 | return authAxios({ url: 'location/distance/table', method: 'POST', data: queryData, success: onSuccess, error: onError }) 11 | } 12 | 13 | const apiPostLocationDistanceTableIds = (queryData, onSuccess, onError) => { 14 | delete queryData.orderBy 15 | delete queryData.ascending 16 | return authAxios({ url: 'location/distance/table/ids', method: 'POST', data: queryData, success: onSuccess, error: onError }) 17 | } 18 | 19 | const apiPostLocationPolygonTable = (queryData, onSuccess, onError) => { 20 | queryData.page -= 1 21 | if (queryData.orderBy === 'distance') { 22 | delete queryData.orderBy 23 | delete queryData.ascending 24 | } 25 | return authAxios({ url: 'location/polygon/table', method: 'POST', data: queryData, success: onSuccess, error: onError }) 26 | } 27 | 28 | const apiPostLocationPolygonTableIds = (queryData, onSuccess, onError) => { 29 | delete queryData.orderBy 30 | delete queryData.ascending 31 | return authAxios({ url: 'location/polygon/table/ids', method: 'POST', data: queryData, success: onSuccess, error: onError }) 32 | } 33 | 34 | const apiPostLocationTableIds = (queryData, onSuccess, onError) => { 35 | delete queryData.orderBy 36 | delete queryData.ascending 37 | return authAxios({ url: 'location/table/ids', method: 'POST', data: queryData, success: onSuccess, error: onError }) 38 | } 39 | 40 | const apiPostGroupLocationTableExport = (groupId, queryData, onSuccess, onError) => authAxios({ url: `group/${groupId}/location/export`, method: 'POST', dataType: 'blob', data: queryData, success: onSuccess, error: onError }) 41 | 42 | const apiPostGroupLocationTable = (groupId, queryData, onSuccess, onError) => { 43 | queryData.page -= 1 44 | return authAxios({ url: `group/${groupId}/location`, method: 'POST', data: queryData, success: onSuccess, error: onError }) 45 | } 46 | 47 | const apiPostGroupLocationTableIds = (groupId, queryData, onSuccess, onError) => { 48 | delete queryData.orderBy 49 | delete queryData.ascending 50 | return authAxios({ url: `group/${groupId}/location/ids`, method: 'POST', data: queryData, success: onSuccess, error: onError }) 51 | } 52 | 53 | const apiGetCountries = (onSuccess, onError) => authAxios({ url: 'country', success: onSuccess, error: onError }) 54 | 55 | export { 56 | apiPostLocationTable, 57 | apiPostLocationTableIds, 58 | apiPostLocationDistanceTable, 59 | apiPostLocationDistanceTableIds, 60 | apiPostLocationPolygonTable, 61 | apiPostLocationPolygonTableIds, 62 | apiPostGroupLocationTableExport, 63 | apiPostGroupLocationTable, 64 | apiPostGroupLocationTableIds, 65 | apiGetCountries 66 | } 67 | -------------------------------------------------------------------------------- /src/mixins/api/project.js: -------------------------------------------------------------------------------- 1 | import { authAxios, authForm } from '@/mixins/api/base' 2 | 3 | const apiPostProjectTable = (queryData, onSuccess, onError) => { 4 | queryData.page -= 1 5 | return authAxios({ url: 'project/table', method: 'POST', data: queryData, success: onSuccess, error: onError }) 6 | } 7 | 8 | const apiPostProjectTableIds = (queryData, onSuccess, onError) => { 9 | delete queryData.orderBy 10 | delete queryData.ascending 11 | return authAxios({ url: 'project/table/ids', method: 'POST', data: queryData, success: onSuccess, error: onError }) 12 | } 13 | 14 | const apiPostProject = (formData, onSuccess, onError) => authForm({ url: 'project', formData: formData, success: onSuccess, error: onError }) 15 | 16 | const apiPatchProject = (projectId, formData, onSuccess, onError) => authForm({ url: `project/${projectId}`, method: 'patch', formData: formData, success: onSuccess, error: onError }) 17 | 18 | const apiDeleteProject = (projectId, onSuccess, onError) => authAxios({ url: `project/${projectId}`, method: 'DELETE', success: onSuccess, error: onError }) 19 | 20 | const apiGetProjectStats = (projectId, onSuccess, onError) => authAxios({ url: `project/${projectId}/stats`, success: onSuccess, error: onError }) 21 | 22 | export { 23 | apiPostProjectTable, 24 | apiPostProjectTableIds, 25 | apiPostProject, 26 | apiPatchProject, 27 | apiDeleteProject, 28 | apiGetProjectStats 29 | } 30 | -------------------------------------------------------------------------------- /src/mixins/api/stats.js: -------------------------------------------------------------------------------- 1 | import { authAxios } from '@/mixins/api/base' 2 | 3 | const apiGetOverviewStats = (onSuccess, onError) => authAxios({ url: 'stats/overview', success: onSuccess, error: onError }) 4 | 5 | const apiGetEntityTypeStats = (onSuccess, onError) => authAxios({ url: 'stats/entitytype', success: onSuccess, error: onError }) 6 | 7 | const apiGetStatsFile = (type, onSuccess, onError) => authAxios({ url: `stats/${type}`, dataType: 'blob', success: onSuccess, error: onError }) 8 | 9 | export { 10 | apiGetOverviewStats, 11 | apiGetEntityTypeStats, 12 | apiGetStatsFile 13 | } 14 | -------------------------------------------------------------------------------- /src/mixins/api/usergroup.js: -------------------------------------------------------------------------------- 1 | import { authAxios } from '@/mixins/api/base' 2 | 3 | const apiPostDatasetUserGroupTable = (queryData, datasetId, onSuccess, onError) => { 4 | queryData.page -= 1 5 | return authAxios({ url: `dataset/${datasetId}/usergroup`, method: 'POST', data: queryData, success: onSuccess, error: onError }) 6 | } 7 | 8 | const apiPostDatasetUserGroupIds = (queryData, datasetId, onSuccess, onError) => { 9 | delete queryData.orderBy 10 | delete queryData.ascending 11 | return authAxios({ url: `dataset/${datasetId}/usergroup/ids`, method: 'POST', data: queryData, success: onSuccess, error: onError }) 12 | } 13 | 14 | const apiPostUserGroupTable = (queryData, onSuccess, onError) => { 15 | queryData.page -= 1 16 | return authAxios({ url: 'usergroup/table', method: 'POST', data: queryData, success: onSuccess, error: onError }) 17 | } 18 | 19 | const apiPostUserGroupIds = (queryData, onSuccess, onError) => { 20 | delete queryData.orderBy 21 | delete queryData.ascending 22 | return authAxios({ url: 'usergroup/table/ids', method: 'POST', data: queryData, success: onSuccess, error: onError }) 23 | } 24 | 25 | const apiDeleteUserGroup = (groupId, onSuccess, onError) => authAxios({ url: `usergroup/${groupId}`, method: 'DELETE', success: onSuccess, error: onError }) 26 | 27 | const apiPutUserGroup = (group, onSuccess, onError) => authAxios({ url: 'usergroup', method: 'PUT', data: group, success: onSuccess, error: onError }) 28 | 29 | const apiPatchUserGroup = (group, onSuccess, onError) => authAxios({ url: `usergroup/${group.id}`, data: group, method: 'PATCH', success: onSuccess, error: onError }) 30 | 31 | const apiGetUsers = (params, onSuccess, onError) => { 32 | if (params && params.userGroupId) { 33 | return authAxios({ url: `usergroup/${params.userGroupId}/user`, success: onSuccess, error: onError }) 34 | } else if (params && params.datasetId) { 35 | return authAxios({ url: `dataset/${params.datasetId}/user`, success: onSuccess, error: onError }) 36 | } else { 37 | return authAxios({ url: 'user', success: onSuccess, error: onError }) 38 | } 39 | } 40 | 41 | const apiPatchUserGroupMembers = (queryData, onSuccess, onError) => authAxios({ url: `usergroup/${queryData.userGroupId}/user`, data: queryData, method: 'PATCH', success: onSuccess, error: onError }) 42 | 43 | const apiPatchDatasetUserGroups = (queryData, onSuccess, onError) => authAxios({ url: `dataset/${queryData.datasetId}/usergroup`, data: queryData, method: 'PATCH', success: onSuccess, error: onError }) 44 | 45 | const apiPatchDatasetUserMembers = (queryData, onSuccess, onError) => authAxios({ url: `dataset/${queryData.datasetId}/user`, data: queryData, method: 'PATCH', success: onSuccess, error: onError }) 46 | 47 | export { 48 | apiPostDatasetUserGroupTable, 49 | apiPostDatasetUserGroupIds, 50 | apiPostUserGroupTable, 51 | apiPostUserGroupIds, 52 | apiDeleteUserGroup, 53 | apiPutUserGroup, 54 | apiPatchUserGroup, 55 | apiGetUsers, 56 | apiPatchUserGroupMembers, 57 | apiPatchDatasetUserGroups, 58 | apiPatchDatasetUserMembers 59 | } 60 | -------------------------------------------------------------------------------- /src/mixins/image.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | import { toUrlString } from '@/mixins/formatting' 3 | 4 | const getImageUrl = (name, params) => { 5 | const paramString = toUrlString(params) 6 | 7 | let finalName = name || '' 8 | let index = finalName.lastIndexOf('\\') 9 | if (index !== -1) { 10 | finalName = finalName.substring(index + 1) 11 | } 12 | index = finalName.lastIndexOf('/') 13 | if (index !== -1) { 14 | finalName = finalName.substring(index + 1) 15 | } 16 | 17 | return `${store.getters.storeBaseUrl}image/src/${encodeURI(finalName)}?${paramString}` 18 | } 19 | 20 | const getImageUrlById = (id, params) => { 21 | const paramString = toUrlString(params) 22 | 23 | return `${store.getters.storeBaseUrl}image/${id}/src?${paramString}` 24 | } 25 | 26 | const getFeedbackImageUrl = (id, params) => { 27 | const paramString = toUrlString(params) 28 | 29 | return `${store.getters.storeBaseUrl}feedback/${id}/img?${paramString}` 30 | } 31 | 32 | export { 33 | getImageUrl, 34 | getFeedbackImageUrl, 35 | getImageUrlById 36 | } 37 | -------------------------------------------------------------------------------- /src/mixins/pages.js: -------------------------------------------------------------------------------- 1 | const Pages = Object.freeze({ 2 | home: 'home', 3 | login: 'login', 4 | setup: 'setup', 5 | cookies: 'cookies', 6 | userPermissions: 'user-permissions', 7 | germinateSettings: 'germinate-settings', 8 | germplasm: 'germplasm', 9 | germplasmUnifier: 'germplasm-unifier', 10 | germplasmMatch: 'germplasm-match', 11 | passport: 'passport', 12 | climates: 'climates', 13 | climateDetails: 'climate-details', 14 | traits: 'traits', 15 | traitDetails: 'trait-details', 16 | trialCreation: 'trial-creation', 17 | markers: 'markers', 18 | markerDetails: 'marker-details', 19 | maps: 'maps', 20 | mapDetails: 'map-details', 21 | export: 'export', 22 | exportCrossComparison: 'export-cross-comparison', 23 | exportGenotypes: 'export-genotypes', 24 | exportClimates: 'export-climates', 25 | exportTraits: 'export-trials', 26 | exportAlleleFrequency: 'export-allelefrequency', 27 | locations: 'locations', 28 | geographicSearch: 'geographic-search', 29 | datasets: 'datasets', 30 | experiments: 'experiments', 31 | experimentDetails: 'experiment-details', 32 | dataResources: 'data-resources', 33 | statistics: 'statistics', 34 | images: 'images', 35 | markedItems: 'marked-items', 36 | markedItemsType: 'marked-items-type', 37 | importUpload: 'import-upload', 38 | importUploadType: 'import-upload-type', 39 | search: 'search', 40 | searchQuery: 'search-query', 41 | publications: 'publications', 42 | publicationDetails: 'publication-details', 43 | stories: 'stories', 44 | groups: 'groups', 45 | groupDetails: 'group-details', 46 | groupUpload: 'group-upload', 47 | aboutGerminate: 'about-germinate', 48 | aboutProject: 'about-project', 49 | aboutExportFormats: 'about-export-formats', 50 | aboutExportFormatsType: 'about-export-formats-type', 51 | userFeedback: 'user-feedback', 52 | projects: 'projects', 53 | projectDetails: 'project-details', 54 | fourZeroThree: '403', 55 | fourZeroFour: '404', 56 | fallback: 'fallback', 57 | genesysRequest: 'genesys-request' 58 | }) 59 | 60 | export { 61 | Pages 62 | } 63 | -------------------------------------------------------------------------------- /src/mixins/search.js: -------------------------------------------------------------------------------- 1 | import { i18n } from '@/plugins/i18n.js' 2 | 3 | const operators = { 4 | and: { 5 | text: () => i18n.t('operatorsAnd'), 6 | value: 'and' 7 | }, 8 | or: { 9 | text: () => i18n.t('operatorsOr'), 10 | value: 'or' 11 | } 12 | } 13 | 14 | const comparators = { 15 | contains: { 16 | text: () => i18n.t('comparatorsContains'), 17 | values: 1 18 | }, 19 | equals: { 20 | text: () => i18n.t('comparatorsEqual'), 21 | values: 1 22 | }, 23 | isNull: { 24 | text: () => i18n.t('comparatorsIsNull'), 25 | value: 0 26 | }, 27 | isNotNull: { 28 | text: () => i18n.t('comparatorsIsNotNull'), 29 | value: 0 30 | }, 31 | between: { 32 | text: () => i18n.t('comparatorsBetween'), 33 | values: 2 34 | }, 35 | greaterThan: { 36 | text: () => i18n.t('comparatorsGreaterThan'), 37 | values: 1 38 | }, 39 | greaterOrEquals: { 40 | text: () => i18n.t('comparatorsGreaterThanOrEquals'), 41 | values: 1 42 | }, 43 | lessThan: { 44 | text: () => i18n.t('comparatorsLessThan'), 45 | values: 1 46 | }, 47 | lessOrEquals: { 48 | text: () => i18n.t('comparatorsLessThanOrEquals'), 49 | values: 1 50 | }, 51 | inSet: { 52 | text: () => i18n.t('comparatorsInSet'), 53 | values: 1 54 | }, 55 | jsonSearch: { 56 | text: () => i18n.t('comparatorsJsonSearch'), 57 | values: 1 58 | }, 59 | arrayContains: { 60 | text: () => i18n.t('comparatorsArrayContains'), 61 | values: 1 62 | } 63 | } 64 | 65 | export { 66 | comparators, 67 | operators 68 | } 69 | -------------------------------------------------------------------------------- /src/plugins/charts/plotly-sunburst-chart.js: -------------------------------------------------------------------------------- 1 | export function plotlySunburstChart (Plotly) { 2 | let height = 800 3 | let onLeafClicked = null 4 | let darkMode = false 5 | let colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'] 6 | 7 | function chart (selection) { 8 | selection.each(function (rows) { 9 | const data = [{ 10 | labels: rows.labels, 11 | parents: rows.parents, 12 | values: rows.values, 13 | type: 'sunburst', 14 | marker: { line: { width: 1 } }, 15 | branchvalues: 'total', 16 | textinfo: 'label+value' 17 | }] 18 | 19 | const config = { 20 | modeBarButtonsToRemove: ['toImage'], 21 | displayModeBar: false, 22 | responsive: true, 23 | displaylogo: false 24 | } 25 | 26 | const layout = { 27 | height: height, 28 | paper_bgcolor: 'transparent', 29 | plot_bgcolor: 'transparent', 30 | margin: { l: 20, r: 20, b: 20, t: 20 }, 31 | xaxis: { 32 | automargin: true 33 | }, 34 | yaxis: { 35 | automargin: true 36 | }, 37 | legend: { 38 | bgcolor: 'rgba(0,0,0,0)', 39 | orientation: 'h', 40 | font: { color: darkMode ? 'white' : 'black' } 41 | }, 42 | sunburstcolorway: colors, 43 | extendsunburstcolorway: true 44 | } 45 | 46 | // Plotly.purge(this) 47 | Plotly.react(this, data, layout, config) 48 | 49 | if (onLeafClicked) { 50 | this.on('plotly_sunburstclick', data => { 51 | if (data.nextLevel === undefined && data.points && data.points.length > 0) { 52 | const path = data.points[0].currentPath.split('/') 53 | if (path.length > 0 && path[0] === '') { 54 | path.shift() 55 | } 56 | path.pop() 57 | 58 | path.push(data.points[0].label) 59 | 60 | onLeafClicked(path) 61 | } 62 | }) 63 | } 64 | }) 65 | } 66 | 67 | chart.height = (_) => { 68 | if (!arguments.length) { 69 | return height 70 | } 71 | height = _ 72 | return chart 73 | } 74 | 75 | chart.colors = (_) => { 76 | if (!arguments.length) { 77 | return colors 78 | } 79 | colors = _ 80 | return chart 81 | } 82 | 83 | chart.onLeafClicked = (_) => { 84 | if (!arguments.length) { 85 | return onLeafClicked 86 | } 87 | onLeafClicked = _ 88 | return chart 89 | } 90 | 91 | chart.darkMode = (_) => { 92 | if (!arguments.length) { 93 | return darkMode 94 | } 95 | darkMode = _ 96 | return chart 97 | } 98 | 99 | return chart 100 | } 101 | -------------------------------------------------------------------------------- /src/plugins/charts/plotly-treemap-chart.js: -------------------------------------------------------------------------------- 1 | export function plotlyTreemapChart (Plotly) { 2 | let height = 800 3 | let onLeafClicked = null 4 | let darkMode = false 5 | let colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'] 6 | 7 | function chart (selection) { 8 | selection.each(function (rows) { 9 | const data = [{ 10 | type: 'treemap', 11 | branchvalues: 'total', 12 | textinfo: 'label+value+percent parent+percent entry', 13 | labels: rows.labels, 14 | parents: rows.parents, 15 | values: rows.values, 16 | pathbar: { visible: true } 17 | }] 18 | 19 | const config = { 20 | modeBarButtonsToRemove: ['toImage'], 21 | displayModeBar: false, 22 | responsive: true, 23 | displaylogo: false 24 | } 25 | 26 | const layout = { 27 | height: height, 28 | paper_bgcolor: 'transparent', 29 | plot_bgcolor: 'transparent', 30 | margin: { l: 20, r: 20, b: 20, t: 20 }, 31 | xaxis: { 32 | automargin: true 33 | }, 34 | yaxis: { 35 | automargin: true 36 | }, 37 | legend: { 38 | bgcolor: 'rgba(0,0,0,0)', 39 | orientation: 'h', 40 | font: { color: darkMode ? 'white' : 'black' } 41 | }, 42 | treemapcolorway: colors, 43 | extendtreemapcolorway: true 44 | } 45 | 46 | // Plotly.purge(this) 47 | Plotly.react(this, data, layout, config) 48 | 49 | if (onLeafClicked) { 50 | this.on('plotly_treemapclick', data => { 51 | if (data.points && data.points.length > 0 && data.nextLevel === data.points[0].parent) { 52 | const parts = [] 53 | 54 | data.points[0].parent.split('|').map(p => p.trim()).forEach(p => { 55 | if (!parts.includes(p)) { 56 | parts.push(p) 57 | } 58 | }) 59 | 60 | data.points[0].label.split('|').map(p => p.trim()).forEach(p => { 61 | if (!parts.includes(p)) { 62 | parts.push(p) 63 | } 64 | }) 65 | 66 | onLeafClicked(parts) 67 | } 68 | }) 69 | } 70 | }) 71 | } 72 | 73 | chart.height = (_) => { 74 | if (!arguments.length) { 75 | return height 76 | } 77 | height = _ 78 | return chart 79 | } 80 | 81 | chart.colors = (_) => { 82 | if (!arguments.length) { 83 | return colors 84 | } 85 | colors = _ 86 | return chart 87 | } 88 | 89 | chart.onLeafClicked = (_) => { 90 | if (!arguments.length) { 91 | return onLeafClicked 92 | } 93 | onLeafClicked = _ 94 | return chart 95 | } 96 | 97 | chart.darkMode = (_) => { 98 | if (!arguments.length) { 99 | return darkMode 100 | } 101 | darkMode = _ 102 | return chart 103 | } 104 | 105 | return chart 106 | } 107 | -------------------------------------------------------------------------------- /src/plugins/debounce.js: -------------------------------------------------------------------------------- 1 | export function debounce (fn, delay) { 2 | let timeoutID = null 3 | return function () { 4 | clearTimeout(timeoutID) 5 | const args = arguments 6 | const that = this 7 | timeoutID = setTimeout(function () { 8 | fn.apply(that, args) 9 | }, delay) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/plugins/i18n.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import store from '@/store' 4 | 5 | import deDE from '@/plugins/i18n/de_DE.json' 6 | import enGB from '@/plugins/i18n/en_GB.json' 7 | 8 | const axios = require('axios').default 9 | 10 | const protectedProperties = ['pageAboutGerminateTitle', 'pageAboutGerminateSubtitle', 'pageAboutGerminateText', 'pageAboutGerminateCardHomepageText', 'pageAboutGerminateCardGithubText', 'pageAboutGerminateCardPublicationText', 'pageAboutGerminateCardDocumentationText', 'pageAboutGerminateTeamTitle', 'pageAboutGerminateTeamSubtitle', 'pageAboutGerminateTeamOthersTitle', 'pageAboutGerminateTeamOthersSubtitle', 'pageAboutGerminateTeamOthersText', 'pageAboutGerminateLocationTitle', 'pageAboutGerminateLocationSubtitle', 'pageAboutGerminateFundersTitle', 'pageAboutGerminateFundersSubtitle', 'pageAboutGerminateFundersText', 'pageAboutGerminateTeamSebastian', 'pageAboutGerminateTeamJobSebastian', 'pageAboutGerminateTeamIain', 'pageAboutGerminateTeamJobIain', 'pageAboutGerminateTeamPaul', 'pageAboutGerminateTeamJobPaul'] 11 | 12 | Vue.use(VueI18n) 13 | 14 | const messages = { 15 | en_GB: enGB 16 | } 17 | 18 | export const i18n = new VueI18n({ 19 | locale: null, 20 | fallbackLocale: 'en_GB', 21 | warnHtmlInMessage: 'off', 22 | messages: messages 23 | }) 24 | 25 | const loadedLanguages = [] 26 | 27 | function setI18nLanguage (lang) { 28 | i18n.locale = lang 29 | axios.defaults.headers.common['Accept-Language'] = lang 30 | let htmlTag = lang 31 | const underscoreIndex = lang.indexOf('_') 32 | if (underscoreIndex !== -1) { 33 | htmlTag = lang.substring(0, underscoreIndex) 34 | } 35 | document.querySelector('html').setAttribute('lang', htmlTag) 36 | return lang 37 | } 38 | 39 | export function loadLanguageAsync (lang) { 40 | // If the same language 41 | if (i18n.locale === lang) { 42 | return Promise.resolve(setI18nLanguage(lang)) 43 | } 44 | 45 | // If the language was already loaded 46 | if (loadedLanguages.includes(lang)) { 47 | return Promise.resolve(setI18nLanguage(lang)) 48 | } 49 | 50 | // If the language hasn't been loaded yet 51 | return axios({ 52 | baseURL: store.getters.storeBaseUrl, 53 | method: 'get', 54 | url: /* webpackChunkName: "lang-[request]" */ `clientlocale/${lang}` 55 | }).then(m => { 56 | // If we get a response from the server, use it 57 | if (m.data) { 58 | // Delete the content of the about Germinate page, we don't want people to change it. 59 | protectedProperties.forEach(p => delete m.data[p]) 60 | } 61 | 62 | if (lang === 'de_DE') { 63 | // We do have some default text for de_DE, so load this here 64 | messages[lang] = deDE 65 | } 66 | if (!messages[lang]) { 67 | messages[lang] = {} 68 | } 69 | 70 | Object.assign(messages[lang], m.data) 71 | i18n.setLocaleMessage(lang, messages[lang]) 72 | loadedLanguages.push(lang) 73 | return setI18nLanguage(lang) 74 | }).catch(() => { 75 | // If we can't get it from the server, use the fallback we've got locally 76 | i18n.setLocaleMessage(lang, messages[lang]) 77 | loadedLanguages.push(lang) 78 | return setI18nLanguage(lang) 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /src/views/AboutView.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/Cookies.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 34 | 35 | 37 | -------------------------------------------------------------------------------- /src/views/GroupUpload.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /src/views/Images.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 81 | 82 | 85 | -------------------------------------------------------------------------------- /src/views/Projects.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 68 | 69 | 72 | -------------------------------------------------------------------------------- /src/views/Stories.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 21 | 24 | -------------------------------------------------------------------------------- /src/views/admin/UserFeedback.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /src/views/admin/UserPermissions.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 54 | 55 | 60 | -------------------------------------------------------------------------------- /src/views/data/DataResources.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 63 | 64 | 67 | -------------------------------------------------------------------------------- /src/views/data/Datasets.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 73 | 74 | 76 | -------------------------------------------------------------------------------- /src/views/data/ExperimentDetails.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 74 | 75 | 78 | -------------------------------------------------------------------------------- /src/views/data/Experiments.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 47 | 48 | 50 | -------------------------------------------------------------------------------- /src/views/data/climate/Climates.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /src/views/data/export/GenotypeExport.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 105 | 106 | 108 | -------------------------------------------------------------------------------- /src/views/data/genotype/Markers.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /src/views/data/germplasm/Germplasm.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 97 | 98 | 100 | -------------------------------------------------------------------------------- /src/views/error/Page403.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | 31 | 41 | -------------------------------------------------------------------------------- /src/views/error/Page404.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | 31 | 41 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 2 | const NodePolyfillPlugin = require('node-polyfill-webpack-plugin') 3 | const { defineConfig } = require('@vue/cli-service') 4 | 5 | module.exports = defineConfig({ 6 | devServer: { 7 | port: 4000 8 | }, 9 | transpileDependencies: false, 10 | publicPath: process.env.NODE_ENV === 'production' ? './' : '/', 11 | configureWebpack: { 12 | plugins: [ 13 | new NodePolyfillPlugin() 14 | ], 15 | // plugins: [ 16 | // new BundleAnalyzerPlugin() 17 | // ], 18 | resolve: { 19 | // ... rest of the resolve config 20 | fallback: { 21 | path: require.resolve('path-browserify'), 22 | querystring: require.resolve('querystring-es3') 23 | } 24 | }, 25 | devtool: 'source-map', 26 | target: 'web' 27 | } 28 | }) 29 | --------------------------------------------------------------------------------