├── .codeclimate.yml ├── .eslintignore ├── .github └── workflows │ └── main.yaml ├── .gitignore ├── .gitmodules ├── .npmignore ├── .nycrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── core.api.js ├── core.client.js ├── core.common.js ├── core ├── api │ ├── application.js │ ├── authentication.js │ ├── db.js │ ├── hooks │ │ ├── hooks.authentication.js │ │ ├── hooks.authorisations.js │ │ ├── hooks.logger.js │ │ ├── hooks.model.js │ │ ├── hooks.push.js │ │ ├── hooks.query.js │ │ ├── hooks.schemas.js │ │ ├── hooks.service.js │ │ ├── hooks.storage.js │ │ ├── hooks.users.js │ │ └── index.js │ ├── index.js │ ├── marshall.js │ ├── models │ │ ├── messages.model.mongodb.js │ │ └── users.model.mongodb.js │ └── services │ │ ├── account │ │ ├── account.hooks.js │ │ └── account.service.js │ │ ├── authorisations │ │ ├── authorisations.hooks.js │ │ └── authorisations.service.js │ │ ├── databases │ │ ├── databases.hooks.js │ │ └── databases.service.js │ │ ├── import-export │ │ ├── import-export.hooks.js │ │ └── import-export.service.js │ │ ├── index.js │ │ ├── mailer │ │ ├── mailer.hooks.js │ │ └── mailer.service.js │ │ ├── messages │ │ └── messages.hooks.js │ │ ├── push │ │ ├── push.hooks.js │ │ └── push.service.js │ │ ├── storage │ │ ├── storage.hooks.js │ │ └── storage.service.js │ │ └── users │ │ ├── users.hooks.js │ │ └── users.service.js ├── client │ ├── api.js │ ├── broadcaster.js │ ├── capabilities.js │ ├── components │ │ ├── KActivity.vue │ │ ├── KAvatar.vue │ │ ├── KChip.vue │ │ ├── KContent.vue │ │ ├── KDialog.vue │ │ ├── KEditor.vue │ │ ├── KExpandable.vue │ │ ├── KFollower.vue │ │ ├── KLogo.vue │ │ ├── KModal.vue │ │ ├── KPanel.vue │ │ ├── KScrollArea.vue │ │ ├── KSponsor.vue │ │ ├── KStamp.vue │ │ ├── KStore.vue │ │ ├── KTab.vue │ │ ├── KTextArea.vue │ │ ├── KTree.vue │ │ ├── KVersion.vue │ │ ├── account │ │ │ ├── KAccount.vue │ │ │ ├── KDeleteAccountManager.vue │ │ │ ├── KEmailManager.vue │ │ │ ├── KPasswordManager.vue │ │ │ ├── KProfile.vue │ │ │ ├── KResetPassword.vue │ │ │ ├── KSendResetPassword.vue │ │ │ ├── KSubscription.vue │ │ │ ├── KSubscriptionsManager.vue │ │ │ ├── KVerifyEmailManager.vue │ │ │ └── index.js │ │ ├── action │ │ │ ├── KAction.vue │ │ │ ├── KBugReportAction.vue │ │ │ ├── KToggleFullscreenAction.vue │ │ │ ├── KToggleStickyVisibility.vue │ │ │ ├── KToggleWidgetVisibility.vue │ │ │ └── index.js │ │ ├── app │ │ │ ├── KAbout.vue │ │ │ ├── KHome.vue │ │ │ ├── KPlatform.vue │ │ │ ├── KRequestProgressBar.vue │ │ │ ├── KSettings.vue │ │ │ ├── KTour.vue │ │ │ ├── KWelcome.vue │ │ │ └── index.js │ │ ├── chart │ │ │ ├── KChart.vue │ │ │ ├── KDataTable.vue │ │ │ ├── KStatisticsChart.vue │ │ │ ├── KTimeSeriesChart.vue │ │ │ └── index.js │ │ ├── collection │ │ │ ├── KBoard.vue │ │ │ ├── KCard.vue │ │ │ ├── KCardSection.vue │ │ │ ├── KColumn.vue │ │ │ ├── KDescriptionCardSection.vue │ │ │ ├── KFilter.vue │ │ │ ├── KFilterView.vue │ │ │ ├── KGrid.vue │ │ │ ├── KHistory.vue │ │ │ ├── KHistoryEntry.vue │ │ │ ├── KItem.vue │ │ │ ├── KScrollDown.vue │ │ │ ├── KScrollToTop.vue │ │ │ ├── KSearchFilterControl.vue │ │ │ ├── KSorter.vue │ │ │ ├── KTable.vue │ │ │ ├── KTagsFilterControl.vue │ │ │ ├── KTagsFilterView.vue │ │ │ ├── KTimeFilterControl.vue │ │ │ ├── KTimeFilterView.vue │ │ │ ├── KTimeLine.vue │ │ │ └── index.js │ │ ├── document │ │ │ ├── KBrowser.vue │ │ │ ├── KCsv.vue │ │ │ ├── KDocument.vue │ │ │ ├── KHtml.vue │ │ │ ├── KImage.vue │ │ │ ├── KMarkdown.vue │ │ │ ├── KPdf.vue │ │ │ ├── KUploader.vue │ │ │ └── KVideo.vue │ │ ├── editor │ │ │ ├── KEditor.vue │ │ │ ├── KModalEditor.vue │ │ │ └── index.js │ │ ├── form │ │ │ ├── KChipsField.vue │ │ │ ├── KColorField.vue │ │ │ ├── KColorScaleField.vue │ │ │ ├── KDateField.vue │ │ │ ├── KDateTimeRangeField.vue │ │ │ ├── KDatetimeField.vue │ │ │ ├── KEmailField.vue │ │ │ ├── KFileField.vue │ │ │ ├── KForm.vue │ │ │ ├── KIconField.vue │ │ │ ├── KItemField.vue │ │ │ ├── KNumberField.vue │ │ │ ├── KOptionsField.vue │ │ │ ├── KPasswordField.vue │ │ │ ├── KPhoneField.vue │ │ │ ├── KPropertyItemField.vue │ │ │ ├── KResolutionField.vue │ │ │ ├── KRoleField.vue │ │ │ ├── KSelectField.vue │ │ │ ├── KTextField.vue │ │ │ ├── KTextareaField.vue │ │ │ ├── KToggleField.vue │ │ │ ├── KTokenField.vue │ │ │ ├── KUnitField.vue │ │ │ ├── KUrlField.vue │ │ │ ├── KView.vue │ │ │ └── index.js │ │ ├── graphics │ │ │ └── KIcon.vue │ │ ├── index.js │ │ ├── input │ │ │ ├── KColorChooser.vue │ │ │ ├── KColorPicker.vue │ │ │ ├── KIconChooser.vue │ │ │ ├── KIconPicker.vue │ │ │ ├── KOptionsChooser.vue │ │ │ ├── KPalette.vue │ │ │ ├── KShapePicker.vue │ │ │ └── index.js │ │ ├── layout │ │ │ ├── KFab.vue │ │ │ ├── KLayout.vue │ │ │ ├── KOpener.vue │ │ │ ├── KPage.vue │ │ │ ├── KWindow.vue │ │ │ └── index.js │ │ ├── media │ │ │ ├── KColorScale.vue │ │ │ ├── KImageViewer.vue │ │ │ ├── KMarkdownViewer.vue │ │ │ ├── KMediaBrowser.vue │ │ │ ├── KRibbon.vue │ │ │ ├── KShape.vue │ │ │ └── index.js │ │ ├── menu │ │ │ ├── KMenu.vue │ │ │ ├── KRadialFab.vue │ │ │ ├── KRadialFabItem.vue │ │ │ ├── KSubMenu.vue │ │ │ └── index.js │ │ ├── messages │ │ │ ├── KMessageCard.vue │ │ │ ├── KMessageComposer.vue │ │ │ ├── KMessagesTimeLine.vue │ │ │ └── index.js │ │ ├── screen │ │ │ ├── KErrorScreen.vue │ │ │ ├── KLoginScreen.vue │ │ │ ├── KLogoutScreen.vue │ │ │ ├── KOAuthLoginScreen.vue │ │ │ ├── KOAuthLogoutScreen.vue │ │ │ ├── KRegisterScreen.vue │ │ │ ├── KScreen.vue │ │ │ ├── KScreenFooter.vue │ │ │ ├── KScreenHeader.vue │ │ │ ├── KUnauthorizedScreen.vue │ │ │ └── index.js │ │ ├── time │ │ │ ├── KAbsoluteTimeRange.vue │ │ │ ├── KDate.vue │ │ │ ├── KDateTime.vue │ │ │ ├── KDateTimeRange.vue │ │ │ ├── KRelativeTimeRanges.vue │ │ │ ├── KTime.vue │ │ │ ├── KTimeControl.vue │ │ │ └── index.js │ │ ├── tool │ │ │ └── KExportTool.vue │ │ └── viewer │ │ │ ├── KModalViewer.vue │ │ │ └── KViewer.vue │ ├── composables │ │ ├── activity.js │ │ ├── collection-counter.js │ │ ├── collection-filter.js │ │ ├── collection-timerange.js │ │ ├── collection.js │ │ ├── context.js │ │ ├── errors.js │ │ ├── index.js │ │ ├── layout.js │ │ ├── messages.js │ │ ├── pwa.js │ │ ├── schema.js │ │ ├── screen.js │ │ ├── selection.js │ │ ├── session.js │ │ ├── store.js │ │ └── version.js │ ├── context.js │ ├── directives │ │ ├── index.js │ │ ├── v-drop-file.js │ │ └── v-hover.js │ ├── document.js │ ├── events.js │ ├── exporter.js │ ├── filter.js │ ├── guards.js │ ├── hooks │ │ ├── hooks.events.js │ │ ├── hooks.logger.js │ │ ├── hooks.offline.js │ │ ├── hooks.users.js │ │ └── index.js │ ├── i18n.js │ ├── i18n │ │ ├── core_en.json │ │ └── core_fr.json │ ├── index.js │ ├── layout.js │ ├── local-cache.js │ ├── local-storage.js │ ├── mixins │ │ ├── index.js │ │ ├── mixin.base-activity.js │ │ ├── mixin.base-editor.js │ │ ├── mixin.base-field.js │ │ ├── mixin.base-item.js │ │ ├── mixin.base-modal.js │ │ ├── mixin.base-viewer.js │ │ ├── mixin.object-proxy.js │ │ ├── mixin.schema-proxy.js │ │ └── mixin.service.js │ ├── platform.js │ ├── reader.js │ ├── readers │ │ ├── index.js │ │ ├── reader.blob.js │ │ ├── reader.csv.js │ │ └── reader.json.js │ ├── search.js │ ├── services │ │ ├── index.js │ │ └── local-settings.service.js │ ├── sorter.js │ ├── storage.js │ ├── store.js │ ├── template-context.js │ ├── theme.js │ ├── time.js │ ├── units.js │ └── utils │ │ ├── index.js │ │ ├── utils.account.js │ │ ├── utils.actions.js │ │ ├── utils.collection.js │ │ ├── utils.colors.js │ │ ├── utils.content.js │ │ ├── utils.files.js │ │ ├── utils.items.js │ │ ├── utils.locale.js │ │ ├── utils.math.js │ │ ├── utils.push.js │ │ ├── utils.pwa.js │ │ ├── utils.screen.js │ │ ├── utils.services.js │ │ ├── utils.session.js │ │ ├── utils.shapes.js │ │ └── utils.time.js └── common │ ├── errors.js │ ├── index.js │ ├── permissions.js │ ├── schema.js │ ├── schemas │ ├── messages.update.json │ ├── settings.update.json │ └── users.update-profile.json │ ├── utils.js │ └── utils.offline.js ├── docs ├── .vitepress │ ├── config.mjs │ ├── public │ │ ├── Widgets Workflow Diagram.drawio │ │ ├── aggregated-feature-data-model.xml │ │ ├── alert-data-model.xml │ │ ├── application-hooks.xml │ │ ├── catalog-data-model.xml │ │ ├── cd-pipeline-v1.xml │ │ ├── cd-pipeline-v2.xml │ │ ├── cd-pipeline.xml │ │ ├── component-view.xml │ │ ├── domain-model.xml │ │ ├── editor-lifecycle.xml │ │ ├── event-log-lifecycle.xml │ │ ├── feature-data-model.xml │ │ ├── global-architecture.xml │ │ ├── images │ │ │ ├── PWA-client-server-sync.png │ │ │ ├── PWA-offline-client.png │ │ │ ├── PWA-online-client-caching.png │ │ │ ├── PWA-online-client.png │ │ │ ├── PWA-server-server-sync.png │ │ │ ├── aggregated-feature-data-model.png │ │ │ ├── airtac-list.png │ │ │ ├── aktnmap-layout.png │ │ │ ├── aktnmap.png │ │ │ ├── alert-data-model.png │ │ │ ├── animated-wall.gif │ │ │ ├── application-hooks.svg │ │ │ ├── attributionClosed.png │ │ │ ├── attributionOpened.png │ │ │ ├── catalog-data-model.png │ │ │ ├── cd-pipeline-android.svg │ │ │ ├── cd-pipeline-app.svg │ │ │ ├── cd-pipeline-env.svg │ │ │ ├── cd-pipeline-global.svg │ │ │ ├── cd-pipeline-ios.svg │ │ │ ├── cd-pipeline-travis.svg │ │ │ ├── cd-pipeline-v1.svg │ │ │ ├── cd-pipeline-v2.svg │ │ │ ├── component-view.png │ │ │ ├── desaturate-post-process.png │ │ │ ├── domain-model.svg │ │ │ ├── editor-lifecycle.png │ │ │ ├── feathers-services.png │ │ │ ├── feature-data-model.png │ │ │ ├── global-architecture.svg │ │ │ ├── gradient-path.png │ │ │ ├── great-circle-2D.png │ │ │ ├── great-circle-3D.png │ │ │ ├── item-collections.png │ │ │ ├── kalisio-banner.png │ │ │ ├── kano-3D.png │ │ │ ├── kano-components.png │ │ │ ├── kano-iframe.png │ │ │ ├── kano-layout-1.png │ │ │ ├── kano-layout-2.png │ │ │ ├── kano-style-2D.png │ │ │ ├── kano-style-3D.png │ │ │ ├── kano-weather.png │ │ │ ├── kdk-workspace.png │ │ │ ├── layers-panel.png │ │ │ ├── line-gradient-2D.png │ │ │ ├── line-offset-2D.png │ │ │ ├── marker-cluster-2D.png │ │ │ ├── marker-cluster-3D.png │ │ │ ├── operations-methods-events.png │ │ │ ├── theme-colors.svg │ │ │ ├── timeseries.png │ │ │ ├── users-data-model.svg │ │ │ └── wall-3D.png │ │ ├── items.drawio │ │ ├── layout.drawio │ │ ├── manifest.json │ │ ├── offline.drawio │ │ ├── organizations-data-model.xml │ │ ├── theme.drawio │ │ └── users-data-model.xml │ └── theme │ │ ├── custom.css │ │ └── index.js ├── about │ ├── contact.md │ ├── contributing.md │ ├── introduction.md │ ├── license.md │ └── roadmap.md ├── api │ ├── core │ │ ├── application.md │ │ ├── components.md │ │ ├── components │ │ │ ├── collections.md │ │ │ ├── diagrams │ │ │ │ ├── collections.drawio │ │ │ │ ├── items.drawio │ │ │ │ ├── kcard-expanded.png │ │ │ │ ├── kcard-header-footer.png │ │ │ │ ├── kcard-heading.png │ │ │ │ ├── kcard.png │ │ │ │ ├── kcardsection.png │ │ │ │ ├── kgrid-header-footer.png │ │ │ │ ├── kgrid-infinite-scroll.png │ │ │ │ ├── kgrid-layout.png │ │ │ │ └── kgrid-pagination.png │ │ │ ├── graphics.md │ │ │ ├── items.md │ │ │ └── time.md │ │ ├── composables.md │ │ ├── directives.md │ │ ├── hooks.md │ │ ├── introduction.md │ │ ├── mixins.md │ │ ├── services.md │ │ ├── utilities.md │ │ └── utilities │ │ │ ├── utils.collection.md │ │ │ ├── utils.colors.md │ │ │ ├── utils.locale.md │ │ │ ├── utils.screen.md │ │ │ └── utils.services.md │ ├── introduction.md │ └── map │ │ ├── components.md │ │ ├── composables.md │ │ ├── globe-mixins.md │ │ ├── hooks.md │ │ ├── introduction.md │ │ ├── map-mixins.md │ │ ├── mixins.md │ │ ├── services.md │ │ ├── utilities.md │ │ └── utilities │ │ └── utils.features.md ├── architecture │ ├── component-view.md │ ├── data-model-view.md │ ├── global-architecture.md │ ├── introduction.md │ └── main-concepts.md ├── guides │ ├── basics │ │ └── introduction.md │ ├── development │ │ ├── configure.md │ │ ├── deploy.md │ │ ├── develop.md │ │ ├── publish.md │ │ ├── setup.md │ │ └── test.md │ ├── introduction.md │ └── migration │ │ ├── v2.5.md │ │ └── v2.6.md ├── index.md ├── package.json └── yarn.lock ├── extras ├── configs │ ├── helpers.js │ ├── panes.left.js │ ├── panes.top.js │ ├── stickies.js │ ├── widgets.left.js │ └── widgets.top.js ├── css │ └── core.variables.scss ├── icons │ ├── anticlockwise.png │ ├── attribution.png │ ├── center-on-feature.svg │ ├── clockwise.png │ ├── json.svg │ ├── kanban.png │ ├── map-legend.svg │ ├── mapillary-marker.png │ ├── mapillary.png │ ├── marker-icon-2x.png │ ├── marker-icon.png │ ├── marker-shadow.png │ ├── pdf.png │ ├── position-cursor.png │ ├── view-plus.png │ ├── wind-speed-0.svg │ ├── wind-speed-10.svg │ ├── wind-speed-100.svg │ ├── wind-speed-105.svg │ ├── wind-speed-15.svg │ ├── wind-speed-20.svg │ ├── wind-speed-25.svg │ ├── wind-speed-30.svg │ ├── wind-speed-35.svg │ ├── wind-speed-40.svg │ ├── wind-speed-45.svg │ ├── wind-speed-5.svg │ ├── wind-speed-50.svg │ ├── wind-speed-55.svg │ ├── wind-speed-60.svg │ ├── wind-speed-65.svg │ ├── wind-speed-70.svg │ ├── wind-speed-75.svg │ ├── wind-speed-80.svg │ ├── wind-speed-85.svg │ ├── wind-speed-90.svg │ └── wind-speed-95.svg ├── images │ ├── kalisio.png │ ├── north.svg │ └── target.svg └── tours │ ├── core │ ├── account-profile.js │ ├── account.js │ ├── add-member.js │ ├── add-tag.js │ ├── create-group.js │ ├── create-organisation.js │ ├── create-tag.js │ ├── edit-member-role.js │ ├── groups.js │ ├── join-group.js │ ├── login.js │ ├── members.js │ ├── register.js │ ├── send-reset-password.js │ └── tags.js │ └── map │ ├── add-layer.js │ ├── catalog-categories.js │ ├── catalog-panel.js │ ├── connect-layer.js │ ├── create-layer.js │ ├── create-view.js │ ├── fab.js │ ├── home.js │ ├── import-layer.js │ ├── navigation-bar.js │ ├── side-nav.js │ └── timeline.js ├── map.api.js ├── map.client.globe.js ├── map.client.js ├── map.client.map.js ├── map.common.js ├── map.config.cjs ├── map ├── api │ ├── config │ │ ├── categories.cjs │ │ ├── layers.cjs │ │ └── sublegends.cjs │ ├── hooks │ │ ├── hooks.catalog.js │ │ ├── hooks.features.js │ │ ├── hooks.query.js │ │ └── index.js │ ├── index.js │ ├── marshall.js │ ├── models │ │ ├── alerts.model.mongodb.js │ │ ├── catalog.model.mongodb.js │ │ ├── features.model.mongodb.js │ │ ├── projects.model.mongodb.js │ │ └── styles.model.mongodb.js │ └── services │ │ ├── alerts │ │ ├── alerts.hooks.js │ │ └── alerts.service.js │ │ ├── catalog │ │ └── catalog.hooks.js │ │ ├── daptiles │ │ └── daptiles.service.js │ │ ├── features │ │ ├── features.hooks.js │ │ └── features.service.js │ │ ├── index.js │ │ ├── projects │ │ └── projects.hooks.js │ │ └── styles │ │ └── styles.hooks.js ├── client │ ├── canvas-draw-context.js │ ├── capture.js │ ├── cesium │ │ └── utils │ │ │ ├── index.js │ │ │ ├── utils.cesium.js │ │ │ ├── utils.events.js │ │ │ ├── utils.features.js │ │ │ ├── utils.geojson.js │ │ │ ├── utils.popup.js │ │ │ └── utils.style.js │ ├── components │ │ ├── KCapture.vue │ │ ├── KCaptureTextArea.vue │ │ ├── KCompass.vue │ │ ├── KEditLayerData.vue │ │ ├── KFeatureActionButton.vue │ │ ├── KFeatureEditor.vue │ │ ├── KFeaturesChart.vue │ │ ├── KFeaturesFilterEditor.vue │ │ ├── KFeaturesFilterManager.vue │ │ ├── KFeaturesTable.vue │ │ ├── KFilterCondition.vue │ │ ├── KLayerEditionToolbar.vue │ │ ├── KLayerEditor.vue │ │ ├── KMeasureTool.vue │ │ ├── KProjectMenu.vue │ │ ├── KTimezoneMap.vue │ │ ├── catalog │ │ │ ├── KAddLayer.vue │ │ │ ├── KBaseLayersSelector.vue │ │ │ ├── KCategoryItem.vue │ │ │ ├── KConnectLayer.vue │ │ │ ├── KCreateLayer.vue │ │ │ ├── KCreateOfflineView.vue │ │ │ ├── KCreateView.vue │ │ │ ├── KFilteredLayerItem.vue │ │ │ ├── KImportLayer.vue │ │ │ ├── KLayerCategories.vue │ │ │ ├── KLayerItem.vue │ │ │ ├── KLayersList.vue │ │ │ ├── KLayersPanel.vue │ │ │ ├── KLayersSelector.vue │ │ │ ├── KProjectEditor.vue │ │ │ ├── KProjectManager.vue │ │ │ ├── KProjectSelector.vue │ │ │ ├── KProjectsPanel.vue │ │ │ ├── KSelectLayers.vue │ │ │ ├── KSelectViews.vue │ │ │ ├── KViewSelector.vue │ │ │ ├── KViewsPanel.vue │ │ │ └── KWeatherLayersSelector.vue │ │ ├── form │ │ │ ├── KDirectionField.vue │ │ │ ├── KLayerCategoryField.vue │ │ │ ├── KLocationField.vue │ │ │ ├── KOwsLayerField.vue │ │ │ ├── KOwsServiceField.vue │ │ │ ├── KSelectLayersField.vue │ │ │ ├── KSelectViewsField.vue │ │ │ └── KTimezoneField.vue │ │ ├── index.js │ │ ├── legend │ │ │ ├── KColorScaleLegend.vue │ │ │ ├── KImageLegend.vue │ │ │ ├── KLayerLegend.vue │ │ │ ├── KLegend.vue │ │ │ ├── KLegendRenderer.vue │ │ │ ├── KSymbolsLegend.vue │ │ │ └── KVariablesLegend.vue │ │ ├── location │ │ │ ├── KGeocodersFilter.vue │ │ │ ├── KLocationCardSection.vue │ │ │ ├── KLocationMap.vue │ │ │ ├── KLocationSearch.vue │ │ │ ├── KLocationTimeLineCard.vue │ │ │ └── KLocationTip.vue │ │ ├── selection │ │ │ ├── KFeaturesSelection.vue │ │ │ └── KSelectedLayerFeatures.vue │ │ ├── stickies │ │ │ ├── KAttribution.vue │ │ │ ├── KLevelSlider.vue │ │ │ ├── KNorthArrow.vue │ │ │ ├── KPosition.vue │ │ │ └── KTarget.vue │ │ ├── styles │ │ │ ├── KLayerStyleAction.vue │ │ │ ├── KStyleEditor.vue │ │ │ ├── KStyleEditorSection.vue │ │ │ ├── KStyleManager.vue │ │ │ ├── KStylePreview.vue │ │ │ ├── KStylePreviewItem.vue │ │ │ ├── KStylePropertiesGroup.vue │ │ │ ├── KStyleProperty.vue │ │ │ └── KStyleTip.vue │ │ ├── tools │ │ │ ├── KGeolocateTool.vue │ │ │ └── KSearchTool.vue │ │ └── widget │ │ │ ├── KElevationProfile.vue │ │ │ ├── KInformationBox.vue │ │ │ ├── KMapillaryViewer.vue │ │ │ ├── KStackableTimeSeries.vue │ │ │ └── KTimeSeries.vue │ ├── composables │ │ ├── activity.js │ │ ├── catalog.js │ │ ├── highlight.js │ │ ├── index.js │ │ ├── location.js │ │ ├── measure.js │ │ ├── probe.js │ │ ├── project.js │ │ ├── selection.js │ │ └── weather.js │ ├── elevation-utils.js │ ├── geocoder.js │ ├── geolocation.js │ ├── globe.js │ ├── hooks │ │ ├── hooks.offline.js │ │ └── index.js │ ├── i18n │ │ ├── map_en.json │ │ └── map_fr.json │ ├── index.js │ ├── init.js │ ├── leaflet │ │ ├── BoxSelection.js │ │ ├── GSMaPLayer.js │ │ ├── GradientPath.js │ │ ├── MaskLayer.js │ │ ├── ShapeMarker.js │ │ ├── TiledFeatureLayer.js │ │ ├── TiledMeshLayer.js │ │ ├── TiledWindLayer.js │ │ ├── WindBarb.js │ │ └── utils │ │ │ ├── index.js │ │ │ ├── utils.events.js │ │ │ ├── utils.geojson.js │ │ │ ├── utils.popup.js │ │ │ ├── utils.style.js │ │ │ └── utils.tiles.js │ ├── map.js │ ├── mixins │ │ ├── globe │ │ │ ├── index.js │ │ │ ├── mixin.base-globe.js │ │ │ ├── mixin.file-layers.js │ │ │ ├── mixin.geojson-layers.js │ │ │ ├── mixin.globe-activity.js │ │ │ ├── mixin.opendap-layers.js │ │ │ ├── mixin.popup.js │ │ │ ├── mixin.style.js │ │ │ └── mixin.tooltip.js │ │ ├── index.js │ │ ├── map │ │ │ ├── index.js │ │ │ ├── mixin.base-map.js │ │ │ ├── mixin.canvas-layers.js │ │ │ ├── mixin.edit-layers.js │ │ │ ├── mixin.file-layers.js │ │ │ ├── mixin.geojson-layers.js │ │ │ ├── mixin.gsmap-layers.js │ │ │ ├── mixin.heatmap-layers.js │ │ │ ├── mixin.map-activity.js │ │ │ ├── mixin.mapillary-layers.js │ │ │ ├── mixin.pmtiles-layers.js │ │ │ ├── mixin.popup.js │ │ │ ├── mixin.style.js │ │ │ ├── mixin.tiled-mesh-layers.js │ │ │ ├── mixin.tiled-wind-layers.js │ │ │ └── mixin.tooltip.js │ │ ├── mixin.activity.js │ │ ├── mixin.context.js │ │ ├── mixin.feature-selection.js │ │ ├── mixin.feature-service.js │ │ ├── mixin.infobox.js │ │ ├── mixin.levels.js │ │ ├── mixin.style.js │ │ └── mixin.weacast.js │ ├── navigator.js │ ├── pixi-utils.js │ ├── planets.js │ ├── readers │ │ ├── index.js │ │ ├── reader.geojson.js │ │ ├── reader.gpx.js │ │ ├── reader.kml.js │ │ └── reader.shp.js │ ├── utils.all.js │ ├── utils.globe.js │ ├── utils.js │ ├── utils.map.js │ └── utils │ │ ├── index.js │ │ ├── utils.capture.js │ │ ├── utils.catalog.js │ │ ├── utils.features.js │ │ ├── utils.js │ │ ├── utils.layers.js │ │ ├── utils.location.js │ │ ├── utils.offline.js │ │ ├── utils.project.js │ │ ├── utils.schema.js │ │ ├── utils.style.js │ │ ├── utils.time-series.js │ │ └── utils.weacast.js └── common │ ├── dynamic-grid-source.js │ ├── errors.js │ ├── geotiff-grid-source.js │ ├── grid.js │ ├── index.js │ ├── meteo-model-grid-source.js │ ├── moment-utils.js │ ├── opendap-grid-source.js │ ├── opendap-utils.js │ ├── permissions.js │ ├── schemas │ ├── capture.create.json │ ├── catalog.update.json │ ├── projects.create.json │ └── projects.update.json │ ├── time-based-grid-source.js │ ├── tms-utils.js │ ├── wcs-grid-source.js │ ├── wcs-utils.js │ ├── weacast-grid-source.js │ ├── wfs-utils.js │ ├── wms-utils.js │ └── wmts-utils.js ├── package.json ├── scripts ├── build_docs.sh ├── init_runner.sh ├── run_tests.sh └── setup_workspace.sh ├── test.api.js ├── test.client.js ├── test ├── api │ ├── core │ │ ├── account.test.js │ │ ├── authentication.test.js │ │ ├── client.test.js.skip │ │ ├── config │ │ │ ├── default.cjs │ │ │ └── email-templates │ │ │ │ ├── confirmInvitation │ │ │ │ └── html.ejs │ │ │ │ ├── identityChange │ │ │ │ └── html.ejs │ │ │ │ ├── newDevice │ │ │ │ └── html.ejs │ │ │ │ ├── newSubscription │ │ │ │ └── html.ejs │ │ │ │ ├── passwordChange │ │ │ │ └── html.ejs │ │ │ │ ├── resendVerifySignup │ │ │ │ └── html.ejs │ │ │ │ ├── resetPwd │ │ │ │ └── html.ejs │ │ │ │ ├── sendResetPwd │ │ │ │ └── html.ejs │ │ │ │ └── verifySignup │ │ │ │ └── html.ejs │ │ ├── data │ │ │ ├── 10k_most_common_passwords.txt │ │ │ ├── invalid-objects.json │ │ │ ├── logo.png │ │ │ ├── schema.json │ │ │ └── valid-objects.json │ │ ├── hooks.test.js │ │ ├── import-export.test.js │ │ ├── index.js │ │ ├── index.test.js │ │ ├── offline.test.js │ │ ├── push.test.js │ │ ├── schemas.test.js │ │ ├── storage.test.js │ │ └── utils.js │ ├── index.js │ └── map │ │ ├── alerts.test.js │ │ ├── catalog.test.js │ │ ├── config │ │ ├── default.cjs │ │ └── layers.json │ │ ├── data │ │ ├── DescribeCoverage.xml │ │ ├── GetCoverage.tif │ │ ├── adsb.observations.json │ │ ├── dataset.grb.das │ │ ├── dataset.grb.dds │ │ ├── dataset.grb.dods │ │ ├── lat_lon_bounds.grb.dods │ │ ├── openradiation.json │ │ ├── subdataset.grb.dods │ │ ├── vigicrues.observations.H.json │ │ ├── vigicrues.observations.Q.json │ │ ├── vigicrues.stations.json │ │ └── zones.json │ │ ├── grid-sources.test.js │ │ ├── hooks.test.js │ │ ├── index.js │ │ ├── index.test.js │ │ └── style.test.js └── client │ ├── core │ ├── account.js │ ├── api.js │ ├── collection.js │ ├── dialogs.js │ ├── index.js │ ├── layout.js │ ├── runner.js │ ├── screens.js │ ├── time.js │ └── utils.js │ ├── index.js │ └── map │ ├── api.js │ ├── catalog.js │ ├── controls.js │ ├── index.js │ ├── time.js │ └── utils.js └── yarn.lock /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | exclude_patterns: 2 | - "lib/" 3 | plugins: 4 | fixme: 5 | enabled: true 6 | checks: 7 | argument-count: 8 | config: 9 | threshold: 4 10 | complex-logic: 11 | config: 12 | threshold: 4 13 | file-lines: 14 | config: 15 | threshold: 500 16 | method-complexity: 17 | config: 18 | threshold: 20 19 | method-count: 20 | config: 21 | threshold: 20 22 | method-lines: 23 | config: 24 | threshold: 50 25 | nested-control-flow: 26 | config: 27 | threshold: 4 28 | return-statements: 29 | config: 30 | threshold: 4 31 | similar-code: 32 | enabled: false 33 | identical-code: 34 | enabled: false 35 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | core/client/components/action/KAction.vue 2 | map/client/components/catalog/KLayersPanel.vue -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .vscode 3 | .idea 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Coverage directory used by tools like istanbul 18 | .nyc_output 19 | coverage 20 | 21 | # Dependency directories 22 | node_modules/ 23 | 24 | # Compiled library 25 | lib/ 26 | 27 | # Optional npm cache directory 28 | .npm 29 | 30 | # Yarn Integrity file 31 | .yarn-integrity 32 | 33 | # dotenv environment variables file 34 | .env 35 | 36 | # draw.io backup 37 | *.bkp 38 | *.dtmp 39 | 40 | # vitepress 41 | docs/.vitepress/cache 42 | docs/.vitepress/dist 43 | docs/node_modules/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "scripts/kash"] 2 | path = scripts/kash 3 | url = http://github.com/kalisio/kash 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .editorconfig 2 | .jshintrc 3 | .travis.yml 4 | .istanbul.yml 5 | .babelrc 6 | .idea/ 7 | docs/ 8 | 9 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "include": ["core/**/*.js", "map/**/*.js"], 4 | "exclude": ["**/client/**"], 5 | "reporter": ["text", "html", "lcov"] 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-20xx Kalisio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /core.api.js: -------------------------------------------------------------------------------- 1 | // Need this as export * only exports named exports but not the default export 2 | import api from './core/api/index.js' 3 | export default api 4 | 5 | export * from './core/api/index.js' 6 | -------------------------------------------------------------------------------- /core.client.js: -------------------------------------------------------------------------------- 1 | // Need this as export * only exports named exports but not the default export 2 | import client from './core/client/index.js' 3 | export default client 4 | 5 | export * from './core/client/index.js' 6 | -------------------------------------------------------------------------------- /core.common.js: -------------------------------------------------------------------------------- 1 | export * from './core/common/index.js' 2 | -------------------------------------------------------------------------------- /core/api/hooks/hooks.authentication.js: -------------------------------------------------------------------------------- 1 | import makeDebug from 'debug' 2 | import common from 'feathers-hooks-common' 3 | import local from '@feathersjs/authentication-local' 4 | 5 | const debug = makeDebug('kdk:core:authentication:hooks') 6 | const { discard } = common 7 | 8 | // Make it more easy to access 9 | export const hashPassword = local.hooks.hashPassword 10 | 11 | export function discardAuthenticationProviders (hook) { 12 | const providers = hook.app.authenticationProviders || [] 13 | 14 | // Iterate through known providers 15 | for (const provider of providers) { 16 | discard(provider)(hook) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/api/hooks/hooks.logger.js: -------------------------------------------------------------------------------- 1 | // A hook that logs service method before, after and error 2 | export function log (hook) { 3 | let message = `${hook.type}: ${hook.path} - Method: ${hook.method}` 4 | 5 | if (hook.type === 'error') { 6 | message += `: ${hook.error.message}` 7 | } 8 | 9 | if (hook.error) { 10 | hook.app.logger.error(message, hook.error.stack) 11 | } else { 12 | hook.app.logger.debug(message) 13 | } 14 | 15 | // Required as the logger causes high CPU usage to serialize messages 16 | // even if the current log level should discard it 17 | // See https://github.com/kalisio/kdk/issues/287 18 | if (process.env.NODE_ENV === 'development') { 19 | hook.app.logger.silly('hook.data', hook.data) 20 | hook.app.logger.silly('hook.params', hook.params) 21 | 22 | if (hook.result) { 23 | hook.app.logger.silly('hook.result', hook.result) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/api/hooks/index.js: -------------------------------------------------------------------------------- 1 | export * from './hooks.authentication.js' 2 | export * from './hooks.authorisations.js' 3 | export * from './hooks.logger.js' 4 | export * from './hooks.model.js' 5 | export * from './hooks.query.js' 6 | export * from './hooks.schemas.js' 7 | export * from './hooks.service.js' 8 | export * from './hooks.storage.js' 9 | export * from './hooks.users.js' 10 | export * from './hooks.push.js' 11 | -------------------------------------------------------------------------------- /core/api/index.js: -------------------------------------------------------------------------------- 1 | import makeDebug from 'debug' 2 | import services from './services/index.js' 3 | import * as hooks from './hooks/index.js' 4 | import { Schema } from '../common/index.js' 5 | 6 | export * from './services/index.js' 7 | export { hooks } 8 | export * from './db.js' 9 | export * from './authentication.js' 10 | export * from './application.js' 11 | export * from './marshall.js' 12 | export * from '../common/index.js' 13 | 14 | const debug = makeDebug('kdk:core') 15 | 16 | export default async function init (app) { 17 | debug('Initializing KDK core') 18 | 19 | Schema.initialize(app.get('schema')) 20 | await app.configure(services) 21 | } 22 | -------------------------------------------------------------------------------- /core/api/models/messages.model.mongodb.js: -------------------------------------------------------------------------------- 1 | export default function (app, options) { 2 | const db = options.db || app.db 3 | options.Model = db.collection('messages') 4 | // Collation provided in query ensure sorting to be case insensitive w.r.t. user's language 5 | // We built indices with collation to cover the most used languages, it requires different naming... 6 | options.Model.createIndex({ createdAt: -1 }) 7 | options.Model.createIndex({ title: 1 }, { name: 'title-en', collation: { locale: 'en', strength: 1 } }) 8 | options.Model.createIndex({ title: 1 }, { name: 'title-fr', collation: { locale: 'fr', strength: 1 } }) 9 | options.Model.createIndex({ body: 1 }, { name: 'body-en', collation: { locale: 'en', strength: 1 } }) 10 | options.Model.createIndex({ body: 1 }, { name: 'body-fr', collation: { locale: 'fr', strength: 1 } }) 11 | options.Model.createIndex({ author: 1 }, { name: 'author-en', collation: { locale: 'en', strength: 1 } }) 12 | options.Model.createIndex({ author: 1 }, { name: 'author-fr', collation: { locale: 'fr', strength: 1 } }) 13 | } -------------------------------------------------------------------------------- /core/api/models/users.model.mongodb.js: -------------------------------------------------------------------------------- 1 | export default function (app, options) { 2 | options.Model = app.db.collection('users') 3 | options.Model.createIndex({ email: 1 }, { unique: true }) 4 | // Collation provided in query ensure sorting to be case insensitive w.r.t. user's language 5 | // We built indices with collation to cover the most used languages, it requires different naming... 6 | options.Model.createIndex({ 'profile.name': 1 }, { name: 'name-en', collation: { locale: 'en', strength: 1 } }) 7 | options.Model.createIndex({ 'profile.name': 1 }, { name: 'name-fr', collation: { locale: 'fr', strength: 1 } }) 8 | // Inactive user account might expire at a given date 9 | options.Model.createIndex({ expireAt: 1 }, { expireAfterSeconds: 0 }) 10 | } 11 | -------------------------------------------------------------------------------- /core/api/services/account/account.hooks.js: -------------------------------------------------------------------------------- 1 | import common from 'feathers-hooks-common' 2 | import { enforcePasswordPolicy } from '../../hooks/index.js' 3 | 4 | const { when } = common 5 | 6 | export default { 7 | before: { 8 | all: [], 9 | find: [], 10 | get: [], 11 | create: [ 12 | when( 13 | (hook) => ['passwordChange', 'resetPwdShort', 'verifySignupSetPasswordLong', 'verifySignupSetPasswordShort'].includes(hook.data.action), 14 | enforcePasswordPolicy({ userAsItem: false, passwordField: 'value.password' }) 15 | ), 16 | ], 17 | update: [], 18 | patch: [], 19 | remove: [], 20 | }, 21 | 22 | after: { 23 | all: [], 24 | find: [], 25 | get: [], 26 | create: [], 27 | update: [], 28 | patch: [], 29 | remove: [], 30 | }, 31 | 32 | error: { 33 | all: [], 34 | find: [], 35 | get: [], 36 | create: [], 37 | update: [], 38 | patch: [], 39 | remove: [], 40 | }, 41 | } 42 | -------------------------------------------------------------------------------- /core/api/services/authorisations/authorisations.hooks.js: -------------------------------------------------------------------------------- 1 | import { populateSubjects, populateResource } from '../../hooks/index.js' 2 | 3 | export default { 4 | before: { 5 | all: [], 6 | find: [], 7 | get: [], 8 | create: [populateSubjects, populateResource], 9 | update: [], 10 | patch: [], 11 | remove: [populateSubjects, populateResource] 12 | }, 13 | 14 | after: { 15 | all: [], 16 | find: [], 17 | get: [], 18 | create: [], 19 | update: [], 20 | patch: [], 21 | remove: [] 22 | }, 23 | 24 | error: { 25 | all: [], 26 | find: [], 27 | get: [], 28 | create: [], 29 | update: [], 30 | patch: [], 31 | remove: [] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/api/services/databases/databases.hooks.js: -------------------------------------------------------------------------------- 1 | import common from 'feathers-hooks-common' 2 | 3 | const { disallow } = common 4 | 5 | export default { 6 | before: { 7 | // Only used internally 8 | all: [disallow('external')], 9 | find: [], 10 | get: [], 11 | create: [], 12 | update: [], 13 | patch: [], 14 | remove: [] 15 | }, 16 | 17 | after: { 18 | all: [], 19 | find: [], 20 | get: [], 21 | create: [], 22 | update: [], 23 | patch: [], 24 | remove: [] 25 | }, 26 | 27 | error: { 28 | all: [], 29 | find: [], 30 | get: [], 31 | create: [], 32 | update: [], 33 | patch: [], 34 | remove: [] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/api/services/databases/databases.service.js: -------------------------------------------------------------------------------- 1 | import mongoManager from 'feathers-mongodb-management' 2 | 3 | export default function (name, app, options) { 4 | return mongoManager.database({ adminDb: app.db.instance.admin(), client: app.db.client }) 5 | } 6 | -------------------------------------------------------------------------------- /core/api/services/import-export/import-export.hooks.js: -------------------------------------------------------------------------------- 1 | import commonHooks from 'feathers-hooks-common' 2 | 3 | export default { 4 | before: { 5 | all: [], 6 | find: [commonHooks.disallow()], 7 | get: [commonHooks.disallow()], 8 | create: [], 9 | update: [commonHooks.disallow()], 10 | patch: [commonHooks.disallow()], 11 | remove: [commonHooks.disallow()] 12 | }, 13 | 14 | after: { 15 | all: [], 16 | find: [], 17 | get: [], 18 | create: [], 19 | update: [], 20 | patch: [], 21 | remove: [] 22 | }, 23 | 24 | error: { 25 | all: [], 26 | find: [], 27 | get: [], 28 | create: [], 29 | update: [], 30 | patch: [], 31 | remove: [] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/api/services/import-export/import-export.service.js: -------------------------------------------------------------------------------- 1 | import { Service } from '@kalisio/feathers-import-export' 2 | import makeDebug from 'debug' 3 | 4 | const debug = makeDebug('kdk:import-export:service') 5 | 6 | export default function (name, app, options) { 7 | const config = app.get('import-export') 8 | debug('Creating import-export service with config ', config) 9 | const s3ServicePath = app.get('apiPath') + '/' + (config.s3Service) 10 | return new Service(Object.assign({ app }, config, { s3ServicePath })) 11 | } 12 | -------------------------------------------------------------------------------- /core/api/services/mailer/mailer.hooks.js: -------------------------------------------------------------------------------- 1 | import common from 'feathers-hooks-common' 2 | 3 | const { disallow } = common 4 | 5 | export default { 6 | before: { 7 | all: [disallow('external')], 8 | find: [], 9 | get: [], 10 | create: [], 11 | update: [], 12 | patch: [], 13 | remove: [] 14 | }, 15 | 16 | after: { 17 | all: [], 18 | find: [], 19 | get: [], 20 | create: [], 21 | update: [], 22 | patch: [], 23 | remove: [] 24 | }, 25 | 26 | error: { 27 | all: [], 28 | find: [], 29 | get: [], 30 | create: [], 31 | update: [], 32 | patch: [], 33 | remove: [] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/api/services/mailer/mailer.service.js: -------------------------------------------------------------------------------- 1 | import mailer from 'feathers-mailer' 2 | import makeDebug from 'debug' 3 | 4 | const debug = makeDebug('kdk:core:mailer:service') 5 | 6 | export default function (name, app, options) { 7 | // Keep track of mailer config in service options 8 | const config = Object.assign(options, app.get('mailer')) 9 | debug('Mailer created with config ', config) 10 | return mailer(config) 11 | } 12 | -------------------------------------------------------------------------------- /core/api/services/messages/messages.hooks.js: -------------------------------------------------------------------------------- 1 | import commonHooks from 'feathers-hooks-common' 2 | import fuzzySearch from 'feathers-mongodb-fuzzy-search' 3 | import { diacriticSearch, marshallComparisonQuery } from '../../hooks/index.js' 4 | 5 | export default { 6 | before: { 7 | all: [], 8 | find: [ 9 | fuzzySearch({ fields: ['title', 'body', 'author'] }), 10 | diacriticSearch(), 11 | marshallComparisonQuery 12 | ], 13 | get: [], 14 | create: [commonHooks.setNow('createdAt')], 15 | update: [], 16 | patch: [], 17 | remove: [] 18 | }, 19 | 20 | after: { 21 | all: [], 22 | find: [], 23 | get: [], 24 | create: [], 25 | update: [], 26 | patch: [], 27 | remove: [] 28 | }, 29 | 30 | error: { 31 | all: [], 32 | find: [], 33 | get: [], 34 | create: [], 35 | update: [], 36 | patch: [], 37 | remove: [] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /core/api/services/push/push.hooks.js: -------------------------------------------------------------------------------- 1 | import { deleteExpiredSubscriptions } from '@kalisio/feathers-webpush' 2 | import commonHooks from 'feathers-hooks-common' 3 | import { disallowExternalPush } from '../../hooks/index.js' 4 | 5 | export default { 6 | before: { 7 | all: [], 8 | find: [], 9 | get: [], 10 | create: [commonHooks.when(disallowExternalPush, commonHooks.disallow('external'))], 11 | update: [], 12 | patch: [], 13 | remove: [] 14 | }, 15 | 16 | after: { 17 | all: [], 18 | find: [], 19 | get: [], 20 | create: [deleteExpiredSubscriptions], 21 | update: [], 22 | patch: [], 23 | remove: [] 24 | }, 25 | 26 | error: { 27 | all: [], 28 | find: [], 29 | get: [], 30 | create: [], 31 | update: [], 32 | patch: [], 33 | remove: [] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/api/services/push/push.service.js: -------------------------------------------------------------------------------- 1 | import { Service } from '@kalisio/feathers-webpush' 2 | import makeDebug from 'debug' 3 | 4 | const debug = makeDebug('kdk:push:service') 5 | 6 | export default function (name, app, options) { 7 | const config = app.get('push') 8 | debug('Creating push service with config ', config) 9 | const service = new Service(Object.assign({ app }, config)) 10 | 11 | return service 12 | } 13 | -------------------------------------------------------------------------------- /core/api/services/storage/storage.hooks.js: -------------------------------------------------------------------------------- 1 | import common from 'feathers-hooks-common' 2 | 3 | const { disallow, discard } = common 4 | 5 | export default { 6 | before: { 7 | all: [], 8 | find: [], 9 | get: [], 10 | create: [], 11 | update: [disallow()], 12 | patch: [disallow()], 13 | remove: [] 14 | }, 15 | 16 | after: { 17 | all: [], 18 | find: [], 19 | get: [], 20 | create: [discard('buffer')], 21 | update: [], 22 | patch: [], 23 | remove: [discard('buffer')] 24 | }, 25 | 26 | error: { 27 | all: [], 28 | find: [], 29 | get: [], 30 | create: [], 31 | update: [], 32 | patch: [], 33 | remove: [] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/api/services/users/users.service.js: -------------------------------------------------------------------------------- 1 | export default { 2 | logout (user) { 3 | this.emit('logout', user) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /core/client/broadcaster.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import logger from 'loglevel' 3 | import config from 'config' 4 | 5 | export const Broadcaster = { 6 | initialize () { 7 | this.channelName = _.get(config, 'appSlug', _.kebabCase(_.get(config, 'appName', 'kdk'))) 8 | this.channel = new BroadcastChannel(this.channelName) 9 | logger.debug(`[KDK] Broadcaster initialized with channel '${this.channelName}'`) 10 | }, 11 | getChannelName () { 12 | return this.channelName 13 | }, 14 | getChannel () { 15 | return this.channel 16 | }, 17 | post (message) { 18 | this.channel.postMessage(message) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /core/client/components/KExpandable.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 37 | 38 | 50 | -------------------------------------------------------------------------------- /core/client/components/KLogo.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 32 | -------------------------------------------------------------------------------- /core/client/components/KSponsor.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /core/client/components/KTree.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 32 | -------------------------------------------------------------------------------- /core/client/components/KVersion.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 20 | -------------------------------------------------------------------------------- /core/client/components/account/KSubscriptionsManager.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 46 | -------------------------------------------------------------------------------- /core/client/components/account/index.js: -------------------------------------------------------------------------------- 1 | import KAccount from './KAccount.vue' 2 | import KProfile from './KProfile.vue' 3 | 4 | export { 5 | KAccount, 6 | KProfile 7 | } 8 | -------------------------------------------------------------------------------- /core/client/components/action/KBugReportAction.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 39 | -------------------------------------------------------------------------------- /core/client/components/action/KToggleFullscreenAction.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 26 | -------------------------------------------------------------------------------- /core/client/components/action/KToggleStickyVisibility.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 42 | -------------------------------------------------------------------------------- /core/client/components/action/KToggleWidgetVisibility.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 42 | -------------------------------------------------------------------------------- /core/client/components/action/index.js: -------------------------------------------------------------------------------- 1 | import KAction from './KAction.vue' 2 | import KBugReportAction from './KBugReportAction.vue' 3 | 4 | export { 5 | KAction, 6 | KBugReportAction 7 | } -------------------------------------------------------------------------------- /core/client/components/app/KHome.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /core/client/components/app/index.js: -------------------------------------------------------------------------------- 1 | import KAbout from './KAbout.vue' 2 | import KHome from './KHome.vue' 3 | import KPlatform from './KPlatform.vue' 4 | import KSettings from './KSettings.vue' 5 | import KTour from './KTour.vue' 6 | import KWelcome from './KWelcome.vue' 7 | 8 | export { 9 | KAbout, 10 | KHome, 11 | KPlatform, 12 | KSettings, 13 | KTour, 14 | KWelcome 15 | } 16 | -------------------------------------------------------------------------------- /core/client/components/chart/index.js: -------------------------------------------------------------------------------- 1 | import KChart from './KChart.vue' 2 | import KStatisticsChart from './KStatisticsChart.vue' 3 | import KTimeSeriesChart from './KTimeSeriesChart.vue' 4 | 5 | export { 6 | KChart, 7 | KStatisticsChart, 8 | KTimeSeriesChart 9 | } 10 | -------------------------------------------------------------------------------- /core/client/components/collection/KBoard.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 55 | -------------------------------------------------------------------------------- /core/client/components/collection/KCardSection.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 44 | -------------------------------------------------------------------------------- /core/client/components/collection/KFilterView.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 16 | -------------------------------------------------------------------------------- /core/client/components/collection/KSorter.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 47 | -------------------------------------------------------------------------------- /core/client/components/collection/KTimeFilterControl.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 41 | -------------------------------------------------------------------------------- /core/client/components/collection/index.js: -------------------------------------------------------------------------------- 1 | import KCard from './KCard.vue' 2 | import KCardSection from './KCardSection.vue' 3 | import KGrid from './KGrid.vue' 4 | import KItem from './KItem.vue' 5 | import KHistory from './KHistory.vue' 6 | import KTable from './KTable.vue' 7 | 8 | export { 9 | KCard, 10 | KCardSection, 11 | KGrid, 12 | KItem, 13 | KHistory, 14 | KTable 15 | } 16 | -------------------------------------------------------------------------------- /core/client/components/document/KCsv.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 46 | 47 | 53 | -------------------------------------------------------------------------------- /core/client/components/document/KHtml.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 34 | -------------------------------------------------------------------------------- /core/client/components/document/KMarkdown.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 42 | -------------------------------------------------------------------------------- /core/client/components/document/KVideo.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 40 | -------------------------------------------------------------------------------- /core/client/components/editor/index.js: -------------------------------------------------------------------------------- 1 | import KEditor from './KEditor.vue' 2 | import KModalEditor from './KModalEditor.vue' 3 | 4 | export { 5 | KEditor, 6 | KModalEditor 7 | } 8 | -------------------------------------------------------------------------------- /core/client/components/form/KEmailField.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 44 | -------------------------------------------------------------------------------- /core/client/components/form/KNumberField.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 44 | -------------------------------------------------------------------------------- /core/client/components/form/KPhoneField.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 43 | -------------------------------------------------------------------------------- /core/client/components/form/KUrlField.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 43 | -------------------------------------------------------------------------------- /core/client/components/form/index.js: -------------------------------------------------------------------------------- 1 | import KForm from './KForm.vue' 2 | import KView from './KView.vue' 3 | 4 | export { 5 | KForm, 6 | KView 7 | } 8 | -------------------------------------------------------------------------------- /core/client/components/index.js: -------------------------------------------------------------------------------- 1 | import KAvatar from './KAvatar.vue' 2 | import KChip from './KChip.vue' 3 | import KContent from './KContent.vue' 4 | import KDialog from './KDialog.vue' 5 | import KExpandable from './KExpandable.vue' 6 | import KLogo from './KLogo.vue' 7 | import KModal from './KModal.vue' 8 | import KPanel from './KPanel.vue' 9 | import KSponsor from './KSponsor.vue' 10 | import KScrollArea from './KScrollArea.vue' 11 | import KStamp from './KStamp.vue' 12 | import KTextArea from './KTextArea.vue' 13 | import KVersion from './KVersion.vue' 14 | 15 | export { 16 | KAvatar, 17 | KChip, 18 | KContent, 19 | KDialog, 20 | KExpandable, 21 | KLogo, 22 | KModal, 23 | KPanel, 24 | KSponsor, 25 | KScrollArea, 26 | KStamp, 27 | KTextArea, 28 | KVersion 29 | } 30 | 31 | export * from './action/index.js' 32 | export * from './layout/index.js' 33 | export * from './form/index.js' 34 | export * from './collection/index.js' 35 | export * from './editor/index.js' 36 | export * from './input/index.js' 37 | export * from './account/index.js' 38 | export * from './media/index.js' 39 | export * from './menu/index.js' 40 | export * from './screen/index.js' 41 | export * from './time/index.js' 42 | export * from './chart/index.js' 43 | export * from './messages/index.js' 44 | -------------------------------------------------------------------------------- /core/client/components/input/KPalette.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 42 | -------------------------------------------------------------------------------- /core/client/components/input/index.js: -------------------------------------------------------------------------------- 1 | import KColorChooser from './KColorChooser.vue' 2 | import KColorPicker from './KColorPicker.vue' 3 | import KIconChooser from './KIconChooser.vue' 4 | import KIconPicker from './KIconPicker.vue' 5 | import KOptionsChooser from './KOptionsChooser.vue' 6 | import KPalette from './KPalette.vue' 7 | import KShapePicker from './KShapePicker.vue' 8 | 9 | export { 10 | KColorChooser, 11 | KColorPicker, 12 | KIconChooser, 13 | KIconPicker, 14 | KOptionsChooser, 15 | KPalette, 16 | KShapePicker 17 | } 18 | -------------------------------------------------------------------------------- /core/client/components/layout/index.js: -------------------------------------------------------------------------------- 1 | import KFab from './KFab.vue' 2 | import KLayout from './KLayout.vue' 3 | import KPage from './KPage.vue' 4 | import KWindow from './KWindow.vue' 5 | 6 | export { 7 | KFab, 8 | KLayout, 9 | KPage, 10 | KWindow 11 | } 12 | -------------------------------------------------------------------------------- /core/client/components/media/KMarkdownViewer.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 56 | -------------------------------------------------------------------------------- /core/client/components/media/KShape.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 32 | -------------------------------------------------------------------------------- /core/client/components/media/index.js: -------------------------------------------------------------------------------- 1 | import KColorScale from './KColorScale.vue' 2 | import KImageViewer from './KImageViewer.vue' 3 | import KMarkdownViewer from './KMarkdownViewer.vue' 4 | import KMediaBrowser from './KMediaBrowser.vue' 5 | import KShape from './KShape.vue' 6 | 7 | export { 8 | KColorScale, 9 | KMarkdownViewer, 10 | KMediaBrowser, 11 | KImageViewer, 12 | KShape 13 | } 14 | -------------------------------------------------------------------------------- /core/client/components/menu/index.js: -------------------------------------------------------------------------------- 1 | import KMenu from './KMenu.vue' 2 | import KRadialFab from './KRadialFab.vue' 3 | import KRadialFabItem from './KRadialFabItem.vue' 4 | 5 | export { 6 | KMenu, 7 | KRadialFab, 8 | KRadialFabItem 9 | } 10 | -------------------------------------------------------------------------------- /core/client/components/messages/index.js: -------------------------------------------------------------------------------- 1 | import KMessageCard from './KMessageCard.vue' 2 | import KMessageComposer from './KMessageComposer.vue' 3 | 4 | export { 5 | KMessageCard, 6 | KMessageComposer 7 | } 8 | -------------------------------------------------------------------------------- /core/client/components/screen/KErrorScreen.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | -------------------------------------------------------------------------------- /core/client/components/screen/KLogoutScreen.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | -------------------------------------------------------------------------------- /core/client/components/screen/KOAuthLoginScreen.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /core/client/components/screen/KOAuthLogoutScreen.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 34 | -------------------------------------------------------------------------------- /core/client/components/screen/KScreenFooter.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /core/client/components/screen/KScreenHeader.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | -------------------------------------------------------------------------------- /core/client/components/screen/KUnauthorizedScreen.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /core/client/components/screen/index.js: -------------------------------------------------------------------------------- 1 | import KScreen from './KScreen.vue' 2 | 3 | export { 4 | KScreen 5 | } 6 | -------------------------------------------------------------------------------- /core/client/components/time/index.js: -------------------------------------------------------------------------------- 1 | import KAbsoluteTimeRange from './KAbsoluteTimeRange.vue' 2 | import KRelativeTimeRanges from './KRelativeTimeRanges.vue' 3 | 4 | export { 5 | KAbsoluteTimeRange, 6 | KRelativeTimeRanges 7 | } 8 | -------------------------------------------------------------------------------- /core/client/components/tool/KExportTool.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 58 | -------------------------------------------------------------------------------- /core/client/components/viewer/KModalViewer.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 48 | -------------------------------------------------------------------------------- /core/client/components/viewer/KViewer.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | -------------------------------------------------------------------------------- /core/client/composables/index.js: -------------------------------------------------------------------------------- 1 | export * from './activity.js' 2 | export * from './collection-counter.js' 3 | export * from './collection-filter.js' 4 | export * from './collection-timerange.js' 5 | export * from './collection.js' 6 | export * from './context.js' 7 | export * from './errors.js' 8 | export * from './layout.js' 9 | export * from './messages.js' 10 | export * from './pwa.js' 11 | export * from './session.js' 12 | export * from './schema.js' 13 | export * from './screen.js' 14 | export * from './selection.js' 15 | export * from './store.js' 16 | export * from './version.js' 17 | -------------------------------------------------------------------------------- /core/client/composables/messages.js: -------------------------------------------------------------------------------- 1 | import { api } from '../api.js' 2 | 3 | export function useMessages () { 4 | const messagesService = api.getService('messages') 5 | 6 | // Functions 7 | async function createMessage (message, query) { 8 | return messagesService.create(message, { query }) 9 | } 10 | 11 | // Expose 12 | return { 13 | createMessage 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /core/client/composables/screen.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import { ref, computed, readonly } from 'vue' 3 | import { useQuasar } from 'quasar' 4 | import { Fullscreen, toggleFullscreen, lockOrientation } from '../utils/utils.screen.js' 5 | 6 | const Orientation = ref(null) 7 | 8 | export function useScreen (options = {}) { 9 | // Data 10 | const $q = useQuasar() 11 | const denseBreakpoint = _.get(options, 'dense', 'sm') 12 | const wideBreakpoint = _.get(options, 'wide', 'sm') 13 | 14 | // Computed 15 | const dense = computed(() => { 16 | return $q.screen.lt[denseBreakpoint] 17 | }) 18 | const wide = computed(() => { 19 | return $q.screen.gt[wideBreakpoint] 20 | }) 21 | const orientation = computed(() => { 22 | return $q.screen.width >= $q.screen.height ? 'landscape' : 'portrait' 23 | }) 24 | 25 | // Expose 26 | return { 27 | Screen: readonly($q.screen), 28 | dense, 29 | wide, 30 | orientation, 31 | Fullscreen: readonly(Fullscreen), 32 | toggleFullscreen, 33 | lockOrientation 34 | } 35 | } -------------------------------------------------------------------------------- /core/client/composables/store.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import { reactive } from 'vue' 3 | 4 | // states store 5 | const Store = {} 6 | 7 | export function useStore (name, initialStore) { 8 | // data 9 | if (!_.has(Store, name)) { // Initialize on first call 10 | _.set(Store, name, reactive(initialStore || {})) 11 | } 12 | const store = _.get(Store, name) 13 | 14 | // functions 15 | function clear () { 16 | _.forOwn(store, function (value, key) { 17 | _.unset(store, key) 18 | }) 19 | } 20 | function set (path, value) { 21 | _.set(store, path, value) 22 | } 23 | function get (path, defaultValue) { 24 | // If no path is given return the whole store object 25 | return (path ? _.get(store, path, defaultValue) : store) 26 | } 27 | function unset (path) { 28 | _.unset(store, path) 29 | } 30 | function has (path) { 31 | return _.has(store, path) 32 | } 33 | function forOwn (f) { 34 | _.forOwn(store, function (value, key) { 35 | f(value, key) 36 | }) 37 | } 38 | 39 | // expose 40 | return { 41 | Store, 42 | store, 43 | clear, 44 | set, 45 | get, 46 | unset, 47 | has, 48 | forOwn 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/client/composables/version.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import config from 'config' 3 | import { ref, computed, readonly } from 'vue' 4 | import { Capabilities } from '../index.js' 5 | 6 | const Version = ref({ 7 | client: { 8 | number: _.get(config, 'version'), 9 | buildNumber: _.get(config, 'buildNumber') 10 | }, 11 | api: { 12 | number: undefined, 13 | buildNumber: undefined 14 | }, 15 | flavor: _.get(config, 'flavor') 16 | }) 17 | let isInitialized = false 18 | 19 | export function useVersion () { 20 | // Computed 21 | const clientVersionName = computed(() => { 22 | const clientVersion = Version.value.client 23 | let version = clientVersion.number 24 | if (clientVersion.buildNumber) version += ` (${clientVersion.buildNumber})` 25 | return version 26 | }) 27 | const apiVersionName = computed(() => { 28 | const apiVersion = Version.value.api 29 | let version = apiVersion.number 30 | if (apiVersion.buildNumber) version += ` (${apiVersion.buildNumber})` 31 | return version 32 | }) 33 | 34 | // Immediate 35 | if (!isInitialized) { 36 | isInitialized = true 37 | Version.value.api.number = Capabilities.get('version') 38 | Version.value.api.buildNumber = Capabilities.get('buildNumber') 39 | } 40 | 41 | // Expose 42 | return { 43 | Version: readonly(Version), 44 | clientVersionName, 45 | apiVersionName 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/client/context.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import logger from 'loglevel' 3 | import config from 'config' 4 | import { api } from './api.js' 5 | import { Store } from './store.js' 6 | 7 | // Export singleton 8 | export const Context = { 9 | serviceName: null, 10 | service: null, 11 | initialize () { 12 | this.serviceName = _.get(config, 'context.service') 13 | if (!_.isEmpty(this.serviceName)) { 14 | // initialize the store 15 | Store.set('context', null) 16 | logger.debug(`[KDK] Context configured with service '${this.serviceName}'`) 17 | } 18 | }, 19 | get () { 20 | return Store.get('context') 21 | }, 22 | getId () { 23 | return _.get(this.get(), '_id') 24 | }, 25 | getRef () { 26 | return Store.getRef('context') 27 | }, 28 | getService () { 29 | if (_.isEmpty(this.serviceName)) throw new Error('[KDK] Context service undefined !') 30 | if (this.service) return this.service 31 | this.service = api.getService(this.serviceName) 32 | if (_.isNil(this.service)) throw new Error('[KDK] Context service not found !') 33 | return this.service 34 | }, 35 | set (context) { 36 | Store.set('context', context) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /core/client/directives/index.js: -------------------------------------------------------------------------------- 1 | export * from './v-drop-file.js' 2 | export * from './v-hover.js' -------------------------------------------------------------------------------- /core/client/directives/v-hover.js: -------------------------------------------------------------------------------- 1 | import { Platform } from '../platform.js' 2 | 3 | export const vHover = { 4 | 5 | mounted (el, binding) { 6 | if (Platform.touch) return 7 | el.__vHoverEnter__ = binding.value.enter || (() => {}) 8 | el.__vHoverOver__ = binding.value.over || (() => {}) 9 | el.__vHoverLeave__ = binding.value.leave || (() => {}) 10 | 11 | // Add Event Listeners 12 | el.addEventListener('mouseenter', el.__vHoverEnter__) 13 | el.addEventListener('mouseover', el.__vHoverOver__) 14 | el.addEventListener('mouseleave', el.__vHoverLeave__) 15 | }, 16 | 17 | beforeUnmount (el, binding) { 18 | // Remove Event Listeners 19 | el.removeEventListener('mouseenter', el.__vHoverEnter__) 20 | el.removeEventListener('mouseover', el.__vHoverOver__) 21 | el.removeEventListener('mouseleave', el.__vHoverLeave__) 22 | delete el.__vHoverEnter__ 23 | delete el.__vHoverOver__ 24 | delete el.__vHoverLeave__ 25 | } 26 | } -------------------------------------------------------------------------------- /core/client/events.js: -------------------------------------------------------------------------------- 1 | import { EventBus } from 'quasar' 2 | 3 | export const Events = new EventBus() 4 | -------------------------------------------------------------------------------- /core/client/hooks/hooks.events.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import { Events } from '../events.js' 3 | 4 | export function emit (hook) { 5 | if (_.get(hook, `params.skip-${hook.type}-event`)) return 6 | Events.emit(hook.type + '-hook', hook) 7 | } 8 | -------------------------------------------------------------------------------- /core/client/hooks/hooks.logger.js: -------------------------------------------------------------------------------- 1 | // A hook that logs service method before, after and error 2 | import logger from 'loglevel' 3 | 4 | export function log (hook) { 5 | let message = `[KDK] ${hook.type}: ${hook.path} - Method: ${hook.method}` 6 | 7 | if (hook.type === 'error') { 8 | message += `: ${hook.error.message}` 9 | } 10 | 11 | logger.debug(message) 12 | if (hook.error) { 13 | logger.error(hook.error) 14 | } 15 | if (hook.data) { 16 | logger.trace(hook.data) 17 | } 18 | if (hook.params) { 19 | logger.trace(hook.params) 20 | } 21 | if (hook.result) { 22 | logger.trace(hook.result) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/client/hooks/hooks.users.js: -------------------------------------------------------------------------------- 1 | export async function checkUnique (hook) { 2 | const accountService = hook.app.getService('account') 3 | try { 4 | await accountService.create({ 5 | action: 'checkUnique', 6 | value: { email: hook.data.email } 7 | }, { 8 | // As we manage error we make this call transparent from the client perspective 9 | // This will avoid displaying an error message twice 10 | 'skip-before-event': true, 11 | 'skip-after-event': true, 12 | 'skip-error-event': true 13 | }) 14 | } catch (error) { 15 | // BadRequest is thrown when the user already exists, use a more specific message in this case 16 | if (error.code === 400) { 17 | error.data.translation = { key: 'EMAIL_ALREADY_TAKEN' } 18 | } 19 | throw error 20 | } 21 | return hook 22 | } 23 | -------------------------------------------------------------------------------- /core/client/hooks/index.js: -------------------------------------------------------------------------------- 1 | export * from './hooks.events.js' 2 | export * from './hooks.logger.js' 3 | export * from './hooks.offline.js' 4 | export * from './hooks.users.js' 5 | -------------------------------------------------------------------------------- /core/client/local-storage.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import logger from 'loglevel' 3 | import config from 'config' 4 | 5 | export const LocalStorage = { 6 | initialize () { 7 | this.prefix = _.get(config, 'appSlug', _.kebabCase(_.get(config, 'appName', 'kdk'))) 8 | logger.debug(`[KDK] LocalStorage initialized with prefix: '${this.prefix}'`) 9 | }, 10 | localKey (key) { 11 | const keyPrefix = `${this.prefix}-` 12 | if (_.startsWith(keyPrefix)) return key 13 | return `${keyPrefix}${key}` 14 | }, 15 | set (key, value) { 16 | const jsonValue = JSON.stringify(value) 17 | window.localStorage.setItem(this.localKey(key), jsonValue) 18 | }, 19 | has (key) { 20 | const value = window.localStorage.getItem(this.localKey(key)) 21 | return !_.isNil(value) 22 | }, 23 | get (key, defaultValue) { 24 | const value = window.localStorage.getItem(this.localKey(key)) 25 | if (_.isNil(value)) { 26 | logger.debug(`[KDK] Cannot find local storage value with key '${key}'. Returning default value '${defaultValue}'`) 27 | return defaultValue 28 | } 29 | return JSON.parse(value) 30 | }, 31 | clear (key) { 32 | window.localStorage.removeItem(this.localKey(key)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/client/mixins/index.js: -------------------------------------------------------------------------------- 1 | export * from './mixin.base-activity.js' 2 | export * from './mixin.base-editor.js' 3 | export * from './mixin.base-item.js' 4 | export * from './mixin.base-field.js' 5 | export * from './mixin.base-viewer.js' 6 | export * from './mixin.base-modal.js' 7 | export * from './mixin.object-proxy.js' 8 | export * from './mixin.schema-proxy.js' 9 | export * from './mixin.service.js' 10 | -------------------------------------------------------------------------------- /core/client/mixins/mixin.base-modal.js: -------------------------------------------------------------------------------- 1 | export const baseModal = { 2 | emits: ['opened', 'closed'], 3 | props: { 4 | routerMode: { 5 | type: Boolean, 6 | default: true 7 | } 8 | }, 9 | data () { 10 | return { 11 | isModalOpened: false, 12 | isModalMaximized: false 13 | } 14 | }, 15 | methods: { 16 | openModal (maximized = false) { 17 | // Can be overloaded if needed 18 | this.isModalMaximized = maximized 19 | this.isModalOpened = true 20 | this.$emit('opened') 21 | }, 22 | closeModal () { 23 | this.isModalOpened = false 24 | if (this.routerMode) { 25 | this.$router.push(this.previousRoute) 26 | } 27 | this.$emit('closed') 28 | } 29 | }, 30 | created () { 31 | if (this.routerMode) { 32 | this.previousRoute = this.$router.options.history.state.back 33 | this.openModal() 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/client/mixins/mixin.base-viewer.js: -------------------------------------------------------------------------------- 1 | export const baseViewer = { 2 | props: { 3 | perspective: { 4 | type: String, 5 | default: '' 6 | }, 7 | clearButton: { 8 | type: String, 9 | default: '' 10 | }, 11 | resetButton: { 12 | type: String, 13 | default: '' 14 | } 15 | }, 16 | computed: { 17 | viewerTitle () { 18 | // Retuns the schema title 19 | if (this.getSchema()) { 20 | const schemaTitle = this.getSchema().title 21 | return this.$t(schemaTitle, { object: this.getObject() }) 22 | } 23 | return '' 24 | } 25 | }, 26 | methods: { 27 | getSchemaName () { 28 | // When used with a service by default use the same name for schema as for service 29 | let schemaName = this.service + '.get' 30 | if (this.perspective) { 31 | schemaName += ('-' + this.perspective) 32 | } 33 | return schemaName 34 | }, 35 | async refresh () { 36 | // We can then load the schema/object and local refs in parallel 37 | await Promise.all([ 38 | this.loadSchema(this.getSchemaName()), 39 | this.loadObject() 40 | ]) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /core/client/mixins/mixin.object-proxy.js: -------------------------------------------------------------------------------- 1 | import { createQuerablePromise } from '../utils/index.js' 2 | 3 | export const objectProxy = { 4 | props: { 5 | objectId: { 6 | type: String, 7 | default: '' 8 | } 9 | }, 10 | data () { 11 | return { 12 | object: null 13 | } 14 | }, 15 | methods: { 16 | getObject () { 17 | return this.object 18 | }, 19 | getObjectId () { 20 | return this.object ? this.object._id : '' 21 | }, 22 | loadObject () { 23 | if (!this.objectId) { 24 | this.object = null 25 | return Promise.resolve(null) 26 | } 27 | // Create a new mixin promise if required 28 | const objectChanged = (this.getObjectId() !== this.objectId) 29 | if (!this.objectPromise || objectChanged) { 30 | this.objectPromise = createQuerablePromise((resolve, reject) => { 31 | this.getService() 32 | .get(this.objectId) 33 | .then(object => { 34 | this.object = object 35 | resolve(object) 36 | }) 37 | .catch(error => { 38 | reject(error) 39 | }) 40 | }) 41 | } 42 | return this.objectPromise 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /core/client/mixins/mixin.service.js: -------------------------------------------------------------------------------- 1 | export const service = { 2 | props: { 3 | service: { 4 | type: String, 5 | default: '' 6 | } 7 | }, 8 | 9 | methods: { 10 | getService () { 11 | const service = this.$api.getService(this.service) 12 | if (!service) { 13 | throw new Error('Cannot retrieve target service ' + this.service) 14 | } 15 | return service 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/client/readers/index.js: -------------------------------------------------------------------------------- 1 | export * from './reader.json.js' 2 | export * from './reader.csv.js' 3 | export * from './reader.blob.js' 4 | -------------------------------------------------------------------------------- /core/client/readers/reader.blob.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import logger from 'loglevel' 3 | import { i18n } from '../i18n.js' 4 | 5 | export const BLOBReader = { 6 | read (files, options) { 7 | if (files.length !== 1) { 8 | logger.debug('[KDK] invalid \'files\' arguments') 9 | return 10 | } 11 | const file = files[0] 12 | logger.debug(`[KDK] reading Blob file ${file.name}`) 13 | return new Promise((resolve, reject) => { 14 | const reader = new FileReader() 15 | reader.onloadend = () => { 16 | const content = reader.result 17 | if (!content) { 18 | reject(new Error(i18n.t('errors.INVALID_BLOB_FILE', { file: file.name }))) 19 | return 20 | } 21 | resolve(content) 22 | } 23 | reader.onerror = (error) => { 24 | logger.debug(error) 25 | reject(new Error(i18n.t('errors.CANNOT_READ_FILE', { file: file.name }), { errors: error })) 26 | } 27 | const expectedType = _.get(options, 'type', 'arrayBuffer') 28 | if (expectedType === 'dataUrl') { 29 | reader.readAsDataURL(file) 30 | } else { 31 | if (expectedType !== 'arrayBuffer') logger.error(`[KDK] Undefined expected type ${expectedType}. Read as Array buffer.`) 32 | reader.readAsArrayBuffer(file) 33 | } 34 | }) 35 | }, 36 | getAdditionalFiles () { 37 | return [] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /core/client/readers/reader.csv.js: -------------------------------------------------------------------------------- 1 | import logger from 'loglevel' 2 | import Papa from 'papaparse' 3 | import { i18n } from '../i18n.js' 4 | 5 | export const CSVReader = { 6 | read (files, options) { 7 | if (files.length !== 1) { 8 | logger.debug('[KDK] invalid \'files\' arguments') 9 | return 10 | } 11 | const file = files[0] 12 | logger.debug(`[KDK] reading CSV file ${file.name}`) 13 | return new Promise((resolve, reject) => { 14 | const reader = new FileReader() 15 | reader.onloadend = () => { 16 | let content = reader.result 17 | const papaParseOptions = Object.assign({ skipEmptyLines: true }, options) 18 | content = Papa.parse(content, papaParseOptions) 19 | if (content.errors.length > 0) { 20 | logger.debug(content.errors) 21 | reject(new Error(i18n.t('errors.INVALID_CSV_FILE', { file: file.name }), { errors: content.errors })) 22 | return 23 | } 24 | resolve(content.data) 25 | } 26 | reader.onerror = (error) => { 27 | logger.debug(error) 28 | reject(new Error(i18n.t('errors.CANNOT_READ_FILE', { file: file.name }), { errors: error })) 29 | } 30 | reader.readAsText(file) 31 | }) 32 | }, 33 | getAdditionalFiles () { 34 | return [] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/client/readers/reader.json.js: -------------------------------------------------------------------------------- 1 | import logger from 'loglevel' 2 | import { i18n } from '../i18n.js' 3 | 4 | export const JSONReader = { 5 | read (files, options) { 6 | if (files.length !== 1) { 7 | logger.debug('[KDK] invalid \'files\' arguments') 8 | return 9 | } 10 | const file = files[0] 11 | logger.debug(`[KDK] reading JSON file ${file.name}`) 12 | return new Promise((resolve, reject) => { 13 | const reader = new FileReader() 14 | reader.onloadend = () => { 15 | let content = reader.result 16 | try { 17 | content = JSON.parse(content) 18 | } catch (error) { 19 | logger.debug(error) 20 | reject(new Error(i18n.t('errors.INVALID_JSON_FILE', { file }), { errors: error })) 21 | } 22 | resolve(content) 23 | } 24 | reader.onerror = (error) => { 25 | logger.debug(error) 26 | reject(new Error(i18n.t('errors.CANNOT_READ_FILE', { file }), { errors: error })) 27 | } 28 | reader.readAsText(file) 29 | }) 30 | }, 31 | getAdditionalFiles () { 32 | return [] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/client/sorter.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import { Events } from './events.js' 3 | import { Store } from './store.js' 4 | 5 | // Export singleton 6 | export const Sorter = { 7 | initialize () { 8 | // This object is used to sort collections 9 | Store.set('sorter', { field: 'name', order: '1', query: {} }) 10 | // Make filter react to external changes to update the query 11 | Events.on('sorter-changed', () => this.updateSorterQuery()) 12 | }, 13 | get () { 14 | return Store.get('sorter') 15 | }, 16 | getField () { 17 | return this.get().field 18 | }, 19 | getOrder () { 20 | return this.get().order 21 | }, 22 | getQuery () { 23 | return Store.get('sorter.query') 24 | }, 25 | // Build sort query 26 | updateSorterQuery () { 27 | const query = { $sort: { [this.getField()]: this.getOrder() } } 28 | // Avoid reentrance as we listen to other filter property changes 29 | if (!_.isEqual(query, this.getQuery())) Store.patch('sorter', { query }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /core/client/store.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import { toRef } from 'vue' 3 | import { useStore } from './composables/store.js' 4 | import { Events } from './events.js' 5 | 6 | const { store, set, get, unset, has } = useStore('store') 7 | 8 | // Export singleton 9 | export const Store = Object.assign(store, { 10 | get, 11 | has, 12 | // Override write methods to send events 13 | set (path, value) { 14 | const previousValue = get(path) 15 | set(path, value) 16 | const eventName = _.kebabCase(`${path}-changed`) 17 | Events.emit(eventName, value, previousValue) 18 | Events.emit('store-changed', path, value, previousValue) 19 | }, 20 | patch (path, value) { 21 | // Patching should not change the object reference to maintain reactivity 22 | const previousValue = get(path) 23 | if (previousValue) { 24 | Object.assign(previousValue, value) 25 | this.set(path, previousValue) 26 | } 27 | }, 28 | unset (path) { 29 | unset(path) 30 | const eventName = _.kebabCase(`${path}-changed`) 31 | Events.emit(eventName, undefined) 32 | }, 33 | getRef (path) { 34 | const index = path.lastIndexOf('.') 35 | const key = path.substring(index + 1) 36 | const object = (index < 0 ? store : get(path.replace(`.${key}`, ''))) 37 | return toRef(object, key) 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /core/client/template-context.js: -------------------------------------------------------------------------------- 1 | import { useStore } from './composables/store.js' 2 | import { Events } from './events.js' 3 | 4 | const { store, set, get, unset, has } = useStore('template-context') 5 | 6 | // This is a singleton used to inject data in string template evaluation contexts (lodash) 7 | export const TemplateContext = Object.assign(store, { 8 | get, 9 | has, 10 | unset, 11 | // Override write methods to send events 12 | set (path, value) { 13 | const previousValue = get(path) 14 | set(path, value) 15 | Events.emit('template-context-changed', path, value, previousValue) 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /core/client/utils/utils.files.js: -------------------------------------------------------------------------------- 1 | import path from 'path-browserify' 2 | 3 | export function getFileName (filePath) { 4 | return path.basename(filePath) 5 | } 6 | 7 | export function getExtension (filePath) { 8 | return path.extname(filePath) 9 | } 10 | 11 | export function getBaseName (filePath) { 12 | return path.basename(filePath, getExtension(filePath)) 13 | } 14 | 15 | export function getDir (filePath) { 16 | return path.dirname(filePath) 17 | } 18 | -------------------------------------------------------------------------------- /core/client/utils/utils.items.js: -------------------------------------------------------------------------------- 1 | export const CardSectionProps = { 2 | title: { 3 | type: String, 4 | default: '' 5 | }, 6 | item: { 7 | type: Object, 8 | default: () => null 9 | }, 10 | actions: { 11 | type: Array, 12 | default: () => null 13 | }, 14 | actionsFilter: { 15 | type: [String, Array], 16 | default: () => null 17 | }, 18 | hideSeparator: { 19 | type: Boolean, 20 | default: false 21 | }, 22 | hideHeader: { 23 | type: Boolean, 24 | default: false 25 | }, 26 | dense: { 27 | type: Boolean, 28 | default: false 29 | } 30 | } -------------------------------------------------------------------------------- /core/client/utils/utils.locale.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import config from 'config' 3 | 4 | export function getBrowserLocale () { 5 | return navigator.language || 6 | _.get(navigator, 'languages.0') || 7 | navigator.browserLanguage || 8 | navigator.userLanguage || 9 | navigator.systemLanguage 10 | } 11 | 12 | export function getLocale (languageOnly = true) { 13 | const locale = _.get(config, 'locale.default', getBrowserLocale()) 14 | if (!languageOnly) return locale 15 | const codes = _.split(locale, '-') 16 | return _.head(codes) 17 | } 18 | 19 | export function getFallbackLocale (languageOnly = true) { 20 | const locale = _.get(config, 'locale.fallbackLocale', 'en-GB') 21 | if (!languageOnly) return locale 22 | const codes = _.split(locale, '-') 23 | return _.head(codes) 24 | } 25 | -------------------------------------------------------------------------------- /core/client/utils/utils.math.js: -------------------------------------------------------------------------------- 1 | export function clamp (value, min, max) { 2 | return Math.min(Math.max(value, min), max) 3 | } 4 | 5 | export function easeOut (t, linearity = 0.5) { 6 | return 1 - Math.pow(1 - t, 1 / linearity) 7 | } 8 | 9 | export function linear (t, initial = 0, final = 1) { 10 | return initial + t * final 11 | } 12 | 13 | export function cubicBezier (t, x1 = 0.42, y1 = 0, x2 = 0.58, y2 = 1) { 14 | return ( 15 | (1 - t) * (1 - t) * (1 - t) * y1 + 16 | 3 * (1 - t) * (1 - t) * t * x1 + 17 | 3 * (1 - t) * t * t * x2 + 18 | t * t * t * y2 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /core/client/utils/utils.time.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | import { Time } from '../time.js' 3 | 4 | // Add UTC offset to timezone name 5 | export function getTimezoneLabel (timezone) { 6 | const offset = moment().tz(timezone).format('Z') 7 | return `${timezone} (${offset})` 8 | } 9 | 10 | // Convert to local time with timezone offset 11 | // datetime must be expressed as an ISOString 12 | export function toLocalTimezone (datetime, timezone) { 13 | return timezone ? moment.tz(datetime, timezone) : moment(datetime).local() 14 | } 15 | 16 | // Convert from moment date/time to quasar format in local time zone 17 | export function toQuasarDate (date, format) { 18 | return Time.convertToLocal(date).format(format) 19 | } 20 | 21 | export function toQuasarTime (time, format) { 22 | return Time.convertToLocal(time).format(format) 23 | } 24 | 25 | // Convert from quasar format in local time zone to moment date/time 26 | export function fromQuasarDate (date, format) { 27 | return (Time.getFormatTimezone() 28 | ? moment.tz(date, format, Time.getFormatTimezone()) 29 | : moment(date, format)) 30 | } 31 | 32 | export function fromQuasarTime (time, format) { 33 | return (Time.getFormatTimezone() 34 | ? moment.tz(time, format, Time.getFormatTimezone()) 35 | : moment(time, format)) 36 | } -------------------------------------------------------------------------------- /core/common/errors.js: -------------------------------------------------------------------------------- 1 | export class KError extends Error {} 2 | -------------------------------------------------------------------------------- /core/common/index.js: -------------------------------------------------------------------------------- 1 | // We faced a bug in babel so that transform-runtime with export * from 'x' generates import statements in transpiled code 2 | // Tracked here : https://github.com/babel/babel/issues/2877 3 | // We tested the workaround given here https://github.com/babel/babel/issues/2877#issuecomment-270700000 with success so far 4 | import * as errors from './errors.js' 5 | import * as permissions from './permissions.js' 6 | 7 | export { errors } 8 | export { permissions } 9 | export * from './schema.js' 10 | export * from './utils.js' 11 | -------------------------------------------------------------------------------- /core/common/schema.js: -------------------------------------------------------------------------------- 1 | import Ajv from 'ajv' 2 | import addFormats from 'ajv-formats' 3 | import addKeywords from 'ajv-keywords' 4 | 5 | const defaultOptions = { 6 | allErrors: true, 7 | strict: false, 8 | $data: true, 9 | keywords: ['field'] 10 | } 11 | 12 | export const Schema = { 13 | initialize (options) { 14 | this.ajv = new Ajv(options || defaultOptions) 15 | addKeywords(this.ajv) 16 | addFormats(this.ajv) 17 | }, 18 | register (schema) { 19 | if (!this.ajv) throw new Error('Schema must be initialized first') 20 | if (!schema.$id) throw new Error('the schema must have an `$id` property') 21 | return this.ajv.getSchema(schema.$id) || this.ajv.compile(schema) 22 | }, 23 | addKeyword (keyword) { 24 | if (!this.ajv) throw new Error('Schema must be initialized first') 25 | this.ajv.addKeyword(keyword) 26 | }, 27 | getKeyword (keyword) { 28 | if (!this.ajv) throw new Error('Schema must be initialized first') 29 | return this.ajv.getKeyword(keyword) 30 | }, 31 | removeKeyword (keyword) { 32 | if (!this.ajv) throw new Error('Schema must be initialized first') 33 | this.ajv.removeKeyword(keyword) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/common/schemas/messages.update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/schemas/messages.edit.json", 4 | "description": "Messages edition schema", 5 | "type": "object", 6 | "properties": { 7 | "body": { 8 | "type": "string", 9 | "minLength": 1, 10 | "field": { 11 | "component": "form/KTextareaField" 12 | } 13 | } 14 | }, 15 | "required": ["body"] 16 | } 17 | -------------------------------------------------------------------------------- /core/common/schemas/users.update-profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "http://kalisio.xyz/schemas/users.update-profile.json#", 4 | "title": "schemas.OBJECT_NAME", 5 | "description": "User profile", 6 | "type": "object", 7 | "properties": { 8 | "name": { 9 | "type": "string", 10 | "maxLength": 128, 11 | "minLength": 3, 12 | "field": { 13 | "component": "form/KTextField", 14 | "label": "schemas.NAME_FIELD_LABEL" 15 | } 16 | }, 17 | "avatar": { 18 | "type": "object", 19 | "field": { 20 | "component": "form/KFileField", 21 | "label": "schemas.AVATAR_FIELD_LABEL", 22 | "mimeTypes": ".png,.jpg,.jpeg,.webp", 23 | "maxSize": 524288, 24 | "readContent": false, 25 | "storage": { 26 | "path": "avatars/<%= _id %>" 27 | } 28 | } 29 | } 30 | }, 31 | "required": ["name"] 32 | } 33 | 34 | -------------------------------------------------------------------------------- /docs/.vitepress/public/images/PWA-client-server-sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/PWA-client-server-sync.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/PWA-offline-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/PWA-offline-client.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/PWA-online-client-caching.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/PWA-online-client-caching.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/PWA-online-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/PWA-online-client.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/PWA-server-server-sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/PWA-server-server-sync.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/aggregated-feature-data-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/aggregated-feature-data-model.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/airtac-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/airtac-list.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/aktnmap-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/aktnmap-layout.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/aktnmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/aktnmap.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/alert-data-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/alert-data-model.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/animated-wall.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/animated-wall.gif -------------------------------------------------------------------------------- /docs/.vitepress/public/images/attributionClosed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/attributionClosed.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/attributionOpened.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/attributionOpened.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/catalog-data-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/catalog-data-model.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/component-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/component-view.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/desaturate-post-process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/desaturate-post-process.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/editor-lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/editor-lifecycle.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/feathers-services.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/feathers-services.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/feature-data-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/feature-data-model.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/gradient-path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/gradient-path.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/great-circle-2D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/great-circle-2D.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/great-circle-3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/great-circle-3D.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/item-collections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/item-collections.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/kalisio-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/kalisio-banner.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/kano-3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/kano-3D.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/kano-components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/kano-components.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/kano-iframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/kano-iframe.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/kano-layout-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/kano-layout-1.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/kano-layout-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/kano-layout-2.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/kano-style-2D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/kano-style-2D.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/kano-style-3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/kano-style-3D.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/kano-weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/kano-weather.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/kdk-workspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/kdk-workspace.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/layers-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/layers-panel.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/line-gradient-2D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/line-gradient-2D.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/line-offset-2D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/line-offset-2D.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/marker-cluster-2D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/marker-cluster-2D.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/marker-cluster-3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/marker-cluster-3D.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/operations-methods-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/operations-methods-events.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/timeseries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/timeseries.png -------------------------------------------------------------------------------- /docs/.vitepress/public/images/wall-3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/.vitepress/public/images/wall-3D.png -------------------------------------------------------------------------------- /docs/.vitepress/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kalisio Develoment Kit", 3 | "short_name": "KDK", 4 | "icons": [ 5 | { 6 | "src": "https://s3.eu-central-1.amazonaws.com/kalisioscope/kdk/kdk-logo-black-128x128.png", 7 | "sizes": "128x128", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "https://s3.eu-central-1.amazonaws.com/kalisioscope/kdk/kdk-logo-black-192x192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "https://s3.eu-central-1.amazonaws.com/kalisioscope/kdk/kdk-logo-black-512x512.png", 17 | "sizes": "512x512", 18 | "type": "image/png" 19 | } 20 | ], 21 | "start_url": "/index.html", 22 | "display": "standalone" 23 | } -------------------------------------------------------------------------------- /docs/.vitepress/public/theme.drawio: -------------------------------------------------------------------------------- 1 | zZhdb5swFIZ/DZeR+AqUyzVpu4t1nRQp3a4mBxuwamxiTCD59TPBBjI6rVPnkggJ5z32wT7POTaJ5a3y5oGDIntkEBHLtWFjeWvLdR37JpC3Vjl2ShQoIeUYqk6DsMEnpEcqtcIQlRcdBWNE4OJSjBmlKBYXGuCc1ZfdEkYun1qAFE2ETQzIVH3GUGSdeuOGg/4Z4TTTT3aCqLPkQHdWKykzAFk9krw7y1txxkTXypsVIm3wdFy6cfd/sPYT44iKtwzYbsH+8Vgtw/DwZb0+QfpcbxfKywGQSi3YcgMi/d0mTLqVsxZHFYpgXzFtWJRnUJ9kB8cvmsEoW2l7LzjOAT9qZ3JWnb/OqgLSu3Y5qyhE7UQdaa4zLNCmAHFrrWVeSS0TOVHm1tOKEcbPY73k/Gl1TMhIt3eBvKReCs5ekLZQRlE/hXH0dCgQF6gZSSqaD4jlSLRLsrV1qciq1O5J10OiODqPs1GS6HFA5Wbaux7wyYYi+A80XUM0SyTLC34QT/s2kNeUZ+zBJNkZ5OlG18bTM8QTxDE605uvOJMwSWyDMH3v2mD6hmDKunyZFWXo2yEwiHIZzoiS/fiZbBbfxOnp6fv+64GTAJvbZzFN2LVX5YTbK3TffmRGc6M0tcUWrMQCH9CcOMPdLvL/8gb0LpyTE3N2nKY2WYpS8OE4X8X2+1tR6AMf/B+ckzNzdpxLQzhrwCmm6ZzFCe0o8hyDxTk5Ng3SlF+HH7Jn2+jvAO/uFw== -------------------------------------------------------------------------------- /docs/.vitepress/theme/custom.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --vp-c-brand-1: #3aa7ca; 3 | --vp-c-brand-2: rgb(58, 167, 202, .75); 4 | --vp-c-brand-3: #3aa7ca; 5 | } -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.js: -------------------------------------------------------------------------------- 1 | import theme from 'vitepress-theme-kalisio' 2 | import './custom.css' 3 | export default theme -------------------------------------------------------------------------------- /docs/about/contact.md: -------------------------------------------------------------------------------- 1 | # Contact 2 | 3 | Please feel free to join our [slack channel](https://kalisio.slack.com/) using the [invitation link](https://join.slack.com/t/kalisio/shared_invite/zt-mfyu6evk-ehKFK7wSle4lX9imk5huew). -------------------------------------------------------------------------------- /docs/about/introduction.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | The **Kalisio Development Kit** (**KDK**) aims to simplify the development of geospatial web applications running on desktop or mobile devices. It is a strongly opiniated stack initially developed to build multitenancy applications provided as SaaS (i.e. Cloud) like [Kalisio Crisis](https://crisis.kalisio.com). However, you can also build legacy applications because of the modularity and the flexibility of the KDK. 4 | 5 | ![Kano application built with the KDK](../.vitepress/public/images/kano-weather.png) 6 | 7 | Our objective is to propose a microservice based platform. Each building block has the responsibility to deliver specific and limited functionalities. Such an architectural approach plays a key role in helping us face the challenge of maintaining several mature products that need scalability within multiple contexts in terms of processing, storage, and features delivery. 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/about/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | MIT License 4 | 5 | Copyright (c) 2017-20xx Kalisio 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /docs/about/roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | The roadmap is available on [Github](https://github.com/kalisio/kdk/projects/1). 4 | 5 | ## Release Notes 6 | 7 | Release Notes for each modules are available on Github: 8 | 9 | The changelogs are available on [Github](https://github.com/kalisio/kdk/blob/master/CHANGELOG.md) 10 | 11 | -------------------------------------------------------------------------------- /docs/api/core/components/diagrams/kcard-expanded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/api/core/components/diagrams/kcard-expanded.png -------------------------------------------------------------------------------- /docs/api/core/components/diagrams/kcard-header-footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/api/core/components/diagrams/kcard-header-footer.png -------------------------------------------------------------------------------- /docs/api/core/components/diagrams/kcard-heading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/api/core/components/diagrams/kcard-heading.png -------------------------------------------------------------------------------- /docs/api/core/components/diagrams/kcard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/api/core/components/diagrams/kcard.png -------------------------------------------------------------------------------- /docs/api/core/components/diagrams/kcardsection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/api/core/components/diagrams/kcardsection.png -------------------------------------------------------------------------------- /docs/api/core/components/diagrams/kgrid-header-footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/api/core/components/diagrams/kgrid-header-footer.png -------------------------------------------------------------------------------- /docs/api/core/components/diagrams/kgrid-infinite-scroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/api/core/components/diagrams/kgrid-infinite-scroll.png -------------------------------------------------------------------------------- /docs/api/core/components/diagrams/kgrid-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/api/core/components/diagrams/kgrid-layout.png -------------------------------------------------------------------------------- /docs/api/core/components/diagrams/kgrid-pagination.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/docs/api/core/components/diagrams/kgrid-pagination.png -------------------------------------------------------------------------------- /docs/api/core/components/graphics.md: -------------------------------------------------------------------------------- 1 | # Graphics 2 | 3 | The `graphics` folder contains reusable components for rendering a variety of graphical elements. 4 | 5 | ## `KIcon` 6 | 7 | The `KIcon` component is a wrapper around [Quasar's Icon](https://quasar.dev/vue-components/icon/) that enables displaying a primary icon with an optional stacked overlay icon. This is useful for creating composite or symbolic icons by layering one icon on top of another. Moreover, and unlike **Quasar**'s built-in **QIcon**, `KIcon` supports any valid HTML color definition. 8 | 9 | ### Props 10 | 11 | | Prop | Type | Default | Description | 12 | | ------ | ------------------ | ----------- | --------------------------------------------------------------------------------- | 13 | | `icon` | `String \| Object` | `undefined` | Main icon name (as string) or an object describing the main icon and its overlay. | 14 | 15 | ### Usage 16 | 17 | * An icon using a name: 18 | 19 | ```html 20 | 21 | ``` 22 | * And the same icon with a red slash through it: 23 | 24 | ```html 25 | 35 | ``` -------------------------------------------------------------------------------- /docs/api/core/directives.md: -------------------------------------------------------------------------------- 1 | # Directives 2 | 3 | ::: tip 4 | The **Directives** provided by the **KDK** must be registered by each application within the corresponding `kdk.js` [boot file](https://quasar.dev/quasar-cli-vite/boot-files#anatomy-of-a-boot-file). For instance: 5 | 6 | ```js 7 | // Register global directives 8 | app.directive('hover', kdkCoreDirectives.vHover) 9 | ``` 10 | ::: 11 | 12 | ## v-hover 13 | 14 | `v-hover` is a lightweight directive that allows you to react when an element either becomes hovered or unhovered. 15 | 16 | Here is an example of use: 17 | 18 | ```html 19 |
20 | .... 21 |
22 | 23 | 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/api/core/introduction.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | ## API 4 | 5 | * [Application](./application.md) 6 | * [Services](./services.md) 7 | * [Hooks](./hooks.md) 8 | 9 | ## Client 10 | 11 | * [Utilities](./utilities.md) 12 | * [Directives](./directives.md) 13 | * [Mixins](./mixins.md) 14 | * [Composables](./composables.md) 15 | * [Components](./components.md) 16 | -------------------------------------------------------------------------------- /docs/api/core/utilities/utils.locale.md: -------------------------------------------------------------------------------- 1 | # Locale 2 | 3 | ## Overview 4 | 5 | The `utils.locale.js` module provide functions to retrieve locale application settings. 6 | 7 | ## Functions 8 | 9 | ### `getBrowserLocale()` 10 | 11 | Returns the user's browser locale using several possible sources. 12 | 13 | - **Returns:** 14 | - *(string)* — A locale string in the `language-region` format (e.g., `en-US`, `fr-FR`). 15 | 16 | ### `getLocale(languageOnly = true)` 17 | 18 | Retrieves the application's default locale from configuration, falling back to the browser locale if not specified. 19 | 20 | - **Parameters:** 21 | - `languageOnly` *(`boolean`, default: `true`)*: If `true`, returns only the language part of the locale (e.g., `en` from `en-GB`). 22 | 23 | - **Returns:** 24 | - *(string)* — The configured or detected locale string, either full (`en-GB`) or just the language (`en`). 25 | 26 | ### `getFallbackLocale(languageOnly = true)` 27 | 28 | Retrieves the fallback locale defined in the configuration, with an option to extract just the language portion. 29 | 30 | - **Parameters:** 31 | - `languageOnly` *(`boolean`, default: `true`)*: If `true`, returns only the language part of the fallback locale. 32 | 33 | - **Returns:** 34 | - *(string)* — The fallback locale, either full (`en-GB`) or just the language (`en`). 35 | 36 | -------------------------------------------------------------------------------- /docs/architecture/component-view.md: -------------------------------------------------------------------------------- 1 | # Component view 2 | 3 | The typical components and the underlying dependencies of **KDK** are summarized in the following diagram. 4 | 5 | ![Component view](./../.vitepress/public/images/component-view.png) -------------------------------------------------------------------------------- /docs/architecture/introduction.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | Before jumping to the [global architecture view](./global-architecture.md) we recommend to read about the [main concepts](./main-concepts.md) of **KDK**. 4 | 5 | You can then have a look to the: 6 | 1. [component-oriented view](./component-view.md) 7 | 2. [data model-oriented view](./data-model-view.md) 8 | -------------------------------------------------------------------------------- /docs/guides/development/configure.md: -------------------------------------------------------------------------------- 1 | # Configure your app 2 | 3 | Please follow our [application template configuration guide](https://kalisio.github.io/skeleton/guides/development/configure.html). 4 | -------------------------------------------------------------------------------- /docs/guides/development/deploy.md: -------------------------------------------------------------------------------- 1 | # Deploy your app 2 | 3 | Please follow our [application template deployment guide](https://kalisio.github.io/skeleton/guides/development/deploy.html). 4 | -------------------------------------------------------------------------------- /docs/guides/development/develop.md: -------------------------------------------------------------------------------- 1 | # Develop with KDK 2 | 3 | KDK and third-party Kalisio modules are [Feathers modules](https://docs.feathersjs.com), so you will find most of the required information in the linked Feathers documentation. Typically for development you will do the following so that a module is ready-to-use: 4 | ```bash 5 | cd kdk 6 | yarn install 7 | yarn link 8 | ``` 9 | 10 | ## Linting the code 11 | 12 | The **KDK** relies on [JavaScript standard style](https://github.com/feross/standard). 13 | 14 | To lint the code: 15 | 16 | ```bash 17 | $yarn lint 18 | ``` 19 | 20 | You can also lint each of the submodules independently using the following commands: 21 | 22 | ```bash 23 | $yarn lint:core # lint the core part 24 | $yarn lint:map # lint the map part 25 | ``` 26 | ::: 27 | 28 | ## Web app 29 | 30 | Please follow our [application template development guide](https://kalisio.github.io/skeleton/guides/development/develop.html). 31 | -------------------------------------------------------------------------------- /docs/guides/development/test.md: -------------------------------------------------------------------------------- 1 | # Testing with KDK 2 | 3 | The **KDK** relies on the [Mocha](https://mochajs.org/) testing framework and the [Chai](https://www.chaijs.com/) assertion library. 4 | 5 | KDK and third-party Kalisio modules are [Feathers modules](https://docs.feathersjs.com/guides/basics/testing.html), so you will find most of the required information in the linked Feathers documentation. 6 | 7 | To run the module tests including linting and coverage : `$ yarn test` 8 | 9 | To speed-up things simply run the tests with: `$ yarn mocha` 10 | 11 | You can run the tests of each submodule independently using the following commands for the KDK: 12 | 13 | ```bash 14 | $yarn mocha:core # test the core module 15 | $yarn mocha:map # test the map module 16 | ``` 17 | 18 | :::tip 19 | If you need to perform some specific tests, you can use the `-g` or `--grep` option of the `mocha` command: 20 | 21 | ```bash 22 | $yarn mocha:core -g "core:team" # run the team tests 23 | ``` 24 | ::: 25 | 26 | ## Web app 27 | 28 | Please follow our [application template testing guide](https://kalisio.github.io/skeleton/guides/development/test.html). 29 | -------------------------------------------------------------------------------- /docs/guides/introduction.md: -------------------------------------------------------------------------------- 1 | # Guides 2 | 3 | ## The Basics 4 | 5 | The goal of this guide is to get you to the "A-ha!" moment as efficiently as possible. You will learn more about the underlying technological stack and how to deploy your first KDK app. 6 | 7 | * [A Step-by-Step introduction to KDK](./basics/introduction.md) 8 | 9 | ## Development 10 | 11 | In these guides you will learn step-by-step how the setup your development environment. You'll also learn how to create, develop and publish your own app and modules, which is also how we develop the KDK. 12 | 13 | * [Setup your environment](./development/setup.md) 14 | * [Develop with KDK](./development/develop.md) 15 | * [Test with KDK](./development/test.md) 16 | * [Publish with KDK](./development/publish.md) 17 | * [Configure your app](./development/configure.md) 18 | * [Deploy your app](./development/deploy.md) 19 | 20 | ## Migration 21 | 22 | In this guides you will find an overview of the most important new features or breaking changes for each KDK version. 23 | 24 | * [v2.5](./migration/v2.5.md) with related [milestone](https://github.com/kalisio/kdk/milestone/13) on GitHub. 25 | * [v2.6](./migration/v2.6.md) with related [milestone](https://github.com/kalisio/kdk/milestone/14) on GitHub. 26 | -------------------------------------------------------------------------------- /docs/guides/migration/v2.6.md: -------------------------------------------------------------------------------- 1 | # v2.6 - Not yet released 2 | 3 | More details can be found in the related [milestone](https://github.com/kalisio/kdk/milestone/14) on GitHub. 4 | 5 | ## Major breaking changes 6 | 7 | 💥 Renamed functions in `utils.locale.js` to resolve confusion between `getLocale` and `getAppLocale`, which previously led to inconsistent behavior and locale-related issues. 8 | * Renamed `getLocale` to `getBrowserLocale` 9 | * Renamed `getAppLocale` to `getLocale`. 10 | * Renamed `getAppFallbackLocale` to `getFallbackLocale` 11 | 12 | ::: tip 13 | Both `getLocale`and `getFallbackLocale` have a new signature and allows you to retrieve the full locale in the language-region format (e.g., en-GB). For instance, if the locale is `en-GB`, then 14 | ``` 15 | console.log(getLocale()) 16 | // => en 17 | console.log(getLocale(false)) 18 | // => en-GB 19 | ``` 20 | ::: 21 | 22 | 💥 Renamed `KLayersSelector` component to `KLayersList` and added new `KLayersSelector` component 23 | 24 | ::: caution 25 | The new `KLayersSelector` component is NOT the old `KLayersSelector` component. They are entirely different components, and `KLayersList` should be now used in place of the old `KLayersSelector` ❗ 26 | ::: 27 | 28 | ## Major new features 29 | 30 | 👉 -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | hero: 4 | name: KDK 5 | tagline: The Kalisio Development Kit 6 | image: 7 | src: https://s3.eu-central-1.amazonaws.com/kalisioscope/kdk/kdk-icon-color-2048x2048.png 8 | alt: kalisio-kdk 9 | actions: 10 | - theme: brand 11 | text: Learn more about KDK ? 12 | link: /about/introduction 13 | --- 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vitepress dev", 6 | "build": "vitepress build", 7 | "preview": "vitepress preview" 8 | }, 9 | "devDependencies": { 10 | "keycloak-js": "^23.0.4", 11 | "lodash": "^4.17.21", 12 | "mermaid": "^10.8.0", 13 | "moment": "^2.30.1", 14 | "quasar": "^2.14.3", 15 | "vitepress": "^1.0.0-rc.40", 16 | "vitepress-plugin-mermaid": "^2.0.16", 17 | "vitepress-theme-kalisio": "https://github.com/kalisio/vitepress-theme-kalisio" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /extras/configs/widgets.left.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | LEGEND: { 3 | id: 'legend-widget', 4 | label: 'KLegend.LABEL', 5 | icon: 'las la-atlas', 6 | scrollable: true, 7 | content: { component: 'legend/KLegend' } 8 | }, 9 | FEATURES_SELECTION: { 10 | id: 'selection-widget', 11 | label: 'KFeaturesSelection.LABEL', 12 | icon: 'las la-object-group', 13 | scrollable: true, 14 | content: { component: 'selection/KFeaturesSelection' } 15 | }, 16 | STYLE_MANAGER: { 17 | id: 'style-manager', 18 | label: 'KStyleManager.TITLE', 19 | icon: 'las la-paint-brush', 20 | scrollable: true, 21 | content: { component: 'styles/KStyleManager' } 22 | } 23 | } -------------------------------------------------------------------------------- /extras/icons/anticlockwise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/extras/icons/anticlockwise.png -------------------------------------------------------------------------------- /extras/icons/attribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/extras/icons/attribution.png -------------------------------------------------------------------------------- /extras/icons/clockwise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/extras/icons/clockwise.png -------------------------------------------------------------------------------- /extras/icons/kanban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/extras/icons/kanban.png -------------------------------------------------------------------------------- /extras/icons/mapillary-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/extras/icons/mapillary-marker.png -------------------------------------------------------------------------------- /extras/icons/mapillary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/extras/icons/mapillary.png -------------------------------------------------------------------------------- /extras/icons/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/extras/icons/marker-icon-2x.png -------------------------------------------------------------------------------- /extras/icons/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/extras/icons/marker-icon.png -------------------------------------------------------------------------------- /extras/icons/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/extras/icons/marker-shadow.png -------------------------------------------------------------------------------- /extras/icons/pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/extras/icons/pdf.png -------------------------------------------------------------------------------- /extras/icons/position-cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/extras/icons/position-cursor.png -------------------------------------------------------------------------------- /extras/icons/view-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/extras/icons/view-plus.png -------------------------------------------------------------------------------- /extras/icons/wind-speed-0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 0–2 knots 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-10.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 8–12 knots 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-100.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 98–102 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-105.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 102–107 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-15.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 13–17 knots 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-20.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 18–22 knots 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-25.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 23–27 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-30.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 28–32 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-35.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 33–37 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 38–42 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-45.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 43–47 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 3–7 knots 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-50.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 63–67 knots 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-55.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 53–57 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-60.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 58–62 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-65.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 63–67 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-70.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 68–72 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-75.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 73–77 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-80.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 78–82 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-85.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 83–87 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-90.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 88–92 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /extras/icons/wind-speed-95.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Observed wind speed: 93–97 knots 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /extras/images/kalisio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/extras/images/kalisio.png -------------------------------------------------------------------------------- /extras/images/north.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /extras/images/target.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /extras/tours/core/account-profile.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#edit-profile', 3 | content: 'tours.account.PROFILE_LABEL', 4 | params: { 5 | placement: 'bottom', 6 | clickOnPrevious: '#left-opener', 7 | clickOnNext: ['#left-opener', '#edit-profile'], 8 | nextDelay: 500 9 | } 10 | }, { 11 | target: '#name-field', 12 | content: 'tours.account.NAME_LABEL', 13 | params: { 14 | placement: 'bottom', 15 | clickOnPrevious: ['#cancel-action', '#left-opener'], 16 | previousDelay: 500 17 | } 18 | }, { 19 | target: '#avatar-field', 20 | content: 'tours.account.AVATAR_LABEL', 21 | params: { 22 | placement: 'bottom' 23 | } 24 | }, { 25 | target: '#ok-button', 26 | content: 'tours.account.UPDATE_LABEL', 27 | params: { 28 | placement: 'left', 29 | clickOnNext: ['#cancel-action', '#left-opener'], 30 | nextDelay: 500 31 | } 32 | }] 33 | -------------------------------------------------------------------------------- /extras/tours/core/add-tag.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#tag-field', 3 | content: 'tours.add-tag.TAG_NAME_LABEL', 4 | params: { 5 | placement: 'top' 6 | } 7 | }, { 8 | target: '#join-button', 9 | content: 'tours.add-tag.ADD_TAG_LABEL', 10 | params: { 11 | placement: 'left' 12 | } 13 | }] -------------------------------------------------------------------------------- /extras/tours/core/create-group.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#name-field', 3 | content: 'tours.create-group.GROUP_NAME_LABEL', 4 | params: { 5 | placement: 'bottom' 6 | } 7 | }, { 8 | target: '#description-field', 9 | content: 'tours.create-group.GROUP_DESCRIPTION_LABEL', 10 | params: { 11 | placement: 'bottom' 12 | } 13 | }, { 14 | target: '#apply-button', 15 | content: 'tours.create-group.CREATE_GROUP_LABEL', 16 | params: { 17 | placement: 'left' 18 | } 19 | }] 20 | -------------------------------------------------------------------------------- /extras/tours/core/create-organisation.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#name-field', 3 | content: 'tours.create-organisation.ORGANISATION_NAME_LABEL', 4 | params: { 5 | placement: 'bottom' 6 | } 7 | }, { 8 | target: '#description-field', 9 | content: 'tours.create-organisation.ORGANISATION_DESCRIPTION_LABEL', 10 | params: { 11 | placement: 'top' 12 | } 13 | }, { 14 | target: '#apply-button', 15 | content: 'tours.create-organisation.CREATE_ORGANISATION_LABEL', 16 | params: { 17 | placement: 'left' 18 | } 19 | }] 20 | -------------------------------------------------------------------------------- /extras/tours/core/create-tag.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#value-field', 3 | content: 'tours.create-tag.TAG_NAME_LABEL', 4 | params: { 5 | placement: 'bottom' 6 | } 7 | }, { 8 | target: '#icon-field', 9 | content: 'tours.create-tag.TAG_ICON_LABEL', 10 | params: { 11 | placement: 'bottom' 12 | } 13 | }, { 14 | target: '#description-field', 15 | content: 'tours.create-tag.TAG_DESCRIPTION_LABEL', 16 | params: { 17 | placement: 'bottom' 18 | } 19 | }, { 20 | target: '#apply-button', 21 | content: 'tours.create-tag.CREATE_TAG_LABEL', 22 | params: { 23 | placement: 'left' 24 | } 25 | }] 26 | 27 | -------------------------------------------------------------------------------- /extras/tours/core/edit-member-role.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#role-field', 3 | content: 'tours.role-member.ROLE_LABEL', 4 | params: { 5 | placement: 'top' 6 | } 7 | }, { 8 | target: '#update-button', 9 | content: 'tours.role-member.UPDATE_ROLE_LABEL', 10 | params: { 11 | placement: 'left' 12 | } 13 | }] 14 | -------------------------------------------------------------------------------- /extras/tours/core/join-group.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#group-field', 3 | content: 'tours.join-group.GROUP_NAME_LABEL', 4 | params: { 5 | placement: 'top' 6 | } 7 | }, { 8 | target: '#join-button', 9 | content: 'tours.join-group.ADD_MEMBER_LABEL', 10 | params: { 11 | placement: 'left' 12 | } 13 | }] 14 | -------------------------------------------------------------------------------- /extras/tours/core/login.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#email-field', 3 | title: 'tours.login.LOCAL_LABEL', 4 | content: 'tours.login.EMAIL_LABEL', 5 | params: { 6 | placement: 'left' 7 | } 8 | }, { 9 | target: '#password-field', 10 | content: 'tours.login.PASSWORD_LABEL', 11 | params: { 12 | placement: 'left' 13 | } 14 | }, { 15 | target: '#password-field-visibility', 16 | content: 'tours.login.PASSWORD_VISIBILITY_LABEL', 17 | params: { 18 | placement: 'top', 19 | clickDelay: 1000 20 | } 21 | }, { 22 | target: '#local', 23 | content: 'tours.login.LOGIN_LABEL', 24 | params: { 25 | placement: 'right' 26 | } 27 | }, { 28 | target: '#reset-password-link', 29 | link: 'tours.login.LOST_PASSWORD_LINK_LABEL', 30 | params: { 31 | placement: 'bottom', 32 | route: { name: 'send-reset-password' } 33 | } 34 | }, { 35 | target: '#register-link', 36 | link: 'tours.login.REGISTER_LINK_LABEL', 37 | params: { 38 | placement: 'bottom', 39 | route: { name: 'register' } 40 | } 41 | }] 42 | -------------------------------------------------------------------------------- /extras/tours/core/send-reset-password.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#email-field', 3 | title: 'tours.reset-password.RESET_PROCEDURE_LABEL', 4 | content: 'tours.reset-password.EMAIL_LABEL', 5 | params: { 6 | placement: 'left' 7 | } 8 | }, { 9 | target: '#send-reset-password', 10 | content: 'tours.reset-password.RESET_LABEL', 11 | params: { 12 | placement: 'bottom' 13 | } 14 | }] 15 | -------------------------------------------------------------------------------- /extras/tours/map/add-layer.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#import-layer', 3 | title: 'tours.add-layer.IMPORT_LAYER_LABEL', 4 | link: 'tours.add-layer.IMPORT_LAYER_LINK_LABEL', 5 | params: { 6 | placement: 'bottom', 7 | clickOnLink: '#import-layer', 8 | tour: 'import-layer' 9 | } 10 | }, { 11 | target: '#connect-layer', 12 | title: 'tours.add-layer.CONNECT_LAYER_LABEL', 13 | link: 'tours.add-layer.CONNECT_LAYER_LINK_LABEL', 14 | params: { 15 | placement: 'bottom', 16 | clickOnLink: '#connect-layer', 17 | tour: 'connect-layer' 18 | } 19 | }, { 20 | target: '#create-layer', 21 | title: 'tours.add-layer.CREATE_LAYER_LABEL', 22 | link: 'tours.add-layer.CREATE_LAYER_LINK_LABEL', 23 | params: { 24 | placement: 'bottom', 25 | clickOnLink: '#create-layer', 26 | tour: 'create-layer' 27 | } 28 | }] 29 | -------------------------------------------------------------------------------- /extras/tours/map/connect-layer.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#service-field', 3 | content: 'tours.add-layer.SERVICE_LABEL', 4 | params: { 5 | placement: 'bottom' 6 | } 7 | }, { 8 | target: '#layer-field', 9 | content: 'tours.add-layer.SERVICE_LAYER_LABEL', 10 | params: { 11 | placement: 'bottom' 12 | } 13 | }, { 14 | target: '#name-field', 15 | content: 'tours.add-layer.LAYER_NAME_LABEL', 16 | params: { 17 | placement: 'bottom' 18 | } 19 | }, { 20 | target: '#description-field', 21 | content: 'tours.add-layer.LAYER_DESCRIPTION_LABEL', 22 | params: { 23 | placement: 'bottom' 24 | } 25 | }, { 26 | target: '#style-field', 27 | content: 'tours.add-layer.LAYER_STYLE_LABEL', 28 | params: { 29 | placement: 'bottom' 30 | } 31 | }, { 32 | target: '#connect-layer-action', 33 | title: 'tours.add-layer.CONNECT_NEW_LAYER_LABEL', 34 | link: 'tours.add-layer.ADD_LAYER_LINK_LABEL', 35 | params: { 36 | placement: 'left', 37 | tour: 'add-layer' 38 | } 39 | }] 40 | -------------------------------------------------------------------------------- /extras/tours/map/create-layer.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#name-field', 3 | content: 'tours.add-layer.LAYER_NAME_LABEL', 4 | params: { 5 | placement: 'bottom' 6 | } 7 | }, { 8 | target: '#description-field', 9 | content: 'tours.add-layer.LAYER_DESCRIPTION_LABEL', 10 | params: { 11 | placement: 'bottom' 12 | } 13 | }, { 14 | target: '#schema-field', 15 | content: 'tours.add-layer.LAYER_SCHEMA_LABEL', 16 | params: { 17 | placement: 'bottom' 18 | } 19 | }, { 20 | target: '#featureId-field', 21 | content: 'tours.add-layer.LAYER_FEATURE_ID_LABEL', 22 | params: { 23 | placement: 'bottom' 24 | } 25 | }, { 26 | target: '#create-layer-action', 27 | title: 'tours.add-layer.CREATE_NEW_LAYER_LABEL', 28 | link: 'tours.add-layer.ADD_LAYER_LINK_LABEL', 29 | params: { 30 | placement: 'left', 31 | tour: 'add-layer' 32 | } 33 | }] 34 | -------------------------------------------------------------------------------- /extras/tours/map/create-view.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#name-field', 3 | content: 'tours.create-view.NAME_FIELD_LABEL', 4 | params: { 5 | placement: 'bottom' 6 | } 7 | }, { 8 | target: '#description-field', 9 | content: 'tours.create-view.DESCRIPTION_FIELD_LABEL', 10 | params: { 11 | placement: 'bottom' 12 | } 13 | }, { 14 | target: '#layers-field', 15 | content: 'tours.create-view.LAYERS_FIELD_LABEL', 16 | params: { 17 | placement: 'bottom' 18 | } 19 | }, { 20 | target: '#apply-button', 21 | title: 'tours.create-view.APPLY_BUTTON_LABEL', 22 | params: { 23 | placement: 'bottom' 24 | } 25 | }] 26 | -------------------------------------------------------------------------------- /extras/tours/map/fab.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | probeLocation: (options) => { 3 | return Object.assign({ 4 | target: '#probe-location', 5 | title: 'tours.fab.PROBE_LABEL', 6 | content: 'tours.fab.PROBE_CURSOR_LABEL', 7 | params: { 8 | placement: 'left' 9 | } 10 | }, options) 11 | }, 12 | addLayer: (options) => { 13 | return Object.assign({ 14 | target: '#add-layer', 15 | title: 'tours.fab.ADD_LAYER_LABEL', 16 | link: 'tours.fab.ADD_LAYER_LINK_LABEL', 17 | params: { 18 | placement: 'left', 19 | clickOnLink: '#add-layer', 20 | tour: 'add-layer' 21 | } 22 | }, options) 23 | }, 24 | createView: (options) => { 25 | return Object.assign({ 26 | target: '#create-view', 27 | title: 'tours.fab.CREATE_VIEW_LABEL', 28 | link: 'tours.fab.CREATE_VIEW_LINK_LABEL', 29 | params: { 30 | placement: 'left', 31 | clickOnLink: '#create-view', 32 | tour: 'create-view' 33 | } 34 | }, options) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /extras/tours/map/import-layer.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | target: '#file-field', 3 | content: 'tours.add-layer.FILE_LABEL', 4 | params: { 5 | placement: 'bottom' 6 | } 7 | }, { 8 | target: '#name-field', 9 | content: 'tours.add-layer.LAYER_NAME_LABEL', 10 | params: { 11 | placement: 'bottom' 12 | } 13 | }, { 14 | target: '#description-field', 15 | content: 'tours.add-layer.LAYER_DESCRIPTION_LABEL', 16 | params: { 17 | placement: 'bottom' 18 | } 19 | }, { 20 | target: '#featureId-field', 21 | content: 'tours.add-layer.LAYER_FEATURE_ID_LABEL', 22 | params: { 23 | placement: 'bottom' 24 | } 25 | }, { 26 | target: '#import-layer-action', 27 | title: 'tours.add-layer.IMPORT_NEW_LAYER_LABEL', 28 | link: 'tours.add-layer.ADD_LAYER_LINK_LABEL', 29 | params: { 30 | placement: 'left', 31 | tour: 'add-layer' 32 | } 33 | }] 34 | -------------------------------------------------------------------------------- /map.api.js: -------------------------------------------------------------------------------- 1 | // Need this as export * only exports named exports but not the default export 2 | import api from './map/api/index.js' 3 | export default api 4 | 5 | export * from './map/api/index.js' 6 | -------------------------------------------------------------------------------- /map.client.globe.js: -------------------------------------------------------------------------------- 1 | // Need this as export * only exports named exports but not the default export 2 | import client from './map/client/globe.js' 3 | export default client 4 | 5 | export * from './map/client/globe.js' 6 | -------------------------------------------------------------------------------- /map.client.js: -------------------------------------------------------------------------------- 1 | // Need this as export * only exports named exports but not the default export 2 | import client from './map/client/index.js' 3 | export default client 4 | 5 | export * from './map/client/index.js' 6 | -------------------------------------------------------------------------------- /map.client.map.js: -------------------------------------------------------------------------------- 1 | // Need this as export * only exports named exports but not the default export 2 | import client from './map/client/map.js' 3 | export default client 4 | 5 | export * from './map/client/map.js' 6 | -------------------------------------------------------------------------------- /map.common.js: -------------------------------------------------------------------------------- 1 | export * from './map/common/index.js' 2 | -------------------------------------------------------------------------------- /map.config.cjs: -------------------------------------------------------------------------------- 1 | const getCategories = require('./map/api/config/categories.cjs') 2 | const getLayers = require('./map/api/config/layers.cjs') 3 | const getSublegends = require('./map/api/config/sublegends.cjs') 4 | 5 | module.exports = { 6 | getCategories, 7 | getLayers, 8 | getSublegends 9 | } 10 | -------------------------------------------------------------------------------- /map/api/hooks/index.js: -------------------------------------------------------------------------------- 1 | export * from './hooks.catalog.js' 2 | export * from './hooks.query.js' 3 | export * from './hooks.features.js' 4 | -------------------------------------------------------------------------------- /map/api/index.js: -------------------------------------------------------------------------------- 1 | import makeDebug from 'debug' 2 | import services from './services/index.js' 3 | import * as hooks from './hooks/index.js' 4 | 5 | export * from './services/index.js' 6 | export { hooks } 7 | export * from './marshall.js' 8 | export * from '../common/index.js' 9 | 10 | const debug = makeDebug('kdk:map') 11 | 12 | export default async function init () { 13 | const app = this 14 | 15 | debug('Initializing KDK map') 16 | 17 | await app.configure(services) 18 | } 19 | -------------------------------------------------------------------------------- /map/api/marshall.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | 3 | function coordinatesToNumbers (value) { 4 | if (typeof value === 'string') { 5 | return _.toNumber(value) 6 | } else if (Array.isArray(value)) { 7 | return value.map(item => coordinatesToNumbers(item)) 8 | } else { 9 | return value 10 | } 11 | } 12 | 13 | export function marshallGeometry (geometry) { 14 | if (typeof geometry === 'object') { 15 | // Geospatial operators begin with $ 16 | let geoOperator = _.keys(geometry).find(key => key.startsWith('$')) 17 | geoOperator = geometry[geoOperator] 18 | _.forOwn(geoOperator, (value, key) => { 19 | // Geospatial parameters begin with $ 20 | if (key.startsWith('$')) { 21 | // Some target coordinates 22 | if (!_.isNil(value.coordinates)) { 23 | value.coordinates = coordinatesToNumbers(value.coordinates) 24 | } else { 25 | // Other simple values or array of values 26 | geoOperator[key] = coordinatesToNumbers(value) 27 | } 28 | } 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /map/api/models/alerts.model.mongodb.js: -------------------------------------------------------------------------------- 1 | export default function (app, options) { 2 | const db = options.db || app.db 3 | options.Model = db.collection('alerts') 4 | options.Model.createIndex({ geometry: '2dsphere' }) 5 | // Expire at a given date 6 | options.Model.createIndex({ expireAt: 1 }, { expireAfterSeconds: 0 }) 7 | } 8 | -------------------------------------------------------------------------------- /map/api/models/projects.model.mongodb.js: -------------------------------------------------------------------------------- 1 | export default function (app, options) { 2 | const db = options.db || app.db 3 | options.Model = db.collection('projects') 4 | // Collation provided in query ensure sorting to be case insensitive w.r.t. user's language 5 | // We built indices with collation to cover the most used languages, it requires different naming... 6 | options.Model.createIndex({ name: 1 }, { name: 'name-en', collation: { locale: 'en', strength: 1 } }) 7 | options.Model.createIndex({ name: 1 }, { name: 'name-fr', collation: { locale: 'fr', strength: 1 } }) 8 | } 9 | -------------------------------------------------------------------------------- /map/api/models/styles.model.mongodb.js: -------------------------------------------------------------------------------- 1 | export default function (app, options) { 2 | const db = options.db || app.db 3 | options.Model = db.collection('styles') 4 | // Collation provided in query ensure sorting to be case insensitive w.r.t. user's language 5 | // We built indices with collation to cover the most used languages, it requires different naming... 6 | options.Model.createIndex({ name: 1 }, { name: 'name-en', collation: { locale: 'en', strength: 1 } }) 7 | options.Model.createIndex({ name: 1 }, { name: 'name-fr', collation: { locale: 'fr', strength: 1 } }) 8 | options.Model.createIndex({ name: 'text' }) 9 | } 10 | -------------------------------------------------------------------------------- /map/api/services/styles/styles.hooks.js: -------------------------------------------------------------------------------- 1 | import fuzzySearch from 'feathers-mongodb-fuzzy-search' 2 | import { hooks as kdkCoreHooks } from '../../../../core/api/index.js' 3 | 4 | export default { 5 | before: { 6 | all: [], 7 | find: [ 8 | fuzzySearch({ fields: ['name'] }), 9 | kdkCoreHooks.diacriticSearch() 10 | ], 11 | get: [], 12 | create: [kdkCoreHooks.checkUnique({ field: 'name' })], 13 | update: [kdkCoreHooks.checkUnique({ field: 'name' })], 14 | patch: [kdkCoreHooks.checkUnique({ field: 'name' })], 15 | remove: [] 16 | }, 17 | 18 | after: { 19 | all: [], 20 | find: [], 21 | get: [], 22 | create: [], 23 | update: [], 24 | patch: [], 25 | remove: [] 26 | }, 27 | 28 | error: { 29 | all: [], 30 | find: [], 31 | get: [], 32 | create: [], 33 | update: [], 34 | patch: [], 35 | remove: [] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /map/client/canvas-draw-context.js: -------------------------------------------------------------------------------- 1 | export const CanvasDrawContext = { 2 | initialize () { 3 | if (this.ctx) return 4 | this.ctx = {} 5 | }, 6 | 7 | get () { 8 | return this.ctx 9 | }, 10 | 11 | merge (ctx) { 12 | this.ctx = Object.assign({}, this.ctx, ctx) 13 | } 14 | } 15 | 16 | CanvasDrawContext.initialize() 17 | -------------------------------------------------------------------------------- /map/client/capture.js: -------------------------------------------------------------------------------- 1 | import { Notify } from 'quasar' 2 | import { capture } from './utils/utils.capture.js' 3 | import { i18n } from '../../core/client/index.js' 4 | 5 | // Export singleton 6 | export const Capture = { 7 | processing: false, 8 | async process (values) { 9 | if (this.processing) Notify.create({ type: 'negative', message: i18n.t('KCapture.ERROR_MESSAGE') }) 10 | else { 11 | this.processing = true 12 | await capture(values) 13 | this.processing = false 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /map/client/cesium/utils/index.js: -------------------------------------------------------------------------------- 1 | export * from './utils.cesium.js' 2 | export * from './utils.events.js' 3 | export * from './utils.geojson.js' 4 | export * from './utils.popup.js' 5 | export * from './utils.style.js' 6 | export * from './utils.features.js' 7 | -------------------------------------------------------------------------------- /map/client/cesium/utils/utils.events.js: -------------------------------------------------------------------------------- 1 | export function convertCesiumHandlerEvent (type) { 2 | const buttonMapping = { 3 | left: 0, 4 | middle: 1, 5 | right: 2 6 | } 7 | const buttonMovement = type.split('_') 8 | const movement = buttonMovement[1].toLowerCase() 9 | let button = buttonMovement[0].toLowerCase() 10 | let name 11 | if (type.startsWith('PINCH')) name = 'pinch' 12 | else if (type.endsWith('CLICK')) name = 'click' 13 | else if (type.endsWith('DOUBLE_CLICK')) name = 'dblclick' 14 | else if (type.startsWith('WHEEL')) name = 'wheel' 15 | else name = 'mouse' 16 | 17 | if (name === 'mouse') { 18 | name += movement 19 | button = buttonMapping[button] 20 | } else if (name.endsWith('click')) { 21 | button = buttonMapping[button] 22 | } else if (name === 'pinch') { 23 | name += movement 24 | button = undefined 25 | } else { 26 | button = 1 // wheel 27 | } 28 | 29 | return { name, button } 30 | } -------------------------------------------------------------------------------- /map/client/cesium/utils/utils.features.js: -------------------------------------------------------------------------------- 1 | import { kml } from '@tmcw/togeojson' 2 | import { exportKml } from 'cesium' 3 | 4 | export async function convertEntitiesToGeoJson(entities) { 5 | const features = [] 6 | if (entities.values.length === 0) return { type: 'FeatureCollection', features } 7 | // As cesium does not we usually keep track of original feature associated with an entity 8 | _.forEach(entities.values, entity => { 9 | if (entity.feature) features.push(entity.feature) 10 | }) 11 | if (features.length > 0) return { type: 'FeatureCollection', features } 12 | // Otherwise try to export as KML then convert to GeoJson 13 | const kmlEntities = await exportKml({ entities, modelCallback: () => '' }) 14 | const parser = new DOMParser() 15 | return kml(parser.parseFromString(kmlEntities.kml, 'application/xml')) 16 | } 17 | -------------------------------------------------------------------------------- /map/client/cesium/utils/utils.popup.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import { utils as kdkCoreUtils } from '../../../../core/client/index.js' 3 | 4 | export function getTextTable (properties) { 5 | properties = kdkCoreUtils.dotify(properties) 6 | properties = _.pickBy(properties, value => !_.isNil(value)) 7 | const keys = _.keys(properties) 8 | let text 9 | if (keys.length === 0) return null 10 | else if (keys.length === 1) text = _.get(properties, keys[0]) 11 | else { 12 | text = keys 13 | .map(key => key + ': ' + _.get(properties, key)) 14 | .join('\n') 15 | } 16 | return text 17 | } -------------------------------------------------------------------------------- /map/client/components/KEditLayerData.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 24 | -------------------------------------------------------------------------------- /map/client/components/index.js: -------------------------------------------------------------------------------- 1 | // Warning: Do not delete this file as it seems to be required to enable webpack directoy scanning (scandir) 2 | -------------------------------------------------------------------------------- /map/client/components/legend/KColorScaleLegend.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 36 | -------------------------------------------------------------------------------- /map/client/components/legend/KImageLegend.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 47 | 48 | 53 | -------------------------------------------------------------------------------- /map/client/components/legend/KLegendRenderer.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 23 | -------------------------------------------------------------------------------- /map/client/components/location/KGeocodersFilter.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 43 | -------------------------------------------------------------------------------- /map/client/components/stickies/KNorthArrow.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | -------------------------------------------------------------------------------- /map/client/components/stickies/KTarget.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | 28 | 35 | -------------------------------------------------------------------------------- /map/client/composables/index.js: -------------------------------------------------------------------------------- 1 | export * from './activity.js' 2 | export * from './highlight.js' 3 | export * from './location.js' 4 | export * from './measure.js' 5 | export * from './probe.js' 6 | export * from './selection.js' 7 | export * from './weather.js' 8 | export * from './catalog.js' 9 | export * from './project.js' 10 | -------------------------------------------------------------------------------- /map/client/globe.js: -------------------------------------------------------------------------------- 1 | import * as commonMixins from './mixins/index.js' 2 | import * as globeMixins from './mixins/globe/index.js' 3 | import * as hooks from './hooks/index.js' 4 | import * as utils from './utils.globe.js' 5 | import init from './init.js' 6 | 7 | const mixins = Object.assign({}, commonMixins, { globe: globeMixins }) 8 | 9 | export * from './geolocation.js' 10 | export * from './planets.js' 11 | export * from './navigator.js' 12 | export { hooks } 13 | export { utils } 14 | export { mixins } 15 | export * from '../common/index.js' 16 | export * from './init.js' 17 | 18 | export default init 19 | -------------------------------------------------------------------------------- /map/client/hooks/index.js: -------------------------------------------------------------------------------- 1 | export * from './hooks.offline.js' 2 | -------------------------------------------------------------------------------- /map/client/index.js: -------------------------------------------------------------------------------- 1 | import * as composables from './composables/index.js' 2 | import * as commonMixins from './mixins/index.js' 3 | import * as mapMixins from './mixins/map/index.js' 4 | import * as globeMixins from './mixins/globe/index.js' 5 | import * as hooks from './hooks/index.js' 6 | import * as utils from './utils.all.js' 7 | import * as elevationUtils from './elevation-utils.js' 8 | import init from './init.js' 9 | const mixins = Object.assign({}, commonMixins, { map: mapMixins, globe: globeMixins }) 10 | 11 | export * from './geolocation.js' 12 | export * from './capture.js' 13 | export * from './planets.js' 14 | export * from './navigator.js' 15 | export * from './canvas-draw-context.js' 16 | export { hooks } 17 | export { utils } 18 | export { elevationUtils } 19 | export { composables } 20 | export { mixins } 21 | export * from '../common/index.js' 22 | export * from './init.js' 23 | 24 | export default init 25 | -------------------------------------------------------------------------------- /map/client/leaflet/utils/index.js: -------------------------------------------------------------------------------- 1 | export * from './utils.events.js' 2 | export * from './utils.geojson.js' 3 | export * from './utils.popup.js' 4 | export * from './utils.style.js' 5 | export * from './utils.tiles.js' 6 | -------------------------------------------------------------------------------- /map/client/leaflet/utils/utils.popup.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import { utils as kdkCoreUtils } from '../../../../core/client/index.js' 3 | 4 | export function getHtmlTable (properties) { 5 | properties = kdkCoreUtils.dotify(properties) 6 | properties = _.pickBy(properties, value => !_.isNil(value)) 7 | const keys = _.keys(properties) 8 | let html 9 | if (keys.length === 0) return null 10 | else if (keys.length === 1) html = _.get(properties, keys[0]) 11 | else { 12 | const borderStyle = ' style="border: 1px solid black; border-collapse: collapse;"' 13 | html = '' 14 | html += keys 15 | .map(key => '' + key + '' + _.get(properties, key) + '') 17 | .join('') 18 | html += '' 19 | } 20 | return html 21 | } -------------------------------------------------------------------------------- /map/client/map.js: -------------------------------------------------------------------------------- 1 | import * as composables from './composables/index.js' 2 | import * as commonMixins from './mixins/index.js' 3 | import * as mapMixins from './mixins/map/index.js' 4 | import * as hooks from './hooks/index.js' 5 | import * as utils from './utils.map.js' 6 | import init from './init.js' 7 | 8 | const mixins = Object.assign({}, commonMixins, { map: mapMixins }) 9 | 10 | export * from './geolocation.js' 11 | export * from './planets.js' 12 | export * from './navigator.js' 13 | export { hooks } 14 | export { utils } 15 | export { composables } 16 | export { mixins } 17 | export * from '../common/index.js' 18 | export * from './init.js' 19 | 20 | export default init 21 | -------------------------------------------------------------------------------- /map/client/mixins/globe/index.js: -------------------------------------------------------------------------------- 1 | export * from './mixin.base-globe.js' 2 | export * from './mixin.geojson-layers.js' 3 | export * from './mixin.file-layers.js' 4 | export * from './mixin.style.js' 5 | export * from './mixin.tooltip.js' 6 | export * from './mixin.popup.js' 7 | export * from './mixin.globe-activity.js' 8 | export * from './mixin.opendap-layers.js' 9 | -------------------------------------------------------------------------------- /map/client/mixins/index.js: -------------------------------------------------------------------------------- 1 | export * from './mixin.activity.js' 2 | export * from './mixin.context.js' 3 | export * from './mixin.feature-selection.js' 4 | export * from './mixin.feature-service.js' 5 | export * from './mixin.infobox.js' 6 | export * from './mixin.levels.js' 7 | export * from './mixin.style.js' 8 | export * from './mixin.weacast.js' 9 | -------------------------------------------------------------------------------- /map/client/mixins/map/index.js: -------------------------------------------------------------------------------- 1 | export * from './mixin.base-map.js' 2 | export * from './mixin.geojson-layers.js' 3 | export * from './mixin.file-layers.js' 4 | export * from './mixin.edit-layers.js' 5 | export * from './mixin.style.js' 6 | export * from './mixin.tooltip.js' 7 | export * from './mixin.popup.js' 8 | export * from './mixin.map-activity.js' 9 | export * from './mixin.tiled-mesh-layers.js' 10 | export * from './mixin.tiled-wind-layers.js' 11 | export * from './mixin.heatmap-layers.js' 12 | export * from './mixin.mapillary-layers.js' 13 | export * from './mixin.gsmap-layers.js' 14 | export * from './mixin.canvas-layers.js' 15 | export * from './mixin.pmtiles-layers.js' 16 | -------------------------------------------------------------------------------- /map/client/mixins/map/mixin.gsmap-layers.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import { Time } from '../../../../core/client/time.js' 3 | import { GSMaPLayer } from '../../leaflet/GSMaPLayer.js' 4 | 5 | export const gsmapLayers = { 6 | methods: { 7 | async createLeafletGSMaPLayer (options) { 8 | const leafletOptions = options.leaflet || options 9 | // Check for valid type 10 | if (leafletOptions.type !== 'gsmapLayer') return 11 | 12 | // Copy options 13 | const colorMap = _.get(options, 'variables[0].chromajs', null) 14 | if (colorMap) Object.assign(leafletOptions, { chromajs: colorMap }) 15 | 16 | // Add current time to options 17 | leafletOptions.time = Time.getCurrentTime() 18 | 19 | // Then create layer 20 | return new GSMaPLayer(leafletOptions) 21 | } 22 | }, 23 | created () { 24 | // Register the new layer constructor 25 | this.registerLeafletConstructor(this.createLeafletGSMaPLayer) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /map/client/mixins/map/mixin.style.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import { getDefaultPointStyle, getDefaultLineStyle, getDefaultPolygonStyle } from '../../utils.map.js' 3 | 4 | export const style = { 5 | created () { 6 | this.registerStyle('point', getDefaultPointStyle) 7 | this.registerStyle('line', getDefaultLineStyle) 8 | this.registerStyle('polygon', getDefaultPolygonStyle) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /map/client/mixins/mixin.feature-selection.js: -------------------------------------------------------------------------------- 1 | export const featureSelection = { 2 | watch: { 3 | 'selection.items': { 4 | handler () { 5 | this.updateHighlights() 6 | this.handleWidget(this.getWidgetForSelection()) 7 | } 8 | }, 9 | 'probe.item': { 10 | handler () { 11 | this.updateHighlights() 12 | this.handleWidget(this.getWidgetForProbe()) 13 | } 14 | } 15 | }, 16 | methods: { 17 | updateHighlights () { 18 | this.clearHighlights() 19 | this.getSelectedItems().forEach(item => { 20 | this.highlight(item.feature || item.location, item.layer) 21 | }) 22 | if (this.hasProbedLocation()) this.highlight(this.getProbedLocation(), this.getProbedLayer()) 23 | }, 24 | handleWidget (widget) { 25 | // If window already open on another widget keep it 26 | if (widget && (widget !== 'none') && !this.isWidgetWindowVisible(widget)) this.openWidget(widget) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /map/client/mixins/mixin.infobox.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | 3 | export const infobox = { 4 | methods: { 5 | getDefaultInfoBox (feature, options) { 6 | let properties = feature.properties 7 | if (properties) { 8 | const engineOptions = _.get(options, this.engine, options) 9 | // Check if explicitely disabled first 10 | if (_.has(engineOptions, 'infobox') && !_.get(engineOptions, 'infobox')) return [] 11 | if (_.has(properties, 'infobox') && !_.get(properties, 'infobox')) return [] 12 | // Otherwise merge options 13 | const infoboxStyle = Object.assign({}, 14 | _.get(this, 'activityOptions.engine.infobox'), 15 | engineOptions.infobox, properties.infobox) 16 | 17 | if (infoboxStyle.pick) { 18 | properties = _.pick(properties, infoboxStyle.pick) 19 | } else if (infoboxStyle.omit) { 20 | properties = _.omit(properties, infoboxStyle.omit) 21 | } 22 | } 23 | return properties 24 | } 25 | }, 26 | created () { 27 | this.registerStyle('infobox', this.getDefaultInfoBox) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /map/client/mixins/mixin.style.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | 3 | export const style = { 4 | methods: { 5 | registerStyle (type, generator) { 6 | // Initialize on first registration 7 | if (!this[type + 'Factory']) this[type + 'Factory'] = [] 8 | this[type + 'Factory'].push(generator) 9 | }, 10 | unregisterStyle (type, generator) { 11 | _.pull(this[type + 'Factory'], generator) 12 | }, 13 | generateStyle () { 14 | const args = Array.from(arguments) 15 | const type = args[0] 16 | if (!this[type + 'Factory']) return 17 | args.shift() 18 | let style 19 | // Iterate over all registered generators until we find one 20 | // Last registered overrides previous ones (usefull to override default styles) 21 | for (let i = this[type + 'Factory'].length - 1; i >= 0; i--) { 22 | const generator = this[type + 'Factory'][i] 23 | style = generator(...args) 24 | if (style) break 25 | } 26 | return style 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /map/client/navigator.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import logger from 'loglevel' 3 | import { Store, Platform, LocalStorage } from '../../core/client/index.js' 4 | 5 | export const Navigator = { 6 | initialize () { 7 | this.availableApps = { 8 | waze: 'https://waze.com/ul?q=<%= lat %>,<%= lon %>', 9 | 'google-maps': 'https://www.google.com/maps/dir/?api=1&destination=<%= lat %>,<%= lon %>', 10 | 'apple-plan': 'https://maps.apple.com/place?ll=<%= lat %>,<%= lon %>' 11 | } 12 | const settings = LocalStorage.get('settings') 13 | if (_.isNil(settings)) { 14 | let app = 'google-maps' 15 | if (Platform.ios) app = 'apple-plan' 16 | Store.set('navigator', app) 17 | logger.debug('[KDK] Navigator initialized to:', this.get()) 18 | } else { 19 | logger.debug('[KDK] Navigator initialized to:', settings.navigator) 20 | } 21 | }, 22 | get () { 23 | return Store.get('navigator') 24 | }, 25 | navigateTo (lat, lon) { 26 | // Retrieve the default app 27 | const app = this.get() 28 | if (_.isEmpty(app)) { 29 | logger.debug('[KDK] Default navigator is undefined') 30 | return 31 | } 32 | // Template the url 33 | const compiledUrl = _.template(this.availableApps[app]) 34 | const interpolatedUrl = compiledUrl({ lat, lon }) 35 | // Open the interpolated url 36 | window.open(interpolatedUrl) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /map/client/readers/index.js: -------------------------------------------------------------------------------- 1 | export * from './reader.geojson.js' 2 | export * from './reader.kml.js' 3 | export * from './reader.gpx.js' 4 | export * from './reader.shp.js' 5 | -------------------------------------------------------------------------------- /map/client/readers/reader.gpx.js: -------------------------------------------------------------------------------- 1 | import logger from 'loglevel' 2 | import { gpx } from '@tmcw/togeojson' 3 | import { i18n } from '../../../core/client/i18n.js' 4 | 5 | export const GPXReader = { 6 | read (files, options) { 7 | if (files.length !== 1) { 8 | logger.debug('invalid \'files\' arguments') 9 | return 10 | } 11 | const file = files[0] 12 | logger.debug(`reading GPX file ${file.name}`) 13 | return new Promise((resolve, reject) => { 14 | const reader = new FileReader() 15 | reader.onloadend = () => { 16 | let content = reader.result 17 | try { 18 | content = gpx(new DOMParser().parseFromString(content, 'text/xml')) 19 | } catch (error) { 20 | logger.debug(error) 21 | reject(new Error(i18n.t('errors.INVALID_GPX_FILE', { file: file.name }), { errors: error })) 22 | return 23 | } 24 | resolve(content) 25 | } 26 | reader.onerror = (error) => { 27 | logger.debug(error) 28 | reject(new Error(i18n.t('errors.CANNOT_READ_FILE', { file: file.name }), { errors: error })) 29 | } 30 | reader.readAsText(file) 31 | }) 32 | }, 33 | getAdditionalFiles () { 34 | return [] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /map/client/utils.all.js: -------------------------------------------------------------------------------- 1 | export * from './utils/index.js' 2 | export * from './leaflet/utils/index.js' 3 | export * from './cesium/utils/index.js' 4 | -------------------------------------------------------------------------------- /map/client/utils.globe.js: -------------------------------------------------------------------------------- 1 | export * from './utils/index.js' 2 | export * from './cesium/utils/index.js' 3 | -------------------------------------------------------------------------------- /map/client/utils.js: -------------------------------------------------------------------------------- 1 | export * from './utils/index.js' 2 | // Now in separated files to avoid mixin Leaflet/Cesium elements 3 | // export * from './leaflet/utils.js' 4 | // export * from './cesium/utils.js' 5 | -------------------------------------------------------------------------------- /map/client/utils.map.js: -------------------------------------------------------------------------------- 1 | export * from './utils/index.js' 2 | export * from './leaflet/utils/index.js' 3 | -------------------------------------------------------------------------------- /map/client/utils/index.js: -------------------------------------------------------------------------------- 1 | export * from './utils.js' 2 | export * from './utils.capture.js' 3 | export * from './utils.catalog.js' 4 | export * from './utils.features.js' 5 | export * from './utils.layers.js' 6 | export * from './utils.location.js' 7 | export * from './utils.offline.js' 8 | export * from './utils.project.js' 9 | export * from './utils.schema.js' 10 | export * from './utils.style.js' 11 | export * from './utils.time-series.js' 12 | -------------------------------------------------------------------------------- /map/client/utils/utils.project.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | 3 | export function getCatalogProjectQuery (project) { 4 | // As we keep track of ID/name depending on if a layer is a user-defined one or not we need to process both 5 | const idQuery = { _id: { $in: _.map(_.filter(project.layers, '_id'), '_id') } } 6 | const nameQuery = { name: { $in: _.map(_.filter(project.layers, 'name'), 'name') } } 7 | return { $or: [idQuery, nameQuery] } 8 | } 9 | -------------------------------------------------------------------------------- /map/common/errors.js: -------------------------------------------------------------------------------- 1 | import { errors } from '../../core/common/index.js' 2 | 3 | export class KGeolocationError extends errors.KError {} 4 | -------------------------------------------------------------------------------- /map/common/moment-utils.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | export function readAsTimeOrDuration (conf) { 4 | let ret = null 5 | if (typeof conf === 'string') { 6 | if (conf.charAt(0) === 'P') { 7 | // treat as a duration 8 | ret = moment.duration(conf) 9 | } else { 10 | // treat as time 11 | ret = moment.utc(conf) 12 | } 13 | 14 | ret = ret.isValid() ? ret : null 15 | } else if (!conf) { 16 | ret = moment.duration(0) 17 | } 18 | 19 | return ret 20 | } 21 | 22 | export function makeTime (timeOrDuration, referenceTime) { 23 | return moment.isDuration(timeOrDuration) ? referenceTime.clone().add(timeOrDuration) : timeOrDuration 24 | } 25 | -------------------------------------------------------------------------------- /map/common/permissions.js: -------------------------------------------------------------------------------- 1 | // Hook computing map abilities for a given user 2 | export function defineUserAbilities (subject, can, cannot) { 3 | can('service', 'geocoder') 4 | can('create', 'geocoder') 5 | can('service', 'catalog') 6 | can('read', 'catalog') 7 | can('service', 'projects') 8 | can('read', 'projects') 9 | can('service', 'alerts') 10 | can('read', 'alerts') 11 | can('service', 'styles') 12 | can('read', 'styles') 13 | // can('service', 'daptiles') 14 | // can('get', 'daptiles') 15 | } 16 | -------------------------------------------------------------------------------- /map/common/schemas/catalog.update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "http://www.kalisio.xyz/schemas/catalog.update.json#", 4 | "title": "schemas.OBJECT_NAME", 5 | "description": "Layer edition schema", 6 | "type": "object", 7 | "properties": { 8 | "name": { 9 | "type": "string", 10 | "maxLength": 128, 11 | "minLength": 3, 12 | "field": { 13 | "component": "form/KTextField", 14 | "label": "schemas.CATALOG_NAME_FIELD_LABEL" 15 | } 16 | }, 17 | "description": { 18 | "type": ["string", "null"], 19 | "maxLength": 256, 20 | "field": { 21 | "component": "form/KTextField", 22 | "label": "schemas.CATALOG_DESCRIPTION_FIELD_LABEL" 23 | } 24 | }, 25 | "featureId": { 26 | "type": ["string", "null"], 27 | "maxLength": 256, 28 | "field": { 29 | "component": "form/KTextField", 30 | "label": "schemas.CATALOG_FEATURE_ID_FIELD_LABEL" 31 | } 32 | }, 33 | "featureLabel": { 34 | "type": ["string", "null"], 35 | "maxLength": 256, 36 | "field": { 37 | "component": "form/KTextField", 38 | "label": "schemas.CATALOG_FEATURE_LABEL_FIELD_LABEL" 39 | } 40 | } 41 | }, 42 | "required": ["name"] 43 | } 44 | 45 | -------------------------------------------------------------------------------- /scripts/build_docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | # set -x 4 | 5 | THIS_FILE=$(readlink -f "${BASH_SOURCE[0]}") 6 | THIS_DIR=$(dirname "$THIS_FILE") 7 | ROOT_DIR=$(dirname "$THIS_DIR") 8 | WORKSPACE_DIR="$(dirname "$ROOT_DIR")" 9 | 10 | . "$THIS_DIR/kash/kash.sh" 11 | 12 | ## Parse options 13 | ## 14 | 15 | PUBLISH=false 16 | CI_STEP_NAME="Build docs" 17 | while getopts "pr:" OPT; do 18 | case $OPT in 19 | p) # publish doc 20 | PUBLISH=true 21 | ;; 22 | r) # report outcome to slack 23 | CI_STEP_NAME=$OPTARG 24 | load_env_files "$WORKSPACE_DIR/development/common/SLACK_WEBHOOK_LIBS.enc.env" 25 | trap 'slack_ci_report "$ROOT_DIR" "$CI_STEP_NAME" "$?" "$SLACK_WEBHOOK_LIBS"' EXIT 26 | ;; 27 | *) 28 | ;; 29 | esac 30 | done 31 | 32 | 33 | ## Build docs 34 | ## 35 | 36 | build_docs "$ROOT_DIR" "kalisio/kdk" "$PUBLISH" 37 | 38 | -------------------------------------------------------------------------------- /scripts/init_runner.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | # set -x 4 | 5 | JOB_ID=$1 6 | 7 | THIS_FILE=$(readlink -f "${BASH_SOURCE[0]}") 8 | THIS_DIR=$(dirname "$THIS_FILE") 9 | 10 | . "$THIS_DIR/kash/kash.sh" 11 | 12 | ### Github Actions 13 | 14 | init_github_run_tests() { 15 | install_reqs age sops nvm node20 mongo7 cc_test_reporter 16 | } 17 | 18 | init_github_additional_tests() { 19 | install_reqs age sops nvm node20 node22 mongo8 20 | } 21 | 22 | init_github_build_docs() { 23 | install_reqs age sops nvm node18 24 | } 25 | 26 | begin_group "Init $CI_ID for $JOB_ID" 27 | 28 | init_"${CI_ID}_${JOB_ID}" 29 | 30 | end_group "Init $CI_ID for $JOB_ID" 31 | -------------------------------------------------------------------------------- /scripts/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | # set -x 4 | 5 | THIS_FILE=$(readlink -f "${BASH_SOURCE[0]}") 6 | THIS_DIR=$(dirname "$THIS_FILE") 7 | ROOT_DIR=$(dirname "$THIS_DIR") 8 | WORKSPACE_DIR="$(dirname "$ROOT_DIR")" 9 | 10 | . "$THIS_DIR/kash/kash.sh" 11 | 12 | ## Parse options 13 | ## 14 | 15 | NODE_VER=20 16 | MONGO_VER=7 17 | CI_STEP_NAME="Run tests" 18 | CODE_COVERAGE=false 19 | while getopts "m:n:cr:" option; do 20 | case $option in 21 | m) # defines mongo version 22 | MONGO_VER=$OPTARG 23 | ;; 24 | n) # defines node version 25 | NODE_VER=$OPTARG 26 | ;; 27 | c) # publish code coverage 28 | CODE_COVERAGE=true 29 | ;; 30 | r) # report outcome to slack 31 | CI_STEP_NAME=$OPTARG 32 | load_env_files "$WORKSPACE_DIR/development/common/SLACK_WEBHOOK_LIBS.enc.env" 33 | trap 'slack_ci_report "$ROOT_DIR" "$CI_STEP_NAME" "$?" "$SLACK_WEBHOOK_LIBS"' EXIT 34 | ;; 35 | *) 36 | ;; 37 | esac 38 | done 39 | 40 | ## Init workspace 41 | ## 42 | 43 | . "$WORKSPACE_DIR/development/workspaces/libs/libs.sh" kdk 44 | 45 | ## Run tests 46 | ## 47 | 48 | run_lib_tests "$ROOT_DIR" "$CODE_COVERAGE" "$NODE_VER" "$MONGO_VER" 49 | -------------------------------------------------------------------------------- /test.api.js: -------------------------------------------------------------------------------- 1 | export * from './test/client/index.js' -------------------------------------------------------------------------------- /test.client.js: -------------------------------------------------------------------------------- 1 | export * from './test/client/index.js' -------------------------------------------------------------------------------- /test/api/core/client.test.js.skip: -------------------------------------------------------------------------------- 1 | import logger from 'loglevel' 2 | import chai, { util, expect } from 'chai' 3 | import chailint from 'chai-lint' 4 | import 'jsdom-global/register' 5 | import 'babel-polyfill' 6 | import fetch from 'isomorphic-fetch' 7 | // Importing the whole weacast module makes Leaflet time dimension fail due to jQuery not be defined 8 | // The following workaround, although presented as working on the internet, does not help 9 | // import jQuery from 'jquery' 10 | // window.jQuery = window.$ = jQuery 11 | // global.jQuery = global.$ = jQuery 12 | // For now simply testing the API 13 | import { kalisio } from '../src/client' 14 | 15 | window.fetch = fetch 16 | 17 | describe('kClient', () => { 18 | let app 19 | 20 | before(() => { 21 | chailint(chai, util) 22 | app = kalisio() 23 | }) 24 | 25 | it('is ES6 compatible', () => { 26 | expect(typeof kalisio).to.equal('function') 27 | }) 28 | 29 | it('registers the users service', () => { 30 | expect(app.users).toExist() 31 | }) 32 | 33 | it('registers the log options', () => { 34 | logger.info('This is a log test') 35 | expect(logger.getLevel()).to.equal(logger.levels.ERROR) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /test/api/core/config/email-templates/confirmInvitation/html.ejs: -------------------------------------------------------------------------------- 1 |

