Skip to content

Commit

Permalink
♻️ refactor: improve readability & increase consistency
Browse files Browse the repository at this point in the history
  • Loading branch information
ShayanTheNerd committed Mar 22, 2024
1 parent 59eeb9d commit 8a73f49
Show file tree
Hide file tree
Showing 24 changed files with 332 additions and 289 deletions.
4 changes: 4 additions & 0 deletions public/icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 0 additions & 16 deletions src/assets/ts/app.ts

This file was deleted.

13 changes: 13 additions & 0 deletions src/assets/ts/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const IMG_DONWLOAD_TIMEOUT_MS = 1000;
const ACTIVE_FILTER_CLASS = 'btn--filter--active';
const ACTIVE_FILTER_VALUE_CSS_VAR = '--_value';
const SPLIT_FROM_LAST_DOT_REGEXP = /\.(?=[^.]+$)/;
const SPIN_MODES = ['Rotate Right', 'Rotate Left', 'Vertical Flip', 'Horizontal Flip'] as const;

export {
SPIN_MODES as spinModes,
ACTIVE_FILTER_CLASS as activeFilterClass,
IMG_DONWLOAD_TIMEOUT_MS as imgDownloadTimeoutMS,
SPLIT_FROM_LAST_DOT_REGEXP as splitFromLastDotRegExp,
ACTIVE_FILTER_VALUE_CSS_VAR as activeFilterValueCSSVar,
};
20 changes: 20 additions & 0 deletions src/assets/ts/domElements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { activeFilterClass } from '@ts/constants.ts';

const DOMElements = Object.freeze({
editOptionsContainer: document.getElementById('edit_options_container') as HTMLFieldSetElement,
filtersContainer: document.getElementById('filters_container') as HTMLDivElement,
activeFilterName: document.getElementById('active_filter_name') as HTMLSpanElement,
activeFilterValue: document.getElementById('active_filter_value') as HTMLSpanElement,
activeFilterRangeInput: document.getElementById('active_filter_range_input') as HTMLInputElement,
spinsContainer: document.getElementById('spins_container') as HTMLDivElement,
resetFiltersBtn: document.getElementById('reset_filters_btn') as HTMLButtonElement,
imgDropZone: document.getElementById('img_drop_zone') as HTMLElement,
imgSelectInput: document.getElementById('img_select_input') as HTMLInputElement,
imgSaveAnchor: document.getElementById('img_save_anchor') as HTMLAnchorElement,
});

/* prettier-ignore */
const getActiveFilterBtn = () => DOMElements.filtersContainer.querySelector(`.${activeFilterClass}`) as HTMLButtonElement;
const getImgElement = () => document.getElementById('img') as HTMLImageElement;

export { DOMElements, getActiveFilterBtn, getImgElement };
200 changes: 84 additions & 116 deletions src/assets/ts/imgStore.ts
Original file line number Diff line number Diff line change
@@ -1,178 +1,146 @@
interface ImgStore {
state: {
name: null | string;
extension: null | string;
activeFilterIndex: number;
rotationDeg: number;
verticalFlip: number;
horizontalFlip: number;
CSSFilters: string;
filters: Filter[];
};
_defaultState: null | ImgStore;
_cloneState: Function;
updateCSSFilters: Function;
reset: Function;
title: string;
activeFilter: Filter;
_hasFilter: boolean;
_isRotated: boolean;
isLandscape: boolean;
_isFlipped: boolean;
isEdited: boolean;
activeFilterIndex: string;
updateFilterValue: number;
newNameAndExtension: { name: string; extension: string };
spin: string;
}
interface Filter {
name: string;
max: number;
value: number;
unit: string;
}
import type { FilterName, Filter, State, SpinMode } from '@ts/types.d.ts';
import { deepCopy } from '@ts/utils.ts';
import { splitFromLastDotRegExp } from '@ts/constants.ts';

const deepCopy: Function = (obj: object) => structuredClone(obj) || JSON.parse(JSON.stringify(obj));

