index.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. function createStore(reducer) {
  2. let state = reducer(undefined, {}); //стартовая инициализация состояния, запуск редьюсера со state === undefined
  3. let cbs = []; //массив подписчиков
  4. const getState = () => state; //функция, возвращающая переменную из замыкания
  5. const subscribe = (cb) => (
  6. cbs.push(cb), //запоминаем подписчиков в массиве
  7. () => (cbs = cbs.filter((c) => c !== cb))
  8. ); //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
  9. const dispatch = (action) => {
  10. if (typeof action === "function") {
  11. //если action - не объект, а функция
  12. return action(dispatch, getState); //запускаем эту функцию и даем ей dispatch и getState для работы
  13. }
  14. const newState = reducer(state, action); //пробуем запустить редьюсер
  15. if (newState !== state) {
  16. //проверяем, смог ли редьюсер обработать action
  17. state = newState; //если смог, то обновляем state
  18. for (let cb of cbs) cb(); //и запускаем подписчиков
  19. }
  20. };
  21. return {
  22. getState, //добавление функции getState в результирующий объект
  23. dispatch,
  24. subscribe, //добавление subscribe в объект
  25. };
  26. }
  27. function promiseReducer(state = {}, { type, status, payload, error, name }) {
  28. if (type === "PROMISE") {
  29. return {
  30. ...state,
  31. [name]: { status, payload, error },
  32. };
  33. }
  34. return state;
  35. }
  36. const actionPending = (name) => ({ type: "PROMISE", status: "PENDING", name });
  37. const actionResolved = (name, payload) => ({
  38. type: "PROMISE",
  39. status: "RESOLVED",
  40. name,
  41. payload,
  42. });
  43. const actionRejected = (name, error) => ({
  44. type: "PROMISE",
  45. status: "REJECTED",
  46. name,
  47. error,
  48. });
  49. const actionPromise = (name, promise) => async (dispatch) => {
  50. dispatch(actionPending(name));
  51. try {
  52. let data = await promise;
  53. dispatch(actionResolved(name, data));
  54. return data;
  55. } catch (error) {
  56. dispatch(actionRejected(name, error));
  57. }
  58. };
  59. const store = createStore(promiseReducer);
  60. store.subscribe(() => console.log(store.getState()));
  61. const delay = (ms) => new Promise((ok) => setTimeout(() => ok(ms), ms));
  62. const getGQL =
  63. (url) =>
  64. async (query, variables = {}) => {
  65. let obj = await fetch(url, {
  66. method: "POST",
  67. headers: {
  68. "Content-Type": "application/json",
  69. ...(localStorage.authToken
  70. ? { Authorization: "Bearer " + localStorage.authToken }
  71. : {}),
  72. },
  73. body: JSON.stringify({ query, variables }),
  74. });
  75. let a = await obj.json();
  76. if (!a.data && a.errors) throw new Error(JSON.stringify(a.errors));
  77. return a.data[Object.keys(a.data)[0]];
  78. };
  79. const backURL = "http://shop-roles.asmer.fs.a-level.com.ua";
  80. const gql = getGQL(backURL + "/graphql");
  81. const actionRootCats = () =>
  82. actionPromise(
  83. "rootCats",
  84. gql(`query {
  85. CategoryFind(query: "[{\\"parent\\":null}]"){
  86. _id name
  87. }
  88. }`)
  89. );
  90. const actionCatById = (_id) =>
  91. actionPromise(
  92. "catById",
  93. gql(
  94. `query catById($q: String){
  95. CategoryFindOne(query: $q){
  96. _id name,
  97. goods{
  98. _id name price images {
  99. url
  100. }
  101. },
  102. subCategories{
  103. name, subCategories{
  104. name
  105. }
  106. }
  107. }
  108. }`,
  109. { q: JSON.stringify([{ _id }]) }
  110. )
  111. );
  112. const actionGoodById = (_id) =>
  113. actionPromise(
  114. "goodById",
  115. gql(
  116. `query goodById($q: String){
  117. GoodFindOne(query: $q){
  118. _id name description price images{
  119. url
  120. }
  121. }
  122. }`,
  123. { q: JSON.stringify([{ _id }]) }
  124. )
  125. );
  126. store.dispatch(actionRootCats());
  127. store.dispatch(actionGoodById());
  128. store.subscribe(() => {
  129. const { rootCats } = store.getState();
  130. if (rootCats?.payload) {
  131. aside.innerHTML = "";
  132. for (const { _id, name } of rootCats?.payload) {
  133. const link = document.createElement("a");
  134. link.href = `#/category/${_id}`;
  135. link.innerText = name;
  136. aside.append(link);
  137. }
  138. }
  139. });
  140. window.onhashchange = () => {
  141. const [, route, _id] = location.hash.split("/");
  142. const routes = {
  143. category() {
  144. store.dispatch(actionCatById(_id));
  145. },
  146. good() {
  147. store.dispatch(actionGoodById(_id));
  148. },
  149. };
  150. if (route in routes) routes[route]();
  151. };
  152. window.onhashchange();
  153. store.subscribe(() => {
  154. const { catById } = store.getState();
  155. const [, route, _id] = location.hash.split("/");
  156. if (catById?.payload && route === "category") {
  157. const { name } = catById.payload;
  158. main.innerHTML = `<h1>${name}</h1>`;
  159. if (catById.payload?.subCategories) {
  160. for (const { _id, name } of catById.payload.subCategories) {
  161. const link = document.createElement("a");
  162. link.href = `#/category/${_id}`;
  163. link.innerText = name;
  164. main.append(link);
  165. main.innerHTML += "<br/>";
  166. }
  167. }
  168. if (catById.payload?.goods) {
  169. for (const { _id, name, price, images } of catById.payload.goods) {
  170. const card = document.createElement("div");
  171. const link = document.createElement("a");
  172. card.innerHTML = `<h2>${name}</h2>
  173. <img src="${backURL}/${images[0].url}" /><br/>
  174. <strong>${price}$</strong><br/>
  175. `;
  176. link.innerText = name;
  177. link.href = `#/good/${_id}`;
  178. card.appendChild(link);
  179. main.append(card);
  180. }
  181. }
  182. }
  183. });
  184. store.subscribe(() => {
  185. const { goodById } = store.getState();
  186. const [, route, _id] = location.hash.split("/");
  187. if (goodById?.payload && route === "good") {
  188. main.innerHTML = "";
  189. const { name, description, price, images } = goodById.payload;
  190. main.innerHTML = `
  191. <div>
  192. <div>
  193. <img src="${backURL}/${images[0].url}" width="400" height="400"/>
  194. </div>
  195. <div>
  196. <h2>${name}</h2>
  197. <p><strong>${price}$</strong></p>
  198. <p><span>${description}</span></p>
  199. </div>
  200. </div>`;
  201. }
  202. });
  203. //store.dispatch(actionPromise('', delay(1000)))
  204. //store.dispatch(actionPromise('delay2000', delay(2000)))
  205. //store.dispatch(actionPromise('luke', fetch('https://swapi.dev/api/people/1/').then(res => res.json())))