/*Store Class Переделайте задание Store на синтаксис ES6-классов: Добавьте нужные параметры в методы, их код, а так же геттер state, который работает аналогично getState. Проверьте на ларьке, ведь объект, созданный из этого класса будет таким же, как и объект, созданный createStore */ { class Store { #reducer; #state; #cbs = [] constructor(reducer) { this.#reducer = reducer this.#state = reducer(undefined, {}) } get getState() { return this.#state } subscribe(cb) { this.#cbs.push(cb) return () => this.#cbs = this.#cbs.filter(c => c !== cb) } dispatch(action) { if (typeof action === 'function') { //если action - не объект, а функция return action(this.dispatch, this.getState) //запускаем эту функцию и даем ей dispatch и getState для работы } const newState = this.#reducer(this.#state, action) //пробуем запустить редьюсер if (newState !== this.#state) { this.#state = newState for (let cb of this.#cbs) cb() } } } function reducer(state, { type, productName, productQuantity, summ }) { //объект action деструктуризируется на три переменных if (!state) { //начальная уборка в ларьке: return { пиво: { quantity: 100, price: 30, }, чипсы: { quantity: 100, price: 52, }, сиги: { quantity: 100, price: 89, }, касса: 0 } } if (type === 'КУПИТЬ' && productQuantity <= state[productName].quantity && summ >= state[productName].price * productQuantity) { //если тип action - КУПИТЬ, то: return { ...state, //берем все что было из ассортимента [productName]: { quantity: state[productName].quantity - productQuantity, price: state[productName].price }, касса: state.касса + state[productName].price * productQuantity //и уменьшаем то, что покупается на количество } } return state //если мы не поняли, что от нас просят в `action` - оставляем все как есть } function actionCreator(type, productName, productQuantity, summ) { return { type, productName, productQuantity, summ } } const store = new Store(reducer) myState = store.getState const showcase = document.createElement('section') document.body.append(showcase) showcase.classList.add('showcase') const orderSection = document.createElement('section') document.body.append(orderSection) orderSection.classList.add('orderSection') const orderSelectProd = document.createElement('select') orderSection.append(orderSelectProd) const orderInputQuantity = document.createElement('input') orderInputQuantity.type = 'number' orderInputQuantity.min = '0' orderSection.append(orderInputQuantity) const orderInputQuantityName = document.createElement('div') orderInputQuantity.before(orderInputQuantityName) orderInputQuantityName.innerText = 'количество товара:' const orderSendMoney = document.createElement('input') orderSendMoney.type = 'number' orderSendMoney.min = '0' orderSection.append(orderSendMoney) const orderSendMoneyName = document.createElement('div') orderSendMoney.before(orderSendMoneyName) orderSendMoneyName.innerText = 'сумма:' const orderBuyButton = document.createElement('input') orderBuyButton.type = 'button' orderBuyButton.value = 'КУПИТЬ' orderSection.append(orderBuyButton) orderBuyButton.onclick = () => { store.dispatch(actionCreator(orderBuyButton.value, orderSelectProd.value, +orderInputQuantity.value, +orderSendMoney.value)) } for (const elemProduct in store.getState) { if (elemProduct === 'касса') continue const productCard = document.createElement('div') const productName = document.createElement('h2') const productPrice = document.createElement('div') const productQuantity = document.createElement('div') productCard.append(productName) productCard.append(productQuantity) productCard.append(productPrice) showcase.append(productCard) productCard.classList.add('productCard') productName.classList.add('productName') productPrice.classList.add('productPrice') productQuantity.classList.add('productQuantity') productName.innerText = elemProduct productPrice.innerText = store.getState[elemProduct].price + ' грн' productQuantity.innerText = store.getState[elemProduct].quantity + ' шт\nв наличии' const selectProdOption = document.createElement('option') selectProdOption.value = selectProdOption.innerText = elemProduct orderSelectProd.append(selectProdOption) const productPriceUnsubscribe = store.subscribe(() => { productPrice.innerText = store.getState[elemProduct].price + ' грн' }) const productQuantityUnsubscribe = store.subscribe(() => { productQuantity.innerText = store.getState[elemProduct].quantity + ' шт\nв наличии' }) } } /*Password Class По аналогии, переделайте код задания Password в синтаксис классов ES6. Спрячьте все что можно в #приватные свойства объектов класса. Проверьте на форме логина - ведь она использует Password*/ { class Password { #inputPass = document.createElement('input') #checkboxPass = document.createElement('input') #open = '' constructor(parent, openTrueOrFalse) { parent.append(this.#inputPass) this.#checkboxPass.type = 'checkbox' parent.append(this.#checkboxPass) if (openTrueOrFalse === true) { this.#open = true this.#checkboxPass.checked = true this.#inputPass.type = "text" } else { this.#open = false this.#checkboxPass.checked = false this.#inputPass.type = "password" } this.#inputPass.oninput = () => { if (typeof this.onChange === 'function') this.onChange(this.#inputPass.value) } this.#checkboxPass.oninput = () => this.open = this.#checkboxPass.checked } set value(value) { this.#inputPass.value = value if (typeof this.onChange === 'function') this.onChange(this.#inputPass.value) // запускается по событию oninput в поле ввода, передает текст в колбэк } //задает зн get value() { return this.#inputPass.value } set open(openTrueOrFalse) { if (openTrueOrFalse === true && this.#open === false) { this.#open = true this.#checkboxPass.checked = true this.#inputPass.type = "text" if (typeof this.onOpenChange === 'function') this.onOpenChange(this.#open) } else if (this.#open === true) { this.#open = false this.#checkboxPass.checked = false this.#inputPass.type = "password" if (typeof this.onOpenChange === 'function') this.onOpenChange(this.#open) } } get open() { return this.#checkboxPass.checked } } let pass = new Password(document.body, false) pass.onChange = function (data) { console.log(data) } pass.onOpenChange = function (open) { console.log(open) } pass.value = 'qwerty' console.log(pass.value) pass.open = true console.log(pass.open) console.log(pass) } /*StoreThunk Class Унаследуйте класс Store в новом классе StoreThunk. Новый класс должен перекрывать метод dispatch, проверять тип переданного экшона и если это функция, запускать её, передав у неё this.dispatch и this.getState. Данное условие написано тут. Учтите, что в thunk передаются функции dispatch и getState без объекта до точечки, а эти методы в классе Store являются обычными функциями, склонными к потере this. Для прибития this намертво к функции используйте метод bind. Посмотреть можно тут и тут Проверьте на модульном проекте */ { class Store { #reducer; #state; #cbs = [] constructor(reducer) { this.#reducer = reducer this.#state = reducer(undefined, {}) } getState() { return this.#state } get state() { return this.#state } subscribe(cb) { this.#cbs.push(cb) return () => this.#cbs = this.#cbs.filter(c => c !== cb) } dispatch(action) { // if (typeof action === 'function') { //если action - не объект, а функция // return action(this.dispatch, this.getState) //запускаем эту функцию и даем ей dispatch и getState для работы // } const newState = this.#reducer(this.#state, action) //пробуем запустить редьюсер if (newState !== this.#state) { this.#state = newState for (let cb of this.#cbs) cb() } } } function promiseReducer(state = {}, { promiseName, type, status, payload, error }) { if (type === 'PROMISE') { return { ...state, [promiseName]: { status, payload, error } } } return state } const actionPending = promiseName => ({ promiseName, type: 'PROMISE', status: 'PENDING' }) const actionFulfilled = (promiseName, payload) => ({ promiseName, type: 'PROMISE', status: 'FULFILLED', payload }) const actionRejected = (promiseName, error) => ({ promiseName, type: 'PROMISE', status: 'REJECTED', error }) const actionPromise = (promiseName, promise) => async dispatch => { dispatch(actionPending(promiseName)) //сигнализируем redux, что промис начался try { const payload = await promise //ожидаем промиса dispatch(actionFulfilled(promiseName, payload)) //сигнализируем redux, что промис успешно выполнен return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса } catch (error) { dispatch(actionRejected(promiseName, error)) //в случае ошибки - сигнализируем redux, что промис несложился } } class StoreThunk extends Store { constructor(...params) { super(...params) //вызов конструктора предка для создания нового объекта console.log(this) } dispatch(action) { if (typeof action === 'function') { //если action - не объект, а функция return action(super.dispatch.bind(this), super.getState.bind(this)) //запускаем эту функцию и даем ей dispatch и getState для работы } return super.dispatch(action) } } let store = new StoreThunk(promiseReducer) store.subscribe(() => console.log(store.getState)) store.dispatch(actionPromise('tatooine', fetch("https://swapi.dev/api/planets/1").then(res => res.json()))) } /*Напишите класс RGB, приватными свойствами которого являются три числа #r, #g, #b. Класс должен обладать следующими геттерами и сеттерами: r. Служит для чтения/изменения #r g. Служит для чтения/изменения #g b. Служит для чтения/изменения #b rgb. Служит для чтения/изменения всех трех цветовых каналов. Используется строковой CSS синтаксис типа rgb(128,255,64) hex. Служит для чтения/изменения всех трех цветовых каналов. Используется строковой CSS синтаксис типа #RRGGBB Для проверки строк в сеттерах rgb и hex используйте следующие регулярные выражения: hex: /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/. Так же это регулярное выражение, при использовании метода match даст вам все три цветовых канала по отдельности в отдельных ячейках результирующего массива: console.log('#FFAA08'.match(/^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/)) rgb: возьмите отсюда В случае, если match возвращает null, выбрасывайте исключение SyntaxError. Также, в сеттерах r, g, b, проверяйте тип и диапазон (он должен быть от 0 до 255) и выбрасывайте исключение RangeError.*/ class RGB { #r #g #b #regExpRgb = /^(rgb||rgba)?\(?([01]?\d\d?|2[0-4]\d|25[0-5])(\W+)([01]?\d\d?|2[0-4]\d|25[0-5])\W+(([01]?\d\d?|2[0-4]\d|25[0-5])\)?)$/ #regExpHex = /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/ get r() { return this.#r } get g() { return this.#g } get b() { return this.#b } set r(value) { if (typeof value === 'string' && +value <= 255) { this.#r = +value } else if (typeof value === 'number' && value <= 255) { this.#r = value } else throw RangeError("ожидается число до 255 включительно") } set g(value) { if (typeof value === 'string' && +value <= 255) { this.#g = +value } else if (typeof value === 'number' && value <= 255) { this.#g = value } else throw RangeError("ожидается число до 255 включительно") } set b(value) { if (typeof value === 'string' && +value <= 255) { this.#b = +value } else if (typeof value === 'number' && value <= 255) { this.#b = value } else throw RangeError("ожидается число до 255 включительно") } get rgb() { return `rgb(${this.#r},${this.#g},${this.#b})` } set rgb(rgbColor) { if (this.#regExpRgb.test(rgbColor)) { const arrRgbColor = rgbColor.match(this.#regExpRgb) this.#r = +arrRgbColor[2] this.#g = +arrRgbColor[4] this.#b = +arrRgbColor[6] return this.rgb } else { throw new SyntaxError } } get hex() { const hex = '#' + (this.#r > 15 ? this.#r.toString(16) : "0" + this.#r.toString(16)) + (this.#g > 15 ? this.#g.toString(16) : "0" + this.#g.toString(16)) + (this.#b > 15 ? this.#b.toString(16) : "0" + this.#b.toString(16)); return hex } set hex(hexColor) { if (this.#regExpHex.test(hexColor)) { const arrHexColor = hexColor.match(this.#regExpHex) this.#r = +(parseInt(arrHexColor[1], 16).toString(10)) this.#g = +(parseInt(arrHexColor[2], 16).toString(10)) this.#b = +(parseInt(arrHexColor[3], 16).toString(10)) return this.hex } else { throw new SyntaxError } } } const rgb = new RGB rgb.r = 15 rgb.g = 128 rgb.b = 192 console.log(rgb.hex) //#0F80C0 console.log(rgb.rgb) //rgb(15,128,192) rgb.hex = '#203040' console.log(rgb.rgb) //rgb(32, 48, 64) rgb.rgb = 'rgb(100, 90, 50)' console.log(rgb.r, rgb.g, rgb.b) //100, 90, 50 //rgb.hex = 'дичь' //SyntaxError //rgb.r = 1000 //RangeError /* RGBA Class Создайте класс-наследник класса RGB под названием RGBA. В нем должно добавиться новое приватное поле #a, содержащее значение прозрачности в диапазоне от 0 до 1. Создайте сеттер и геттер a. Перекройте сеттер и геттер hex, что бы в классе-наследнике работал синтаксис #RRGGBBAA. Учтите, что сеттер и геттер предка могут вам помочь. Также, сеттер hex должен поддерживать синтаксис #RRGGBB без прозрачности. Добавьте сеттер и геттер rgba, которые работают с CSS-синтаксисом вида rgba(128,255,64, 0.5). Добавьте сеттер color, в который можно присваивать любой из синтаксисов CSS - #RRGGBB, #RRGGBBAA, rgb(1,2,3) и rgba(1,2,3,0.5). Сеттер a должен проверять диапазон и выбрасывать исключение в случае несоответствия диапазона. Для приведения целочисленного значения прозрачности в шестнадцатиричной нотации к диапазону 0..1, поделите на 255.*/ class RGBA extends RGB { #a = 1 #regExpHexA = /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/ #regExpRGBA = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/ set a(value) { if (typeof value === 'string' && 0 <= +value && +value <= 1) { this.a = +value } else if (typeof value === 'number' && 0 <= +value && +value <= 1) { this.#a = value } else throw RangeError("ожидается число от 0 до 1 включительно") } get a() { return this.#a } set hex(hexColorA) { if (this.#regExpHexA.test(hexColorA)) { const arrHexColorA = hexColorA.match(this.#regExpHexA) this.#a = +((+(parseInt(arrHexColorA[4], 16).toString(10))) / 255).toFixed(2) super.hex = hexColorA.slice(0, -2) } else super.hex = hexColorA } get hex() { if (this.#a) { const hex = super.hex + (+(this.#a * 255).toFixed()).toString(16) return hex } else return super.hex } get rgba() { let rgb = (super.rgb).slice(4, -1) return 'rgba(' + rgb + ',' + this.#a + ')' } set rgba(value) { if (this.#regExpRGBA.test(value)) { const arrRgbaColor = value.match(this.#regExpRGBA) if (0 <= arrRgbaColor[4] && arrRgbaColor[4] <= 1) { this.#a = arrRgbaColor.pop() const [, ...newArr] = arrRgbaColor super.rgb = newArr.join(',') return this.rgb } } else { throw new SyntaxError } } set color(value) { if (this.#regExpRGBA.test(value)) { const arrRgbaColor = value.match(this.#regExpRGBA) if (0 <= arrRgbaColor[4] && arrRgbaColor[4] <= 1) { this.#a = arrRgbaColor.pop() const [, ...newArr] = arrRgbaColor super.rgb = newArr.join(',') return this.rgb } } else if (this.#regExpHexA.test(value)) { const arrHexColorA = value.match(this.#regExpHexA) this.#a = +((+(parseInt(arrHexColorA[4], 16).toString(10))) / 255).toFixed(2) super.hex = value.slice(0, -2) } else super.hex = value } } let aaa = new RGBA const rgba = new RGBA rgba.hex = '#80808080' console.log(rgba.a) //0.5 console.log(rgba.rgba) //rgba(128,128,128,0.5) rgba.r = 192 rgba.a = 0.25 console.log(rgba.hex) //#C0808040 rgba.color = 'rgba(1,2,3,0.70)' rgba.b *= 10 console.log(rgba.hex) //#01021EB3