refactor: settings tabs, improvements to logic + readability

Co-authored-by: David Ralph <me@davidcralph.co.uk>
This commit is contained in:
alexsparkes 2024-02-07 23:43:23 +00:00
parent 85b0e9826c
commit bd9c868196
38 changed files with 1417 additions and 1168 deletions

View File

@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"target": "es6"
},
"exclude": ["node_modules"],
"exclude": ["node_modules"]
}

View File

@ -20,7 +20,7 @@
"@muetab/react-color-gradient-picker": "0.1.2",
"@muetab/react-sortable-hoc": "^2.0.1",
"@mui/material": "5.15.7",
"@sentry/react": "^7.100.0",
"@sentry/react": "^7.100.1",
"embla-carousel-autoplay": "8.0.0-rc22",
"embla-carousel-react": "8.0.0-rc22",
"fast-blurhash": "^1.1.2",

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,6 @@ import EventBus from 'modules/helpers/eventbus';
import Welcome from './welcome/Welcome';
import Apps from './apps/Apps';
export default class Modals extends PureComponent {
constructor() {
super();
@ -78,9 +76,6 @@ export default class Modals extends PureComponent {
}
render() {
const navZoom = localStorage.getItem('zoomNavbar');
const appsInfo = JSON.parse(localStorage.getItem('applinks'));
return (
<>
{this.state.welcomeModal === false && (
@ -108,27 +103,6 @@ export default class Modals extends PureComponent {
>
<Welcome modalClose={() => this.closeWelcome()} modalSkip={() => this.previewWelcome()} />
</Modal>
<Modal
closeTimeoutMS={300}
onRequestClose={() => this.toggleModal('appsModal', false)}
isOpen={this.state.appsModal}
className="Modal appsmodal"
overlayClassName="Overlay"
shouldCloseOnOverlayClick={true}
ariaHideApp={false}
style={{
content: {
position: 'absolute',
right: '1rem',
top: `calc(1rem + ${55 + Math.ceil((navZoom / 20) * (navZoom * 0.01))}px)`,
overflow: 'visible',
},
}}
>
<Apps appsInfo={appsInfo} />
</Modal>
{this.state.preview && <Preview setup={() => window.location.reload()} />}
</>
);

View File

@ -1,47 +0,0 @@
import variables from 'modules/variables';
import { MdLinkOff } from 'react-icons/md';
import Tooltip from 'components/helpers/tooltip/Tooltip';
import './scss/index.scss';
const Apps = ({ appsInfo }) => {
return (
<div className="appsShortcutContainer">
{appsInfo.length > 0 ? (
appsInfo.map((info, i) => (
<Tooltip
title={info.name.split(' ')[0]}
subtitle={info.name.split(' ').slice(1).join(' ')}
key={i}
>
<a href={info.url} className="appsIcon">
<img
src={
info.icon === ''
? `https://icon.horse/icon/ ${info.url.replace('https://', '').replace('http://', '')}`
: info.icon
}
width="40px"
height="40px"
alt="Google"
/>
<span>{info.name}</span>
</a>
</Tooltip>
))
) : (
<div className="noAppsContainer">
<div className="emptyNewMessage">
<MdLinkOff />
<span className="title">
{variables.language.getMessage(variables.languagecode, 'widgets.navbar.apps.no_apps')}
</span>
</div>
</div>
)}
</div>
);
};
export default Apps;

View File

@ -114,7 +114,7 @@ class Marketplace extends PureComponent {
).json();
this.setState({
items: collection.data.items,
collectionTitle: collection.data.name,
collectionTitle: collection.data.display_name,
collectionDescription: collection.data.description,
collectionImg: collection.data.img,
collection: true,
@ -235,14 +235,6 @@ class Marketplace extends PureComponent {
});
}
reloadItems() {
this.setState({
done: false,
});
this.getItems();
}
componentDidMount() {
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
return;
@ -344,7 +336,7 @@ class Marketplace extends PureComponent {
<div
className="collectionPage"
style={{
backgroundImage: `linear-gradient(to bottom, transparent, black), url('${this.state.collectionImg}')`,
backgroundImage: `linear-gradient(to bottom, transparent, black), url('${variables.constants.DDG_IMAGE_PROXY + this.state.collectionImg}')`,
}}
>
<div className="nice-tag">
@ -386,9 +378,6 @@ class Marketplace extends PureComponent {
/>
<MdSearch />
</form>
{/*<span className="link marketplaceRefresh" onClick={() => this.reloadItems()}>
<MdRefresh /> {variables.getMessage('widgets.navbar.tooltips.refresh')}
</span>*/}
</div>
)}
<Dropdown
@ -412,7 +401,7 @@ class Marketplace extends PureComponent {
item.news
? { backgroundColor: item.background_colour }
: {
backgroundImage: `linear-gradient(to left, #000, transparent, #000), url('${item.img}')`,
backgroundImage: `linear-gradient(to left, #000, transparent, #000), url('${variables.constants.DDG_IMAGE_PROXY + item.img}')`,
}
}
>

View File

@ -51,6 +51,7 @@ div.color-preview-area > div > div:nth-child(5) {
justify-content: space-between;
align-items: center;
flex-flow: row-reverse;
gap: 20px;
}
.gradient-controls {

View File

@ -16,7 +16,7 @@ const PreferencesWrapper = ({ children, ...props }) => {
});
return (
<div className={shown ? '' : 'inactiveSetting'}>
<div className={shown ? 'preferences' : 'preferencesInactive'}>
{props.zoomSetting && (
<SettingsItem
title={variables.getMessage(

View File

@ -0,0 +1,18 @@
import { MdOutlineKeyboardArrowRight } from 'react-icons/md';
export default function Section({ title, subtitle, icon, onClick }) {
return (
<div className="moreSettings" onClick={onClick}>
<div className="left">
{icon}
<div className="content">
<span className="title">{title}</span>
<span className="subtitle">{subtitle}</span>
</div>
</div>
<div className="action">
<MdOutlineKeyboardArrowRight />
</div>
</div>
);
}

View File

@ -6,6 +6,8 @@ import {
MdUpload as ImportIcon,
MdDownload as ExportIcon,
MdRestartAlt as ResetIcon,
MdOutlineKeyboardArrowRight,
MdDataUsage,
} from 'react-icons/md';
import { exportSettings, importSettings } from 'modules/helpers/settings/modals';
@ -16,106 +18,153 @@ import Switch from '../Switch';
import ResetModal from '../ResetModal';
import Dropdown from '../Dropdown';
import SettingsItem from '../SettingsItem';
import Section from '../Section';
import time_zones from 'components/widgets/time/timezones.json';
export default function AdvancedSettings() {
const [resetModal, setResetModal] = useState(false);
const [data, setData] = useState(false);
const ADVANCED_SECTION = 'modals.main.settings.sections.advanced';
const Data = () => {
return (
<>
{localStorage.getItem('welcomePreview') !== 'true' && (
<div className="settingsRow">
<div className="content">
<span className="title">
{variables.getMessage('modals.main.settings.sections.advanced.data')}
</span>
<span className="subtitle">
{variables.getMessage('modals.main.settings.sections.advanced.data_subtitle')}
</span>
</div>
<div className="action activityButtons">
<button onClick={() => setResetModal(true)}>
{variables.getMessage('modals.main.settings.buttons.reset')}
<ResetIcon />
</button>
<button onClick={() => exportSettings()}>
{variables.getMessage('modals.main.settings.buttons.export')}
<ExportIcon />
</button>
<button onClick={() => document.getElementById('file-input').click()}>
{variables.getMessage('modals.main.settings.buttons.import')}
<ImportIcon />
</button>
</div>
</div>
)}
</>
);
};
let header;
if (data) {
header = (
<span className="mainTitle">
<span className="backTitle" onClick={() => setData(false)}>
{variables.getMessage(`${ADVANCED_SECTION}.title`)}
</span>
<MdOutlineKeyboardArrowRight />
{variables.getMessage(`${ADVANCED_SECTION}.data`)}
</span>
);
} else {
header = (
<span className="mainTitle"> {variables.getMessage(`${ADVANCED_SECTION}.title`)}</span>
);
}
return (
<>
<span className="mainTitle">{variables.getMessage(`${ADVANCED_SECTION}.title`)}</span>
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.advanced.offline_mode')}
subtitle={variables.getMessage('modals.main.settings.sections.advanced.offline_subtitle')}
>
<Switch name="offlineMode" element=".other" />
</SettingsItem>
{localStorage.getItem('welcomePreview') !== 'true' && (
<div className="settingsRow">
<div className="content">
<span className="title">
{variables.getMessage('modals.main.settings.sections.advanced.data')}
</span>
<span className="subtitle">
{variables.getMessage('modals.main.settings.sections.advanced.data_subtitle')}
</span>
</div>
<div className="action activityButtons">
<button onClick={() => setResetModal(true)}>
{variables.getMessage('modals.main.settings.buttons.reset')}
<ResetIcon />
</button>
<button onClick={() => exportSettings()}>
{variables.getMessage('modals.main.settings.buttons.export')}
<ExportIcon />
</button>
<button onClick={() => document.getElementById('file-input').click()}>
{variables.getMessage('modals.main.settings.buttons.import')}
<ImportIcon />
</button>
</div>
</div>
{header}
{data ? (
<Data />
) : (
<>
<Section
title={variables.getMessage(`${ADVANCED_SECTION}.data`)}
subtitle={variables.getMessage(
'modals.main.settings.sections.appearance.accessibility.description',
)}
onClick={() => setData(true)}
icon={<MdDataUsage />}
/>
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.advanced.offline_mode')}
subtitle={variables.getMessage(
'modals.main.settings.sections.advanced.offline_subtitle',
)}
>
<Switch name="offlineMode" element=".other" />
</SettingsItem>
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.advanced.timezone.title')}
subtitle={variables.getMessage(
'modals.main.settings.sections.advanced.timezone.subtitle',
)}
>
<Dropdown name="timezone" category="timezone" manual={true}>
<MenuItem value="auto">
{variables.getMessage('modals.main.settings.sections.advanced.timezone.automatic')}
</MenuItem>
{time_zones.map((timezone) => (
<MenuItem value={timezone} key={timezone}>
{timezone}
</MenuItem>
))}
</Dropdown>
</SettingsItem>
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.advanced.tab_name')}
subtitle={variables.getMessage(
'modals.main.settings.sections.advanced.tab_name_subtitle',
)}
>
<Text name="tabName" default={variables.getMessage('tabname')} category="other" />
</SettingsItem>
<FileUpload
id="file-input"
accept="application/json"
type="settings"
loadFunction={(e) => importSettings(e)}
/>
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.advanced.custom_css')}
subtitle={variables.getMessage(
'modals.main.settings.sections.advanced.custom_css_subtitle',
)}
>
<Text name="customcss" textarea={true} category="other" customcss={true} />
</SettingsItem>
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.experimental.title')}
subtitle={variables.getMessage(
'modals.main.settings.sections.advanced.experimental_warning',
)}
final={true}
>
<Switch
name="experimental"
text={variables.getMessage('modals.main.settings.enabled')}
element=".other"
/>
</SettingsItem>
<Modal
closeTimeoutMS={100}
onRequestClose={() => setResetModal(false)}
isOpen={resetModal}
className="Modal resetmodal mainModal"
overlayClassName="Overlay resetoverlay"
ariaHideApp={false}
>
<ResetModal modalClose={() => setResetModal(false)} />
</Modal>
</>
)}
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.advanced.timezone.title')}
subtitle={variables.getMessage('modals.main.settings.sections.advanced.timezone.subtitle')}
>
<Dropdown name="timezone" category="timezone" manual={true}>
<MenuItem value="auto">
{variables.getMessage('modals.main.settings.sections.advanced.timezone.automatic')}
</MenuItem>
{time_zones.map((timezone) => (
<MenuItem value={timezone} key={timezone}>
{timezone}
</MenuItem>
))}
</Dropdown>
</SettingsItem>
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.advanced.tab_name')}
subtitle={variables.getMessage('modals.main.settings.sections.advanced.tab_name_subtitle')}
>
<Text name="tabName" default={variables.getMessage('tabname')} category="other" />
</SettingsItem>
<FileUpload
id="file-input"
accept="application/json"
type="settings"
loadFunction={(e) => importSettings(e)}
/>
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.advanced.custom_css')}
subtitle={variables.getMessage(
'modals.main.settings.sections.advanced.custom_css_subtitle',
)}
>
<Text name="customcss" textarea={true} category="other" customcss={true} />
</SettingsItem>
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.experimental.title')}
subtitle={variables.getMessage(
'modals.main.settings.sections.advanced.experimental_warning',
)}
final={true}
>
<Switch
name="experimental"
text={variables.getMessage('modals.main.settings.enabled')}
element=".other"
/>
</SettingsItem>
<Modal
closeTimeoutMS={100}
onRequestClose={() => setResetModal(false)}
isOpen={resetModal}
className="Modal resetmodal mainModal"
overlayClassName="Overlay resetoverlay"
ariaHideApp={false}
>
<ResetModal modalClose={() => setResetModal(false)} />
</Modal>
</>
);
}

View File

@ -1,4 +1,4 @@
import { memo } from 'react';
import { memo, useState } from 'react';
import variables from 'modules/variables';
@ -8,125 +8,119 @@ import Radio from '../Radio';
import Slider from '../Slider';
import Text from '../Text';
import SettingsItem from '../SettingsItem';
import Section from '../Section';
import { MdSource, MdOutlineKeyboardArrowRight, MdAccessibility } from 'react-icons/md';
import { values } from 'modules/helpers/settings/modals';
import Settings from '../../tabs/Settings';
function AppearanceSettings() {
return (
<>
<span className="mainTitle">
{variables.getMessage('modals.main.settings.sections.appearance.title')}
</span>
<div className="settingsRow">
<div className="content">
<span className="title">
{variables.getMessage('modals.main.settings.sections.appearance.theme.title')}
</span>
<span className="subtitle">
{' '}
{variables.getMessage('modals.main.settings.sections.appearance.theme.description')}
</span>
</div>
<div className="action">
<Radio
name="theme"
options={[
{
name: variables.getMessage('modals.main.settings.sections.appearance.theme.auto'),
value: 'auto',
},
{
name: variables.getMessage('modals.main.settings.sections.appearance.theme.light'),
value: 'light',
},
{
name: variables.getMessage('modals.main.settings.sections.appearance.theme.dark'),
value: 'dark',
},
]}
category="other"
/>
</div>
</div>
<div className="settingsRow">
<div className="content">
<span className="title">
{variables.getMessage('modals.main.settings.sections.appearance.font.title')}
</span>
<span className="subtitle">
{variables.getMessage('modals.main.settings.sections.appearance.font.description')}
</span>
</div>
<div className="action">
<Checkbox
name="fontGoogle"
text={variables.getMessage('modals.main.settings.sections.appearance.font.google')}
category="other"
/>
<Text
title={variables.getMessage('modals.main.settings.sections.appearance.font.custom')}
name="font"
upperCaseFirst={true}
category="other"
/>
<Dropdown
label={variables.getMessage(
'modals.main.settings.sections.appearance.font.weight.title',
const [accessibility, setAccessibility] = useState(false);
const ThemeSelection = () => {
return (
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.appearance.theme.title')}
subtitle={variables.getMessage(
'modals.main.settings.sections.appearance.theme.description',
)}
>
<Radio
name="theme"
options={[
{
name: variables.getMessage('modals.main.settings.sections.appearance.theme.auto'),
value: 'auto',
},
{
name: variables.getMessage('modals.main.settings.sections.appearance.theme.light'),
value: 'light',
},
{
name: variables.getMessage('modals.main.settings.sections.appearance.theme.dark'),
value: 'dark',
},
]}
category="other"
/>
</SettingsItem>
);
};
const FontOptions = () => {
return (
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.appearance.font.title')}
subtitle={variables.getMessage('modals.main.settings.sections.appearance.font.description')}
>
<Checkbox
name="fontGoogle"
text={variables.getMessage('modals.main.settings.sections.appearance.font.google')}
category="other"
/>
<Text
title={variables.getMessage('modals.main.settings.sections.appearance.font.custom')}
name="font"
upperCaseFirst={true}
category="other"
/>
<Dropdown
label={variables.getMessage('modals.main.settings.sections.appearance.font.weight.title')}
name="fontweight"
category="other"
>
{/* names are taken from https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight */}
<option value="100">
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.thin')}
</option>
<option value="200">
{variables.getMessage(
'modals.main.settings.sections.appearance.font.weight.extra_light',
)}
name="fontweight"
category="other"
>
{/* names are taken from https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight */}
<option value="100">
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.thin')}
</option>
<option value="200">
{variables.getMessage(
'modals.main.settings.sections.appearance.font.weight.extra_light',
)}
</option>
<option value="300">
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.light')}
</option>
<option value="400">
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.normal')}
</option>
<option value="500">
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.medium')}
</option>
<option value="600">
{variables.getMessage(
'modals.main.settings.sections.appearance.font.weight.semi_bold',
)}
</option>
<option value="700">
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.bold')}
</option>
<option value="800">
{variables.getMessage(
'modals.main.settings.sections.appearance.font.weight.extra_bold',
)}
</option>
</Dropdown>
<Dropdown
label={variables.getMessage(
'modals.main.settings.sections.appearance.font.style.title',
</option>
<option value="300">
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.light')}
</option>
<option value="400">
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.normal')}
</option>
<option value="500">
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.medium')}
</option>
<option value="600">
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.semi_bold')}
</option>
<option value="700">
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.bold')}
</option>
<option value="800">
{variables.getMessage(
'modals.main.settings.sections.appearance.font.weight.extra_bold',
)}
name="fontstyle"
category="other"
>
<option value="normal">
{variables.getMessage('modals.main.settings.sections.appearance.font.style.normal')}
</option>
<option value="italic">
{variables.getMessage('modals.main.settings.sections.appearance.font.style.italic')}
</option>
<option value="oblique">
{variables.getMessage('modals.main.settings.sections.appearance.font.style.oblique')}
</option>
</Dropdown>
</div>
</div>
</option>
</Dropdown>
<Dropdown
label={variables.getMessage('modals.main.settings.sections.appearance.font.style.title')}
name="fontstyle"
category="other"
>
<option value="normal">
{variables.getMessage('modals.main.settings.sections.appearance.font.style.normal')}
</option>
<option value="italic">
{variables.getMessage('modals.main.settings.sections.appearance.font.style.italic')}
</option>
<option value="oblique">
{variables.getMessage('modals.main.settings.sections.appearance.font.style.oblique')}
</option>
</Dropdown>
</SettingsItem>
);
};
const WidgetStyle = () => {
return (
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.appearance.style.title')}
subtitle={variables.getMessage(
@ -149,7 +143,11 @@ function AppearanceSettings() {
category="widgets"
/>
</SettingsItem>
);
};
const AccessibilityOptions = () => {
return (
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.appearance.accessibility.title')}
subtitle={variables.getMessage(
@ -207,6 +205,50 @@ function AppearanceSettings() {
}
/>
</SettingsItem>
);
};
let header;
if (accessibility) {
header = (
<span className="mainTitle">
<span className="backTitle" onClick={() => setAccessibility(false)}>
{variables.getMessage('modals.main.settings.sections.appearance.title')}
</span>
<MdOutlineKeyboardArrowRight />
{variables.getMessage('modals.main.settings.sections.appearance.accessibility.title')}
</span>
);
} else {
header = (
<span className="mainTitle">
{' '}
{variables.getMessage('modals.main.settings.sections.appearance.title')}
</span>
);
}
return (
<>
{header}
{accessibility ? (
<AccessibilityOptions />
) : (
<>
<Section
title={variables.getMessage(
'modals.main.settings.sections.appearance.accessibility.title',
)}
subtitle={variables.getMessage(
'modals.main.settings.sections.appearance.accessibility.description',
)}
icon={<MdAccessibility />}
onClick={() => setAccessibility(true)}
/>
<ThemeSelection />
<FontOptions />
<WidgetStyle />
</>
)}
</>
);
}

View File

@ -5,6 +5,7 @@ import Header from '../Header';
import Checkbox from '../Checkbox';
import Dropdown from '../Dropdown';
import SettingsItem from '../SettingsItem';
import PreferencesWrapper from '../PreferencesWrapper';
export default function Date() {
const [dateType, setDateType] = useState(localStorage.getItem('dateType') || 'long');
@ -75,47 +76,49 @@ export default function Date() {
zoomSetting="zoomDate"
switch={true}
/>
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.time.type')}
subtitle={variables.getMessage('modals.main.settings.sections.date.type.subtitle')}
>
<Dropdown
name="dateType"
onChange={(value) => {
setDateType(value);
localStorage.setItem('dateType', value);
}}
category="date"
<PreferencesWrapper setting="date" switch={true} zoomSetting="zoomDate">
<SettingsItem
title={variables.getMessage('modals.main.settings.sections.time.type')}
subtitle={variables.getMessage('modals.main.settings.sections.date.type.subtitle')}
>
<option value="long">
{variables.getMessage('modals.main.settings.sections.date.type.long')}
</option>
<option value="short">
{variables.getMessage('modals.main.settings.sections.date.type.short')}
</option>
</Dropdown>
</SettingsItem>
<SettingsItem
title={
dateType === 'long'
? variables.getMessage('modals.main.settings.sections.date.type.long')
: variables.getMessage('modals.main.settings.sections.date.type.short')
}
subtitle={variables.getMessage('modals.main.settings.sections.date.type_settings')}
final={true}
>
{dateType === 'long' ? longSettings : shortSettings}
<Checkbox
name="weeknumber"
text={variables.getMessage('modals.main.settings.sections.date.week_number')}
category="date"
/>
<Checkbox
name="datezero"
text={variables.getMessage('modals.main.settings.sections.time.digital.zero')}
category="date"
/>
</SettingsItem>
<Dropdown
name="dateType"
onChange={(value) => {
setDateType(value);
localStorage.setItem('dateType', value);
}}
category="date"
>
<option value="long">
{variables.getMessage('modals.main.settings.sections.date.type.long')}
</option>
<option value="short">
{variables.getMessage('modals.main.settings.sections.date.type.short')}
</option>
</Dropdown>
</SettingsItem>
<SettingsItem
title={
dateType === 'long'
? variables.getMessage('modals.main.settings.sections.date.type.long')
: variables.getMessage('modals.main.settings.sections.date.type.short')
}
subtitle={variables.getMessage('modals.main.settings.sections.date.type_settings')}
final={true}
>
{dateType === 'long' ? longSettings : shortSettings}
<Checkbox
name="weeknumber"
text={variables.getMessage('modals.main.settings.sections.date.week_number')}
category="date"
/>
<Checkbox
name="datezero"
text={variables.getMessage('modals.main.settings.sections.time.digital.zero')}
category="date"
/>
</SettingsItem>
</PreferencesWrapper>
</>
);
}

View File

@ -1,89 +1,97 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { useState } from 'react';
import Header from '../Header';
import Checkbox from '../Checkbox';
import Switch from '../Switch';
import Text from '../Text';
import SettingsItem from '../SettingsItem';
import PreferencesWrapper from '../PreferencesWrapper';
export default class GreetingSettings extends PureComponent {
constructor() {
super();
this.state = {
birthday: new Date(localStorage.getItem('birthday')) || new Date(),
};
}
const GreetingSettings = () => {
const [birthday, setBirthday] = useState(
new Date(localStorage.getItem('birthday')) || new Date(),
);
changeDate = (e) => {
localStorage.setItem('birthday', e.target.value || new Date());
this.setState({
birthday: e.target.value ? new Date(e.target.value) : new Date(),
});
const changeDate = (e) => {
const newDate = e.target.value ? new Date(e.target.value) : new Date();
localStorage.setItem('birthday', newDate);
setBirthday(newDate);
};
render() {
const GREETING_SECTION = 'modals.main.settings.sections.greeting';
const GREETING_SECTION = 'modals.main.settings.sections.greeting';
const AdditionalOptions = () => {
return (
<>
<Header
title={variables.getMessage(`${GREETING_SECTION}.title`)}
setting="greeting"
<SettingsItem
title={variables.getMessage('modals.main.settings.additional_settings')}
subtitle={variables.getMessage(`${GREETING_SECTION}.additional`)}
>
<Checkbox
name="events"
text={variables.getMessage(`${GREETING_SECTION}.events`)}
category="greeting"
element=".greeting"
zoomSetting="zoomGreeting"
switch={true}
/>
<SettingsItem
title={variables.getMessage('modals.main.settings.additional_settings')}
subtitle={variables.getMessage(`${GREETING_SECTION}.additional`)}
>
<Checkbox
name="events"
text={variables.getMessage(`${GREETING_SECTION}.events`)}
category="greeting"
/>
<Checkbox
name="defaultGreetingMessage"
text={variables.getMessage(`${GREETING_SECTION}.default`)}
category="greeting"
/>
<Text
title={variables.getMessage(`${GREETING_SECTION}.name`)}
name="greetingName"
category="greeting"
/>
</SettingsItem>
<SettingsItem
title={variables.getMessage(`${GREETING_SECTION}.birthday`)}
subtitle={variables.getMessage(
'modals.main.settings.sections.greeting.birthday_subtitle',
)}
final={true}
>
<Switch
name="birthdayenabled"
text={variables.getMessage('modals.main.settings.enabled')}
category="greeting"
/>
<Checkbox
name="birthdayage"
text={variables.getMessage(`${GREETING_SECTION}.birthday_age`)}
category="greeting"
/>
<p style={{ marginRight: 'auto' }}>
{variables.getMessage(`${GREETING_SECTION}.birthday_date`)}
</p>
<input
type="date"
onChange={this.changeDate}
value={this.state.birthday.toISOString().substring(0, 10)}
required
/>
</SettingsItem>
</>
<Checkbox
name="defaultGreetingMessage"
text={variables.getMessage(`${GREETING_SECTION}.default`)}
category="greeting"
/>
<Text
title={variables.getMessage(`${GREETING_SECTION}.name`)}
name="greetingName"
category="greeting"
/>
</SettingsItem>
);
}
}
};
const BirthdayOptions = () => {
return (
<SettingsItem
title={variables.getMessage(`${GREETING_SECTION}.birthday`)}
subtitle={variables.getMessage('modals.main.settings.sections.greeting.birthday_subtitle')}
final={true}
>
<Switch
name="birthdayenabled"
text={variables.getMessage('modals.main.settings.enabled')}
category="greeting"
/>
<Checkbox
name="birthdayage"
text={variables.getMessage(`${GREETING_SECTION}.birthday_age`)}
category="greeting"
/>
<p style={{ marginRight: 'auto' }}>
{variables.getMessage(`${GREETING_SECTION}.birthday_date`)}
</p>
<input
type="date"
onChange={changeDate}
value={birthday.toISOString().substring(0, 10)}
required
/>
</SettingsItem>
);
};
return (
<>
<Header
title={variables.getMessage(`${GREETING_SECTION}.title`)}
setting="greeting"
category="greeting"
element=".greeting"
zoomSetting="zoomGreeting"
switch={true}
/>
<PreferencesWrapper setting="greeting" zoomSetting="zoomGreeting" switch={true}>
<AdditionalOptions />
{BirthdayOptions()}
</PreferencesWrapper>
</>
);
};
export default GreetingSettings;

View File

@ -8,6 +8,7 @@ import SettingsItem from '../SettingsItem';
import Header from '../Header';
import EventBus from 'modules/helpers/eventbus';
import PreferencesWrapper from '../PreferencesWrapper';
export default class Message extends PureComponent {
constructor() {
@ -70,64 +71,66 @@ export default class Message extends PureComponent {
zoomSetting="zoomMessage"
switch={true}
/>
<SettingsItem title={variables.getMessage(`${MESSAGE_SECTION}.messages`)} final={true}>
<button onClick={() => this.modifyMessage('add')}>
{variables.getMessage(`${MESSAGE_SECTION}.add`)} <MdAdd />
</button>
</SettingsItem>
<div className="messagesContainer">
{this.state.messages.map((_url, index) => (
<div className="messageMap" key={index}>
<div className="flexGrow">
<div className="icon">
<MdOutlineTextsms />
<PreferencesWrapper setting="message" switch={true} zoomSetting="zoomMessage">
<SettingsItem title={variables.getMessage(`${MESSAGE_SECTION}.messages`)} final={true}>
<button onClick={() => this.modifyMessage('add')}>
{variables.getMessage(`${MESSAGE_SECTION}.add`)} <MdAdd />
</button>
</SettingsItem>
<div className="messagesContainer">
{this.state.messages.map((_url, index) => (
<div className="messageMap" key={index}>
<div className="flexGrow">
<div className="icon">
<MdOutlineTextsms />
</div>
<div className="messageText">
<span className="subtitle">
{variables.getMessage(`${MESSAGE_SECTION}.title`)}
</span>
<TextareaAutosize
value={this.state.messages[index]}
placeholder={variables.getMessage(
'modals.main.settings.sections.message.content',
)}
onChange={(e) => this.message(e, true, index)}
varient="outlined"
style={{ padding: '0' }}
/>
</div>
</div>
<div className="messageText">
<span className="subtitle">
{variables.getMessage(`${MESSAGE_SECTION}.title`)}
</span>
<TextareaAutosize
value={this.state.messages[index]}
placeholder={variables.getMessage(
'modals.main.settings.sections.message.content',
)}
onChange={(e) => this.message(e, true, index)}
varient="outlined"
style={{ padding: '0' }}
/>
<div>
<div className="messageAction">
<button
className="deleteButton"
onClick={() => this.modifyMessage('remove', index)}
>
{variables.getMessage('modals.main.marketplace.product.buttons.remove')}
<MdCancel />
</button>
</div>
</div>
</div>
<div>
<div className="messageAction">
<button
className="deleteButton"
onClick={() => this.modifyMessage('remove', index)}
>
{variables.getMessage('modals.main.marketplace.product.buttons.remove')}
<MdCancel />
</button>
</div>
</div>
</div>
))}
</div>
{this.state.messages.length === 0 && (
<div className="photosEmpty">
<div className="emptyNewMessage">
<MdOutlineTextsms />
<span className="title">
{variables.getMessage(`${MESSAGE_SECTION}.no_messages`)}
</span>
<span className="subtitle">
{variables.getMessage(`${MESSAGE_SECTION}.add_some`)}
</span>
<button onClick={() => this.modifyMessage('add')}>
{variables.getMessage(`${MESSAGE_SECTION}.add`)}
<MdAdd />
</button>
</div>
))}
</div>
)}
{this.state.messages.length === 0 && (
<div className="photosEmpty">
<div className="emptyNewMessage">
<MdOutlineTextsms />
<span className="title">
{variables.getMessage(`${MESSAGE_SECTION}.no_messages`)}
</span>
<span className="subtitle">
{variables.getMessage(`${MESSAGE_SECTION}.add_some`)}
</span>
<button onClick={() => this.modifyMessage('add')}>
{variables.getMessage(`${MESSAGE_SECTION}.add`)}
<MdAdd />
</button>
</div>
</div>
)}
</PreferencesWrapper>
</>
);
}

View File

@ -12,7 +12,7 @@ import Dropdown from '../Dropdown';
import SettingsItem from '../SettingsItem';
import Header from '../Header';
import { getTitleFromUrl, isValidUrl } from './utils/utils';
import { getTitleFromUrl, isValidUrl } from 'modules/helpers/settings/modals';
import QuickLink from './quicklinks/QuickLink';
function Navbar() {
@ -32,7 +32,7 @@ function Navbar() {
const data = JSON.parse(localStorage.getItem('applinks'));
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = 'http://' + url;
url = 'https://' + url;
}
if (url.length <= 0 || isValidUrl(url) === false) {

View File

@ -11,7 +11,8 @@ import AddModal from './quicklinks/AddModal';
import EventBus from 'modules/helpers/eventbus';
import QuickLink from './quicklinks/QuickLink';
import { getTitleFromUrl, isValidUrl } from './utils/utils';
import { getTitleFromUrl, isValidUrl } from 'modules/helpers/settings/modals';
import PreferencesWrapper from '../PreferencesWrapper';
export default class QuickLinks extends PureComponent {
constructor() {
@ -136,67 +137,69 @@ export default class QuickLinks extends PureComponent {
zoomSetting="zoomQuicklinks"
switch={true}
/>
<SettingsItem
title={variables.getMessage('modals.main.settings.additional_settings')}
subtitle={variables.getMessage(`${QUICKLINKS_SECTION}.additional`)}
>
<Checkbox
name="quicklinksnewtab"
text={variables.getMessage(`${QUICKLINKS_SECTION}.open_new`)}
category="quicklinks"
/>
<Checkbox
name="quicklinkstooltip"
text={variables.getMessage(`${QUICKLINKS_SECTION}.tooltip`)}
category="quicklinks"
/>
</SettingsItem>
<SettingsItem
title={variables.getMessage(`${QUICKLINKS_SECTION}.styling`)}
subtitle={variables.getMessage(
'modals.main.settings.sections.quicklinks.styling_description',
)}
>
<Dropdown
label={variables.getMessage(`${QUICKLINKS_SECTION}.style`)}
name="quickLinksStyle"
category="quicklinks"
<PreferencesWrapper setting="quicklinksenabled" switch={true} zoomSetting="zoomQuicklinks">
<SettingsItem
title={variables.getMessage('modals.main.settings.additional_settings')}
subtitle={variables.getMessage(`${QUICKLINKS_SECTION}.additional`)}
>
<option value="icon">
{variables.getMessage(`${QUICKLINKS_SECTION}.options.icon`)}
</option>
<option value="text">
{variables.getMessage(`${QUICKLINKS_SECTION}.options.text_only`)}
</option>
<option value="metro">
{variables.getMessage(`${QUICKLINKS_SECTION}.options.metro`)}
</option>
</Dropdown>
</SettingsItem>
<Checkbox
name="quicklinksnewtab"
text={variables.getMessage(`${QUICKLINKS_SECTION}.open_new`)}
category="quicklinks"
/>
<Checkbox
name="quicklinkstooltip"
text={variables.getMessage(`${QUICKLINKS_SECTION}.tooltip`)}
category="quicklinks"
/>
</SettingsItem>
<SettingsItem
title={variables.getMessage(`${QUICKLINKS_SECTION}.styling`)}
subtitle={variables.getMessage(
'modals.main.settings.sections.quicklinks.styling_description',
)}
>
<Dropdown
label={variables.getMessage(`${QUICKLINKS_SECTION}.style`)}
name="quickLinksStyle"
category="quicklinks"
>
<option value="icon">
{variables.getMessage(`${QUICKLINKS_SECTION}.options.icon`)}
</option>
<option value="text">
{variables.getMessage(`${QUICKLINKS_SECTION}.options.text_only`)}
</option>
<option value="metro">
{variables.getMessage(`${QUICKLINKS_SECTION}.options.metro`)}
</option>
</Dropdown>
</SettingsItem>
<SettingsItem title={variables.getMessage(`${QUICKLINKS_SECTION}.title`)} final={true}>
<button onClick={() => this.setState({ showAddModal: true })}>
{variables.getMessage(`${QUICKLINKS_SECTION}.add_link`)} <MdAddLink />
</button>
</SettingsItem>
<SettingsItem title={variables.getMessage(`${QUICKLINKS_SECTION}.title`)} final={true}>
<button onClick={() => this.setState({ showAddModal: true })}>
{variables.getMessage(`${QUICKLINKS_SECTION}.add_link`)} <MdAddLink />
</button>
</SettingsItem>
{this.state.items.length === 0 && (
<div className="photosEmpty">
<div className="emptyNewMessage">
<MdLinkOff />
<span className="title">
{variables.getMessage(`${QUICKLINKS_SECTION}.no_quicklinks`)}
</span>
<span className="subtitle">
{variables.getMessage('modals.main.settings.sections.message.add_some')}
</span>
<button onClick={() => this.setState({ showAddModal: true })}>
{variables.getMessage(`${QUICKLINKS_SECTION}.add_link`)}
<MdAddLink />
</button>
{this.state.items.length === 0 && (
<div className="photosEmpty">
<div className="emptyNewMessage">
<MdLinkOff />
<span className="title">
{variables.getMessage(`${QUICKLINKS_SECTION}.no_quicklinks`)}
</span>
<span className="subtitle">
{variables.getMessage('modals.main.settings.sections.message.add_some')}
</span>
<button onClick={() => this.setState({ showAddModal: true })}>
{variables.getMessage(`${QUICKLINKS_SECTION}.add_link`)}
<MdAddLink />
</button>
</div>
</div>
</div>
)}
)}
</PreferencesWrapper>
<div className="messagesContainer" ref={this.quicklinksContainer}>
{this.state.items.map((item, i) => (

View File

@ -11,13 +11,13 @@ import SettingsItem from '../SettingsItem';
import EventBus from 'modules/helpers/eventbus';
import searchEngines from 'components/widgets/search/search_engines.json';
import PreferencesWrapper from '../PreferencesWrapper';
export default class SearchSettings extends PureComponent {
constructor() {
super();
this.state = {
customEnabled: false,
customDisplay: 'none',
customValue: localStorage.getItem('customSearchEngine') || '',
};
}
@ -34,7 +34,6 @@ export default class SearchSettings extends PureComponent {
componentDidMount() {
if (localStorage.getItem('searchEngine') === 'custom') {
this.setState({
customDisplay: 'block',
customEnabled: true,
});
} else {
@ -53,12 +52,10 @@ export default class SearchSettings extends PureComponent {
setSearchEngine(input) {
if (input === 'custom') {
this.setState({
customDisplay: 'block',
customEnabled: true,
});
} else {
this.setState({
customDisplay: 'none',
customEnabled: false,
});
localStorage.setItem('searchEngine', input);
@ -70,14 +67,8 @@ export default class SearchSettings extends PureComponent {
render() {
const SEARCH_SECTION = 'modals.main.settings.sections.search';
return (
<>
<Header
title={variables.getMessage(`${SEARCH_SECTION}.title`)}
setting="searchBar"
category="widgets"
switch={true}
/>
const AdditionalOptions = () => {
return (
<SettingsItem
title={variables.getMessage('modals.main.settings.additional_settings')}
subtitle={variables.getMessage(`${SEARCH_SECTION}.additional`)}
@ -108,12 +99,17 @@ export default class SearchSettings extends PureComponent {
category="search"
/>
</SettingsItem>
);
};
const SearchEngineSelection = () => {
return (
<SettingsItem
title={variables.getMessage(`${SEARCH_SECTION}.search_engine`)}
subtitle={variables.getMessage(
'modals.main.settings.sections.search.search_engine_subtitle',
)}
final={this.state.customDisplay === 'none' ? true : false}
final={!this.state.customEnabled}
>
<Dropdown
name="searchEngine"
@ -130,22 +126,37 @@ export default class SearchSettings extends PureComponent {
</MenuItem>
</Dropdown>
</SettingsItem>
<div style={{ display: this.state.customDisplay }}>
<SettingsItem title={variables.getMessage(`${SEARCH_SECTION}.custom`)} final={true}>
<TextField
label={variables.getMessage(`${SEARCH_SECTION}.custom`)}
value={this.state.customValue}
onInput={(e) => this.setState({ customValue: e.target.value })}
varient="outlined"
InputLabelProps={{ shrink: true }}
/>
<p style={{ marginTop: '0px' }}>
<span className="link" onClick={() => this.resetSearch()}>
{variables.getMessage('modals.main.settings.buttons.reset')}
</span>
</p>
</SettingsItem>
</div>
);
};
return (
<>
<Header
title={variables.getMessage(`${SEARCH_SECTION}.title`)}
setting="searchBar"
category="widgets"
switch={true}
/>
<PreferencesWrapper setting="searchBar" category="widgets" switch={true}>
<AdditionalOptions />
<SearchEngineSelection />
{this.state.customEnabled && (
<SettingsItem title={variables.getMessage(`${SEARCH_SECTION}.custom`)} final={true}>
<TextField
label={variables.getMessage(`${SEARCH_SECTION}.custom`)}
value={this.state.customValue}
onInput={(e) => this.setState({ customValue: e.target.value })}
varient="outlined"
InputLabelProps={{ shrink: true }}
/>
<p style={{ marginTop: '0px' }}>
<span className="link" onClick={() => this.resetSearch()}>
{variables.getMessage('modals.main.settings.buttons.reset')}
</span>
</p>
</SettingsItem>
)}
</PreferencesWrapper>
</>
);
}

View File

@ -1,208 +1,210 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import React, { useState } from 'react';
import Header from '../Header';
import Checkbox from '../Checkbox';
import Dropdown from '../Dropdown';
import Radio from '../Radio';
import SettingsItem from '../SettingsItem';
import PreferencesWrapper from '../PreferencesWrapper';
export default class TimeSettings extends PureComponent {
constructor() {
super();
this.state = {
timeType: localStorage.getItem('timeType') || 'digital',
hourColour: localStorage.getItem('hourColour') || '#ffffff',
minuteColour: localStorage.getItem('minuteColour') || '#ffffff',
};
}
import { MdRefresh } from 'react-icons/md';
updateColour(type, event) {
const TimeSettings = () => {
const [timeType, setTimeType] = useState(localStorage.getItem('timeType') || 'digital');
const [hourColour, setHourColour] = useState(localStorage.getItem('hourColour') || '#ffffff');
const [minuteColour, setMinuteColour] = useState(
localStorage.getItem('minuteColour') || '#ffffff',
);
const updateColour = (type, event) => {
const colour = event.target.value;
this.setState({ [type]: colour });
if (type === 'hourColour') {
setHourColour(colour);
} else if (type === 'minuteColour') {
setMinuteColour(colour);
}
localStorage.setItem(type, colour);
};
let timeSettings = null;
const TIME_SECTION = 'modals.main.settings.sections.time';
const WidgetType = () => {
return (
<SettingsItem
title={variables.getMessage(`${TIME_SECTION}.type`)}
subtitle={variables.getMessage(`${TIME_SECTION}.type_subtitle`)}
final={timeType === 'percentageComplete'}
>
<Dropdown name="timeType" onChange={(value) => setTimeType(value)} category="clock">
<option value="digital">{variables.getMessage(`${TIME_SECTION}.digital.title`)}</option>
<option value="analogue">{variables.getMessage(`${TIME_SECTION}.analogue.title`)}</option>
<option value="percentageComplete">
{variables.getMessage(`${TIME_SECTION}.percentage_complete`)}
</option>
<option value="verticalClock">
{variables.getMessage(`${TIME_SECTION}.vertical_clock.title`)}
</option>
</Dropdown>
</SettingsItem>
);
};
const digitalSettings = (
<SettingsItem
title={variables.getMessage(`${TIME_SECTION}.digital.title`)}
subtitle={variables.getMessage(`${TIME_SECTION}.digital.subtitle`)}
final={true}
>
<Radio
name="timeformat"
options={[
{
name: variables.getMessage(`${TIME_SECTION}.digital.twentyfourhour`),
value: 'twentyfourhour',
},
{
name: variables.getMessage(`${TIME_SECTION}.digital.twelvehour`),
value: 'twelvehour',
},
]}
smallTitle={true}
category="clock"
/>
<Checkbox
name="seconds"
text={variables.getMessage(`${TIME_SECTION}.digital.seconds`)}
category="clock"
/>
<Checkbox
name="zero"
text={variables.getMessage(`${TIME_SECTION}.digital.zero`)}
category="clock"
/>
</SettingsItem>
);
const analogSettings = (
<SettingsItem
title={variables.getMessage(`${TIME_SECTION}.analogue.title`)}
subtitle={variables.getMessage(`${TIME_SECTION}.analogue.subtitle`)}
final={true}
>
<Checkbox
name="secondHand"
text={variables.getMessage(`${TIME_SECTION}.analogue.second_hand`)}
category="clock"
/>
<Checkbox
name="minuteHand"
text={variables.getMessage(`${TIME_SECTION}.analogue.minute_hand`)}
category="clock"
/>
<Checkbox
name="hourHand"
text={variables.getMessage(`${TIME_SECTION}.analogue.hour_hand`)}
category="clock"
/>
<Checkbox
name="hourMarks"
text={variables.getMessage(`${TIME_SECTION}.analogue.hour_marks`)}
category="clock"
/>
<Checkbox
name="minuteMarks"
text={variables.getMessage(`${TIME_SECTION}.analogue.minute_marks`)}
category="clock"
/>
<Checkbox
name="roundClock"
text={variables.getMessage(`${TIME_SECTION}.analogue.round_clock`)}
category="clock"
/>
</SettingsItem>
);
const verticalClock = (
<>
<SettingsItem
title={variables.getMessage(
'modals.main.settings.sections.time.vertical_clock.change_hour_colour',
)}
>
<div className="colourInput">
<input
type="color"
name="hourColour"
className="minuteColour"
onChange={(event) => updateColour('hourColour', event)}
value={hourColour}
></input>
<label htmlFor={'hourColour'} className="customBackgroundHex">
{hourColour}
</label>
</div>
<span className="link" onClick={() => localStorage.setItem('hourColour', '#ffffff')}>
<MdRefresh />
{variables.getMessage('modals.main.settings.buttons.reset')}
</span>
</SettingsItem>
<SettingsItem
title={variables.getMessage(
'modals.main.settings.sections.time.vertical_clock.change_minute_colour',
)}
>
<div className="colourInput">
<input
type="color"
name="minuteColour"
className="minuteColour"
onChange={(event) => updateColour('minuteColour', event)}
value={minuteColour}
></input>
<label htmlFor={'minuteColour'} className="customBackgroundHex">
{minuteColour}
</label>
</div>
<span className="link" onClick={() => localStorage.setItem('minuteColour', '#ffffff')}>
<MdRefresh />
{variables.getMessage('modals.main.settings.buttons.reset')}
</span>
</SettingsItem>
{digitalSettings}
</>
);
switch (timeType) {
case 'digital':
timeSettings = digitalSettings;
break;
case 'analogue':
timeSettings = analogSettings;
break;
case 'verticalClock':
timeSettings = verticalClock;
break;
default:
timeSettings = null;
}
render() {
let timeSettings = null;
const TIME_SECTION = 'modals.main.settings.sections.time';
const WidgetType = () => {
return (
<SettingsItem
title={variables.getMessage(`${TIME_SECTION}.type`)}
subtitle={variables.getMessage(`${TIME_SECTION}.type_subtitle`)}
final={this.state.timeType === 'percentageComplete'}
>
<Dropdown
name="timeType"
onChange={(value) => this.setState({ timeType: value })}
category="clock"
>
<option value="digital">{variables.getMessage(`${TIME_SECTION}.digital.title`)}</option>
<option value="analogue">
{variables.getMessage(`${TIME_SECTION}.analogue.title`)}
</option>
<option value="percentageComplete">
{variables.getMessage(`${TIME_SECTION}.percentage_complete`)}
</option>
<option value="verticalClock">
{variables.getMessage(`${TIME_SECTION}.vertical_clock.title`)}
</option>
</Dropdown>
</SettingsItem>
);
};
const digitalSettings = (
<SettingsItem
title={variables.getMessage(`${TIME_SECTION}.digital.title`)}
subtitle={variables.getMessage(`${TIME_SECTION}.digital.subtitle`)}
final={true}
>
<Radio
name="timeformat"
options={[
{
name: variables.getMessage(`${TIME_SECTION}.digital.twentyfourhour`),
value: 'twentyfourhour',
},
{
name: variables.getMessage(`${TIME_SECTION}.digital.twelvehour`),
value: 'twelvehour',
},
]}
smallTitle={true}
category="clock"
/>
<Checkbox
name="seconds"
text={variables.getMessage(`${TIME_SECTION}.digital.seconds`)}
category="clock"
/>
<Checkbox
name="zero"
text={variables.getMessage(`${TIME_SECTION}.digital.zero`)}
category="clock"
/>
</SettingsItem>
);
const analogSettings = (
<SettingsItem
title={variables.getMessage(`${TIME_SECTION}.analogue.title`)}
subtitle={variables.getMessage(`${TIME_SECTION}.analogue.subtitle`)}
final={true}
>
<Checkbox
name="secondHand"
text={variables.getMessage(`${TIME_SECTION}.analogue.second_hand`)}
category="clock"
/>
<Checkbox
name="minuteHand"
text={variables.getMessage(`${TIME_SECTION}.analogue.minute_hand`)}
category="clock"
/>
<Checkbox
name="hourHand"
text={variables.getMessage(`${TIME_SECTION}.analogue.hour_hand`)}
category="clock"
/>
<Checkbox
name="hourMarks"
text={variables.getMessage(`${TIME_SECTION}.analogue.hour_marks`)}
category="clock"
/>
<Checkbox
name="minuteMarks"
text={variables.getMessage(`${TIME_SECTION}.analogue.minute_marks`)}
category="clock"
/>
<Checkbox
name="roundClock"
text={variables.getMessage(`${TIME_SECTION}.analogue.round_clock`)}
category="clock"
/>
</SettingsItem>
);
const verticalClock = (
<>
<SettingsItem
title={variables.getMessage(
'modals.main.settings.sections.time.vertical_clock.change_hour_colour',
)}
>
<div className="colourInput">
<input
type="color"
name="hourColour"
className="minuteColour"
onChange={(event) => this.updateColour('hourColour', event)}
value={this.state.hourColour}
></input>
<label htmlFor={'hourColour'} className="customBackgroundHex">
{this.state.hourColour}
</label>
</div>
<span className="link" onClick={() => localStorage.setItem('hourColour', '#ffffff')}>
{variables.getMessage('modals.main.settings.buttons.reset')}
</span>
</SettingsItem>
<SettingsItem
title={variables.getMessage(
'modals.main.settings.sections.time.vertical_clock.change_minute_colour',
)}
>
<div className="colourInput">
<input
type="color"
name="minuteColour"
className="minuteColour"
onChange={(event) => this.updateColour('minuteColour', event)}
value={this.state.minuteColour}
></input>
<label htmlFor={'minuteColour'} className="customBackgroundHex">
{this.state.minuteColour}
</label>
</div>
<span className="link" onClick={() => localStorage.setItem('minuteColour', '#ffffff')}>
{variables.getMessage('modals.main.settings.buttons.reset')}
</span>
</SettingsItem>
{digitalSettings}
</>
);
switch (this.state.timeType) {
case 'digital':
timeSettings = digitalSettings;
break;
case 'analogue':
timeSettings = analogSettings;
break;
case 'verticalClock':
timeSettings = verticalClock;
break;
default:
timeSettings = null;
}
return (
<>
<Header
title={variables.getMessage(`${TIME_SECTION}.title`)}
setting="time"
category="clock"
element=".clock-container"
zoomSetting="zoomClock"
switch={true}
/>
return (
<>
<Header
title={variables.getMessage(`${TIME_SECTION}.title`)}
setting="time"
category="clock"
element=".clock-container"
zoomSetting="zoomClock"
switch={true}
/>
<PreferencesWrapper setting="time" zoomSetting="zoomClock" switch={true}>
<WidgetType />
{timeSettings}
</>
);
}
}
</PreferencesWrapper>
</>
);
};
export default TimeSettings;

View File

@ -11,7 +11,7 @@ import { TextField } from '@mui/material';
import SettingsItem from '../SettingsItem';
import PreferencesWrapper from '../PreferencesWrapper';
export default class TimeSettings extends PureComponent {
export default class WeatherSettings extends PureComponent {
constructor() {
super();
this.state = {

View File

@ -205,13 +205,15 @@ export default class BackgroundSettings extends PureComponent {
</SettingsItem>
{this.state.backgroundAPI === 'unsplash' && (
<SettingsItem
title="Unsplash Collection(s)"
subtitle="Select the collection(s) you want to use for your background"
title={variables.getMessage('modals.main.settings.sections.background.unsplash.title')}
subtitle={variables.getMessage('modals.main.settings.sections.background.subtitle')}
final={true}
>
<Text
title="Collection ID(s)"
subtitle="Enter the collection ID(s) you want to use for your background"
title={variables.getMessage('modals.main.settings.sections.background.id')}
subtitle={variables.getMessage(
'modals.main.settings.sections.background.id_subtitle',
)}
placeholder="e.g. 123456, 654321"
name="unsplashCollections"
category="background"

View File

@ -1,28 +0,0 @@
const getTitleFromUrl = async (url) => {
let title;
try {
let response = await fetch(url);
if (response.redirected) {
response = await fetch(response.url);
}
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
title = doc.title;
} catch (e) {
title = url;
}
return title;
};
const isValidUrl = (url) => {
// regex: https://ihateregex.io/expr/url/
// eslint-disable-next-line no-useless-escape
const urlRegex =
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_.~#?&=]*)/;
return urlRegex.test(url);
};
export { getTitleFromUrl, isValidUrl };

View File

@ -0,0 +1,147 @@
// TODO: make it work with pins or on click or smth
import variables from 'modules/variables';
import { PureComponent, memo, useState } from 'react';
import { MdLinkOff, MdOutlineApps } from 'react-icons/md';
import Tooltip from 'components/helpers/tooltip/Tooltip';
import { shift, useFloating } from '@floating-ui/react-dom';
import EventBus from 'modules/helpers/eventbus';
class Apps extends PureComponent {
constructor() {
super();
this.state = {
apps: JSON.parse(localStorage.getItem('applinks')),
visibility: localStorage.getItem('appsPinned') === 'true' ? 'visible' : 'hidden',
marginLeft: localStorage.getItem('refresh') === 'false' ? '-200px' : '-130px',
showApps: localStorage.getItem('appsPinned') === 'true',
};
}
setZoom() {
this.setState({
zoomFontSize: Number(((localStorage.getItem('zoomNavbar') || 100) / 100) * 1.2) + 'rem',
});
}
componentDidMount() {
EventBus.on('refresh', (data) => {
if (data === 'navbar') {
this.forceUpdate();
try {
this.setZoom();
} catch (e) {}
}
});
this.setZoom();
}
componentWillUnmount() {
EventBus.off('refresh');
}
showApps() {
this.setState({
showApps: true,
});
}
hideApps() {
this.setState({
showApps: localStorage.getItem('AppsPinned') === 'true',
});
}
render() {
const appsInfo = this.state.apps;
return (
<div className="notes" onMouseLeave={() => this.hideApps()} onFocus={() => this.showApps()}>
<button
className="first"
onMouseEnter={() => this.showApps()}
onFocus={() => this.hideApps()}
onBlur={() => this.showApps()}
ref={this.props.appsRef}
style={{ fontSize: this.state.zoomFontSize }}
>
<MdOutlineApps className="topicons" />
</button>
{this.state.showApps && (
<span
className="notesContainer"
ref={this.props.floatRef}
style={{
position: this.props.position,
top: this.props.yPosition ?? '44px',
left: this.props.xPosition ?? '',
}}
>
{appsInfo.length > 0 ? (
<div className="appsShortcutContainer">
{appsInfo.map((info, i) => (
<Tooltip
title={info.name.split(' ')[0]}
subtitle={info.name.split(' ').slice(1).join(' ')}
key={i}
>
<a href={info.url} className="appsIcon">
<img
src={
info.icon === ''
? `https://icon.horse/icon/ ${info.url.replace('https://', '').replace('http://', '')}`
: info.icon
}
width="40px"
height="40px"
alt="Google"
/>
<span>{info.name}</span>
</a>
</Tooltip>
))}
</div>
) : (
<div className="noAppsContainer">
<div className="emptyNewMessage">
<MdLinkOff />
<span className="title">
{variables.language.getMessage(
variables.languagecode,
'widgets.navbar.apps.no_apps',
)}
</span>
</div>
</div>
)}
</span>
)}
</div>
);
}
}
function AppsWrapper() {
const [reference, setReference] = useState(null);
const { x, y, refs, strategy } = useFloating({
placement: 'bottom',
middleware: [shift()],
elements: {
reference,
},
});
return (
<Apps
appsRef={setReference}
floatRef={refs.setFloating}
position={strategy}
xPosition={x}
yPosition={y}
/>
);
}
export default memo(AppsWrapper);

View File

@ -1,10 +1,11 @@
import variables from 'modules/variables';
import { PureComponent, createRef } from 'react';
import { MdRefresh, MdSettings, MdOutlineApps } from 'react-icons/md';
import { MdRefresh, MdSettings } from 'react-icons/md';
import Notes from './Notes';
import Todo from './Todo';
import Apps from './Apps';
import Maximise from '../background/Maximise';
import Tooltip from 'components/helpers/tooltip/Tooltip';
@ -109,6 +110,9 @@ class Navbar extends PureComponent {
{localStorage.getItem('todoEnabled') === 'true' && (
<Todo fontSize={this.state.zoomFontSize} />
)}
{localStorage.getItem('appsEnabled') === 'true' && (
<Apps fontSize={this.state.zoomFontSize} />
)}
{this.refreshEnabled !== 'false' && (
<Tooltip
@ -125,20 +129,6 @@ class Navbar extends PureComponent {
</Tooltip>
)}
{localStorage.getItem('appsEnabled') === 'true' && (
<>
<Tooltip title={variables.getMessage('widgets.navbar.apps.title')}>
<button
style={{ fontSize: this.state.zoomFontSize }}
onClick={() => this.props.openModal('appsModal')}
id="appsShortcutBtn"
>
<MdOutlineApps className="topicons" />
</button>
</Tooltip>
</>
)}
<Tooltip
title={variables.getMessage('modals.main.navbar.settings', {
type: variables.getMessage(

View File

@ -20,6 +20,8 @@ $appsWidth: 21rem;
}
.noAppsContainer {
grid-column: 1/3;
h3 {
margin: 0;
display: flex;

View File

@ -1,5 +1,6 @@
@import 'notes';
@import 'todo';
@import 'apps';
@import 'scss/variables';
.navbar {

View File

@ -17,7 +17,7 @@ const convertTemperature = (temp, format) => {
return Math.round(temp);
};
export default class Weather extends PureComponent {
export default class WeatherSettings extends PureComponent {
constructor() {
super();
this.state = {

View File

@ -121,3 +121,30 @@ export function values(type) {
return marks[type] || [];
}
export async function getTitleFromUrl(url) {
let title;
try {
let response = await fetch(url);
if (response.redirected) {
response = await fetch(response.url);
}
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
title = doc.title;
} catch (e) {
title = url;
}
return title;
}
export function isValidUrl(url) {
// regex: https://ihateregex.io/expr/url/
// eslint-disable-next-line no-useless-escape
const urlRegex =
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_.~#?&=]*)/;
return urlRegex.test(url);
}

View File

@ -129,7 +129,12 @@ body {
}
}
.inactiveSetting {
.preferences {
transition: 0.4s ease-in-out;
}
.preferencesInactive {
opacity: 0.5;
pointer-events: none;
transition: 0.4s ease-in-out;
}

View File

@ -266,13 +266,13 @@
"search": {
"title": "Search",
"additional": "Additional options for search widget display and functionality",
"search_engine": "Search engine",
"search_engine": "Search Engine",
"search_engine_subtitle": "Choose search engine to use in the search bar",
"custom": "Custom search URL",
"custom": "Custom Search URL",
"autocomplete": "Autocomplete",
"autocomplete_provider": "Autocomplete Provider",
"autocomplete_provider_subtitle": "Search engine to use for autocomplete dropdown results",
"voice_search": "Voice search",
"voice_search": "Voice Search",
"dropdown": "Search dropdown",
"focus": "Focus on tab open"
},
@ -354,7 +354,7 @@
"navbar": {
"title": "Navbar",
"notes": "Notes",
"refresh": "Refresh button",
"refresh": "Refresh Button",
"refresh_subtitle": "Choose what is refreshed when you click the refresh button",
"hover": "Only display on hover",
"additional": "Modify navbar style and which buttons you want to display",
@ -396,8 +396,8 @@
"old": "Old",
"none": "None"
},
"widget_zoom": "Widget zoom",
"toast_duration": "Toast duration",
"widget_zoom": "Widget Zoom",
"toast_duration": "Toast Duration",
"milliseconds": "milliseconds"
}
},

View File

@ -226,6 +226,12 @@
"random_colour": "Random colour",
"random_gradient": "Random gradient"
},
"unsplash": {
"title": "Unsplash Collection(s)",
"subtitle": "Select the collection(s) you want to use for your background",
"id": "Collection ID(s)",
"id_subtitle": "Enter a comma separated list of Unsplash IDs"
},
"source": {
"title": "Source",
"subtitle": "Select where to get background images from",
@ -270,14 +276,14 @@
"search": {
"title": "Search",
"additional": "Additional options for search widget display and functionality",
"search_engine": "Search engine",
"search_engine": "Search Engine",
"search_engine_subtitle": "Choose search engine to use in the search bar",
"custom": "Custom search URL",
"custom": "Custom Search URL",
"autocomplete": "Autocomplete",
"autocomplete_provider": "Autocomplete Provider",
"autocomplete_provider_subtitle": "Search engine to use for autocomplete dropdown results",
"voice_search": "Voice search",
"dropdown": "Search dropdown",
"dropdown": "Search Dropdown",
"focus": "Focus on tab open"
},
"weather": {
@ -328,7 +334,7 @@
"text_only": "Text Only",
"metro": "Metro"
},
"styling": "Quick Links Styling",
"styling": "Styling",
"styling_description": "Customise Quick Links appearance"
},
"message": {
@ -358,7 +364,7 @@
"navbar": {
"title": "Navbar",
"notes": "Notes",
"refresh": "Refresh button",
"refresh": "Refresh Button",
"refresh_subtitle": "Choose what is refreshed when you click the refresh button",
"hover": "Only display on hover",
"additional": "Modify navbar style and which buttons you want to display",
@ -401,8 +407,8 @@
"old": "Old",
"none": "None"
},
"widget_zoom": "Widget zoom",
"toast_duration": "Toast duration",
"widget_zoom": "Widget Zoom",
"toast_duration": "Toast Duration",
"milliseconds": "milliseconds"
}
},

View File

@ -270,14 +270,14 @@
"search": {
"title": "Search",
"additional": "Additional options for search widget display and functionality",
"search_engine": "Search engine",
"search_engine": "Search Engine",
"search_engine_subtitle": "Choose search engine to use in the search bar",
"custom": "Custom search URL",
"custom": "Custom Search URL",
"autocomplete": "Autocomplete",
"autocomplete_provider": "Autocomplete Provider",
"autocomplete_provider_subtitle": "Search engine to use for autocomplete dropdown results",
"voice_search": "Voice search",
"dropdown": "Search dropdown",
"voice_search": "Voice Search",
"dropdown": "Search Dropdown",
"focus": "Focus on tab open"
},
"weather": {
@ -328,7 +328,7 @@
"text_only": "Text Only",
"metro": "Metro"
},
"styling": "Quick Links Styling",
"styling": "Styling",
"styling_description": "Customise Quick Links appearance"
},
"message": {
@ -358,7 +358,7 @@
"navbar": {
"title": "Navbar",
"notes": "Notes",
"refresh": "Refresh button",
"refresh": "Refresh Button",
"refresh_subtitle": "Choose what is refreshed when you click the refresh button",
"hover": "Only display on hover",
"additional": "Modify navbar style and which buttons you want to display",
@ -402,7 +402,7 @@
"none": "None"
},
"widget_zoom": "Widget zoom",
"toast_duration": "Toast duration",
"toast_duration": "Toast Duration",
"milliseconds": "milliseconds"
}
},

View File

@ -266,7 +266,7 @@
"search": {
"title": "Búsqueda",
"additional": "Additional options for search widget display and functionality",
"search_engine": "Motor de búsqueda",
"search_engine": "Motor de Búsqueda",
"search_engine_subtitle": "Choose search engine to use in the search bar",
"custom": "URL de búsqueda personalizada",
"autocomplete": "Autocompletado",
@ -324,7 +324,7 @@
"text_only": "Text Only",
"metro": "Metro"
},
"styling": "Quick Links Styling",
"styling": "Styling",
"styling_description": "Customise Quick Links appearance"
},
"message": {

View File

@ -266,7 +266,7 @@
"search": {
"title": "Barre de Recherche",
"additional": "Additional options for search widget display and functionality",
"search_engine": "Moteur de recherche",
"search_engine": "Moteur de Recherche",
"search_engine_subtitle": "Choose search engine to use in the search bar",
"custom": "URL de recherche personnalisée",
"autocomplete": "Autocomplete",
@ -324,7 +324,7 @@
"text_only": "Text Only",
"metro": "Metro"
},
"styling": "Quick Links Styling",
"styling": "Styling",
"styling_description": "Customise Quick Links appearance"
},
"message": {
@ -354,7 +354,7 @@
"navbar": {
"title": "Navbar",
"notes": "Notes",
"refresh": "Refresh button",
"refresh": "Refresh Button",
"refresh_subtitle": "Choose what is refreshed when you click the refresh button",
"hover": "Only display on hover",
"additional": "Modify navbar style and which buttons you want to display",

View File

@ -268,7 +268,7 @@
"additional": "Additional options for search widget display and functionality",
"search_engine": "Mesin pencari",
"search_engine_subtitle": "Choose search engine to use in the search bar",
"custom": "URL kustom",
"custom": "URL Kustom",
"autocomplete": "Autocomplete",
"autocomplete_provider": "Provider Autocomplete",
"autocomplete_provider_subtitle": "Search engine to use for autocomplete dropdown results",
@ -324,7 +324,7 @@
"text_only": "Text Only",
"metro": "Metro"
},
"styling": "Quick Links Styling",
"styling": "Styling",
"styling_description": "Customise Quick Links appearance"
},
"message": {
@ -396,7 +396,7 @@
"old": "Old",
"none": "None"
},
"widget_zoom": "Widget zoom",
"widget_zoom": "Widget Zoom",
"toast_duration": "Durasi toast",
"milliseconds": "milliseconds"
}

View File

@ -174,8 +174,8 @@
"title": "Widget text shadow"
},
"title": "Accessibility",
"toast_duration": "Toast duration",
"widget_zoom": "Widget zoom"
"toast_duration": "Toast Duration",
"widget_zoom": "Widget Zoom"
},
"font": {
"custom": "Custom font",
@ -205,7 +205,7 @@
"apps_subtitle": "Maak een snelkoppeling van je andere vaak gebruikte websites.",
"hover": "Only display on hover",
"notes": "Notes",
"refresh": "Refresh button",
"refresh": "Refresh Button",
"refresh_options": {
"none": "None",
"page": "Page"

View File

@ -268,7 +268,7 @@
"additional": "Additional options for search widget display and functionality",
"search_engine": "Søkemotor",
"search_engine_subtitle": "Choose search engine to use in the search bar",
"custom": "Custom search URL",
"custom": "Custom Search",
"autocomplete": "Autocomplete",
"autocomplete_provider": "Autocomplete Provider",
"autocomplete_provider_subtitle": "Search engine to use for autocomplete dropdown results",
@ -324,7 +324,7 @@
"text_only": "Text Only",
"metro": "Metro"
},
"styling": "Quick Links Styling",
"styling": "Styling",
"styling_description": "Customise Quick Links appearance"
},
"message": {
@ -354,7 +354,7 @@
"navbar": {
"title": "Navbar",
"notes": "Notes",
"refresh": "Refresh button",
"refresh": "Refresh Button",
"refresh_subtitle": "Choose what is refreshed when you click the refresh button",
"hover": "Only display on hover",
"additional": "Modify navbar style and which buttons you want to display",
@ -396,8 +396,8 @@
"old": "Old",
"none": "None"
},
"widget_zoom": "Widget zoom",
"toast_duration": "Toast duration",
"widget_zoom": "Widget Zoom",
"toast_duration": "Toast Duration",
"milliseconds": "milliseconds"
}
},

View File

@ -396,7 +396,7 @@
"old": "Old",
"none": "None"
},
"widget_zoom": "Widget zoom",
"widget_zoom": "Widget Zoom",
"toast_duration": "Продолжительность подсказки",
"milliseconds": "миллисекунды"
}

View File

@ -324,7 +324,7 @@
"text_only": "Text Only",
"metro": "Metro"
},
"styling": "Quick Links Styling",
"styling": "Styling",
"styling_description": "Customise Quick Links appearance"
},
"message": {