├── .npmrc
├── .browserslistrc
├── lib
└── xlsx-0.20.3.tgz
├── public
├── img
│ ├── banner.png
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── mstile-70x70.png
│ ├── tools
│ │ ├── helium.png
│ │ ├── strudel.png
│ │ ├── tassel.png
│ │ ├── flapjack.png
│ │ └── curlywhirly.png
│ ├── funders
│ │ ├── divseek.png
│ │ └── norway.svg
│ ├── mstile-144x144.png
│ ├── mstile-150x150.png
│ ├── mstile-310x150.png
│ ├── mstile-310x310.png
│ ├── team
│ │ ├── iain-milne.jpg
│ │ ├── paul-shaw.jpg
│ │ └── sebastian-raubach.jpg
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── browserconfig.xml
│ ├── site.webmanifest
│ └── safari-pinned-tab.svg
└── index.html
├── src
├── assets
│ └── img
│ │ ├── helium.png
│ │ ├── image-icon-flash.svg
│ │ ├── image-icon-gps.svg
│ │ ├── image-icon-focal-length.svg
│ │ ├── image-icon-flash-no.svg
│ │ ├── image-icon-aperture.svg
│ │ ├── image-icon-exposure.svg
│ │ ├── image-icon-iso.svg
│ │ └── image-icon-camera.svg
├── views
│ ├── AboutView.vue
│ ├── Stories.vue
│ ├── data
│ │ ├── climate
│ │ │ └── Climates.vue
│ │ ├── genotype
│ │ │ └── Markers.vue
│ │ ├── Experiments.vue
│ │ ├── ExperimentDetails.vue
│ │ ├── DataResources.vue
│ │ ├── Datasets.vue
│ │ ├── germplasm
│ │ │ └── Germplasm.vue
│ │ └── export
│ │ │ └── GenotypeExport.vue
│ ├── admin
│ │ ├── UserFeedback.vue
│ │ └── UserPermissions.vue
│ ├── GroupUpload.vue
│ ├── error
│ │ ├── Page403.vue
│ │ └── Page404.vue
│ ├── Cookies.vue
│ ├── Projects.vue
│ └── Images.vue
├── plugins
│ ├── debounce.js
│ ├── charts
│ │ ├── plotly-sunburst-chart.js
│ │ └── plotly-treemap-chart.js
│ └── i18n.js
├── auth.js
├── components
│ ├── icons
│ │ ├── SidebarIcon.vue
│ │ └── MdiIcon.vue
│ ├── structure
│ │ └── SidebarLogoComponent.vue
│ ├── tables
│ │ ├── details
│ │ │ ├── CollaboratorDetails.vue
│ │ │ └── InstitutionDetails.vue
│ │ ├── MarkerTable.vue
│ │ ├── MarkedItems.vue
│ │ ├── UserTable.vue
│ │ └── MapTable.vue
│ ├── modals
│ │ ├── GroupUploadModal.vue
│ │ ├── PublicationsModal.vue
│ │ ├── InstitutionModal.vue
│ │ ├── GenotypeExportModal.vue
│ │ ├── ImageUploadModal.vue
│ │ ├── GetTokenModal.vue
│ │ ├── YesNoCancelModal.vue
│ │ ├── ReferenceModal.vue
│ │ ├── EditTagModal.vue
│ │ ├── LocationSelectionModal.vue
│ │ ├── FileResourceTypeModal.vue
│ │ ├── GroupEditAddModal.vue
│ │ ├── ClimateOverlayModal.vue
│ │ ├── LocationCountrySelectionModal.vue
│ │ ├── TraitEditModal.vue
│ │ ├── AddAboutPartnerModal.vue
│ │ ├── AddStoryStepModal.vue
│ │ ├── ExperimentCreationModal.vue
│ │ └── CustomChartColorModal.vue
│ ├── export
│ │ ├── DatasetOverview.vue
│ │ ├── ExportDownloadSelection.vue
│ │ ├── IndividualDatasetWidget.vue
│ │ └── BoxplotSelection.vue
│ ├── util
│ │ ├── Links.vue
│ │ ├── DatasetsWithUnacceptedLicense.vue
│ │ ├── TraitCategories.vue
│ │ ├── DatasetMetadataDownload.vue
│ │ ├── DatasetMetadataDownload copy.vue
│ │ ├── DropFilePreview.vue
│ │ ├── MarkerLookup.vue
│ │ ├── SignInForm.vue
│ │ ├── BannerCard.vue
│ │ ├── PasswordInput.vue
│ │ ├── TrialGermplasmLookup.vue
│ │ └── Collapse.vue
│ ├── admin
│ │ ├── DatasetPermissions.vue
│ │ ├── UserGroupMembers.vue
│ │ └── DatasetUserPermissions.vue
│ ├── images
│ │ ├── ImageTags.vue
│ │ └── ImageCarousel.vue
│ ├── germplasm
│ │ └── GermplasmTableComponent.vue
│ ├── dropdowns
│ │ └── LocaleDropdown.vue
│ └── map
│ │ └── TrialsLocationMap.vue
├── mixins
│ ├── api
│ │ ├── stats.js
│ │ ├── project.js
│ │ ├── climate.js
│ │ ├── group.js
│ │ ├── auth.js
│ │ ├── location.js
│ │ └── usergroup.js
│ ├── image.js
│ ├── search.js
│ └── pages.js
├── const
│ └── table-props.js
└── main.js
├── .editorconfig
├── babel.config.js
├── jsconfig.json
├── .gitignore
├── .eslintrc.js
├── vue.config.js
├── .github
└── workflows
│ ├── docker-ci-push.yml
│ └── docker-ci-tag.yml
├── docker
└── Dockerfile
└── package.json
/.npmrc:
--------------------------------------------------------------------------------
1 | legacy-peer-deps=true
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/lib/xlsx-0.20.3.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/lib/xlsx-0.20.3.tgz
--------------------------------------------------------------------------------
/public/img/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/banner.png
--------------------------------------------------------------------------------
/public/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/favicon.ico
--------------------------------------------------------------------------------
/src/assets/img/helium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/src/assets/img/helium.png
--------------------------------------------------------------------------------
/public/img/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/favicon-16x16.png
--------------------------------------------------------------------------------
/public/img/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/favicon-32x32.png
--------------------------------------------------------------------------------
/public/img/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/mstile-70x70.png
--------------------------------------------------------------------------------
/public/img/tools/helium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/tools/helium.png
--------------------------------------------------------------------------------
/public/img/tools/strudel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/tools/strudel.png
--------------------------------------------------------------------------------
/public/img/tools/tassel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/tools/tassel.png
--------------------------------------------------------------------------------
/public/img/funders/divseek.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/funders/divseek.png
--------------------------------------------------------------------------------
/public/img/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/mstile-144x144.png
--------------------------------------------------------------------------------
/public/img/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/mstile-150x150.png
--------------------------------------------------------------------------------
/public/img/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/mstile-310x150.png
--------------------------------------------------------------------------------
/public/img/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/mstile-310x310.png
--------------------------------------------------------------------------------
/public/img/team/iain-milne.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/team/iain-milne.jpg
--------------------------------------------------------------------------------
/public/img/team/paul-shaw.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/team/paul-shaw.jpg
--------------------------------------------------------------------------------
/public/img/tools/flapjack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/tools/flapjack.png
--------------------------------------------------------------------------------
/public/img/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/img/tools/curlywhirly.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/tools/curlywhirly.png
--------------------------------------------------------------------------------
/src/views/AboutView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is an about page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/public/img/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/img/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/img/team/sebastian-raubach.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/germinateplatform/germinate-vue/HEAD/public/img/team/sebastian-raubach.jpg
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/img/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #2d89ef
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/assets/img/image-icon-flash.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/views/Stories.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageStoriesTitle') }}
4 |
5 |
{{ $t('pageStoriesText') }}
6 |
7 |
8 |
9 |
10 |
11 |
20 |
21 |
24 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/components/icons/SidebarIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
26 |
27 |
30 |
--------------------------------------------------------------------------------
/src/components/structure/SidebarLogoComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
24 |
--------------------------------------------------------------------------------
/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/views/data/climate/Climates.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageClimatesTitle') }}
4 |
5 |
6 |
7 |
8 |
23 |
24 |
27 |
--------------------------------------------------------------------------------
/src/views/admin/UserFeedback.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageUserFeedbackTitle') }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
26 |
27 |
30 |
--------------------------------------------------------------------------------
/src/components/tables/details/CollaboratorDetails.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
26 |
27 |
30 |
--------------------------------------------------------------------------------
/src/assets/img/image-icon-gps.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/components/modals/GroupUploadModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
31 |
32 |
35 |
--------------------------------------------------------------------------------
/src/views/data/genotype/Markers.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageMarkersTitle') }}
4 |
5 |
{{ $t('pageMarkersText') }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
33 |
34 |
37 |
--------------------------------------------------------------------------------
/src/components/export/DatasetOverview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $t('widgetSelectedDatasetsTitle') }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
30 |
31 |
33 |
--------------------------------------------------------------------------------
/src/components/modals/PublicationsModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
38 |
39 |
41 |
--------------------------------------------------------------------------------
/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/views/GroupUpload.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageGroupUploadTitle') }}
4 |
{{ $t('pageGroupUploadText') }}
5 |
6 |
7 |
8 |
9 |
10 |
36 |
37 |
40 |
--------------------------------------------------------------------------------
/src/views/error/Page403.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 403
12 |
13 |
14 | {{ $t('page403Title') }}
15 | {{ $t('page403Text') }}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
41 |
--------------------------------------------------------------------------------
/src/views/error/Page404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 404
12 |
13 |
14 | {{ $t('page404Title') }}
15 | {{ $t('page404Text') }}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
41 |
--------------------------------------------------------------------------------
/src/components/tables/details/InstitutionDetails.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
44 |
45 |
48 |
--------------------------------------------------------------------------------
/src/components/icons/MdiIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
50 |
51 |
56 |
--------------------------------------------------------------------------------
/src/views/data/Experiments.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageExperimentsTitle') }}
4 |
5 |
{{ $t('pageExperimentsText') }}
6 |
7 |
8 |
9 |
10 |
47 |
48 |
50 |
--------------------------------------------------------------------------------
/src/components/modals/InstitutionModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
45 |
46 |
49 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/components/modals/GenotypeExportModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
19 |
49 |
--------------------------------------------------------------------------------
/src/components/modals/ImageUploadModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 | {{ $t('modalTextImageUpload') }}
13 |
14 |
15 |
16 |
17 |
55 |
56 |
59 |
--------------------------------------------------------------------------------
/src/components/modals/GetTokenModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
47 |
48 |
51 |
--------------------------------------------------------------------------------
/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/components/util/Links.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
64 |
65 |
68 |
--------------------------------------------------------------------------------
/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/components/modals/YesNoCancelModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ message }}
4 |
5 |
6 | {{ yesTitle }}
7 |
8 |
9 | {{ noTitle }}
10 |
11 |
12 | {{ cancelTitle }}
13 |
14 |
15 |
16 |
17 |
18 |
64 |
65 |
67 |
--------------------------------------------------------------------------------
/src/assets/img/image-icon-focal-length.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/modals/ReferenceModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
58 |
59 |
61 |
--------------------------------------------------------------------------------
/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/components/util/DatasetsWithUnacceptedLicense.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('widgetAdditionalDatasetsTitle') }}
4 |
{{ $t('widgetAdditionalDatasetsText', { type: datasetType ? datasetTypes[datasetType].text() : '' }) }}
5 |
6 |
7 |
8 |
9 |
10 |
58 |
59 |
62 |
--------------------------------------------------------------------------------
/src/views/Cookies.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageCookiesTitle') }}
4 |
5 |
{{ $t('pageCookiesTitleGerminate') }}
6 |
{{ $t('pageCookiesTextGerminate') }}
7 |
8 | - {{ $t('pageCookiesDescriptionTitleSession') }}
- {{ $t('pageCookiesDescriptionDataSession') }}
9 | - {{ $t('pageCookiesDescriptionTitleMarkedItems') }}
- {{ $t('pageCookiesDescriptionDataMarkedItems') }}
10 | - {{ $t('pageCookiesDescriptionTitleLocale') }}
- {{ $t('pageCookiesDescriptionDataLocale') }}
11 | - {{ $t('pageCookiesDescriptionTitleTableColumns') }}
- {{ $t('pageCookiesDescriptionDataTableColumns') }}
12 | - {{ $t('pageCookiesDescriptionTitleAsync') }}
- {{ $t('pageCookiesDescriptionDataAsync') }}
13 | - {{ $t('pageCookiesDescriptionTitleOther') }}
- {{ $t('pageCookiesDescriptionDataOther') }}
14 |
15 |
{{ $t('pageCookiesTextExplanationGerminate') }}
16 |
17 | {{ $t('pageCookiesTitleThirdParty') }}
18 | {{ $t('pageCookiesTextThirdParty') }}
19 |
20 |
21 |
22 |
23 |
34 |
35 |
37 |
--------------------------------------------------------------------------------
/src/components/util/TraitCategories.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('widgetTraitCategoriesTitle') }}
4 |
5 |
6 | {{ $t('widgetTraitCategoriesDeselect') }}
7 |
8 |
9 |
10 | {{ category.name }}
11 |
12 |
13 |
14 |
15 |
52 |
53 |
56 |
--------------------------------------------------------------------------------
/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/views/admin/UserPermissions.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageUserPermissionsTitle') }}
4 |
5 |
{{ $t('pageUserPermissionsText') }}
6 |
7 |
8 |
11 |
12 |
13 | {{ $t('pageUserPermissionsTabUserGroups') }}
14 |
15 | {{ $t('pageUserPermissionsUserGroupsText') }}
16 |
17 |
18 |
19 |
20 |
21 | {{ $t('pageUserPermissionsTabDatasetPermissions') }}
22 |
23 | {{ $t('pageUserPermissionsDatasetsText') }}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
54 |
55 |
60 |
--------------------------------------------------------------------------------
/src/components/modals/EditTagModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 | {{ $t('modalTextEditTags') }}
10 |
11 |
12 |
13 |
14 |
15 |
70 |
71 |
73 |
--------------------------------------------------------------------------------
/src/components/util/DatasetMetadataDownload.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('widgetDatasetDownloadMetadataTitle') }}
4 |
{{ $t('widgetDatasetDownloadMetadataText') }}
5 |
{{ $t('buttonDownload') }}
6 |
7 |
8 |
9 |
69 |
70 |
73 |
--------------------------------------------------------------------------------
/src/components/util/DatasetMetadataDownload copy.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('widgetDatasetDownloadMetadataTitle') }}
4 |
{{ $t('widgetDatasetDownloadMetadataText') }}
5 |
{{ $t('buttonDownload') }}
6 |
7 |
8 |
9 |
69 |
70 |
73 |
--------------------------------------------------------------------------------
/src/assets/img/image-icon-flash-no.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/views/Projects.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageProjectsTitle') }}
4 |
5 |
6 |
{{ $t('pageProjectsText') }}
7 |
8 |
9 |
15 |
16 |
17 |
18 |
68 |
69 |
72 |
--------------------------------------------------------------------------------
/src/components/admin/DatasetPermissions.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ dataset.datasetName }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
59 |
60 |
63 |
--------------------------------------------------------------------------------
/src/components/util/DropFilePreview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t(errorMessage) }}
6 |
7 |
8 |
9 |
61 |
62 |
65 |
--------------------------------------------------------------------------------
/src/components/modals/LocationSelectionModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 | {{ $t('modalTextSelectLocation') }}
11 |
16 |
17 |
18 |
19 |
64 |
65 |
68 |
--------------------------------------------------------------------------------
/src/mixins/pages.js:
--------------------------------------------------------------------------------
1 | const Pages = Object.freeze({
2 | home: 'home',
3 | login: 'login',
4 | setup: 'setup',
5 | backup: 'backup',
6 | cookies: 'cookies',
7 | userPermissions: 'user-permissions',
8 | germinateSettings: 'germinate-settings',
9 | germplasm: 'germplasm',
10 | germplasmUnifier: 'germplasm-unifier',
11 | germplasmMatch: 'germplasm-match',
12 | passport: 'passport',
13 | climates: 'climates',
14 | climateDetails: 'climate-details',
15 | traits: 'traits',
16 | traitDetails: 'trait-details',
17 | trialCreation: 'trial-creation',
18 | markers: 'markers',
19 | markerDetails: 'marker-details',
20 | maps: 'maps',
21 | mapDetails: 'map-details',
22 | export: 'export',
23 | exportCrossComparison: 'export-cross-comparison',
24 | exportGenotypes: 'export-genotypes',
25 | exportClimates: 'export-climates',
26 | exportTraits: 'export-trials',
27 | exportAlleleFrequency: 'export-allelefrequency',
28 | locations: 'locations',
29 | geographicSearch: 'geographic-search',
30 | datasets: 'datasets',
31 | datasetDetails: 'datasets-details',
32 | experiments: 'experiments',
33 | experimentDetails: 'experiment-details',
34 | dataResources: 'data-resources',
35 | statistics: 'statistics',
36 | images: 'images',
37 | markedItems: 'marked-items',
38 | markedItemsType: 'marked-items-type',
39 | importUpload: 'import-upload',
40 | importUploadType: 'import-upload-type',
41 | search: 'search',
42 | searchQuery: 'search-query',
43 | publications: 'publications',
44 | publicationDetails: 'publication-details',
45 | stories: 'stories',
46 | groups: 'groups',
47 | groupDetails: 'group-details',
48 | groupUpload: 'group-upload',
49 | aboutGerminate: 'about-germinate',
50 | aboutProject: 'about-project',
51 | aboutExportFormats: 'about-export-formats',
52 | aboutExportFormatsType: 'about-export-formats-type',
53 | userFeedback: 'user-feedback',
54 | projects: 'projects',
55 | projectDetails: 'project-details',
56 | fourZeroThree: '403',
57 | fourZeroFour: '404',
58 | fallback: 'fallback',
59 | genesysRequest: 'genesys-request'
60 | })
61 |
62 | export {
63 | Pages
64 | }
65 |
--------------------------------------------------------------------------------
/src/views/data/ExperimentDetails.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageExperimentDetailsTitle') }} {{ experiment.name }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
74 |
75 |
78 |
--------------------------------------------------------------------------------
/src/views/data/DataResources.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageDataResourcesTitle') }}
4 |
5 |
{{ $t('pageDataResourcesText') }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
63 |
64 |
67 |
--------------------------------------------------------------------------------
/src/components/images/ImageTags.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('widgetImageTagsTitle') }}
4 |
5 |
6 | {{ $t('widgetImageTagsDeselect') }}
7 |
8 |
9 |
10 | {{ tag.tagName }}
11 |
12 |
13 |
14 |
15 |
72 |
73 |
76 |
--------------------------------------------------------------------------------
/src/components/modals/FileResourceTypeModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
69 |
70 |
73 |
--------------------------------------------------------------------------------
/src/components/modals/GroupEditAddModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
16 |
17 |
18 |
22 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
66 |
67 |
70 |
--------------------------------------------------------------------------------
/src/components/util/MarkerLookup.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
80 |
81 |
84 |
--------------------------------------------------------------------------------
/src/components/germplasm/GermplasmTableComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
66 |
67 |
70 |
--------------------------------------------------------------------------------
/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/assets/img/image-icon-aperture.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/admin/UserGroupMembers.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageUserPermissionsGroupMembersTitle') }} - {{ group.userGroupName }}
4 |
5 |
6 | {{ $t('pageUserPermissionsGroupMembersCurrentTitle') }}
7 | {{ $t('pageUserPermissionsGroupMembersCurrentText') }}
8 |
9 |
10 |
11 |
12 | {{ $t('pageUserPermissionsGroupMembersNewTitle') }} - {{ $t('pageUserPermissionsTableSearchHint') }}
13 | {{ $t('pageUserPermissionsGroupMembersNewText') }}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
70 |
71 |
74 |
--------------------------------------------------------------------------------
/src/components/modals/ClimateOverlayModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 | {{ $t('pageLocationsMapsClimateOverlaysText') }}
9 |
10 |
11 | {{ climate.climateName }}
12 |
13 | {{ climate.climateDescription }}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
65 |
66 |
68 |
--------------------------------------------------------------------------------
/.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 }}
57 |
58 | - name: Docker Hub Description
59 | uses: peter-evans/dockerhub-description@v4
60 | with:
61 | username: ${{ secrets.DOCKERHUB_USERNAME }}
62 | password: ${{ secrets.DOCKERHUB_TOKEN }}
63 | repository: cropgeeks/gridscore-next
64 | short-description: ${{ github.event.repository.description }}
65 | enable-url-completion: true
--------------------------------------------------------------------------------
/src/components/modals/LocationCountrySelectionModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $t('modalTextLocationInformation') }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
73 |
74 |
77 |
--------------------------------------------------------------------------------
/src/assets/img/image-icon-exposure.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/dropdowns/LocaleDropdown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ $t('dropdownLocales') }}
8 | {{ language.name }}
9 |
10 |
11 |
12 |
80 |
81 |
83 |
--------------------------------------------------------------------------------
/src/components/admin/DatasetUserPermissions.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageUserPermissionsUserPermissionsTitle') }}
4 |
{{ $t('pageUserPermissionsUserPermissionsText') }}
5 |
6 |
7 | {{ $t('pageUserPermissionsUserPermissionsCurrentTitle') }}
8 | {{ $t('pageUserPermissionsUserPermissionsCurrentText') }}
9 |
10 |
11 |
12 |
13 | {{ $t('pageUserPermissionsUserPermissionsNewTitle') }} - {{ $t('pageUserPermissionsTableSearchHint') }}
14 | {{ $t('pageUserPermissionsUserPermissionsNewText') }}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
76 |
77 |
80 |
--------------------------------------------------------------------------------
/src/components/export/ExportDownloadSelection.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
14 |
94 |
95 |
97 |
--------------------------------------------------------------------------------
/src/views/Images.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageImagesTitle') }}
4 |
5 |
{{ $t('pageImagesText') }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
81 |
82 |
85 |
--------------------------------------------------------------------------------
/src/components/export/IndividualDatasetWidget.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ truncateAfterWords(dataset.datasetId + ' - ' + dataset.datasetName, 10) }}
6 |
7 |
8 |
9 |
10 | {{ truncateAfterWords(dataset.datasetDescription, lengthLimit) }}
11 |
12 |
13 | {{ dataset.datasetDescription }}
14 |
15 |
16 |
17 | {{ collapsed ? $t('buttonReadMore') : $t('buttonReadLess') }}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
73 |
74 |
76 |
--------------------------------------------------------------------------------
/.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 }}
62 |
63 | - name: Docker Hub Description
64 | uses: peter-evans/dockerhub-description@v4
65 | with:
66 | username: ${{ secrets.DOCKERHUB_USERNAME }}
67 | password: ${{ secrets.DOCKERHUB_TOKEN }}
68 | repository: cropgeeks/gridscore-next
69 | short-description: ${{ github.event.repository.description }}
70 | enable-url-completion: true
--------------------------------------------------------------------------------
/src/components/map/TrialsLocationMap.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
95 |
96 |
99 |
--------------------------------------------------------------------------------
/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/assets/img/image-icon-iso.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
42 |
--------------------------------------------------------------------------------
/src/components/export/BoxplotSelection.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t(texts.boxplotTitle) }}
4 |
{{ $t(texts.boxplotText) }}
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
95 |
96 |
99 |
--------------------------------------------------------------------------------
/src/views/data/Datasets.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageDatasetsTitle') }}
4 |
5 |
{{ $t('pageDatasetsInternalTitle') }}
6 |
{{ $t('pageDatasetsInternalText') }}
7 |
8 |
9 |
{{ $t('pageDatasetsExternalTitle') }}
10 |
{{ $t('pageDatasetsExternalText') }}
11 |
12 |
13 |
14 |
15 |
16 |
93 |
94 |
96 |
--------------------------------------------------------------------------------
/src/assets/img/image-icon-camera.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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/components/modals/TraitEditModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
78 |
79 |
87 |
--------------------------------------------------------------------------------
/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-9.0.0-bin.zip -P /tmp/ && \
28 | unzip /tmp/gradle-9.0.0-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-9.0.0/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 mysql-client && \
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 |
--------------------------------------------------------------------------------
/src/components/util/SignInForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
29 |
30 |
31 |
84 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/img/funders/norway.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
49 |
--------------------------------------------------------------------------------
/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/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/components/util/BannerCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ formattedDisplayInfo }}
7 | {{ $t(title) }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
90 |
91 |
97 |
--------------------------------------------------------------------------------
/src/components/util/PasswordInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{ message }}
13 |
14 |
15 |
16 |
17 |
18 |
95 |
96 |
106 |
--------------------------------------------------------------------------------
/src/components/modals/AddAboutPartnerModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
93 |
94 |
97 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "germinate",
3 | "version": "4.9.2",
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 | "xlsx": "./lib/xlsx-0.20.3.tgz",
57 | "zxcvbn": "~4.4.2"
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 |
--------------------------------------------------------------------------------
/src/components/util/TrialGermplasmLookup.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
101 |
102 |
105 |
--------------------------------------------------------------------------------
/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/components/tables/MarkerTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 | {{ data.item.markerId }}
13 |
14 |
15 |
16 | {{ data.item.markerName }}
17 |
18 |
19 |
20 | {{ data.item.markerSynonyms.join(', ') }}
21 |
22 |
23 |
24 |
25 |
26 |
105 |
106 |
108 |
--------------------------------------------------------------------------------
/src/components/tables/MarkedItems.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ getNumberWithSuffix(storeMarkedIds[itemType].length, 1) }}
11 | {{ getNumberWithSuffix(storeMarkedIds[itemType].length, 1) }}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
88 |
89 |
91 |
--------------------------------------------------------------------------------
/src/components/modals/AddStoryStepModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
15 |
16 |
17 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
97 |
98 |
101 |
--------------------------------------------------------------------------------
/src/components/images/ImageCarousel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{ $t('buttonEditCarousel') }}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
93 |
94 |
108 |
--------------------------------------------------------------------------------
/src/views/data/germplasm/Germplasm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageGermplasmTitle') }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{ $t('widgetGermplasmMapText') }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
97 |
98 |
100 |
--------------------------------------------------------------------------------
/src/views/data/export/GenotypeExport.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('pageGenotypesExportTitle') }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
105 |
106 |
108 |
--------------------------------------------------------------------------------
/src/components/modals/ExperimentCreationModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
100 |
101 |
103 |
--------------------------------------------------------------------------------
/src/components/tables/UserTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
116 |
117 |
123 |
--------------------------------------------------------------------------------
/src/components/tables/MapTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 | {{ data.item.mapId }}
11 |
12 |
13 |
14 | {{ data.item.mapName }}
15 |
16 |
17 |
18 |
19 |
20 |
21 | {{ data.item.mapDescription }}
22 |
23 |
24 |
25 |
26 |
27 |
99 |
100 |
102 |
--------------------------------------------------------------------------------
/src/components/modals/CustomChartColorModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
15 | {{ color }}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{ $t('buttonResetColorsToDefault') }}
28 |
29 |
30 |
31 |
32 |
97 |
98 |
101 |
--------------------------------------------------------------------------------
/src/components/util/Collapse.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
100 |
101 |
112 |
--------------------------------------------------------------------------------