Test MVP #86

Merged
matthieu42morin merged 8 commits from master into deploy/prod 2023-04-27 17:08:59 +00:00
16 changed files with 425 additions and 117 deletions

View File

@ -1,13 +1,15 @@
<script lang="ts">
import './main.scss'
import { i18n, isLoading as localeLoading } from '$lib/locales'
import { isLoading as authLoading, user } from '$lib/appwrite'
import client, { isLoading as authLoading, databases, user } from '$lib/appwrite'
import { onMount } from 'svelte'
import Routes from './__routes.svelte'
/** import CookiesPopUp from '$lib/components/Cookies/CookiesPopUp.svelte' */
import { navigate } from '$lib/router'
import LocationRequest from '$lib/components/Map/LocationRequest.svelte'
import { Models } from 'appwrite'
import { UsersAnswersDocument } from '$lib/TStypes/experiences'
let isMounted = false
$: isReady = $localeLoading === false && $authLoading === false && isMounted

View File

@ -1,9 +1,6 @@
export type CheckPoint = {
$id: string
$collectionId: string
$createdAt: string
$databaseId: string
$permissions: string
import { Models } from 'appwrite'
export interface CheckPoint extends Models.Document {
$updatedAt: string
CPAfter: string
CPAnswerID: string
@ -17,13 +14,7 @@ export type CheckPoint = {
CPType: 'CHECKBOX' | 'TEXT' | 'INFO' | 'RADIO' | 'NUMBER'
}
export type Experience = {
$id: string
$collectionId: string
$createdAt: string
$databaseId: string
$permissions: string
$updatedAt: string
export interface Experience extends Models.Document {
ExpApproved: boolean
ExpCPsID: string[]
ExpCategory?: string
@ -41,3 +32,33 @@ export type Experience = {
checkPoints: Array<CheckPoint>
rating: number
}
export interface ExperienceDocument extends Models.Document {
ExpApproved: boolean
ExpCPsID: string[]
ExpCategory?: string
ExpEnd0: string
ExpEnd60: string
ExpEnd100: string
ExpImage?: string
ExpIntroduction: string
ExpLocation: number[]
ExpName: string
ExpStart: string
ExpTestingCode: string
ExpURL: string
UserID: string
checkPoints: Array<string>
}
export interface CPAnswerDocument extends Models.Document {
CPAnswer: string[]
}
export interface UsersAnswersDocument extends Models.Document {
userId: string
checkPoint: string
experience: string
correct: boolean
attemptCount: number
}

10
src/lib/TStypes/users.ts Normal file
View File

@ -0,0 +1,10 @@
export interface Account {
userId: string;
userName: string;
erantId: string;
userImage: string;
userInterests: string;
userRecommended: string;
userTravelBuddy: string;
termsAccepted: boolean;
}

View File

@ -6,13 +6,12 @@
export let href: string = ''
export let disabled: boolean = false
export let primary: boolean = false
export let style: string = ''
let className = ''
export { className as class }
</script>
<button {disabled} class:disabled {style} on:click={() => navigate(href)} on:click={() => dispatch('submit', true)} on:click class={className} class:primary>
<button {disabled} class:disabled on:click={() => navigate(href)} on:click={() => dispatch('submit', true)} on:click class={className} class:primary>
<slot />
</button>

View File

@ -0,0 +1,21 @@
<script lang="ts">
import { Alert } from 'flowbite-svelte'
export let color: 'red' | 'yellow' | 'green' | 'purple' | 'pink' | 'blue' | 'light' | 'dark' = 'green'
export let dismissable = false
let className = ''
export { className as class }
</script>
<Alert {...$$restProps} {dismissable} class="mt-4 ml-2 mr-2 absolute top-0 z-50 {className}" {color}>
<span slot="icon"><slot name="icon" {color} /></span>
<span class="text-lg font-medium"><slot name="title" {color} /></span>
<div slot="extra">
<div class="mt-2 mb-4 text-sm">
<slot {color} />
</div>
<div class="flex gap-2">
<slot name="buttons" {color} />
</div>
</div>
</Alert>

View File

@ -1,13 +1,26 @@
<script lang="ts">
import elementIdGenerator from '$lib/utils/elementIdGenerator'
import { Input } from 'flowbite-svelte'
import { createEventDispatcher, onMount } from 'svelte'
const dispatch = createEventDispatcher()
export let value = ''
export let placeholder = ''
export let id = elementIdGenerator()
export let readOnly = false
export let maxLength = Infinity
export let icon: string | null | boolean | undefined = null
export let iconFunction: 'password' = null
export let changedIconOnActive = icon
export let iconPosition: 'right' | 'left' = 'left'
//erant-new
export let autocomplete = ''
export let pattern: RegExp = null
export let value: string = ''
export let focus = false
export let prefix: string = ''
export let placeholder: string = ''
export let autocomplete: string = ''
const id = elementIdGenerator()
export let invisiblePrefix = icon ? true : false
export let type:
| 'color'
| 'date'
@ -29,28 +42,162 @@
| 'search'
| 'textarea' = 'text'
let className = 'bg-white text-black border-4 border-blue-300 rounded-[25px] text-left text-[30px] p-[16px] outline-none appearance-none w-full'
let className: string = ''
export { className as class }
const setType = (node: HTMLInputElement) => {
node.type = type
$: inputValue = /*pattern ? value.replace(pattern, '') :*/ value //this is here because we always want to equal input.value = value
$: prefixControl(inputValue) //when inputValue changes => it will check prefix (so when we do $: inputValue = value this func will trigger every moment when inputValue will change and it will overwhite currect input.value thats will be {value})
$: patternControl(inputValue) //when inputValue changes => it will check prefix (so when we do $: inputValue = value this func will trigger every moment when inputValue will change and it will overwhite currect input.value thats will be {value})
const prefixControl = (_e) => {
if (prefix) {
if (!invisiblePrefix) {
if (inputValue?.indexOf(prefix) !== 0) inputValue = `${prefix}${inputValue}`
value = inputValue
} else if (inputValue?.indexOf(prefix) !== 0) {
value = `${prefix}${inputValue}`
//inputValue = value.slice(prefix.length, value.length)
} else value = inputValue
} else {
value = inputValue
}
}
const setFocus = (node: HTMLInputElement | HTMLTextAreaElement) => {
focus && node.focus()
const patternControl = (_e) => {
inputValue = inputValue.replace(pattern, '')
}
$: if (prefix && !value.startsWith(prefix)) value = `${prefix}${value}`
$: console.log(pattern?.test(inputValue))
let inputElement: HTMLElement = null
onMount(() => {
prefixControl('')
patternControl('')
inputElement = document.getElementById(id)
})
let iconActived = false
let iconPushedCount = 0
const iconClick = () => {
iconPushedCount++
iconActived = iconPushedCount % 2 === 1
dispatch('iconClick')
if (iconFunction === 'password') {
type = type === 'text' ? 'password' : 'text'
retype(inputElement)
}
}
const retype = (e: HTMLElement) => {
e.setAttribute('type', type)
}
</script>
{#if type === 'textarea'}
<textarea {id} {placeholder} use:setFocus bind:value class={className} {...$$restProps} />
{:else}
<input {autocomplete} {id} {placeholder} class:prefixPlaceholder={prefix.length === value.length} class={'' + className} use:setType use:setFocus bind:value {...$$restProps} />
{/if}
<div class="relative w-full">
{#if icon}
<button
class={`absolute inset-y-0 ${iconPosition === 'left' ? 'left-0 pl-3' : 'right-0 pr-3'} flex items-center pointer-events-none ${
!iconFunction ? 'pointer-events-none' : 'pointer-events-auto'
} z-50 `}
on:click={iconClick}
>
{#if typeof icon === 'string'}
{#if iconActived}
{changedIconOnActive}
{:else}
{icon}
{/if}
{:else}
<slot active={iconActived} />
{/if}
</button>
{/if}
<input
use:retype
{id}
class="input rounded-xl p-4 text-gray-900 border border-gray-300 text-left outline-none appearance-none {icon
? iconPosition === 'left'
? 'pl-12'
: 'pr-12'
: ''} {className} {readOnly ? 'cursor-pointer' : ''}"
{placeholder}
bind:value={inputValue}
on:input
readonly={readOnly ? true : null}
maxlength={maxLength}
{...$$restProps}
{autocomplete}
/>
</div>
<!--
{#if icon}
{#if iconPosition === 'left'}
<Input
{autocomplete}
{...$$restProps}
maxlength={maxLength}
readonly={readOnly ? true : null}
{id}
{type}
bind:value={inputValue}
class="input rounded-xl p-4 text-gray-900 border border-gray-300 text-left outline-none appearance-none {className} {readOnly ? 'cursor-pointer' : ''}"
color={!podm ? 'red' : null}
{placeholder}
on:input
>
<button class={`w-auto ${!iconFunction ? 'pointer-events-none' : 'pointer-events-auto'} z-50 `} on:click={iconClick} slot="left">
{#if iconActived}
{changedIconOnActive}
{:else}
{icon}
{/if}
</button>
</Input>
{:else}
<Input
{autocomplete}
{...$$restProps}
maxlength={maxLength}
readonly={readOnly ? true : null}
{id}
{type}
bind:value={inputValue}
class="input rounded-xl p-4 text-gray-900 border border-gray-300 text-left outline-none appearance-none {className} {readOnly ? 'cursor-pointer' : ''}"
color={!podm ? 'red' : null}
{placeholder}
on:input
>
<button class={`w-auto ${!iconFunction ? 'pointer-events-none' : 'pointer-events-auto'} z-50 `} on:click={iconClick} slot="right">
{#if iconActived}
{changedIconOnActive}
{:else}
{icon}
{/if}
</button>
</Input>
{/if}
{:else}
<Input
{autocomplete}
{...$$restProps}
maxlength={maxLength}
readonly={readOnly ? true : null}
{id}
{type}
bind:value={inputValue}
class="input rounded-xl p-4 text-gray-900 border border-gray-300 text-left outline-none appearance-none {className} {readOnly ? 'cursor-pointer' : ''}"
color={!podm ? 'red' : null}
{placeholder}
on:input
/>
{/if}
-->
<style>
input::placeholder {
.input::placeholder {
color: #8f8f8f;
font-size: 18px;
font-family: 'Source Sans Pro';

View File

@ -3,7 +3,7 @@
import Map from './Map.svelte'
import { navigate } from 'svelte-routing'
import NavigationBarLayout from '../Layouts/NavigationBarLayout.svelte'
import { createEventDispatcher, onMount } from 'svelte'
import { createEventDispatcher } from 'svelte'
import LocationRequest from './LocationRequest.svelte'
const dispatch = createEventDispatcher()
@ -12,8 +12,6 @@
export let mapComponent = null
export let userLocation = { lat: 0, lng: 0 }
$: console.log(userLocation, center)
let className = ''
export { className as class }
/*;(() => {
@ -59,9 +57,12 @@
options={{
trackUserLocation: true,
showUserHeading: true,
maximumAge: 1500,
positionOptions: {
enableHighAccuracy: true,
},
showUserLocation: true,
showAccuracyCircle: false,
}}
bind:trigger
on:trackuserlocationstart={() => {}}

View File

@ -3,7 +3,7 @@ import { Account, Models, Permission, Query, Role } from 'appwrite'
import database from 'svelte-appwrite-client/src/lib/database'
import { getLocationDataFromLatAndLong } from '../locations'
import { Writable, writable } from 'svelte/store'
import { CheckPoint, Experience } from '$lib/TStypes/experiences'
import { CPAnswerDocument, CheckPoint, Experience, UsersAnswersDocument } from '$lib/TStypes/experiences'
import collections from '$lib/collections'
export type AnswerState = 'wrong-firstTime' | 'wrong-secondTime' | 'correct' | 'not-control' | null
@ -13,31 +13,26 @@ userStore.subscribe((res) => (user = res))
//Loading of checkpoints and rating is done in the same function to prevent multiple requests to the database
export const load = async (pathName: string, previewQuestionsCount?: number, preview?: Function) => {
let checkPoints: Array<CheckPoint> = []
// @ts-ignore
const experience: Experience = (await databases.listDocuments('63cef30d6da945dd4250', '63cef4bd210fdf2e5888', [Query.equal('ExpURL', pathName)])).documents[0]
const experience = (await databases.listDocuments<Models.Document & Experience>('63cef30d6da945dd4250', '63cef4bd210fdf2e5888', [Query.equal('ExpURL', pathName)])).documents[0]
const rating = await getRating(experience.$id)
for (let i = 0; i < Math.ceil(experience.ExpCPsID.length / 25); i++) {
const { documents, total } = await databases.listDocuments<Models.Document & CheckPoint>('63cef30d6da945dd4250', '63cef84d908acf805758', [
Query.equal('$id', experience.ExpCPsID),
Query.limit(25),
Query.offset(i * 25),
])
checkPoints = [...checkPoints, ...documents]
}
/*if (checkPointsIds.indexOf(checkPointId) === previewQuestionsCount - 1) {
experience['rating'] = rating
experience['checkPoints'] = checkPoints
preview(experience)
}*/
console.log(checkPoints)
const checkPointsDocument = (
await Promise.all(
new Array(Math.ceil(experience.ExpCPsID.length / 25)).fill(null).map((_lap, i) => {
return databases.listDocuments<Models.Document & CheckPoint>('63cef30d6da945dd4250', '63cef84d908acf805758', [
Query.equal('$id', experience.ExpCPsID),
Query.limit(25),
Query.offset(i * 25),
])
}),
)
)
.map((listItem) => listItem.documents)
.flat()
experience['rating'] = rating
experience['checkPoints'] = checkPoints
experience['checkPoints'] = checkPointsDocument
return experience
}
@ -62,13 +57,15 @@ export const answer = async (experienceId: string, checkPointId: string, answer:
try {
const checkPoint = await databases.getDocument('63cef30d6da945dd4250', '63cef84d908acf805758', checkPointId)
const { CPType, CPAnswerID } = checkPoint
const correctAnswer = CPType !== 'INFO' ? (await databases.getDocument('63cef30d6da945dd4250', '63dd5c2b764061e40025', CPAnswerID)).CPAnswer : true
const correctAnswer =
CPType !== 'INFO' ? (await databases.getDocument<Models.Document & CPAnswerDocument>('63cef30d6da945dd4250', '63dd5c2b764061e40025', CPAnswerID)).CPAnswer : null
let correct: boolean = false
if (CPType === 'CHECKBOX') correct = JSON.stringify(answer) === JSON.stringify(correctAnswer)
if (CPType === 'RADIO' || CPType === 'NUMBER' || CPType === 'TEXT') correct = answer === correctAnswer[0]
if (CPType === 'RADIO' || CPType === 'NUMBER') correct = answer === correctAnswer[0]
if (CPType === 'INFO') correct = false
if (CPType === 'TEXT') correct = correctAnswer[0].localeCompare(answer, 'en', { sensitivity: 'base' }) === 0
await saveAnswerIntoDatabase(experienceId, checkPointId, correct)
return correct
@ -97,7 +94,7 @@ const saveAnswerIntoDatabase = async (experienceId: string, checkPointId: string
experience: experienceId,
attemptCount: 1,
},
[Permission.read(Role.user(user.$id)), Permission.update(Role.user(user.$id))],
[Permission.read(Role.user(user.$id)), Permission.update(Role.user(user.$id)), Permission.delete(Role.user(user.$id))],
)
}
} else {
@ -144,11 +141,32 @@ export const getExperiencesAsStore = () => {
}
export const getUserProgress = async (experienceId: string) => {
const { documents } = await databases.listDocuments('63cef30d6da945dd4250', 'users-answers', [Query.equal('userId', user.$id), Query.equal('experience', experienceId)])
return documents
const { documents, total } = await databases.listDocuments<Models.Document & UsersAnswersDocument>('63cef30d6da945dd4250', 'users-answers', [
Query.equal('userId', user.$id),
Query.equal('experience', experienceId),
Query.limit(25),
Query.offset(0),
])
const allOtherDocuments = (
await Promise.all(
new Array(Math.ceil((total - 25) / 25) > 0 ? Math.ceil((total - 25) / 25) : 0)
.fill(null)
.map((_n, i) =>
databases.listDocuments<Models.Document & UsersAnswersDocument>('63cef30d6da945dd4250', 'users-answers', [
Query.equal('userId', user.$id),
Query.equal('experience', experienceId),
Query.limit(25),
Query.offset((i + 1) * 25),
]),
),
)
).flatMap((list) => list.documents)
return [...documents, ...allOtherDocuments]
}
export const getUserProgressAsStore = (experienceId: string) => {
const store = writable<Models.Document[]>([])
const store = writable<(Models.Document & UsersAnswersDocument)[]>([])
const loading = writable<boolean>(true)
getUserProgress(experienceId).then((documents) => {
store.set(documents)

View File

@ -5,7 +5,10 @@
const urlFail = `${location.origin}/register/failed`
const urlSuccess = `${location.origin}/create/account`
export let disableLogin = false
const login = async (platform: 'facebook' | 'google') => {
if (disableLogin) return
try {
await user.deleteSessions()
} catch (error) {}
@ -14,12 +17,12 @@
</script>
<div class="continue_with">
<button>
<button on:click>
<GoogleLogo />
<p>Continue with Google</p>
</button>
<button on:click={() => login('facebook')}>
<button on:click on:click={() => login('facebook')}>
<p>Continue with Facebook</p>
</button>
</div>

View File

@ -9,6 +9,8 @@
import { getErrorMessage } from '../utils/authorizationErrors'
import Button from '$lib/components/Buttons/Button.svelte'
import SocialLogin from '../Components/SocialLogin.svelte'
import Input from '$lib/components/Inputs/Input.svelte'
import Eye from '$lib/svg/Eye.svelte'
export let purpose = 'login' //possible values login, register
@ -58,8 +60,18 @@
<Helper class="ml-4" color="red">{error}</Helper>
{/if}
<div class="inform">
<input bind:value={email} type="text" placeholder="E-mail" autocomplete="email" required />
<HiddenInput bind:value={password} placeholder="Password" />
<Input bind:value={email} type="text" placeholder="E-mail" autocomplete="email" class="p-3 border-1 rounded-[15px] bg-[#eeeeee] text-lg w-full" />
<Input
bind:value={password}
let:active
iconPosition="right"
icon
type="password"
placeholder="Password"
class="p-3 border-1 rounded-[15px] bg-[#eeeeee] text-lg w-full"
>
<Eye {active} />
</Input>
<div class="forgot_password">
<a href="/forgot-password">Forgot password?</a>
</div>

View File

@ -18,14 +18,13 @@
let repeatPassword = ''
let name = ''
let erantId = ''
let termsChecked: boolean = false
let termsChecked = false
let termsCheckboxShake = false
$: if (termsCheckboxShake) setTimeout(() => (termsCheckboxShake = false), 750)
let state: 'email-sent' | 'register' | 'loading' = 'register'
let error: string | null = null
$: buttonCodition = name.length > 0 && email.length > 0 && password.length >= 8 && password === repeatPassword && erantId.length > 2 && termsChecked && nicknameIsValid
const pattern = /^[a-zA-Z0-9+@]+$/
$: nicknameIsValid = pattern.test(erantId)
$: buttonCodition = name.length > 0 && email.length > 0 && password.length >= 8 && password === repeatPassword && erantId.length > 2 && termsChecked
const register = async () => {
//if (password === repeatPassword || name.length < 8 || email.length < 8) throw new Error('conditions are not fine')
@ -52,6 +51,8 @@
state = 'register'
}
}
$: console.log(erantId)
</script>
{#if state !== 'email-sent'}
@ -70,14 +71,35 @@
<Input bind:value={name} placeholder="Your name" autocomplete="full-name" class="p-3 border-1 rounded-[15px] bg-[#eeeeee] text-lg w-full" />
<Input bind:value={email} placeholder="Your e-mail" autocomplete="email" class="p-3 border-1 rounded-[15px] bg-[#eeeeee] text-lg w-full" />
<div class="w-full">
<Input bind:value={erantId} placeholder="@your_nickname" class="p-3 border-1 rounded-[15px] bg-[#eeeeee] text-lg w-full" prefix="@" />
{#if !nicknameIsValid}
<Helper class="flex justify-start w-full pl-4" color="red">Your nickname can include only a-zA-Z0-9 characters</Helper>
{/if}
<Input
bind:value={erantId}
prefix="@"
placeholder="your_nickname"
class="p-3 border-1 rounded-[15px] bg-[#eeeeee] text-lg w-full"
pattern={/[^@a-zA-Z0-9]/g}
invisiblePrefix
icon="@"
/>
</div>
<HiddenInput bind:value={password} placeholder="Password" />
<Input
iconFunction="password"
bind:value={password}
placeholder="Password"
class="p-3 border-1 rounded-[15px] bg-[#eeeeee] text-lg w-full"
inputFunction="password"
iconPosition="right"
icon="eye"
/>
<div class="w-full">
<HiddenInput bind:value={repeatPassword} placeholder="Re-type password" />
<Input
iconFunction="password"
bind:value={repeatPassword}
placeholder="Re-type password"
class="p-3 border-1 rounded-[15px] bg-[#eeeeee] text-lg w-full"
inputFunction="password"
iconPosition="right"
icon="eye"
/>
{#if password !== repeatPassword && password.length >= 8}
<Helper class="flex justify-start w-full pl-4" helperClass="text-sm" color="red">Passwords are not equal</Helper>
{/if}
@ -86,7 +108,9 @@
<div class="flex items-center">
<Checkbox
bind:checked={termsChecked}
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
class="w-5 h-5 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 {termsCheckboxShake
? 'animate-bounce border-4 border-red-500'
: ''}"
/>
<label for="link-checkbox" class="ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>I agree with the <a href="erant.cz/terms-and-conditions" class="text-blue-600 dark:text-blue-500 hover:underline">terms and conditions</a>,
@ -104,7 +128,12 @@
</div>
</div>
<SocialLogin />
<SocialLogin
disableLogin={!termsChecked}
on:click={() => {
if (!termsChecked) termsCheckboxShake = true
}}
/>
<div class="LR_switch">
<p>Already have an account? <a href="/login">Log In</a></p>

View File

@ -30,7 +30,7 @@
<div class="flex items-center justify-center flex-col w-full gap-6">
<span class="relative"> <slot name="answers" /> </span>
<div class="w-full relative flex justify-center h-fit size">
<div class="w-full relative flex justify-center h-fit size">
{#if control === 'not-control' || control === null}
<span />
{:else if control === 'wrong-secondTime' || control === 'correct'}
@ -46,7 +46,13 @@
{/if}
</div>
{#if control === 'not-control' || control === 'correct' || control === 'wrong-secondTime'}
<Button on:submit={() => dispatch('nextQuestion')} primary class="w-3/4 max-w-sm min-w-[400px] h-16 bottom-0 m-10 relative">Na další otázku</Button>
<Button on:submit={() => dispatch('nextQuestion')} primary class="w-3/4 max-w-sm min-w-[400px] h-16 bottom-0 m-10 relative">
{#if loading}
<Loading />
{:else}
Na další otázku
{/if}
</Button>
{:else if control === 'wrong-firstTime' || control === null}
<Button disabled={loading} on:submit primary class="w-3/4 max-w-sm min-w-[400px] h-16 bottom-0 m-10 relative">
{#if loading}

View File

@ -6,12 +6,11 @@
export let gameData: Experience
export let client
let score = (client.points / client.possiblePointsToSeize) * 100
console.log(score)
</script>
<LayoutImg img={gameData.ExpImage}>
<div class="w-full h-auto flex flex-wrap flex-row gap-4 justify-center">
<div class="h-full w-full flex justify-self-center justify-center text-[32px] flex-wrap flex-col gap-4 items-center">
<div class="h-full w-full flex justify-self-center justify-center text-[32px] flex-wrap flex-col gap-4 items-center">
<span> Získali jste {client.points} / {client.possiblePointsToSeize} bodů</span>
<span>
{#if score > 90}
@ -23,6 +22,6 @@
{/if}
</span>
</div>
<Button class="w-80 mt-8 " on:click={() => navigate(-1)}>ukončit hru</Button>
<Button class="w-80 mt-8 " on:click={() => navigate('/')}>ukončit hru</Button>
</div>
</LayoutImg>

View File

@ -7,9 +7,6 @@
export let checkPoint: CheckPoint
export let myAnswer
export let clear: false | true = false
$: if (clear) myAnswers = new Array(checkPoint.CPOptions.length).fill(false)
let myAnswers = new Array(checkPoint.CPOptions.length).fill(false)
$: myAnswer = checkPoint.CPOptions.filter((item, i) => {
if (myAnswers[i] === true) return item

View File

@ -13,11 +13,12 @@
import { Experience } from '$lib/TStypes/experiences'
import Layout from '../Components/Layout.svelte'
import { answer, AnswerState, getUserAnswer, getUserProgressAsStore } from '$lib/utils/database/experience'
import { user } from '$lib/appwrite'
import client, { user } from '$lib/appwrite'
import Button from '$lib/components/Buttons/Button.svelte'
import LayoutImg from '$lib/components/Layouts/LayoutImg.svelte'
import { Models } from 'appwrite'
import { Writable } from 'svelte/store'
import { Button as FlowbiteButton } from 'flowbite-svelte'
const components = {
TEXT: TextForm,
@ -29,29 +30,32 @@
INFO: Info,
}
$: console.log({ checkPoints: gameData.checkPoints })
export let control: AnswerState = null
let view: 'question' | 'map' | 'end' | 'start' | 'start-map' = 'start-map'
export let gameData: Experience //data
$: [userProgress, userProgressLoading] = gameData ? getUserProgressAsStore(gameData?.$id) : []
export let userProgress
//$: [userProgress, userProgressLoading] = getUserProgressAsStore(gameData.$id)
let client = {
//user data about game
pos: 0,
end: gameData.ExpCPsID.length - 1, //kolik otázek
points: 0, //body
possiblePointsToSeize: gameData.checkPoints.length * 2,
points: 0,
possiblePointsToSeize: new Array(gameData.checkPoints.length).fill(2).reduce((accumulator, currentValue, index) => {
if (gameData.checkPoints[index].CPType !== 'INFO') return accumulator + currentValue
else return accumulator
}),
}
$: if (client.pos < $userProgress?.length - 1 + 1) {
$: if (client.pos < userProgress?.length - 1 + 1) {
// nastaví na continue
client.pos = $userProgress?.length - 1 + 1
client.pos = userProgress?.length - 1 + 1
client.points = userProgress?.map((i) => (i.correct ? 2 : 0))?.reduce((accumulator, currentValue) => accumulator + currentValue)
view = 'map'
}
$: if (gameData.checkPoints[client.pos].CPType === 'INFO' && !$userProgressLoading) control = 'not-control'
$: console.log(control)
$: if (gameData.checkPoints[client.pos].CPType === 'INFO') control = 'not-control'
const nextQuestion = () => {
//další otázka
@ -64,7 +68,7 @@
}
$: checkPoint = gameData.checkPoints[client.pos]
$: checkPointType = checkPoint.CPType
$: checkPointType = checkPoint?.CPType
let page = null
$: page = view === 'question' ? components[checkPointType] : null
@ -97,14 +101,26 @@
} catch (error) {
console.log(error)
}
myAnswer = ''
answerLoading = false
}
const admins = [
'641b2cd262519fdd33ec',
'643bfde664ea0c643112',
'643bfdd8c64e75d0b8ea',
'6427218926d6ab7f8e52',
'641d5642c8fa96066cf2',
'641b42847ac86f9a306c',
'63daafd3355edb14483d',
]
</script>
<input type="number" bind:value={client.pos} />
<button on:click={() => (view = view === 'start-map' ? 'start' : 'question')}>disappear map</button>
{#if view === 'map' || view === 'start-map'}
{#if admins.includes($user.$id)}<!--only if admin-->
<FlowbiteButton class="absolute z-50" color="red" on:click={() => (view = view === 'start-map' ? 'start' : 'question')}>disappear map</FlowbiteButton>
{/if}
<Erantmap bind:userLocation class="w-full h-full">
<Marker on:enter={() => (view = view === 'start-map' ? 'start' : 'question')} {lat} {lng} {userLocation} />
</Erantmap>

View File

@ -6,32 +6,56 @@
import IconStar from '../../lib/svg/Star.svelte'
import IconPoint from '../../lib/svg/Point.svelte'
import Loading from '../../lib/components/Common/Loading.svelte'
import { data } from '../../lib/stores/stores'
import GeolocateControl from '@beyonk/svelte-mapbox/src/lib/map/controls/GeolocateControl.svelte'
import Map from '../../lib/components/Map/Map.svelte'
import Renderer from './Forms/Renderer.svelte'
import Marker from '../../lib/components/Map/Marker.svelte'
import { onMount } from 'svelte'
import { getExperienceIdByUrl, getExperienceIdByUrlAsStore, getUserProgress, getUserProgressAsStore, load, loadAsStore } from '$lib/utils/database/experience'
import { getExperienceIdByUrlAsStore, getUserProgressAsStore, loadAsStore } from '$lib/utils/database/experience'
import { getLocationDataFromLatAndLong } from '$lib/utils/locations'
import { navigate } from '$lib/router'
import { Experience } from '$lib/TStypes/experiences'
import Progressbar from '$lib/components/erant/Progressbar.svelte'
import { writable } from 'svelte/store'
import Alert from '$lib/components/Common/Alert.svelte'
import { Button as FlowbiteButton } from 'flowbite-svelte'
import { databases, user } from '$lib/appwrite'
import { Query } from 'appwrite'
export let params: { gameurl: string }
let gameData = writable<Experience>(null)
let [id] = getExperienceIdByUrlAsStore(params.gameurl)
$: [userProgress] = $gameData ? getUserProgressAsStore($id) : []
$: [userProgress, userProgressLoading] = $gameData ? getUserProgressAsStore($id) : []
$: [gameData] = $gameData ? [] : loadAsStore(params.gameurl)
$: if ($gameData) view = 'experience-preview'
$: if ($gameData && !$userProgressLoading) view = 'experience-preview'
let view: 'experience-play' | 'experience-loading' | 'experience-preview' = 'experience-loading'
const deleteProgress = async () => {
const documentsToDelete = $userProgress.map(({ $id }) => databases.deleteDocument('63cef30d6da945dd4250', 'users-answers', $id))
const res = await Promise.all(documentsToDelete)
deleteProgressAlertVisible = false
$userProgress = []
}
let deleteProgressAlertVisible = false
</script>
{#if deleteProgressAlertVisible}
<Alert color="red" let:color>
<span slot="title">Delete your progress</span>
<div class="text-black">
<span>Are you sure that you want to delete your progress in experience {$gameData?.ExpName}</span>
<span>Experience Id: {$gameData?.$id}</span>
<span>Your current score is: {$userProgress?.length} checkPoints</span>
</div>
<div slot="buttons">
<FlowbiteButton on:click={deleteProgress} {color}>Yes I'm sure</FlowbiteButton>
<FlowbiteButton on:click={() => (deleteProgressAlertVisible = false)} outline {color}>No, I missed clicked</FlowbiteButton>
</div>
</Alert>
{/if}
{#if view === 'experience-loading'}
<h1 class="flex items-center justify-center flex-col">
<h1 class="flex items-center justify-center flex-col h-full">
<span>Experience is loading...</span>
<Loading />
</h1>
@ -54,20 +78,20 @@
</div>
{#if $gameData.ExpIntroduction}
<Section title="Popis">
<Section title="Description">
<span>
{@html $gameData.ExpIntroduction}
</span>
</Section>
{/if}
<Section title="Fotky">
<!--<Section title="Fotky">
<div class="w-full relative">
<div class="px-4 m-auto" style="max-width: var(--max-viewport-width);">
<!--<ImageSlider images={assets} />-->
<ImageSlider images={assets} />
</div>
</div>
</Section>
</Section>-->
<Section title="Progress">
<Progressbar max={$gameData.ExpCPsID.length} progress={$userProgress.length} showWrittenProgress />
</Section>
@ -82,12 +106,15 @@
</div>
</div>
{#if $gameData.checkPoints.length}
{#if $gameData.checkPoints.length && !($gameData.ExpCPsID.length === $userProgress.length)}
<Button on:click={() => (view = 'experience-play')} primary>{$userProgress.length ? 'Pokračovat' : 'Hrát'}</Button>
{/if}
{#if $userProgress.length}
<Button class="!bg-red-500" on:click={() => (deleteProgressAlertVisible = true)} primary>resetovat progress</Button>
{/if}
</Overlay>
{:else if view === 'experience-play'}
<Renderer gameData={$gameData} />
<Renderer userProgress={$userProgress} gameData={$gameData} />
{/if}
<style>