<%= email.subject %>

2 | 3 |

Dear <%= user.profile.name %>.

4 | 5 |

You'be been invited to join us

6 | 7 |

An account has been created for you and is ready to use with the following details: 8 |

    9 |
  • email: <%= user.email %>
  • 10 |
  • password: <%= user.password %>
  • 11 |
12 | 13 |

14 | 15 |

Follow this link to access the login page.

16 | 17 |

If you do not want to get registrated to kApp please let us know by following this link.

18 | 19 | -------------------------------------------------------------------------------- /test/api/core/config/email-templates/identityChange/html.ejs: -------------------------------------------------------------------------------- 1 |

[Kalisio KDK] <%= email.subject %>

2 | 3 |

Hi <%= user.profile.name %>, your user information was just changed. Please verify that you made these changes.

4 |
    5 | <% for (var key in user.verifyChanges) {%> 6 |
  • <%= key %>: <%= user.verifyChanges[key] %>
  • 7 | <% } %> 8 |
9 | 10 |

Use the verification code below to approve the changes.

11 | 12 |

<%= user.verifyShortToken %>

13 | 14 |

If you didn't request this change please let us know by following this link.

15 | -------------------------------------------------------------------------------- /test/api/core/config/email-templates/newDevice/html.ejs: -------------------------------------------------------------------------------- 1 |

