ivar_n 2 роки тому
батько
коміт
fab49b0d8f
3 змінених файлів з 212 додано та 17 видалено
  1. 5 2
      js/15_graph-ql/index.js
  2. 27 4
      js/16_redux-thunk/index.html
  3. 180 11
      js/16_redux-thunk/index.js

+ 5 - 2
js/15_graph-ql/index.js

@@ -1,9 +1,9 @@
 
-
+// // получение объекта из jwt токена
 // const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2MWE0ZGIyOWM3NTBjMTJiYTZiYTQwMjIiLCJsb2dpbiI6ImVxd2VxZXdldyIsImFjbCI6WyI2MWE0ZGIyOWM3NTBjMTJiYTZiYTQwMjIiLCJ1c2VyIl19LCJpYXQiOjE2MzgxOTQ1NzZ9.Pi1GO6x7wdNrIrUKCQT-32-SsqmgFY-oFDrrXmw74-8'
 // JSON.parse(atob(token.split('.')[1]))
 
-
+// // получение контрольных сумм
 // let qqq = 'fdhfakfalfjskgfsdadasdasdasdasdadfsdfkarieqfowerdaesfa'
 
 // function checkSum(str) {
@@ -15,6 +15,9 @@
 // }
 // console.log(checkSum(qqq))
 
+
+// // добавление соли (секретная последовательность) к информации 
+// // для формирования уникальной контрольной суммы
 // function sign(data, salt) {
 //    const json = JSON.stringify(data)
 //    const sum = checkSum(json + salt)

+ 27 - 4
js/16_redux-thunk/index.html

@@ -5,9 +5,32 @@
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
+   <style>
+      #mainContainer {
+          display: flex;
+      }
+      #aside {
+          width: 30%;
+      }
+      #aside > a{
+          display: block;
+      }
+      #main > a{
+          display: block;
+      }
+  </style>
 </head>
-<body>
-   
-   <script src="./index.js"></script>
-</body>
+   <body>
+      <header>КУДА Я ПОПАЛ?</header>
+      <div id='mainContainer'>
+          <aside id='aside'>
+              Категории
+          </aside>
+          <main id='main'>
+              Контент
+          </main>
+      </div>
+      <script src='index.js'></script>
+  </body>
+
 </html>

+ 180 - 11
js/16_redux-thunk/index.js

