Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
idatt2106_2024_02_frontend
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
IDATT2106 - Gruppe 2
idatt2106_2024_02_frontend
Commits
c05decec
Commit
c05decec
authored
1 year ago
by
Valdemar Åstorp Beere
Browse files
Options
Downloads
Patches
Plain Diff
feat(homepage):
Implemented components in HomeView.vue
parent
dfc5c18d
No related branches found
Branches containing commit
No related tags found
3 merge requests
!66
Final merge
,
!12
feat(homepage):
,
!4
Pipeline fix
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
src/views/HomeView.vue
+312
-14
312 additions, 14 deletions
src/views/HomeView.vue
with
312 additions
and
14 deletions
src/views/HomeView.vue
+
312
−
14
View file @
c05decec
<
template
>
<div
class=
"flex flex-row max-h-[80vh]"
>
<div
class=
"flex flex-col basis-1/2"
>
<InteractiveSpare
:speech=
"speech"
:direction=
"'right'"
:pngSize=
"60"
class=
"w-80 h-80"
></InteractiveSpare>
<div
class=
"m-8 p-8"
>
<ButtonAddGoalOrChallenge
:buttonText=
"'Legg til sparemål'"
class=
"mb-4"
/>
<ButtonAddGoalOrChallenge
:buttonText=
"'Legg til spareutfordring'"
/>
</div>
</div>
<div
class=
"flex flex-col basis-1/2"
>
<div
class=
"flex justify-center align-center"
>
<span
class=
"
w-full
max-w-60
max-h-12
bg-green-500
text-white font-bold py-2 rounded
mt-8
text-center
space-x-2
"
>
Din Sparesti
</span>
</div>
<div
ref=
"containerRef"
class=
"container mx-auto p-6 no-scrollbar max-h-[60vh] overflow-y-auto"
>
<div
v-for=
"(challenge, index) in challenges"
:key=
"challenge.title"
class=
"flex flex-col items-center mx-8"
>
<!-- Challenge Row -->
<div
:class=
"
{ 'justify-end ml-30': index % 2 === 1, 'justify-start': index % 2 === 0 }" class="flex flex-row w-2/3 ml-8">
<!-- Challenge Icon and Details -->
<div
class=
"flex"
>
<!-- Challenge Icon -->
<div
class=
"flex flex-col"
>
<img
:src=
"getChallengeIcon(challenge)"
class=
"max-w-20 max-h-20"
:alt=
"challenge.title"
>
<!-- Progress Bar, if the challenge is not complete -->
<div
v-if=
"challenge.completion
<
100"
class=
"flex-grow w-full mt-2"
>
<div
class=
"flex flex-row"
>
<div
class=
"flex flex-col"
>
<div
class=
"bg-gray-200 rounded-full h-2.5 dark:bg-gray-700"
>
<div
class=
"bg-green-600 h-2.5 rounded-full"
:style=
"
{ width: (challenge.saved / challenge.target * 100) + '%' }">
</div>
</div>
<div
class=
"text-center"
>
{{
challenge
.
saved
}}
kr /
{{
challenge
.
target
}}
kr
</div>
</div>
<button
@
click=
"incrementSaved(challenge)"
type=
"button"
class=
"inline-block mb-2 ml-2 max-h-8 max-w-8 rounded-full bg-green-500 p-1 uppercase leading-normal text-white bg-color-green shadow-green-500 transition duration-150 ease-in-out hover:bg-green-700 hover:shadow-green-200 focus:bg-green-accent-300 focus:shadow-green-2 focus:outline-none focus:ring-0 active:bg-green-600 active:shadow-green-200 motion-reduce:transition-none dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong"
>
<svg
xmlns=
"http://www.w3.org/2000/svg"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
class=
"w-4 h-4"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"2"
d=
"M12 4v16m8-8H4"
/>
</svg>
</button>
</div>
</div>
<span
v-else
class=
"text-center"
>
Ferdig:
{{
challenge
.
saved
}}
</span>
</div>
<!-- Check Icon -->
<div
v-if=
"challenge.completion >= 100"
class=
"max-w-16 max-h-16"
>
<img
src=
"@/assets/completed.png"
alt=
""
>
️
</div>
<div
v-else
class=
"max-w-16 max-h-16"
>
<img
src=
"@/assets/pending.png"
alt=
""
>
️
</div>
</div>
</div>
<!-- Piggy Steps, centered -->
<div
v-if=
"index !== challenges.length - 1"
class=
"flex justify-center w-full"
>
<img
:src=
"getPigStepsIcon()"
:class=
"
{ 'transform scale-x-[-1]': (index) % 2 === 0 }" class="w-20 h-20" alt="Pig Steps">
</div>
</div>
</div>
<!-- Finish line -->
<img
src=
"@/assets/finishLine.png"
class=
"w-full max-h-4 mx-auto"
alt=
"Finish Line"
>
<!-- Goal -->
<div
v-if=
"currentGoal"
class=
"flex items-center justify-between m-t-2 pt-6"
>
<div
class=
"flex flex-col items-start"
>
<img
:src=
"getGoalIcon(currentGoal)"
class=
"w-12 h-12 mx-auto"
:alt=
"currentGoal.title"
>
<div
class=
"text-lg font-bold"
>
{{
currentGoal
.
title
}}
</div>
</div>
<div
class=
"flex flex-col items-end"
>
<div
@
click=
"goToEditGoal"
class=
"cursor-pointer"
>
<h3
class=
"text-blue-500 text-base"
>
Endre mål
</h3>
</div>
<div
ref=
"targetRef"
class=
"bg-yellow-400 px-4 py-1 rounded-full text-black"
>
{{
currentGoal
.
saved
}}
kr /
{{
currentGoal
.
target
}}
kr
</div>
</div>
</div>
</div>
<!-- Animation icon -->
<img
src=
"@/assets/coins.png"
alt=
"Coins"
ref=
"iconRef"
class=
"max-w-20 max-h-20 absolute opacity-0"
>
</div>
<div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
computed
,
nextTick
,
onMounted
,
ref
,
watch
}
from
'
vue
'
;
import
anime
from
'
animejs
'
;
import
InteractiveSpare
from
"
@/components/InteractiveSpare.vue
"
;
import
ButtonAddGoalOrChallenge
from
"
@/components/ButtonAddGoalOrChallange.vue
"
;
import
router
from
"
@/router
"
;
// Define your speech array
const
speechArray
=
[
"
Hei! Jeg er Sparemannen.
"
,
"
Jeg hjelper deg med å spare penger.
"
,
"
Klikk på meg for å høre mer.
"
];
// Correctly initialize the ref
const
speech
=
ref
(
speechArray
);
// Reactive references for DOM elements
const
iconRef
=
ref
<
HTMLElement
|
null
>
(
null
);
const
containerRef
=
ref
<
HTMLElement
|
null
>
(
null
);
const
targetRef
=
ref
<
HTMLElement
|
null
>
(
null
);
const
goals
=
ref
([
{
title
:
'
Gaming
'
,
saved
:
280
,
target
:
100
,
description
:
'
Gaming console
'
,
priority
:
1
,
completion
:
0
},
// Other goals...
])
const
challenges
=
ref
([
{
title
:
'
Kaffe
'
,
challengeType
:
'
COFFEE
'
,
saved
:
100
,
target
:
100
,
description
:
'
Morning boost
'
,
completion
:
100
},
{
title
:
'
Mat og Drikke
'
,
challengeType
:
'
SNACKS
'
,
saved
:
80
,
target
:
100
,
description
:
'
Morning boost
'
,
completion
:
80
},
{
title
:
'
Gaming
'
,
challengeType
:
'
GAMING
'
,
saved
:
20
,
target
:
100
,
description
:
'
Morning boost
'
,
completion
:
20
},
{
title
:
'
Kaffe
'
,
challengeType
:
'
COFFEE
'
,
saved
:
90
,
target
:
100
,
description
:
'
Morning boost
'
,
completion
:
90
},
{
title
:
'
Mat og Drikke
'
,
challengeType
:
'
SNACKS
'
,
saved
:
80
,
target
:
100
,
description
:
'
Morning boost
'
,
completion
:
80
},
{
title
:
'
Gaming
'
,
challengeType
:
'
GAMING
'
,
saved
:
20
,
target
:
100
,
description
:
'
Morning boost
'
,
completion
:
20
},
// Other challenges...
])
// Additional state to track if the animation has been played
// Computed current goal
const
currentGoal
=
computed
(()
=>
{
// Logic to determine the current goal
return
goals
.
value
.
find
(
goal
=>
goal
.
completion
<
100
)
})
// Increment saved amount
function
incrementSaved
(
challenge
)
{
challenge
.
saved
+=
10
;
if
(
challenge
.
saved
>=
challenge
.
target
)
{
challenge
.
completion
=
100
;
}
}
function
recalculateAndAnimate
()
{
nextTick
(()
=>
{
if
(
iconRef
.
value
&&
containerRef
.
value
&&
targetRef
.
value
)
{
animateIcon
();
}
else
{
console
.
error
(
'
Element references are not ready.
'
);
}
});
}
const
animatedChallenges
=
ref
(
new
Set
());
const
loadAnimatedStates
=
()
=>
{
const
animated
=
localStorage
.
getItem
(
'
animatedChallenges
'
);
animatedChallenges
.
value
=
animated
?
new
Set
(
JSON
.
parse
(
animated
))
:
new
Set
();
};
const
saveAnimatedState
=
(
title
)
=>
{
animatedChallenges
.
value
.
add
(
title
);
localStorage
.
setItem
(
'
animatedChallenges
'
,
JSON
.
stringify
([...
animatedChallenges
.
value
]));
};
const
animateChallenge
=
(
challenge
)
=>
{
if
(
challenge
.
completion
>=
100
&&
!
animatedChallenges
.
value
.
has
(
challenge
.
title
))
{
console
.
log
(
"
Animating for:
"
,
challenge
.
title
);
recalculateAndAnimate
();
// Assumes this function triggers the actual animation
saveAnimatedState
(
challenge
.
title
);
}
};
watch
(
challenges
,
(
newChallenges
)
=>
{
newChallenges
.
forEach
(
challenge
=>
{
if
(
challenge
.
completion
===
100
&&
!
animatedChallenges
.
value
.
has
(
challenge
.
title
))
{
animateChallenge
(
challenge
);
}
});
},
{
deep
:
true
});
onMounted
(()
=>
{
// Filter challenges that are already completed
const
completedChallenges
=
challenges
.
value
.
filter
(
challenge
=>
challenge
.
completion
===
100
).
map
(
challenge
=>
challenge
.
title
);
// For testing purposes, clear localStorage
localStorage
.
clear
();
// Update localStorage with the titles of completed challenges
localStorage
.
setItem
(
'
animatedChallenges
'
,
JSON
.
stringify
(
completedChallenges
));
// Load the initial state of animated challenges from localStorage
loadAnimatedStates
();
});
function
animateIcon
()
{
const
icon
=
iconRef
.
value
;
const
container
=
containerRef
.
value
;
const
target
=
targetRef
.
value
;
if
(
!
icon
||
!
container
||
!
target
)
{
console
.
error
(
'
Required animation elements are not available.
'
);
return
;
}
const
containerRect
=
container
.
getBoundingClientRect
();
const
targetRect
=
target
.
getBoundingClientRect
();
const
iconRect
=
icon
.
getBoundingClientRect
();
const
translateX1
=
containerRect
.
left
+
(
containerRect
.
width
/
2
)
-
(
iconRect
.
width
/
2
)
-
iconRect
.
left
;
const
translateY1
=
containerRect
.
top
+
(
containerRect
.
height
/
2
)
-
(
iconRect
.
height
/
2
)
-
iconRect
.
top
;
const
translateX2
=
targetRect
.
left
+
(
targetRect
.
width
/
2
)
-
(
iconRect
.
width
/
2
)
-
iconRect
.
left
;
const
translateY2
=
targetRect
.
top
+
(
targetRect
.
height
/
2
)
-
(
iconRect
.
height
/
2
)
-
iconRect
.
top
;
anime
.
timeline
({
easing
:
'
easeInOutQuad
'
,
duration
:
1500
})
.
add
({
targets
:
icon
,
translateX
:
translateX1
,
translateY
:
translateY1
,
opacity
:
0
,
// Start invisible
duration
:
1000
})
.
add
({
targets
:
icon
,
opacity
:
1
,
// Reveal the icon once it starts moving to the container
duration
:
1000
,
// Make the opacity change almost instantaneously
scale
:
3
,
})
.
add
({
targets
:
icon
,
translateX
:
translateX2
,
translateY
:
translateY2
,
scale
:
0.5
,
opacity
:
1
,
// Keep the icon visible while moving to the target
duration
:
1500
})
.
add
({
targets
:
icon
,
opacity
:
0
,
// Fade out once it reaches the target
scale
:
1
,
duration
:
500
})
.
add
({
targets
:
icon
,
translateX
:
0
,
// Reset translation to original
translateY
:
0
,
// Reset translation to original
opacity
:
0
,
// Fade out once it reaches the target
scale
:
1
,
duration
:
500
,
});
}
// Helper methods to get icons
function
getChallengeIcon
(
challenge
)
{
return
`src/assets/
${
challenge
.
challengeType
.
toLowerCase
()}
.png`
}
function
getGoalIcon
(
goal
)
{
return
`src/assets/
${
goal
.
title
.
toLowerCase
()}
.png`
}
function
getPigStepsIcon
()
{
return
'
src/assets/pigSteps.png
'
;
}
function
goToEditGoal
()
{
router
.
push
({
name
:
'
EditGoal
'
});
}
</
script
>
<
template
>
<h1>
Heading 1
</h1>
<h2>
Heading 2
</h2>
<h3>
Heading 3
</h3>
<p>
Paragraph
</p>
<button>
Button
</button>
<br>
<a
href=
"#"
>
Link
</a>
<div>
Div
</div>
<section>
Section
</section>
<article>
Article
</article>
</
template
>
<
style
scoped
>
/* Tailwind CSS - Custom CSS for hiding scrollbars */
.no-scrollbar
::-webkit-scrollbar
{
display
:
none
;
/* for Chrome, Safari, and Opera */
}
.no-scrollbar
{
-ms-overflow-style
:
none
;
/* for Internet Explorer and Edge */
}
</
style
>
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment