-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
♻️ refactor: improve readability & increase consistency
- Loading branch information
1 parent
59eeb9d
commit 8a73f49
Showing
24 changed files
with
332 additions
and
289 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.