refactor: Convert pure components to functional components

Co-authored-by: David Ralph <me@davidcralph.co.uk>
Co-authored-by: Isaac <contact@eartharoid.me>
This commit is contained in:
alexsparkes
2024-06-14 18:51:36 +01:00
parent 0f9393d3e6
commit 4747dea06b
37 changed files with 1718 additions and 1849 deletions

View File

@@ -1,29 +1,26 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { useState } from 'react';
import { MdStar, MdStarBorder } from 'react-icons/md';
class Favourite extends PureComponent {
buttons = {
favourited: <MdStar onClick={() => this.favourite()} className="topicons" />,
unfavourited: <MdStarBorder onClick={() => this.favourite()} className="topicons" />,
function Favourite({ credit, offline, pun, tooltipText }) {
const [favourited, setFavourited] = useState(
localStorage.getItem('favourite') ? (
<MdStar onClick={() => favourite()} className="topicons" />
) : (
<MdStarBorder onClick={() => favourite()} className="topicons" />
),
);
const buttons = {
favourited: <MdStar onClick={() => favourite()} className="topicons" />,
unfavourited: <MdStarBorder onClick={() => favourite()} className="topicons" />,
};
constructor() {
super();
this.state = {
favourited: localStorage.getItem('favourite')
? this.buttons.favourited
: this.buttons.unfavourited,
};
}
async favourite() {
async function favourite() {
if (localStorage.getItem('favourite')) {
localStorage.removeItem('favourite');
this.setState({
favourited: this.buttons.unfavourited,
});
this.props.tooltipText(variables.getMessage('widgets.quote.favourite'));
setFavourited(buttons.unfavourited);
tooltipText(variables.getMessage('widgets.quote.favourite'));
variables.stats.postEvent('feature', 'Background favourite');
} else {
const type = localStorage.getItem('backgroundType');
@@ -57,58 +54,57 @@ class Favourite extends PureComponent {
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(await (await fetch(url)).blob());
});
if (type === 'custom') {
localStorage.setItem(
'favourite',
JSON.stringify({
type,
url,
}),
);
} else {
const location = document.getElementById('infoLocation');
const camera = document.getElementById('infoCamera');
localStorage.setItem(
'favourite',
JSON.stringify({
type,
url,
credit,
location: location?.innerText,
camera: camera?.innerText,
resolution: document.getElementById('infoResolution').textContent || '',
offline,
pun,
}),
);
setFavourited(buttons.favourited);
tooltipText(variables.getMessage('widgets.quote.unfavourite'));
variables.stats.postEvent('feature', 'Background unfavourite');
}
}
if (type === 'custom') {
localStorage.setItem(
'favourite',
JSON.stringify({
type,
url,
}),
);
} else {
// photo information now hides information if it isn't sent, unless if photoinformation hover is hidden
const location = document.getElementById('infoLocation');
const camera = document.getElementById('infoCamera');
localStorage.setItem(
'favourite',
JSON.stringify({
type,
url,
credit: this.props.credit || '',
location: location?.innerText,
camera: camera?.innerText,
resolution: document.getElementById('infoResolution').textContent || '',
offline: this.props.offline,
pun: this.props.pun,
}),
);
}
return setFavourited(buttons.favourited);
}
this.setState({
favourited: this.buttons.favourited,
});
this.props.tooltipText(variables.getMessage('widgets.quote.unfavourite'));
variables.stats.postEvent('feature', 'Background unfavourite');
}
}
render() {
if (localStorage.getItem('backgroundType') === 'colour') {
return null;
}
this.props.tooltipText(
localStorage.getItem('favourite')
? variables.getMessage('widgets.quote.unfavourite')
: variables.getMessage('widgets.quote.favourite'),
);
return this.state.favourited;
if (localStorage.getItem('backgroundType') === 'colour') {
return null;
}
tooltipText(
localStorage.getItem('favourite')
? variables.getMessage('widgets.quote.unfavourite')
: variables.getMessage('widgets.quote.favourite'),
);
return favourited;
}
export default Favourite;

View File

@@ -1,5 +1,5 @@
import variables from 'config/variables';
import { PureComponent, createRef } from 'react';
import { useRef, useState, useEffect } from 'react';
import { nth, convertTimezone } from 'utils/date';
import EventBus from 'utils/eventbus';
@@ -8,15 +8,11 @@ import defaults from './options/default';
import './greeting.scss';
const isEventsEnabled = localStorage.getItem('events') !== 'false';
export default class Greeting extends PureComponent {
constructor() {
super();
this.state = {
greeting: '',
};
this.timer = undefined;
this.greeting = createRef();
}
function Greeting() {
const [greeting, setGreeting] = useState('');
const timer = useRef(null);
const greetingRef = useRef(null);
/**
* Change the greeting message for the events of Christmas, New Year and Halloween.
@@ -25,7 +21,8 @@ export default class Greeting extends PureComponent {
* @param {String} message The current greeting message.
* @returns The message variable is being returned.
*/
doEvents(time, message) {
const doEvents = (time, message) => {
if (!isEventsEnabled) {
return message;
}
@@ -49,15 +46,15 @@ export default class Greeting extends PureComponent {
* It takes a date object and returns the age of the person in years.
* @param {Date} date The date of birth.
* @returns The age of the person.
*/
calculateAge(date) {
* */
const calculateAge = (date) => {
const diff = Date.now() - date.getTime();
const birthday = new Date(diff);
return Math.abs(birthday.getUTCFullYear() - 1970);
}
getGreeting(time = 60000 - (Date.now() % 60000)) {
this.timer = setTimeout(() => {
const getGreeting = (time = 60000 - (Date.now() % 60000)) => {
timer.current = setTimeout(() => {
let now = new Date();
const timezone = localStorage.getItem('timezone');
if (timezone && timezone !== 'auto') {
@@ -84,7 +81,7 @@ export default class Greeting extends PureComponent {
if (custom === 'false') {
message = '';
} else {
message = this.doEvents(now, message);
message = doEvents(now, message);
}
// Name
@@ -108,9 +105,9 @@ export default class Greeting extends PureComponent {
const birth = new Date(localStorage.getItem('birthday'));
if (birth.getDate() === now.getDate() && birth.getMonth() === now.getMonth()) {
if (localStorage.getItem('birthdayage') === 'true' && this.calculateAge(birth) !== 0) {
if (localStorage.getItem('birthdayage') === 'true' && calculateAge(birth) !== 0) {
const text = variables.getMessage('widgets.greeting.birthday').split(' ');
message = `${text[0]} ${nth(this.calculateAge(birth))} ${text[1]}`;
message = `${text[0]} ${nth(calculateAge(birth))} ${text[1]}`;
} else {
message = variables.getMessage('widgets.greeting.birthday');
}
@@ -118,49 +115,45 @@ export default class Greeting extends PureComponent {
}
// Set the state to the greeting string
this.setState({
greeting: `${message}${name}`,
});
setGreeting(`${message}${name}`);
this.getGreeting();
getGreeting();
}, time);
}
componentDidMount() {
useEffect(() => {
EventBus.on('refresh', (data) => {
if (data === 'greeting' || data === 'timezone') {
if (localStorage.getItem('greeting') === 'false') {
return (this.greeting.current.style.display = 'none');
return (greetingRef.current.style.display = 'none');
}
this.timer = null;
this.getGreeting(0);
timer.current = null;
getGreeting(0);
this.greeting.current.style.display = 'block';
this.greeting.current.style.fontSize = `${
greetingRef.current.style.display = 'block';
greetingRef.current.style.fontSize = `${
1.6 * Number((localStorage.getItem('zoomGreeting') || defaults.zoomGreeting) / 100)
}em`;
}
});
// this comment can apply to all widget zoom features apart from the general one in the Accessibility section
// in a nutshell: 1.6 is the current font size, and we do "localstorage || 100" so we don't have to try that 4.0 -> 5.0 thing again
this.greeting.current.style.fontSize = `${
greetingRef.current.style.fontSize = `${
1.6 * Number((localStorage.getItem('zoomGreeting') || defaults.zoomGreeting) / 100)
}em`;
this.getGreeting(0);
}
getGreeting(0);
componentWillUnmount() {
EventBus.off('refresh');
}
return () => {
EventBus.off('refresh');
}
}, []);
render() {
return (
<span className="greeting" ref={this.greeting}>
{this.state.greeting}
</span>
);
}
return (
<span className="greeting" ref={greetingRef}>
{greeting}
</span>
);
}
export default Greeting;

View File

@@ -13,6 +13,7 @@ import { Checkbox, Switch, Text } from 'components/Form/Settings';
import { TextareaAutosize } from '@mui/material';
import { Button } from 'components/Elements';
import { toast } from 'react-toastify';
import { useTab } from 'components/Elements/MainModal/backend/TabContext';
import defaults from './default';
import defaultEvents from '../events.json';
@@ -20,6 +21,7 @@ import defaultEvents from '../events.json';
import { MdEventNote, MdAdd, MdCancel, MdRefresh } from 'react-icons/md';
const GreetingOptions = () => {
const { subSection } = useTab();
const [customEvents, setCustomEvents] = useState(
JSON.parse(localStorage.getItem('customEvents')) || defaultEvents
);
@@ -297,7 +299,8 @@ const GreetingOptions = () => {
return (
<>
{header}
{events ? (
{subSection}
{subSection === "events" ? (
<>
<Row>
<Content
@@ -327,6 +330,7 @@ const GreetingOptions = () => {
>
<AdditionalOptions />
<Section
id="events"
title={variables.getMessage(`${GREETING_SECTION}.events`)}
subtitle={variables.getMessage(`${GREETING_SECTION}.events_description`)}
onClick={() => setEvents(true)}

View File

@@ -0,0 +1,3 @@
import sortItems from './sort';
export { sortItems };

View File

@@ -0,0 +1,42 @@
import variables from 'config/variables';
export default function SortItems(data, method = 'a-z') {
//const sort = localStorage.getItem('sortMarketplace') || method;
const sort = method;
variables.stats.postEvent('marketplace', 'sort');
if (!data) {
return data;
}
if (sort === 'a-z') {
return data.sort((a, b) => {
if (a.display_name < b.display_name) {
return -1;
}
if (a.display_name > b.display_name) {
return 1;
}
return 0;
});
}
if (sort === 'z-a') {
return data.sort((a, b) => {
if (a.display_name > b.display_name) {
return -1;
}
if (a.display_name < b.display_name) {
return 1;
}
return 0;
});
}
if (sort === 'newest') {
return data.reverse();
} else {
return data;
}
}

View File

@@ -12,6 +12,7 @@ import { Header, CustomActions } from 'components/Layout/Settings';
import { Button } from 'components/Elements';
import { install, uninstall } from 'utils/marketplace';
import { sortItems } from '../api';
export default class Added extends PureComponent {
constructor() {
@@ -130,42 +131,6 @@ export default class Added extends PureComponent {
variables.stats.postEvent('marketplace', 'Uninstall');
}
sortAddons(value, sendEvent) {
let installed = JSON.parse(localStorage.getItem('installed'));
switch (value) {
case 'newest':
installed.reverse();
break;
case 'oldest':
break;
case 'a-z':
installed.sort((a, b) => {
if (a.display_name < b.display_name) {
return -1;
}
if (a.display_name > b.display_name) {
return 1;
}
return 0;
});
break;
case 'z-a':
installed.sort();
installed.reverse();
break;
default:
break;
}
this.setState({
installed,
});
if (sendEvent) {
variables.stats.postEvent('marketplace', 'Sort');
}
}
updateCheck() {
let updates = 0;
this.state.installed.forEach(async (item) => {
@@ -207,7 +172,9 @@ export default class Added extends PureComponent {
}
componentDidMount() {
this.sortAddons(localStorage.getItem('sortAddons'), false);
this.setState({
installed: sortItems(JSON.parse(localStorage.getItem('installed')), 'newest'),
});
}
render() {
@@ -297,7 +264,7 @@ export default class Added extends PureComponent {
<Dropdown
label={variables.getMessage('modals.main.addons.sort.title')}
name="sortAddons"
onChange={(value) => this.sortAddons(value)}
onChange={(value) => this.setState({ installed: sortItems(this.state.installed, value) })}
items={[
{
value: 'newest',

View File

@@ -1,475 +1,332 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { useState, useEffect } from 'react';
import { toast } from 'react-toastify';
import {
MdWifiOff,
MdLocalMall,
MdClose,
MdSearch,
MdOutlineArrowForward,
MdLibraryAdd,
} from 'react-icons/md';
import { MdWifiOff, MdLocalMall, MdOutlineArrowForward, MdLibraryAdd } from 'react-icons/md';
import ItemPage from './ItemPage';
import Items from '../components/Items/Items';
import Dropdown from '../../../components/Form/Settings/Dropdown/Dropdown';
import { Header } from 'components/Layout/Settings';
import { Button } from 'components/Elements';
import { install, uninstall } from 'utils/marketplace';
import { install } from 'utils/marketplace';
import { sortItems } from '../api';
class Marketplace extends PureComponent {
constructor() {
super();
this.state = {
items: [],
button: '',
done: false,
item: {},
collection: false,
filter: '',
type: 'all',
};
this.buttons = {
uninstall: (
<Button
type="settings"
onClick={() => this.manage('uninstall')}
icon={<MdClose />}
label={variables.getMessage('modals.main.marketplace.product.buttons.remove')}
/>
),
install: (
<Button
type="settings"
onClick={() => this.manage('install')}
icon={<MdLibraryAdd />}
label={variables.getMessage('modals.main.marketplace.product.buttons.addtomue')}
/>
),
};
this.controller = new AbortController();
}
function Marketplace() {
const [items, setItems] = useState([]);
const [done, setDone] = useState(false);
const [item, setItem] = useState({});
const [collection, setCollection] = useState({});
const [filter, setFilter] = useState('');
const [type, setType] = useState('all');
const [busy, setBusy] = useState(false);
const [collectionTitle, setCollectionTitle] = useState('');
const controller = new AbortController();
async toggle(pageType, data) {
async function toggle(pageType, data) {
if (pageType === 'item') {
let info;
// get item info
try {
let type = this.props.type;
if (type === 'all' || type === 'collections') {
type = data.type;
}
info = await (
await fetch(`${variables.constants.API_URL}/marketplace/item/${type}/${data.name}`, {
signal: this.controller.signal,
})
).json();
} catch (e) {
if (this.controller.signal.aborted === false) {
return toast(variables.getMessage('toasts.error'));
}
}
const toggleType = type === 'all' || type === 'collections' ? data.type : type;
const item = await (
await fetch(`${variables.constants.API_URL}/marketplace/item/${toggleType}/${data.name}`, {
signal: controller.signal,
})
).json();
if (this.controller.signal.aborted === true) {
if (controller.signal.aborted === true) {
return;
}
// check if already installed
let button = this.buttons.install;
let addonInstalled = false;
let addonInstalledVersion;
const installed = JSON.parse(localStorage.getItem('installed'));
if (installed.some((item) => item.name === info.data.name)) {
button = this.buttons.uninstall;
if (installed.some((item) => item.name === item.data.name)) {
addonInstalled = true;
for (let i = 0; i < installed.length; i++) {
if (installed[i].name === info.data.name) {
if (installed[i].name === item.data.name) {
addonInstalledVersion = installed[i].version;
break;
}
}
}
this.setState({
item: {
onCollection: data._onCollection,
type: info.data.type,
display_name: info.data.name,
author: info.data.author,
description: info.data.description,
//updated: info.updated,
version: info.data.version,
icon: info.data.screenshot_url,
data: info.data,
addonInstalled,
addonInstalledVersion,
api_name: data.name,
setItem({
onCollection: data._onCollection,
type: item.data.type,
display_name: item.data.name,
author: item.data.author,
description: item.data.description,
version: item.data.version,
icon: item.data.screenshot_url,
data: item.data,
local: {
installed: addonInstalled,
version: addonInstalledVersion,
},
button: button,
slug: data.name,
});
document.querySelector('#modal').scrollTop = 0;
variables.stats.postEvent('marketplace-item', `${this.state.item.display_name} viewed`);
setType('item');
variables.stats.postEvent('marketplace-item', `${item.display_name} viewed`);
} else if (pageType === 'collection') {
this.setState({
done: false,
item: {},
});
setDone(false);
setItem({});
const collection = await (
await fetch(`${variables.constants.API_URL}/marketplace/collection/${data}`, {
signal: this.controller.signal,
signal: controller.signal,
})
).json();
this.setState({
items: collection.data.items,
collectionTitle: collection.data.display_name,
collectionDescription: collection.data.description,
collectionImg: collection.data.img,
collection: true,
done: true,
setItems(collection.data.items);
setCollection({
visible: true,
title: collection.data.display_name,
description: collection.data.description,
img: collection.data.img,
});
setType('collection');
} else {
this.setState({
item: {},
});
setItem({});
setCollection({});
setType('normal');
}
}
async getItems() {
this.setState({
done: false,
});
async function getItems() {
setDone(false);
const dataURL =
this.props.type === 'collections'
? variables.constants.API_URL + '/marketplace/collections'
: variables.constants.API_URL + '/marketplace/items/' + this.props.type;
variables.constants.API_URL +
(type === 'collections' ? '/marketplace/collections' : '/marketplace/items/' + type);
const { data } = await (
await fetch(dataURL, {
signal: this.controller.signal,
signal: controller.signal,
})
).json();
const collections = await (
if (controller.signal.aborted === true) {
return;
}
setItems(sortItems(data, 'z-a'));
setDone(true);
}
async function getCollections() {
setDone(false);
const { data } = await (
await fetch(variables.constants.API_URL + '/marketplace/collections', {
signal: this.controller.signal,
signal: controller.signal,
})
).json();
if (this.controller.signal.aborted === true) {
return;
}
const sorted = this.sortMarketplace(data, false);
this.setState({
items: sorted.items,
sortType: sorted.sortType,
oldItems: sorted.items,
collections: collections.data,
displayedCollection:
collections.data[Math.floor(Math.random() * collections.data.length)] || [],
done: true,
});
setItems(data);
setDone(true);
}
manage(type) {
if (type === 'install') {
install(this.state.item.type, this.state.item.data);
} else {
uninstall(this.state.item.type, this.state.item.display_name);
}
toast(variables.getMessage('toasts.' + type + 'ed'));
this.setState({
button: type === 'install' ? this.buttons.uninstall : this.buttons.install,
});
variables.stats.postEvent(
'marketplace-item',
`${this.state.item.display_name} ${type === 'install' ? 'installed' : 'uninstalled'}`,
);
variables.stats.postEvent('marketplace', type === 'install' ? 'Install' : 'Uninstall');
function returnToMain() {
setCollection(false);
}
async installCollection() {
this.setState({ busy: true });
try {
const installed = JSON.parse(localStorage.getItem('installed'));
for (const item of this.state.items) {
if (installed.some((i) => i.name === item.display_name)) continue; // don't install if already installed
let { data } = await (
await fetch(`${variables.constants.API_URL}/marketplace/item/${item.type}/${item.name}`, {
signal: this.controller.signal,
})
).json();
install(data.type, data, false, true);
variables.stats.postEvent('marketplace-item', `${item.display_name} installed}`);
variables.stats.postEvent('marketplace', 'Install');
}
toast(variables.getMessage('toasts.installed'));
window.location.reload();
} catch (error) {
console.error(error);
toast(variables.getMessage('toasts.error'));
} finally {
this.setState({ busy: false });
}
}
sortMarketplace(data, sendEvent) {
const value = localStorage.getItem('sortMarketplace') || 'a-z';
let items = data || this.state.items;
if (!items) {
return;
}
switch (value) {
case 'a-z':
// sort by name key alphabetically
const sorted = items.sort((a, b) => {
if (a.display_name < b.display_name) {
return -1;
}
if (a.display_name > b.display_name) {
return 1;
}
return 0;
});
items = sorted;
break;
case 'z-a':
items.sort();
items.reverse();
break;
default:
break;
}
if (sendEvent) {
variables.stats.postEvent('marketplace', 'Sort');
}
return {
items: items,
sortType: value,
};
}
changeSort(value) {
localStorage.setItem('sortMarketplace', value);
this.setState(this.sortMarketplace(null, true));
}
returnToMain() {
this.setState({
items: this.state.oldItems,
collection: false,
});
}
componentDidMount() {
useEffect(() => {
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
return;
}
this.getItems();
}
getItems();
}, []);
componentWillUnmount() {
useEffect(() => {
// stop making requests
this.controller.abort();
return () => controller.abort();
}, []);
const errorMessage = (msg) => {
return (
<>
<div className="flexTopMarketplace">
<span className="mainTitle">
{variables.getMessage('modals.main.navbar.marketplace')}
</span>
</div>
<div className="emptyItems">
<div className="emptyMessage">{msg}</div>
</div>
</>
);
};
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
return errorMessage(
<>
<MdWifiOff />
<span className="title">
{variables.getMessage('modals.main.marketplace.offline.title')}
</span>
<span className="subtitle">
{variables.getMessage('modals.main.marketplace.offline.description')}
</span>
</>,
);
}
render() {
const errorMessage = (msg) => {
return (
if (done === false) {
return errorMessage(
<div className="loaderHolder">
<div id="loader"></div>
<span className="subtitle">{variables.getMessage('modals.main.loading')}</span>
</div>,
);
}
if (!items || items?.length === 0) {
getItems();
return errorMessage(
<>
<MdLocalMall />
<span className="title">{variables.getMessage('modals.main.addons.empty.title')}</span>
<span className="subtitle">{variables.getMessage('modals.main.marketplace.no_items')}</span>
</>,
);
}
if (item.display_name) {
return (
<ItemPage
data={item}
toggleFunction={(...args) => toggle(...args)}
icon={item.screenshot_url}
/>
);
}
return (
<>
{collection === true ? (
<>
<div className="flexTopMarketplace">
<Header
title={variables.getMessage('modals.main.navbar.marketplace')}
secondaryTitle={collectionTitle}
report={false}
goBack={() => returnToMain()}
/>
<div
className="collectionPage"
style={{
// backgroundImage: `linear-gradient(to bottom, transparent, black), url('${collectionImg}')`,
}}
>
<div className="nice-tag">
{variables.getMessage('modals.main.marketplace.collection')}
</div>
<div className="content">
<span className="mainTitle">{collectionTitle}</span>
= </div>
<Button
type="collection"
onClick={() => installCollection()}
disabled={busy}
icon={<MdLibraryAdd />}
label={
busy
? variables.getMessage('modals.main.marketplace.installing')
: variables.getMessage('modals.main.marketplace.add_all')
}
/>
</div>
</>
) : (
<>
{/*<div className="flexTopMarketplace">
<span className="mainTitle">
{variables.getMessage('modals.main.navbar.marketplace')}
</span>
</div>
<div className="emptyItems">
<div className="emptyMessage">{msg}</div>
</div>
</>
);
};
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
return errorMessage(
<>
<MdWifiOff />
<span className="title">
{variables.getMessage('modals.main.marketplace.offline.title')}
</span>
<span className="subtitle">
{variables.getMessage('modals.main.marketplace.offline.description')}
</span>
</>,
);
}
if (this.state.done === false) {
return errorMessage(
<div className="loaderHolder">
<div id="loader"></div>
<span className="subtitle">{variables.getMessage('modals.main.loading')}</span>
</div>,
);
}
if (!this.state.items || this.state.items?.length === 0) {
this.getItems();
return errorMessage(
<>
<MdLocalMall />
<span className="title">{variables.getMessage('modals.main.addons.empty.title')}</span>
<span className="subtitle">
{variables.getMessage('modals.main.marketplace.no_items')}
</span>
</>,
);
}
if (this.state.item.display_name) {
return (
<ItemPage
data={this.state.item}
button={this.state.button}
toggleFunction={(...args) => this.toggle(...args)}
addonInstalled={this.state.item.addonInstalled}
addonInstalledVersion={this.state.item.addonInstalledVersion}
icon={this.state.item.screenshot_url}
/>
);
}
return (
<>
{this.state.collection === true ? (
<>
<Header
title={variables.getMessage('modals.main.navbar.marketplace')}
secondaryTitle={this.state.collectionTitle}
report={false}
goBack={() => this.returnToMain()}
<div className="headerExtras marketplaceCondition">
{type !== 'collections' && (
<div>
<form className="max-w-md mx-auto relative">
<input
label={variables.getMessage('widgets.search')}
placeholder={variables.getMessage('widgets.search')}
name="filter"
id="filter"
value={filter}
onChange={(event) => setFilter(event.target.value)}
className="block w-full px-4 py-3 ps-10 text-sm text-gray-900 border border-[#484848] rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-white/5 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-neutral-100"
/>
<div className="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
<MdSearch />
</div>
</form>
</div>
)}
<Dropdown
label={variables.getMessage('modals.main.addons.sort.title')}
name="sortMarketplace"
onChange={(value) => changeSort(value
items)}
items={[
{
value: 'a-z',
text: variables.getMessage('modals.main.addons.sort.a_z'),
},
{
value: 'z-a',
text: variables.getMessage('modals.main.addons.sort.z_a'),
},
]}
/>
<div
className="collectionPage"
style={{
backgroundImage: `linear-gradient(to bottom, transparent, black), url('${this.state.collectionImg}')`,
}}
>
<div className="nice-tag">
{variables.getMessage('modals.main.marketplace.collection')}
</div>
<div className="content">
<span className="mainTitle">{this.state.collectionTitle}</span>
<span className="subtitle">{this.state.collectionDescription}</span>
</div>
</div>*/}
</>
)}
{type === 'collections' && !collection ? (
items.map((item) =>
!item.news ? (
<div
className="collection"
style={
item.news
? { backgroundColor: item.background_colour }
: {
backgroundImage: `linear-gradient(to left, #000, transparent, #000), url('${item.img}')`,
}
}
>
<div className="content">
<span className="title">{item.display_name}</span>
<span className="subtitle">{item.description}</span>
</div>
<Button
type="collection"
onClick={() => this.installCollection()}
disabled={this.state.busy}
icon={<MdLibraryAdd />}
label={
this.state.busy
? variables.getMessage('modals.main.marketplace.installing')
: variables.getMessage('modals.main.marketplace.add_all')
}
onClick={() => toggle('collection', item.name)}
icon={<MdOutlineArrowForward />}
label={variables.getMessage('modals.main.marketplace.explore_collection')}
iconPlacement="right"
/>
</div>
</>
) : (
<>
{/*<div className="flexTopMarketplace">
<span className="mainTitle">
{variables.getMessage('modals.main.navbar.marketplace')}
</span>
</div>
<div className="headerExtras marketplaceCondition">
{this.props.type !== 'collections' && (
<div>
<form className="max-w-md mx-auto relative">
<input
label={variables.getMessage('widgets.search')}
placeholder={variables.getMessage('widgets.search')}
name="filter"
id="filter"
value={this.state.filter}
onChange={(event) => this.setState({ filter: event.target.value })}
className="block w-full px-4 py-3 ps-10 text-sm text-gray-900 border border-[#484848] rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-white/5 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-neutral-100"
/>
<div className="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
<MdSearch />
</div>
</form>
</div>
)}
<Dropdown
label={variables.getMessage('modals.main.addons.sort.title')}
name="sortMarketplace"
onChange={(value) => this.changeSort(value)}
items={[
{
value: 'a-z',
text: variables.getMessage('modals.main.addons.sort.a_z'),
},
{
value: 'z-a',
text: variables.getMessage('modals.main.addons.sort.z_a'),
},
]}
/>
</div>*/}
</>
)}
{this.props.type === 'collections' && !this.state.collection ? (
this.state.items.map((item) =>
!item.news ? (
<div
className="collection"
style={
item.news
? { backgroundColor: item.background_colour }
: {
backgroundImage: `linear-gradient(to left, #000, transparent, #000), url('${item.img}')`,
}
}
>
<div className="content">
<span className="title">{item.display_name}</span>
<span className="subtitle">{item.description}</span>
</div>
<Button
type="collection"
onClick={() => this.toggle('collection', item.name)}
icon={<MdOutlineArrowForward />}
label={variables.getMessage('modals.main.marketplace.explore_collection')}
iconPlacement="right"
/>
</div>
) : null,
)
) : (
<Items
type={this.state.type}
items={this.state.items}
collection={this.state.displayedCollection}
onCollection={this.state.collection}
toggleFunction={(input) => this.toggle('item', input)}
collectionFunction={(input) => this.toggle('collection', input)}
filter={this.state.filter}
showCreateYourOwn={true}
/>
)}
</>
);
}
) : null,
)
) : (
<Items
type={type}
items={items}
toggleFunction={(input) => toggle('item', input)}
filter={filter}
showCreateYourOwn={true}
/>
)}
</>
);
}
export default Marketplace;

View File

@@ -0,0 +1,68 @@
function CollectionPage() {
async function installCollection() {
setBusy(true);
try {
const installed = JSON.parse(localStorage.getItem('installed'));
for (const item of items) {
if (installed.some((i) => i.name === item.display_name)) continue; // don't install if already installed
let { data } = await (
await fetch(`${variables.constants.API_URL}/marketplace/item/${item.type}/${item.name}`, {
signal: controller.signal,
})
).json();
install(data.type, data, false, true);
variables.stats.postEvent('marketplace-item', `${item.display_name} installed}`);
variables.stats.postEvent('marketplace', 'Install');
}
toast(variables.getMessage('toasts.installed'));
window.location.reload();
} catch (error) {
console.error(error);
toast(variables.getMessage('toasts.error'));
} finally {
setBusy(false);
}
}
function returnToMain() {
setCollection(false);
}
return (
<>
<Header
title={variables.getMessage('modals.main.navbar.marketplace')}
secondaryTitle={collectionTitle}
report={false}
goBack={() => returnToMain()}
/>
<div
className="collectionPage"
style={
{
// backgroundImage: `linear-gradient(to bottom, transparent, black), url('${collectionImg}')`,
}
}
>
<div className="nice-tag">{variables.getMessage('modals.main.marketplace.collection')}</div>
<div className="content">
<span className="mainTitle">{collectionTitle}</span>={' '}
</div>
<Button
type="collection"
onClick={() => installCollection()}
disabled={busy}
icon={<MdLibraryAdd />}
label={
busy
? variables.getMessage('modals.main.marketplace.installing')
: variables.getMessage('modals.main.marketplace.add_all')
}
/>
</div>
</>
);
}
export default CollectionPage;

View File

@@ -1,41 +1,32 @@
/* eslint-disable no-unused-vars */
import variables from 'config/variables';
import { PureComponent } from 'react';
import { MdOutlineExtensionOff } from 'react-icons/md';
import { Button } from 'components/Elements';
export default class Create extends PureComponent {
constructor() {
super();
this.state = {};
}
render() {
return (
<>
<div className="flexTopMarketplace">
<span className="mainTitle">
{variables.getMessage('modals.main.addons.create.title')}
function Create() {
return (
<>
<div className="flexTopMarketplace">
<span className="mainTitle">{variables.getMessage('modals.main.addons.create.title')}</span>
</div>
<div className="emptyItems">
<div className="emptyNewMessage">
<MdOutlineExtensionOff />
<span className="title">
{variables.getMessage('modals.main.addons.create.moved_title')}
</span>
</div>
<div className="emptyItems">
<div className="emptyNewMessage">
<MdOutlineExtensionOff />
<span className="title">
{variables.getMessage('modals.main.addons.create.moved_title')}
</span>
<span className="subtitle">
{variables.getMessage('modals.main.addons.create.moved_description')}
</span>
<div className="createButtons">
<Button
type="settings"
label={variables.getMessage('modals.main.addons.create.moved_button')}
/>
</div>
<span className="subtitle">
{variables.getMessage('modals.main.addons.create.moved_description')}
</span>
<div className="createButtons">
<Button
type="settings"
label={variables.getMessage('modals.main.addons.create.moved_button')}
/>
</div>
</div>
</>
);
}
</div>
</>
);
}
export default Create;

View File

@@ -11,6 +11,8 @@ import {
MdTranslate,
MdOutlineWarning,
MdStyle,
MdClose,
MdLibraryAdd,
} from 'react-icons/md';
import Modal from 'react-modal';
@@ -28,14 +30,32 @@ import Markdown from 'markdown-to-jsx';
class ItemPage extends PureComponent {
constructor(props) {
super(props);
console.log(this.props)
this.state = {
showUpdateButton:
this.props.addonInstalled === true &&
this.props.addonInstalledVersion !== this.props.data.version,
this.props.data.local.installed === true && this.props.data.local.version !== this.props.data.version,
shareModal: false,
count: 5,
moreByCurator: [],
};
this.buttons = {
uninstall: (
<Button
type="settings"
onClick={() => this.manage('uninstall')}
icon={<MdClose />}
label={variables.getMessage('modals.main.marketplace.product.buttons.remove')}
/>
),
install: (
<Button
type="settings"
onClick={() => this.manage('install')}
icon={<MdLibraryAdd />}
label={variables.getMessage('modals.main.marketplace.product.buttons.addtomue')}
/>
),
};
}
async getCurator(name) {
@@ -53,6 +73,10 @@ class ItemPage extends PureComponent {
componentDidMount() {
this.getCurator(this.props.data.author);
document.querySelector('#modal').scrollTop = 0;
this.setState({
button: this.props.data.local.installed ? this.buttons.uninstall : this.buttons.install,
})
}
updateAddon() {
@@ -82,10 +106,31 @@ class ItemPage extends PureComponent {
return nameMappings[name] || name;
}
manage(type) {
if (type === 'install') {
install(this.props.data.type, this.props.data.data);
} else {
uninstall(this.props.data.type,this.props.data.display_name);
}
toast(variables.getMessage('toasts.' + type + 'ed'));
this.setState({
button: type === 'install' ? this.buttons.uninstall : this.buttons.install,
});
variables.stats.postEvent(
'marketplace-item',
`${this.state.item.display_name} ${type === 'install' ? 'installed' : 'uninstalled'}`,
);
variables.stats.postEvent('marketplace', type === 'install' ? 'Install' : 'Uninstall');
}
render() {
const locale = localStorage.getItem('language');
const shortLocale = locale.includes('_') ? locale.split('_')[0] : locale;
let languageNames = new Intl.DisplayNames([shortLocale], { type: 'language' });
const convertedType = (() => {
const map = {
photos: 'photo_packs',
@@ -94,6 +139,7 @@ class ItemPage extends PureComponent {
};
return map[this.props.data.data.type];
})();
const moreByCurator = this.state.moreByCurator
.filter((item) => item.type === convertedType && item.name !== this.props.data.data.name)
.sort(() => 0.5 - Math.random())
@@ -181,9 +227,7 @@ class ItemPage extends PureComponent {
onRequestClose={() => this.setState({ shareModal: false })}
>
<ShareModal
data={
variables.constants.API_URL + '/marketplace/share/' + btoa(this.props.data.api_name)
}
data={variables.constants.API_URL + '/marketplace/share/' + btoa(this.props.data.slug)}
modalClose={() => this.setState({ shareModal: false })}
/>
</Modal>
@@ -347,7 +391,7 @@ class ItemPage extends PureComponent {
}}
/>
{localStorage.getItem('welcomePreview') !== 'true' ? (
this.props.button
this.state.button
) : (
<p style={{ textAlign: 'center' }}>
{variables.getMessage(

View File

@@ -1,5 +1,5 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { useState } from 'react';
import { MdCancel, MdAdd, MdOutlineTextsms } from 'react-icons/md';
import { toast } from 'react-toastify';
import { TextareaAutosize } from '@mui/material';
@@ -8,142 +8,125 @@ import { Header, Row, Content, Action, PreferencesWrapper } from 'components/Lay
import { Button } from 'components/Elements';
import EventBus from 'utils/eventbus';
class MessageOptions extends PureComponent {
constructor() {
super();
this.state = {
messages: JSON.parse(localStorage.getItem('messages')) || [],
};
}
function MessageOptions() {
const [messages, setMessages] = useState(JSON.parse(localStorage.getItem('messages')) || []);
reset = () => {
const reset = () => {
localStorage.setItem('messages', '[]');
this.setState({
messages: [],
});
toast(variables.getMessage(this.languagecode, 'toasts.reset'));
setMessages([]);
toast(variables.getMessage('toasts.reset'));
EventBus.emit('refresh', 'message');
};
modifyMessage(type, index) {
const messages = this.state.messages;
const modifyMessage = (type, index) => {
const messages = messages;
if (type === 'add') {
messages.push('');
} else {
messages.splice(index, 1);
}
this.setState({
messages,
});
this.forceUpdate();
setMessages(messages);
localStorage.setItem('messages', JSON.stringify(messages));
}
};
message(e, text, index) {
const message = (e, text, index) => {
const result = text === true ? e.target.value : e.target.result;
const messages = this.state.messages;
const messages = messages;
messages[index] = result;
this.setState({
messages,
});
this.forceUpdate();
setMessages(messages);
localStorage.setItem('messages', JSON.stringify(messages));
document.querySelector('.reminder-info').style.display = 'flex';
localStorage.setItem('showReminder', true);
}
};
render() {
const MESSAGE_SECTION = 'modals.main.settings.sections.message';
return (
<>
<Header
title={variables.getMessage(`${MESSAGE_SECTION}.title`)}
setting="message"
category="message"
element=".message"
zoomSetting="zoomMessage"
visibilityToggle={true}
/>
<PreferencesWrapper
setting="message"
visibilityToggle={true}
category="message"
zoomSetting="zoomMessage"
>
<Row final={true}>
<Content title={variables.getMessage(`${MESSAGE_SECTION}.messages`)} />
<Action>
<Button
type="settings"
onClick={() => this.modifyMessage('add')}
icon={<MdAdd />}
label={variables.getMessage(`${MESSAGE_SECTION}.add`)}
/>
</Action>
</Row>
<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>
return (
<>
<Header
title={variables.getMessage('modals.main.settings.sections.message.title')}
setting="message"
category="message"
element=".message"
zoomSetting="zoomMessage"
visibilityToggle={true}
/>
<PreferencesWrapper
setting="message"
visibilityToggle={true}
category="message"
zoomSetting="zoomMessage"
>
<Row final={true}>
<Content title={variables.getMessage('modals.main.settings.sections.message.messages')} />
<Action>
<Button
type="settings"
onClick={() => modifyMessage('add')}
icon={<MdAdd />}
label={variables.getMessage('modals.main.settings.sections.message.add')}
/>
</Action>
</Row>
<div className="messagesContainer">
{messages.map((_url, index) => (
<div className="messageMap" key={index}>
<div className="flexGrow">
<div className="icon">
<MdOutlineTextsms />
</div>
<div>
<div className="messageAction">
<Button
type="settings"
onClick={() => this.modifyMessage('remove', index)}
icon={<MdCancel />}
label={variables.getMessage('modals.main.marketplace.product.buttons.remove')}
/>
</div>
<div className="messageText">
<span className="subtitle">
{variables.getMessage('modals.main.settings.sections.message.title')}
</span>
<TextareaAutosize
value={messages[index]}
placeholder={variables.getMessage(
'modals.main.settings.sections.message.content',
)}
onChange={(e) => message(e, true, index)}
varient="outlined"
style={{ padding: '0' }}
/>
</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
type="settings"
onClick={() => this.modifyMessage('add')}
icon={<MdAdd />}
label={variables.getMessage(`${MESSAGE_SECTION}.add`)}
/>
<div>
<div className="messageAction">
<Button
type="settings"
onClick={() => modifyMessage('remove', index)}
icon={<MdCancel />}
label={variables.getMessage('modals.main.marketplace.product.buttons.remove')}
/>
</div>
</div>
</div>
)}
</PreferencesWrapper>
</>
);
}
))}
</div>
{messages.length === 0 && (
<div className="photosEmpty">
<div className="emptyNewMessage">
<MdOutlineTextsms />
<span className="title">
{variables.getMessage('modals.main.settings.sections.message.no_messages')}
</span>
<span className="subtitle">
{variables.getMessage('modals.main.settings.sections.message.add_some')}
</span>
<Button
type="settings"
onClick={() => modifyMessage('add')}
icon={<MdAdd />}
label={variables.getMessage('modals.main.settings.sections.message.add')}
/>
</div>
</div>
)}
</PreferencesWrapper>
</>
);
}
export { MessageOptions as default, MessageOptions };

View File

@@ -1,35 +1,22 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { useState, useEffect } from 'react';
import Modal from 'react-modal';
import { MainModal } from 'components/Elements';
import Navbar from '../../navbar/Navbar';
import Preview from '../../helpers/preview/Preview';
import EventBus from 'utils/eventbus';
import Welcome from 'features/welcome/Welcome';
function Modals() {
const [main, setMainVisible] = useState(false);
const [welcome, setWelcomeVisible] = useState(false);
export default class Modals extends PureComponent {
constructor() {
super();
this.state = {
mainModal: false,
updateModal: false,
welcomeModal: false,
appsModal: false,
preview: false,
};
}
componentDidMount() {
useEffect(() => {
if (
localStorage.getItem('showWelcome') !== 'false' &&
window.location.search !== '?nointro=true'
) {
this.setState({
welcomeModal: true,
});
setWelcomeVisible(true);
variables.stats.postEvent('modal', 'Opened welcome');
}
@@ -43,56 +30,45 @@ export default class Modals extends PureComponent {
// hide refresh reminder once the user has refreshed the page
localStorage.setItem('showReminder', false);
}
}, []);
closeWelcome() {
localStorage.setItem('showWelcome', false);
this.setState({
welcomeModal: false,
});
EventBus.emit('refresh', 'widgetsWelcomeDone');
EventBus.emit('refresh', 'widgets');
EventBus.emit('refresh', 'backgroundwelcome');
}
previewWelcome() {
localStorage.setItem('showWelcome', false);
localStorage.setItem('welcomePreview', true);
this.setState({
welcomeModal: false,
preview: true,
});
EventBus.emit('refresh', 'widgetsWelcome');
}
toggleModal(type, action) {
this.setState({
[type]: action,
});
const toggleModal = (type, action) => {
switch (type) {
case 'main':
setMainVisible(action);
break;
case 'welcome':
setWelcomeVisible(action);
break;
default:
break;
}
if (action !== false) {
variables.stats.postEvent('modal', `Opened ${type.replace('Modal', '')}`);
}
}
render() {
return (
<>
{this.state.welcomeModal === false && (
<Navbar openModal={(modal) => this.toggleModal(modal, true)} />
)}
<Modal
closeTimeoutMS={300}
id="modal"
onRequestClose={() => this.toggleModal('mainModal', false)}
isOpen={this.state.mainModal}
className="Modal mainModal"
overlayClassName="Overlay"
ariaHideApp={false}
>
<MainModal modalClose={() => this.toggleModal('mainModal', false)} />
</Modal>
</>
);
}
return (
<>
{welcome === false && (
<Navbar openModal={(modal) => toggleModal('main', true)} />
)}
<Modal
closeTimeoutMS={300}
id="modal"
onRequestClose={() => toggleModal('main', false)}
isOpen={main}
className="Modal mainModal"
overlayClassName="Overlay"
ariaHideApp={false}
>
<MainModal modalClose={() => toggleModal('main', false)} />
</Modal>
</>
);
}
export default Modals;

View File

@@ -15,6 +15,7 @@ import { FileUpload, Text, Switch, Dropdown } from 'components/Form/Settings';
import { ResetModal, Button } from 'components/Elements';
import { Header, Section, Row, Content, Action } from 'components/Layout/Settings';
import { useTab } from 'components/Elements/MainModal/backend/TabContext';
import time_zones from 'features/time/timezones.json';
@@ -22,6 +23,7 @@ function AdvancedOptions() {
const [resetModal, setResetModal] = useState(false);
const [data, setData] = useState(false);
const ADVANCED_SECTION = 'modals.main.settings.sections.advanced';
const { subSection } = useTab();
const Data = () => {
return localStorage.getItem('welcomePreview') !== 'true' ? (
@@ -93,7 +95,7 @@ function AdvancedOptions() {
return (
<>
{header}
{data ? (
{subSection === "data" ? (
<>
<Data />
<Modal
@@ -110,6 +112,7 @@ function AdvancedOptions() {
) : (
<>
<Section
id="data"
title={variables.getMessage(`${ADVANCED_SECTION}.data`)}
subtitle={variables.getMessage(`${ADVANCED_SECTION}.data_subtitle`)}
onClick={() => setData(true)}

View File

@@ -4,6 +4,7 @@ import variables from 'config/variables';
import { Checkbox, Dropdown, Radio, Slider, Text } from 'components/Form/Settings';
import { Header, Section, Row, Content, Action } from 'components/Layout/Settings';
import { useTab } from 'components/Elements/MainModal/backend/TabContext';
import { MdAccessibility } from 'react-icons/md';
@@ -11,6 +12,7 @@ import values from 'utils/data/slider_values.json';
function AppearanceOptions() {
const [accessibility, setAccessibility] = useState(false);
const { subSection } = useTab();
const ThemeSelection = () => {
return (
@@ -262,11 +264,12 @@ function AppearanceOptions() {
return (
<>
{header}
{accessibility ? (
{subSection === "accessibility" ? (
<AccessibilityOptions />
) : (
<>
<Section
id="accessibility"
title={variables.getMessage(
'modals.main.settings.sections.appearance.accessibility.title',
)}

View File

@@ -1,56 +1,27 @@
import variables from 'config/variables';
import { PureComponent, createRef } from 'react';
import { useState, createRef, useEffect } from 'react';
import Markdown from 'markdown-to-jsx';
import { MdOutlineWifiOff } from 'react-icons/md';
class Changelog extends PureComponent {
constructor() {
super();
this.state = {
title: null,
};
this.offlineMode = localStorage.getItem('offlineMode') === 'true';
this.controller = new AbortController();
this.changelog = createRef();
}
function Changelog() {
const [title, setTitle] = useState(null);
const [error, setError] = useState(false);
const [content, setContent] = useState(null);
const [date, setDate] = useState(null);
parseMarkdown = (text) => {
if (typeof text !== 'string') {
throw new Error('Input must be a string');
}
const offlineMode = localStorage.getItem('offlineMode') === 'true';
const controller = new AbortController();
const changelog = createRef();
// Replace markdown syntax
text = text
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/^## (.*$)/gm, '<span class="title">$1</span>')
.replace(
/((http|https):\/\/[^\s]+)/g,
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>',
)
// resolve @ to github user link
.replace(
/@([a-zA-Z0-9-_]+)/g,
'<a href="https://github.com/$1" target="_blank" class="changelogAt">@$1</a>',
);
// Replace list items
text = text.replace(/^\* (.*$)/gm, '<li>$1</li>');
// Wrap list items in <ul></ul>
text = text.replace(/((<li>.*<\/li>\s*)+)/g, '<ul>$1</ul>');
return text;
};
async getUpdate() {
const getUpdate = async () => {
const releases = await fetch(
`https://api.github.com/repos/${variables.constants.ORG_NAME}/${variables.constants.REPO_NAME}/releases`,
{
signal: this.controller.signal,
signal: controller.signal,
},
);
if (this.controller.signal.aborted === true) {
if (controller.signal.aborted === true) {
return;
}
@@ -63,92 +34,88 @@ class Changelog extends PureComponent {
}
// request the changelog
const res = await fetch(release.url, { signal: this.controller.signal });
const res = await fetch(release.url, { signal: controller.signal });
if (res.status === 404) {
this.setState({ error: true });
setError(true);
return;
}
if (this.controller.signal.aborted === true) {
if (controller.signal.aborted === true) {
return;
}
const changelog = await res.json();
this.setState({
title: changelog.name,
content: changelog.body,
date: new Date(changelog.published_at).toLocaleDateString(),
});
setTitle(changelog.name);
setContent(changelog.body);
setDate(new Date(changelog.published_at).toLocaleDateString());
}
componentDidMount() {
if (navigator.onLine === false || this.offlineMode) {
useEffect(() => {
if (navigator.onLine === false || offlineMode) {
return;
}
this.getUpdate();
}
getUpdate();
}, []);
componentWillUnmount() {
useEffect(() => {
// stop making requests
this.controller.abort();
}
render() {
const errorMessage = (msg) => {
return (
<div className="emptyItems">
<div className="emptyMessage">{msg}</div>
</div>
);
};
if (navigator.onLine === false || this.offlineMode) {
return errorMessage(
<>
<MdOutlineWifiOff />
<h1>{variables.getMessage('modals.main.marketplace.offline.title')}</h1>
<p className="description">
{variables.getMessage('modals.main.marketplace.offline.description')}
</p>
</>,
);
}
if (this.state.error === true) {
return errorMessage(
<>
<MdOutlineWifiOff />
<span className="title">{variables.getMessage('modals.main.error_boundary.title')}</span>
<span className="subtitle">
{variables.getMessage('modals.main.error_boundary.message')}
</span>
</>,
);
}
if (!this.state.title) {
return errorMessage(
<div className="loaderHolder">
<div id="loader"></div>
<span className="subtitle">{variables.getMessage('modals.main.loading')}</span>
</div>,
);
}
return () => controller.abort();
}, []);
const errorMessage = (msg) => {
return (
<article className="changelogtab prose dark:prose-invert" ref={this.changelog}>
<div className="not-prose">
<span className="mainTitle">{this.state.title}</span>
<span className="subtitle">Released on {this.state.date}</span>
</div>
<Markdown options={{ overrides: { a: { props: { target: '_blank' } } } }}>
{this.state.content}
</Markdown>
</article>
<div className="emptyItems">
<div className="emptyMessage">{msg}</div>
</div>
);
};
if (navigator.onLine === false || offlineMode) {
return errorMessage(
<>
<MdOutlineWifiOff />
<h1>{variables.getMessage('modals.main.marketplace.offline.title')}</h1>
<p className="description">
{variables.getMessage('modals.main.marketplace.offline.description')}
</p>
</>,
);
}
if (error === true) {
return errorMessage(
<>
<MdOutlineWifiOff />
<span className="title">{variables.getMessage('modals.main.error_boundary.title')}</span>
<span className="subtitle">
{variables.getMessage('modals.main.error_boundary.message')}
</span>
</>,
);
}
if (!title) {
return errorMessage(
<div className="loaderHolder">
<div id="loader"></div>
<span className="subtitle">{variables.getMessage('modals.main.loading')}</span>
</div>,
);
}
return (
<article className="changelogtab prose dark:prose-invert" ref={changelog}>
<div className="not-prose">
<span className="mainTitle">{title}</span>
<span className="subtitle">Released on {date}</span>
</div>
<Markdown options={{ overrides: { a: { props: { target: '_blank' } } } }}>
{content}
</Markdown>
</article>
);
}
export { Changelog as default, Changelog };

View File

@@ -29,6 +29,7 @@ const SortableItem = sortableElement(({ value }) => (
</li>
));
const SortableContainer = sortableContainer(({ children }) => (
<ul className="sortableContainer">{children}</ul>
));

View File

@@ -1,20 +1,25 @@
import variables from 'config/variables';
import { memo } from 'react';
import Tabs from 'components/Elements/MainModal/backend/Tabs';
import Added from '../../marketplace/views/Added';
import Create from '../../marketplace/views/Create';
function Addons(props) {
return (
<Tabs changeTab={(type) => props.changeTab(type)} current="addons" modalClose={props.modalClose}>
{
/*<Tabs changeTab={(type) => props.changeTab(type)} current="addons" modalClose={props.modalClose}>
<div label={variables.getMessage('modals.main.addons.added')} name="added">
<Added />
</div>
<div label={variables.getMessage('modals.main.addons.create.title')} name="create">
<Create />
</div>
</Tabs>
</Tabs>*/
},
(
<div className="modalTabContent">
<Added />
</div>
)
);
}

View File

@@ -1,12 +1,12 @@
import variables from 'config/variables';
import { memo } from 'react';
import Tabs from '../../../components/Elements/MainModal/backend/Tabs';
import MarketplaceTab from '../../marketplace/views/Browse';
function Marketplace(props) {
return (
<Tabs changeTab={(type) => props.changeTab(type)} current="marketplace" modalClose={props.modalClose}>
{
/*<Tabs changeTab={(type) => props.changeTab(type)} current="marketplace" modalClose={props.modalClose}>
<div label={variables.getMessage('modals.main.marketplace.all')} name="all">
<MarketplaceTab type="all" />
</div>
@@ -25,7 +25,13 @@ function Marketplace(props) {
<div label={variables.getMessage('modals.main.marketplace.collections')} name="collections">
<MarketplaceTab type="collections" />
</div>
</Tabs>
</Tabs>*/
},
(
<div className="modalTabContent">
<MarketplaceTab type="all" />
</div>
)
);
}

View File

@@ -1,7 +1,8 @@
import variables from 'config/variables';
import { memo } from 'react';
import Tabs from 'components/Elements/MainModal/backend/Tabs';
//import Tabs from 'components/Elements/MainModal/backend/Tabs';
import { Tabs } from 'components/Elements/MainModal/backend/newTabs';
import { NavbarOptions } from 'features/navbar';
import { GreetingOptions } from 'features/greeting';
@@ -90,13 +91,9 @@ const sections = [
function Settings(props) {
return (
<Tabs setSubTab={props.setSubTab} changeTab={(type) => props.changeTab(type)} current="settings" modalClose={props.modalClose}>
{sections.map(({ label, name, component: Component }) => (
<div key={name} label={variables.getMessage(label)} name={name}>
<Component />
</div>
))}
</Tabs>
<>
<Tabs sections={sections} />
</>
);
}

View File

@@ -1,4 +1,4 @@
import { PureComponent, Fragment, Suspense, lazy } from 'react';
import { Fragment, Suspense, lazy, useState, useEffect } from 'react';
import Clock from '../../time/Clock';
import Greeting from '../../greeting/Greeting';
@@ -18,74 +18,61 @@ import defaults from 'config/default';
// as seen here it is ridiculously large
const Weather = lazy(() => import('../../weather/Weather'));
export default class Widgets extends PureComponent {
online = localStorage.getItem('offlineMode') === 'false';
constructor() {
super();
this.state = {
order: JSON.parse(localStorage.getItem('order')) || defaults.order,
welcome: localStorage.getItem('showWelcome'),
};
// widgets we can re-order
this.widgets = {
time: this.enabled('time') && <Clock />,
greeting: this.enabled('greeting') && <Greeting />,
quote: this.enabled('quote') && <Quote />,
date: this.enabled('date') && <Date />,
quicklinks: this.enabled('quicklinksenabled') && this.online ? <QuickLinks /> : null,
message: this.enabled('message') && <Message />,
};
}
export function Widgets() {
const [order, setOrder] = useState(JSON.parse(localStorage.getItem('order')) || defaults.order);
const [welcome, setWelcome] = useState(localStorage.getItem('showWelcome'));
enabled(key) {
const online = localStorage.getItem('offlineMode') === 'false';
const widgets = {
time: enabled('time') && <Clock />,
greeting: enabled('greeting') && <Greeting />,
quote: enabled('quote') && <Quote />,
date: enabled('date') && <Date />,
quicklinks: enabled('quicklinksenabled') && online ? <QuickLinks /> : null,
message: enabled('message') && <Message />,
};
function enabled(key) {
return localStorage.getItem(key) === 'true';
}
componentDidMount() {
useEffect(() => {
EventBus.on('refresh', (data) => {
switch (data) {
case 'widgets':
return this.setState({
order: JSON.parse(localStorage.getItem('order')) || defaults.order,
});
setOrder(JSON.parse(localStorage.getItem('order')) || defaults.order);
break;
case 'widgetsWelcome':
this.setState({
welcome: localStorage.getItem('showWelcome'),
});
setWelcome(localStorage.getItem('showWelcome'));
localStorage.setItem('showWelcome', true);
window.onbeforeunload = () => {
localStorage.clear();
};
break;
case 'widgetsWelcomeDone':
this.setState({
welcome: localStorage.getItem('showWelcome'),
});
return (window.onbeforeunload = null);
setWelcome(localStorage.getItem('showWelcome'));
break;
default:
break;
}
});
}
return () => EventBus.off('refresh');
}, []);
componentWillUnmount() {
EventBus.off('refresh');
}
render() {
// don't show when welcome is there
return this.state.welcome !== 'false' ? (
<WidgetsLayout />
) : (
<WidgetsLayout>
<Suspense fallback={<></>}>
{this.enabled('searchBar') && <Search />}
{this.state.order.map((element, key) => (
<Fragment key={key}>{this.widgets[element]}</Fragment>
))}
{this.enabled('weatherEnabled') && this.online ? <Weather /> : null}
</Suspense>
</WidgetsLayout>
);
}
return welcome !== 'false' ? (
<WidgetsLayout />
) : (
<WidgetsLayout>
<Suspense fallback={<></>}>
{enabled('searchBar') && <Search />}
{order.map((element, key) => (
<Fragment key={key}>{widgets[element]}</Fragment>
))}
{enabled('weatherEnabled') && online ? <Weather /> : null}
</Suspense>
</WidgetsLayout>
);
}
export default Widgets;

View File

@@ -1,18 +1,14 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
import { useState } from 'react';
import { MdCropFree } from 'react-icons/md';
import { Tooltip } from 'components/Elements';
class Maximise extends PureComponent {
constructor() {
super();
this.state = {
hidden: false,
};
}
setAttribute(blur, brightness, filter) {
function Maximise(props) {
const [hidden, setHidden] = useState(false);
const setAttribute = (blur, brightness, filter) => {
// don't attempt to modify the background if it isn't an image
const backgroundType = localStorage.getItem('backgroundType');
if (
@@ -43,48 +39,35 @@ class Maximise extends PureComponent {
: ''
};`,
);
}
};
maximise = () => {
const maximise = () => {
// hide widgets
const widgets = document.getElementById('widgets');
this.state.hidden === false
? (widgets.style.display = 'none')
: (widgets.style.display = 'flex');
setHidden(!hidden);
widgets.style.display = hidden ? 'none' : 'flex';
if (this.state.hidden === false) {
this.setState({
hidden: true,
});
this.setAttribute(0, 100);
if (hidden === false) {
setAttribute(0, 100);
variables.stats.postEvent('feature', 'Background maximise');
} else {
this.setState({
hidden: false,
});
this.setAttribute(localStorage.getItem('blur'), localStorage.getItem('brightness'), true);
setAttribute(localStorage.getItem('blur'), localStorage.getItem('brightness'), true);
variables.stats.postEvent('feature', 'Background unmaximise');
}
};
render() {
return (
<Tooltip
title={variables.getMessage('modals.main.settings.sections.background.buttons.view')}
return (
<Tooltip title={variables.getMessage('modals.main.settings.sections.background.buttons.view')}>
<button
className="navbarButton"
style={{ fontSize: props.fontSize }}
onClick={maximise}
aria-label={variables.getMessage('modals.main.settings.sections.background.buttons.view')}
>
<button
className="navbarButton"
style={{ fontSize: this.props.fontSize }}
onClick={this.maximise}
aria-label={variables.getMessage('modals.main.settings.sections.background.buttons.view')}
>
<MdCropFree className="topicons" />
</button>
</Tooltip>
);
}
<MdCropFree className="topicons" />
</button>
</Tooltip>
);
}
export { Maximise as default, Maximise };

View File

@@ -1,5 +1,5 @@
import variables from 'config/variables';
import React, { PureComponent } from 'react';
import React, { useState, useEffect } from 'react';
import { MdCancel, MdAdd, MdSource, MdOutlineFormatQuote } from 'react-icons/md';
import TextareaAutosize from '@mui/material/TextareaAutosize';
@@ -13,70 +13,52 @@ import {
} from 'components/Layout/Settings';
import { Checkbox, Dropdown } from 'components/Form/Settings';
import { Button } from 'components/Elements';
import { useTab } from 'components/Elements/MainModal/backend/TabContext';
import { toast } from 'react-toastify';
import EventBus from 'utils/eventbus';
import defaults from './default';
class QuoteOptions extends PureComponent {
constructor() {
super();
this.state = {
quoteType: localStorage.getItem('quoteType') || defaults.quoteType,
customQuote: this.getCustom(),
sourceSection: false,
};
}
const QuoteOptions = () => {
const [quoteType, setQuoteType] = useState(localStorage.getItem('quoteType') || defaults.quoteType);
const [customQuote, setCustomQuote] = useState(getCustom());
const [sourceSection, setSourceSection] = useState(false);
const QUOTE_SECTION = 'modals.main.settings.sections.quote';
const { subSection } = useTab();
resetCustom = () => {
useEffect(() => {
localStorage.setItem('quoteType', quoteType);
}, [quoteType]);
function resetCustom() {
localStorage.setItem('customQuote', '[{"quote": "", "author": ""}]');
this.setState({
customQuote: [
{
quote: '',
author: '',
},
],
});
setCustomQuote([{ quote: '', author: '' }]);
toast(variables.getMessage('toasts.reset'));
EventBus.emit('refresh', 'background');
};
}
customQuote(e, text, index, type) {
const result = text === true ? e.target.value : e.target.result;
const customQuote = this.state.customQuote;
customQuote[index][type] = result;
this.setState({
customQuote,
});
this.forceUpdate();
localStorage.setItem('customQuote', JSON.stringify(customQuote));
function handleCustomQuote(e, index, type) {
const result = e.target.value;
const newCustomQuote = [...customQuote];
newCustomQuote[index][type] = result;
setCustomQuote(newCustomQuote);
localStorage.setItem('customQuote', JSON.stringify(newCustomQuote));
document.querySelector('.reminder-info').style.display = 'flex';
localStorage.setItem('showReminder', true);
}
modifyCustomQuote(type, index) {
const customQuote = this.state.customQuote;
function modifyCustomQuote(type, index) {
let newCustomQuote = [...customQuote];
if (type === 'add') {
customQuote.push({
quote: '',
author: '',
});
newCustomQuote.push({ quote: '', author: '' });
} else {
customQuote.splice(index, 1);
newCustomQuote.splice(index, 1);
}
this.setState({
customQuote,
});
this.forceUpdate();
localStorage.setItem('customQuote', JSON.stringify(customQuote));
setCustomQuote(newCustomQuote);
localStorage.setItem('customQuote', JSON.stringify(newCustomQuote));
}
getCustom() {
function getCustom() {
let data = JSON.parse(localStorage.getItem('customQuote'));
if (data === null) {
data = [];
@@ -84,222 +66,211 @@ class QuoteOptions extends PureComponent {
return data;
}
render() {
const QUOTE_SECTION = 'modals.main.settings.sections.quote';
const ButtonOptions = () => {
return (
<Row>
<Content
title={variables.getMessage(`${QUOTE_SECTION}.buttons.title`)}
subtitle={variables.getMessage('modals.main.settings.sections.quote.buttons.subtitle')}
/>
<Action>
<Checkbox
name="copyButton"
text={variables.getMessage(`${QUOTE_SECTION}.buttons.copy`)}
category="quote"
/>
<Checkbox
name="quoteShareButton"
text={variables.getMessage('widgets.quote.share')}
category="quote"
/>
<Checkbox
name="favouriteQuoteEnabled"
text={variables.getMessage(`${QUOTE_SECTION}.buttons.favourite`)}
category="quote"
/>
</Action>
</Row>
);
};
const SourceDropdown = () => {
return (
<Dropdown
name="quoteType"
label={variables.getMessage('modals.main.settings.sections.background.type.title')}
onChange={(value) => this.setState({ quoteType: value })}
category="quote"
items={[
localStorage.getItem('quote_packs') && {
value: 'quote_pack',
text: variables.getMessage('modals.main.navbar.marketplace'),
},
{
value: 'api',
text: variables.getMessage('modals.main.settings.sections.background.type.api'),
},
{ value: 'custom', text: variables.getMessage(`${QUOTE_SECTION}.custom`) },
]}
const ButtonOptions = () => {
return (
<Row>
<Content
title={variables.getMessage(`${QUOTE_SECTION}.buttons.title`)}
subtitle={variables.getMessage('modals.main.settings.sections.quote.buttons.subtitle')}
/>
);
};
<Action>
<Checkbox
name="copyButton"
text={variables.getMessage(`${QUOTE_SECTION}.buttons.copy`)}
category="quote"
/>
<Checkbox
name="quoteShareButton"
text={variables.getMessage('widgets.quote.share')}
category="quote"
/>
<Checkbox
name="favouriteQuoteEnabled"
text={variables.getMessage(`${QUOTE_SECTION}.buttons.favourite`)}
category="quote"
/>
</Action>
</Row>
);
};
const AdditionalOptions = () => {
return (
const SourceDropdown = () => {
return (
<Dropdown
name="quoteType"
label={variables.getMessage('modals.main.settings.sections.background.type.title')}
onChange={(value) => setQuoteType(value)}
category="quote"
items={[
localStorage.getItem('quote_packs') && {
value: 'quote_pack',
text: variables.getMessage('modals.main.navbar.marketplace'),
},
{
value: 'api',
text: variables.getMessage('modals.main.settings.sections.background.type.api'),
},
{ value: 'custom', text: variables.getMessage(`${QUOTE_SECTION}.custom`) },
]}
/>
);
};
const AdditionalOptions = () => {
return (
<Row final={true}>
<Content
title={variables.getMessage('modals.main.settings.additional_settings')}
subtitle={variables.getMessage(`${QUOTE_SECTION}.additional`)}
/>
<Action>
<Checkbox
name="authorLink"
text={variables.getMessage(`${QUOTE_SECTION}.author_link`)}
element=".other"
/>
<Checkbox
name="authorImg"
text={variables.getMessage(`${QUOTE_SECTION}.author_img`)}
element=".other"
/>
</Action>
</Row>
);
};
let customSettings;
if (quoteType === 'custom' && subSection === 'source') {
customSettings = (
<>
<Row final={true}>
<Content
title={variables.getMessage('modals.main.settings.additional_settings')}
subtitle={variables.getMessage(`${QUOTE_SECTION}.additional`)}
title={variables.getMessage(`${QUOTE_SECTION}.custom`)}
subtitle={variables.getMessage(`${QUOTE_SECTION}.custom_subtitle`)}
/>
<Action>
<Checkbox
name="authorLink"
text={variables.getMessage(`${QUOTE_SECTION}.author_link`)}
element=".other"
/>
<Checkbox
name="authorImg"
text={variables.getMessage(`${QUOTE_SECTION}.author_img`)}
element=".other"
<Button
type="settings"
onClick={() => modifyCustomQuote('add')}
icon={<MdAdd />}
label={variables.getMessage(`${QUOTE_SECTION}.add`)}
/>
</Action>
</Row>
);
};
let customSettings;
if (this.state.quoteType === 'custom' && this.state.sourceSection === true) {
customSettings = (
<>
<Row final={true}>
<Content
title={variables.getMessage(`${QUOTE_SECTION}.custom`)}
subtitle={variables.getMessage(`${QUOTE_SECTION}.custom_subtitle`)}
/>
<Action>
{customQuote.length !== 0 ? (
<div className="messagesContainer">
{customQuote.map((_url, index) => (
<div className="messageMap" key={index}>
<div className="icon">
<MdOutlineFormatQuote />
</div>
<div className="messageText">
<TextareaAutosize
value={customQuote[index].quote}
placeholder={variables.getMessage('modals.main.settings.sections.quote.title')}
onChange={(e) => handleCustomQuote(e, index, 'quote')}
varient="outlined"
style={{ fontSize: '22px', fontWeight: 'bold' }}
/>
<TextareaAutosize
value={customQuote[index].author}
placeholder={variables.getMessage('modals.main.settings.sections.quote.author')}
className="subtitle"
onChange={(e) => handleCustomQuote(e, index, 'author')}
varient="outlined"
/>
</div>
<div>
<div className="messageAction">
<Button
type="settings"
onClick={() => modifyCustomQuote('remove', index)}
icon={<MdCancel />}
label={variables.getMessage('modals.main.marketplace.product.buttons.remove')}
/>
</div>
</div>
</div>
))}
</div>
) : (
<div className="photosEmpty">
<div className="emptyNewMessage">
<MdOutlineFormatQuote />
<span className="title">{variables.getMessage(`${QUOTE_SECTION}.no_quotes`)}</span>
<span className="subtitle">
{variables.getMessage('modals.main.settings.sections.message.add_some')}
</span>
<Button
type="settings"
onClick={() => this.modifyCustomQuote('add')}
onClick={() => modifyCustomQuote('add')}
icon={<MdAdd />}
label={variables.getMessage(`${QUOTE_SECTION}.add`)}
/>
</Action>
</Row>
{this.state.customQuote.length !== 0 ? (
<div className="messagesContainer">
{this.state.customQuote.map((_url, index) => (
<div className="messageMap">
<div className="icon">
<MdOutlineFormatQuote />
</div>
<div className="messageText">
<TextareaAutosize
value={this.state.customQuote[index].quote}
placeholder={variables.getMessage(
'modals.main.settings.sections.quote.title',
)}
onChange={(e) => this.customQuote(e, true, index, 'quote')}
varient="outlined"
style={{ fontSize: '22px', fontWeight: 'bold' }}
/>
<TextareaAutosize
value={this.state.customQuote[index].author}
placeholder={variables.getMessage(
'modals.main.settings.sections.quote.author',
)}
className="subtitle"
onChange={(e) => this.customQuote(e, true, index, 'author')}
varient="outlined"
/>
</div>
<div>
<div className="messageAction">
<Button
type="settings"
onClick={() => this.modifyCustomQuote('remove', index)}
icon={<MdCancel />}
label={variables.getMessage(
'modals.main.marketplace.product.buttons.remove',
)}
/>
</div>
</div>
</div>
))}
</div>
) : (
<div className="photosEmpty">
<div className="emptyNewMessage">
<MdOutlineFormatQuote />
<span className="title">{variables.getMessage(`${QUOTE_SECTION}.no_quotes`)}</span>
<span className="subtitle">
{variables.getMessage('modals.main.settings.sections.message.add_some')}
</span>
<Button
type="settings"
onClick={() => this.modifyCustomQuote('add')}
icon={<MdAdd />}
label={variables.getMessage(`${QUOTE_SECTION}.add`)}
/>
</div>
</div>
)}
</>
);
} else {
// api
customSettings = <></>;
}
return (
<>
{this.state.sourceSection ? (
<Header
title={variables.getMessage(`${QUOTE_SECTION}.title`)}
secondaryTitle={variables.getMessage(
'modals.main.settings.sections.background.source.title',
)}
goBack={() => this.setState({ sourceSection: false })}
report={false}
/>
) : (
<Header
title={variables.getMessage(`${QUOTE_SECTION}.title`)}
setting="quote"
category="quote"
element=".quotediv"
zoomSetting="zoomQuote"
visibilityToggle={true}
/>
</div>
)}
{this.state.sourceSection && (
<Row final={true}>
<Content
title={variables.getMessage('modals.main.settings.sections.background.source.title')}
subtitle={variables.getMessage(`${QUOTE_SECTION}.source_subtitle`)}
/>
<Action>
<SourceDropdown />
</Action>
</Row>
)}
{!this.state.sourceSection && (
<PreferencesWrapper
setting="quote"
zoomSetting="zoomQuote"
category="quote"
visibilityToggle={true}
>
<Section
icon={<MdSource />}
title={variables.getMessage('modals.main.settings.sections.background.source.title')}
subtitle={variables.getMessage(`${QUOTE_SECTION}.source_subtitle`)}
onClick={() => this.setState({ sourceSection: true })}
>
<SourceDropdown />
</Section>
<ButtonOptions />
<AdditionalOptions />
</PreferencesWrapper>
)}
{customSettings}
</>
);
} else {
// api
customSettings = <></>;
}
}
return (
<>
{subSection === 'source' ? (
<Header
title={variables.getMessage(`${QUOTE_SECTION}.title`)}
secondaryTitle={variables.getMessage('modals.main.settings.sections.background.source.title')}
goBack={() => setSourceSection(false)}
report={false}
/>
) : (
<Header
title={variables.getMessage(`${QUOTE_SECTION}.title`)}
setting="quote"
category="quote"
element=".quotediv"
zoomSetting="zoomQuote"
visibilityToggle={true}
/>
)}
{subSection === 'source' && (
<Row final={true}>
<Content
title={variables.getMessage('modals.main.settings.sections.background.source.title')}
subtitle={variables.getMessage(`${QUOTE_SECTION}.source_subtitle`)}
/>
<Action>
<SourceDropdown />
</Action>
</Row>
)}
{subSection !== 'source' && (
<PreferencesWrapper
setting="quote"
zoomSetting="zoomQuote"
category="quote"
visibilityToggle={true}
>
<Section
id="source"
icon={<MdSource />}
title={variables.getMessage('modals.main.settings.sections.background.source.title')}
subtitle={variables.getMessage(`${QUOTE_SECTION}.source_subtitle`)}
onClick={() => setSourceSection(true)}
>
<SourceDropdown />
</Section>
<ButtonOptions />
<AdditionalOptions />
</PreferencesWrapper>
)}
{customSettings}
</>
);
};
export { QuoteOptions as default, QuoteOptions };

View File

@@ -37,7 +37,8 @@ function WelcomeModal() {
useEffect(() => {
setFirstRender(false);
const welcomeTab = localStorage.getItem('welcomeTab');
const randomQuote = offline_quotes[Math.floor(Math.random() * offline_quotes.length)].quote;
const usableQuotes = offline_quotes.filter((q) => q.quote.length <= 100);
const randomQuote = usableQuotes[Math.floor(Math.random() * usableQuotes.length)].quote;
setQuote(randomQuote);
if (welcomeTab) {
const tab = Number(welcomeTab);
@@ -116,29 +117,31 @@ function WelcomeModal() {
const Navigation = () => {
return (
<div className="welcomeButtons">
{currentTab !== 0 ? (
<div className="welcomeButtons z-50 backdrop-blur-sm absolute bottom-0 right-0 w-1/2">
<div className="flex justify-end gap-5 p-6">
{currentTab !== 0 ? (
<Button
type="settings"
onClick={() => prevTab()}
icon={<MdArrowBackIosNew />}
label={variables.getMessage('modals.welcome.buttons.previous')}
/>
) : (
<Button
type="settings"
onClick={() => modalSkip()}
icon={<MdOutlinePreview />}
label={variables.getMessage('modals.welcome.buttons.preview')}
/>
)}
<Button
type="settings"
onClick={() => prevTab()}
icon={<MdArrowBackIosNew />}
label={variables.getMessage('modals.welcome.buttons.previous')}
onClick={() => nextTab()}
icon={<MdArrowForwardIos />}
label={buttonText}
iconPlacement={'right'}
/>
) : (
<Button
type="settings"
onClick={() => modalSkip()}
icon={<MdOutlinePreview />}
label={variables.getMessage('modals.welcome.buttons.preview')}
/>
)}
<Button
type="settings"
onClick={() => nextTab()}
icon={<MdArrowForwardIos />}
label={buttonText}
iconPlacement={'right'}
/>
</div>
</div>
);
};
@@ -196,17 +199,17 @@ function WelcomeModal() {
transition={{ type: 'spring', duration: 3 }}
>
<motion.span
className="text-6xl font-bold max-w-[50%] leading-normal"
className="text-6xl font-bold mx-auto max-w-[75%] 2xl:max-w-[50%] leading-tight lg:leading-snug"
initial={{ opacity: 0, y: 100 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 0 }}
transition={{ type: 'spring', duration: 2 }}
key={currentTab}
>
<mark className="bg-[#ff6b6b]">"{quote}"</mark>
<mark className="bg-transparent text-white shadow-black text-shadow-lg">"{quote}"</mark>
</motion.span>
</motion.div>
<div className="welcomeCredit">
<div className="absolute bottom-4 left-4 text-white">
{variables.getMessage('widgets.background.credit')}{' '}
<motion.span
initial={{ opacity: 0, x: -100 }}
@@ -231,7 +234,7 @@ function WelcomeModal() {
transition={{ type: 'tween', duration: 0.8 }}
style={{ position: 'absolute', height: '100%', width: '50%' }}
>
<div style={{ height: '100%', overflow: 'auto' }}>{tabs[currentTab]}</div>
<div className="h-full overflow-auto p-8">{tabs[currentTab]}</div>
</motion.div>
</AnimatePresence>
<Navigation />

View File

@@ -12,12 +12,6 @@
background-color: t($modal-background);
}
.welcomeCredit {
position: absolute;
bottom: 1rem;
left: 1rem;
}
.MuiFormControlLabel-root {
margin-right: 0;
}
@@ -331,19 +325,6 @@ a.privacy {
}
.welcomeButtons {
z-index: 999;
backdrop-filter: blur(2px);
backdrop-filter: blur(2px);
position: sticky;
bottom: 0;
padding: 25px;
display: flex;
justify-content: flex-end;
gap: 20px;
position: absolute;
bottom: 0;
right: 0;
button {
@include modal-button(standard);
}