diff --git a/package.json b/package.json
index 454db94..5593708 100644
--- a/package.json
+++ b/package.json
@@ -39,8 +39,10 @@
"@playwright/test": "^1.38.0",
"@rushstack/eslint-patch": "^1.3.3",
"@tsconfig/node18": "^18.2.2",
+ "@types/base-64": "^1.0.2",
"@types/jsdom": "^21.1.2",
"@types/node": "^18.17.15",
+ "@types/pako": "^2.0.3",
"@vitejs/plugin-vue": "^4.3.4",
"@vitejs/plugin-vue-jsx": "^3.0.2",
"@vue/eslint-config-prettier": "^8.0.0",
diff --git a/src/components/CompareProject.vue b/src/components/CompareProject.vue
index 9ed5397..76a7b3b 100644
--- a/src/components/CompareProject.vue
+++ b/src/components/CompareProject.vue
@@ -1,25 +1,83 @@
+
+
+
+
+
+
+
+ {{ $t('compareProject.before') }}
+
+
+
+
+
+
+
+
+
+ {{ $t('compareProject.after') }}
+
+
+
+
+
+
+
diff --git a/src/components/CompareProjectTutorial.vue b/src/components/CompareProjectTutorial.vue
new file mode 100644
index 0000000..938a95b
--- /dev/null
+++ b/src/components/CompareProjectTutorial.vue
@@ -0,0 +1,265 @@
+
+
+
+
+
+
+
+ {{ instructionMessage }}
+
+
+
+
+
+
+
+
+ {{ $t('projectTutorial.showAnswer') }}
+
+
+ {{ $t('projectTutorial.nextTask') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/FindProject.vue b/src/components/FindProject.vue
index 1fa5a90..ff904d5 100644
--- a/src/components/FindProject.vue
+++ b/src/components/FindProject.vue
@@ -1,10 +1,10 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/FindProjectTutorial.vue b/src/components/FindProjectTutorial.vue
new file mode 100644
index 0000000..b7ba3b9
--- /dev/null
+++ b/src/components/FindProjectTutorial.vue
@@ -0,0 +1,369 @@
+
+
+
+
+
+
+
+ {{ instructionMessage }}
+
+
+
+
+
+
+
+
+ {{ $t('projectTutorial.showAnswer') }}
+
+
+ {{ $t('projectTutorial.nextTask') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/ImageTile.vue b/src/components/ImageTile.vue
index bd24d7a..f4f0f96 100644
--- a/src/components/ImageTile.vue
+++ b/src/components/ImageTile.vue
@@ -5,11 +5,9 @@ export default defineComponent({
props: {
url: {
type: String,
- required: true,
},
urlB: {
type: String,
- require: false,
},
spinner: {
type: Boolean,
@@ -25,7 +23,6 @@ export default defineComponent({
},
maxSize: {
type: String,
- require: false,
},
},
})
@@ -33,6 +30,7 @@ export default defineComponent({
-
+
+
+ {{ $t('imageTile.failureMessage') }}
+
+
+
+
+ {{ $t('imageTile.notAvailableMessage') }}
+
-
+
diff --git a/src/components/ImageTileOverlay.vue b/src/components/ImageTileOverlay.vue
new file mode 100644
index 0000000..a2ceb87
--- /dev/null
+++ b/src/components/ImageTileOverlay.vue
@@ -0,0 +1,49 @@
+
+
+
+
+ {{ overlayLabel }}
+
+
+
+
diff --git a/src/components/MagnifyImageTile.vue b/src/components/MagnifyImageTile.vue
index 0877683..e48db63 100644
--- a/src/components/MagnifyImageTile.vue
+++ b/src/components/MagnifyImageTile.vue
@@ -1,24 +1,18 @@
-
+
-
- {{ task.label }}
-
+
diff --git a/src/components/OptionButtons.vue b/src/components/OptionButtons.vue
index 6d3e410..dc3ddff 100644
--- a/src/components/OptionButtons.vue
+++ b/src/components/OptionButtons.vue
@@ -1,10 +1,26 @@
+
+
+ mdi-check-circle
+
+ {{ $t('projectTutorial.completionTitle') }}
+
+
+ {{ $t('projectTutorial.completionDescription') }}
+
+
+ {{ $t('projectTutorial.startMapping') }}
+
+
+
+
diff --git a/src/components/ValidateProject.vue b/src/components/ValidateProject.vue
index a8c0585..a724769 100644
--- a/src/components/ValidateProject.vue
+++ b/src/components/ValidateProject.vue
@@ -1,20 +1,21 @@
@@ -84,6 +88,14 @@ export default defineComponent({
{{ $t('validateProjectInstructions.seenAll') }}
+ {{ $t('projectInstructions.dontWorry') }}
+
+ {{
+ $t('projectInstructions.everyTaskIsViewedBy', {
+ verificationNumber: this.verificationNumber,
+ })
+ }}.
+
diff --git a/src/components/ValidateProjectTask.vue b/src/components/ValidateProjectTask.vue
new file mode 100644
index 0000000..1172b4e
--- /dev/null
+++ b/src/components/ValidateProjectTask.vue
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/ValidateProjectTutorial.vue b/src/components/ValidateProjectTutorial.vue
new file mode 100644
index 0000000..4b3aced
--- /dev/null
+++ b/src/components/ValidateProjectTutorial.vue
@@ -0,0 +1,285 @@
+
+
+
+
+
+
+
+ {{ instructionMessage }}
+
+
+
+
+
+
+
+
+ {{ $t('projectTutorial.showAnswer') }}
+
+
+ {{ $t('projectTutorial.nextTask') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/firebase/index.ts b/src/firebase/index.ts
index 34a0af5..5888e3a 100644
--- a/src/firebase/index.ts
+++ b/src/firebase/index.ts
@@ -42,8 +42,9 @@ export const projectsRef = ref(db, 'v2/projects')
export const userGroupsRef = ref(db, 'v2/userGroups')
export const userGroupMembershipLogsRef = ref(db, '/v2/userGroupMembershipLogs/')
+export const getGroupsRef = (projectId: string) => ref(db, `v2/groups/${projectId}`)
// export reusable database queries
export const getGroupsQuery = (projectId: string) => {
- return query(ref(db, `v2/groups/${projectId}`), orderByChild('requiredCount'), startAfter(0))
+ return query(getGroupsRef(projectId), orderByChild('requiredCount'), startAfter(0))
}
export const activeProjectsQuery = query(projectsRef, orderByChild('status'), equalTo('active'))
diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json
index 4d398a1..015903a 100644
--- a/src/i18n/locales/de.json
+++ b/src/i18n/locales/de.json
@@ -144,6 +144,8 @@
"projectInstructions": {
"classifyTitle": "Gib deine Antwort",
"classifyInstruction": "Benutze Buttons, um deine Antwort zu geben",
+ "dontWorry": "Keine Sorge, wenn du unsicher bist",
+ "everyTaskIsViewedBy": "Jede Aufgabe wird von mindestens {verificationNumber} MapSwipern gesehen. Es ist daher OK, wenn du mal etwas übersiehst",
"howToContribute": "Wie kann ich beitragen?",
"imageCredits": "Bildnachweis",
"move": "Zurück und weiter bewegen",
@@ -203,5 +205,16 @@
"drawNewFeature": "Neue Objekte einzeichnen",
"modifyFeature": "Objekte bearbeiten",
"deleteFeature": "Objekte löschen"
+ },
+ "projectTutorial": {
+ "showAnswer": "Antwort zeigen",
+ "nextTask": "Nächste Aufgabe",
+ "completionTitle": "Du bist bereit zum MapSwipen!",
+ "completionDescription": "Du hast jetzt alles was du brauchst, um mit der App einen Beitrag zur humanitären Kartierung zu leisten. Du kannst jederzeit für eine Auffrischung zurückkommen.",
+ "startMapping": "Beginnen"
+ },
+ "imageTile": {
+ "failureMessage": "Bild konnte nicht geladen werden!",
+ "notAvailableMessage": "Bild nicht verfügbar!"
}
}
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index ab96ba5..469eac2 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -144,6 +144,8 @@
"projectInstructions": {
"classifyTitle": "Provide your answer",
"classifyInstruction": "Use buttons to provide your answers",
+ "dontWorry": "Don't worry if you are not sure",
+ "everyTaskIsViewedBy": "Every tasks is viewed by at least {verificationNumber} MapSwipers, so it's ok if you think you missed something",
"howToContribute": "How to Contribute?",
"imageCredits": "Image credits",
"move": "Move back and forward",
@@ -155,7 +157,8 @@
"toggleOpacityInstruction": "For a clearer view of the underlying imagery, toggle the opacity of the overlay",
"useButtonsToNavigate": "Use buttons to navigate",
"whereIamTitle": "Where am I mapping?",
- "whereIamInstruction": "Open a map of where you are currently contributing"
+ "whereIamInstruction": "Open a map of where you are currently contributing",
+ "tutorial": "Tutorial"
},
"findProjectInstructions": {
"classifyTitle": "Click or tap to classify",
@@ -202,5 +205,16 @@
"drawNewFeature": "Draw new features",
"modifyFeature": "Modify features",
"deleteFeature": "Delete features"
+ },
+ "projectTutorial": {
+ "showAnswer": "Show answer",
+ "nextTask": "Next task",
+ "completionTitle": "You're ready to start MapSwiping!",
+ "completionDescription": "You're now equipped with all you need to contribute to humanitarian mapping using the app. You can come back anytime for refresher.",
+ "startMapping": "Start mapping"
+ },
+ "imageTile": {
+ "failureMessage": "Failed to load image!",
+ "notAvailableMessage": "Image not available!"
}
}
diff --git a/src/utils/common.ts b/src/utils/common.ts
new file mode 100644
index 0000000..1bdc070
--- /dev/null
+++ b/src/utils/common.ts
@@ -0,0 +1,3 @@
+export function isDefined(item: T | null | undefined): item is T {
+ return item !== null && item !== undefined
+}
diff --git a/src/utils/matchIcon.ts b/src/utils/matchIcon.ts
index f148992..12301f2 100644
--- a/src/utils/matchIcon.ts
+++ b/src/utils/matchIcon.ts
@@ -1,4 +1,4 @@
-const iconLookup = {
+const iconLookup: Record = {
'add-outline': 'mdi-plus',
'alert-outline': 'mdi-exclamation',
'ban-outline': 'mdi-cancel',
@@ -27,7 +27,7 @@ const iconLookup = {
'warning-outline': 'mdi-alert-outline',
}
-const matchIcon = (icon) => {
+const matchIcon = (icon: string) => {
const mdiIcon = iconLookup[icon]
return mdiIcon
}
diff --git a/src/utils/tasks.ts b/src/utils/tasks.ts
new file mode 100644
index 0000000..f6d59b4
--- /dev/null
+++ b/src/utils/tasks.ts
@@ -0,0 +1,22 @@
+import { inflate } from 'pako'
+import { decode } from 'base-64'
+
+export function decompressTasks(tasks: unknown) {
+ if (Array.isArray(tasks)) {
+ return tasks
+ }
+
+ if (typeof tasks !== 'string') {
+ // NOTE: invalid tasks
+ return []
+ }
+
+ const strTasks = decode(tasks)
+ const charTasks = strTasks.split('').map((x) => {
+ return x.charCodeAt(0)
+ })
+ const binaryTasks = new Uint8Array(charTasks)
+ const expandedTasks = inflate(binaryTasks, { to: 'string' })
+
+ return JSON.parse(expandedTasks) as unknown[]
+}
diff --git a/src/views/ProjectView.vue b/src/views/ProjectView.vue
index 8938156..195891e 100644
--- a/src/views/ProjectView.vue
+++ b/src/views/ProjectView.vue
@@ -10,8 +10,6 @@ import {
getTasksRef,
logAnalyticsEvent,
} from '@/firebase'
-import { inflate } from 'pako'
-import { decode } from 'base-64'
import { mapStores } from 'pinia'
import { i18nRoute } from '@/i18n/translation'
import { useCurrentUserStore } from '@/stores/currentUser'
@@ -24,6 +22,7 @@ import MediaProject from '@/components/MediaProject.vue'
import ValidateProject from '@/components/ValidateProject.vue'
import DigitizeProject from '@/components/DigitizeProject.vue'
import projectTypes from '@/config/projectTypes'
+import { decompressTasks } from '@/utils/tasks'
export default defineComponent({
components: {
@@ -162,9 +161,8 @@ export default defineComponent({
},
bindTasks() {
onValue(getTasksRef(this.projectId, this.group?.groupId), (snapshot) => {
- const data = snapshot.val() || []
- const tasks = typeof data == 'string' ? this.decompressTasks(data) : data
- this.tasks = tasks
+ const data = snapshot.val()
+ this.tasks = decompressTasks(data)
})
},
bindTutorial() {
@@ -188,15 +186,6 @@ export default defineComponent({
this.nextDialog = false
this.mode = 'contribute'
},
- decompressTasks(tasks) {
- const strTasks = decode(tasks)
- const charTasks = strTasks.split('').map(function (x) {
- return x.charCodeAt(0)
- })
- const binaryTasks = new Uint8Array(charTasks)
- const expandedTasks = inflate(binaryTasks, { to: 'string' })
- return JSON.parse(expandedTasks)
- },
i18nRoute,
leaveProject() {
this.hideDialog()
diff --git a/yarn.lock b/yarn.lock
index 199ef8d..f9c53e4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1346,6 +1346,11 @@
"@turf/helpers" "^6.5.0"
"@turf/meta" "^6.5.0"
+"@types/base-64@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@types/base-64/-/base-64-1.0.2.tgz#f7bc80d242306f20c57f076d79d1efe2d31032ca"
+ integrity sha512-uPgKMmM9fmn7I+Zi6YBqctOye4SlJsHKcisjHIMWpb2YKZRc36GpKyNuQ03JcT+oNXg1m7Uv4wU94EVltn8/cw==
+
"@types/chai-subset@^1.3.3":
version "1.3.5"
resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.5.tgz#3fc044451f26985f45625230a7f22284808b0a9a"
@@ -1391,6 +1396,11 @@
dependencies:
undici-types "~5.26.4"
+"@types/pako@^2.0.3":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@types/pako/-/pako-2.0.3.tgz#b6993334f3af27c158f3fe0dfeeba987c578afb1"
+ integrity sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==
+
"@types/raf@^3.4.0":
version "3.4.3"
resolved "https://registry.yarnpkg.com/@types/raf/-/raf-3.4.3.tgz#85f1d1d17569b28b8db45e16e996407a56b0ab04"
@@ -4378,16 +4388,7 @@ std-env@^3.3.3:
resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2"
integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==
-"string-width-cjs@npm:string-width@^4.2.0":
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -4443,14 +4444,7 @@ string.prototype.trimstart@^1.0.8:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -5048,16 +5042,7 @@ word-wrap@^1.2.5:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
-wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==