<%= email.subject %>

2 | 3 |

Hi <%= user.profile.name %>, your account was just signed in from a new <%= device.platform %> device. You're getting this email to make sure it was you.

4 | 5 |

Follow this link to check the activity.

6 | 7 |

If you do not recognize this activity please let us know by following this link.

8 | -------------------------------------------------------------------------------- /test/api/core/config/email-templates/newSubscription/html.ejs: -------------------------------------------------------------------------------- 1 |

[Kalisio KDK] <%= email.subject %>

2 | 3 |

Hi <%= user.profile.name %>, your account was just signed in from a new <%= subscription.browser.name %> browser. You are getting this email to make sure it was you.

4 | 5 |

Follow this link to check the activity.

6 | 7 |

If you do not recognize this activity please let us know by following this link.

8 | -------------------------------------------------------------------------------- /test/api/core/config/email-templates/passwordChange/html.ejs: -------------------------------------------------------------------------------- 1 |

<%= email.subject %>

2 | 3 |

Hi <%= user.profile.name %>, the password associated to your email <%= user.email %> was just changed.

4 | 5 |

If you didn't request this change please let us know by following this link.

6 | -------------------------------------------------------------------------------- /test/api/core/config/email-templates/resendVerifySignup/html.ejs: -------------------------------------------------------------------------------- 1 |