@@ -1,24 +1,193 @@
-htmlTree()
-function htmlTree() {
 
+function createStore(reducer){
+    let state       = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
+    let cbs         = []                     //массив подписчиков
+    
+    const getState  = () => state            //функция, возвращающая переменную из замыкания
+    const subscribe = cb => (cbs.push(cb),   //запоминаем подписчиков в массиве
+                             () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
+                             
+    const dispatch  = action => { 
+        if (typeof action === 'function'){ //если action - не объект, а функция
+            return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
+        }
+        const newState = reducer(state, action) //пробуем запустить редьюсер
+        if (newState !== state){ //проверяем, смог ли редьюсер обработать action
+            state = newState //если смог, то обновляем state 
+            for (let cb of cbs)  cb() //и запускаем подписчиков
+        }
+    }
+    
+    return {
+        getState, //добавление функции getState в результирующий объект
+        dispatch,
+        subscribe //добавление subscribe в объект
+    }
 }
 
-htmlTree()
-function htmlTree() {
 
+
+function promiseReducer(state={}, {type, status, payload, error, name}) {
+    if (!state) { 
+        return {}
+            //{ login: {status, payload, error},
+            //  catById: {status, payload, error}
+            //}
+    }
+
+    if (type === 'PROMISE') {         
+        return {
+            ...state,
+            [name]: {
+                status: status,
+                payload : payload,
+                error: error,
+            }
+        }
+    }
+    return state
 }
 
-htmlTree()
-function htmlTree() {
+const actionPending = (name) => ({type: 'PROMISE', status: 'PENDING', name})
+const actionResolved = (name, payload) => ({type: 'PROMISE', status: 'RESOLVED', name, payload})
+const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error})
+
+const store = createStore(promiseReducer)
+store.subscribe(() => console.log(store.getState()))
+
+
+// const delay = (ms) => new Promise((ok) => setTimeout(() => ok(ms), ms))
 
+// store.dispatch(actionPending('delay1000'))
+// delay(1000).then(data => store.dispatch(actionResolved('delay1000', data)),
+//                  error => store.dispatch(actionRejected('delay1000', error)))
+
+// store.dispatch(actionPending('delay2000'))
+// delay(2000).then(data => store.dispatch(actionResolved('delay2000', data)),
+//                  error => store.dispatch(actionRejected('delay2000', error)))
+
+
+const actionPromise = (name, promise) => 
+async dispatch => {
+    dispatch(actionPending(name))
+    try { 
+        let data = await promise
+        dispatch(actionResolved(name, data))
+        return data
+    }
+    catch(error){
+        dispatch(actionRejected(name, error))
+    }
 }
 
-htmlTree()
-function htmlTree() {
+const getGQL = url =>
+  async (query, variables={}) => {
+    // try {
+      let obj = await fetch(url, {
+        method: 'POST',
+        headers: {
+          "Content-Type": "application/json"
+        },
+        body: JSON.stringify({ query, variables })
+      })
+      let a = await obj.json()
+      if (!a.data && a.errors) {
+          throw new Error(JSON.stringify(a.errors))
+      } else {
+          return a.data[Object.keys(a.data)[0]]
+      }      
+    // }
+    // catch (error) {
+    //   console.log('Что-то не так, Бро ' + error);
+    // }
+  }
+
+  const backURL = 'http://shop-roles.asmer.fs.a-level.com.ua/'
+
+  const gql = getGQL(backURL + 'graphql');
 
+const actionRootCats = () => 
+    actionPromise('rootCats', gql(`query {
+        CategoryFind(query: "[{\\"parent\\":null}]"){
+            _id name
+        }
+    }`))
+
+const actionCatById = (_id) => 
+    actionPromise('catById', gql(`query catById($q: String){
+        CategoryFindOne(query: $q){
+            _id name goods {
+                _id name price images {
+                    url
+                }
+            }
+        }
+    }`, {q: JSON.stringify([{_id}])}))
+
+//actionGoodById по аналогии
+
+store.dispatch(actionRootCats())
+store.subscribe(() => {
+    const {rootCats} = store.getState()
+    if (rootCats?.payload) {
+        aside.innerHTML = ''
+        for (const {_id, name} of rootCats?.payload) {
+            const link = document.createElement('a')
+            link.href = `#/category/${_id}`
+            link.innerText = name
+            aside.append(link)
+        }
+    }
+})
+
+// location.hash - адресная строка после решетки
+window.onhashchange = () => {
+    const [, route, _id] = location.hash.split('/')
+    const routes = {
+        category() {
+            store.dispatch(actionCatById(_id))
+        },
+        good() {
+             //задиспатчить actionGoodById
+        },
+    }
+    if (route in routes) {
+        routes[route]()
+    }
 }
 
-htmlTree()
-function htmlTree() {
 
-}
+// запускает обработчик при загрузке страницы
+window.onhashchange()
+
+
+store.subscribe(() => {
+    const {catById} = store.getState()
+    const [,route, _id] = location.hash.split('/')
+    if (catById?.payload && route === 'category'){
+        const {name} = catById.payload 
+        main.innerHTML = `<h1>${name}</h1> ТУТ ДОЛЖНЫ БЫТЬ ПОДКАТЕГОРИИ`
+        for (const {_id, name, price, images} of catById.payload.goods){
+            const card      = document.createElement('div')
+            card.innerHTML = `<h2>${name}</h2>
+                              <img src="${backURL}/${images[0].url}" />
+                              <strong>${price}</strong>
+                              ТУТ ДОЛЖНА БЫТЬ ССЫЛКА НА СТРАНИЦУ ТОВАРА
+                              ВИДА #/good/АЙДИ
+                                `
+            main.append(card)
+        }
+    }
+})
+
+
+store.subscribe(() => {
+    //ТУТ ДОЛЖНА БЫТЬ ПРОВЕРКА НА НАЛИЧИЕ goodById в редакс
+    //и проверка на то, что сейчас в адресной строке адрес ВИДА #/good/АЙДИ
+    //в таком случае очищаем main и рисуем информацию про товар с подробностями
+})
+
+// store.dispatch(actionPromise('delay1000', delay(1000)))
+// store.dispatch(actionPromise('delay2000', delay(2000)))     
+// store.dispatch(actionPromise('luke', fetch('https://swapi.dev/api/people/1/').then(res => res.json())))
+