diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..18a25b4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +node_modules +dist +.git +.vscode diff --git a/.env.development.local.sample b/.env.development.local.sample index 21fd0af..244e21b 100644 --- a/.env.development.local.sample +++ b/.env.development.local.sample @@ -12,3 +12,7 @@ VITE_FIREBASE_MEASUREMENT_ID= VITE_MAPILLARY_API_KEY= VITE_OSM_OAUTH_REDIRECT_URI= + +VITE_FIREBASE_DB_EMULATOR_HOST= +VITE_FIREBASE_DB_EMULATOR_PORT= +VITE_FIREBASE_AUTH_EMULATOR_URL= diff --git a/.github/workflows/publish-web-app-serve.yml b/.github/workflows/publish-web-app-serve.yml new file mode 100644 index 0000000..20bdbfe --- /dev/null +++ b/.github/workflows/publish-web-app-serve.yml @@ -0,0 +1,23 @@ +name: Publish web app serve + +on: + workflow_dispatch: + push: + branches: + - develop + - project/* + +permissions: + packages: write + +jobs: + publish_image: + name: Publish Docker Image + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Publish web-app-serve + uses: toggle-corp/web-app-serve-action@v0.1.1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5582964 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,70 @@ +# -------------------------- Dev --------------------------------------- + +FROM node:22-bullseye AS dev + +RUN apt-get update -y \ + && apt-get install -y --no-install-recommends git \ + && rm -rf /var/lib/apt/lists/* \ + && npm install -g yarn@1.22.19 --force \ + && git config --global --add safe.directory /code + +WORKDIR /code + +# Build stage for web app +FROM dev AS web-app-serve-build + +COPY ./package.json ./yarn.lock /code/ + +RUN yarn install +COPY . /code/ + +ENV VITE_FIREBASE_API_KEY=ExampleF1rebaseAP1k3y +ENV VITE_FIREBASE_AUTH_DOMAIN=example-key.firebaseapp.com +ENV VITE_FIREBASE_DATABASE_URL=https://example-database.firebaseio.com +ENV VITE_FIREBASE_PROJECT_ID=example +ENV VITE_FIREBASE_STORAGE_BUCKET=example.appspot.com +ENV VITE_FIREBASE_MESSAGING_SENDER_ID=123123456123 +ENV VITE_FIREBASE_APP_ID=1:23456789:web:1abc234def567 +ENV VITE_COMMUNITY_DASHBOARD_URL=https://mapswipe.org + +ENV VITE_FIREBASE_MEASUREMENT_ID= +ENV VITE_MAPILLARY_API_KEY= +ENV VITE_BASE_URL=https://mapswipe.org/privacy +ENV VITE_PRIVACY_POLICY_URL=https://mapswipe.org/privacy/ +ENV VITE_IMPRINT_URL=https://mapswipe.org/privacy/ +ENV VITE_APP_LOGO=./img/mapswipe-white.svg +ENV VITE_PROJECTS_FALLBACK_IMAGE=./img/map-pin-600x400.jpg +ENV VITE_ALLOW_UNVERIFIED_USERS=true + +ENV VITE_DEFAULT_LOCALE=en +ENV VITE_FALLBACK_LOCALE=en +ENV VITE_SUPPORTED_LOCALES=en,de,fr + +ENV VITE_THEME_LIGHT_PRIMARY=#060E2F +ENV VITE_THEME_LIGHT_SECONDARY=#0D1949 +ENV VITE_THEME_LIGHT_TERTIARY=#EEF2FB +ENV VITE_THEME_LIGHT_ACCENT=#589AE3 +ENV VITE_THEME_LIGHT_ERROR=#C62828 +ENV VITE_THEME_LIGHT_WARNING=#8E0000 +ENV VITE_THEME_LIGHT_INFO=#2196f3 +ENV VITE_THEME_LIGHT_SUCCESS=#4caf50 +ENV VITE_THEME_LIGHT_NEUTRAL=#272727 + +ENV VITE_APP_NAME=MapSwipe +ENV VITE_APP_WEBSITE_URL=https://mapswipe.org +ENV VITE_APP_ATTRIBUTION_TITLE=MapSwipe +ENV VITE_APP_ATTRIBUTION_URL=https://mapswipe.org/privacy/ + +RUN WEB_APP_SERVE_ENABLED=true yarn build-only --outDir=/code/build + +FROM ghcr.io/toggle-corp/web-app-serve:v0.1.2 AS web-app-serve + +LABEL org.opencontainers.image.source="github.com/mapswipe/mapswipe-web" +LABEL org.opencontainers.image.authors="dev@togglecorp.com" + +ENV APPLY_CONFIG__SOURCE_DIRECTORY=/code/build/ + +COPY ./web-app-serve/web-app-apply-config.sh /code/ +ENV APPLY_CONFIG__APPLY_CONFIG_PATH=/code/web-app-apply-config.sh + +COPY --from=web-app-serve-build /code/build "$APPLY_CONFIG__SOURCE_DIRECTORY" diff --git a/env.ts b/env.ts new file mode 100644 index 0000000..12d54c1 --- /dev/null +++ b/env.ts @@ -0,0 +1,57 @@ +import { + defineConfig, + overrideDefineForWebAppServe, + Schema, +} from '@togglecorp/vite-plugin-validate-env'; + +const webAppServeEnabled = process.env.WEB_APP_SERVE_ENABLED?.toLowerCase() === 'true'; +if (webAppServeEnabled) { + // eslint-disable-next-line no-console + console.warn('Building application for web-app-serve'); +} +const overrideDefine = webAppServeEnabled + ? overrideDefineForWebAppServe + : undefined; + +export default defineConfig({ + overrideDefine, + validator: 'builtin', + schema: { + VITE_FIREBASE_API_KEY: Schema.string.optional(), + VITE_FIREBASE_AUTH_DOMAIN: Schema.string.optional(), + VITE_FIREBASE_DATABASE_URL: Schema.string({ format: 'url', protocol: true, tld: false }), + VITE_FIREBASE_PROJECT_ID: Schema.string.optional(), + VITE_FIREBASE_STORAGE_BUCKET: Schema.string.optional(), + VITE_FIREBASE_MESSAGING_SENDER_ID: Schema.string.optional(), + VITE_FIREBASE_APP_ID: Schema.string.optional(), + VITE_FIREBASE_MEASUREMENT_ID: Schema.string.optional(), + VITE_BASE_URL: Schema.string.optional({ format: 'url', protocol: true, tld: false }), + VITE_PRIVACY_POLICY_URL: Schema.string({ format: 'url', protocol: true, tld: false }), + VITE_IMPRINT_URL: Schema.string.optional({ format: 'url', protocol: true, tld: false }), + VITE_APP_NAME: Schema.string.optional(), + VITE_APP_ATTRIBUTION_TITLE: Schema.string.optional(), + VITE_APP_ATTRIBUTION_URL: Schema.string.optional({ format: 'url', protocol: true, tld: false }), + VITE_APP_WEBSITE_URL: Schema.string({ format: 'url', protocol: true, tld: false }), + VITE_APP_LOGO: Schema.string.optional(), + VITE_PROJECTS_FALLBACK_IMAGE: Schema.string.optional(), + VITE_ALLOW_UNVERIFIED_USERS: Schema.boolean(), + + VITE_DEFAULT_LOCALE: Schema.string.optional(), + VITE_FALLBACK_LOCALE: Schema.string.optional(), + VITE_SUPPORTED_LOCALES: Schema.string.optional(), + + VITE_THEME_LIGHT_PRIMARY: Schema.string.optional(), + VITE_THEME_LIGHT_SECONDARY: Schema.string.optional(), + VITE_THEME_LIGHT_TERTIARY: Schema.string.optional(), + VITE_THEME_LIGHT_ACCENT: Schema.string.optional(), + VITE_THEME_LIGHT_ERROR: Schema.string.optional(), + VITE_THEME_LIGHT_WARNING: Schema.string.optional(), + VITE_THEME_LIGHT_INFO: Schema.string.optional(), + VITE_THEME_LIGHT_SUCCESS: Schema.string.optional(), + VITE_THEME_LIGHT_NEUTRAL: Schema.string.optional(), + + VITE_COMMUNITY_DASHBOARD_URL: Schema.string({ format: 'url', protocol: true, tld: false }), + + VITE_MAPILLARY_API_KEY: Schema.string.optional(), + }, +}); diff --git a/package.json b/package.json index 825d164..4e8c62b 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "mapswipe-web", "version": "0.2.8", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "run-p type-check \"build-only {@}\" --", @@ -17,9 +18,12 @@ }, "dependencies": { "@intlify/unplugin-vue-i18n": "^6.0.8", + "@togglecorp/fujs": "^2.2.0", + "@turf/bbox": "^7.2.0", "base-64": "^1.0.0", "firebase": "^11.8.1", "mapillary-js": "^4.1.2", + "maplibre-gl": "^5.6.1", "ol": "^10.5.0", "ol-contextmenu": "^5.5.0", "ol-ext": "^4.0.31", @@ -39,6 +43,7 @@ "@mdi/font": "^7.4.47", "@playwright/test": "^1.38.0", "@rushstack/eslint-patch": "^1.3.3", + "@togglecorp/vite-plugin-validate-env": "^2.2.1", "@tsconfig/node18": "^18.2.2", "@types/base-64": "^1.0.2", "@types/jsdom": "^21.1.2", diff --git a/src/components/BaseMap.vue b/src/components/BaseMap.vue new file mode 100644 index 0000000..38c8405 --- /dev/null +++ b/src/components/BaseMap.vue @@ -0,0 +1,436 @@ + + + + + diff --git a/src/components/CompareProject.vue b/src/components/CompareProject.vue index 533de87..0bac7f9 100644 --- a/src/components/CompareProject.vue +++ b/src/components/CompareProject.vue @@ -28,6 +28,7 @@ interface ProjectCompareType { } credits: string lookFor: string + projectInstruction: string } const defaultOptions: Option[] = [ @@ -146,7 +147,9 @@ export default defineComponent({ return attribution.join('; ') }, mission() { - const message = this.$t('projectView.youAreLookingFor', { lookFor: this.project.lookFor }) + const message = isDefined(this.project.projectInstruction) + ? this.project.projectInstruction + : this.$t('projectView.youAreLookingFor', { lookFor: this.project.lookFor }) return message }, }, diff --git a/src/components/CompareProjectTutorial.vue b/src/components/CompareProjectTutorial.vue index 23ec507..35b4113 100644 --- a/src/components/CompareProjectTutorial.vue +++ b/src/components/CompareProjectTutorial.vue @@ -18,6 +18,7 @@ export interface Tutorial { projectId: string name: string lookFor?: string + projectInstruction?: string screens: { hint: Screen instructions: Screen @@ -58,7 +59,9 @@ export default defineComponent({ }, computed: { mission() { - const message = this.$t('projectView.youAreLookingFor', { lookFor: this.tutorial.lookFor }) + const message = isDefined(this.tutorial?.projectInstruction) + ? this.tutorial.projectInstruction + : this.$t('projectView.youAreLookingFor', { lookFor: this.tutorial?.lookFor }) return message }, currentScreen() { diff --git a/src/components/CompletenessProject.vue b/src/components/CompletenessProject.vue new file mode 100644 index 0000000..2d3803f --- /dev/null +++ b/src/components/CompletenessProject.vue @@ -0,0 +1,404 @@ + + + + + diff --git a/src/components/CompletenessProjectTutorial.vue b/src/components/CompletenessProjectTutorial.vue new file mode 100644 index 0000000..a889a05 --- /dev/null +++ b/src/components/CompletenessProjectTutorial.vue @@ -0,0 +1,391 @@ + + + + + diff --git a/src/components/FindProjectTutorial.vue b/src/components/FindProjectTutorial.vue index 23eb7df..81593b9 100644 --- a/src/components/FindProjectTutorial.vue +++ b/src/components/FindProjectTutorial.vue @@ -27,6 +27,7 @@ export interface Tutorial { projectId: string name: string lookFor?: string + projectInstruction?: string screens: { hint: Screen instructions: Screen @@ -86,7 +87,9 @@ export default defineComponent({ ) }, mission() { - const message = this.$t('projectView.youAreLookingFor', { lookFor: this.tutorial.lookFor }) + const message = isDefined(this.tutorial?.projectInstruction) + ? this.tutorial.projectInstruction + : this.$t('projectView.youAreLookingFor', { lookFor: this.tutorial.lookFor }) return message }, currentScreen() { diff --git a/src/components/MediaProject.vue b/src/components/MediaProject.vue index 230c71f..25e868e 100644 --- a/src/components/MediaProject.vue +++ b/src/components/MediaProject.vue @@ -6,6 +6,7 @@ import ProjectHeader from './ProjectHeader.vue' import ProjectInfo from './ProjectInfo.vue' import TaskProgress from '@/components/TaskProgress.vue' import MediaProjectInstructions from './MediaProjectInstructions.vue' +import { isDefined } from '@togglecorp/fujs' export default defineComponent({ components: { @@ -86,7 +87,9 @@ export default defineComponent({ return attribution }, mission() { - const message = this.project?.lookFor + const message = isDefined(this.project?.projectInstruction) + ? this.project.projectInstruction + : this.project?.lookFor return message }, isImageTask() { diff --git a/src/components/OauthReturn.vue b/src/components/OauthReturn.vue index c5dcfed..caf8fb4 100644 --- a/src/components/OauthReturn.vue +++ b/src/components/OauthReturn.vue @@ -1,8 +1,8 @@ + + + + diff --git a/src/components/ValidateImageProjectInstructions.vue b/src/components/ValidateImageProjectInstructions.vue new file mode 100644 index 0000000..c41d8c3 --- /dev/null +++ b/src/components/ValidateImageProjectInstructions.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/components/ValidateImageProjectTask.vue b/src/components/ValidateImageProjectTask.vue new file mode 100644 index 0000000..c5e0fac --- /dev/null +++ b/src/components/ValidateImageProjectTask.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/src/components/ValidateImageProjectTutorial.vue b/src/components/ValidateImageProjectTutorial.vue new file mode 100644 index 0000000..657de4f --- /dev/null +++ b/src/components/ValidateImageProjectTutorial.vue @@ -0,0 +1,211 @@ + + + + + diff --git a/src/components/ValidateProject.vue b/src/components/ValidateProject.vue index 5029e05..fb82c69 100644 --- a/src/components/ValidateProject.vue +++ b/src/components/ValidateProject.vue @@ -1,6 +1,7 @@