[Kalisio KDK] <%= email.subject %>

2 | 3 |

Thank you for signing up <%= user.profile.name %>.

4 | 5 |

Please verify your email <%= user.email %> so you can reset your password if need be. You may still use the site if your email is unverified but we can't send you emails.

6 | 7 |

Use the verification code below to verify your email.

8 | 9 |

<%= user.verifyShortToken %>

10 | 11 |

If you didn't request this change please let us know by following this link.

12 | 13 | -------------------------------------------------------------------------------- /test/api/core/config/email-templates/resetPwd/html.ejs: -------------------------------------------------------------------------------- 1 |

<%= email.subject %>

2 | 3 |

Hi <%= user.profile.name %>, the password associated to your email <%= user.email %> was just reset.

4 | 5 |

If you didn't request this change please let us know by following this link.

6 | -------------------------------------------------------------------------------- /test/api/core/config/email-templates/sendResetPwd/html.ejs: -------------------------------------------------------------------------------- 1 |

[Kalisio KDK] <%= email.subject %>

2 | 3 |

Hi <%= user.profile.name %>, we got a request to reset your password associated to your email <%= user.email %>.

4 | 5 |

Follow this link and use the verification code below to reset your password.

6 | 7 |

Use the verification code below to reset your password.

8 | 9 |

