// Store Class // Переделайте задание Store на синтаксис ES6-классов: // Добавьте нужные параметры в методы, их код, а так же геттер state, который работает аналогично // getState. Проверьте на ларьке, ведь объект, созданный из этого класса будет таким же, как и // объект, созданный createStore store_class: { class Store { #reducer(state, { type, name, amount, money }) { //объект action деструктуризируется на три переменных if (!state) { //начальная уборка в ларьке: return { products: { пиво: { amount: 100, price: 30, }, чипсы: { amount: 100, price: 25, }, сиги: { amount: 100, price: 35, } }, balance: { amount: 0 } } } if (type === 'buy') { //если тип action - КУПИТЬ, то: if (amount > state.products[name].amount) { alert('You have entered more quantity than is available'); return { ...state, } } if (money < amount * state.products[name].price) { alert('You don`t have enought money'); return { ...state, } } if (money > amount * state.products[name].price) { alert('You gave more money'); return { ...state, } } let updatedProducts = state.products let updatedField = updatedProducts[name]; updatedField.amount -= amount; let updatedBalance = state.balance; updatedBalance.amount += +money; return { ...state, //берем все что было из ассортимента products: updatedProducts, //и уменьшаем то, что покупается на количество balance: updatedBalance, } } return state; //если мы не поняли, что от нас просят в `action` - оставляем все как есть } #state = this.#reducer(undefined, {}); #cbs = []; constructor() { } get state() { return this.#state; } subscribe(cb) { this.#cbs.push(cb); return () => this.#cbs = this.#cbs.filter(c => c !== cb); } dispatch(action) { const newState = this.#reducer(this.#state, action); //пробуем запустить редьюсер if (newState !== this.#state) { //проверяем, смог ли редьюсер обработать action this.#state = newState; //если смог, то обновляем state for (let cb of this.#cbs) cb(); //и запускаем подписчиков } } } const store = new Store; const unsubscribe = store.subscribe(() => console.log(store.state)); setTimeout(unsubscribe, 10000); //отпишемся через 10 секунд, например const buyAction = (name, amount, money) => ({ type: 'buy', name, amount, money }); store.dispatch(buyAction('пиво', 1, 30)); store.dispatch(buyAction('чипсы', 4, 100)); } // Password Class // По аналогии, переделайте код задания Password в синтаксис классов ES6. Спрячьте все что можно в // #приватные свойства объектов класса. Проверьте на форме логина - ведь она использует Password password_class: { class Password { #passInputEl = document.createElement('input'); #passVisibilityCheckboxEl = document.createElement('input'); constructor(parent, open) { parent.append(this.#passInputEl); this.#passVisibilityCheckboxEl.type = 'checkbox'; this.#passVisibilityCheckboxEl.checked = open; parent.append(this.#passVisibilityCheckboxEl); if (open) { this.#passInputEl.type = 'text'; } else { this.#passInputEl.type = 'password'; } this.#passVisibilityCheckboxEl.addEventListener('change', (event) => { if (event.currentTarget.checked) { this.#passInputEl.type = 'text'; } else { this.#passInputEl.type = 'password'; } if (this.onOpenChange) { this.onOpenChange(event.currentTarget.checked); } }); this.#passInputEl.addEventListener('input', (event) => { this.onChange(); }); } set value(value) { this.#passInputEl.value = value; } get value() { return this.#passInputEl.value; } set open(value) { this.#passVisibilityCheckboxEl.checked = value; } get open() { return this.#passVisibilityCheckboxEl.checked; } } function Login(parent) { let loginInputEl = document.createElement('input'); loginInputEl.type = 'text'; parent.append(loginInputEl); loginInputEl.addEventListener('input', (event) => { this.onChange(); }); this.setValue = (value) => { loginInputEl.value = value; } this.getValue = () => { return loginInputEl.value; } } let form = document.createElement('form'); document.body.append(form); let loginLabel = document.createElement('label'); loginLabel.innerText = 'Login: '; form.append(loginLabel); let login = new Login(form); let passwordLabel = document.createElement('label'); passwordLabel.innerText = 'Password: '; form.append(passwordLabel); let password = new Password(form, false); let submit = document.createElement('button'); submit.innerText = 'Submit'; form.append(submit); function validateForm() { if (login.getValue() == '' || password.value == '') { submit.disabled = true; } else { submit.disabled = false; } } validateForm(); login.onChange = validateForm; password.onChange = validateForm; } // StoreThunk Class // Унаследуйте класс Store в новом классе StoreThunk. Новый класс должен перекрывать метод dispatch, проверять // тип переданного экшона и если это функция, запускать её, передав у неё this.dispatch и this.getState. Данное // условие написано тут. Учтите, что в thunk передаются функции dispatch и getState без объекта до точечки, а // эти методы в классе Store являются обычными функциями, склонными к потере this. Для прибития this намертво к // функции используйте метод bind. Посмотреть можно тут и тут Проверьте на модульном проекте store_thunk_class: { class Store { #reducer(state, { type, name, amount, money }) { //объект action деструктуризируется на три переменных if (!state) { //начальная уборка в ларьке: return { products: { пиво: { amount: 100, price: 30, }, чипсы: { amount: 100, price: 25, }, сиги: { amount: 100, price: 35, } }, balance: { amount: 0 } } } if (type === 'buy') { //если тип action - КУПИТЬ, то: if (amount > state.products[name].amount) { alert('You have entered more quantity than is available'); return { ...state, } } if (money < amount * state.products[name].price) { alert('You don`t have enought money'); return { ...state, } } if (money > amount * state.products[name].price) { alert('You gave more money'); return { ...state, } } let updatedProducts = state.products let updatedField = updatedProducts[name]; updatedField.amount -= amount; let updatedBalance = state.balance; updatedBalance.amount += +money; return { ...state, //берем все что было из ассортимента products: updatedProducts, //и уменьшаем то, что покупается на количество balance: updatedBalance, } } return state; //если мы не поняли, что от нас просят в `action` - оставляем все как есть } #state = this.#reducer(undefined, {}); #cbs = []; constructor() { } get state() { return this.#state; } subscribe(cb) { this.#cbs.push(cb); return () => this.#cbs = this.#cbs.filter(c => c !== cb); } dispatch(action) { const newState = this.#reducer(this.#state, action); //пробуем запустить редьюсер if (newState !== this.#state) { //проверяем, смог ли редьюсер обработать action this.#state = newState; //если смог, то обновляем state for (let cb of this.#cbs) cb(); //и запускаем подписчиков } } } class StoreThunk extends Store { constructor(...params) { super(...params); console.log(this) } dispatch(action) { if (typeof action === 'function') { //если action - не объект, а функция return action(this.dispatch.bind(this), this.state) //запускаем эту функцию и даем ей dispatch и getState для работы } super.dispatch(action); } } const storeTh = new StoreThunk; const unsubscribe = storeTh.subscribe(() => console.log(storeTh.state)); setTimeout(unsubscribe, 10000); const buyAction = (name, amount, money) => ({ type: 'buy', name, amount, money }); const actionЗатарится = () => dispatch => { dispatch(buyAction('пиво', 1, 30)); dispatch(buyAction('чипсы', 5, 125)); } storeTh.dispatch(actionЗатарится()); } // RGB Class // Напишите класс RGB, приватными свойствами которого являются три числа #r, #g, #b. Класс должен обладать // следующими геттерами и сеттерами: // r. Служит для чтения/изменения #r // g. Служит для чтения/изменения #g // b. Служит для чтения/изменения #b // rgb. Служит для чтения/изменения всех трех цветовых каналов. Используется строковой CSS синтаксис типа // rgb(128,255,64) // hex. Служит для чтения/изменения всех трех цветовых каналов. Используется строковой CSS синтаксис типа // #RRGGBB rgb_class: { class CheckCondition extends Error { constructor(msg) { super(msg); } } class RGB { #r; #g; #b; get r() { return this.#r; } set r(value) { if (typeof value == 'number' && value >= 0 && value <= 255) { this.#r = value; } else { throw new RangeError(); } } get g() { return this.#g; } set g(value) { if (typeof value == 'number' && value >= 0 && value <= 255) { this.#g = value; } else { throw new RangeError(); } } get b() { return this.#b; } set b(value) { if (typeof value == 'number' && value >= 0 && value <= 255) { this.#b = value; } else { throw new RangeError(); } } get rgb() { return `rgb(${this.#r}, ${this.#g}, ${this.#b})`; } set rgb(value) { let regex = /^(rgb)?\(?([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])\)?)$/; if (regex.test(value)) { let valueArr = value.match(regex); this.#r = parseInt(valueArr[2]); this.#g = parseInt(valueArr[4]); this.#b = parseInt(valueArr[6]); } else { throw new SyntaxError(); } } get hex() { let color = ((this.#r >= 0 && this.#r < 16) ? ('0' + Math.floor(this.#r).toString(16)) : Math.floor(this.#r).toString(16)) + ((this.#g >= 0 && this.#g < 16) ? ('0' + Math.floor(this.#g).toString(16)) : Math.floor(this.#g).toString(16)) + ((this.#b >= 0 && this.#b < 16) ? ('0' + Math.floor(this.#b).toString(16)) : Math.floor(this.#b).toString(16)); return '#' + color; } set hex(value) { let regex = /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/; if (regex.test(value)) { let valueArr = value.match(regex); this.#r = parseInt(valueArr[1], 16); this.#g = parseInt(valueArr[2], 16); this.#b = parseInt(valueArr[3], 16); } else { throw new SyntaxError(); } } } const rgb = new RGB; rgb.r = 15; rgb.g = 128; rgb.b = 192; console.log(rgb.hex); console.log(rgb.rgb); console.log(rgb.r, rgb.g, rgb.b); rgb.hex = '#203040'; console.log(rgb.rgb); console.log(rgb.hex); rgb.rgb = 'rgb(100, 90, 50)'; console.log(rgb.r, rgb.g, rgb.b); } // 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 должен проверять диапазон и выбрасывать исключение в случае несоответствия диапазона. rgba_class: { class CheckCondition extends Error { constructor(msg) { super(msg); } } class RGB { #r; #g; #b; constructor() { } get r() { return this.#r; } set r(value) { if (typeof value == 'number' && value >= 0 && value <= 255) { this.#r = value; } else { throw new RangeError(); } } get g() { return this.#g; } set g(value) { if (typeof value == 'number' && value >= 0 && value <= 255) { this.#g = value; } else { throw new RangeError(); } } get b() { return this.#b; } set b(value) { if (typeof value == 'number' && value >= 0 && value <= 255) { this.#b = value; } else { throw new RangeError(); } } get rgb() { return `rgb(${this.#r}, ${this.#g}, ${this.#b})`; } set rgb(value) { let regex = /^(rgb)?\(?([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])\)?)$/; if (regex.test(value)) { let valueArr = value.match(regex); this.#r = parseInt(valueArr[2]); this.#g = parseInt(valueArr[4]); this.#b = parseInt(valueArr[6]); } else { throw new SyntaxError(); } } get hex() { let color = ((this.#r >= 0 && this.#r < 16) ? ('0' + Math.floor(this.#r).toString(16)) : Math.floor(this.#r).toString(16)) + ((this.#g >= 0 && this.#g < 16) ? ('0' + Math.floor(this.#g).toString(16)) : Math.floor(this.#g).toString(16)) + ((this.#b >= 0 && this.#b < 16) ? ('0' + Math.floor(this.#b).toString(16)) : Math.floor(this.#b).toString(16)); return '#' + color; } set hex(value) { let regex = /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/; if (regex.test(value)) { let valueArr = value.match(regex); this.#r = parseInt(valueArr[1], 16); this.#g = parseInt(valueArr[2], 16); this.#b = parseInt(valueArr[3], 16); } else { throw new SyntaxError(); } } } class RGBA extends RGB { constructor(...params) { super(...params); } #a; get a() { return this.#a; } set a(value) { if (typeof value == 'number' && value >= 0 && value <= 1) { this.#a = value; } } get rgba() { return `rbda(${super.r}, ${super.g}, ${super.b}, ${this.#a})` } set rgba(value) { let regex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/; if (regex.test(value)) { let valueArr = value.match(regex); super.r = +valueArr[1]; super.g = +valueArr[2]; super.b = +valueArr[3]; this.#a = valueArr[4]; } else { throw new SyntaxError(); } } get hex() { if (this.#a) { return super.hex + Math.ceil(this.#a * 255).toString(16); } else { return super.hex; } } set hex(value) { if (value.length == 9) { let opacity = value.slice(-2); this.#a = +(parseInt(opacity, 16) / 255).toFixed(2); super.hex = value.slice(0, 7); } else if (value.length == 7) { this.#a = null; super.hex = value; } else { throw new SyntaxError(); } } set color(value) { if (value.slice(0, 1) == '#') { this.hex = value; } else if (value.slice(0, 4) == 'rgb(') { super.rgb = value; } else if (value.slice(0, 4) == 'rgba') { this.rgba = value; } else { throw new SyntaxError(); } } } const rgba = new RGBA(); rgba.hex = '#80808080'; console.log(rgba.hex) console.log(rgba.a); //0.5 rgba.rgba = 'rgba(128, 128, 128, 0.5)'; console.log(rgba.rgba); //rgba(128,128,128,0.5) rgba.r = 192; rgba.a = 0.25; console.log(rgba.hex); //#C0808040 console.log(rgba.a); rgba.color = 'rgba(1,2,3,0.70)'; rgba.b *= 10; console.log(rgba.hex); //#01021EB3 rgba.color = '#e1b9c9'; console.log(rgba.hex); }