Some say heroes are shaped, but they all have to start somewhere!
One of the main qualities of components is its ability to pass data in and out, through props, bindings, and events.
In Svelte, one way to accomplish this is by exposing props in a component and using bindings, which is the two-way binding for Svelte, to bind these values to some variable in a parent component.
Tip: (hint 1a) To see the updates to the actual character model, click "Character Debug" in the lower right corner.
src/lib/components/race-select/RaceSelect.svelte
src/routes/creator/+page.svelte
- Observe that changing the race now doesn't reflect in the character data model in the Character Debug panel.
- Expose the
value
variable as a prop in the RaceSelect component. - Bind the corresponding variable in the parent component (character creator page), to that prop, so that:
- Changing the race selector updates the character data with the new race.
npm run hint 1a
npm run hint 1b
npm run hint 1c
npm run hint 1d
click click... Our heroes' journey can not start if we can not even click a button!
For consistent design and functionality we wish to create a Button component that can be reused in our application.
The Button component currently has the three variables: variant
, small
, and disabled
.
But they are currently not exposed as props, and can not be set by parent components.
We also wish to expose the click
event of the inner button so any parent components can listen for that event,
as that is the main use case for a button.
Lastly, our button should also have some content. We wish for anything that is inside our Button element to be reflected
into the inner button. Example: <Button>This should be displayed inside the button</Button>
.
In Svelte this is done using Slots.
src/lib/components/button/Button.svelte
- Expose the variables
variant
,small
, anddisabled
so that they can be set by the parent component. - Propagate the
click
event from the button to parent components. - Use slots in our Button component to display the content of our Button's html tag provided in parent components.
npm run hint 2a
npm run hint 2b
npm run hint 2c
Our mighty heroes seem a bit weak, and their investment in their attributes isn't helping!
Luckily we are able to assist them by boosting their attributes' effects on their stats.
Currently, the attributes do not affect the stats in any way. In the "Goals section" you can see how we wish attributes should affect stats. You should be able to see any changes in the stats when an attribute is changed.
To accomplish our goal we can use Reactive Declarations to change one value whenever any referenced values change.
The parent component is already passing the attributes to the Stats component in the character creator.
src/lib/components/attributes/stats/Stats.svelte
- Use Reactive Declarations to set minimum damage as a reactive variable based on the strength attribute,
replacing the existing implementation of the
meleeDamageMin
variable.- Minimum damage should be equal to strength x 2.
- Maximum damage equals strength x 3.
- Block chance equals dexterity x 0.5.
- Hitpoints equals constitution x 5.
- Mana equals wisdom x 10.
- Spellpower equals intelligence x 2.5.
- When charisma is < 12 speech should be
Timid
, when charisma is >= 12 speech should beWell Spoken
, and when charisma is >= 16 speech should beCharming
.
npm run hint 3a
npm run hint 3b
npm run hint 3c
npm run hint 3d
Our heroes look mighty powerful! But they do still not seem satisfied... Maybe we should let them train some skills, and impress them by making it look smooth?
The current solution provides a list of available skills and selected skills. Currently, when selecting or deselecting a skill, the skill is instantly moved between the two lists.
To ease our heroes' weary eyes we want to animate the items moving between the two lists.
Svelte offers powerful motion tools to deal with simple transitions, or more advanced transitions and animations when taking elements in or out of the DOM and moving them somewhere else.
Here, when selecting a skill, the button is removed from the list it was in, and added to the DOM in the other list. For removing and adding elements to the DOM Svelte offers several transition directives, each with a different effect. Most of these deal only with the position the element is in when it's removed or should appear when it's added. But others have logic for transitioning the element between where it's added, i.e. received, and where it's removed, i.e. sent. These are called Deferred Transitions.
These kinds of transitions allow us to move an element between where it's added or removed and to where it's new position is.
Svelte also offers us animation directives for animating the surrounding elements in the DOM, which aren't transitioning. This allows us to make very convincing animations where many elements move "naturally".
src/lib/components/skill-select/SkillSelect.svelte
- Use Deferred Transitions to transition the skill selection, making a clicked skill move smoothly from one list to the other, sending it from one element and receiving it another.
- Use Animations to animate the movement of the other elements in the list when an element is clicked and moves between them.
- Deferred Transitions
- Animations
- (For the curious: FLIP animations)
npm run hint 4a
npm run hint 4b
npm run hint 4c
npm run hint 4d
Some of our heroes seem to need help to decide what attributes possess, and are requesting our assistance.
Svelte is full of useful tools for animating changes, i.e. moving a value from one value to another. Start by looking into Motion (Tweened) or Motion (Spring). Both of these directives will work for our task.
We want to make our heroes' attributes move smoothly from one value to another when it's changed, instead of being replaced right away. This can more easily be spotted when using the "Randomize" button than when adding or removing an attribute.
Remember to also update and animate pointsRemaining
, in addition to the attributes themselves.
Play around with the motion settings to see how it affects the animation.
Tip: It's often a good idea to maintain two separate variables when working with animating UI elements: one for the actual value, and another "display"-value that animates/moves towards the real value when it changes.
src/lib/components/attributes/attribute-overview/AttributeOverview.svelte
- Use Motion (Tweened) or Motion (Spring) to animate the attributes changing and points remaining when they are increased, decreased, or randomized.
npm run hint 5a
npm run hint 5b
npm run hint 5c
npm run hint 5d
Someone has vandalized our hall of fame, and all our heroes' pictures were stolen! Help them reclaim their pictures.
Currently, the PortraitPicker
component shows a modal where we should be able to pick a portrait for our character,
but nothing shows up.
To get a portrait we first have to fetch a list of portrait IDs from our server. We then have to fetch each portrait to show it. Now, this is a lot of asynchronous logic, and we'll be dealing with Promises.
Promises might seem scary, but luckily Svelte has a special construct for dealing with them, directly in our markup: Await Blocks.
Short explanation of promises: When you "fire" a promise with a target URL, it goes through a couple of states:
- First it's in a loading state while it resolves the payload at the URL.
- Then it either:
- Resolves with a value.
- Rejects with an error.
In this task, you need to deal with two different promises, one which fetches all the portrait IDs, and another which fires based on the resolved value of the first promise, and use await blocks to handle this in your markup.
To help you with dealing with the different states of the promises, the Loader
and PortraitPreview
components will
be essential.
src/lib/components/portraits/portrait-selection/PortraitSelection.svelte
- Use an await block to show three different things, based on the state of the
getPortraitsPromise
promise:- Loading: A
Loader
component. - Resolved: For now, just show the IDs that were fetched, see next goal for improving this.
- Rejected: An error message.
- Loading: A
- When you have a working await block for the list of portrait IDs, use an
each block to iterate over each of the IDs and fetch the portrait itself.
- Note: The portrait data is located at the URL
/api/portraits/{ID}
.
- Note: The portrait data is located at the URL
- Inside the each block, use another await block to render the different states of the portrait promises:
- Loading: A
PortraitPreview
where theisLoading
prop is set totrue
. - Resolved: A
PortraitPreview
with the value prop set to the payload resolved in this promise. - Rejected: An error message.
- Loading: A
- Feel free to style the portrait selector as you'd like.
npm run hint 6a
npm run hint 6b
npm run hint 6c
I swear I saw the recruitment board somewhere around here...
Now that we have a way of creating characters, we need to be able to see who's available for hire! For that, we're going to implement a recruitment board. This will be a whole new page in our Single-page application.
We need to take advantage of SvelteKit in this quest. SvelteKit gives us some great benefits, among them filesystem routing in our application. For those of you familiar with Next.js, SvelteKit works in a similar way. For those not familiar with filesystem routing, see SvelteKit's page about it.
Our recruitment board needs to get all created characters from our API, and display them as a list with some basic information.
A basic component for showing a character has been created: CharacterPreview.svelte
. Feel free to modify this to show
more information about the characters if you'd like.
src/lib/components/page-layout/header/Header.svelte
src/routes/...
- (Not necessary to modify:
src/lib/components/characters/character-preview/CharacterPreview.svelte
)
- Create an entry for the "Characters" page in the application header's navigation bar.
- When clicked it should navigate to the URL
/characters
. - The link should be shown as the active link when at the
/characters
page.
- When clicked it should navigate to the URL
- Create a new page for listing all existing characters.
- Should use Svelte's
onMount
to fire a promise to get all characters from/api/characters
. You can use the premadeapiFetch
function, which returns a promise, for making calls to the API. - Should use the
<svelte:head>
tag to change the title of the page. - Should handle the different states of the initial promise, just as in the previous quest.
- You can use the
CharacterPreviewList
component to actually display the characters when resolved.
- Should use Svelte's
- Optionally modify
CharacterPreview
to show more information about the characters.
- Routing: Pages
- data-sveltekit-preload-data
- Note: Not important to add, but a nice feature to be aware of.
- onMount
- Promises
- svelte:head
- Await Blocks
npm run hint 7a
npm run hint 7b
Our heroes stand tall in our Hall of Fame, but some of our visitors want a personal audience with some of the heroes.
After implementing the Recruitment Board in Quest 7, we're able to view a quick preview of all the heroes. But what if we wanted to view all the information about a hero as well? To accomplish this, you should create a new page for displaying a character.
To accomplish this, a SvelteKit route with a slug/parameter to reference the character ID is recommended. To access this
parameter, SvelteKit's load function can be used in a +page.ts
file next
to your page component.
The character page should be accessed by clicking one of the character previews in the Characters page. This link is
already set up in the CharacterPreview
component.
src/lib/components/characters/character-preview/CharacterPreview.svelte
src/routes/characters/[uid]/+page.svelte
src/routes/characters/[uid]/+page.ts
- Add a Character page component.
- In the
+page.ts
file next to your component, access theuid
parameter in aload
function and expose it as a prop to the normal<script>
tag. - In your component
<script>
tag, fire a call to the API to get the character, handling loading states of the promise. - Display all the information about the character on the page, using any existing components you find relevant, and implementing your own display of information about the character and your own CSS styles to achieve this.
npm run hint t1a
The people only deserve the best heroes. Who will rise to the top as champion?
To evaluate which hero is the best combatant, we need to pit them against each other in a test of prowess.
For this quest, you're to implement a combat simulator which pits two heroes against each other, using their character stats and perhaps some randomness to determine the best fighter.
You are free to simulate the combat any way you want to, but here's a few ideas to get you started:
- Establish your own page for the combat simulator.
- You can fetch any character, perhaps two random or picked characters, from the API.
- Each battle can be a single battle, or be a series of hits/attacks against each other.
- Perhaps starting with a single attack and then implementing more would be a good start?
- Use the data available on each character to simulate a battle between the two.
- Svelte has many cool features to animate and move elements, check them out and see if any can be used to create cool
effects!
- Perhaps a Transition to move the representation of the characters in to the "battlefield"? Or even an Animation to actually move the elements from a character picker and into a designated "combatant slot" element?
- Perhaps a Tween to gradually reduce stats or health bars?
- Perhaps a Spring to simulate movement of the character representations when they get hit?
- Any deemed necessary.
- Simulate a battle between two characters with a winner, a loser, or a draw as the end result.
- Routing: Pages
- onMount
- Svelte Tutorial
- Demonstrates many features that can be used for this quest.
npm run hint t2a
npm run hint t2b