<%= user.resetShortToken %>

10 | 11 |

If you didn't request this change please let us know by following this link.

12 | 13 | -------------------------------------------------------------------------------- /test/api/core/config/email-templates/verifySignup/html.ejs: -------------------------------------------------------------------------------- 1 |

<%= email.subject %>

2 | 3 |

Your email <%= user.email %> has been verified. We can now safely send you a link to reset your password if you ever need it.

4 | -------------------------------------------------------------------------------- /test/api/core/data/invalid-objects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "PUI-PPI-B9-1", 6 | "value": 150 7 | }, 8 | "geometry": { 9 | "type": "LineString", 10 | "coordinates": [ 11 | 139.72669982910156, 12 | 35.64739990234375, 13 | 0 14 | ] 15 | } 16 | }, 17 | { 18 | "type": "Feature", 19 | "properties": { 20 | "id": "PUI-PPI-B9-2", 21 | "value": 150 22 | }, 23 | "geometry": { 24 | "type": "Point", 25 | "coordinates": [] 26 | } 27 | }, 28 | { 29 | "type": "Feature", 30 | "properties": { 31 | "id": "PUI-PPI-B9-3", 32 | "value": 150 33 | }, 34 | "geometry": { 35 | "type": "Point", 36 | "coordinates": [ 37 | 139.72669982910156, 38 | 35.64739990234375, 39 | 0 40 | ] 41 | } 42 | }, 43 | { 44 | "type": "Feature", 45 | "properties": { 46 | "type": "manual", 47 | "id": "PUI-PPI-B9-4", 48 | "date": "2022-01-07", 49 | "time": "16:20:20" 50 | }, 51 | "geometry": { 52 | "type": "Point", 53 | "coordinates": [ 54 | 139.72669982910156, 55 | 35.64739990234375, 56 | 0 57 | ] 58 | } 59 | } 60 | ] -------------------------------------------------------------------------------- /test/api/core/data/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/test/api/core/data/logo.png -------------------------------------------------------------------------------- /test/api/core/data/valid-objects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "type": "manual", 6 | "id": "PUI-PPI-B9-1", 7 | "date": "2022-01-07", 8 | "time": "16:20:02", 9 | "value": 61 10 | }, 11 | "geometry": { 12 | "type": "Point", 13 | "coordinates": [ 14 | 1.95, 15 | 43.31, 16 | 0 17 | ] 18 | } 19 | }, 20 | { 21 | "type": "Feature", 22 | "properties": { 23 | "type": "automatic", 24 | "id": "PUI-PPI-B9-2", 25 | "date": "2022-01-07", 26 | "time": "16:20:20", 27 | "value": 150 28 | }, 29 | "geometry": { 30 | "type": "Point", 31 | "coordinates": [ 32 | 1.95, 33 | 43.31, 34 | 0 35 | ] 36 | } 37 | } 38 | ] -------------------------------------------------------------------------------- /test/api/core/index.js: -------------------------------------------------------------------------------- 1 | export * from './utils.js' 2 | -------------------------------------------------------------------------------- /test/api/index.js: -------------------------------------------------------------------------------- 1 | import * as core from './core/index.js' 2 | import * as map from './map/index.js' 3 | 4 | export { core, map } -------------------------------------------------------------------------------- /test/api/map/data/GetCoverage.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/test/api/map/data/GetCoverage.tif -------------------------------------------------------------------------------- /test/api/map/data/dataset.grb.dds: -------------------------------------------------------------------------------- 1 | Dataset { 2 | Int32 LatLon_Projection; 3 | Float32 lat[lat = 361]; 4 | Float32 lon[lon = 721]; 5 | Float64 reftime; 6 | Float64 time[time = 25]; 7 | Float32 height_above_ground[height_above_ground = 10]; 8 | Grid { 9 | ARRAY: 10 | Float32 Temperature_height_above_ground[time = 25][height_above_ground = 10][lat = 361][lon = 721]; 11 | MAPS: 12 | Float64 time[time = 25]; 13 | Float32 height_above_ground[height_above_ground = 10]; 14 | Float32 lat[lat = 361]; 15 | Float32 lon[lon = 721]; 16 | } Temperature_height_above_ground; 17 | } mf-arpege-05/2019/11/26/06/T6086_G_T_Hau_20191126060000.grb; 18 | -------------------------------------------------------------------------------- /test/api/map/data/dataset.grb.dods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/test/api/map/data/dataset.grb.dods -------------------------------------------------------------------------------- /test/api/map/data/lat_lon_bounds.grb.dods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/test/api/map/data/lat_lon_bounds.grb.dods -------------------------------------------------------------------------------- /test/api/map/data/subdataset.grb.dods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/test/api/map/data/subdataset.grb.dods -------------------------------------------------------------------------------- /test/api/map/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalisio/kdk/f549695948c120831ebc754e53c2d0dedfbb9c55/test/api/map/index.js -------------------------------------------------------------------------------- /test/client/core/dialogs.js: -------------------------------------------------------------------------------- 1 | import { click, isElementVisible } from './utils.js' 2 | 3 | export async function closeWelcomeDialog (page) { 4 | await click(page, '.q-dialog #close-button') 5 | } 6 | 7 | export async function closeInstallDialog (page) { 8 | await click(page, '.q-dialog #ignore-button') 9 | } 10 | 11 | export async function isToastVisible (page) { 12 | return isElementVisible(page, '[role="alert"]') 13 | } 14 | -------------------------------------------------------------------------------- /test/client/core/index.js: -------------------------------------------------------------------------------- 1 | export * from './account.js' 2 | export * from './api.js' 3 | export * from './collection.js' 4 | export * from './dialogs.js' 5 | export * from './layout.js' 6 | export * from './runner.js' 7 | export * from './screens.js' 8 | export * from './time.js' 9 | export * from './utils.js' 10 | -------------------------------------------------------------------------------- /test/client/core/time.js: -------------------------------------------------------------------------------- 1 | import makeDebug from 'debug' 2 | 3 | const debug = makeDebug('kdk:core:test:time') 4 | 5 | export async function setCurrentTime (page, time) { 6 | debug(`Setting current time to ${time}`) 7 | await page.evaluate((time) => { 8 | window.$time.setCurrentTime(time) 9 | }, time) 10 | } 11 | -------------------------------------------------------------------------------- /test/client/index.js: -------------------------------------------------------------------------------- 1 | import * as core from './core/index.js' 2 | import * as map from './map/index.js' 3 | 4 | export { core, map } -------------------------------------------------------------------------------- /test/client/map/api.js: -------------------------------------------------------------------------------- 1 | import * as core from '../core/index.js' 2 | 3 | // Decorate core API with some of the required features for map 4 | export class Api extends core.Api { 5 | createClient (options = {}) { 6 | const client = super.createClient(options) 7 | 8 | client.createGeoJsonFeature = async (layerId, coordinates, properties = {}, service = 'features') => { 9 | // Deduce geometry type from coordinates 10 | let type = 'Point' 11 | if (Array.isArray(coordinates[0])) { 12 | type = (Array.isArray(coordinates[0][0]) ? 'Polygon' : 'LineString') 13 | } 14 | const feature = await client.getService(service).create(Object.assign({ 15 | type: 'Feature', 16 | geometry: { 17 | type, 18 | coordinates 19 | }, 20 | layer: layerId 21 | }, properties)) 22 | return feature 23 | } 24 | 25 | client.updateGeoJsonFeature = async (featureId, coordinates, properties = {}, service = 'features') => { 26 | const feature = await client.getService(service).patch(featureId, Object.assign({ 27 | 'geometry.coordinates': coordinates 28 | }, properties)) 29 | return feature 30 | } 31 | 32 | return client 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/client/map/index.js: -------------------------------------------------------------------------------- 1 | export * from './api.js' 2 | export * from './catalog.js' 3 | export * from './controls.js' 4 | export * from './time.js' 5 | export * from './utils.js' 6 | -------------------------------------------------------------------------------- /test/client/map/time.js: -------------------------------------------------------------------------------- 1 | import makeDebug from 'debug' 2 | import * as core from '../core/index.js' 3 | 4 | const debug = makeDebug('kdk:map:test:time') 5 | 6 | export async function openTimeline (page) { 7 | const isTimelineOpened = await core.isPaneVisible(page, 'bottom') 8 | if (!isTimelineOpened) await core.clickOpener(page, 'bottom') 9 | return isTimelineOpened 10 | } 11 | 12 | export async function clickTimelineHour (page, hour, wait = 1000) { 13 | const isTimelineOpened = await openTimeline(page) 14 | await core.click(page, '#time-button', wait) 15 | const xpath = `//div[contains(@class, "q-time__clock-pos-${hour}")]` 16 | const elements = await page.$x(xpath) 17 | if (elements.length > 0) { 18 | elements[0].click() 19 | await page.waitForTimeout(wait) 20 | debug(`Clicked timeline hour ${hour}`) 21 | } else { 22 | debug(`Timeline hour ${hour} not found`) 23 | } 24 | if (!isTimelineOpened) await core.clickOpener(page, 'bottom') 25 | } 26 | -------------------------------------------------------------------------------- /test/client/map/utils.js: -------------------------------------------------------------------------------- 1 | import * as core from '../core/index.js' 2 | 3 | /* Moves the map for a specific times: 4 | - in a choosen direction (left, right, up, down) 5 | - zoom in or out 6 | */ 7 | export async function moveMap (page, direction, times, wait = 500) { 8 | let dir 9 | if (direction === 'up') { dir = 'ArrowUp' } else if (direction === 'down') { dir = 'ArrowDown' } else if (direction === 'left') { dir = 'ArrowLeft' } else if (direction === 'right') { dir = 'ArrowRight' } else if (direction === 'in') { dir = '+' } else if (direction === 'out') { dir = '-' } 10 | await page.focus('#map') 11 | await page.waitForTimeout(wait) 12 | let i = 0 13 | for (i = 0; i < times; i++) { 14 | await page.keyboard.press(dir) 15 | await page.waitForTimeout(wait) 16 | } 17 | } 18 | 19 | /* Zooms the map to a specific level 20 | */ 21 | export async function zoomToLevel (page, storePath, level, wait = 500) { 22 | // FIXME: better way to get activity state ? 23 | // At current time the activity should implement state saving to make it work 24 | const zoom = await core.getFromStore(page, `${storePath}.state.zoom`) 25 | const diff = level - zoom 26 | const action = (level > zoom) ? 'in' : 'out' 27 | await moveMap(page, action, Math.abs(diff), wait) 28 | await page.waitForTimeout(wait) 29 | } 30 | --------------------------------------------------------------------------------