Skip to content

Komponent CardsCreator

Mateusz edited this page Jun 9, 2021 · 2 revisions

Dokumentacja komponentu CardsCreator

Stany komponentu

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, gdy showDescribeInputs będzie true 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

Metody komponentu, handlery

sendCardToApi

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.

componentDidMount

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.

showDescribeInputsHandler

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.

hideDescribeInputsHandler

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});
}

updateDescribePreview

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ć.

levelCostValuesHandler

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});
  }

levelCostClearHandler

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.

levelCostResetHandler

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.

setEffectsToSendHandler

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.

hideCardChooseHandler

Ukrywa wybór karty do edycji.

hideCardChooseHandler = (event) => {
    event.preventDefault();
    this.setState({showCardChoose: false});
}

Wywołuje się, gdy już wybierzemy kartę do edycji.

chosenCardHandler

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.

setLevelListFromCard

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.

setLevelCostValuesFromCard

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.

setChosenEffectsFromCard

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.

hideSendMessageHandler

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.

refreshPage

Metoda odświerzająca stronę.

refreshPage = () => {
    window.location.reload();
}

Wywoływana gdy chcemy wybrać kolejną lub inną kartę do edycji.

Warstwa prezentacyjna komponentu

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} />
      </>
  );
}

CardChoose

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

Wrapper

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.

NavHeader

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.

Main

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.

CardDescribePreview

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}
/>

Form

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;

CardDescribeInputs

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}
/>

CardProperties

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

Div

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'.

SendMessage

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} />