const imgStore: ImgStore = {
state: {
class ImgStore {
state: State = {
name: null,
extension: null,
activeFilterIndex: 0,
rotationDeg: 0,
verticalFlip: 1,
horizontalFlip: 1,
CSSFilters: '',
filters: [
{
name: 'brightness',
max: 200,
maxValue: 200,
value: 100,
unit: '%',
isActive: true,
},
{
name: 'grayscale',
max: 100,
maxValue: 100,
value: 0,
unit: '%',
isActive: false,
},
{
name: 'blur',
max: 10,
maxValue: 10,
value: 0,
unit: 'px',
isActive: false,
},
{
name: 'hue-rotation',
max: 100,
name: 'hue-rotate',
maxValue: 100,
value: 0,
unit: 'deg',
isActive: false,
},
{
name: 'opacity',
max: 100,
maxValue: 100,
value: 100,
unit: '%',
isActive: false,
},
{
name: 'contrast',
max: 200,
maxValue: 200,
value: 100,
unit: '%',
isActive: false,
},
{
name: 'saturation',
max: 200,
name: 'saturate',
maxValue: 200,
value: 100,
unit: '%',
isActive: false,
},
{
name: 'sepia',
max: 100,
maxValue: 100,
value: 0,
unit: '%',
isActive: false,
},
],
},
_defaultState: null,
};
#defaultState = deepCopy(this.state);

/* Methods */
_cloneState() {
this._defaultState = deepCopy(this.state);
},
/*** Methods ***/
updateCSSFilters() {
this.state.CSSFilters = this.state.filters.reduce((acc: string, { name, value, unit }) => {
if (name === 'blur') value /= 2.5;
if (name === 'saturation') name = 'saturate';
if (name === 'hue-rotation') name = 'hue-rotate';
this.state.CSSFilters = this.state.filters.reduce((acc, { name, value, unit }) => {
if (name === 'blur') value /= 2.5; // Visually reduce blur effect to improve UX
return `${acc}${name}(${value}${unit}) `;
}, '');
},
}
reset() {
const { name, extension, rotationDeg } = this.state;
const normalizedRotationDeg: number = this._isRotated ? Math.round(rotationDeg / 360) * 360 : rotationDeg;
this.state = { ...deepCopy(this._defaultState), name, extension, rotationDeg: normalizedRotationDeg };
},

/* Getters */
get title() {
const { name, extension } = this.state;
return `${name}.${extension}`;
},
get activeFilter() {
const { filters, activeFilterIndex } = this.state;
return filters[activeFilterIndex];
},
get _hasFilter() {
const isFilterValueChanged = (filter: Filter, index: number) => {
const defaultFilterValue = this._defaultState.filters[index].value;
return filter.value !== defaultFilterValue;
const normalizedRotationDeg = this.#isRotated ? Math.round(rotationDeg / 360) * 360 : rotationDeg;
this.state = {
...deepCopy(this.#defaultState),
name,
extension,
rotationDeg: normalizedRotationDeg,
};
}

return this.state.filters.some(isFilterValueChanged);
},
get _isRotated() {
/*** Getters ***/
get #isRotated() {
return this.state.rotationDeg % 360 !== 0;
},
get isLandscape() {
return this.state.rotationDeg % 180 !== 0;
},
get _isFlipped() {
}
get #isFlipped() {
const { verticalFlip, horizontalFlip } = this.state;
const { verticalFlip: defaultVerticalFlip, horizontalFlip: defaultHorizontalFlip } = this._defaultState;
const { verticalFlip: defaultVerticalFlip, horizontalFlip: defaultHorizontalFlip } = this.#defaultState;
return verticalFlip !== defaultVerticalFlip || horizontalFlip !== defaultHorizontalFlip;
},
}
get #hasFilter() {
const filterValueIsChanged = (filter: Filter, index: number) => {
const defaultFilterValue = this.#defaultState.filters[index]!.value;
return filter.value !== defaultFilterValue;
};

return this.state.filters.some(filterValueIsChanged);
}
get isEdited() {
return this._hasFilter || this._isRotated || this._isFlipped;
},
return this.#isRotated || this.#isFlipped || this.#hasFilter;
}
get isLandscape() {
return this.state.rotationDeg % 180 !== 0;
}
get title() {
const { name, extension } = this.state;
return `${name}.${extension}`;
}
get activeFilter(): Filter {
return this.state.filters.find(filter => filter.isActive) as Filter;
}

