refactor: split quicklinks, move features to functional components

This commit is contained in:
David Ralph
2025-10-28 23:04:19 +00:00
parent 2eed0f7307
commit 293cc93500
39 changed files with 2847 additions and 3021 deletions

View File

@@ -1,61 +1,53 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { memo, useState, useCallback } from 'react';
import { Checkbox as CheckboxUI, FormControlLabel } from '@mui/material';
import EventBus from 'utils/eventbus';
class Checkbox extends PureComponent {
constructor(props) {
super(props);
this.state = {
checked: localStorage.getItem(this.props.name) === 'true',
};
}
const Checkbox = memo((props) => {
const [checked, setChecked] = useState(localStorage.getItem(props.name) === 'true');
handleChange = () => {
const value = this.state.checked !== true;
localStorage.setItem(this.props.name, value);
const handleChange = useCallback(() => {
const value = !checked;
localStorage.setItem(props.name, value);
setChecked(value);
this.setState({
checked: value,
});
if (this.props.onChange) {
this.props.onChange(value);
if (props.onChange) {
props.onChange(value);
}
variables.stats.postEvent(
'setting',
`${this.props.name} ${this.state.checked === true ? 'enabled' : 'disabled'}`,
`${props.name} ${checked ? 'enabled' : 'disabled'}`,
);
if (this.props.element) {
if (!document.querySelector(this.props.element)) {
if (props.element) {
if (!document.querySelector(props.element)) {
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
}
}
EventBus.emit('refresh', this.props.category);
};
EventBus.emit('refresh', props.category);
}, [checked, props]);
render() {
return (
<FormControlLabel
control={
<CheckboxUI
name={this.props.name}
color="primary"
className="checkbox"
checked={this.state.checked}
onChange={this.handleChange}
disabled={this.props.disabled || false}
/>
}
label={this.props.text}
/>
);
}
}
return (
<FormControlLabel
control={
<CheckboxUI
name={props.name}
color="primary"
className="checkbox"
checked={checked}
onChange={handleChange}
disabled={props.disabled || false}
/>
}
label={props.text}
/>
);
});
Checkbox.displayName = 'Checkbox';
export { Checkbox as default, Checkbox };

View File

@@ -1,78 +1,72 @@
import variables from 'config/variables';
import { PureComponent, createRef } from 'react';
import { memo, useState, useCallback, useRef } from 'react';
import { InputLabel, MenuItem, FormControl, Select } from '@mui/material';
import EventBus from 'utils/eventbus';
class Dropdown extends PureComponent {
constructor(props) {
super(props);
this.state = {
value: localStorage.getItem(this.props.name) || this.props.items[0].value,
title: '',
};
this.dropdown = createRef();
}
const Dropdown = memo((props) => {
const [value, setValue] = useState(
localStorage.getItem(props.name) || props.items[0].value,
);
const dropdown = useRef();
onChange = (e) => {
const { value } = e.target;
const onChange = useCallback((e) => {
const newValue = e.target.value;
if (value === variables.getMessage('modals.main.loading')) {
if (newValue === variables.getMessage('modals.main.loading')) {
return;
}
variables.stats.postEvent('setting', `${this.props.name} from ${this.state.value} to ${value}`);
variables.stats.postEvent('setting', `${props.name} from ${value} to ${newValue}`);
this.setState({
value,
});
setValue(newValue);
if (!this.props.noSetting) {
localStorage.setItem(this.props.name, value);
localStorage.setItem(this.props.name2, this.props.value2);
if (!props.noSetting) {
localStorage.setItem(props.name, newValue);
localStorage.setItem(props.name2, props.value2);
}
if (this.props.onChange) {
this.props.onChange(value);
if (props.onChange) {
props.onChange(newValue);
}
if (this.props.element) {
if (!document.querySelector(this.props.element)) {
if (props.element) {
if (!document.querySelector(props.element)) {
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
}
}
EventBus.emit('refresh', this.props.category);
};
EventBus.emit('refresh', props.category);
}, [value, props]);
render() {
const id = 'dropdown' + this.props.name;
const label = this.props.label || '';
const id = 'dropdown' + props.name;
const label = props.label || '';
return (
<FormControl fullWidth className={id}>
<InputLabel id={id}>{label}</InputLabel>
<Select
labelId={id}
id={this.props.name}
value={this.state.value}
label={label}
onChange={this.onChange}
ref={this.dropdown}
key={id}
>
{this.props.items.map((item) =>
item !== null ? (
<MenuItem key={id + item.value} value={item.value}>
{item.text}
</MenuItem>
) : null,
)}
</Select>
</FormControl>
);
}
}
return (
<FormControl fullWidth className={id}>
<InputLabel id={id}>{label}</InputLabel>
<Select
labelId={id}
id={props.name}
value={value}
label={label}
onChange={onChange}
ref={dropdown}
key={id}
>
{props.items.map((item) =>
item !== null ? (
<MenuItem key={id + item.value} value={item.value}>
{item.text}
</MenuItem>
) : null,
)}
</Select>
</FormControl>
);
});
Dropdown.displayName = 'Dropdown';
export { Dropdown as default, Dropdown };

View File

@@ -1,19 +1,22 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { memo, useEffect } from 'react';
import { toast } from 'react-toastify';
import { compressAccurately, filetoDataURL } from 'image-conversion';
import videoCheck from 'features/background/api/videoCheck';
class FileUpload extends PureComponent {
componentDidMount() {
document.getElementById(this.props.id).onchange = (e) => {
const FileUpload = memo(({ id, type, accept, loadFunction }) => {
useEffect(() => {
const fileInput = document.getElementById(id);
if (!fileInput) return;
const handleChange = (e) => {
const reader = new FileReader();
const file = e.target.files[0];
if (this.props.type === 'settings') {
if (type === 'settings') {
reader.readAsText(file, 'UTF-8');
reader.onload = (e) => {
return this.props.loadFunction(e.target.result);
return loadFunction(e.target.result);
};
} else {
// background upload
@@ -29,7 +32,7 @@ class FileUpload extends PureComponent {
return toast(variables.getMessage('toasts.no_storage'));
}
return this.props.loadFunction(file);
return loadFunction(file);
}
compressAccurately(file, {
@@ -40,7 +43,7 @@ class FileUpload extends PureComponent {
return toast(variables.getMessage('toasts.no_storage'));
}
this.props.loadFunction({
loadFunction({
target: {
result: await filetoDataURL(res),
},
@@ -48,18 +51,26 @@ class FileUpload extends PureComponent {
});
}
};
}
render() {
return (
<input
id={this.props.id}
type="file"
style={{ display: 'none' }}
accept={this.props.accept}
/>
);
}
}
fileInput.onchange = handleChange;
return () => {
if (fileInput) {
fileInput.onchange = null;
}
};
}, [id, type, loadFunction]);
return (
<input
id={id}
type="file"
style={{ display: 'none' }}
accept={accept}
/>
);
});
FileUpload.displayName = 'FileUpload';
export { FileUpload as default, FileUpload };

View File

@@ -1,5 +1,5 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { memo, useState, useCallback } from 'react';
import {
Radio as RadioUI,
RadioGroup,
@@ -11,77 +11,69 @@ import {
import EventBus from 'utils/eventbus';
import { translations } from 'lib/translations';
class Radio extends PureComponent {
constructor(props) {
super(props);
this.state = {
value: localStorage.getItem(this.props.name),
};
}
const Radio = memo((props) => {
const [value, setValue] = useState(localStorage.getItem(props.name));
handleChange = async (e) => {
const { value } = e.target;
const handleChange = useCallback(async (e) => {
const newValue = e.target.value;
if (value === 'loading') {
if (newValue === 'loading') {
return;
}
if (this.props.name === 'language') {
if (props.name === 'language') {
// old tab name
if (localStorage.getItem('tabName') === variables.getMessage('tabname')) {
localStorage.setItem('tabName', translations[value.replace('-', '_')].tabname);
localStorage.setItem('tabName', translations[newValue.replace('-', '_')].tabname);
}
}
localStorage.setItem(this.props.name, value);
localStorage.setItem(props.name, newValue);
setValue(newValue);
this.setState({
value,
});
if (this.props.onChange) {
this.props.onChange(value);
if (props.onChange) {
props.onChange(newValue);
}
variables.stats.postEvent('setting', `${this.props.name} from ${this.state.value} to ${value}`);
variables.stats.postEvent('setting', `${props.name} from ${value} to ${newValue}`);
if (this.props.element) {
if (!document.querySelector(this.props.element)) {
if (props.element) {
if (!document.querySelector(props.element)) {
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
}
}
EventBus.emit('refresh', this.props.category);
};
EventBus.emit('refresh', props.category);
}, [value, props]);
render() {
return (
<FormControl component="fieldset">
<FormLabel
className={this.props.smallTitle ? 'radio-title-small' : 'radio-title'}
component="legend"
>
{this.props.title}
</FormLabel>
<RadioGroup
aria-label={this.props.name}
name={this.props.name}
onChange={this.handleChange}
value={this.state.value}
>
{this.props.options.map((option) => (
<FormControlLabel
value={option.value}
control={<RadioUI />}
label={option.name}
key={option.name}
/>
))}
</RadioGroup>
</FormControl>
);
}
}
return (
<FormControl component="fieldset">
<FormLabel
className={props.smallTitle ? 'radio-title-small' : 'radio-title'}
component="legend"
>
{props.title}
</FormLabel>
<RadioGroup
aria-label={props.name}
name={props.name}
onChange={handleChange}
value={value}
>
{props.options.map((option) => (
<FormControlLabel
value={option.value}
control={<RadioUI />}
label={option.name}
key={option.name}
/>
))}
</RadioGroup>
</FormControl>
);
});
Radio.displayName = 'Radio';
export { Radio as default, Radio };

View File

@@ -1,88 +1,80 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { memo, useState, useCallback } from 'react';
import { toast } from 'react-toastify';
import { Slider } from '@mui/material';
import { MdRefresh } from 'react-icons/md';
import EventBus from 'utils/eventbus';
class SliderComponent extends PureComponent {
constructor(props) {
super(props);
this.state = {
value: localStorage.getItem(this.props.name) || this.props.default,
};
}
const SliderComponent = memo((props) => {
const [value, setValue] = useState(localStorage.getItem(props.name) || props.default);
handleChange = (e, text) => {
let { value } = e.target;
value = Number(value);
const handleChange = useCallback((e, text) => {
let newValue = e.target.value;
newValue = Number(newValue);
if (text) {
if (value === '') {
return this.setState({
value: 0,
});
if (newValue === '') {
setValue(0);
return;
}
if (value > this.props.max) {
value = this.props.max;
if (newValue > props.max) {
newValue = props.max;
}
if (value < this.props.min) {
value = this.props.min;
if (newValue < props.min) {
newValue = props.min;
}
}
localStorage.setItem(this.props.name, value);
this.setState({
value,
});
localStorage.setItem(props.name, newValue);
setValue(newValue);
if (this.props.element) {
if (!document.querySelector(this.props.element)) {
if (props.element) {
if (!document.querySelector(props.element)) {
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
}
}
EventBus.emit('refresh', this.props.category);
};
EventBus.emit('refresh', props.category);
}, [props]);
resetItem = () => {
this.handleChange({
const resetItem = useCallback(() => {
handleChange({
target: {
value: this.props.default || '',
value: props.default || '',
},
});
toast(variables.getMessage('toasts.reset'));
};
}, [handleChange, props.default]);
render() {
return (
<>
<span className={'sliderTitle'}>
{this.props.title}
<span>{Number(this.state.value)}</span>
<span className="link" onClick={this.resetItem}>
<MdRefresh />
{variables.getMessage('modals.main.settings.buttons.reset')}
</span>
return (
<>
<span className={'sliderTitle'}>
{props.title}
<span>{Number(value)}</span>
<span className="link" onClick={resetItem}>
<MdRefresh />
{variables.getMessage('modals.main.settings.buttons.reset')}
</span>
<Slider
value={Number(this.state.value)}
onChange={this.handleChange}
valueLabelDisplay="auto"
default={Number(this.props.default)}
min={Number(this.props.min)}
max={Number(this.props.max)}
step={Number(this.props.step) || 1}
getAriaValueText={(value) => `${value}`}
marks={this.props.marks || []}
/>
</>
);
}
}
</span>
<Slider
value={Number(value)}
onChange={handleChange}
valueLabelDisplay="auto"
default={Number(props.default)}
min={Number(props.min)}
max={Number(props.max)}
step={Number(props.step) || 1}
getAriaValueText={(value) => `${value}`}
marks={props.marks || []}
/>
</>
);
});
SliderComponent.displayName = 'SliderComponent';
export { SliderComponent as default, SliderComponent as Slider };

View File

@@ -1,60 +1,52 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { memo, useState, useCallback } from 'react';
import { Switch as SwitchUI, FormControlLabel } from '@mui/material';
import EventBus from 'utils/eventbus';
class Switch extends PureComponent {
constructor(props) {
super(props);
this.state = {
checked: localStorage.getItem(this.props.name) === 'true',
};
}
const Switch = memo((props) => {
const [checked, setChecked] = useState(localStorage.getItem(props.name) === 'true');
handleChange = () => {
const value = this.state.checked !== true;
localStorage.setItem(this.props.name, value);
const handleChange = useCallback(() => {
const value = !checked;
localStorage.setItem(props.name, value);
setChecked(value);
this.setState({
checked: value,
});
if (this.props.onChange) {
this.props.onChange(value);
if (props.onChange) {
props.onChange(value);
}
variables.stats.postEvent(
'setting',
`${this.props.name} ${this.state.checked === true ? 'enabled' : 'disabled'}`,
`${props.name} ${checked ? 'enabled' : 'disabled'}`,
);
if (this.props.element) {
if (!document.querySelector(this.props.element)) {
if (props.element) {
if (!document.querySelector(props.element)) {
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
}
}
EventBus.emit('refresh', this.props.category);
};
EventBus.emit('refresh', props.category);
}, [checked, props]);
render() {
return (
<FormControlLabel
control={
<SwitchUI
name={this.props.name}
color="primary"
checked={this.state.checked}
onChange={this.handleChange}
/>
}
label={this.props.header ? '' : this.props.text}
labelPlacement="start"
/>
);
}
}
return (
<FormControlLabel
control={
<SwitchUI
name={props.name}
color="primary"
checked={checked}
onChange={handleChange}
/>
}
label={props.header ? '' : props.text}
labelPlacement="start"
/>
);
});
Switch.displayName = 'Switch';
export { Switch as default, Switch };

View File

@@ -1,84 +1,77 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { memo, useState, useCallback } from 'react';
import { toast } from 'react-toastify';
import { TextField } from '@mui/material';
import { MdRefresh } from 'react-icons/md';
import EventBus from 'utils/eventbus';
class Text extends PureComponent {
constructor(props) {
super(props);
this.state = {
value: localStorage.getItem(this.props.name) || '',
};
}
const Text = memo((props) => {
const [value, setValue] = useState(localStorage.getItem(props.name) || '');
handleChange = (e) => {
const handleChange = useCallback((e) => {
let { value } = e.target;
// Alex wanted font to work with montserrat and Montserrat, so I made it work
if (this.props.upperCaseFirst === true) {
if (props.upperCaseFirst === true) {
value = value.charAt(0).toUpperCase() + value.slice(1);
}
localStorage.setItem(this.props.name, value);
this.setState({
value,
});
localStorage.setItem(props.name, value);
setValue(value);
if (this.props.element) {
if (!document.querySelector(this.props.element)) {
if (props.element) {
if (!document.querySelector(props.element)) {
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
}
}
EventBus.emit('refresh', this.props.category);
};
EventBus.emit('refresh', props.category);
}, [props.name, props.upperCaseFirst, props.element, props.category]);
resetItem = () => {
this.handleChange({
const resetItem = useCallback(() => {
handleChange({
target: {
value: this.props.default || '',
value: props.default || '',
},
});
toast(variables.getMessage('toasts.reset'));
};
}, [handleChange, props.default]);
render() {
return (
<>
{this.props.textarea === true ? (
<TextField
label={this.props.title}
value={this.state.value}
onChange={this.handleChange}
varient="outlined"
className={this.props.customcss ? 'customcss' : ''}
multiline
spellCheck={false}
minRows={4}
maxRows={10}
InputLabelProps={{ shrink: true }}
/>
) : (
<TextField
label={this.props.title}
value={this.state.value}
onChange={this.handleChange}
varient="outlined"
InputLabelProps={{ shrink: true }}
placeholder={this.props.placeholder || ''}
/>
)}
<span className="link" onClick={this.resetItem}>
<MdRefresh />
{variables.getMessage('modals.main.settings.buttons.reset')}
</span>
</>
);
}
}
return (
<>
{props.textarea === true ? (
<TextField
label={props.title}
value={value}
onChange={handleChange}
varient="outlined"
className={props.customcss ? 'customcss' : ''}
multiline
spellCheck={false}
minRows={4}
maxRows={10}
InputLabelProps={{ shrink: true }}
/>
) : (
<TextField
label={props.title}
value={value}
onChange={handleChange}
varient="outlined"
InputLabelProps={{ shrink: true }}
placeholder={props.placeholder || ''}
/>
)}
<span className="link" onClick={resetItem}>
<MdRefresh />
{variables.getMessage('modals.main.settings.buttons.reset')}
</span>
</>
);
});
Text.displayName = 'Text';
export { Text as default, Text };