Skip to content

Commit 3aedc67

Browse files
committed
Add tutorial for validate image project type
1 parent 2cab128 commit 3aedc67

File tree

5 files changed

+308
-22
lines changed

5 files changed

+308
-22
lines changed

src/components/ValidateImageProject.vue

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
<script lang="ts" setup>
22
33
import { isDefined } from '@togglecorp/fujs';
4-
import { computed, inject, onMounted, ref, shallowRef, watchEffect } from 'vue';
5-
import type { Project, TaskGroup, TutorialTileTask, Tutorial, CustomOption, ImageTask } from '@/utils/types';
4+
import { computed, inject, onMounted, ref, shallowRef, useTemplateRef, watchEffect } from 'vue';
5+
import type { Project, TaskGroup, Tutorial, CustomOption, ImageTask, TutorialImageTask } from '@/utils/types';
66
import ValidateImageProjectTask from '@/components/ValidateImageProjectTask.vue';
77
import OptionButtons from '@/components/OptionButtons.vue';
8-
import TaskProgress from './TaskProgress.vue';
98
import ProjectHeader from '@/components/ProjectHeader.vue'
10-
import ProjectInfo from './ProjectInfo.vue';
9+
import TaskProgress from '@/components/TaskProgress.vue';
10+
import ProjectInfo from '@/components/ProjectInfo.vue';
11+
import ValidateImageProjectInstructions from '@/components/ValidateImageProjectInstructions.vue';
1112
import createInformationPages from '@/utils/createInformationPages';
1213
import { createFallbackInformationPages } from '@/utils/domain';
14+
import { useI18n } from 'vue-i18n';
15+
import ValidateImageProjectTutorial from './ValidateImageProjectTutorial.vue';
16+
17+
const { t } = useI18n();
1318
1419
interface Props {
1520
group: TaskGroup;
@@ -18,33 +23,33 @@ interface Props {
1823
project: Project;
1924
tasks: ImageTask[];
2025
tutorial: Tutorial;
21-
tutorialTasks: TutorialTileTask[];
26+
tutorialTasks: TutorialImageTask[];
2227
}
2328
2429
const taskOffset = ref(0);
25-
// const projectInfoRef = useTemplateRef('projectInfo');
30+
const projectInfoRef = useTemplateRef('projectInfo');
2631
2732
const props = withDefaults(defineProps<Props>(), {
2833
options: () => [
2934
{
3035
mdiIcon: 'mdi-check-bold',
31-
description: `The shape does outline a building in the image`,
36+
description: `The shape does outline the feature in image`,
3237
iconColor: '#388E3C',
3338
shortKey: 1,
3439
title: 'Yes',
3540
value: 1,
3641
},
3742
{
3843
mdiIcon: 'mdi-close-thick',
39-
description: `The shape doesn't match a building in the image`,
44+
description: `The shape doesn't match the feature in image`,
4045
iconColor: '#D32F2F',
4146
shortKey: 2,
4247
title: 'No',
4348
value: 0,
4449
},
4550
{
4651
mdiIcon: 'mdi-minus-thick',
47-
description: `If you're not sure or there is cloud cover / bad imagery.`,
52+
description: `If you're not sure`,
4853
iconColor: '#616161',
4954
title: 'Not sure',
5055
shortKey: 3,
@@ -57,6 +62,9 @@ const logMappingStarted = inject<(projectType: string) => void>('logMappingStart
5762
const saveResults = inject<(results: Record<string, number>, startTime: string) => void>('saveResults');
5863
const arrowKeys = ref(true);
5964
const startTime = shallowRef<string>();
65+
const instruction = computed(
66+
() => t('validateProject.doesTheShapeOutline', { feature: props.project.lookFor })
67+
);
6068
6169
const emit = defineEmits<{ created: []}>();
6270
const results = ref<Record<string, number>>({});
@@ -100,7 +108,7 @@ function handleForward() {
100108

101109
<template>
102110
<ProjectHeader
103-
:mission="$t('projectView.youAreLookingFor', { lookFor: props.project.lookFor })"
111+
:mission="instruction"
104112
>
105113
<Project-info
106114
ref="projectInfo"
@@ -109,23 +117,17 @@ function handleForward() {
109117
:manualUrl="project?.manualUrl"
110118
@toggle-dialog="arrowKeys = !arrowKeys"
111119
>
112-
<!-- TODO: add instruction -->
113-
<!--
114120
<template #instructions>
115-
<validate-project-instructions :mission="mission" :options="options" />
121+
<ValidateImageProjectInstructions :instruction="instruction" :options="props.options" />
116122
</template>
117-
-->
118-
<!-- FIXME: add tutorial -->
119-
<!--
120123
<template #tutorial>
121-
<validate-project-tutorial
124+
<ValidateImageProjectTutorial
122125
:tutorial="tutorial"
123126
:tasks="tutorialTasks"
124127
:options="options"
125128
@tutorialComplete="projectInfoRef?.toggleDialog"
126129
/>
127130
</template>
128-
-->
129131
<!-- FIXME: add tutorial -->
130132
</Project-info>
131133
</ProjectHeader>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<script lang="ts" setup>
2+
import OptionButton from '@/components/OptionButton.vue'
3+
import type { CustomOption } from '@/utils/types';
4+
5+
interface Props {
6+
instruction: string;
7+
options: CustomOption[];
8+
}
9+
10+
const props = defineProps<Props>();
11+
12+
</script>
13+
14+
<template>
15+
<v-card-text>
16+
<div class="text-h6">
17+
{{ $t('projectInstructions.classifyTitle') }}
18+
</div>
19+
<div class="text-p">{{ props.instruction }} {{ $t('projectInstructions.classifyInstruction') }}.</div>
20+
<v-row v-for="(option, optionIndex) in props.options" :key="optionIndex" align="center" dense>
21+
<v-col cols="auto" class="mt-5 mr-4">
22+
<option-button :option="option" :only-icon="true" />
23+
</v-col>
24+
<v-col class="mt-5">{{
25+
[option.title, option.description].filter(Boolean).join(': ')
26+
}}</v-col>
27+
</v-row>
28+
29+
<div class="text-h6 mt-10">{{ $t('projectInstructions.useButtonsToNavigate') }}</div>
30+
<div class="text-p mt-2">
31+
<v-row class="align-center" dense>
32+
<v-col cols="auto" class="mr-4">
33+
<v-btn icon="mdi-chevron-left" color="secondary" class="mr-2" variant="text" />
34+
<v-btn icon="mdi-chevron-right" color="secondary" variant="text" />
35+
</v-col>
36+
<v-col>{{ $t('projectInstructions.move') }}</v-col>
37+
</v-row>
38+
</div>
39+
40+
<div class="text-h6 mt-10">{{ $t('projectInstructions.saveYourAnswers') }}</div>
41+
<div class="text-p mt-2">
42+
<v-row class="align-center" dense>
43+
<v-col cols="auto" class="mr-4">
44+
<v-btn icon="mdi-content-save" color="primary" variant="text" />
45+
</v-col>
46+
<v-col>{{ $t('validateProjectInstructions.seenAll') }}</v-col>
47+
</v-row>
48+
</div>
49+
<div class="text-h6 mt-10">{{ $t('projectInstructions.dontWorry') }}</div>
50+
<div class="text-p">{{ $t('projectInstructions.everyTaskIsViewedBy') }}.</div>
51+
</v-card-text>
52+
</template>
53+
54+
<style scoped></style>
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
<script lang="ts" setup>
2+
import { computed, ref } from 'vue';
3+
import { useI18n } from 'vue-i18n';
4+
import { isDefined } from '@togglecorp/fujs';
5+
import matchIcon from '@/utils/matchIcon'
6+
import type { CustomOption, Tutorial, TutorialImageTask } from '@/utils/types';
7+
import ValidateImageProjectTask from './ValidateImageProjectTask.vue';
8+
import OptionButtons from '@/components/OptionButtons.vue'
9+
import TaskProgress from '@/components/TaskProgress.vue'
10+
import TutorialCompletionCard from '@/components/TutorialCompletionCard.vue'
11+
12+
const { t } = useI18n();
13+
14+
interface Props {
15+
tutorial: Tutorial;
16+
options: CustomOption[];
17+
tasks: TutorialImageTask[];
18+
}
19+
20+
const props = defineProps<Props>();
21+
22+
const currentTaskIndex = ref(0);
23+
const userAttempts = ref(0);
24+
const answersRevealed = ref(false);
25+
const results = ref<Record<string, number>>({});
26+
27+
const instruction = computed(
28+
() => t('validateProject.doesTheShapeOutline', { feature: props.tutorial.lookFor })
29+
);
30+
31+
const hasTasks = computed(() => isDefined(props.tasks) && props.tasks.length !== 0);
32+
33+
const task = computed(() => (
34+
props.tasks?.[currentTaskIndex.value]
35+
))
36+
37+
const hasCompletedAllTasks = computed(() => {
38+
if (!hasTasks.value) {
39+
return false
40+
}
41+
42+
const maxIndex = props.tasks.length
43+
return currentTaskIndex.value === maxIndex
44+
});
45+
46+
const currentScreen = computed(() => (
47+
props.tutorial?.screens[currentTaskIndex.value]
48+
));
49+
50+
const answeredCorrectly = computed(() => {
51+
if (!hasTasks.value) {
52+
return false
53+
}
54+
55+
if (hasCompletedAllTasks.value) {
56+
return true
57+
}
58+
59+
const result = results.value[task.value?.taskId]
60+
return isDefined(result) && result === task.value?.referenceAnswer
61+
});
62+
63+
const alertContent = computed(() => {
64+
if (!currentScreen.value) {
65+
return undefined
66+
}
67+
68+
// FIXME: we may not have success and hint for this project type
69+
const { instructions, success, hint } = currentScreen.value;
70+
71+
if (answeredCorrectly.value && success) {
72+
const icon = success.icon
73+
74+
return {
75+
type: 'success' as const,
76+
title: success.title,
77+
text: success.description,
78+
icon: icon ? matchIcon(icon) : undefined,
79+
}
80+
}
81+
82+
if (userAttempts.value > 0 && hint) {
83+
const icon = hint.icon
84+
85+
return {
86+
type: undefined,
87+
title: hint.title,
88+
text: hint.description,
89+
icon: icon ? matchIcon(icon) : undefined,
90+
}
91+
}
92+
93+
if (!instructions) {
94+
return undefined
95+
}
96+
97+
const icon = instructions.icon
98+
99+
return {
100+
type: 'info' as const,
101+
title: instructions.title,
102+
text: instructions.description,
103+
icon: icon ? matchIcon(icon) : undefined,
104+
}
105+
});
106+
107+
function showAnswer() {
108+
answersRevealed.value = true;
109+
results.value[task.value.taskId] = task.value.referenceAnswer;
110+
}
111+
112+
function nextTask() {
113+
if (!hasCompletedAllTasks.value) {
114+
currentTaskIndex.value += 1
115+
userAttempts.value = 0
116+
answersRevealed.value = false
117+
}
118+
}
119+
120+
function addResult(value: number) {
121+
if (!answersRevealed.value) {
122+
userAttempts.value += 1;
123+
results.value[task.value.taskId] = value;
124+
}
125+
};
126+
127+
</script>
128+
129+
<template>
130+
<v-container>
131+
<v-row v-if="!hasCompletedAllTasks">
132+
<v-col>
133+
<div>
134+
{{ instruction }}
135+
</div>
136+
</v-col>
137+
</v-row>
138+
<v-row v-if="alertContent">
139+
<v-col>
140+
<v-alert
141+
prominent
142+
density="compact"
143+
border="start"
144+
variant="tonal"
145+
:type="alertContent.type"
146+
:title="alertContent.title"
147+
:text="alertContent.text"
148+
:icon="alertContent.icon"
149+
>
150+
<template #append>
151+
<v-btn
152+
v-if="userAttempts > 1 && !answeredCorrectly && !answersRevealed"
153+
@click="showAnswer"
154+
>
155+
{{ $t('projectTutorial.showAnswer') }}
156+
</v-btn>
157+
<v-btn v-if="!hasCompletedAllTasks && answeredCorrectly" @click="nextTask">
158+
{{ $t('projectTutorial.nextTask') }}
159+
</v-btn>
160+
</template>
161+
</v-alert>
162+
</v-col>
163+
</v-row>
164+
<v-row>
165+
<v-col>
166+
<div
167+
v-if="tasks[currentTaskIndex] && tutorial"
168+
class="task-container"
169+
>
170+
<ValidateImageProjectTask
171+
:task="tasks[currentTaskIndex]"
172+
/>
173+
</div>
174+
</v-col>
175+
</v-row>
176+
<v-row v-if="options">
177+
<v-col>
178+
<option-buttons
179+
v-if="task?.taskId"
180+
:options="options"
181+
:result="results[task.taskId]"
182+
:taskId="task.taskId"
183+
@addResult="addResult"
184+
/>
185+
</v-col>
186+
</v-row>
187+
<v-row v-if="!hasTasks" justify="center">
188+
<v-col cols="auto">
189+
<v-progress-circular indeterminate />
190+
</v-col>
191+
</v-row>
192+
<v-row v-if="hasTasks && !hasCompletedAllTasks">
193+
<v-col>
194+
<task-progress :progress="currentTaskIndex" :total="tasks?.length ?? 0" />
195+
</v-col>
196+
</v-row>
197+
<v-row v-if="hasCompletedAllTasks">
198+
<v-col>
199+
<tutorial-completion-card @on-start-mapping-click="$emit('tutorialComplete')" />
200+
</v-col>
201+
</v-row>
202+
</v-container>
203+
</template>
204+
205+
<style scoped>
206+
.task-container {
207+
height: calc(100vh - 600px);
208+
}
209+
</style>

src/components/ValidateProjectTutorial.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ interface Tutorial extends Project {
3838
name: string
3939
lookFor?: string
4040
screens: {
41-
hint: Screen
4241
instructions: Screen
42+
43+
// FIXME: we may not have success and hint for this project type
44+
hint: Screen
4345
success: Screen
4446
}[]
4547
}
@@ -110,6 +112,7 @@ export default defineComponent({
110112
return undefined
111113
}
112114
115+
// FIXME: we may not have success and hint for this project type
113116
const { instructions, success, hint } = this.currentScreen
114117
115118
if (this.answeredCorrectly && success) {

0 commit comments

Comments
 (0)