-
Notifications
You must be signed in to change notification settings - Fork 3
Komponent CardsCreator
Komponent w swoich stanach zawiera wszystkie potrzebne dane, aby wysłać odpowiednią strukturę json do Backendu, oraz aby wczytać dane z karty, którą chcemy edytować.
state = {
cardId: undefined,
cardName: 'Nazwa Karty',
cardSubject: 'Przedmiot',
cardTooltip: 'Opis Karty',
levelCostValues: [],
effectsFromApi: [],
effectsToSend: [[], [], []],
showDescribeInputs: false,
headerLabel: '',
showCardChoose: false,
cardsFromApi: [],
levelsListFromCard: [],
chosenEffectsFromCard: [[], [], []],
showSendMessage: false,
sendSuccess: false,
}
-
cardId
przechowuje id karty, które jest potrzebne do edycji karty o konkretnym id -
cardName
przechowuje nazwę karty -
cardSubject
przechowuje informację z jakiego przedmiotu karta się wywodzi -
cardTooltip
przechowuje opis działania karty -
levelCostValues
przechowuje wartości do ulepszania kart na następne poziomy -
effectsFromApi
przechowuje listę effektów wczytanych z Api -
effectsToSend
przechowuje listy efektów do każdego poziomu karty - stan
showDescribeInputs
odpowiada za pokazanie pól formularza do określenia tych opisowych atrybutów karty, gdyshowDescribeInputs
będzietrue
to obszar z tymi polami pojawi się, wysunie się z góry -
headerLabel
przechowuje ciąg znaków odpowiadający za podpis widoku w pasku nawigacyjnym -
showCardChoose
odpowiada za pojawienie się wyboru karty do edycji -
cardsFromApi
przechowuje listę kart wczytanych z Api -
levelsListFromCard
przechowuje informacje o istniejących poziomach w danej karcie -
chosenEffectsFromCard
przechowuje listy wybranych efektów do poszczególnych poziomów karty -
showSendMessage
odpowiada za pojawienie się komunikatu czy dana karta pomyślnie wysłała się do Api -
sendSuccess
przechowuje informacje czy wysłanie karty się powiodło czy nie
Metoda, która wysyła kartę do Api, aby wstawić ją do bazy danych.
const levelsToSend = [];
for(let i=0; i < this.state.effectsToSend.length; i++) {
if(this.state.effectsToSend[i].length !== 0) {
levelsToSend.push (
{
level: String(i + 1),
next_level_cost: this.state.levelCostValues[i],
effects: this.state.effectsToSend[i]
}
);
}
}
Powyższy fragment odpowiednio umieszcza efekty do wysłania zgodnie ze strukturą Json w Api.
if(this.props.creatorType === 'create') {
try {
let result = fetch(`http://${API}/api/cards/`, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-type': 'application/json',
},
body: JSON.stringify({
name: this.state.cardName,
subject: this.state.cardSubject,
image: null,
tooltip: this.state.cardTooltip,
levels: levelsToSend
})
}) .then (
response => {
if(response.ok) {
this.setState({
showSendMessage: true,
sendSuccess: true
});
} else {
this.setState({
showSendMessage: true,
sendSuccess: false
});
}
}
);
console.log('Result: ' + result);
} catch (e) {
console.log(e);
}
}
Powyższy fragment wysyła do Api nową kartę i ustawa odpowiednio stany komponentu odpowiedzialne za wiadomość informującą o rezultacie wysłania nowej karty.
if(this.props.creatorType === 'edit') {
try {
let result = fetch(`http://${API}/api/cards/${this.state.cardId}/`, {
method: 'put',
headers: {
'Accept': 'application/json',
'Content-type': 'application/json',
},
body: JSON.stringify({
name: this.state.cardName,
subject: this.state.cardSubject,
image: null,
tooltip: this.state.cardTooltip,
levels: levelsToSend
})
}) .then (
response => {
if(response.ok) {
this.setState({
showSendMessage: true,
sendSuccess: true
});
} else {
this.setState({
showSendMessage: true,
sendSuccess: false
});
}
}
);
console.log('Result: ' + result);
} catch (e) {
console.log(e);
}
}
Powyższy fragment wysyła edytowaną kartę do Api i ustawa odpowiednio stany komponentu odpowiedzialne za wiadomość informującą o rezultacie edycji karty.
W tej metodzie wykonują się operacje podczas gdy komponent powstaje.
if(this.props.creatorType === 'edit')
this.setState({headerLabel: 'Edytowanie karty', showCardChoose: true});
else if(this.props.creatorType === 'create')
this.setState({headerLabel: 'Nowa karta', showCardChoose: false});
Powyższy fragment ustawia odpowiednio nnapis w nagłówku nawigacyjnym oraz czy udostępnić dla użytkownika wybór karty do edycji czy nie.
if(this.props.creatorType === 'edit') {
fetch(`http://${API}/api/cards/`)
.then(response => {
return response.json();
})
.then(data => this.setState({cardsFromApi: data}))
.catch(error => console.log(error));
}
Powyższy fragment pobiera istniejące karty z Api, jeśli jesteśmy w widoku do edytowania karty.
fetch(`http://${API}/api/cards/card-effect/`)
.then(response => {
return response.json();
})
.then(data => this.setState({effectsFromApi: data}))
.catch(error => console.log(error));
Powyższy fragment pobiera możliwe effekty kart z Api, potrzebne przy tworzeniu lub edycji karty.
Ustawia stan showDescribeInputs
odpowiedzialny za wyświetlenie inputów opisowych kartę na true
.
showDescribeInputsHandler = (event) => {
event.preventDefault();
this.setState({showDescribeInputs: true});
}
Dzięki event.preventDefault()
strona nie odświerza się po kliknięciu w button.
Przełącza stan showDescribeInputs
na false
, wywoływany jest wtedy, kiedy chcemy z powrotem "schować" inputy
do opisu karty.
hideDescribeInputsHandler = (event) => {
event.preventDefault();
this.setState({showDescribeInputs: false});
}
To handler odpowiedzialny za aktualizację naszego podglądu dla Nazwy Karty, jej Przedmiotu oraz Opisu działania. Zmienia on treść nagłówka "Nazwa Karty" oraz paragrafów "Przedmiot" oraz "Opis Karty". Zmienia stany również na '-', jeśli ktoś pozostawi je puste, aby struktura elemtów html była zachowana i nie wyglądało to dziwnie.
updateDescribePreview = (event) => {
const keyName = event.target.name;
let keyValue = '';
if(event.target.value !== '')
keyValue = event.target.value;
else keyValue = '-';
this.setState({[keyName]: keyValue});
}
Jest to zrobione w taki sposób, że to co wpiszemy w tych opisowych inputach, to będzie wartość na którą zaaktualizuje się dany stan reprezentujący jakiś konkretny atrybut karty na przykład jej nazwę. A te elementy html zmienią swoją treść, bo jest ona uzależniona od wartości tych stanów.
Na przykład mamy stany {cardName: 'nazwa karty', cardSubject: 'Przedmiot'}
i mamy w strukturze html:
<input name="cardName" onChange={handleChange}/>
<input name="cardSubject" onChange={handleChange}/>
To dzięki odpowiedniemu atrybutowi name, handleChange
rozpozna który stan komponentu zaaktualizować.
Obsługuje przypisywanie do stanu levelCostValues
odpowiednich wartości.
levelCostValuesHandler = (event) => {
let newList = this.state.levelCostValues.slice();
if(event.target.value > 0)
newList[Number(event.target.id[0]) - 1] = event.target.value;
else newList[Number(event.target.id[0]) - 1] = undefined;
this.setState({levelCostValues: newList});
}
Wstawia pod odpowiedni indeks wartość undefined
, aby formularz myślał że nigdy taka wartość nie istniała.
levelCostClearHandler = (event, rank) => {
event.preventDefault();
let newList = this.state.levelCostValues.slice();
newList[rank - 1] = undefined;
this.setState({levelCostValues: newList});
}
Potrzebne przy usuwaniu jakiegoś levelu karty.
Wstawia pod odpowiedni indeks jedynkę dla stanu levelCostValues
.
Dlatego zawsze na początku wartość ulepszenia następnego levelu jest 1.
levelCostResetHandler = (event, rank) => {
event.preventDefault();
let newList = this.state.levelCostValues.slice();
newList[rank - 1] = 1;
this.setState({levelCostValues: newList});
}
Potrzebne, bo inaczej po usunięciu levela, wartość w stanie byłaby undefined
a w inpucie wciąż ta poprzednia.
UWAGA: W konsoli wywala jakiś warning, bo aby to uzyskać, to musiałem uzależnić atrybut value
w <input />
od stanu levelCostValues
, działa wszystko, ale nie wiem jak to zrobić bez tego warninga.
Ustawia stan reprezentujący efekty do karty, którą chcemy wysłać listą podaną jako argument do tej metody.
setEffectsToSendHandler = (effects) => {
this.setState({effectsToSend: effects});
}
Wywoływana jest w dziecku CardsProperties.
Ukrywa wybór karty do edycji.
hideCardChooseHandler = (event) => {
event.preventDefault();
this.setState({showCardChoose: false});
}
Wywołuje się, gdy już wybierzemy kartę do edycji.
Aktualizuje odpowiednie stany, gdy się już wybierze kartę do edycji.
chosenCardHandler = (event, id, name, subject, tooltip, levels) => {
event.preventDefault();
this.setState({
cardId: id,
cardName: name,
cardSubject: subject,
cardTooltip: tooltip
});
this.setLevelsListFromCard(levels);
this.setLevelCostValuesFromCard(levels);
this.setChosenEffectsFromCard(levels);
this.hideCardChooseHandler(event);
}
Id, name, subject oraz tooltip, można aktualizują się za pomocą setState, a pozostałe są zaimplementowane w osobnych metodach poniżej. Na końcu wywoływane jest również ukrycie komponentu odpowiedzialnego za wybór karty do edycji.
Ustawia listę istniejących poziomów w wybranej karcie do edycji.
setLevelsListFromCard = (levels) => {
let newLevelsList = [];
for (let i=0; i<levels.length; i++) {
newLevelsList.push(levels[i].level);
}
this.setState({levelsListFromCard: newLevelsList});
}
Trzeba pamiętać że jest możliwość istnienia poziomów [2,3], albo nawet od razu [3], nie musi rozpoczynać się od pierwszego.
Ustawia listę kosztów ulepszenia na następny poziom w wybranej karcie do edycji.
setLevelCostValuesFromCard = (levels) => {
let newCostList = this.state.levelCostValues.slice();
try {
if(levels[0].next_level_cost)
newCostList[levels[0].level - 1] = levels[0].next_level_cost;
if(levels[1].next_level_cost)
newCostList[levels[1].level - 1] = levels[1].next_level_cost;
this.setState({levelCostValues: newCostList});
} catch (error) {
console.log(error);
}
}
Te if'y są aby wstawić pod właściwy indeks koszt ulepszenia, aby zgadzał się z poziomem karty.
Ustawia listę efektów według wybranej karty do edycji.
setChosenEffectsFromCard = (levels) => {
let newEffectsList = [[], [], []];
let newChosenEffectsList = [[], [], []];
let chosenEffectElem;
for (let i=0; i<levels.length; i++) {
for (let j=0; j<levels[i].effects.length; j++) {
newEffectsList[levels[i].level - 1].push({
card_effect: levels[i].effects[j].card_effect,
target: levels[i].effects[j].target,
power: levels[i].effects[j].power,
range: levels[i].effects[j].range,
level: levels[i].level
});
chosenEffectElem = this.state.effectsFromApi.filter(function (elem) {
return elem.id === levels[i].effects[j].card_effect;
})
newChosenEffectsList[levels[i].level - 1].push(chosenEffectElem[0]);
}
}
this.setState({effectsToSend: newEffectsList});
this.setState({chosenEffectsFromCard: newChosenEffectsList});
}
Są tutaj aktualizowane zarówno lista wybranych efektów jak i lista efektów do wysłania, aby wcześniej wybrane atrybuty były widoczne przy edycji.
Ukrywa wiadomość o rezultacie wysłania karty do Api.
hideSendMessageHandler = (event) => {
event.preventDefault();
this.setState({showSendMessage: false});
}
Wywołuje się gdy klikniemy poza obszar komponentu SendMessage.
Metoda odświerzająca stronę.
refreshPage = () => {
window.location.reload();
}
Wywoływana gdy chcemy wybrać kolejną lub inną kartę do edycji.
render() {
return (
<>
<CardChoose showCardChoose={this.state.showCardChoose}
hideCardChooseHandler={this.hideCardChooseHandler}
cardsFromAPI={this.state.cardsFromApi}
chosenCardHandler={this.chosenCardHandler} />
<Wrapper>
<NavHeader backLink={'/cards-creator-start'} label={this.state.headerLabel} />
<Main>
<CardDescribePreview cardName={this.state.cardName}
cardSubject={this.state.cardSubject}
cardTooltip={this.state.cardTooltip}
showDescribeInputsHandler={this.showDescribeInputsHandler}
/>
<Form>
<CardDescribeInputs updateDescribePreview={this.updateDescribePreview}
show={this.state.showDescribeInputs}
hideDescribeInputsHandler={this.hideDescribeInputsHandler}
/>
<CardProperties creatorType={this.props.creatorType}
levelCostValues={this.state.levelCostValues}
levelCostValuesHandler={this.levelCostValuesHandler}
levelCostClearHandler={this.levelCostClearHandler}
levelCostResetHandler={this.levelCostResetHandler}
effectsFromApi={this.state.effectsFromApi}
setEffectsToSendHandler={this.setEffectsToSendHandler}
levelsListFromCard={this.state.levelsListFromCard}
chosenEffectsFromCard={this.state.chosenEffectsFromCard}
effectsToSend={this.state.effectsToSend}
/>
<Div>
<Button type='submit' onClick={this.sendCardToApi} show={true}>
Wyślij
</Button>
<Button onClick={this.refreshPage} show={this.props.creatorType}>
Edytuj inną kartę
</Button>
</Div>
</Form>
</Main>
</Wrapper>
<SendMessage showMessage={this.state.showSendMessage}
sendSuccess={this.state.sendSuccess}
hideSendMessageHandler={this.hideSendMessageHandler} />
</>
);
}
Komponent wyboru karty do edycji
<CardChoose showCardChoose={this.state.showCardChoose}
hideCardChooseHandler={this.hideCardChooseHandler}
cardsFromAPI={this.state.cardsFromApi}
chosenCardHandler={this.chosenCardHandler} />
Komponent przyjmuje jako props:
- stan odpowiedzialny za pokazanie się wyboru karty do edycji, aby uzależnić od niego swoją widoczność
- handler do ustawienia widoczności na false, aby ukryć ten komponent we właściwym momencie
- stan z listą kart wczytanych z Api, aby wyświetlić karty do wyboru, którą chcemy edytować
- handler do aktualizacji wszystkich stanów odpowiedzialnych za reprezentację atrybutów danej karty
Element Wrapper to główny pojemnik na nasz Kreator Kart, zwykły div, który środkuje nasze elementy,
nadaje wysokość, oraz sprawia, że możemy określać koordynaty elementów względem obszaru tego Wrapper dzięki
position: relative
.
import styled from 'styled-components';
const Wrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
position: relative;
overflow: hidden;
`;
export default Wrapper;
Elementy wyjeżdżające poza ten element nie będą wpływać na rozmiar okna przeglądarki dzięki overflow: hidden
.
To nagłówek widoku, gdzie jest przycisk do cofania się i rozwinięcie menu aplikacji.
<NavHeader backLink={'/cards-creator-start'} label={this.state.headerLabel} />
Przyjmuje props ścieszki gdzie się cofnie oraz napis do podpisu widoku.
Obszar głownej zawartości Kreatora, selektor html <main></main>
dla zachowania semantyczności.
import styled from 'styled-components';
const Main = styled.main`
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
min-height: calc(100vh - 56px);
padding: 20px 16px;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
background-color: ${({theme}) => theme.colors.uiBlue};
`;
export default Main;
- Również wyśrodkowany flexem
- Jego szerokość 100% rodzica, czyli będzie na całą stronę, a wysokość to całkowita wysokość okna przeglądarki minus wysokość nagłówka nawigacyjnego
- Wykorzystany jest tutaj theme provider, dzięki któremu nie musimy importować pliku z globalnymi zmiennymi takich jak kolory, tylko możemy się do nich odnieść za pomocą tej ostatniej linijki przy background-color.
Wstawiamy tutaj komponent CardDescribePreview i przekazujemy mu następujące propsy (takie argumenty):
- Stany przechowujące nazwę karty, jej przedmiot oraz opis, aby wiedział jaką treść pokazać,
- Handler odpowiedzialny za pokazanie pól do wypełnienia dla nazwy karty itd., dzięki temu będziemy mogli wywołać
w tym komponencie zmianę stanu
showDescribeInputs
.
<CardDescribePreview
cardName={this.state.cardName}
cardSubject={this.state.cardSubject}
cardTooltip={this.state.cardTooltip}
showDescribeInputsHandler={this.showDescribeInputsHandler}
/>
To znacznik html określający formularz, który będziemy wysyłać.
import styled from 'styled-components';
const Form = styled.form`
width: 100%;
height: 100%;
padding: 0;
margin: 0;
`;
export default Form;
A tutaj już w formularzu zawarty jest komponent CardDescribeInputs, przekazujemy mu takie propsy:
- handlera updateDescribePreview, aby mógł go wywołać do zaaktualizowania podglądu dla Nazwy Karty itd.,
- stan showDescribeInputs, aby wiedział kiedy się pokazać,
- handlera hideDescribeInputsHandler, aby mógł wywołać ukrycie się z powrotem.
<CardDescribeInputs
updateDescribePreview={this.updateDescribePreview}
show={this.state.showDescribeInputs}
hideDescribeInputsHandler={this.hideDescribeInputsHandler}
/>
Dalej mamy komponent CardProperties, który reprezentuje cały obszar z pozostałymi atrybutami kart takimi jak efekty itp.
<CardProperties creatorType={this.props.creatorType}
levelCostValues={this.state.levelCostValues}
levelCostValuesHandler={this.levelCostValuesHandler}
levelCostClearHandler={this.levelCostClearHandler}
levelCostResetHandler={this.levelCostResetHandler}
effectsFromApi={this.state.effectsFromApi}
setEffectsToSendHandler={this.setEffectsToSendHandler}
levelsListFromCard={this.state.levelsListFromCard}
chosenEffectsFromCard={this.state.chosenEffectsFromCard}
effectsToSend={this.state.effectsToSend} />
Komponent przyjmuje jako props:
- creatorType aby mieć informację o tym czy tworzymy nową kartę czy edytujemy
- levelCostValues aby prezentować wartości kosztów ulepszenia karty na następny poziom
- levelCostValuesHandler aby atualizować wartości kosztów ulepszenia karty na następny poziom
- levelCostClearHandler aby wymazać wartości kosztów ulepszenia karty na następny poziom, gdy usuniemy dany poziom karty
- levelCostResetHandler aby przypisać wartość 1 kosztów ulepszenia karty na następny poziom, gdy utworzymy na nowo dany poziom karty
- effectsFromApi aby wyświetlić efekty do wyboru, gdy dodaje się nowy efekt do karty
- setEffectsToSendHandler aby zaaktualizować stan effectsToSend w tym komponencie, który jest rodzicem
- levelsListFromCard aby mieć informację o istniejących poziomach wybranej karty do edycji
- chosenEffectsFromCard aby móc zaprezentować wybrane efekty w danej karcie
- effectsToSend aby posiadać efekty do wysłania z wybranej karty do edycji
Obszar wyśrodkowany poziomym flexem, który posiada dwa przyciski.
<Div>
<Button type='submit' onClick={this.sendCardToApi} show={true}>
Wyślij
</Button>
<Button onClick={this.refreshPage} show={this.props.creatorType}>
Edytuj inną kartę
</Button>
</Div>
Jeden przycisk wysyła kartę do Api, drugi jest do edycji następnej karty, który odświerza stronę. Button "Wyślij" jest zawsze widoczny, natomiast Button do edycji jest widoczny tylko wtedy gdy props creatorType === 'edit'.
To komponent reprezentujący wiadomość zwrotną czy wysłanie karty się powiodło czy nie.
<SendMessage showMessage={this.state.showSendMessage}
sendSuccess={this.state.sendSuccess}
hideSendMessageHandler={this.hideSendMessageHandler} />
WMI Adventure
Dokumentacja
- System punktów
- API
- Backend
-
Frontend
- Struktura plików
- Komponenty:
-
Komponent CardsCreator
- Atoms:
- Organisms:
-
Komponent CardsCreator