/* Setters */
set activeFilterIndex(filterName: string) {
this.state.activeFilterIndex = this.state.filters.findIndex((filter: Filter) => filter.name === filterName);
},
set updateFilterValue(newValue: number) {
/* @ts-ignore */
return (this.activeFilter.value = newValue);
},
set newNameAndExtension({ name, extension }) {
Object.assign(this.state, { name, extension });
},
set spin(spinType: string) {
switch (spinType) {
case 'rotate left':
case 'rotate right': {
this.state.rotationDeg += spinType === 'rotate left' ? -90 : 90;
break;
}
case 'vertical flip':
case 'horizontal flip': {
spinType = spinType === 'vertical flip' ? 'verticalFlip' : 'horizontalFlip';
this.state[spinType] *= -1;
break;
}
/*** Setters ***/
/* Type “any” is required when getter and setter don't share the same type. */
set activeFilter(newFilterName: FilterName | any) {
this.activeFilter.isActive = false;
const newFilter = this.state.filters.find((filter: Filter) => filter.name === newFilterName) as Filter;
newFilter.isActive = true;
}
set title(imgFileName: string) {
const [imgName, imgExtension] = imgFileName.split(splitFromLastDotRegExp);
Object.assign(this.state, { name: imgName, extension: imgExtension?.toLowerCase() });
}
set spin(spinMode: SpinMode) {
if (spinMode.startsWith('Rotate')) {
this.state.rotationDeg += spinMode === 'Rotate Left' ? -90 : 90;
} else {
const flipMode = spinMode === 'Vertical Flip' ? 'verticalFlip' : 'horizontalFlip';
this.state[flipMode] *= -1;
}
},
};
imgStore._cloneState();
}
}

const imgStore = new ImgStore();

export default imgStore;
export { type FilterName, imgStore };
32 changes: 18 additions & 14 deletions src/assets/ts/modules/applyFilter.mts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import imgStore from '@ts/imgStore.ts';
import { DOMElements, FILTER_VALUE_CSS_VARIABLE as filterValueCSSVariable } from '@ts/app.ts';
import { imgStore } from '@ts/imgStore.ts';
import { activeFilterValueCSSVar } from '@ts/constants.ts';
import { DOMElements, getImgElement } from '@ts/domElements.ts';

export default function applyFilter({ newFilter = false }) {
const { name, unit, max: maxValue } = imgStore.activeFilter;
const { filterName, filterValue, filterRangeInput, selectedImg } = DOMElements;
const value = newFilter ? imgStore.activeFilter.value : (imgStore.updateFilterValue = Number(filterRangeInput.value));
const newValue = name === 'hue-rotation' ? Math.round((value * 360) / 100) : value;
export function applyFilter({ newFilter = false }) {
const imgElement = getImgElement();
const { name, unit, maxValue } = imgStore.activeFilter;
const { activeFilterName, activeFilterValue, activeFilterRangeInput } = DOMElements;

filterName.textContent = name;
filterValue.textContent = `${newValue}${unit}`;
filterRangeInput.max = String(maxValue);
filterRangeInput.value = String(value); // Visual value
filterRangeInput.setAttribute('value', String(value)); // Actual value
filterRangeInput.style.setProperty(filterValueCSSVariable, `${Math.max((value / maxValue) * 99, 5)}%`);
if (!newFilter) imgStore.activeFilter.value = Number(activeFilterRangeInput.value);
const { value } = imgStore.activeFilter;
const newValue = name === 'hue-rotate' ? Math.round((value * 360) / 100) : value;

activeFilterName.textContent = name;
activeFilterValue.textContent = `${newValue}${unit}`;
activeFilterRangeInput.max = String(maxValue);
activeFilterRangeInput.value = String(value); // Visual value
activeFilterRangeInput.setAttribute('value', String(value)); // Actual value
activeFilterRangeInput.style.setProperty(activeFilterValueCSSVar, `${Math.max((value / maxValue) * 99, 5)}%`);

imgStore.updateCSSFilters();
selectedImg.style.filter = imgStore.state.CSSFilters;
imgElement.style.filter = imgStore.state.CSSFilters;
}
Loading

0 comments on commit 8a73f49

Please sign in to comment.