├── .babelrc ├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ └── main.yml ├── .gitignore ├── Dockerfile ├── LICENSE.md ├── README.md ├── changelog.md ├── config.json ├── docker-compose.override.yml ├── docker-compose.sso.yml ├── docker-compose.yml ├── nginx └── default.conf.template ├── package-lock.json ├── package.json ├── public ├── CIEL background for OCL.png ├── bootstrap.min.css ├── env-config.js ├── favicon.ico ├── fhir.svg ├── fontello │ ├── LICENSE.txt │ ├── README.txt │ ├── config.json │ ├── css │ │ ├── animation.css │ │ ├── fontello-codes.css │ │ ├── fontello-embedded.css │ │ ├── fontello-ie7-codes.css │ │ ├── fontello-ie7.css │ │ └── fontello.css │ ├── demo.html │ └── font │ │ ├── fontello.eot │ │ ├── fontello.svg │ │ ├── fontello.ttf │ │ ├── fontello.woff │ │ └── fontello.woff2 ├── index.html └── openmrs_logo_white.gif ├── release_notes.py ├── release_version.sh ├── set_build_version.sh ├── src ├── assets │ └── .gitkeep ├── common │ ├── constants.js │ ├── defaultConfigs.js │ ├── serverConfigs.js │ └── utils.js ├── components │ ├── app │ │ ├── App.jsx │ │ ├── App.scss │ │ ├── AppVersions.jsx │ │ ├── DeprecatedBrowser.jsx │ │ ├── DocumentTitle.jsx │ │ ├── Favorites.jsx │ │ ├── Footer.jsx │ │ ├── Header.jsx │ │ ├── Languages.jsx │ │ ├── LayoutContext.jsx │ │ ├── MenuOptions.jsx │ │ ├── RecentHistory.jsx │ │ ├── RootView.jsx │ │ ├── SigninRedirect.jsx │ │ └── SignupRedirect.jsx │ ├── collections │ │ ├── Collection.jsx │ │ ├── CollectionForm.jsx │ │ ├── CollectionFormConfigs.jsx │ │ ├── CollectionHome.jsx │ │ ├── CollectionHomeChildrenList.jsx │ │ ├── CollectionHomeHeader.jsx │ │ ├── CollectionHomeTabs.jsx │ │ ├── CollectionVersionForm.jsx │ │ ├── ExpansionForm.jsx │ │ ├── ReferenceForm.jsx │ │ ├── ResourceReferenceForm.jsx │ │ ├── URLReferenceForm.jsx │ │ └── VersionList.jsx │ ├── common │ │ ├── APIPreview.jsx │ │ ├── About.jsx │ │ ├── AccessChip.jsx │ │ ├── AccessDenied.jsx │ │ ├── AddReferencesResult.jsx │ │ ├── AddToCollection.jsx │ │ ├── AppsMenu.jsx │ │ ├── AutocompleteLoading.jsx │ │ ├── BetaLabel.jsx │ │ ├── Captcha.jsx │ │ ├── CascadeLevelDropdown.jsx │ │ ├── CascadeParametersForm.jsx │ │ ├── ChipDatePicker.jsx │ │ ├── CloneToSource.jsx │ │ ├── CloneToSourceDialogContent.jsx │ │ ├── CloneToSourceResultPreview.jsx │ │ ├── CloseSquareIcon.jsx │ │ ├── CollapsibleAttributes.jsx │ │ ├── CollapsibleDivider.jsx │ │ ├── CollectionButton.jsx │ │ ├── CollectionListItem.jsx │ │ ├── CommonAccordion.jsx │ │ ├── CommonFormDrawer.jsx │ │ ├── Comparison.jsx │ │ ├── ComparisonAttributes.jsx │ │ ├── ConceptButton.jsx │ │ ├── ConceptClassAutoComplete.jsx │ │ ├── ConceptContainerDelete.jsx │ │ ├── ConceptContainerExport.jsx │ │ ├── ConceptContainerForm.jsx │ │ ├── ConceptContainerLabel.jsx │ │ ├── ConceptContainerSummaryHorizontal.jsx │ │ ├── ConceptContainerTip.jsx │ │ ├── ConceptContainerVersionForm.jsx │ │ ├── ConceptContainerVersionList.jsx │ │ ├── ConceptContainersAutocomplete.jsx │ │ ├── ConceptMapButton.jsx │ │ ├── ConceptSearchAutocomplete.jsx │ │ ├── ConfigSelect.jsx │ │ ├── CustomAttributes.jsx │ │ ├── CustomAttributesAccordion.jsx │ │ ├── CustomAttributesFormatted.jsx │ │ ├── CustomAttributesPopup.jsx │ │ ├── CustomMarkdown.jsx │ │ ├── CustomMarkdown.scss │ │ ├── CustomMarkup.jsx │ │ ├── CustomText.jsx │ │ ├── DialogTitleWithCloseButton.jsx │ │ ├── DoesnotExistsInOCLIcon.jsx │ │ ├── DownloadButton.jsx │ │ ├── DynamicConfigResourceIcon.jsx │ │ ├── ErrorBoundary.jsx │ │ ├── ErrorUI.jsx │ │ ├── ExistsInOCLIcon.jsx │ │ ├── ExpansionButton.jsx │ │ ├── ExpansionChip.jsx │ │ ├── ExpansionSelectorButton.jsx │ │ ├── ExternalIdLabel.jsx │ │ ├── ExtrasDiff.jsx │ │ ├── ExtrasForm.jsx │ │ ├── FileUploader.jsx │ │ ├── FileUploader.scss │ │ ├── FilterButton.jsx │ │ ├── FilterDrawer.jsx │ │ ├── FormTooltip.jsx │ │ ├── GroupHeader.jsx │ │ ├── GroupItems.jsx │ │ ├── HeaderAttribute.jsx │ │ ├── HeaderLogo.jsx │ │ ├── HtmlToolTipRaw.jsx │ │ ├── HtmlTooltip.jsx │ │ ├── ImageUploader.jsx │ │ ├── IncludeRetiredFilterChip.jsx │ │ ├── InfiniteScrollChip.jsx │ │ ├── JSONEditor.jsx │ │ ├── JSONIcon.jsx │ │ ├── LastUpdatedOnLabel.jsx │ │ ├── LayoutToggle.jsx │ │ ├── LinearProgressWithLabel.jsx │ │ ├── LinkLabel.jsx │ │ ├── LocaleAutoComplete.jsx │ │ ├── LocationLabel.jsx │ │ ├── ManageSourceChildButton.jsx │ │ ├── MappingButton.jsx │ │ ├── MappingReferenceAddOptionsDialog.jsx │ │ ├── MinusSquareIcon.jsx │ │ ├── MixedOwnersAutocomplete.jsx │ │ ├── NewResourceButton.jsx │ │ ├── NotFound.jsx │ │ ├── OpenMRSDeprecationDialog.jsx │ │ ├── OpenMRSLogo.jsx │ │ ├── OperationsDrawer.jsx │ │ ├── OrgButton.jsx │ │ ├── OwnerButton.jsx │ │ ├── OwnerChip.jsx │ │ ├── OwnerLabel.jsx │ │ ├── OwnerParentSelection.jsx │ │ ├── OwnerSelection.jsx │ │ ├── OwnerSelectorButton.jsx │ │ ├── PasswordFields.jsx │ │ ├── PasswordValidatorIndicator.jsx │ │ ├── PermissionDenied.jsx │ │ ├── Pin.jsx │ │ ├── PinIcon.jsx │ │ ├── Pins.jsx │ │ ├── PlusSquareIcon.jsx │ │ ├── PopperGrow.jsx │ │ ├── ProcessingChip.jsx │ │ ├── RTEditor.jsx │ │ ├── ReferenceCascadeDialog.jsx │ │ ├── ReferenceChip.jsx │ │ ├── ReferenceTranslation.jsx │ │ ├── ReleasedChip.jsx │ │ ├── RepoVersionLabel.jsx │ │ ├── ResourceLabel.jsx │ │ ├── ResourceLabelVertical.jsx │ │ ├── ResourceReferences.jsx │ │ ├── ResourceTextBreadcrumbs.jsx │ │ ├── ResourceVersionLabel.jsx │ │ ├── ResponsiveDrawer.jsx │ │ ├── ResultsCountDropDown.jsx │ │ ├── RetiredChip.jsx │ │ ├── ServerConfigList.jsx │ │ ├── ServerConfigsChip.jsx │ │ ├── SourceButton.jsx │ │ ├── SourceChildCollections.jsx │ │ ├── SourceChildHomeActionButton.jsx │ │ ├── SourceChildVersionAssociationWithContainer.jsx │ │ ├── SourceListItem.jsx │ │ ├── SourceMapTypeDropdown.jsx │ │ ├── SourceSearchAutocomplete.jsx │ │ ├── Split.scss │ │ ├── SupportedLocales.jsx │ │ ├── TabCountLabel.jsx │ │ ├── ThisConceptLabel.jsx │ │ ├── Tip.jsx │ │ ├── UserButton.jsx │ │ ├── VersionButton.jsx │ │ ├── VersionFilter.jsx │ │ ├── VersionList.jsx │ │ ├── VersionSelectorButton.jsx │ │ ├── ViewConfigForm.jsx │ │ └── conceptContainerFormComponents │ │ │ ├── AboutPage.jsx │ │ │ ├── AdvanceSettings.jsx │ │ │ ├── ConfigurationForm.jsx │ │ │ ├── CustomAttributes.jsx │ │ │ ├── FHIRSettings.jsx │ │ │ ├── FormHeader.jsx │ │ │ ├── LanguageForm.jsx │ │ │ ├── NameAndDescription.jsx │ │ │ ├── Others.jsx │ │ │ └── ResourceIDAssignmentSettings.jsx │ ├── concepts │ │ ├── Concept.jsx │ │ ├── ConceptCascadeVisualizeDialog.jsx │ │ ├── ConceptDetailsLocale.jsx │ │ ├── ConceptDisplayName.jsx │ │ ├── ConceptForm.jsx │ │ ├── ConceptHierarchyRow.jsx │ │ ├── ConceptHierarchyTree.jsx │ │ ├── ConceptHome.jsx │ │ ├── ConceptHomeDetails.jsx │ │ ├── ConceptIcon.jsx │ │ ├── ConceptTable.jsx │ │ ├── ConceptsComparison.jsx │ │ ├── HierarchySearch.jsx │ │ ├── HierarchyTraversalList.jsx │ │ ├── HierarchyTreeFilters.jsx │ │ ├── HomeLocales.jsx │ │ ├── HomeMappings.jsx │ │ ├── LocaleForm.jsx │ │ ├── LocalizedTextRow.jsx │ │ ├── ScopeHeader.jsx │ │ └── d3Tree.scss │ ├── fhir │ │ ├── CodeList.jsx │ │ ├── ConceptMapGroups.jsx │ │ ├── ConceptMapHome.jsx │ │ ├── ConceptTable.jsx │ │ ├── Contact.jsx │ │ ├── ContainerHome.jsx │ │ ├── ContainerHomeHeader.jsx │ │ ├── ContainerHomeTabs.jsx │ │ ├── ContainerResource.jsx │ │ ├── Fhir.jsx │ │ ├── FhirTabs.jsx │ │ └── OwnerHome.jsx │ ├── imports │ │ ├── ExistingImports.jsx │ │ ├── ImportHome.jsx │ │ ├── ImportInfo.jsx │ │ ├── NewImport.jsx │ │ ├── Task.jsx │ │ ├── TaskIcon.jsx │ │ ├── Tasks.jsx │ │ ├── Tasks.scss │ │ └── utils.js │ ├── mappings │ │ ├── AllMappingsTables.jsx │ │ ├── ConceptHomeMappingsTableRows.jsx │ │ ├── FromConceptLabel.jsx │ │ ├── FromConceptLabelVertical.jsx │ │ ├── Mapping.jsx │ │ ├── MappingForm.jsx │ │ ├── MappingHome.jsx │ │ ├── MappingHomeDetails.jsx │ │ ├── MappingIcon.jsx │ │ ├── MappingInlineForm.jsx │ │ ├── MappingOptions.jsx │ │ ├── MappingsComparison.jsx │ │ ├── NestedMappingsTable.jsx │ │ ├── ScopeHeader.jsx │ │ ├── ToConceptLabel.jsx │ │ └── ToConceptLabelVertical.jsx │ ├── orgs │ │ ├── HomeHeader.jsx │ │ ├── HomeTabContent.jsx │ │ ├── HomeTabs.jsx │ │ ├── Members.jsx │ │ ├── MembersForm.jsx │ │ ├── OrgForm.jsx │ │ ├── OrgHome.jsx │ │ ├── OrgHomeChildrenList.jsx │ │ ├── OrgHomeHeader.jsx │ │ ├── OrgHomeTabs.jsx │ │ ├── Organization.jsx │ │ ├── Overview.jsx │ │ └── OverviewSettings.jsx │ ├── search │ │ ├── BestMatchSort.jsx │ │ ├── ConceptHierarchyResultsTable.jsx │ │ ├── GenericFilterChip.jsx │ │ ├── MinimalRowComponent.jsx │ │ ├── NavigationButtonGroup.jsx │ │ ├── NumericalIDSort.jsx │ │ ├── PageResultsLabel.jsx │ │ ├── ResourceTabs.jsx │ │ ├── Resources.jsx │ │ ├── ResourcesHorizontal.jsx │ │ ├── ResultConstants.js │ │ ├── Results.jsx │ │ ├── ResultsTable.jsx │ │ ├── RowComponent.jsx │ │ ├── Search.jsx │ │ ├── SearchByAttributeInput.jsx │ │ ├── SearchFilters.jsx │ │ ├── SearchInput.jsx │ │ ├── SelectedResourceControls.jsx │ │ ├── SortButton.jsx │ │ └── utils.js │ ├── sources │ │ ├── Breadcrumbs.jsx │ │ ├── Source.jsx │ │ ├── SourceForm.jsx │ │ ├── SourceFormConfigs.jsx │ │ ├── SourceHome.jsx │ │ ├── SourceHomeChildrenList.jsx │ │ ├── SourceHomeHeader.jsx │ │ ├── SourceHomeTabs.jsx │ │ ├── SourceSummary.jsx │ │ └── SourceVersionForm.jsx │ └── users │ │ ├── EmailVerification.jsx │ │ ├── ForgotPasswordEmailSentMessage.jsx │ │ ├── ForgotPasswordForm.jsx │ │ ├── ForgotPasswordRequest.jsx │ │ ├── ForgotPasswordSuccessMessage.jsx │ │ ├── Login.jsx │ │ ├── OIDLoginCallback.jsx │ │ ├── Signup.jsx │ │ ├── User.jsx │ │ ├── UserForm.jsx │ │ ├── UserHome.jsx │ │ ├── UserHomeDetails.jsx │ │ ├── UserHomeOrgs.jsx │ │ ├── UserHomeTabs.jsx │ │ ├── UserManagement.jsx │ │ ├── UserOptions.jsx │ │ └── VerifyEmailMessage.jsx ├── hooks │ └── useToggle.js ├── i18n │ ├── config.js │ └── locales │ │ ├── en │ │ └── translations.json │ │ ├── es │ │ └── translations.json │ │ └── zh │ │ └── translations.json ├── index.jsx ├── index.scss └── services │ └── APIService.js ├── start-prod.sh ├── start.sh └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/env", { 4 | "targets": { 5 | "browsers": ["last 2 versions", "safari > 8", "not ie < 11"] 6 | } 7 | }], 8 | "@babel/react" 9 | ], 10 | "plugins": [ 11 | "@babel/plugin-proposal-class-properties", 12 | "@babel/plugin-proposal-object-rest-spread" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.scss 2 | *.css 3 | *.config.js 4 | *.json 5 | src/components/external/* 6 | src/stories/* -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | eslint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: stefanoeb/eslint-action@1.0.2 11 | with: 12 | files: src/ 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/stories/ 3 | .storybook/ 4 | dist/ 5 | .idea/ 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage-1 Build and Development Environment 2 | FROM node:14.11 as build 3 | ARG NODE_ENV=production 4 | ARG NODE_OPTIONS=--max_old_space_size=700 5 | ENV NPM_CONFIG_LOGLEVEL warn 6 | ENV WEB_PORT=${WEB_PORT:-4000} 7 | ENV API_URL=${API_URL:-http://127.0.0.1:8000} 8 | ENV RECAPTCHA_SITE_KEY=${RECAPTCHA_SITE_KEY:-6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI} 9 | ENV GA_ACCOUNT_ID=${GA_ACCOUNT_ID:-UA-000000-01} 10 | ENV ERRBIT_URL=${ERRBIT_URL} 11 | ENV ERRBIT_KEY=${ERRBIT_KEY} 12 | ENV HOTJAR_ID=${HOTJAR_ID} 13 | ENV LOGIN_REDIRECT_URL=${LOGIN_REDIRECT_URL} 14 | ENV OIDC_RP_CLIENT_ID=${OIDC_RP_CLIENT_ID} 15 | ENV OIDC_RP_CLIENT_SECRET=${OIDC_RP_CLIENT_SECRET} 16 | RUN mkdir /app 17 | WORKDIR /app 18 | ENV PATH /app/node_modules/.bin:$PATH 19 | 20 | ADD package.json /app/ 21 | ADD package-lock.json /app/ 22 | 23 | RUN npm ci --production=false 24 | 25 | ADD webpack.config.js /app/ 26 | ADD .babelrc /app/ 27 | ADD src /app/src/ 28 | ADD public /app/public/ 29 | 30 | ADD start.sh /app/ 31 | RUN chmod +x start.sh 32 | 33 | ADD set_build_version.sh /app/ 34 | RUN chmod +X set_build_version.sh 35 | 36 | ARG SOURCE_COMMIT 37 | RUN ["bash", "-c", "./set_build_version.sh"] 38 | 39 | RUN npm run build 40 | RUN cp public/bootstrap.min.css dist/ 41 | RUN cp public/favicon.ico dist/ 42 | RUN cp public/fhir.svg dist/ 43 | RUN cp public/openmrs_logo_white.gif dist/ 44 | RUN cp -rf public/fontello dist/ 45 | 46 | EXPOSE ${WEB_PORT} 47 | 48 | CMD ["bash", "-c", "./start.sh"] 49 | 50 | # Stage-2 Production Environment 51 | FROM nginx:1.19-alpine 52 | 53 | ENV WEB_PORT=${WEB_PORT:-4000} 54 | 55 | # Add bash 56 | RUN apk add --no-cache bash 57 | 58 | ADD start-prod.sh / 59 | RUN chmod +x start-prod.sh 60 | 61 | # Copy the tagged files from the build to the production environmnet of the nginx server 62 | COPY --from=build /app/dist/ /usr/share/nginx/html/ 63 | 64 | # Copy nginx configuration 65 | ADD nginx /etc/nginx/templates/ 66 | 67 | # Make port available to the world outside the container 68 | EXPOSE ${WEB_PORT} 69 | 70 | # Start the server 71 | CMD ["bash", "-c", "/start-prod.sh"] 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oclweb2 2 | OCL TermBrowser v2, an open-source ReactJS-based web client for terminology management 3 | 4 | ### Run Dev 5 | 1. docker-compose up -d 6 | 2. Visit http://localhost:4000 7 | 8 | ### Run Dev with KeyCloak (SSO) 9 | 1. docker-compose -f docker-compose.yml -f docker-compose.sso.yml up -d 10 | 2. Visit http://localhost:4000 11 | 12 | ### Run Production (do check CORS origin policy with API_URL) 13 | 1. docker-compose -f docker-compose.yml up -d 14 | 2. Visit http://localhost:4000 15 | 16 | 17 | ### Eslint 18 | docker exec -it bash -c "eslint src/ --ext=.js*" 19 | 20 | ### Release 21 | 22 | Every build is a candidate for release. 23 | 24 | In order to release please trigger the release build step in [our CI](https://ci.openmrs.org/browse/OCL-OW2/latest). Please note 25 | that the maintenance version will be automatically increased after a successful release. It is desired only, if you are releasing the latest build and 26 | should be turned off by setting the increaseMaintenanceRelease variable to false on the Run stage "Release" popup in other cases. 27 | 28 | A deployment release will be automatically created and pushed to the staging environment. 29 | 30 | 31 | #### Major/minor version increase 32 | 33 | In order to increase major/minor version you need to set the new version in [package.json](package.json). Alongside you need to login to our CI and update the next release version on a deployment plan [here](https://ci.openmrs.org/deploy/config/configureDeploymentProjectVersioning.action?id=205619202) with the same value. 34 | 35 | ### Deployment 36 | 37 | In order to deploy please trigger the deployment [here](https://ci.openmrs.org/deploy/viewDeploymentProjectEnvironments.action?id=205619202). 38 | Please use an existing deployment release. 39 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": "dev" 3 | } 4 | -------------------------------------------------------------------------------- /docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | web: 5 | image: openconceptlab/oclweb2:${TAG-dev} 6 | build: 7 | context: . 8 | target: build 9 | args: 10 | NODE_ENV: development 11 | ports: 12 | - "4000:4000" 13 | - "4001:35729" 14 | - "6006:6006" 15 | restart: on-failure 16 | volumes: 17 | - .:/app:z 18 | -------------------------------------------------------------------------------- /docker-compose.sso.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | web: 5 | image: openconceptlab/oclweb2:${TAG-dev} 6 | build: 7 | context: . 8 | target: build 9 | args: 10 | NODE_ENV: development 11 | ports: 12 | - "4000:4000" 13 | - "4001:35729" 14 | - "6006:6006" 15 | restart: on-failure 16 | volumes: 17 | - .:/app:z 18 | environment: 19 | - LOGIN_REDIRECT_URL=${LOGIN_REDIRECT_URL-http://localhost:4000} 20 | - OIDC_RP_CLIENT_ID=${OIDC_RP_CLIENT_ID-ocllocal} 21 | - OIDC_RP_CLIENT_SECRET 22 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | web: 5 | image: openconceptlab/oclweb2:${TAG-latest} 6 | build: 7 | context: . 8 | args: 9 | NODE_ENV: production 10 | ports: 11 | - "4000:4000" 12 | restart: on-failure 13 | environment: 14 | - API_URL=${API_URL-http://127.0.0.1:8000} 15 | - NODE_ENV=${NODE_ENV-development} 16 | - WEB_PORT=4000 17 | - RECAPTCHA_SITE_KEY=${RECAPTCHA_SITE_KEY-6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI} 18 | - GA_ACCOUNT_ID=${GA_ACCOUNT_ID-UA-000000-01} 19 | - ERRBIT_URL 20 | - ERRBIT_KEY 21 | - HOTJAR_ID 22 | -------------------------------------------------------------------------------- /nginx/default.conf.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen ${WEB_PORT}; 3 | server_name localhost; 4 | 5 | root /usr/share/nginx/html; 6 | index index.html; 7 | try_files $uri /index.html; 8 | 9 | gzip on; 10 | gzip_vary on; 11 | gzip_proxied any; 12 | gzip_comp_level 6; 13 | gzip_buffers 16 8k; 14 | gzip_http_version 1.1; 15 | gzip_min_length 256; 16 | gzip_types application/javascript text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon; 17 | } 18 | -------------------------------------------------------------------------------- /public/CIEL background for OCL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConceptLab/oclweb2/7c3d27bd59612b0073f10a6703fbfbfd6cefe924/public/CIEL background for OCL.png -------------------------------------------------------------------------------- /public/env-config.js: -------------------------------------------------------------------------------- 1 | /* Automatically populated by start-prod.sh */ 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConceptLab/oclweb2/7c3d27bd59612b0073f10a6703fbfbfd6cefe924/public/favicon.ico -------------------------------------------------------------------------------- /public/fontello/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Font license info 2 | 3 | 4 | ## Iconic 5 | 6 | Copyright (C) 2012 by P.J. Onori 7 | 8 | Author: P.J. Onori 9 | License: SIL (http://scripts.sil.org/OFL) 10 | Homepage: http://somerandomdude.com/work/iconic/ 11 | 12 | 13 | ## Fontelico 14 | 15 | Copyright (C) 2012 by Fontello project 16 | 17 | Author: Crowdsourced, for Fontello project 18 | License: SIL (http://scripts.sil.org/OFL) 19 | Homepage: http://fontello.com 20 | 21 | 22 | -------------------------------------------------------------------------------- /public/fontello/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "css_prefix_text": "icon-", 4 | "css_use_suffix": false, 5 | "hinting": true, 6 | "units_per_em": 1000, 7 | "ascent": 850, 8 | "glyphs": [ 9 | { 10 | "uid": "142263941b9f4cd4b2b84f1f4cd13b1d", 11 | "css": "emo-thumbsup", 12 | "code": 59396, 13 | "src": "fontelico" 14 | }, 15 | { 16 | "uid": "754557fa790629b8df53349af508af41", 17 | "css": "link", 18 | "code": 59392, 19 | "src": "iconic" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /public/fontello/css/animation.css: -------------------------------------------------------------------------------- 1 | /* 2 | Animation example, for spinners 3 | */ 4 | .animate-spin { 5 | -moz-animation: spin 2s infinite linear; 6 | -o-animation: spin 2s infinite linear; 7 | -webkit-animation: spin 2s infinite linear; 8 | animation: spin 2s infinite linear; 9 | display: inline-block; 10 | } 11 | @-moz-keyframes spin { 12 | 0% { 13 | -moz-transform: rotate(0deg); 14 | -o-transform: rotate(0deg); 15 | -webkit-transform: rotate(0deg); 16 | transform: rotate(0deg); 17 | } 18 | 19 | 100% { 20 | -moz-transform: rotate(359deg); 21 | -o-transform: rotate(359deg); 22 | -webkit-transform: rotate(359deg); 23 | transform: rotate(359deg); 24 | } 25 | } 26 | @-webkit-keyframes spin { 27 | 0% { 28 | -moz-transform: rotate(0deg); 29 | -o-transform: rotate(0deg); 30 | -webkit-transform: rotate(0deg); 31 | transform: rotate(0deg); 32 | } 33 | 34 | 100% { 35 | -moz-transform: rotate(359deg); 36 | -o-transform: rotate(359deg); 37 | -webkit-transform: rotate(359deg); 38 | transform: rotate(359deg); 39 | } 40 | } 41 | @-o-keyframes spin { 42 | 0% { 43 | -moz-transform: rotate(0deg); 44 | -o-transform: rotate(0deg); 45 | -webkit-transform: rotate(0deg); 46 | transform: rotate(0deg); 47 | } 48 | 49 | 100% { 50 | -moz-transform: rotate(359deg); 51 | -o-transform: rotate(359deg); 52 | -webkit-transform: rotate(359deg); 53 | transform: rotate(359deg); 54 | } 55 | } 56 | @-ms-keyframes spin { 57 | 0% { 58 | -moz-transform: rotate(0deg); 59 | -o-transform: rotate(0deg); 60 | -webkit-transform: rotate(0deg); 61 | transform: rotate(0deg); 62 | } 63 | 64 | 100% { 65 | -moz-transform: rotate(359deg); 66 | -o-transform: rotate(359deg); 67 | -webkit-transform: rotate(359deg); 68 | transform: rotate(359deg); 69 | } 70 | } 71 | @keyframes spin { 72 | 0% { 73 | -moz-transform: rotate(0deg); 74 | -o-transform: rotate(0deg); 75 | -webkit-transform: rotate(0deg); 76 | transform: rotate(0deg); 77 | } 78 | 79 | 100% { 80 | -moz-transform: rotate(359deg); 81 | -o-transform: rotate(359deg); 82 | -webkit-transform: rotate(359deg); 83 | transform: rotate(359deg); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /public/fontello/css/fontello-codes.css: -------------------------------------------------------------------------------- 1 | 2 | .icon-link:before { content: '\e800'; } /* '' */ 3 | .icon-emo-thumbsup:before { content: '\e804'; } /* '' */ 4 | -------------------------------------------------------------------------------- /public/fontello/css/fontello-ie7-codes.css: -------------------------------------------------------------------------------- 1 | 2 | .icon-link { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 3 | .icon-emo-thumbsup { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 4 | -------------------------------------------------------------------------------- /public/fontello/css/fontello-ie7.css: -------------------------------------------------------------------------------- 1 | [class^="icon-"], [class*=" icon-"] { 2 | font-family: 'fontello'; 3 | font-style: normal; 4 | font-weight: normal; 5 | 6 | /* fix buttons height */ 7 | line-height: 1em; 8 | 9 | /* you can be more comfortable with increased icons size */ 10 | /* font-size: 120%; */ 11 | } 12 | 13 | .icon-link { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 14 | .icon-emo-thumbsup { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 15 | -------------------------------------------------------------------------------- /public/fontello/css/fontello.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'fontello'; 3 | src: url('../font/fontello.eot?48511308'); 4 | src: url('../font/fontello.eot?48511308#iefix') format('embedded-opentype'), 5 | url('../font/fontello.woff2?48511308') format('woff2'), 6 | url('../font/fontello.woff?48511308') format('woff'), 7 | url('../font/fontello.ttf?48511308') format('truetype'), 8 | url('../font/fontello.svg?48511308#fontello') format('svg'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ 13 | /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ 14 | /* 15 | @media screen and (-webkit-min-device-pixel-ratio:0) { 16 | @font-face { 17 | font-family: 'fontello'; 18 | src: url('../font/fontello.svg?48511308#fontello') format('svg'); 19 | } 20 | } 21 | */ 22 | [class^="icon-"]:before, [class*=" icon-"]:before { 23 | font-family: "fontello"; 24 | font-style: normal; 25 | font-weight: normal; 26 | speak: never; 27 | 28 | display: inline-block; 29 | text-decoration: inherit; 30 | width: 1em; 31 | margin-right: .2em; 32 | text-align: center; 33 | /* opacity: .8; */ 34 | 35 | /* For safety - reset parent styles, that can break glyph codes*/ 36 | font-variant: normal; 37 | text-transform: none; 38 | 39 | /* fix buttons height, for twitter bootstrap */ 40 | line-height: 1em; 41 | 42 | /* Animation center compensation - margins should be symmetric */ 43 | /* remove if not needed */ 44 | margin-left: .2em; 45 | 46 | /* you can be more comfortable with increased icons size */ 47 | /* font-size: 120%; */ 48 | 49 | /* Font smoothing. That was taken from TWBS */ 50 | -webkit-font-smoothing: antialiased; 51 | -moz-osx-font-smoothing: grayscale; 52 | 53 | /* Uncomment for 3D effect */ 54 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ 55 | } 56 | 57 | .icon-link:before { content: '\e800'; } /* '' */ 58 | .icon-emo-thumbsup:before { content: '\e804'; } /* '' */ 59 | -------------------------------------------------------------------------------- /public/fontello/font/fontello.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConceptLab/oclweb2/7c3d27bd59612b0073f10a6703fbfbfd6cefe924/public/fontello/font/fontello.eot -------------------------------------------------------------------------------- /public/fontello/font/fontello.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright (C) 2021 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/fontello/font/fontello.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConceptLab/oclweb2/7c3d27bd59612b0073f10a6703fbfbfd6cefe924/public/fontello/font/fontello.ttf -------------------------------------------------------------------------------- /public/fontello/font/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConceptLab/oclweb2/7c3d27bd59612b0073f10a6703fbfbfd6cefe924/public/fontello/font/fontello.woff -------------------------------------------------------------------------------- /public/fontello/font/fontello.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConceptLab/oclweb2/7c3d27bd59612b0073f10a6703fbfbfd6cefe924/public/fontello/font/fontello.woff2 -------------------------------------------------------------------------------- /public/openmrs_logo_white.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConceptLab/oclweb2/7c3d27bd59612b0073f10a6703fbfbfd6cefe924/public/openmrs_logo_white.gif -------------------------------------------------------------------------------- /release_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | VERSION_FILE="package.json" 5 | 6 | SOURCE_COMMIT=$(git rev-parse HEAD) 7 | export SOURCE_COMMIT=${SOURCE_COMMIT:0:8} 8 | 9 | PROJECT_VERSION=$(cat $VERSION_FILE \ 10 | | grep version \ 11 | | head -1 \ 12 | | awk -F: '{ print $2 }' \ 13 | | sed 's/[",]//g' \ 14 | | tr -d '[[:space:]]') 15 | 16 | TAG="$PROJECT_VERSION-$SOURCE_COMMIT" 17 | 18 | ./set_build_version.sh 19 | 20 | git checkout -b release 21 | git add config.json 22 | git commit -m "Release version $PROJECT_VERSION" 23 | 24 | git tag "$TAG" 25 | 26 | git remote set-url origin ${GIT_REPO_URL} 27 | git push origin --tags 28 | 29 | docker pull $DOCKER_IMAGE_ID 30 | docker tag $DOCKER_IMAGE_ID $DOCKER_IMAGE_NAME:$TAG 31 | docker push $DOCKER_IMAGE_NAME:$TAG 32 | 33 | if [[ "$INCREASE_MAINTENANCE_VERSION" = true ]]; then 34 | git checkout master 35 | 36 | NEW_PROJECT_VERSION=$(echo "${PROJECT_VERSION}" | awk -F. -v OFS=. '{$NF++;print}') 37 | sed -i "s/\"version\": \"$PROJECT_VERSION\"/\"version\": \"$NEW_PROJECT_VERSION\"/" $VERSION_FILE 38 | 39 | git add $VERSION_FILE 40 | git commit -m "[skip ci] Increase maintenance version to $NEW_PROJECT_VERSION" 41 | 42 | git push origin master 43 | fi 44 | -------------------------------------------------------------------------------- /set_build_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #Sets project build version 3 | set -e 4 | 5 | CONFIG_FILE="config.json" 6 | touch ${CONFIG_FILE} 7 | 8 | SHA=${SOURCE_COMMIT:-'dev'} 9 | SHA=${SHA:0:8} 10 | 11 | echo "Setting build version to $SHA in ${CONFIG_FILE}" 12 | 13 | echo "{ \"build\": \"$SHA\" }" > ${CONFIG_FILE} 14 | 15 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConceptLab/oclweb2/7c3d27bd59612b0073f10a6703fbfbfd6cefe924/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/components/app/AppVersions.jsx: -------------------------------------------------------------------------------- 1 | /*eslint no-process-env: 0*/ 2 | 3 | import React from 'react'; 4 | import { Tooltip } from '@mui/material'; 5 | import APIService from '../../services/APIService'; 6 | import { isFHIRServer, toFullAPIURL } from '../../common/utils'; 7 | import packageJson from '../../../package.json'; 8 | import config from '../../../config.json'; 9 | 10 | const VERSION = `${packageJson.version}-${config.build}` 11 | const SWAGGER_URL = toFullAPIURL('/swagger/') 12 | 13 | const AppVersionChip = ({version, label, tooltip}) => { 14 | return ( 15 | 16 | 17 | 18 | {label} 19 | 20 | {version} 21 | 22 | 23 | ) 24 | } 25 | 26 | class AppVersions extends React.Component { 27 | constructor(props) { 28 | super(props) 29 | this.state = {version: null} 30 | } 31 | componentDidMount() { 32 | if(!isFHIRServer()) 33 | APIService 34 | .version() 35 | .get() 36 | .then(response => this.setState({version: response.status === 200 ? response.data : null})) 37 | } 38 | 39 | render() { 40 | return ( 41 | 42 | 43 | { 44 | this.state.version && 45 | 46 | 47 | 48 | } 49 | 50 | ) 51 | } 52 | } 53 | 54 | export default AppVersions; 55 | -------------------------------------------------------------------------------- /src/components/app/DeprecatedBrowser.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Snackbar, Alert } from '@mui/material' 3 | 4 | const MESSAGE = "OCL TermBrowser doesn't support IE/Opera. For best experience please use " 5 | 6 | const DeprecatedBrowser = ({ open, onClose }) => ( 7 | 8 | 9 | {MESSAGE} 10 | 11 | Chrome Browser 12 | 13 | 14 | 15 | ); 16 | 17 | export default DeprecatedBrowser 18 | -------------------------------------------------------------------------------- /src/components/app/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Divider } from '@mui/material'; 3 | import { Copyright as CopyrightIcon } from '@mui/icons-material'; 4 | import { get } from 'lodash'; 5 | import { 6 | getAppliedServerConfig 7 | } from '../../common/utils'; 8 | import AppVersions from './AppVersions'; 9 | const DEFAULT_FOOTER_TEXT = `${new Date().getFullYear()} Open Concept Lab`; 10 | const Footer = () => { 11 | const text = get(getAppliedServerConfig(), 'info.site.footerText', DEFAULT_FOOTER_TEXT); 12 | const isDefault = text === DEFAULT_FOOTER_TEXT; 13 | return ( 14 | 23 | ); 24 | 25 | } 26 | 27 | export default Footer; 28 | -------------------------------------------------------------------------------- /src/components/app/Languages.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTranslation } from "react-i18next"; 3 | import { ListItemButton, Button } from '@mui/material' 4 | import LanguageIcon from '@mui/icons-material/Language'; 5 | import PopperGrow from '../common/PopperGrow'; 6 | 7 | const Languages = () => { 8 | const { i18n } = useTranslation(); 9 | const [locale, setLocale] = React.useState(i18n.language || 'en') 10 | const [open, setOpen] = React.useState(false) 11 | const anchorRef = React.useRef(null); 12 | const handleClick = _lang => { 13 | setLocale(_lang) 14 | i18n.changeLanguage(_lang) 15 | setOpen(false) 16 | }; 17 | const toggleMenu = () => setOpen(!open) 18 | 19 | const handleClose = event => { 20 | if (anchorRef.current && anchorRef.current.contains(event.target)) 21 | return; 22 | 23 | setOpen(false); 24 | }; 25 | 26 | return ( 27 | 28 | 31 | 32 |
33 | { 34 | [{locale: 'en', name: 'English'}, {locale: 'es', name: "Español"}].map(lang => ( 35 | handleClick(lang.locale)}> 36 | {lang.name} - {lang.locale.toUpperCase()} 37 | 38 | )) 39 | } 40 |
41 |
42 |
43 | ); 44 | } 45 | 46 | export default Languages; 47 | -------------------------------------------------------------------------------- /src/components/app/LayoutContext.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | export const OperationsContext = React.createContext(''); 3 | export const OperationsProvider = OperationsContext.Provider; 4 | export const OperationsConsumer = OperationsContext.Consumer; 5 | 6 | const LayoutContext = ({ subPages }) => { 7 | const [openOperations, setOpenOperations] = React.useState(false); 8 | const [menuOpen, setMenuOpen] = React.useState(false); 9 | const [operationItem, setOperationItem] = React.useState(null); 10 | const [parentResource, setParentResource] = React.useState(null); 11 | const [parentItem, setParentItem] = React.useState(null); 12 | const [toggles, setToggles] = React.useState({}); 13 | return ( 14 | 29 | {subPages} 30 | 31 | ) 32 | } 33 | export default LayoutContext; 34 | -------------------------------------------------------------------------------- /src/components/app/RootView.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Redirect } from 'react-router-dom'; 3 | import { isLoggedIn, getCurrentUserUsername } from '../../common/utils'; 4 | import Search from '../search/Search'; 5 | 6 | class RootView extends React.Component { 7 | render() { 8 | return ( 9 | 10 | { 11 | isLoggedIn() ? 12 | : 17 | 18 | } 19 | 20 | ) 21 | } 22 | } 23 | 24 | export default RootView; 25 | -------------------------------------------------------------------------------- /src/components/app/SigninRedirect.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { getLoginURL } from '../../common/utils' 3 | 4 | const SigninRedirect = () => { 5 | useEffect(() => { 6 | window.location.href = getLoginURL(); 7 | }, []); 8 | 9 | return

Redirecting...

; 10 | }; 11 | 12 | export default SigninRedirect; 13 | -------------------------------------------------------------------------------- /src/components/app/SignupRedirect.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { getRegisterURL } from '../../common/utils' 3 | 4 | const SignupRedirect = () => { 5 | useEffect(() => { 6 | window.location.href = getRegisterURL(); 7 | }, []); 8 | 9 | return

Redirecting...

; 10 | }; 11 | 12 | export default SignupRedirect; 13 | -------------------------------------------------------------------------------- /src/components/collections/Collection.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Loyalty as LoyaltyIcon } from '@mui/icons-material' 3 | import { merge, get, isEmpty, map, isArray, reject, includes, keys } from 'lodash' 4 | import { DARKGRAY } from '../../common/constants'; 5 | import ResourceLabel from '../common/ResourceLabel'; 6 | import LastUpdatedOnLabel from '../common/LastUpdatedOnLabel'; 7 | import Summary from '../common/ConceptContainerSummaryHorizontal'; 8 | 9 | const DEFAULT_FIELDS = [{collection_type: 'Collection Type'}] 10 | const LABEL_FIELDS = ['id', 'short_code', 'name', 'owner'] 11 | 12 | const Collection = props => { 13 | const { summary, viewFields, history, currentLayoutURL, url } = props; 14 | const hasSummary = !isEmpty(summary); 15 | const mainClass = 'no-left-padding ' + hasSummary ? 'col-sm-9': 'col-sm-12'; 16 | const customFields = isArray(viewFields) ? reject(viewFields, fieldConfig => includes(LABEL_FIELDS, keys(fieldConfig)[0])) : []; 17 | const fields = isEmpty(customFields) ? DEFAULT_FIELDS : customFields; 18 | const navigateTo = () => { 19 | if(currentLayoutURL) 20 | history.replace(currentLayoutURL) 21 | history.push(url) 22 | } 23 | 24 | return ( 25 |
26 |
27 | 28 | } 31 | colorClass="collection-bg" 32 | /> 33 | 34 |
35 | { 36 | map(fields, field => { 37 | const attr = keys(field)[0] 38 | const label = field[attr]; 39 | return ( 40 | 41 | {label}: 42 | {get(props, attr, '')} 43 |
44 |
45 | ) 46 | }) 47 | } 48 | { 49 | props.description && 50 | {props.description} 51 | } 52 |
53 | 54 |
55 | { 56 | hasSummary && 57 | 58 | } 59 |
60 | ) 61 | } 62 | 63 | export default Collection; 64 | -------------------------------------------------------------------------------- /src/components/collections/CollectionVersionForm.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ConceptContainerVersionForm from '../common/ConceptContainerVersionForm'; 3 | 4 | const CollectionVersionForm = props => ; 5 | 6 | export default CollectionVersionForm; 7 | -------------------------------------------------------------------------------- /src/components/common/APIPreview.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Accordion, AccordionSummary, AccordionDetails, Typography 4 | } from '@mui/material' 5 | import { 6 | ExpandMore as ExpandMoreIcon, 7 | } from '@mui/icons-material' 8 | 9 | const APIPreview = ({payload, verb, url, }) => { 10 | return ( 11 | 12 | }> 13 | API details 14 | 15 | 16 |
17 | { 18 | `${verb} ${url}` 19 | } 20 |
21 |
22 |
23 |             {
24 |               JSON.stringify(payload, undefined, 2)
25 |             }
26 |           
27 |
28 |
29 |
30 | ) 31 | } 32 | 33 | export default APIPreview; 34 | -------------------------------------------------------------------------------- /src/components/common/About.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const About = ({id, about}) => { 4 | return ( 5 |
6 |

7 | {`About ${id}`} 8 |

9 | { 10 | about ? 11 |
: 12 |

No about entry

13 | } 14 |
15 | ); 16 | } 17 | 18 | export default About; 19 | -------------------------------------------------------------------------------- /src/components/common/AccessChip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Public as PublicIcon, 4 | Lock as PrivateIcon, 5 | } from '@mui/icons-material'; 6 | import { Tooltip, Chip } from '@mui/material'; 7 | import { startCase, includes } from 'lodash'; 8 | 9 | const AccessChip = props => { 10 | const publicAccess = props.public_access || props.publicAccess || ''; 11 | const isPublic = includes(['view', 'edit'], publicAccess.toLowerCase()) 12 | const label = isPublic ? 'Public' : 'Private'; 13 | const title = isPublic ? `Public Access: ${startCase(publicAccess)}` : 'Private'; 14 | return ( 15 | 16 | 17 | : 23 | 24 | } 25 | style={{backgroundColor: '#e0e0e0'}} 26 | /> 27 | 28 | 29 | ); 30 | } 31 | 32 | export default AccessChip; 33 | -------------------------------------------------------------------------------- /src/components/common/AccessDenied.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { getLoginURL } from '../../common/utils' 3 | import ErrorUI from './ErrorUI'; 4 | 5 | const AccessDenied = () => { 6 | const loginURL = getLoginURL() 7 | return ( 8 | Sign-in to view this.`} 11 | /> 12 | ) 13 | } 14 | 15 | export default AccessDenied; 16 | -------------------------------------------------------------------------------- /src/components/common/AutocompleteLoading.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box, LinearProgress } from '@mui/material' 3 | import { BLACK } from '../../common/constants' 4 | 5 | const AutocompleteLoading = ({ text }) => { 6 | 7 | return ( 8 | 9 | 10 | { 11 | text && 12 |
13 | Loading results for { text } 14 |
15 | } 16 |
17 | 18 |
19 |
20 | 21 |
22 | ) 23 | } 24 | 25 | export default AutocompleteLoading; 26 | -------------------------------------------------------------------------------- /src/components/common/BetaLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const BetaLabel = ({ label }) => ( 4 | 5 | {label} 6 | beta 7 | 8 | ) 9 | 10 | export default BetaLabel; 11 | -------------------------------------------------------------------------------- /src/components/common/Captcha.jsx: -------------------------------------------------------------------------------- 1 | /*eslint no-process-env: 0*/ 2 | 3 | import React from 'react'; 4 | import ReCAPTCHA from "react-google-recaptcha"; 5 | 6 | /*eslint no-undef: 0*/ 7 | const SITE_KEY = window.RECAPTCHA_SITE_KEY || process.env.RECAPTCHA_SITE_KEY 8 | 9 | const Captcha = ({ onChange }) => { 10 | return ( 11 | 12 | ) 13 | } 14 | 15 | export default Captcha; 16 | -------------------------------------------------------------------------------- /src/components/common/CascadeLevelDropdown.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Autocomplete, TextField } from '@mui/material'; 3 | import { find } from 'lodash' 4 | 5 | const OPTIONS = [{id: '1', name: '1'}, {id: '2', name: '2'}, {id: '3', name: '3'}, {id: '4', name: '4'}, {id: '5', name: '5'}, {id: '*', name: 'All'}] 6 | 7 | const CascadeLevelDropdown = ({value, size, placeholder, onChange}) => { 8 | return ( 9 | 15 | } 16 | getOptionLabel={option => option.name} 17 | onChange={(event, val) => onChange(val?.id)} 18 | /> 19 | ) 20 | } 21 | 22 | export default CascadeLevelDropdown; 23 | -------------------------------------------------------------------------------- /src/components/common/ChipDatePicker.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DatePicker, PickersDay } from "@mui/x-date-pickers"; 3 | import { Tooltip, Badge, TextField } from '@mui/material'; 4 | import { isEmpty, get } from 'lodash'; 5 | 6 | 7 | const ChipDatePicker = props => { 8 | const ref = React.useRef(null); 9 | const [value, setValue] = React.useState('') 10 | const { date, defaultValue, badgedDates } = props; 11 | const onChange = mDate => { 12 | let date = null; 13 | if(mDate) 14 | date = mDate.format('YYYY-MM-DD') 15 | 16 | if(value || date) 17 | props.onChange(date); 18 | } 19 | 20 | const renderDay = (date, selectedDate, DayComponentProps) => { 21 | if(!badgedDates || isEmpty(badgedDates)) 22 | return ; 23 | const fSelectedDate = selectedDate.format('DD-MM-YYYY') 24 | const fDate = date.format('DD-MM-YYYY') 25 | const count = get(badgedDates, fDate) 26 | if(count && fDate !== fSelectedDate) { 27 | return ( 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | return ; 36 | } 37 | 38 | React.useEffect( 39 | () => setValue(defaultValue || date || ''), [defaultValue] 40 | ) 41 | 42 | React.useEffect( 43 | () => setValue(defaultValue || date || ''), [date] 44 | ) 45 | 46 | 47 | const getComponent = () => { 48 | return ( 49 | 50 | 58 | } 59 | value={value} 60 | onChange={onChange} 61 | inputFormat="MM/DD/YYYY" 62 | PopoverProps={{ 63 | anchorEl: ref.current 64 | }} 65 | renderDay={renderDay} 66 | /> 67 | 68 | ) 69 | } 70 | 71 | return ( 72 | props.tooltip ? 73 | 74 | {getComponent()} 75 | : 76 | getComponent() 77 | ) 78 | } 79 | 80 | export default ChipDatePicker; 81 | -------------------------------------------------------------------------------- /src/components/common/CloneToSourceResultPreview.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | CircularProgress, DialogContent, 4 | Divider 5 | } from '@mui/material' 6 | import Concept from '../concepts/Concept' 7 | import Mapping from '../mappings/Mapping' 8 | import { filter, map, isEmpty } from 'lodash' 9 | 10 | const CloneToSourceResultPreview = ({ concept, isLoading}) => { 11 | const concepts = filter(concept.previewBundle?.entry, {type: 'Concept'}) 12 | const mappings = filter(concept.previewBundle?.entry, {type: 'Mapping'}) 13 | return ( 14 | 15 |
16 | { 17 | isLoading ? 18 |
19 | 20 |
: 21 | 22 |
23 |

{`Concepts (${concepts.length})`}

24 | { 25 | isEmpty(concepts) ? 26 |

0 concepts

: 27 | map(concepts, (_concept, index) => ( 28 | 29 | 30 | 31 | 32 | )) 33 | } 34 |
35 |
36 | 37 |
38 |
39 |

{`Mappings (${mappings.length})`}

40 | { 41 | isEmpty(mappings) ? 42 |

0 mappings

: 43 | map(mappings, (_mapping, index) => ( 44 | 45 | 46 | 47 | 48 | )) 49 | } 50 |
51 |
52 | } 53 |
54 |
55 | ) 56 | } 57 | 58 | export default CloneToSourceResultPreview; 59 | -------------------------------------------------------------------------------- /src/components/common/CloseSquareIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SvgIcon } from '@mui/material'; 3 | 4 | const CloseSquareIcon = props => { 5 | return ( 6 | 7 | {/* tslint:disable-next-line: max-line-length */} 8 | 9 | 10 | ); 11 | } 12 | 13 | export default CloseSquareIcon 14 | -------------------------------------------------------------------------------- /src/components/common/CollapsibleAttributes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ArrowDropDown as ArrowDownIcon, ArrowDropUp as ArrowUpIcon 4 | } from '@mui/icons-material'; 5 | import { Collapse, Chip } from '@mui/material'; 6 | import { map, get } from 'lodash'; 7 | import HeaderAttribute from './HeaderAttribute'; 8 | import SupportedLocales from '../common/SupportedLocales'; 9 | 10 | const CollapsibleAttributes = ({ 11 | object, hiddenAttributes 12 | }) => { 13 | const [expand, setExpand] = React.useState(false); 14 | const onExpand = () => setExpand(!expand); 15 | 16 | return ( 17 | 18 | 19 | { 20 | map(hiddenAttributes, (attr) => { 21 | const value = get(object, attr.value) 22 | if (!value) return null 23 | if (attr.value === "supported_locales" || attr.value === "default_locale"){ 24 | return } gridClass="col-md-12" type="component" /> 25 | } 26 | return 27 | }) 28 | } 29 | 30 |
31 | : } 38 | onDelete={onExpand} 39 | style={{border: 'none'}} 40 | /> 41 |
42 |
43 | ) 44 | } 45 | 46 | export default CollapsibleAttributes; 47 | -------------------------------------------------------------------------------- /src/components/common/CollapsibleDivider.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Divider, IconButton, Tooltip } from '@mui/material'; 3 | import { ArrowDropDown as DownIcon, ArrowDropUp as UpIcon } from '@mui/icons-material'; 4 | import { BLACK } from '../../common/constants'; 5 | import { merge } from 'lodash'; 6 | const LIGHT_GRAY = 'rgba(0, 0, 0, 0.12)'; 7 | 8 | const CollapsibleDivider = ({open, tooltip, onClick, light, width, style}) => { 9 | const expandColor = light ? LIGHT_GRAY : BLACK; 10 | const _width = width || '48%' 11 | return ( 12 |
13 | 14 | 15 | 16 | {open ? : } 17 | 18 | 19 | 20 |
21 | 22 | ) 23 | } 24 | 25 | export default CollapsibleDivider; 26 | -------------------------------------------------------------------------------- /src/components/common/CommonAccordion.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Accordion, AccordionSummary, AccordionDetails, Typography, FormHelperText } from '@mui/material'; 3 | import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; 4 | 5 | 6 | const CommonAccordion = ({title, subTitle, children, defaultStyle, ...accordionProps}) => { 7 | return ( 8 |
9 | 10 | }> 11 |
12 | {title} 13 | { 14 | subTitle && 15 | {subTitle} 16 | } 17 |
18 |
19 | 20 | {children} 21 | 22 |
23 |
24 | ) 25 | } 26 | 27 | export default CommonAccordion; 28 | -------------------------------------------------------------------------------- /src/components/common/CommonFormDrawer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import alertifyjs from 'alertifyjs'; 3 | import { Drawer, IconButton } from '@mui/material'; 4 | import CancelIcon from '@mui/icons-material/CancelOutlined'; 5 | import { WHITE } from '../../common/constants' 6 | 7 | const CommonFormDrawer = ({ isOpen, onClose, formComponent, size, ...rest }) => { 8 | const className = 'custom-drawer ' + (size || 'medium') 9 | const [open, setOpen] = React.useState(isOpen); 10 | const onDrawerClose = (event, reason) => { 11 | if(['escapeKeyDown', 'backdropClick'].includes(reason)) { 12 | alertifyjs.confirm('Exit?', 'Are you sure you want to close this?', _close, () => {}) 13 | } else _close(); 14 | } 15 | 16 | const _close = () => setOpen(() => { 17 | if(onClose) 18 | onClose(); 19 | return false; 20 | }) 21 | 22 | React.useEffect(() => setOpen(isOpen), [isOpen]) 23 | 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | { formComponent } 32 | 33 | ) 34 | } 35 | 36 | export default CommonFormDrawer; 37 | -------------------------------------------------------------------------------- /src/components/common/ComparisonAttributes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd'; 3 | import { 4 | Drawer, Checkbox, 5 | } from '@mui/material'; 6 | import { DragIndicator } from '@mui/icons-material'; 7 | import { map, keys } from 'lodash'; 8 | 9 | const getItemStyle = draggableStyle => ({ 10 | // some basic styles to make the items look a bit nicer 11 | userSelect: 'none', 12 | // styles we need to apply on draggables 13 | ...draggableStyle, 14 | }); 15 | const getListStyle = () => ({ 16 | overflow: 'hidden', 17 | }); 18 | 19 | const ComparisonAttributes = ({attributes, open, onClose, onCheckboxClick, onDragEnd}) => { 20 | return ( 21 | 22 |
23 |

24 | Toggle Attributes: 25 |

26 | 27 | 28 | { 29 | (provided, snapshot) => ( 30 |
35 | { 36 | map(keys(attributes), (attr, index) => ( 37 | 38 | { 39 | provided => ( 40 |
41 |
48 | 49 | onCheckboxClick(attr)} /> 50 | {attr} 51 |
52 | {provided.placeholder} 53 |
54 | ) 55 | } 56 |
57 | )) 58 | } 59 | {provided.placeholder} 60 |
61 | ) 62 | } 63 |
64 |
65 |
66 |
67 | ) 68 | } 69 | 70 | export default ComparisonAttributes; 71 | -------------------------------------------------------------------------------- /src/components/common/ConceptButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Tooltip } from '@mui/material'; 3 | import { LocalOffer as LocalOfferIcon } from '@mui/icons-material'; 4 | import { merge } from 'lodash'; 5 | import { BLUE, WHITE, RED, BLACK, UUID_LENGTH } from '../../common/constants'; 6 | 7 | const ConceptButton = ({label, onClick, retired, href, style, ...rest}) => { 8 | const _style = retired ? 9 | {background: 'lightgray', color: RED, boxShadow: 'none', textDecoration: 'line-through', textDecorationColor: BLACK, textTransform: 'none'} : 10 | {background: BLUE, color: WHITE, boxShadow: 'none', textTransform: 'none'}; 11 | const truncLabel = label && label.length === UUID_LENGTH ? `${label.split('-')[0]}...` : label 12 | return ( 13 | 14 | 25 | 26 | ) 27 | } 28 | 29 | export default ConceptButton; 30 | -------------------------------------------------------------------------------- /src/components/common/ConceptContainerLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { 4 | Person as PersonIcon, 5 | AccountBalance as HomeIcon, 6 | List as ListIcon, 7 | Loyalty as LoyaltyIcon, 8 | AccountTreeRounded as TreeIcon, 9 | ChevronRight as SeparatorIcon, 10 | } from '@mui/icons-material'; 11 | 12 | const SEPARATOR = () 13 | const ConceptContainerLabel = props => { 14 | const ownerType = (props.ownerType || props.owner_type || '').toLowerCase() 15 | 16 | let icon = ; 17 | if(props.resource === 'source') 18 | icon = ; 19 | 20 | let ownerIcon = ; 21 | if(ownerType === 'users') 22 | ownerIcon = ; 23 | 24 | return ( 25 |
26 | 27 | { 28 | props.owner && props.ownerType && 29 | 30 | 31 | {ownerIcon} 32 | {props.owner} 33 | 34 | {SEPARATOR} 35 | 36 | } 37 | 38 | {icon} 39 | {props.id} 40 | 41 | { 42 | props.version && 43 | 44 | {SEPARATOR} 45 | 46 | 47 | {props.version} 48 | 49 | 50 | } 51 | 52 |
53 | ) 54 | } 55 | 56 | export default ConceptContainerLabel; 57 | -------------------------------------------------------------------------------- /src/components/common/ConceptContainerSummaryHorizontal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom' 3 | import { 4 | LocalOffer as LocalOfferIcon, 5 | Link as LinkIcon, 6 | AccountTreeRounded as TreeIcon, 7 | } from '@mui/icons-material' 8 | import { Tooltip } from '@mui/material'; 9 | import { isNumber } from 'lodash'; 10 | 11 | const TAG_ICON_STYLES = {width: '12px', marginRight: '4px', marginTop: '2px'} 12 | 13 | const ConceptContainerSummaryHorizontal = props => { 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 | {isNumber(props.summary.active_concepts) ? props.summary.active_concepts : '-'} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {isNumber(props.summary.active_mappings) ? props.summary.active_mappings : '-'} 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {props.summary.versions} 37 | 38 | 39 | 40 |
41 | ) 42 | } 43 | 44 | export default ConceptContainerSummaryHorizontal; 45 | -------------------------------------------------------------------------------- /src/components/common/ConceptContainerTip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { startCase } from 'lodash'; 3 | import Tip from './Tip' 4 | 5 | const getContent = resource => { 6 | return ( 7 | 8 |

9 | Create a new 10 | {` ${startCase(resource)} Version `} 11 | { 12 | `to save the state of a ${resource}'s concepts and mappings at a specific point in time.` 13 | } 14 |

15 |

16 | A 17 | Released 18 | { 19 | ` ${resource} version indicates to your users that a particular source version is prepared for public consumption, while a` 20 | } 21 | Retired 22 | {`${resource} version indicates that it should no longer be used.`} 23 |

24 |
25 | ); 26 | } 27 | 28 | const ConceptContainerTip = ({ resource }) => { 29 | return ( 30 | 31 | ); 32 | } 33 | 34 | export default ConceptContainerTip; 35 | -------------------------------------------------------------------------------- /src/components/common/ConceptContainersAutocomplete.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {List as ListIcon, Loyalty as LoyaltyIcon} from '@mui/icons-material'; 3 | import { TextField } from '@mui/material'; 4 | import Autocomplete from '@mui/material/Autocomplete'; 5 | import { get } from 'lodash' 6 | import { GREEN } from '../../common/constants'; 7 | 8 | const ConceptContainersAutocomplete = ({onChange, items, label, id, required, selected}) => { 9 | return ( 10 | (option.id === get(value, 'id') && option.type === get(value, 'type'))} 14 | value={selected} 15 | id={id || 'conceptContainer'} 16 | options={items} 17 | getOptionLabel={option => option ? `${option.name} (${option.type})` : ''} 18 | fullWidth 19 | required={required} 20 | renderInput={ 21 | params => 22 | } 23 | renderOption={ 24 | (props, option) => ( 25 |
  • 26 | 27 | 28 | { 29 | option.type === 'source' ? 30 | : 31 | 32 | } 33 | 34 | {option.name} 35 | 36 |
  • 37 | ) 38 | } 39 | onChange={(event, item) => onChange(get(item, 'type') || 'source', item)} 40 | /> 41 | ); 42 | } 43 | 44 | export default ConceptContainersAutocomplete; 45 | -------------------------------------------------------------------------------- /src/components/common/ConceptMapButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from '@mui/material'; 3 | import { Link as LinkIcon } from '@mui/icons-material'; 4 | import { GREEN, WHITE } from '../../common/constants'; 5 | 6 | const ConceptMapButton = ({label, onClick, href}) => { 7 | return ( 8 | 16 | ) 17 | } 18 | 19 | export default ConceptMapButton; 20 | -------------------------------------------------------------------------------- /src/components/common/CustomAttributes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { isEmpty } from 'lodash'; 3 | 4 | const None = () => { 5 | return
    None
    6 | } 7 | 8 | const CustomAttributes = ({attributes, preStyles}) => { 9 | return ( 10 |
    11 | { 12 | isEmpty(attributes) ? 13 | None() : 14 |
    {JSON.stringify(attributes, undefined, 2)}
    15 | } 16 |
    17 | ) 18 | } 19 | 20 | export default CustomAttributes; 21 | -------------------------------------------------------------------------------- /src/components/common/CustomAttributesFormatted.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { map, isEmpty, isBoolean, isArray, isObject, find } from 'lodash'; 3 | 4 | const None = () => { 5 | return
    None
    6 | } 7 | 8 | const CustomAttributesFormatted = ({attributes}) => { 9 | const shouldBeNested = value => { 10 | return isArray(value) && Boolean(find(value, isObject)) 11 | } 12 | 13 | const getNestedValueDom = (value, index) => { 14 | return isObject(value) ? 15 |
    16 | {`${JSON.stringify(value).slice(0, 50)}...`} 17 |
    {JSON.stringify(value, undefined, 2)}
    18 |
    : 19 | {JSON.stringify(value)} 20 | } 21 | 22 | return ( 23 |
    24 | { 25 | isEmpty(attributes) ? 26 | None() : 27 | map(attributes, (value, name) => { 28 | const isBool = isBoolean(value) 29 | const needNesting = !isBool && shouldBeNested(value) 30 | const isArr = isArray(value) 31 | return ( 32 |
    33 |
    34 | {name} 35 |
    36 |
    37 | { 38 | isBool && value.toString() 39 | } 40 | { 41 | needNesting && 42 | map(value, (val, index) => getNestedValueDom(val, index)) 43 | } 44 | { 45 | isArr && !needNesting && 46 |
    {JSON.stringify(value)}
    47 | } 48 | { 49 | !isBool && !needNesting && !isArr && isObject(value) && 50 | getNestedValueDom(value) 51 | } 52 | { 53 | !isBool && !needNesting && !isArr && !isObject(value) && 54 | value 55 | } 56 |
    57 |
    58 | ) 59 | }) 60 | } 61 |
    62 | ) 63 | } 64 | 65 | export default CustomAttributesFormatted; 66 | -------------------------------------------------------------------------------- /src/components/common/CustomAttributesPopup.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Chip, Dialog, DialogTitle, DialogContent, DialogActions, Button 4 | } from '@mui/material'; 5 | import ArrowRightIcon from '@mui/icons-material/ArrowRight'; 6 | import { isEmpty } from 'lodash'; 7 | import CustomAttributes from './CustomAttributes' 8 | import CustomAttributesFormatted from './CustomAttributesFormatted' 9 | 10 | const CustomAttributesPopup = ({attributes, color}) => { 11 | const [raw, setRaw] = React.useState(false); 12 | const [open, setOpen] = React.useState(false); 13 | const handleClose = () => setOpen(false) 14 | const onOpen = () => setOpen(true) 15 | 16 | return ( 17 | 18 | { 19 | isEmpty(attributes) ? 20 | None : 21 | 22 | } 29 | onDelete={onOpen} 30 | style={color ? {border: 'none', color: color} : {border: 'none'}} 31 | /> 32 | 33 | Custom Attributes 34 | 35 | { 36 | raw ? 37 | : 38 | 39 | } 40 | 41 | 42 | 45 | 48 | 49 | 50 | 51 | } 52 | 53 | ) 54 | } 55 | 56 | export default CustomAttributesPopup; 57 | -------------------------------------------------------------------------------- /src/components/common/CustomMarkdown.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactMarkdown from 'react-markdown' 3 | import gfm from 'remark-gfm' 4 | import './CustomMarkdown.scss' 5 | 6 | const CustomMarkdown = ({title, markdown, classes, id}) => { 7 | return ( 8 |
    9 | { title &&

    { title }

    } 10 | { 11 | markdown && 12 | 13 | { markdown } 14 | 15 | } 16 |
    17 | ); 18 | } 19 | 20 | export default CustomMarkdown; 21 | -------------------------------------------------------------------------------- /src/components/common/CustomMarkdown.scss: -------------------------------------------------------------------------------- 1 | div.custom-markdown { 2 | tr { 3 | border-top: 1px solid #c6cbd1; 4 | background: #fff; 5 | } 6 | 7 | th, 8 | td { 9 | padding: 6px 13px; 10 | border: 1px solid #dfe2e5; 11 | } 12 | 13 | table tr:nth-child(2n) { 14 | background: #f6f8fa; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/common/CustomMarkup.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const CustomMarkup = ({title, markup}) => { 4 | return ( 5 |
    6 | { title &&

    { title }

    } 7 | { 8 | markup && 9 |
    10 | } 11 |
    12 | ); 13 | } 14 | 15 | export default CustomMarkup; 16 | -------------------------------------------------------------------------------- /src/components/common/CustomText.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CustomMarkup from './CustomMarkup'; 3 | import CustomMarkdown from './CustomMarkdown'; 4 | 5 | const CustomText = ({title, value, format, url}) => { 6 | const [urlText, setURLText] = React.useState(null); 7 | const [fetched, setFetched] = React.useState(false) 8 | if(url && !urlText && !fetched) { 9 | setFetched(true) 10 | fetch(url).then(response => response.text()).then(text => setURLText(text)) 11 | } 12 | return ( 13 |
    14 | { title &&

    { title }

    } 15 | { format === 'md' && } 16 | { format === 'text' &&
    { value }
    } 17 | { (!format || format === 'html') && } 18 | { 19 | (!format || format === 'html') && urlText && 20 | } 21 | { 22 | (format === 'md') && urlText && 23 | } 24 | { 25 | (format === 'text') && urlText &&
    {urlText}
    26 | } 27 |
    28 | ); 29 | } 30 | 31 | export default CustomText; 32 | -------------------------------------------------------------------------------- /src/components/common/DialogTitleWithCloseButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | IconButton, DialogTitle 4 | } from '@mui/material'; 5 | import CloseIcon from '@mui/icons-material/Close'; 6 | 7 | 8 | const DialogTitleWithCloseButton = ({ children, onClose, disabled, ...other }) => { 9 | return ( 10 | 11 | { children } 12 | { 13 | onClose && 14 | 24 | 25 | 26 | } 27 | 28 | ) 29 | } 30 | 31 | export default DialogTitleWithCloseButton; 32 | -------------------------------------------------------------------------------- /src/components/common/DoesnotExistsInOCLIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { LinkOff as Icon } from '@mui/icons-material'; 3 | import { Tooltip } from '@mui/material'; 4 | import { merge } from 'lodash'; 5 | import { ORANGE } from '../../common/constants'; 6 | import { getSiteTitle } from '../../common/utils'; 7 | 8 | const SITE_TITLE = getSiteTitle() 9 | 10 | const DoesnotExistsInOCLIcon = ({containerStyles, iconStyles, title}) => { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | } 19 | export default DoesnotExistsInOCLIcon; 20 | -------------------------------------------------------------------------------- /src/components/common/DynamicConfigResourceIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@mui/material'; 3 | import { 4 | List as SourceIcon, Loyalty as CollectionIcon, Person as UserIcon, 5 | Info as InfoIcon, AccountBalance as HomeIcon, Link as MappingIcon, 6 | AccountTreeRounded as VersionIcon, LocalOffer as ConceptIcon, 7 | Search as SearchIcon, Publish as ImportsIcon, 8 | CompareArrows as CompareIcon, 9 | AspectRatio as ExpansionIcon 10 | } from '@mui/icons-material'; 11 | import { includes, snakeCase } from 'lodash'; 12 | import { GREEN, BLUE, ORANGE } from '../../common/constants'; 13 | 14 | const DynamicConfigResourceIcon = ({resource, index, style, icon, enableColor, ...rest}) => { 15 | const styles = style || {} 16 | if(icon) 17 | return ({snakeCase(icon)}) 18 | if(includes(['source', 'sources'], resource)) 19 | return ; 20 | if(includes(['collection', 'collections'], resource)) 21 | return ; 22 | if(includes(['user', 'users'], resource)) 23 | return ; 24 | if(includes(['org', 'orgs', 'organizations', 'organization'], resource)) 25 | return ; 26 | if(includes(['concept', 'concepts'], resource)) 27 | return ; 28 | if(includes(['mapping', 'mappings'], resource)) 29 | return ; 30 | if(includes(['versions', 'history', 'version'], resource)) 31 | return ; 32 | if(index === 0) 33 | return ; 34 | if(includes(['about', 'text'], resource)) 35 | return ; 36 | if(includes(['search'], resource)) 37 | return ; 38 | if(includes(['import', 'imports'], resource)) 39 | return ; 40 | if(includes(['compare'], resource)) 41 | return ; 42 | if(includes(['reference', 'references'], resource)) 43 | return ; 44 | if(includes(['expansion', 'expansions'], resource)) 45 | return ; 46 | 47 | return ''; 48 | } 49 | 50 | export default DynamicConfigResourceIcon; 51 | -------------------------------------------------------------------------------- /src/components/common/ErrorBoundary.jsx: -------------------------------------------------------------------------------- 1 | /*eslint no-process-env: 0*/ 2 | import React from 'react'; 3 | import StackTrace from "stacktrace-js"; 4 | import { Notifier } from '@airbrake/browser'; 5 | import { map, isString } from 'lodash'; 6 | import { getCurrentUser, getEnv } from '../../common/utils'; 7 | import ErrorUI from './ErrorUI'; 8 | 9 | class ErrorBoundary extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | hasError: false, 14 | error: null, 15 | errorInfo: null 16 | }; 17 | } 18 | 19 | getNotifier = () => { 20 | /*eslint no-undef: 0*/ 21 | this.ERRBIT_KEY = window.ERRBIT_KEY || process.env.ERRBIT_KEY 22 | /*eslint no-undef: 0*/ 23 | this.ERRBIT_URL = window.ERRBIT_URL || process.env.ERRBIT_URL 24 | if(this.ERRBIT_URL && this.ERRBIT_KEY) 25 | return new Notifier({ 26 | projectId: 1, 27 | projectKey: this.ERRBIT_KEY, 28 | environment: getEnv(), 29 | host: this.ERRBIT_URL 30 | }) 31 | } 32 | 33 | componentDidCatch(error, errorInfo) { 34 | this.setState({error: error, errorInfo: errorInfo, hasError: Boolean(error)}, () => { 35 | StackTrace.fromError(this.state.error).then(traces => { 36 | const newTraces = map(traces, trace => ({ 37 | column: trace.columnNumber, 38 | file: trace.fileName, 39 | 'function': trace.functionName, 40 | line: trace.lineNumber 41 | })); 42 | const notifier = this.getNotifier() 43 | if(notifier) { 44 | const user = getCurrentUser() || {}; 45 | notifier.addFilter(notice => { 46 | notice.errors[0].backtrace = newTraces; 47 | return notice; 48 | }); 49 | notifier.notify({ 50 | error: this.state.error, 51 | params: {info: this.state.errorInfo}, 52 | context: { 53 | component: window.location.href, user: {id: user.id, email: user.email} 54 | } 55 | }); 56 | } 57 | }) 58 | }) 59 | } 60 | 61 | getErrorUIProps() { 62 | const props = {header: 'Error', message: isString(this.state.error) ? this.state.error : 'Something went wrong.'} 63 | 64 | if(window.location.hash.match(/debug=true/)) 65 | return {...props, error: this.state.error, errorInfo: this.state.errorInfo} 66 | 67 | return props 68 | } 69 | 70 | render() { 71 | const props = this.getErrorUIProps() 72 | const { hasError } = this.state; 73 | return hasError ? : this.props.children; 74 | } 75 | } 76 | 77 | export default ErrorBoundary; 78 | -------------------------------------------------------------------------------- /src/components/common/ErrorUI.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Divider, Collapse, Chip } from '@mui/material' 3 | import {ArrowRight as RightIcon, ArrowDropDown as DownIcon} from '@mui/icons-material' 4 | import { get, isObject } from 'lodash'; 5 | import { ERROR_RED } from '../../common/constants'; 6 | 7 | const ErrorUI = ({header, message, error, errorInfo}) => { 8 | const [open, setOpen] = React.useState(false) 9 | const icon = open ? 10 | : 11 | ; 12 | const errorDetails = get(errorInfo, 'componentStack') ? errorInfo.componentStack : JSON.stringify(errorInfo, undefined, 2); 13 | const errorMsg = isObject(error) ? error.toString() : error; 14 | return ( 15 | 16 |
    17 |
    18 |

    {header}

    19 | 20 |
    21 |

    22 |

    23 |
    24 | { 25 | errorMsg && 26 |
    27 | setOpen(!open)} 33 | /> 34 | 35 |

    {errorMsg}

    36 |
    37 |               {errorDetails}
    38 |             
    39 |
    40 |
    41 | } 42 |
    43 |
    44 | ) 45 | } 46 | 47 | export default ErrorUI; 48 | -------------------------------------------------------------------------------- /src/components/common/ExistsInOCLIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link as Icon } from '@mui/icons-material'; 3 | import { Tooltip } from '@mui/material'; 4 | import { merge } from 'lodash'; 5 | import { GREEN } from '../../common/constants'; 6 | import { getSiteTitle } from '../../common/utils'; 7 | 8 | const SITE_TITLE = getSiteTitle() 9 | 10 | const ExistsInOCLIcon = ({containerStyles, iconStyles, title}) => { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | } 19 | 20 | export default ExistsInOCLIcon; 21 | -------------------------------------------------------------------------------- /src/components/common/ExpansionButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from '@mui/material'; 3 | import { 4 | AspectRatio as ExpansionIcon 5 | } from '@mui/icons-material'; 6 | import { BLUE, WHITE, RED, BLACK } from '../../common/constants'; 7 | 8 | const ExpansionButton = ({label, onClick, retired, href, bgColor, textColor}) => { 9 | let backgroundColor = bgColor || BLUE; 10 | let txtColor = textColor || WHITE; 11 | const style = retired ? 12 | {background: 'lightgray', color: RED, boxShadow: 'none', textDecoration: 'line-through', textDecorationColor: BLACK, textTransform: 'none'} : 13 | {background: backgroundColor, color: txtColor, boxShadow: 'none', textTransform: 'none'}; 14 | return ( 15 | 23 | ) 24 | } 25 | 26 | export default ExpansionButton; 27 | -------------------------------------------------------------------------------- /src/components/common/ExpansionChip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Check as YesIcon, 4 | Close as NoIcon, 5 | } from '@mui/icons-material'; 6 | import { Tooltip, Chip } from '@mui/material'; 7 | import { ERROR_RED } from '../../common/constants'; 8 | 9 | const ExpansionChip = ({ expanded }) => { 10 | const label = 'Auto-Expand'; 11 | const title = expanded ? 'Auto-Expand' : 'Not expanded automatically' 12 | return ( 13 | 14 | : 21 | 22 | } 23 | /> 24 | 25 | ); 26 | } 27 | 28 | export default ExpansionChip; 29 | -------------------------------------------------------------------------------- /src/components/common/ExternalIdLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ArrowForward as ForwardIcon } from '@mui/icons-material'; 3 | import { merge } from 'lodash'; 4 | 5 | const STYLES = { 6 | medium: { 7 | icon: {width: '9pt', marginTop: '2px', marginRight: '4px', height: '9pt'}, 8 | fontSize: '9pt', 9 | }, 10 | small: { 11 | icon: {width: '8pt', marginTop: '2px', marginRight: '4px', height: '8pt'}, 12 | fontSize: '8pt', 13 | } 14 | } 15 | 16 | const ExternalIdLabel = props => { 17 | const styles = STYLES[props.iconSize || 'small'] 18 | return ( 19 |
    20 | 21 | 29 | 30 | External ID: {props.externalId} 31 |
    32 | ) 33 | } 34 | 35 | export default ExternalIdLabel; 36 | -------------------------------------------------------------------------------- /src/components/common/FileUploader.scss: -------------------------------------------------------------------------------- 1 | .file-uploader-main-container { 2 | padding: 16px; 3 | border: 1px solid rgb(232, 232, 232); 4 | border-radius: 3px; 5 | width: 100%; 6 | display: inline-block; 7 | .file-uploader-container { 8 | display: flex; 9 | flex-direction: column; 10 | font-family: sans-serif; 11 | .dropzone { 12 | flex: 1; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | padding: 20px; 17 | border-width: 2px; 18 | border-radius: 2px; 19 | border-color: #eeeeee; 20 | border-style: dashed; 21 | background-color: #fafafa; 22 | color: #bdbdbd; 23 | outline: none; 24 | transition: border .24s ease-in-out; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/common/FilterButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chip, Tooltip } from '@mui/material'; 3 | import { 4 | FilterAlt as FilterIcon, 5 | } from '@mui/icons-material'; 6 | 7 | const FilterButton = ({tooltipTitle, label, count, disabled, onClick, size, minWidth, isOpen}) => { 8 | const hasFilters = count && count > 0; 9 | const color = (hasFilters || isOpen) ? 'primary' : 'secondary'; 10 | const buttonLabel = (label || 'Filters') + (hasFilters ? ` (${count})` : ''); 11 | 12 | return ( 13 | 14 | } 19 | label={buttonLabel} 20 | style={{minWidth: minWidth || '100px', cursor: disabled ? 'not-allowed' : 'pointer'}} 21 | disabled={disabled} 22 | size={size || 'medium'} 23 | /> 24 | 25 | ) 26 | } 27 | 28 | export default FilterButton; 29 | -------------------------------------------------------------------------------- /src/components/common/FormTooltip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tooltip } from '@mui/material'; 3 | import { InfoOutlined as Icon } from '@mui/icons-material'; 4 | 5 | const FormTooltip = ({ title, placement, style, size }) => ( 6 | 7 | 8 | 9 | 10 | 11 | ) 12 | 13 | export default FormTooltip; 14 | -------------------------------------------------------------------------------- /src/components/common/GroupHeader.jsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/system'; 2 | 3 | const GroupHeader = styled('div')({ 4 | position: 'sticky', 5 | top: '-8px', 6 | padding: '4px 16px', 7 | zIndex: 1000, 8 | backgroundColor: '#f5f5f5', 9 | fontSize: '12px', 10 | color: 'rgba(0, 0, 0, 0.6)', 11 | borderBottom: '1px solid rgba(0, 0, 0, 0.12)' 12 | }); 13 | 14 | 15 | export default GroupHeader; 16 | -------------------------------------------------------------------------------- /src/components/common/GroupItems.jsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/system'; 2 | 3 | const GroupItems = styled('ul')({ 4 | padding: 0, 5 | }); 6 | 7 | 8 | export default GroupItems; 9 | -------------------------------------------------------------------------------- /src/components/common/HeaderLogo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { Dialog, DialogTitle, DialogContent, DialogActions, Tooltip, Button, IconButton } from '@mui/material' 4 | import { Edit as EditIcon, CloudUpload as UploadIcon } from '@mui/icons-material'; 5 | import { last } from 'lodash'; 6 | import { currentUserHasAccess } from '../../common/utils'; 7 | import ImageUploader from './ImageUploader'; 8 | 9 | const HeaderLogo = ({ logoURL, onUpload, defaultIcon, isCircle, shrink, className }) => { 10 | const { t } = useTranslation() 11 | const hasAccess = currentUserHasAccess(); 12 | const [base64, setBase64] = React.useState(null); 13 | const [open, setOpen] = React.useState(false); 14 | const onLogoUpload = (base64, name) => { 15 | setOpen(false); 16 | setBase64(base64) 17 | onUpload(base64, name) 18 | } 19 | const getExistingLogoName = () => { 20 | if(!logoURL) 21 | return 22 | 23 | return last(logoURL.split('/')) 24 | } 25 | 26 | let containerClasses = 'logo-container flex-vertical-center' 27 | if(className) 28 | containerClasses += ` ${className}` 29 | else if(shrink) 30 | containerClasses += ' small' 31 | 32 | const logo = base64 || logoURL 33 | 34 | return ( 35 | 36 |
    37 | { 38 | logo ? 39 | : 40 | defaultIcon 41 | } 42 | { 43 | hasAccess && 44 | 45 | setOpen(true)} 47 | className='logo-edit-button' 48 | color='secondary' 49 | size="large"> 50 | { 51 | logoURL ? 52 | : 53 | 54 | } 55 | 56 | 57 | } 58 |
    59 | setOpen(false)} open={open} fullWidth> 60 | {logoURL ? t('common.logo.dialog.title.edit') : t('common.logo.dialog.title.add')} 61 | 62 | 63 | 64 | 65 | 68 | 69 | 70 |
    71 | ); 72 | } 73 | 74 | export default HeaderLogo; 75 | -------------------------------------------------------------------------------- /src/components/common/HtmlToolTipRaw.jsx: -------------------------------------------------------------------------------- 1 | import { Tooltip } from '@mui/material'; 2 | import withStyles from '@mui/styles/withStyles'; 3 | 4 | const HtmlToolTipRaw = withStyles(theme => ({ 5 | tooltip: { 6 | backgroundColor: '#FFF', 7 | color: 'rgba(0, 0, 0, 0.87)', 8 | maxWidth: 255, 9 | fontSize: theme.typography.pxToRem(12), 10 | border: '1px solid rgba(0, 0, 0, 0.25)', 11 | left: '-5px', 12 | display: 'flex', 13 | alignItems: 'center', 14 | }, 15 | arrow: { 16 | color: '#FFF', 17 | "&::before": { 18 | border: '1px solid rgb(0, 0, 0, 0.25)', 19 | } 20 | } 21 | }))(Tooltip); 22 | 23 | export default HtmlToolTipRaw; 24 | 25 | 26 | export const HtmlToolTipClone = withStyles(theme => ({ 27 | tooltip: { 28 | maxWidth: 255, 29 | fontSize: theme.typography.pxToRem(12), 30 | left: '-5px', 31 | display: 'flex', 32 | alignItems: 'center', 33 | }, 34 | }))(Tooltip); 35 | 36 | 37 | export const HtmlToolTipNormalRaw = withStyles(theme => ({ 38 | tooltip: { 39 | backgroundColor: '#FFF', 40 | color: 'rgba(0, 0, 0, 0.87)', 41 | maxWidth: 300, 42 | fontSize: theme.typography.pxToRem(12), 43 | border: '1px solid rgba(0, 0, 0, 0.25)', 44 | left: '-5px', 45 | display: 'flex', 46 | alignItems: 'center', 47 | }, 48 | arrow: { 49 | color: '#FFF', 50 | "&::before": { 51 | border: '1px solid rgb(0, 0, 0, 0.25)', 52 | } 53 | } 54 | }))(Tooltip); 55 | -------------------------------------------------------------------------------- /src/components/common/HtmlTooltip.jsx: -------------------------------------------------------------------------------- 1 | import { Tooltip } from '@mui/material'; 2 | import withStyles from '@mui/styles/withStyles'; 3 | 4 | const HtmlTooltip = withStyles(theme => ({ 5 | tooltip: { 6 | backgroundColor: '#f5f5f9', 7 | color: 'rgba(0, 0, 0, 0.87)', 8 | maxWidth: 255, 9 | fontSize: theme.typography.pxToRem(12), 10 | border: '1px solid rgb(51, 115, 170)', 11 | left: '-5px', 12 | display: 'flex', 13 | alignItems: 'center', 14 | }, 15 | arrow: { 16 | color: '#f5f5f9', 17 | "&::before": { 18 | border: '1px solid rgb(51, 115, 170)', 19 | } 20 | } 21 | }))(Tooltip); 22 | 23 | export default HtmlTooltip; 24 | -------------------------------------------------------------------------------- /src/components/common/IncludeRetiredFilterChip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chip, Tooltip } from '@mui/material'; 3 | 4 | const IncludeRetiredFilterChip = ({ size, applied, onClick }) => { 5 | const label = applied ? 'Exclude Retired' : 'Include Retired'; 6 | const color = applied ? 'primary' : 'secondary'; 7 | return ( 8 | 9 | 17 | 18 | ); 19 | } 20 | 21 | export default IncludeRetiredFilterChip; 22 | -------------------------------------------------------------------------------- /src/components/common/InfiniteScrollChip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chip, Tooltip } from '@mui/material'; 3 | 4 | const InfiniteScrollChip = ({isInfinite, onClick, size}) => { 5 | const label = isInfinite ? 'Paginated List' : 'Infinite Scroll'; 6 | const tooltipTitle = `Switch to ${label}` 7 | 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | 15 | export default InfiniteScrollChip; 16 | -------------------------------------------------------------------------------- /src/components/common/JSONEditor.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import JSONInput from 'react-json-editor-ajrm'; 3 | import locale from 'react-json-editor-ajrm/locale/en'; 4 | 5 | const JSONEditor = props => { 6 | 7 | return ( 8 | 9 | ) 10 | } 11 | 12 | export default JSONEditor; 13 | -------------------------------------------------------------------------------- /src/components/common/JSONIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon } from '@mui/material'; 3 | 4 | const JSONIcon = props => { 5 | return ( 6 | 7 | 8 | 9 | ) 10 | } 11 | 12 | export default JSONIcon; 13 | -------------------------------------------------------------------------------- /src/components/common/LastUpdatedOnLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import moment from 'moment'; 3 | import {Event as EventIcon} from '@mui/icons-material' 4 | import Divider from '@mui/material/Divider' 5 | import { merge } from 'lodash'; 6 | import { DATE_FORMAT } from '../../common/constants' 7 | import { formatTimeTaken } from '../../common/utils'; 8 | 9 | const STYLES = { 10 | medium: { 11 | icon: {width: '9pt', marginTop: '-4px', marginRight: '4px'}, 12 | fontSize: '9pt', 13 | }, 14 | small: { 15 | icon: {width: '8pt', marginTop: '-4px', marginRight: '4px'}, 16 | fontSize: '8pt', 17 | } 18 | } 19 | 20 | const LastUpdatedOnLabel = props => { 21 | const byLabel = props.by ? `by ${props.by}` : ''; 22 | const mainLabel = props.label || 'Last updated'; 23 | const containerClass = props.noContainerClass ? '' : 'col-xs-12 no-side-padding'; 24 | const styles = STYLES[props.iconSize || 'small'] 25 | 26 | return ( 27 |
    28 | 29 | 30 | 31 | 32 | {mainLabel} on {moment(props.date).format(DATE_FORMAT)} {byLabel} 33 | 34 | { 35 | Boolean(props.timeTaken) && 36 | 37 | 38 | 39 | {props.timeTakenLabel} {formatTimeTaken(props.timeTaken)} 40 | 41 | 42 | } 43 |
    44 | ) 45 | } 46 | 47 | export default LastUpdatedOnLabel; 48 | -------------------------------------------------------------------------------- /src/components/common/LayoutToggle.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chip, MenuItem, Menu, ListItemIcon, Tooltip } from '@mui/material'; 3 | import { 4 | TableChart as TableIcon, 5 | ViewStream as RowsIcon, 6 | ArrowDropDown as ArrowDropDownIcon 7 | } from '@mui/icons-material'; 8 | import { find, map } from 'lodash'; 9 | import { 10 | TABLE_LAYOUT_ID, LIST_LAYOUT_ID 11 | } from '../../common/constants' 12 | 13 | const OPTIONS = [ 14 | {id: TABLE_LAYOUT_ID, name: 'Table View', icon: }, 15 | {id: LIST_LAYOUT_ID, name: 'Row View', icon: }, 16 | ] 17 | 18 | 19 | const LayoutToggle = ({ layoutId, onClick, size }) => { 20 | const [anchorEl, setAnchorEl] = React.useState(null); 21 | const selectedLayoutId = layoutId || 'table'; 22 | const selectedLayout = find(OPTIONS, {id: selectedLayoutId}) 23 | const toggleAnchor = event => setAnchorEl(anchorEl ? null : event.currentTarget) 24 | const onSelect = id => { 25 | onClick(id) 26 | toggleAnchor() 27 | } 28 | 29 | return ( 30 | 31 | 32 | } 40 | onDelete={toggleAnchor} 41 | /> 42 | 43 | 50 | { 51 | map(OPTIONS, ({id, name, icon}) => ( 52 | onSelect(id)}> 53 | 54 | {icon} 55 | 56 | {name} 57 | 58 | )) 59 | } 60 | 61 | 62 | ); 63 | } 64 | 65 | export default LayoutToggle; 66 | -------------------------------------------------------------------------------- /src/components/common/LinearProgressWithLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import makeStyles from '@mui/styles/makeStyles'; 3 | import {LinearProgress, Typography, Box} from '@mui/material'; 4 | 5 | const LinearProgressWithLabel = props => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | {`${Math.round( 13 | props.value, 14 | )}%`} 15 | 16 | 17 | ); 18 | } 19 | 20 | const useStyles = makeStyles({ 21 | root: { 22 | width: '100%', 23 | }, 24 | }); 25 | 26 | const LinearWithValueLabel = ({ progress }) => { 27 | const classes = useStyles(); 28 | 29 | return ( 30 |
    31 | 32 |
    33 | ); 34 | } 35 | 36 | export default LinearWithValueLabel; 37 | -------------------------------------------------------------------------------- /src/components/common/LinkLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link as LinkIcon } from '@mui/icons-material'; 3 | import { merge } from 'lodash'; 4 | import { formatWebsiteLink } from '../../common/utils'; 5 | 6 | const STYLES = { 7 | medium: { 8 | icon: {width: '9.5pt', marginTop: '-3px', marginRight: '4px'}, 9 | fontSize: '9.5pt', 10 | }, 11 | small: { 12 | icon: {width: '8pt', marginTop: '-4px', marginRight: '4px'}, 13 | fontSize: '8pt', 14 | } 15 | } 16 | 17 | const LinkLabel = props => { 18 | const containerClass = props.noContainerClass ? '' : 'col-sm-12 no-side-padding'; 19 | const styles = STYLES[props.iconSize || 'small'] 20 | return ( 21 |
    22 | 23 | 24 | 25 | 26 | {formatWebsiteLink(props.link, {color: 'inherit'})} 27 | 28 |
    29 | ) 30 | } 31 | 32 | export default LinkLabel; 33 | -------------------------------------------------------------------------------- /src/components/common/LocationLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {LocationOn as LocationIcon} from '@mui/icons-material' 3 | import { merge } from 'lodash'; 4 | 5 | const STYLES = { 6 | medium: { 7 | icon: {width: '9pt', marginTop: '-4px', marginRight: '4px'}, 8 | fontSize: '9pt', 9 | }, 10 | small: { 11 | icon: {width: '8pt', marginTop: '-4px', marginRight: '4px'}, 12 | fontSize: '8pt', 13 | } 14 | } 15 | 16 | const LocationLabel = props => { 17 | const containerClass = props.noContainerClass ? '' : 'col-sm-12 no-side-padding'; 18 | const styles = STYLES[props.iconSize || 'small'] 19 | const containerStyles = props.containerStyle || {} 20 | 21 | return ( 22 |
    23 | 24 | 25 | 26 | 27 | {props.location} 28 | 29 |
    30 | ) 31 | } 32 | 33 | export default LocationLabel; 34 | -------------------------------------------------------------------------------- /src/components/common/MappingButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, ButtonGroup, Tooltip } from '@mui/material'; 3 | import { Link as LinkIcon } from '@mui/icons-material'; 4 | import { merge } from 'lodash'; 5 | import { BLUE, WHITE, RED, BLACK, UUID_LENGTH } from '../../common/constants'; 6 | 7 | const MappingButton = ({label, mapType, onClick, retired, href, style, ...rest}) => { 8 | const _style = retired ? 9 | {background: 'lightgray', color: RED, boxShadow: 'none', textDecoration: 'line-through', textDecorationColor: BLACK, textTransform: 'none'} : 10 | {background: BLUE, color: WHITE, boxShadow: 'none', textTransform: 'none'}; 11 | 12 | const truncLabel = label && label.length === UUID_LENGTH ? `${label.split('-')[0]}...` : label 13 | return ( 14 | 15 | 16 | 26 | { 27 | mapType && 28 | 37 | } 38 | 39 | 40 | ) 41 | } 42 | 43 | export default MappingButton; 44 | -------------------------------------------------------------------------------- /src/components/common/MinusSquareIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SvgIcon } from '@mui/material'; 3 | 4 | const MinusSquareIcon = props => { 5 | return ( 6 | 7 | {/* tslint:disable-next-line: max-line-length */} 8 | 9 | 10 | ); 11 | } 12 | 13 | export default MinusSquareIcon 14 | -------------------------------------------------------------------------------- /src/components/common/NewResourceButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { startCase, map, find } from 'lodash'; 3 | import { IconButton, Menu, MenuItem, Tooltip } from '@mui/material'; 4 | import { 5 | Settings as SettingsIcon, 6 | Add as AddIcon, 7 | } from '@mui/icons-material' 8 | 9 | const NewResourceButton = ({resources, onClick, color}) => { 10 | const [anchorEl, setAnchorEl] = React.useState(null); 11 | const toggleAnchorEl = event => setAnchorEl(prev => prev ? null : event?.currentTarget) 12 | 13 | const onItemClick = resource => { 14 | onClick(resource) 15 | toggleAnchorEl() 16 | } 17 | 18 | const formatResourceName = resource => resource.startsWith('edit') ? startCase(resource) : `Add ${startCase(resource)}` 19 | const hasEdit = find(resources, resource => resource.startsWith('edit')) 20 | 21 | return ( 22 | 23 | 24 | 25 | { hasEdit ? : } 26 | 27 | 28 | 29 | { 30 | map(resources, resource => ( 31 | onItemClick(resource)}> 32 | {formatResourceName(resource)} 33 | 34 | )) 35 | } 36 | 37 | 38 | ); 39 | } 40 | 41 | export default NewResourceButton; 42 | -------------------------------------------------------------------------------- /src/components/common/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ErrorUI from './ErrorUI'; 3 | 4 | const NotFound = () => { 5 | return ( 6 | 7 | ) 8 | } 9 | 10 | export default NotFound; 11 | -------------------------------------------------------------------------------- /src/components/common/OpenMRSDeprecationDialog.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Dialog, DialogContent, DialogActions, Button } from '@mui/material' 3 | import DialogTitleWithCloseButton from './DialogTitleWithCloseButton'; 4 | 5 | const OpenMRSDeprecationDialog = ({ isOpen }) => { 6 | const [open, setOpen] = React.useState(isOpen || false); 7 | const onClose = () => setOpen(false); 8 | return ( 9 | 10 | 11 | The OpenMRS Dictionary Manager is going away! 12 | 13 | 14 |

    15 | The OpenMRS Dictionary Manager was deprecated as of March 2023 and is no longer available. Dictionary managers are now encouraged to use the OCL TermBrowser to manage their concept dictionaries. Your user account and data from the OpenMRS Dictionary Manager were unaffected and are available here in the OCL TermBrowser, so you can pick up right where you left off. 16 |

    17 |

    18 | For more information, see the full announcement here: Deprecating the OpenMRS Dictionary Manager and Transitioning to the OCL TermBrowser. 19 |

    20 |

    21 | If you are new to the OCL TermBrowser, we recommend reading our guide for Using OCL for OpenMRS Concept Dictionary Management. 22 |

    23 |
    24 | 25 | 26 | 27 |
    28 | ) 29 | } 30 | 31 | export default OpenMRSDeprecationDialog; 32 | -------------------------------------------------------------------------------- /src/components/common/OpenMRSLogo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const OpenMRSLogo = props => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default OpenMRSLogo; 10 | -------------------------------------------------------------------------------- /src/components/common/OrgButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Tooltip } from '@mui/material'; 3 | import { AccountBalance as HomeIcon } from '@mui/icons-material'; 4 | import { ORANGE, WHITE } from '../../common/constants'; 5 | 6 | const OrgButton = ({label, onClick, href, ...rest}) => { 7 | return ( 8 | 9 | 20 | 21 | ) 22 | } 23 | 24 | export default OrgButton; 25 | -------------------------------------------------------------------------------- /src/components/common/OwnerButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import OrgButton from './OrgButton'; 3 | import UserButton from './UserButton'; 4 | import { toOwnerURI } from '../../common/utils'; 5 | 6 | const OwnerButton = ({uri, href, ownerType, owner_type, owner, onClick , ...rest}) => { 7 | const _uri = uri ? uri : '#' + toOwnerURI(href || '') 8 | let _ownerType = (ownerType || owner_type || '').toLowerCase(); 9 | if(!_ownerType && _uri) 10 | _ownerType = _uri.match('/users/') ? 'user' : 'org'; 11 | 12 | return ( 13 | 14 | { 15 | _ownerType === 'user' ? 16 | : 17 | 18 | } 19 | 20 | ); 21 | } 22 | 23 | export default OwnerButton; 24 | -------------------------------------------------------------------------------- /src/components/common/OwnerChip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chip, Tooltip } from '@mui/material'; 3 | import { 4 | AccountBalance as HomeIcon, Person as PersonIcon 5 | } from '@mui/icons-material'; 6 | import { startCase } from 'lodash'; 7 | 8 | const OwnerChip = ({owner, ownerType, ...rest}) => { 9 | const type = ownerType || 'Organization'; 10 | const icon = (type.toLowerCase() === 'user') ? 11 | : 12 | ; 13 | 14 | return ( 15 | 16 | 17 | 18 | ) 19 | } 20 | 21 | export default OwnerChip; 22 | -------------------------------------------------------------------------------- /src/components/common/OwnerLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | AccountBalance as HomeIcon, 4 | Person as PersonIcon, 5 | } from '@mui/icons-material'; 6 | import { Button } from '@mui/material' 7 | import DynamicConfigResourceIcon from './DynamicConfigResourceIcon'; 8 | 9 | const OwnerLabel = props => { 10 | let icon = ; 11 | if(props.resource === 'user') 12 | icon = ; 13 | 14 | return ( 15 |
    16 | 17 | {icon} 18 | {props.id} 19 | 20 | { 21 | name && 22 | 23 | {props.name} 24 | 25 | } 26 |
    27 | ) 28 | } 29 | 30 | export default OwnerLabel; 31 | 32 | 33 | export const OwnerIdLabel = ({resource, id, bgColor}) => { 34 | let _bgColor = bgColor || 'orange' 35 | 36 | return ( 37 | 38 | 39 | 40 | 41 | {id} 42 | 43 | ) 44 | } 45 | 46 | export const ResourceTextButton = ({resource, id, color, href}) => { 47 | let _color = color || 'orange' 48 | 49 | return ( 50 | 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /src/components/common/PasswordValidatorIndicator.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTranslation } from 'react-i18next' 3 | import { 4 | CheckCircle as CorrectIcon, Cancel as WrongIcon 5 | } from '@mui/icons-material'; 6 | import { ERROR_RED, BLUE } from '../../common/constants'; 7 | import { merge } from 'lodash'; 8 | 9 | const correctIcon = ; 10 | const incorrectIcon = ; 11 | const Indicator = (predicate, label) => { 12 | const commonSpanStyles = {marginLeft: '5px'} 13 | const getStyles = predicate => predicate ? {color: BLUE} : {color: ERROR_RED}; 14 | 15 | return ( 16 |
    17 | { 18 | predicate ? correctIcon : incorrectIcon 19 | } 20 | 21 | {label} 22 | 23 |
    24 | )} 25 | 26 | const PasswordValidatorIndicator = ({password, strength, minStrength, minStrengthLabel}) => { 27 | const hasMinLength = Boolean(password && password.length >= 8) 28 | const hasNumber = Boolean(password && password.match(new RegExp(/[0-9]/g))) 29 | const hasAlphabet = Boolean(password && password.match(new RegExp(/[a-zA-Z]/g))) 30 | const isMinStrength = strength >= minStrength 31 | const { t } = useTranslation() 32 | 33 | return ( 34 | 35 |
    36 | { 37 | Indicator(hasMinLength, t('user.auth.password_length_error')) 38 | } 39 | { 40 | Indicator(hasNumber, t('user.auth.password_number_error')) 41 | } 42 | { 43 | Indicator(hasAlphabet, t('user.auth.password_alpha_error')) 44 | } 45 | { 46 | minStrength && Indicator(isMinStrength, t('user.auth.password_strength_error', {strength: minStrengthLabel})) 47 | } 48 |
    49 |
    50 | ) 51 | } 52 | 53 | export default PasswordValidatorIndicator; 54 | -------------------------------------------------------------------------------- /src/components/common/PermissionDenied.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ErrorUI from './ErrorUI'; 3 | 4 | const PermissionDenied = () => { 5 | return ( 6 | 10 | ) 11 | } 12 | 13 | export default PermissionDenied; 14 | -------------------------------------------------------------------------------- /src/components/common/PinIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon } from '@mui/material'; 3 | 4 | const PinIcon = props => { 5 | let classes = 'pin-icon ' + (props.className || ''); 6 | if(props.pinned) 7 | classes += ' pinned' 8 | return ( 9 | 10 | 11 | 12 | ) 13 | } 14 | 15 | export default PinIcon; 16 | -------------------------------------------------------------------------------- /src/components/common/PlusSquareIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SvgIcon } from '@mui/material'; 3 | 4 | const PlusSquareIcon = props => { 5 | return ( 6 | 7 | {/* tslint:disable-next-line: max-line-length */} 8 | 9 | 10 | ); 11 | } 12 | 13 | export default PlusSquareIcon 14 | -------------------------------------------------------------------------------- /src/components/common/PopperGrow.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Paper, Popper, Grow, ClickAwayListener 4 | } from '@mui/material'; 5 | 6 | const PopperGrow = ({open, anchorRef, handleClose, children, minWidth}) => { 7 | return ( 8 | 9 | {({ TransitionProps, placement }) => ( 10 | 16 | 17 | 18 | {children} 19 | 20 | 21 | 22 | )} 23 | 24 | 25 | ) 26 | } 27 | 28 | export default PopperGrow; 29 | -------------------------------------------------------------------------------- /src/components/common/ProcessingChip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chip, Tooltip } from '@mui/material'; 3 | import { Timer as ProcessingIcon } from '@mui/icons-material'; 4 | import { ORANGE } from '../../common/constants'; 5 | 6 | const ProcessingChip = props => { 7 | const icon = ; 8 | 9 | return ( 10 | 11 | 12 | 13 | ) 14 | } 15 | 16 | export default ProcessingChip; 17 | -------------------------------------------------------------------------------- /src/components/common/RTEditor.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactQuill from 'react-quill'; 3 | import 'react-quill/dist/quill.snow.css'; 4 | 5 | class RTEditor extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | value: props.defaultValue || '', 10 | } 11 | } 12 | 13 | componentDidUpdate(prevProps) { 14 | if(!prevProps.defaultValue && this.props.defaultValue) 15 | this.setState({value: this.props.defaultValue}) 16 | } 17 | 18 | onChange = value => this.setState({value: value}, () => this.props.onChange(value)) 19 | 20 | render() { 21 | const { value } = this.state; 22 | const { label, placeholder } = this.props 23 | return ( 24 | 25 | { 26 | label && 27 |
    28 |

    {label}

    29 |
    30 | } 31 |
    32 | 39 |
    40 |
    41 | ) 42 | } 43 | } 44 | 45 | RTEditor.modules = { 46 | toolbar: [ 47 | [{ 'header': '1'}, {'header': '2'}, { 'font': [] }], 48 | ['bold', 'italic', 'underline', 'strike', 'blockquote'], 49 | [{'list': 'ordered'}, {'list': 'bullet'}, {'indent': '-1'}, {'indent': '+1'}], 50 | ['link',], 51 | ['clean'], 52 | [{ 'align': [] }] 53 | ], 54 | clipboard: { 55 | // toggle to add extra line breaks when pasting HTML: 56 | matchVisual: false, 57 | } 58 | }; 59 | /* 60 | * Quill editor formats 61 | * See https://quilljs.com/docs/formats/ 62 | */ 63 | RTEditor.formats = [ 64 | 'header', 'font', 'size', 65 | 'bold', 'italic', 'underline', 'strike', 'blockquote', 66 | 'list', 'bullet', 'indent', 67 | 'link', 'image', 'video' 68 | ]; 69 | 70 | export default RTEditor; 71 | -------------------------------------------------------------------------------- /src/components/common/ReferenceChip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chip } from '@mui/material'; 3 | import { 4 | LocalOffer as LocalOfferIcon, Link as LinkIcon, 5 | } from '@mui/icons-material'; 6 | import { toFullAPIURL, copyURL } from '../../common/utils'; 7 | 8 | const ReferenceChip = props => { 9 | const isReference = !props.notReference 10 | const isResolved = Boolean(props.last_resolved_at) 11 | const type = props.reference_type; 12 | const expression = isResolved ? props.expression : `${props.expression} (unresolved)`; 13 | let icon = ; 14 | if(type && type.toLowerCase() === 'mappings') 15 | icon = ; 16 | else if(type && type.toLowerCase() === 'concepts') 17 | icon = ; 18 | 19 | const chip = 27 | return ( 28 | 29 | 30 | {chip} 31 | 32 | { 33 | isReference && ( 34 | props.include ? 35 | : 36 | 37 | ) 38 | } 39 | { 40 | isReference && 41 | copyURL(toFullAPIURL(props.expression))} style={{marginLeft: '5px'}} /> 42 | 43 | } 44 | 45 | ) 46 | } 47 | 48 | export default ReferenceChip; 49 | -------------------------------------------------------------------------------- /src/components/common/ReferenceTranslation.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chip } from '@mui/material'; 3 | import { 4 | LocalOffer as LocalOfferIcon, Link as LinkIcon, 5 | } from '@mui/icons-material'; 6 | import { toFullAPIURL, copyURL } from '../../common/utils'; 7 | 8 | const ReferenceTranslation = props => { 9 | const isReference = !props.notReference 10 | const isResolved = Boolean(props.last_resolved_at) 11 | const type = props.reference_type; 12 | const expression = isResolved ? props.translation : `${props.translation} (unresolved)`; 13 | let icon = ; 14 | if(type && type.toLowerCase() === 'mappings') 15 | icon = ; 16 | else if(type && type.toLowerCase() === 'concepts') 17 | icon = ; 18 | 19 | const chip = 28 | return ( 29 | 30 | 31 | {chip} 32 | 33 | { 34 | isReference && 35 | copyURL(toFullAPIURL(props.expression))} style={{marginLeft: '5px'}} /> 36 | 37 | } 38 | 39 | ) 40 | } 41 | 42 | export default ReferenceTranslation; 43 | -------------------------------------------------------------------------------- /src/components/common/ReleasedChip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chip, Tooltip } from '@mui/material'; 3 | import { 4 | NewReleases as ReleaseIcon 5 | } from '@mui/icons-material'; 6 | 7 | const ReleasedChip = props => { 8 | const icon = ; 9 | 10 | return ( 11 | 12 | 13 | 14 | ) 15 | } 16 | 17 | export default ReleasedChip; 18 | -------------------------------------------------------------------------------- /src/components/common/RepoVersionLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const RepoVersionLabel = ({version, href, onClick}) => ( 4 | 5 | 6 | {version} 7 | 8 | 9 | ) 10 | 11 | export default RepoVersionLabel; 12 | -------------------------------------------------------------------------------- /src/components/common/ResourceLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ChevronRight as SeparatorIcon, 4 | } from '@mui/icons-material'; 5 | 6 | const SEPARATOR = () 7 | const ResourceLabel = props => { 8 | const { noSeparator, searchable } = props; 9 | 10 | return ( 11 |
    12 | 13 | {props.icon} 14 | { 15 | props.owner && 16 | {props.owner} 17 | } 18 | { 19 | props.owner && props.parent && 20 | {SEPARATOR} 21 | } 22 | { 23 | props.parent && 24 | {props.parent} 25 | } 26 | { 27 | (!props.owner && !props.parent && props.parentURL) && 28 | {props.parentURL} 29 | } 30 | { 31 | !noSeparator && 32 | {SEPARATOR} 33 | } 34 | {props.id || props.name} 35 | 36 | 37 | {props.name} 38 | 39 |
    40 | ) 41 | } 42 | 43 | export default ResourceLabel; 44 | -------------------------------------------------------------------------------- /src/components/common/ResourceLabelVertical.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { includes } from 'lodash'; 3 | import { 4 | ChevronRight as SeparatorIcon, 5 | } from '@mui/icons-material'; 6 | 7 | const SEPARATOR = () 8 | const ResourceLabelVertical = props => { 9 | const { resource } = props; 10 | const isSourceChild = includes(['concept', 'mapping'], resource); 11 | const parentTextStyles = { 12 | width: '100%', 13 | fontSize: '10px', 14 | background: '#eee', 15 | padding: '2px 5px', 16 | textAlign: 'center', 17 | borderRadius: '2px', 18 | overflowX: 'auto', 19 | } 20 | const nameTextStyles = { 21 | color: 'white', 22 | padding: '0 5px', 23 | borderRadius: '2px', 24 | minHeight: '18px', 25 | } 26 | 27 | return ( 28 |
    29 |
    30 | {props.icon} 31 |
    32 |
    33 |
    34 | { 35 | props.owner && 36 | {props.owner} 37 | } 38 | { 39 | props.owner && props.parent && 40 | {SEPARATOR} 41 | } 42 | { 43 | props.parent && 44 | {props.parent} 45 | } 46 | { 47 | (!props.owner && !props.parent && props.parentURL) && 48 | {props.parentURL} 49 | } 50 | {SEPARATOR} 51 | {props.id || props.name} 52 |
    53 |
    54 | {props.name} 55 |
    56 |
    57 |
    58 | ) 59 | } 60 | 61 | export default ResourceLabelVertical; 62 | -------------------------------------------------------------------------------- /src/components/common/ResourceReferences.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Accordion, AccordionSummary, AccordionDetails, List, ListItem, Tooltip, Chip 4 | } from '@mui/material'; 5 | import { 6 | InfoOutlined as InfoIcon, 7 | } from '@mui/icons-material' 8 | import { get, map } from 'lodash'; 9 | import { toFullAPIURL } from '../../common/utils'; 10 | import TabCountLabel from './TabCountLabel' 11 | 12 | const ResourceReferences = ({headingStyles, detailStyles, references, resource}) => { 13 | const count = get(references, 'length', 0) 14 | return ( 15 | 16 | } 19 | aria-controls="panel1a-content" 20 | style={{cursor: 'inherit'}} 21 | > 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | { 36 | map(references, reference => ( 37 | 38 | {reference.expression} 39 | window.open(toFullAPIURL(reference.uri))} /> 40 | 41 | )) 42 | } 43 | 44 | 45 | 46 | ) 47 | } 48 | 49 | export default ResourceReferences; 50 | -------------------------------------------------------------------------------- /src/components/common/ResourceTextBreadcrumbs.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ChevronRight as SeparatorIcon, 4 | } from '@mui/icons-material'; 5 | import { ResourceTextButton } from '../common/OwnerLabel'; 6 | import { toParentURI, toOwnerURI } from '../../common/utils'; 7 | import { BLUE, GREEN, ORANGE } from '../../common/constants'; 8 | 9 | const ResourceTextBreadcrumbs = ({ resource, style, includeSelf }) => { 10 | return ( 11 |
    12 | 13 | 14 | 15 | { 16 | includeSelf && 17 | 18 | 19 | 20 | { 21 | (resource.uuid !== resource.versioned_object_id?.toString() || resource.is_latest_version) && 22 | 23 | 24 | 25 | 26 | } 27 | 28 | } 29 |
    30 | 31 | ) 32 | } 33 | 34 | export default ResourceTextBreadcrumbs; 35 | -------------------------------------------------------------------------------- /src/components/common/ResourceVersionLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | AccountTreeRounded as TreeIcon, 4 | AspectRatio as ExpansionIcon, 5 | ChevronRight as SeparatorIcon, 6 | } from '@mui/icons-material'; 7 | import { Tooltip } from '@mui/material'; 8 | import ReleasedChip from './ReleasedChip'; 9 | import RetiredChip from './RetiredChip'; 10 | import ProcessingChip from './ProcessingChip'; 11 | import ExpansionChip from './ExpansionChip'; 12 | import AccessChip from './AccessChip'; 13 | import { GREEN } from '../../common/constants'; 14 | 15 | const SEPARATOR = () 16 | const ResourceVersionLabel = props => { 17 | const gridClass = props.gridClass || 'col-sm-12' 18 | return ( 19 |
    20 | 21 | 22 | 23 | 24 | {props.owner} 25 | {SEPARATOR} 26 | {props.short_code} 27 | {SEPARATOR} 28 | [{props.version}] 29 | 30 | { 31 | props.showAccess && props.public_access && 32 | 33 | 34 | 35 | } 36 | { 37 | props.released && 38 | 39 | 40 | 41 | } 42 | { 43 | props.retired && 44 | 45 | 46 | 47 | } 48 | { 49 | props.is_processing && 50 | 51 | 52 | 53 | } 54 | { 55 | props.includeExpanded && props.autoexpand && 56 | 57 | 58 | 59 | } 60 | { 61 | props.includeExpansionIcon && props.autoexpand && 62 | 63 | 64 | 65 | 66 | 67 | } 68 |
    69 | ) 70 | } 71 | 72 | export default ResourceVersionLabel; 73 | -------------------------------------------------------------------------------- /src/components/common/ResponsiveDrawer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from "react"; 2 | import makeStyles from '@mui/styles/makeStyles'; 3 | import Drawer from "@mui/material/Drawer"; 4 | import DragIcon from '@mui/icons-material/MoreVert'; 5 | import { merge } from 'lodash'; 6 | 7 | const defaultWidth = 360; 8 | const minWidth = 50; 9 | const maxWidth = 1000; 10 | 11 | const useStyles = makeStyles(theme => ({ 12 | drawer: { 13 | flexShrink: 0 14 | }, 15 | toolbar: theme.mixins.toolbar, 16 | dragger: { 17 | width: "8px", 18 | cursor: "ew-resize", 19 | padding: "4px 0 0", 20 | borderTop: "1px solid #ddd", 21 | position: "fixed", 22 | top: '64px', 23 | bottom: 0, 24 | zIndex: 100, 25 | backgroundColor: "#f1f1f1", 26 | height: '1000px' 27 | } 28 | })); 29 | 30 | const ResponsiveDrawer = ({formComponent, variant, isOpen, onClose, onWidthChange, width, paperStyle, noToolbar}) => { 31 | const [open, setOpen] = React.useState(isOpen); 32 | const classes = useStyles(); 33 | const [drawerWidth, setDrawerWidth] = React.useState(width || defaultWidth); 34 | 35 | const handleMouseDown = () => { 36 | document.addEventListener("mouseup", handleMouseUp, true); 37 | document.addEventListener("mousemove", handleMouseMove, true); 38 | }; 39 | 40 | const handleMouseUp = () => { 41 | document.removeEventListener("mouseup", handleMouseUp, true); 42 | document.removeEventListener("mousemove", handleMouseMove, true); 43 | }; 44 | 45 | const handleMouseMove = useCallback(e => { 46 | let offsetRight = 47 | document.body.offsetWidth - (e.clientX - document.body.offsetLeft); 48 | if (offsetRight > minWidth && offsetRight < maxWidth) { 49 | setDrawerWidth(offsetRight) 50 | if(onWidthChange) 51 | onWidthChange(offsetRight) 52 | } 53 | 54 | }, []); 55 | 56 | React.useEffect(() => setOpen(isOpen), [isOpen]) 57 | React.useEffect(() => setDrawerWidth(width || defaultWidth), [width]) 58 | 59 | return ( 60 | 68 | { 69 | !noToolbar && 70 |
    71 | } 72 |
    handleMouseDown(e)} className={classes.dragger + ' flex-vertical-center vertical-dragger'}> 73 | 74 |
    75 | { 76 | formComponent 77 | } 78 | 79 | ); 80 | } 81 | 82 | export default ResponsiveDrawer; 83 | -------------------------------------------------------------------------------- /src/components/common/ResultsCountDropDown.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tooltip, Chip, MenuItem, Menu } from '@mui/material' 3 | import { FormatListNumberedRtl as NumberListIcon } from '@mui/icons-material' 4 | import { map } from 'lodash'; 5 | import { DEFAULT_LIMIT } from '../../common/constants'; 6 | 7 | 8 | const OPTIONS = [ 9 | {id: '25', count: 25}, 10 | {id: '50', count: 50}, 11 | {id: '100', count: 100}, 12 | ] 13 | 14 | class ResultsCountDropDown extends React.Component { 15 | constructor(props) { 16 | super(props); 17 | this.state = { 18 | open: false, 19 | limit: DEFAULT_LIMIT, 20 | anchorEl: null, 21 | } 22 | } 23 | 24 | onSetCount = limit => { 25 | let _limit = parseInt(limit) || DEFAULT_LIMIT; 26 | if(_limit !== this.state.limit) 27 | this.setState({limit: _limit}, () => { 28 | this.toggleOpen(); 29 | this.props.onChange(_limit); 30 | }) 31 | } 32 | 33 | toggleOpen = event => { 34 | const newOpen = !this.state.open 35 | this.setState({open: newOpen, anchorEl: newOpen ? event.currentTarget : null}) 36 | } 37 | 38 | componentDidMount() { 39 | this.setDefaultLimitFromParent() 40 | } 41 | 42 | componentDidUpdate() { 43 | this.setDefaultLimitFromParent() 44 | } 45 | 46 | setDefaultLimitFromParent() { 47 | const { defaultLimit } = this.props; 48 | if(defaultLimit && defaultLimit !== this.state.limit) 49 | this.setState({limit: defaultLimit}) 50 | } 51 | 52 | render() { 53 | const { size } = this.props; 54 | const { limit, anchorEl } = this.state; 55 | return ( 56 | 57 | 58 | } 62 | label={`Page Size : ${limit}`} 63 | onClick={this.toggleOpen} 64 | size={size || 'medium'} 65 | style={{minWidth: '80px'}} 66 | /> 67 | 68 | 75 | { 76 | map(OPTIONS, option => ( 77 | this.onSetCount(option.count)}> 78 | {option.id} 79 | 80 | )) 81 | } 82 | 83 | 84 | ) 85 | } 86 | } 87 | 88 | export default ResultsCountDropDown; 89 | -------------------------------------------------------------------------------- /src/components/common/RetiredChip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chip, Tooltip } from '@mui/material'; 3 | import { Block as RetireIcon } from '@mui/icons-material'; 4 | import { RED } from '../../common/constants'; 5 | 6 | const RetiredChip = props => { 7 | const icon = ; 8 | 9 | return ( 10 | 11 | 12 | 13 | ) 14 | } 15 | 16 | export default RetiredChip; 17 | -------------------------------------------------------------------------------- /src/components/common/ServerConfigsChip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Chip, Tooltip, Menu 4 | } from '@mui/material'; 5 | import { 6 | ExpandLess as ExpandLessIcon, 7 | ExpandMore as ExpandMoreIcon, 8 | } from '@mui/icons-material'; 9 | import { getAppliedServerConfig } from '../../common/utils'; 10 | import ServerConfigList from './ServerConfigList'; 11 | 12 | const ServerConfigsChip = props => { 13 | const anchorRef = React.useRef(null); 14 | const [open, setOpen] = React.useState(false); 15 | const applied = getAppliedServerConfig(); 16 | const icon = 17 | 18 | return ( 19 | 20 | 21 | : 28 | } 29 | onDelete={() => setOpen(!open)} 30 | onClick={() => setOpen(!open)} 31 | ref={anchorRef} 32 | {...props} 33 | /> 34 | 35 | { 36 | open && 37 | setOpen(false)} style={{marginTop: '35px'}}> 38 | setOpen(false)} /> 39 | 40 | } 41 | 42 | ) 43 | } 44 | 45 | export default ServerConfigsChip; 46 | -------------------------------------------------------------------------------- /src/components/common/SourceChildVersionAssociationWithContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { map, isEmpty, compact, values } from 'lodash'; 3 | import ConceptContainerLabel from './ConceptContainerLabel'; 4 | 5 | const SourceChildVersionAssociationWithContainer = ({ associatedWith, resource, style }) => { 6 | const getResourceDetails = uri => { 7 | const parts = compact(uri.split('/')) 8 | return { 9 | ownerType: parts[0], 10 | owner: parts[1], 11 | id: parts[3], 12 | version: parts[4], 13 | uri: uri 14 | } 15 | } 16 | 17 | const isPresent = associatedWith && (!isEmpty(associatedWith.source) || !isEmpty(associatedWith.collection)) 18 | const count = values(associatedWith.source).length + values(associatedWith.collection).length 19 | 20 | return ( 21 | 22 | { 23 | isPresent && 24 |
    25 | { 26 | resource && 27 |
    28 | {`This ${resource} version is referenced in the following ${count} source and collection versions:`} 29 |
    30 | } 31 | { 32 | map(associatedWith.source, uri => ( 33 |
    34 | 35 |
    36 | )) 37 | } 38 | { 39 | map(associatedWith.collection, uri => { 40 | return ( 41 |
    42 | 43 |
    44 | ) 45 | }) 46 | } 47 |
    48 | } 49 |
    50 | ) 51 | } 52 | 53 | export default SourceChildVersionAssociationWithContainer; 54 | -------------------------------------------------------------------------------- /src/components/common/Split.scss: -------------------------------------------------------------------------------- 1 | .split { 2 | height: 100vh; 3 | } 4 | 5 | .split > div { 6 | float: left; 7 | min-height: 700px; 8 | height: auto; 9 | } 10 | 11 | .gutter { 12 | background-color: #eee; 13 | background-repeat: no-repeat; 14 | background-position: 50%; 15 | width: 5px !important; 16 | } 17 | 18 | .gutter.gutter-horizontal { 19 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); 20 | cursor: col-resize; 21 | } 22 | -------------------------------------------------------------------------------- /src/components/common/SupportedLocales.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tooltip } from '@mui/material'; 3 | import { map } from 'lodash'; 4 | 5 | const SupportedLocales = ({default_locale, supported_locales}) => { 6 | return ( 7 | 8 | 9 | {default_locale} 10 | 11 | { 12 | map(supported_locales, locale => { 13 | if(locale !== default_locale) { 14 | return ( 15 | , {locale} 16 | ) 17 | } 18 | }) 19 | } 20 | 21 | ) 22 | } 23 | 24 | export default SupportedLocales; 25 | -------------------------------------------------------------------------------- /src/components/common/TabCountLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BLUE, WHITE } from '../../common/constants'; 3 | import { isNumber } from 'lodash'; 4 | 5 | const TabCountLabel = ({label, count, style, color}) => { 6 | if(!isNumber(count)) 7 | return {label}; 8 | 9 | return ( 10 | 11 | {label} 12 | 13 | {count.toLocaleString()} 14 | 15 | 16 | ) 17 | } 18 | 19 | export default TabCountLabel; 20 | -------------------------------------------------------------------------------- /src/components/common/ThisConceptLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ThisConceptLabel = () => { 4 | return ( 5 |
    6 | 7 | 8 | THIS CONCEPT 9 | 10 | 11 |
    12 | ) 13 | } 14 | 15 | export default ThisConceptLabel; 16 | -------------------------------------------------------------------------------- /src/components/common/Tip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Card, CardHeader, CardContent 4 | } from '@mui/material'; 5 | import { 6 | Highlight as HighlightIcon 7 | } from '@mui/icons-material'; 8 | 9 | const Tip = ({ content }) => { 10 | return ( 11 | 12 | } title='Tip' /> 13 | 14 | {content} 15 | 16 | 17 | ) 18 | }; 19 | 20 | export default Tip; 21 | -------------------------------------------------------------------------------- /src/components/common/UserButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Tooltip } from '@mui/material'; 3 | import { Person as PersonIcon } from '@mui/icons-material'; 4 | import { ORANGE, WHITE } from '../../common/constants'; 5 | 6 | const UserButton = ({label, onClick, href, ...rest}) => { 7 | return ( 8 | 9 | 20 | 21 | ) 22 | } 23 | 24 | export default UserButton; 25 | -------------------------------------------------------------------------------- /src/components/common/VersionButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from '@mui/material'; 3 | import { 4 | AccountTreeRounded as TreeIcon 5 | } from '@mui/icons-material'; 6 | import { BLUE, WHITE, RED, BLACK } from '../../common/constants'; 7 | 8 | const VersionButton = ({label, onClick, retired, href, bgColor, textColor}) => { 9 | let backgroundColor = bgColor || BLUE; 10 | let txtColor = textColor || WHITE; 11 | const style = retired ? 12 | {background: 'lightgray', color: RED, boxShadow: 'none', textDecoration: 'line-through', textDecorationColor: BLACK, textTransform: 'none'} : 13 | {background: backgroundColor, color: txtColor, boxShadow: 'none', textTransform: 'none'}; 14 | return ( 15 | 23 | ) 24 | } 25 | 26 | export default VersionButton; 27 | -------------------------------------------------------------------------------- /src/components/common/VersionFilter.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chip, MenuItem, Menu, Tooltip } from '@mui/material'; 3 | import { AccountTreeRounded as TreeIcon, ArrowDropDown as ArrowDropDownIcon } from '@mui/icons-material'; 4 | import { map, without } from 'lodash'; 5 | 6 | const VersionFilter = props => { 7 | const { size, onChange, selected, versions } = props; 8 | const [anchorEl, setAnchorEl] = React.useState(null); 9 | 10 | const onOpen = event => { 11 | versions && setAnchorEl(event.currentTarget); 12 | } 13 | 14 | const onClose = () => { 15 | setAnchorEl(null) 16 | } 17 | 18 | const onSelect = version => { 19 | onChange(version) 20 | onClose(null) 21 | } 22 | 23 | return ( 24 | 25 | 26 | } 30 | label={selected} 31 | onClick={onOpen} 32 | size={size || 'medium'} 33 | style={{minWidth: '80px'}} 34 | deleteIcon={} 35 | onDelete={onOpen} 36 | /> 37 | 38 | 45 | onSelect('HEAD')}> 46 | HEAD 47 | 48 | { 49 | map(without(versions, 'HEAD'), version => ( 50 | onSelect(version)}> 51 | {version} 52 | 53 | )) 54 | } 55 | 56 | 57 | ); 58 | } 59 | 60 | export default VersionFilter; 61 | -------------------------------------------------------------------------------- /src/components/common/conceptContainerFormComponents/AboutPage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CommonAccordion from '../../common/CommonAccordion'; 3 | import RTEditor from '../../common/RTEditor'; 4 | import TabCountLabel from '../TabCountLabel'; 5 | 6 | 7 | const AboutPage = props => { 8 | const configs = props.advanceSettings.about 9 | const [text, setText] = React.useState('') 10 | const onChange = value => { 11 | setText(value) 12 | props.onChange({text: value}) 13 | } 14 | const defaultExpanded = Boolean(props.edit && props.repo.text) 15 | 16 | React.useEffect(() => props.edit && setText(props.repo.text || ''), []) 17 | 18 | return ( 19 | 23 | 24 | 25 | } 26 | subTitle={configs.subTitle} 27 | defaultExpanded={defaultExpanded}> 28 |
    29 | 34 |
    35 |
    36 | ) 37 | } 38 | 39 | export default AboutPage; 40 | -------------------------------------------------------------------------------- /src/components/common/conceptContainerFormComponents/AdvanceSettings.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FHIRSettings from './FHIRSettings'; 3 | import ResourceIDAssignmentSettings from './ResourceIDAssignmentSettings' 4 | import CustomAttributes from './CustomAttributes' 5 | import AboutPage from './AboutPage' 6 | import Others from './Others'; 7 | 8 | const AdvanceSettings = props => { 9 | const configs = props.advanceSettings 10 | return ( 11 |
    12 |
    13 |

    {configs.title}

    14 |
    15 | { configs.assigningIds && } 16 | { configs.fhirSettings && } 17 | 18 | 19 | { configs.others && } 20 |
    21 | ) 22 | } 23 | 24 | export default AdvanceSettings 25 | -------------------------------------------------------------------------------- /src/components/common/conceptContainerFormComponents/FormHeader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DynamicConfigResourceIcon from '../../common/DynamicConfigResourceIcon'; 3 | import { WHITE } from '../../../common/constants' 4 | 5 | const FormHeader = props => { 6 | const iconMarginTop = props.resource === 'source' ? '8px' : '20px' 7 | return ( 8 |
    9 | 10 | 11 | 12 | 13 |
    14 |

    {props.edit ? props.editTitle : props.title}

    15 |
    16 |
    17 | {props.subTitle} 18 |
    19 |
    20 |
    21 | ) 22 | } 23 | 24 | export default FormHeader; 25 | -------------------------------------------------------------------------------- /src/components/concepts/ConceptDetailsLocale.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Flag as FlagIcon, FileCopy as CopyIcon } from '@mui/icons-material'; 3 | import { Chip, Tooltip, IconButton } from '@mui/material'; 4 | import { get } from 'lodash'; 5 | import ExternalIdLabel from '../common/ExternalIdLabel'; 6 | import { toFullAPIURL, copyURL } from '../../common/utils'; 7 | 8 | 9 | const ConceptDetailsLocale = ({ locale, isDescription, url }) => { 10 | const typeAttr = isDescription ? 'description_type' : 'name_type' 11 | const nameAttr = isDescription ? 'description' : 'name' 12 | const type = get(locale, typeAttr); 13 | const onCopyClick = () => copyURL(toFullAPIURL(url)) 14 | 15 | return ( 16 |
    17 |
    18 |
    19 | { get(locale, nameAttr) } 20 | { 21 | type && type !== 'None' && 22 | 23 | 24 | 25 | } 26 | {`[${locale.locale}]`} 27 | { 28 | locale.locale_preferred && 29 | 30 | 31 | 32 | 33 | 34 | } 35 |
    36 | { 37 | locale.external_id && 38 | 39 | } 40 |
    41 |
    42 | 43 | 44 | 45 | 46 | 47 |
    48 |
    49 | ); 50 | } 51 | 52 | export default ConceptDetailsLocale; 53 | -------------------------------------------------------------------------------- /src/components/concepts/ConceptDisplayName.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ConceptDisplayName = ({ concept, style }) => { 4 | let synonymPrefix = '' 5 | const highlights = concept?.search_meta?.search_highlight 6 | const synonymHighlight = highlights?.synonyms 7 | const nameHighlight = highlights?.name 8 | if(!nameHighlight?.length && synonymHighlight?.length) 9 | synonymPrefix = synonymHighlight[0].replace('', "").replace('', '') 10 | return ( 11 | 12 | 13 | { 14 | synonymPrefix && 15 | 16 | 17 | 18 | 19 | } 20 | {concept.display_name} 21 | 22 | { 23 | concept.display_locale && 24 | 25 | [{concept.display_locale}] 26 | 27 | } 28 | 29 | ) 30 | } 31 | 32 | export default ConceptDisplayName; 33 | -------------------------------------------------------------------------------- /src/components/concepts/ConceptIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | LocalOfferOutlined as LocalOfferIcon 4 | } from '@mui/icons-material'; 5 | import { Tooltip } from '@mui/material'; 6 | import { merge } from 'lodash'; 7 | import { toFullAPIURL, copyURL } from '../../common/utils'; 8 | 9 | const ConceptIcon = ({ url, shrink, style }) => { 10 | const onIconClick = () => copyURL(toFullAPIURL(url)) 11 | const classes = 'no-side-padding col-xs-1 home-icon concept flex-vertical-center' + (shrink ? ' small' : '') 12 | 13 | return ( 14 |
    15 | { 16 | url ? 17 | 18 | 19 | : 20 | 21 | } 22 |
    23 | ); 24 | } 25 | 26 | export default ConceptIcon; 27 | -------------------------------------------------------------------------------- /src/components/concepts/d3Tree.scss: -------------------------------------------------------------------------------- 1 | .d3-tip { 2 | line-height: 1.4; 3 | padding: 10px; 4 | pointer-events: none !important; 5 | color: #203d5d; 6 | box-shadow: 0 4px 20px 4px rgba(0, 20, 60, .1), 0 4px 80px -8px rgba(0, 20, 60, .2); 7 | background-color: #fff; 8 | border-radius: 4px; 9 | z-index: 10000; 10 | } 11 | 12 | /* Creates a small triangle extender for the tooltip */ 13 | .d3-tip:after { 14 | box-sizing: border-box; 15 | display: inline; 16 | font-size: 10px; 17 | width: 100%; 18 | line-height: 1; 19 | color: #fff; 20 | position: absolute; 21 | pointer-events: none; 22 | } 23 | 24 | /* Northward tooltips */ 25 | .d3-tip.n:after { 26 | content: "▼"; 27 | margin: -1px 0 0 0; 28 | top: 100%; 29 | left: 0; 30 | text-align: center; 31 | } 32 | 33 | /* Eastward tooltips */ 34 | .d3-tip.e:after { 35 | content: "◀"; 36 | margin: -4px 0 0 0; 37 | top: 50%; 38 | left: -8px; 39 | } 40 | 41 | /* Southward tooltips */ 42 | .d3-tip.s:after { 43 | content: "▲"; 44 | margin: 0 0 1px 0; 45 | top: -8px; 46 | left: 0; 47 | text-align: center; 48 | } 49 | 50 | /* Westward tooltips */ 51 | .d3-tip.w:after { 52 | content: "▶"; 53 | margin: -4px 0 0 -1px; 54 | top: 50%; 55 | left: 100%; 56 | } 57 | -------------------------------------------------------------------------------- /src/components/fhir/Contact.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Divider } from '@mui/material' 3 | import { isEmpty, map } from 'lodash'; 4 | 5 | const Contact = ({name, telecom}) => { 6 | const getHREF = (system, value) => { 7 | if(system === 'url') 8 | return value 9 | if(system === 'email') 10 | return `mailto:${value}` 11 | if(system === 'phone') 12 | return `tel:${value}` 13 | 14 | } 15 | 16 | return ( 17 | 18 | { 19 | name && 20 | {name} 21 | } 22 | { 23 | !isEmpty(telecom) && 24 | map( 25 | telecom, 26 | (com, index) => ( 27 | 28 | 29 | {com.value} 30 | 31 | { 32 | (index !== telecom.length - 1) && 33 | 34 | } 35 | 36 | 37 | ) 38 | ) 39 | } 40 | 41 | 42 | ) 43 | } 44 | 45 | export default Contact; 46 | -------------------------------------------------------------------------------- /src/components/fhir/ContainerResource.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { merge, get, map, isArray } from 'lodash'; 3 | import HeaderAttribute from '../common/HeaderAttribute'; 4 | import Contact from './Contact'; 5 | 6 | const ContainerResource = props => { 7 | const contact = isArray(props.resource.contact) ? 8 | ( 9 | 10 | { 11 | map( 12 | props.resource.contact, 13 | (contact, index) => 14 | ) 15 | } 16 | 17 | ) : 18 | props.resource.contact; 19 | 20 | return ( 21 |
    22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
    30 | ) 31 | } 32 | 33 | export default ContainerResource; 34 | -------------------------------------------------------------------------------- /src/components/fhir/FhirTabs.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tabs, Tab } from '@mui/material'; 3 | import { map } from 'lodash'; 4 | import OrgHomeChildrenList from '../orgs/OrgHomeChildrenList'; 5 | import { DEFAULT_LIMIT } from '../../common/constants'; 6 | 7 | 8 | const FhirTabs = ({ 9 | tab, onTabChange, selectedConfig, org, location, match, url, limit, hapi, nested, paginationParams, searchMode 10 | }) => { 11 | const tabConfigs = selectedConfig.config.tabs; 12 | const selectedTabConfig = tabConfigs[tab]; 13 | const getTABHref = config => { 14 | if(nested) 15 | return `#/fhir${url}${config.type}${location.search}` 16 | return `#/fhir/${config.type}${location.search}` 17 | }; 18 | 19 | return ( 20 |
    21 | 22 | { 23 | map(tabConfigs, config => ) 24 | } 25 | 26 |
    27 | 54 |
    55 |
    56 | ) 57 | } 58 | 59 | export default FhirTabs; 60 | -------------------------------------------------------------------------------- /src/components/imports/ImportInfo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Task = props => { 4 | return ( 5 |
    6 | { 7 | JSON.stringify(props, undefined, 2) 8 | } 9 |
    10 | ) 11 | } 12 | 13 | export default Task; 14 | 15 | -------------------------------------------------------------------------------- /src/components/imports/TaskIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { getTaskIconDetails } from './utils'; 3 | 4 | const TaskIcon = ({status, ...rest}) => ( 5 | 6 | {getTaskIconDetails(status, rest).icon} 7 | 8 | ) 9 | 10 | export default TaskIcon 11 | -------------------------------------------------------------------------------- /src/components/imports/Tasks.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Tasks.scss'; 3 | import { CircularProgress } from '@mui/material'; 4 | import { get, map, isEmpty } from 'lodash'; 5 | import Task from './Task' 6 | 7 | const Tasks = ({ tasks, isLoading, error, onRevoke, onDownload }) => { 8 | const [open, setOpen] = React.useState(null); 9 | 10 | return ( 11 |
    12 | { 13 | isLoading && 14 |
    15 | 16 |
    17 | } 18 | { 19 | error && 20 |
    21 | { get(error, 'detail') || get(error, 'exception') || error } 22 |
    23 | } 24 | { 25 | isEmpty(tasks) && !error && !isLoading ? 26 | 'No Tasks Found.' : 27 | map( 28 | tasks, 29 | task => setOpen(taskId)} 34 | onClose={() => setOpen(null)} 35 | onRevoke={onRevoke} 36 | onDownload={onDownload} 37 | /> 38 | ) 39 | } 40 |
    41 | ) 42 | } 43 | 44 | export default Tasks 45 | -------------------------------------------------------------------------------- /src/components/imports/Tasks.scss: -------------------------------------------------------------------------------- 1 | .tasks-container { 2 | padding: 16px; 3 | border: 1px solid rgb(232, 232, 232); 4 | border-radius: 3px; 5 | width: 100%; 6 | display: inline-block; 7 | .task-summary { 8 | .sub-text { 9 | font-size: 12px; 10 | margin-top: 5px 11 | } 12 | } 13 | } 14 | .failure { 15 | background-color: #ffeef0; 16 | } 17 | .success { 18 | background-color: rgba(92, 184, 92, 0.2); 19 | } 20 | .started, .received { 21 | background-color: rgba(51, 115, 170, 0.2); 22 | } 23 | .pending { 24 | background-color: rgba(253, 164, 41, 0.2); 25 | } 26 | .retry { 27 | background-color: rgba(51, 115, 170, 0.2); 28 | } 29 | .revoked { 30 | background-color: rgba(119, 119, 119, 0.2); 31 | } 32 | -------------------------------------------------------------------------------- /src/components/imports/utils.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Cancel as FailedIcon, 4 | CheckCircle as SuccessIcon, 5 | HourglassFull as PendingIcon, 6 | Replay as RetryIcon, 7 | PanTool as RevokedIcon, 8 | Timer as ReceivedIcon, 9 | } from '@mui/icons-material'; 10 | import { CircularProgress } from '@mui/material'; 11 | import { get } from 'lodash'; 12 | import { ERROR_RED, GREEN, ORANGE, DARKGRAY, BLUE } from '../../common/constants'; 13 | 14 | const COLORS = { 15 | failure: ERROR_RED, success: GREEN, pending: ORANGE, retry: BLUE, started: BLUE, 16 | revoked: DARKGRAY, received: BLUE 17 | }; 18 | 19 | const getIcon = (status, rest) => { 20 | if(!status) 21 | return; 22 | 23 | const state = status.toLowerCase(); 24 | const color = COLORS[state]; 25 | const width = get(rest, 'width', '20px'); 26 | const height = get(rest, 'height', '20px'); 27 | 28 | if(state === 'failure') 29 | return ; 30 | if(state === 'success') 31 | return ; 32 | if(state === 'pending') 33 | return ; 34 | if(state === 'retry') 35 | return ; 36 | if(state === 'started') 37 | return ; 38 | if(state === 'revoked') 39 | return ; 40 | if(state === 'received') 41 | return ; 42 | } 43 | 44 | 45 | export const getTaskIconDetails = (status, rest) => { 46 | const icon = getIcon(status, rest); 47 | return { 48 | icon: icon, 49 | status: status, 50 | color: COLORS[status.toLowerCase()] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/mappings/AllMappingsTables.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CircularProgress } from '@mui/material'; 3 | import { isEmpty } from 'lodash'; 4 | import NestedMappingsTable from './NestedMappingsTable'; 5 | import { getIndirectMappings, getDirectMappings } from '../../common/utils'; 6 | 7 | const AllMappingsTables = ({concept_url, mappings, isLoading}) => { 8 | const getMappingsHeader = (mappings, isDirect) => { 9 | const count = mappings.length; 10 | return `${isDirect ? 'Direct' : 'Inverse'} Mappings (${count})` 11 | } 12 | const directMappings = getDirectMappings(mappings, concept_url); 13 | const indirectMappings = getIndirectMappings(mappings, concept_url); 14 | const directMappingsHeader = getMappingsHeader(directMappings, true); 15 | const indirectMappingsHeader = getMappingsHeader(indirectMappings); 16 | const zeroMappings = isEmpty(mappings); 17 | const getMappingsDom = (mappings, header) => { 18 | const isPresent = !isEmpty(mappings); 19 | return ( 20 |
    23 |

    24 | { header } 25 |

    26 | { 27 | isPresent && 28 | 29 | } 30 |
    31 | ); 32 | } 33 | 34 | return ( 35 | 36 | { 37 | isLoading ? 38 |
    39 | 40 |
    : 41 |
    42 | { getMappingsDom(directMappings, directMappingsHeader) } 43 | { getMappingsDom(indirectMappings, indirectMappingsHeader) } 44 |
    45 | } 46 |
    47 | ) 48 | } 49 | 50 | export default AllMappingsTables; 51 | -------------------------------------------------------------------------------- /src/components/mappings/FromConceptLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Tooltip } from '@mui/material' 4 | import {LocalOffer as LocalOfferIcon} from '@mui/icons-material' 5 | import { get } from 'lodash'; 6 | import ResourceLabel from '../common/ResourceLabel'; 7 | import { getSiteTitle } from '../../common/utils'; 8 | 9 | const SITE_TITLE = getSiteTitle() 10 | 11 | const FromConceptLabel = props => { 12 | const conceptName = props.from_concept_name || props.from_concept_name_resolved || get(props, 'from_concept.display_name') 13 | const existsInOCL = Boolean(props.from_concept_url) 14 | const labelComponent = 24 | 29 | 30 | } />; 31 | return ( 32 | 33 | { 34 | !props.noRedirect && props.from_concept_url ? 35 | {labelComponent}: 36 | {labelComponent} 37 | } 38 | 39 | ) 40 | } 41 | export default FromConceptLabel; 42 | -------------------------------------------------------------------------------- /src/components/mappings/FromConceptLabelVertical.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Tooltip } from '@mui/material' 4 | import {LocalOffer as LocalOfferIcon} from '@mui/icons-material' 5 | import { get } from 'lodash'; 6 | import ResourceLabelVertical from '../common/ResourceLabelVertical'; 7 | import { getSiteTitle } from '../../common/utils'; 8 | 9 | const SITE_TITLE = getSiteTitle() 10 | const FromConceptLabelVertical = props => { 11 | const conceptName = props.from_concept_name || props.from_concept_name_resolved || get(props, 'from_concept.display_name') 12 | const existsInOCL = Boolean(props.from_concept_url) 13 | const labelComponent = 22 | 27 | 28 | } />; 29 | return ( 30 | 31 | { 32 | !props.noRedirect && props.from_concept_url ? 33 | {labelComponent}: 34 | {labelComponent} 35 | } 36 | 37 | ) 38 | } 39 | export default FromConceptLabelVertical; 40 | -------------------------------------------------------------------------------- /src/components/mappings/MappingHomeDetails.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { get } from 'lodash'; 3 | import MappingCollections from '../common/SourceChildCollections'; 4 | import CustomAttributesAccordion from '../common/CustomAttributesAccordion'; 5 | import VersionList from '../common/VersionList'; 6 | import ResourceReferences from '../common/ResourceReferences'; 7 | 8 | const ACCORDIAN_HEADING_STYLES = { 9 | fontWeight: 'bold', 10 | } 11 | const ACCORDIAN_DETAILS_STYLES = { 12 | overflowX: 'auto', width: '100%', padding: '0' 13 | } 14 | 15 | const MappingHomeDetails = ({ mapping, singleColumn, versions, isLoadingCollections, scoped }) => { 16 | let classes = 'col-sm-12 padding-5'; 17 | if(!singleColumn) 18 | classes += ' col-md-6' 19 | return ( 20 |
    21 |
    22 | 27 | { 28 | scoped !== 'collection' && 29 | 34 | } 35 |
    36 | { 37 | scoped === 'collection' ? 38 | : 44 |
    45 | 46 |
    47 | } 48 |
    49 | ); 50 | } 51 | 52 | export default MappingHomeDetails; 53 | -------------------------------------------------------------------------------- /src/components/mappings/MappingIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Link as LinkIcon, 4 | } from '@mui/icons-material'; 5 | import { Tooltip } from '@mui/material'; 6 | import { merge } from 'lodash'; 7 | import { toFullAPIURL, copyURL } from '../../common/utils'; 8 | 9 | const MappingIcon = ({ url, shrink, style }) => { 10 | const onIconClick = () => copyURL(toFullAPIURL(url)) 11 | const classes = 'no-side-padding col-xs-1 home-icon mapping flex-vertical-center' + (shrink ? ' xsmall' : '') 12 | 13 | return ( 14 |
    15 | { 16 | url ? 17 | 18 | 19 | : 20 | 21 | } 22 |
    23 | ); 24 | } 25 | 26 | export default MappingIcon; 27 | -------------------------------------------------------------------------------- /src/components/mappings/ToConceptLabel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Tooltip } from '@mui/material' 4 | import {LocalOffer as LocalOfferIcon} from '@mui/icons-material' 5 | import { get } from 'lodash'; 6 | import ResourceLabel from '../common/ResourceLabel'; 7 | import { getSiteTitle } from '../../common/utils'; 8 | 9 | const SITE_TITLE = getSiteTitle() 10 | 11 | const ToConceptLabel = props => { 12 | const conceptName = props.to_concept_name || props.to_concept_name_resolved || get(props, 'to_concept.display_name') 13 | const hasLink = props.to_concept_url && !props.noRedirect; 14 | const existsInOCL = Boolean(props.to_concept_url) 15 | const labelComponent = 25 | 30 | 31 | } />; 32 | return ( 33 | 34 | { 35 | hasLink ? 36 | {labelComponent}: 37 | {labelComponent} 38 | } 39 | 40 | ) 41 | } 42 | export default ToConceptLabel; 43 | -------------------------------------------------------------------------------- /src/components/mappings/ToConceptLabelVertical.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Tooltip } from '@mui/material' 4 | import {LocalOffer as LocalOfferIcon} from '@mui/icons-material' 5 | import { get } from 'lodash'; 6 | import ResourceLabelVertical from '../common/ResourceLabelVertical'; 7 | import { getSiteTitle } from '../../common/utils'; 8 | 9 | const SITE_TITLE = getSiteTitle() 10 | const ToConceptLabelVertical = props => { 11 | const conceptName = props.to_concept_name || props.to_concept_name_resolved || get(props, 'to_concept.display_name') 12 | const hasLink = props.to_concept_url && !props.noRedirect; 13 | const existsInOCL = Boolean(props.to_concept_url) 14 | const labelComponent = 23 | 28 | 29 | } />; 30 | return ( 31 | 32 | { 33 | hasLink ? 34 | {labelComponent}: 35 | {labelComponent} 36 | } 37 | 38 | ) 39 | } 40 | export default ToConceptLabelVertical; 41 | -------------------------------------------------------------------------------- /src/components/orgs/OrgHomeChildrenList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { merge } from 'lodash'; 3 | import Search from '../search/Search'; 4 | 5 | class OrgHomeChildrenList extends React.Component { 6 | getURL() { 7 | const { url, urlPath, resource } = this.props; 8 | const subPath = urlPath || resource 9 | 10 | return `${url}${subPath}/`; 11 | } 12 | 13 | render() { 14 | const { org, resource, fixedFilters } = this.props; 15 | return ( 16 | 24 | ) 25 | } 26 | } 27 | 28 | export default OrgHomeChildrenList; 29 | -------------------------------------------------------------------------------- /src/components/orgs/Overview.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { isEmpty, has } from 'lodash' 3 | import { AccountBalance as OrgIcon } from '@mui/icons-material'; 4 | import Pins from '../common/Pins'; 5 | import PinIcon from '../common/PinIcon'; 6 | import Members from './Members'; 7 | 8 | const Overview = ({ org, pins, onPinDelete, onPinOrderUpdate, canDeletePin, members }) => { 9 | const about = org.text === '


    ' ? '' : org.text; 10 | const showPins = has(org, 'overview.pins') ? org.overview.pins : true 11 | return ( 12 |
    13 |
    14 | { 15 | showPins && 16 |
    17 | { 18 | isEmpty(pins) ? 19 | 20 |

    21 | 22 | Pinned 23 |

    24 |

    25 | {`${org.id} doesn't have any pinned content yet.`} 26 |

    27 |
    : 28 | 35 | } 36 |
    37 | } 38 |
    39 |

    40 | 41 | {`About ${org.id}`} 42 |

    43 | { 44 | isEmpty(about) ? 45 |

    46 | {`${org.id} doesn't have any about text yet.`} 47 |

    : 48 |
    49 | } 50 |
    51 |
    52 |
    53 | { 54 | !isEmpty(members) && 55 | 56 | } 57 |
    58 |
    59 | ) 60 | } 61 | 62 | export default Overview; 63 | -------------------------------------------------------------------------------- /src/components/search/BestMatchSort.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tooltip, Chip } from '@mui/material'; 3 | import { 4 | ArrowDownward as ArrowDownwardIcon, 5 | ArrowUpward as ArrowUpwardIcon, 6 | } from '@mui/icons-material'; 7 | import { get } from 'lodash'; 8 | 9 | const SORT_ICON_STYLES = {width: '14px', height: '14px'}; 10 | 11 | const BestMatchSort = ({ selected, onSelect, size }) => { 12 | const isSelected = !selected || selected.sortDesc === '_score' || selected.sortAsc === '_score'; 13 | const isAsc = get(selected, 'sortAsc') === '_score'; 14 | const onClick = () => { 15 | if(!isSelected || isAsc) 16 | onSelect({sortDesc: '_score'}) 17 | else 18 | onSelect({sortAsc: '_score'}) 19 | }; 20 | return ( 21 | 22 | 23 | : 27 | 28 | } 29 | variant="outlined" 30 | color={isSelected ? "primary" : "secondary"} 31 | label="Best Match" 32 | onClick={onClick} 33 | size={size || 'medium'} 34 | /> 35 | 36 | 37 | ) 38 | } 39 | 40 | export default BestMatchSort; 41 | -------------------------------------------------------------------------------- /src/components/search/GenericFilterChip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Chip, Tooltip, Menu, MenuItem, MenuList 4 | } from '@mui/material'; 5 | import { 6 | ExpandLess as ExpandLessIcon, 7 | ExpandMore as ExpandMoreIcon, 8 | FilterList as FilterIcon, 9 | Cancel as CancelIcon, 10 | } from '@mui/icons-material'; 11 | import { startCase, map } from 'lodash'; 12 | 13 | const GenericFilterChip = ({id, name, options, tooltip, onChange, value, size}) => { 14 | const anchorRef = React.useRef(null); 15 | const [open, setOpen] = React.useState(false); 16 | const label = name || startCase(id) 17 | const labelWithValue = `${label}${value ? '=' + value : ''}` 18 | const tooltipTitle = tooltip || `Filter by ${label}` 19 | const onValueChange = option => { 20 | setOpen(false) 21 | onChange(id, option) 22 | return false 23 | } 24 | 25 | return ( 26 | 27 | 28 | } 30 | color={value ? 'primary' : 'secondary'} 31 | label={labelWithValue} 32 | size={size || 'medium'} 33 | deleteIcon={ 34 | value ? : (open ? : ) 35 | } 36 | onDelete={() => value ? onValueChange(null) : setOpen(!open)} 37 | onClick={() => setOpen(!open)} 38 | ref={anchorRef} 39 | variant="outlined" 40 | /> 41 | 42 | { 43 | open && 44 | setOpen(false)}> 45 | 46 | { 47 | map(options, (option, index) => ( 48 | onValueChange(option)} 53 | > 54 | {option} 55 | 56 | )) 57 | } 58 | 59 | 60 | } 61 | 62 | ) 63 | } 64 | 65 | export default GenericFilterChip; 66 | -------------------------------------------------------------------------------- /src/components/search/MinimalRowComponent.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Divider, Checkbox } from '@mui/material'; 3 | import ResourceLabel from '../common/ResourceLabel'; 4 | 5 | const MinimalRowComponent = ({resource, item, onSelect}) => { 6 | return ( 7 |
    8 |
    9 |
    10 | onSelect(event, item.url)} /> 11 |
    12 |
    13 | { 14 | resource === 'concepts' && 15 | 16 | } 17 | { 18 | resource === 'mappings' && 19 | 20 | } 21 |
    22 |
    23 | 24 |
    25 | ) 26 | } 27 | export default MinimalRowComponent; 28 | -------------------------------------------------------------------------------- /src/components/search/NavigationButtonGroup.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ButtonGroup, Button} from '@mui/material'; 3 | import { 4 | NavigateBefore as NavigateBeforeIcon, 5 | NavigateNext as NavigateNextIcon 6 | } from '@mui/icons-material'; 7 | 8 | 9 | const NavigationButtonGroup = ({ onClick, prev, next }) => { 10 | return ( 11 | 12 | 15 | 18 | 19 | ) 20 | } 21 | 22 | export default NavigationButtonGroup; 23 | -------------------------------------------------------------------------------- /src/components/search/NumericalIDSort.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTranslation } from 'react-i18next' 3 | import { Tooltip, Chip } from '@mui/material'; 4 | import { 5 | ArrowDownward as ArrowDownwardIcon, 6 | ArrowUpward as ArrowUpwardIcon, 7 | } from '@mui/icons-material'; 8 | import { get } from 'lodash'; 9 | 10 | const SORT_ICON_STYLES = {width: '14px', height: '14px'}; 11 | 12 | const NumericalIDSort = ({ selected, onSelect, size }) => { 13 | const { t } = useTranslation() 14 | const isSelected = !selected || selected.sortDesc === 'numeric_id' || selected.sortAsc === 'numeric_id'; 15 | const isDesc = get(selected, 'sortDesc') === 'numeric_id'; 16 | const onClick = () => { 17 | if(!isSelected || isDesc) 18 | onSelect({sortAsc: 'numeric_id'}) 19 | else 20 | onSelect({sortDesc: 'numeric_id'}) 21 | }; 22 | return ( 23 | 24 | 25 | : 29 | 30 | } 31 | variant="outlined" 32 | color={isSelected ? "primary" : "secondary"} 33 | label={t('search.numerical_sort')} 34 | onClick={onClick} 35 | size={size || 'medium'} 36 | /> 37 | 38 | 39 | ) 40 | } 41 | 42 | export default NumericalIDSort; 43 | -------------------------------------------------------------------------------- /src/components/search/Resources.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Table, TableBody, TableCell, TableContainer, TableRow, Paper, Chip, CircularProgress } from '@mui/material'; 3 | import { 4 | LocalOffer as LocalOfferIcon, Link as LinkIcon, List as ListIcon, 5 | Loyalty as LoyaltyIcon, AccountBalance as HomeIcon, Person as PersonIcon, 6 | } from '@mui/icons-material' 7 | import { map, get } from 'lodash'; 8 | import { BLUE, WHITE, DARKGRAY } from '../../common/constants'; 9 | 10 | const RESOURCES = [ 11 | {id: 'concepts', label: 'Concepts', icon: }, 12 | {id: 'mappings', label: 'Mappings', icon: }, 13 | {id: 'sources', label: 'Sources', icon: }, 14 | {id: 'collections', label: 'Collections', icon: }, 15 | {id: 'organizations', label: 'Organizations', icon: }, 16 | {id: 'users', label: 'Users', icon: }, 17 | ] 18 | 19 | const Resources = props => { 20 | const { active, results } = props; 21 | const onClick = resource => { 22 | if(active !== resource) 23 | props.onClick(resource) 24 | } 25 | return ( 26 | 27 | 28 | 29 | { 30 | map(RESOURCES, resource => { 31 | const isActive = active === resource.id; 32 | const classes = isActive ? 'active' : ''; 33 | const badgeColor = isActive ? BLUE : WHITE; 34 | const badgeBg = isActive ? WHITE : DARKGRAY; 35 | const count = get(results, `${resource.id}.total`, 0).toLocaleString() 36 | return ( 37 | onClick(resource.id)}> 38 | 39 | 40 | {resource.icon} 41 | {resource.label} 42 | 43 | { 44 | results[resource.id].isLoadingCount ? 45 | : 46 | 47 | } 48 | 49 | 50 | 51 | ) 52 | }) 53 | } 54 | 55 |
    56 |
    57 | ) 58 | } 59 | 60 | export default Resources 61 | -------------------------------------------------------------------------------- /src/components/search/RowComponent.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Divider, Checkbox } from '@mui/material'; 3 | import { includes } from 'lodash'; 4 | import Concept from '../concepts/Concept'; 5 | import Mapping from '../mappings/Mapping'; 6 | import Source from '../sources/Source'; 7 | import Collection from '../collections/Collection'; 8 | import Organization from '../orgs/Organization'; 9 | import User from '../users/User'; 10 | 11 | const RowComponent = ({resource, item, onSelect, viewFields, history, currentLayoutURL}) => { 12 | const isSourceChild = includes(['concepts', 'mappings'], resource); 13 | 14 | const getComponent = () => { 15 | if(resource === 'concepts') 16 | return ; 17 | if(resource === 'mappings') 18 | return ; 19 | if(resource === 'sources') 20 | return ; 21 | if(resource === 'collections') 22 | return ; 23 | if(resource === 'organizations') 24 | return ; 25 | if(resource === 'users') 26 | return ; 27 | } 28 | 29 | return ( 30 |
    31 | { 32 | isSourceChild && 33 |
    34 | onSelect(event, item.url)} /> 35 |
    36 | } 37 |
    38 | {getComponent()} 39 |
    40 | 41 |
    42 | ) 43 | } 44 | export default RowComponent; 45 | -------------------------------------------------------------------------------- /src/components/search/SearchFilters.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tabs, Box } from '@mui/material'; 3 | 4 | const SearchFilters = ({ filterControls, layoutControls, nested }) => { 5 | return ( 6 | 7 |
    8 | 9 | 21 | { 22 | filterControls && 23 | 24 | { filterControls } 25 | 26 | } 27 | { 28 | layoutControls && 29 | 30 | { layoutControls } 31 | 32 | } 33 | 34 | 35 |
    36 |
    37 | ) 38 | } 39 | 40 | export default SearchFilters; 41 | -------------------------------------------------------------------------------- /src/components/search/utils.js: -------------------------------------------------------------------------------- 1 | import APIService from '../../services/APIService'; 2 | import { without, forEach } from 'lodash'; 3 | 4 | export const fetchSearchResults = (resource, queryParams, baseURL, beforeCallback, afterCallback) => { 5 | if(!resource) 6 | resource = 'concepts'; 7 | 8 | if(beforeCallback) 9 | beforeCallback(__fetchSearchResults(resource, queryParams, baseURL, afterCallback)); 10 | else 11 | __fetchSearchResults(resource, queryParams, baseURL, afterCallback); 12 | }; 13 | 14 | const __fetchSearchResults = (resource, queryParams, baseURL, callback, method='get') => { 15 | if(!queryParams) 16 | queryParams = {}; 17 | 18 | let service; 19 | 20 | if(baseURL) 21 | service = APIService.new().overrideURL(baseURL); 22 | else if(resource) 23 | service = APIService[resource](); 24 | 25 | if(method === 'get') 26 | service 27 | .get(null, {}, queryParams) 28 | .then(response => callback(response, resource)); 29 | if(method === 'head') 30 | service 31 | .head(null, {}, queryParams) 32 | .then(response => callback(response, resource)); 33 | }; 34 | 35 | export const fetchCounts = (excludeResource, queryParams, callback) => { 36 | let resources = without(['concepts', 'mappings', 'collections', 'sources', 'orgs', 'users'], excludeResource); 37 | if(!queryParams) 38 | queryParams = {}; 39 | 40 | forEach(resources, resource => { 41 | __fetchSearchResults(resource, queryParams, null, callback, 'head') 42 | }); 43 | } 44 | 45 | export const fetchFacets = (resource, queryParams, baseURL, callback) => { 46 | if(!queryParams) 47 | queryParams = {} 48 | queryParams.facetsOnly = true 49 | 50 | __fetchSearchResults(resource, queryParams, baseURL, callback) 51 | } 52 | -------------------------------------------------------------------------------- /src/components/sources/Source.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | List as ListIcon, 4 | } from '@mui/icons-material' 5 | import { merge, get, isEmpty, map, isArray, reject, includes, keys } from 'lodash' 6 | import { DARKGRAY } from '../../common/constants'; 7 | import ResourceLabel from '../common/ResourceLabel'; 8 | import LastUpdatedOnLabel from '../common/LastUpdatedOnLabel'; 9 | import Summary from '../common/ConceptContainerSummaryHorizontal'; 10 | 11 | const DEFAULT_FIELDS = [{source_type: 'Source Type'}] 12 | const LABEL_FIELDS = ['id', 'short_code', 'name', 'owner'] 13 | 14 | const Source = props => { 15 | const { summary, viewFields, history, currentLayoutURL, url } = props; 16 | const hasSummary = !isEmpty(summary); 17 | const mainClass = 'no-left-padding ' + hasSummary ? 'col-sm-9': 'col-sm-12'; 18 | const customFields = isArray(viewFields) ? reject(viewFields, fieldConfig => includes(LABEL_FIELDS, keys(fieldConfig)[0])) : []; 19 | const fields = isEmpty(customFields) ? DEFAULT_FIELDS : customFields; 20 | const navigateTo = () => { 21 | if(currentLayoutURL) 22 | history.replace(currentLayoutURL) 23 | history.push(url) 24 | } 25 | 26 | return ( 27 |
    28 |
    29 | 30 | } 33 | colorClass="source-bg" 34 | /> 35 | 36 |
    37 | { 38 | map(fields, field => { 39 | const attr = keys(field)[0] 40 | const label = field[attr]; 41 | return ( 42 | 43 | {label}: 44 | {get(props, attr, '')} 45 |
    46 |
    47 | ) 48 | }) 49 | } 50 | { 51 | props.description && 52 | {props.description} 53 | } 54 |
    55 | 56 |
    57 | { 58 | hasSummary && 59 | 60 | } 61 |
    62 | ) 63 | } 64 | 65 | export default Source; 66 | -------------------------------------------------------------------------------- /src/components/sources/SourceHomeChildrenList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Search from '../search/Search'; 3 | import { includes, merge } from 'lodash'; 4 | 5 | class SourceHomeChildrenList extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | selectedVersion: this.props.currentVersion || 'HEAD' 10 | } 11 | } 12 | 13 | getURL() { 14 | const { selectedVersion } = this.state; 15 | const { versionedObjectURL, resource } = this.props; 16 | let url = versionedObjectURL; 17 | if(selectedVersion && !includes(['HEAD', 'concepts', 'mappings', 'about', 'versions'], selectedVersion)) 18 | url += `${selectedVersion}/` 19 | url += `${resource}/` 20 | 21 | return url 22 | } 23 | 24 | render() { 25 | const { source, resource, fixedFilters } = this.props 26 | return ( 27 | 36 | ) 37 | } 38 | } 39 | 40 | export default SourceHomeChildrenList; 41 | -------------------------------------------------------------------------------- /src/components/sources/SourceVersionForm.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ConceptContainerVersionForm from '../common/ConceptContainerVersionForm'; 3 | 4 | const SourceVersionForm = props => ; 5 | 6 | export default SourceVersionForm; 7 | -------------------------------------------------------------------------------- /src/components/users/ForgotPasswordEmailSentMessage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MuiAlert from '@mui/material/Alert'; 3 | 4 | const ForgotPasswordEmailSentMessage = ({ email }) => { 5 | return ( 6 |
    7 |
    8 | { 9 | email && 10 | 11 | { `Password reset e-mail sent to ${email}.` } 12 | 13 | } 14 |
    15 |

    Password Reset

    16 |
    17 | We have sent you an email. Please contact us if you do not receive it within a few minutes. 18 |
    19 |
    20 | ) 21 | } 22 | 23 | export default ForgotPasswordEmailSentMessage; 24 | -------------------------------------------------------------------------------- /src/components/users/ForgotPasswordSuccessMessage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import MuiAlert from '@mui/material/Alert'; 4 | 5 | const ForgotPasswordSuccessMessage = () => { 6 | return ( 7 |
    8 |
    9 | 10 | Password successfully changed. 11 | 12 |
    13 |

    Change Password

    14 |
    15 | Your password is now changed. Please proceed to Sign In. 16 |
    17 |
    18 | ) 19 | } 20 | 21 | export default ForgotPasswordSuccessMessage; 22 | -------------------------------------------------------------------------------- /src/components/users/OIDLoginCallback.jsx: -------------------------------------------------------------------------------- 1 | /*eslint no-process-env: 0*/ 2 | import React from 'react'; 3 | import alertifyjs from 'alertifyjs'; 4 | import { get } from 'lodash'; 5 | import { 6 | refreshCurrentUserCache 7 | } from '../../common/utils'; 8 | import APIService from '../../services/APIService' 9 | 10 | 11 | class OIDLoginCallback extends React.Component { 12 | constructor(props) { 13 | super(props) 14 | this.state = { 15 | next: null 16 | } 17 | } 18 | componentDidMount() { 19 | this.exchangeCodeForToken() 20 | } 21 | 22 | exchangeCodeForToken = () => { 23 | const queryParams = new URLSearchParams(this.props.location.search) 24 | const code = queryParams.get('code') 25 | const idToken = queryParams.get('id_token') 26 | const next = queryParams.get('next') 27 | if(code) { 28 | /*eslint no-undef: 0*/ 29 | this.setState({next: next && next !== '/' ? next : null }, () => { 30 | const redirectURL = this.state.next ? window.location.origin + this.state.next : (window.LOGIN_REDIRECT_URL || process.env.LOGIN_REDIRECT_URL) 31 | const clientSecret = window.OIDC_RP_CLIENT_SECRET || process.env.OIDC_RP_CLIENT_SECRET 32 | const clientId = window.OIDC_RP_CLIENT_ID || process.env.OIDC_RP_CLIENT_ID 33 | 34 | APIService.users().appendToUrl('oidc/code-exchange/').post({code: code, redirect_uri: redirectURL, client_id: clientId, client_secret: clientSecret}).then(res => { 35 | if(res.data?.access_token) { 36 | localStorage.removeItem('server_configs') 37 | localStorage.setItem('token', res.data.access_token) 38 | localStorage.setItem('id_token', idToken) 39 | this.cacheUserData() 40 | } else { 41 | alertifyjs.error(res.data) 42 | } 43 | }) 44 | }) 45 | } 46 | } 47 | 48 | cacheUserData() { 49 | refreshCurrentUserCache(response => { 50 | alertifyjs.success(`Successfully signed in`) 51 | if(this.state.next) 52 | window.location.hash = '#' + this.state.next 53 | else { 54 | let returnToURL = response.data.url 55 | if(get(this.props, 'location.search')) { 56 | const queryParams = new URLSearchParams(this.props.location.search) 57 | if(queryParams && queryParams.get('returnTo')) 58 | returnToURL = queryParams.get('returnTo') 59 | } 60 | window.location.hash = '#' + returnToURL 61 | } 62 | }) 63 | } 64 | 65 | render() { 66 | return ( 67 |
    Signing in...
    68 | ) 69 | } 70 | } 71 | 72 | export default OIDLoginCallback; 73 | -------------------------------------------------------------------------------- /src/components/users/UserHomeOrgs.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { 4 | Accordion, AccordionSummary, AccordionDetails, Typography, CircularProgress, 5 | } from '@mui/material'; 6 | import { map, isEmpty } from 'lodash'; 7 | import OwnerLabel from '../common/OwnerLabel'; 8 | 9 | const ACCORDIAN_HEADING_STYLES = { 10 | fontWeight: 'bold', 11 | } 12 | const ACCORDIAN_DETAILS_STYLES = { 13 | maxHeight: '525px', overflow: 'auto', display: 'inline-block', width: '100%', textAlign: 'left', 14 | } 15 | 16 | const UserHomeOrgs = ({ isLoadingOrgs, orgs }) => { 17 | return ( 18 |
    19 | 20 | } 23 | aria-controls="panel1a-content" 24 | > 25 | Organization Membership 26 | 27 | 28 | { 29 | isLoadingOrgs ? 30 | : 31 |
    32 | { 33 | isEmpty(orgs) ? 34 | No User Organizations : 35 | map(orgs, org => ( 36 | 37 | 38 | 39 | )) 40 | } 41 |
    42 | } 43 |
    44 |
    45 |
    46 | ) 47 | } 48 | 49 | export default UserHomeOrgs; 50 | -------------------------------------------------------------------------------- /src/components/users/VerifyEmailMessage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MuiAlert from '@mui/material/Alert'; 3 | 4 | const VerifyEmailMessage = ({ email, message }) => { 5 | return ( 6 |
    7 |
    8 | { 9 | email && 10 | 11 | { `Confirmation e-mail sent to ${email}.` } 12 | 13 | } 14 |
    15 |

    Verify Your Email Address

    16 |
    17 | {message} 18 |
    19 |
    20 | ) 21 | } 22 | 23 | export default VerifyEmailMessage; 24 | -------------------------------------------------------------------------------- /src/hooks/useToggle.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const useToggle = (initialState = false) => { 4 | const [value, setValue] = React.useState(initialState); 5 | const toggle = React.useCallback(() => setValue(state => !state), []); 6 | const setTrue = React.useCallback(() => setValue(true), []); 7 | const setFalse = React.useCallback(() => setValue(false), []); 8 | 9 | return {value, toggle, setTrue, setFalse} 10 | } 11 | 12 | export default useToggle -------------------------------------------------------------------------------- /src/i18n/config.js: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { initReactI18next } from 'react-i18next'; 3 | import en from './locales/en/translations.json' 4 | import es from './locales/es/translations.json' 5 | import zh from './locales/zh/translations.json' 6 | 7 | i18n.use(initReactI18next).init({ 8 | fallbackLng: 'en', 9 | lng: 'en', 10 | resources: { 11 | en: { 12 | translations: en 13 | }, 14 | es: { 15 | translations: es 16 | }, 17 | zh: { 18 | translations: zh 19 | } 20 | }, 21 | ns: ['translations'], 22 | defaultNS: 'translations' 23 | }); 24 | 25 | i18n.languages = ['en', 'es', 'zh']; 26 | 27 | export default i18n; 28 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 16px; 3 | position: relative; 4 | min-height: 100%; 5 | } 6 | 7 | html, body { 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | body { 13 | background-color: #fff !important; 14 | } 15 | -------------------------------------------------------------------------------- /start-prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WEB_PORT=${WEB_PORT:-4000} 4 | 5 | echo "Adding env-config.js" 6 | ENV_FILE="/usr/share/nginx/html/env-config.js" 7 | 8 | rm -f ${ENV_FILE} 9 | touch ${ENV_FILE} 10 | 11 | if [[ ! -z "${API_URL}" ]]; then 12 | echo "var API_URL = \"${API_URL}\";" >> ${ENV_FILE} 13 | fi 14 | if [[ ! -z "${GA_ACCOUNT_ID}" ]]; then 15 | echo "var GA_ACCOUNT_ID = \"${GA_ACCOUNT_ID}\";" >> ${ENV_FILE} 16 | fi 17 | if [[ ! -z "${RECAPTCHA_SITE_KEY}" ]]; then 18 | echo "var RECAPTCHA_SITE_KEY = \"${RECAPTCHA_SITE_KEY}\";" >> ${ENV_FILE} 19 | fi 20 | if [[ ! -z "${ERRBIT_URL}" ]]; then 21 | echo "var ERRBIT_URL = \"${ERRBIT_URL}\";" >> ${ENV_FILE} 22 | fi 23 | if [[ ! -z "${ERRBIT_KEY}" ]]; then 24 | echo "var ERRBIT_KEY = \"${ERRBIT_KEY}\";" >> ${ENV_FILE} 25 | fi 26 | if [[ ! -z "${HOTJAR_ID}" ]]; then 27 | echo "var HOTJAR_ID = \"${HOTJAR_ID}\";" >> ${ENV_FILE} 28 | fi 29 | if [[ ! -z "${LOGIN_REDIRECT_URL}" ]]; then 30 | echo "var LOGIN_REDIRECT_URL = \"${LOGIN_REDIRECT_URL}\";" >> ${ENV_FILE} 31 | fi 32 | if [[ ! -z "${OIDC_RP_CLIENT_ID}" ]]; then 33 | echo "var OIDC_RP_CLIENT_ID = \"${OIDC_RP_CLIENT_ID}\";" >> ${ENV_FILE} 34 | fi 35 | if [[ ! -z "${OIDC_RP_CLIENT_SECRET}" ]]; then 36 | echo "var OIDC_RP_CLIENT_SECRET = \"${OIDC_RP_CLIENT_SECRET}\";" >> ${ENV_FILE} 37 | fi 38 | 39 | echo "Adjusting nginx configuration" 40 | envsubst '$WEB_PORT' < /etc/nginx/templates/default.conf.template > /etc/nginx/conf.d/default.conf 41 | 42 | echo "Starting up the production server" 43 | nginx -g "daemon off;" 44 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Startup in development mode 3 | 4 | npm update && npm install 5 | npm start 6 | --------------------------------------------------------------------------------