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 @@ + + 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 @@ + + + + + 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 @@ + + + + + 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.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 @@ + + + + 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 @@ 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 @@ + + + + + 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==