feat: add work in progress 7.0 code

Co-authored-by: Alex Sparkes <turbomarshmello@gmail.com>
This commit is contained in:
David Ralph 2022-04-08 14:48:36 +01:00
parent e0820c6b2a
commit 361fae7f25
136 changed files with 8631 additions and 3500 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
node_modules/
.vscode/
build/
.idea/
# Files
package-lock.json

View File

@ -1,5 +1,7 @@
import { PureComponent } from 'react';
import { InputBase } from '@mui/material';
import EventBus from 'modules/helpers/eventbus';
import './autocomplete.scss';
@ -53,7 +55,7 @@ export default class Autocomplete extends PureComponent {
});
}
componentWillUnmount() {
componentWillUmount() {
EventBus.off('refresh');
}
@ -63,21 +65,21 @@ export default class Autocomplete extends PureComponent {
// length will only be > 0 if enabled
if (this.state.filtered.length > 0 && this.state.input.length > 0) {
autocomplete = (
<ul className='suggestions'>
<div className='suggestions'>
{this.state.filtered.map((suggestion) => (
<li key={suggestion} onClick={this.onClick}>
<div key={suggestion} onClick={this.onClick}>
{suggestion}
</li>
</div>
))}
</ul>
</div>
);
}
return (
<>
<input type='text' onChange={this.onChange} value={this.state.input} placeholder={this.props.placeholder || ''} autoComplete='off' id={this.props.id || ''} />
<div style={{display: 'flex', flexFlow: 'column'}}>
<input type='text' onChange={this.onChange} value={this.state.input} placeholder={this.props.placeholder || ''} autoComplete='off' spellCheck={false} id={this.props.id || ''} />
{autocomplete}
</>
</div>
);
}
}

View File

@ -1,23 +1,19 @@
@use 'scss/variables';
.suggestions {
@extend %basic;
text-align: left;
font-size: calc(5px + 1.2vmin);
background-color: rgba(255, 255, 255, 0.8);
color: black;
border-top-width: 0;
list-style: none;
margin-top: 40px;
max-height: 143px;
overflow: hidden;
position: relative;
display: inline-block;
margin-left: 40px;
border-radius: 24px;
width: 430px;
opacity: 0;
opacity: 1;
margin-top: 5px;
li {
padding: 0.5rem;
padding-left: 20px;
div {
padding: 0.5rem 0.5rem 0.5rem 20px;
font-size: 0.6em;
&:hover {
background-color: rgba(255, 255, 255, 0.8);
@ -27,24 +23,18 @@
}
.searchBar {
input[type=text]:focus+.suggestions {
input[type='text']:focus + .suggestions {
opacity: 1;
}
}
@media screen and (min-width: 1400px) {
.suggestions {
margin-top: 50px;
}
}
.dark .suggestions {
background-color: rgba(0, 0, 0, 0.5);
background: rgba(0, 0, 0, 0.7);
color: white;
li {
&:hover {
background-color: rgba(0, 0, 0, 0.5);
background: rgba(0, 0, 0, 0.7);
}
}
}

View File

@ -1,5 +1,5 @@
import variables from 'modules/variables';
import { MdArrowForwardIos } from 'react-icons/md';
import './preview.scss';
export default function Preview(props) {
@ -7,9 +7,9 @@ export default function Preview(props) {
return (
<div className='preview-mode'>
<h1>{getMessage('modals.main.settings.reminder.title')}</h1>
<p>{getMessage('modals.welcome.preview.description')}</p>
<button className='pinNote' onClick={() => props.setup()}>{getMessage('modals.welcome.preview.continue')}</button>
<span className='title'>{getMessage('modals.main.settings.reminder.title')}</span>
<span className='subtitle'>{getMessage('modals.welcome.preview.description')}</span>
<button onClick={() => props.setup()}>{getMessage('modals.welcome.preview.continue')}</button>
</div>
);
}

View File

@ -1,17 +1,22 @@
@import 'scss/variables';
.preview-mode {
@extend %basic;
position: absolute;
bottom: 20px;
right: 20px;
bottom: 1rem;
right: 1rem;
padding: 15px;
color: var(--modal-text);
background: var(--background);
max-width: 300px;
border-radius: .7em;
max-width: 250px;
border-radius: 12px;
z-index: 999;
text-align: left;
cursor: default;
display: flex;
flex-flow: column;
gap: 15px;
h1 {
font-size: 1rem;
button {
@include basicIconButton(10px, 14px, ui);
gap: 20px;
}
}

View File

@ -0,0 +1,107 @@
import variables from 'modules/variables';
import { MdClose, MdEmail, MdContentCopy } from 'react-icons/md';
import { FaTwitter, FaFacebookF } from 'react-icons/fa';
import { AiFillWechat } from 'react-icons/ai';
import { SiTencentqq } from 'react-icons/si';
import Tooltip from '../tooltip/Tooltip';
import { toast } from 'react-toastify';
import './sharemodal.scss';
export default function ShareModal({ modalClose, data }) {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const url = variables.constants.MARKETPLACE_URL + '/share/' + btoa(data.api_name);
const copyLink = () => {
navigator.clipboard.writeText(url);
toast('Link copied!');
};
return (
<div className="shareModal">
<div className="shareHeader">
<span className="title">Share</span>
<Tooltip title="Close">
<div className="close" onClick={modalClose}>
<MdClose />
</div>
</Tooltip>
</div>
<div className="buttons">
<Tooltip title="Twitter">
<button
onClick={() =>
window
.open(
`https://twitter.com/intent/tweet?text=Check out ${data.data.name} on @getmue marketplace: ${url}`,
'_blank',
)
.focus()
}
>
<FaTwitter />
</button>
</Tooltip>
<Tooltip title="Facebook">
<button
onClick={() =>
window.open(`https://www.facebook.com/sharer/sharer.php?u=${url}`, '_blank').focus()
}
>
<FaFacebookF />
</button>
</Tooltip>
<Tooltip title="Email">
<button
onClick={() =>
window
.open(
'mailto:email@example.com?subject=Check%20out%20this%20Mue%20addon!&body=' +
data.data.name +
'on Mue: ' +
url,
'_blank',
)
.focus()
}
>
<MdEmail />
</button>
</Tooltip>
<Tooltip title="WeChat">
<button
onClick={() =>
window
.open(
`https://api.qrserver.com/v1/create-qr-code/?size=154x154&data=${url}`,
'_blank',
)
.focus()
}
>
<AiFillWechat />
</button>
</Tooltip>
<Tooltip title="Tencent QQ">
<button
onClick={() =>
window
.open(`http://connect.qq.com/widget/shareqq/index.html?url=${url}`, '_blank')
.focus()
}
>
<SiTencentqq />
</button>
</Tooltip>
</div>
<div className="copy">
<input type="text" value={url} className="left field" readOnly />
<Tooltip title="Copy link" placement="top">
<button onClick={() => copyLink()}>
<MdContentCopy />
</button>
</Tooltip>
</div>
</div>
);
}

View File

@ -0,0 +1,59 @@
@import '../../../scss/variables';
.shareModal {
@extend %tabText;
display: flex;
flex-flow: column;
gap: 15px;
padding: 15px;
@include themed() {
background: t($modal-background);
}
.buttons {
justify-content: space-between;
display: flex;
gap: 15px;
}
button {
place-items: center;
display: grid;
@include basicIconButton(11px, 1.3rem, modal);
}
.copy {
display: flex;
flex-flow: row;
gap: 15px;
}
input[type='text'] {
@include themed() {
background: t($modal-sidebar);
border-radius: t($borderRadius);
box-shadow: 0 0 0 4px t($modal-sidebarActive);
padding: 11px;
flex: 1;
color: t($color);
}
border: none;
outline: none;
}
.close {
padding: 15px;
place-items: center;
display: grid;
cursor: pointer;
&:hover {
@include themed() {
background: t($modal-sidebar);
border-radius: t($borderRadius);
}
}
}
}
.shareHeader {
display: flex;
flex-flow: row;
justify-content: space-between;
align-items: center;
}

View File

@ -1,10 +1,38 @@
import './tooltip.scss';
import { useState } from "react";
import { useFloating, flip, offset, shift } from "@floating-ui/react-dom";
import "./tooltip.scss";
export default function Tooltip({ children, title, style, placement }) {
const [showTooltip, setShowTooltip] = useState(false);
const { x, y, reference, floating, strategy } = useFloating({
placement: placement || "bottom",
middleware: [flip(), offset(15), shift()],
});
export default function Tooltip({ children, title }) {
return (
<div className='tooltip'>
<div
className="tooltip"
style={style}
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
onFocus={() => setShowTooltip(true)}
onBlur={() => setShowTooltip(false)}
ref={reference}
>
{children}
<span className='tooltipTitle'>{title}</span>
{showTooltip && (
<span
ref={floating}
style={{
position: strategy,
top: y ?? "",
left: x ?? "",
}}
className="tooltipTitle"
>
{title}
</span>
)}
</div>
);
}

View File

@ -0,0 +1,45 @@
import { useState } from 'react';
import { useFloating, flip, offset, shift } from '@floating-ui/react-dom';
import './tooltip.scss';
export default function InfoTooltip({
children,
title,
style,
placement,
subtitle,
linkText,
linkURL,
}) {
const [showTooltip, setShowTooltip] = useState(true);
const { x, y, reference, floating, strategy } = useFloating({
placement: placement || 'bottom-end',
middleware: [flip(), offset(15), shift()],
});
return (
<div className="tooltip" style={style} ref={reference}>
{children}
{showTooltip && (
<div
className="notification"
ref={floating}
style={{
position: strategy,
top: y ?? '',
left: x ?? '',
}}
>
<span className="title">{title}</span>
<span className="subtitle">
{subtitle}{' '}
<a className="link" href={linkURL}>
{linkText}
</a>
</span>
<button onClick={() => setShowTooltip(false)}>Ok, Got it!</button>
</div>
)}
</div>
);
}

View File

@ -1,37 +1,79 @@
// todo: possibly add tooltip placement option
@import 'scss/variables';
.tooltip {
position: relative;
display: inline-block;
display: grid;
}
.tooltipTitle {
min-width: 60px;
background-color: rgba(255, 255, 255, 0.89);
color: #000;
text-align: center;
font-size: 0.6rem;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
top: 100%;
left: 50%;
margin-left: -30px;
visibility: hidden;
cursor: initial;
user-select: none;
@keyframes floating {
0% {
transform: translate(0, -5px);
opacity: 0;
transition: 0.2s;
}
&:hover {
.tooltipTitle {
visibility: visible;
opacity: 1;
100% {
transform: translate(0, -0px);
opacity: 1;
}
}
.tooltipTitle {
@extend %basic;
text-align: center;
font-size: 0.6rem;
padding: 5px 10px;
position: absolute;
z-index: 1;
/*top: 100%;
left: 50%;
margin-top: 15px;
margin-left: -30px;*/
cursor: initial;
user-select: none;
opacity: 1;
animation-name: floating;
animation-duration: 0.3s;
animation-timing-function: ease-in;
}
#modal {
.tooltipTitle {
@include themed() {
background: t($modal-sidebar);
box-shadow: 0 0 0 1px t($modal-sidebarActive);
color: t($color);
}
}
}
.dark .tooltipTitle {
background-color: rgba(0, 0, 0, 0.79);
color: #fff;
#root {
.tooltipTitle {
@extend %basic;
}
}
.tooltipTitle:before {
transform: scale3d(0.2, 0.2, 1);
transition: all 0.2s ease-in-out;
}
.tooltipTitle:after {
transform: translate3d(0, 6px, 0);
transition: all 0.1s ease-in-out;
}
.tooltipTitle:hover:before,
.tooltipTitle:hover:after {
opacity: 1;
transform: scale3d(1, 1, 1);
}
.tooltipTitle:hover:after {
transition: all 0.2s 0.1s ease-in-out;
}
#arrow {
position: absolute;
background: #333;
width: 8px;
height: 8px;
transform: rotate(45deg);
}

View File

@ -6,27 +6,42 @@ export default class ErrorBoundary extends PureComponent {
constructor(props) {
super(props);
this.state = {
error: false
error: false,
};
}
static getDerivedStateFromError(error) {
console.log(error);
variables.stats.postEvent('modal', 'Error occurred');
return {
error: true
return {
error: true,
};
}
render() {
if (this.state.error) {
return (
<div className='emptyitems'>
<div className='emptyMessage'>
<MdErrorOutline/>
<h1>{variables.language.getMessage(variables.languagecode, 'modals.main.error_boundary.title')}</h1>
<p>{variables.language.getMessage(variables.languagecode, 'modals.main.error_boundary.message')}</p>
<button className='refresh' onClick={() => window.location.reload()}>{variables.language.getMessage(variables.languagecode, 'modals.main.error_boundary.refresh')}</button>
<div className="emptyItems">
<div className="emptyMessage">
<MdErrorOutline />
<h1>
{variables.language.getMessage(
variables.languagecode,
'modals.main.error_boundary.title',
)}
</h1>
<p>
{variables.language.getMessage(
variables.languagecode,
'modals.main.error_boundary.message',
)}
</p>
<button className="refresh" onClick={() => window.location.reload()}>
{variables.language.getMessage(
variables.languagecode,
'modals.main.error_boundary.refresh',
)}
</button>
</div>
</div>
);

View File

@ -1,5 +1,5 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { PureComponent, Suspense, lazy } from 'react';
import Modal from 'react-modal';
//import Hotkeys from 'react-hot-keys';
@ -9,7 +9,10 @@ import Preview from '../helpers/preview/Preview';
import EventBus from 'modules/helpers/eventbus';
import Welcome from './welcome/Welcome';
// Welcome modal is lazy loaded as the user won't use it every time they open a tab
// We used to lazy load the main and feedback modals, but doing so broke the modal open animation on first click
const Welcome = lazy(() => import('./welcome/Welcome'));
const renderLoader = () => <></>;
export default class Modals extends PureComponent {
constructor() {
@ -18,14 +21,18 @@ export default class Modals extends PureComponent {
mainModal: false,
updateModal: false,
welcomeModal: false,
preview: false
feedbackModal: false,
preview: false,
};
}
componentDidMount() {
if (localStorage.getItem('showWelcome') === 'true' && window.location.search !== '?nointro=true') {
if (
localStorage.getItem('showWelcome') === 'true' &&
window.location.search !== '?nointro=true'
) {
this.setState({
welcomeModal: true
welcomeModal: true,
});
variables.stats.postEvent('modal', 'Opened welcome');
}
@ -45,7 +52,7 @@ export default class Modals extends PureComponent {
closeWelcome() {
localStorage.setItem('showWelcome', false);
this.setState({
welcomeModal: false
welcomeModal: false,
});
EventBus.dispatch('refresh', 'widgetsWelcomeDone');
EventBus.dispatch('refresh', 'widgets');
@ -57,14 +64,14 @@ export default class Modals extends PureComponent {
localStorage.setItem('welcomePreview', true);
this.setState({
welcomeModal: false,
preview: true
preview: true,
});
EventBus.dispatch('refresh', 'widgetsWelcome');
}
toggleModal(type, action) {
this.setState({
[type]: action
[type]: action,
});
if (action !== false) {
@ -75,15 +82,38 @@ export default class Modals extends PureComponent {
render() {
return (
<>
{this.state.welcomeModal === false ? <Navbar openModal={(modal) => this.toggleModal(modal, true)}/> : null}
<Modal closeTimeoutMS={300} id='modal' onRequestClose={() => this.toggleModal('mainModal', false)} isOpen={this.state.mainModal} className='Modal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
<Main modalClose={() => this.toggleModal('mainModal', false)}/>
{this.state.welcomeModal === false ? (
<Navbar openModal={(modal) => this.toggleModal(modal, true)} />
) : null}
<Modal
closeTimeoutMS={300}
id="modal"
onRequestClose={() => this.toggleModal('mainModal', false)}
isOpen={this.state.mainModal}
className="Modal mainModal"
overlayClassName="Overlay"
ariaHideApp={false}
>
<Main modalClose={() => this.toggleModal('mainModal', false)} />
</Modal>
<Modal closeTimeoutMS={300} onRequestClose={() => this.closeWelcome()} isOpen={this.state.welcomeModal} className='Modal welcomemodal mainModal' overlayClassName='Overlay welcomeoverlay' shouldCloseOnOverlayClick={false} ariaHideApp={false}>
<Welcome modalClose={() => this.closeWelcome()} modalSkip={() => this.previewWelcome()}/>
</Modal>
{this.state.preview ? <Preview setup={() => window.location.reload()}/> : null}
{/*variables.keybinds.toggleModal && variables.keybinds.toggleModal !== '' ? <Hotkeys keyName={variables.keybinds.toggleModal} onKeyDown={() => this.toggleModal('mainModal', (this.state.mainModal === true ? false : true))}/> : null*/}
<Suspense fallback={renderLoader()}>
<Modal
closeTimeoutMS={300}
onRequestClose={() => this.closeWelcome()}
isOpen={this.state.welcomeModal}
className="Modal welcomemodal mainModal"
overlayClassName="Overlay welcomeoverlay"
shouldCloseOnOverlayClick={false}
ariaHideApp={false}
>
<Welcome
modalClose={() => this.closeWelcome()}
modalSkip={() => this.previewWelcome()}
/>
</Modal>
</Suspense>
{this.state.preview ? <Preview setup={() => window.location.reload()} /> : null}
{/*variables.keybinds.toggleModal && variables.keybinds.toggleModal !== '' ? <Hotkeys keyName={variables.keybinds.toggleModal} onKeyDown={() => this.toggleModal('mainModal', (this.state.mainModal === true ? false : true))}/> : null*/}
</>
);
}

View File

@ -1,56 +1,70 @@
import variables from 'modules/variables';
import { Suspense, lazy } from 'react';
import { Suspense, lazy, useState } from 'react';
import { MdClose } from 'react-icons/md';
import Tabs from './tabs/backend/Tabs';
import './scss/index.scss';
import Tooltip from '../../helpers/tooltip/Tooltip';
// Lazy load all the tabs instead of the modal itself
const Settings = lazy(() => import('./tabs/Settings'));
const Addons = lazy(() => import('./tabs/Addons'));
const Marketplace = lazy(() => import('./tabs/Marketplace'));
const renderLoader = () => (
<Tabs>
const renderLoader = (current) => (
<Tabs current={current}>
<div label={variables.language.getMessage(variables.languagecode, 'modals.main.loading')}>
<div className='emptyitems'>
<div className='emptyMessage'>
<h1>{variables.language.getMessage(variables.languagecode, 'modals.main.loading')}</h1>
<div className="emptyItems">
<div className="emptyMessage">
<div className="loaderHolder">
<div id="loader"></div>
<span className="subtitle">Just be a sec.</span>
</div>
</div>
</div>
</div>
<div label='' style={{ display: 'none' }}></div>
<div label="" style={{ display: 'none' }}></div>
</Tabs>
);
export default function MainModal({ modalClose }) {
const display = (localStorage.getItem('showReminder') === 'true') ? 'block' : 'none';
const display = localStorage.getItem('showReminder') === 'true' ? 'block' : 'none';
const [currentTab, setCurrentTab] = useState(0);
const changeTab = (type) => {
switch (type) {
case 'settings':
setCurrentTab(<Settings changeTab={changeTab} />);
break;
case 'addons':
setCurrentTab(<Addons changeTab={changeTab} />);
break;
case 'marketplace':
setCurrentTab(<Marketplace changeTab={changeTab} />);
break;
default:
break;
}
};
if (currentTab === 0) {
setCurrentTab(<Settings changeTab={changeTab} />);
}
return (
<>
<span className='closeModal' onClick={modalClose}>&times;</span>
<Tabs navbar={true}>
<div label={variables.language.getMessage(variables.languagecode, 'modals.main.navbar.settings')} name='settings'>
<Suspense fallback={renderLoader()}>
<Settings/>
</Suspense>
</div>
<div label={variables.language.getMessage(variables.languagecode, 'modals.main.navbar.addons')} name='addons'>
<Suspense fallback={renderLoader()}>
<Addons/>
</Suspense>
</div>
<div label={variables.language.getMessage(variables.languagecode, 'modals.main.navbar.marketplace')} name='marketplace'>
<Suspense fallback={renderLoader()}>
<Marketplace/>
</Suspense>
</div>
</Tabs>
<div className='reminder-info' style={{ display }}>
<h1>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.reminder.title')}</h1>
<p>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.reminder.message')}</p>
<button className='pinNote' onClick={() => window.location.reload()}>{variables.language.getMessage(variables.languagecode, 'modals.main.error_boundary.refresh')}</button>
</div>
</>
<div className="frame">
<Tooltip
style={{ position: 'absolute', top: '3rem', right: '3rem' }}
title="close"
key="cheese"
>
<span className="closeModal" onClick={modalClose}>
<MdClose />
</span>
</Tooltip>
<Suspense fallback={renderLoader(currentTab)}>{currentTab}</Suspense>
</div>
);
}

View File

@ -0,0 +1,45 @@
import variables from "modules/variables";
export default function Collection({ items, toggleFunction }) {
return [
<div className='collection starWars'>
<div className='content'>
<div className="tags">
<div className="tag">
<span>Star Wars</span>
</div>
</div>
<span className='title'>Star Wars Collection</span>
<span className='subtitle'>A Collection of stuff inspired by the film franchise star wars..</span>
<button>Explore Collection</button>
</div>
</div>,
<div className="items">
{items.map((item) => (
<div
className="item"
onClick={() => toggleFunction(item)}
key={item.name}
>
<img
alt="icon"
draggable="false"
src={variables.constants.DDG_IMAGE_PROXY + item.icon_url}
/>
<div className="card-details">
<span className="card-title">{item.display_name || item.name}</span>
<span className="card-subtitle">{item.author}</span>
<div className="tags">
<div className="tag">
<span>{item.author}</span>
</div>
<div className='moreTag'>
<span>1</span>
</div>
</div>
</div>
</div>
))}
</div>,
];
}

View File

@ -1,19 +1,38 @@
import variables from 'modules/variables';
import { PureComponent, Fragment } from 'react';
import Tooltip from '../../../helpers/tooltip/Tooltip';
import { toast } from 'react-toastify';
import { MdArrowBack } from 'react-icons/md';
import {
MdArrowBack,
MdFavoriteBorder,
MdIosShare,
MdFlag,
MdWarning,
MdAccountCircle,
MdBugReport,
MdFormatQuote,
MdImage,
MdTranslate,
MdKeyboardArrowDown,
MdKeyboardArrowUp,
} from 'react-icons/md';
import Modal from 'react-modal';
import { install, uninstall } from 'modules/helpers/marketplace';
import Lightbox from './Lightbox';
import ShareModal from '../../../helpers/sharemodal/ShareModal';
export default class Item extends PureComponent {
constructor(props) {
super(props);
this.state = {
showLightbox: false,
showUpdateButton: (this.props.addonInstalled === true && this.props.addonInstalledVersion !== this.props.data.version)
showUpdateButton:
this.props.addonInstalled === true &&
this.props.addonInstalledVersion !== this.props.data.version,
showMore: false,
shareModal: false,
};
}
@ -21,11 +40,19 @@ export default class Item extends PureComponent {
uninstall(this.props.data.type, this.props.data.display_name);
install(this.props.data.type, this.props.data);
toast(variables.language.getMessage(variables.languagecode, 'toasts.updated'));
this.setState({
showUpdateButton: false
this.setState({
showUpdateButton: false,
});
}
toggleShowMore() {
if (this.state.showMore === true) {
this.setState({ showMore: false });
} else {
this.setState({ showMore: true });
}
}
render() {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
@ -36,15 +63,20 @@ export default class Item extends PureComponent {
let warningHTML;
if (this.props.data.quote_api) {
warningHTML = (
<div className='productInformation'>
<ul>
<li className='header'>{getMessage('modals.main.marketplace.product.quote_warning.title')}</li>
<li id='updated'>{getMessage('modals.main.marketplace.product.quote_warning.description')}</li>
</ul>
<div className="itemWarning">
<div className="topRow">
<MdWarning />
<div className="title">
{getMessage('modals.main.marketplace.product.quote_warning.title')}
</div>
</div>
<div className="subtitle">
{getMessage('modals.main.marketplace.product.quote_warning.description')}
</div>
</div>
);
}
// prevent console error
let iconsrc = variables.constants.DDG_IMAGE_PROXY + this.props.data.icon;
if (!this.props.data.icon) {
@ -54,48 +86,170 @@ export default class Item extends PureComponent {
let updateButton;
if (this.state.showUpdateButton) {
updateButton = (
<Fragment key='update'>
<br/><br/>
<button className='removeFromMue' onClick={() => this.updateAddon()}>
<Fragment key="update">
<button className="removeFromMue" onClick={() => this.updateAddon()}>
{getMessage('modals.main.addons.product.buttons.update_addon')}
</button>
</Fragment>
);
}
}
return (
<div id='item'>
<br/>
<MdArrowBack className='backArrow' onClick={this.props.toggleFunction}/>
<br/>
<h1>{this.props.data.display_name}</h1>
{this.props.button}
{updateButton}
<br/><br/>
{iconsrc ? <img alt='product' draggable='false' src={iconsrc} onClick={() => this.setState({ showLightbox: true })}/> : null}
<div className='side'>
<div className='productInformation'>
<ul>
<li className='header'>{getMessage('modals.main.marketplace.product.version')}</li>
{updateButton ? <li>{this.props.data.version} (Installed: {this.props.data.addonInstalledVersion})</li> : <li>{this.props.data.version}</li>}
<br/>
<li className='header'>{getMessage('modals.main.marketplace.product.author')}</li>
<li>{this.props.data.author}</li>
</ul>
</div>
<br/>
{warningHTML}
</div>
<div className='sidebr'>
<br/><br/>
</div>
<div className='informationContainer'>
<h1 className='overview'>{getMessage('modals.main.marketplace.product.overview')}</h1>
<p className='description' dangerouslySetInnerHTML={{ __html: this.props.data.description }}></p>
</div>
<Modal closeTimeoutMS={100} onRequestClose={() => this.setState({ showLightbox: false })} isOpen={this.state.showLightbox} className='Modal lightboxmodal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}>
<Lightbox modalClose={() => this.setState({ showLightbox: false })} img={iconsrc}/>
<div id="item">
<Modal
closeTimeoutMS={300}
isOpen={this.state.shareModal}
className="Modal mainModal"
overlayClassName="Overlay"
ariaHideApp={false}
onRequestClose={() => this.setState({ shareModal: false })}
>
<ShareModal
data={this.props.data}
modalClose={() => this.setState({ shareModal: false })}
/>
</Modal>
<div className="flexTopMarketplace">
<div className="returnButton">
<Tooltip title="back" key="cheese">
<MdArrowBack className="backArrow" onClick={this.props.toggleFunction} />
</Tooltip>
</div>
<span className="mainTitle">Marketplace</span>
</div>
<div className="itemPage">
<div className="itemShowcase">
<div className="titleTop">
<span className="itemTitle">{this.props.data.display_name}</span>
<span className="subtitle">{this.props.data.author}</span>
</div>
<img
alt="product"
draggable="false"
src={iconsrc}
onClick={() => this.setState({ showLightbox: true })}
/>
<span className="title">Description</span>
<span
className={this.state.showMore ? 'description' : 'description truncate'}
dangerouslySetInnerHTML={{ __html: this.props.data.description }}
/>
{this.props.data.description.length > 100 ? (
<div className="showMore" onClick={() => this.toggleShowMore()}>
{this.state.showMore === true ? (
<>
<span>Show Less</span>
<MdKeyboardArrowDown />
</>
) : (
<>
<span>Show More</span>
<MdKeyboardArrowUp />
</>
)}
</div>
) : null}
<div className="moreInfo">
<div className="infoItem">
<MdBugReport />
<div className="text">
<span className="header">
{getMessage('modals.main.marketplace.product.version')}
</span>
{updateButton ? (
<span>
{this.props.data.version} (Installed: {this.props.data.addonInstalledVersion})
</span>
) : (
<span>{this.props.data.version}</span>
)}
</div>
</div>
<div className="infoItem">
<MdAccountCircle />
<div className="text">
<span className="header">
{getMessage('modals.main.marketplace.product.author')}
</span>
<span>{this.props.data.author}</span>
</div>
</div>
{this.props.data.data.quotes ? (
<div className="infoItem">
<MdFormatQuote />
<div className="text">
<span className="header">No. Quotes</span>
<span>{this.props.data.data.quotes.length}</span>
</div>
</div>
) : null}
{this.props.data.data.photos ? (
<div className="infoItem">
<MdImage />
<div className="text">
<span className="header">No. Images</span>
<span>{this.props.data.data.photos.length}</span>
</div>
</div>
) : null}
{!this.props.data.data.photos ? (
<div className="infoItem">
<MdTranslate />
<div className="text">
<span className="header">Language</span>
<span>English</span>
</div>
</div>
) : null}
<div className="infoItem">
<MdIosShare />
<div className="text">
<span className="header">Shares</span>
<span>324</span>
</div>
</div>
</div>
</div>
<div className="itemInfo">
<img
alt="icon"
draggable="false"
src={variables.constants.DDG_IMAGE_PROXY + this.props.data.data.icon_url}
/>
{this.props.button}
<div className="iconButtons">
<Tooltip title="Share" key="cheese">
<MdIosShare onClick={() => this.setState({ shareModal: true })} />
</Tooltip>
<Tooltip title="Report" key="cheese">
<MdFlag
onClick={() =>
window.open(
variables.constants.REPORT_ITEM +
this.props.data.display_name.split(' ').join('+'),
'_blank',
)
}
/>
</Tooltip>
</div>
{this.props.data.data.collection ? (
<div className="inCollection">
<span className="subtitle">Part of</span>
<span className="title">Red Dead Collection</span>
<button>Explore</button>
</div>
) : null}
{warningHTML}
{/*<div className="itemWarning">
<div className="topRow">
<NewReleasesRounded />
<div className="title">Update</div>
</div>
<div className="subtitle">React > Vue</div>
</div>*/}
</div>
</div>
</div>
);
}

View File

@ -1,17 +1,45 @@
import variables from 'modules/variables';
export default function Items({ items, toggleFunction }) {
export default function Items({
type,
items,
collections,
toggleFunction,
collectionFunction,
onCollection,
}) {
return (
<div className='items'>
{items.map((item) => (
<div className='item' onClick={() => toggleFunction(item.name)} key={item.name}>
<img alt='icon' draggable='false' src={variables.constants.DDG_IMAGE_PROXY + item.icon_url} />
<div className='details'>
<h4>{item.display_name || item.name}</h4>
<p>{item.author}</p>
<>
{type === 'all' && !onCollection ? (
<>
{collections.map((collection) => (
<div className="collection">
<div className="content">
<span className="title">{collection.display_name}</span>
<span className="subtitle">{collection.description}</span>
<button onClick={() => collectionFunction(collection.name)}>
Explore Collection
</button>
</div>
</div>
))}
</>
) : null}
<div className="items">
{items.map((item) => (
<div className="item" onClick={() => toggleFunction(item)} key={item.name}>
<img
alt="icon"
draggable="false"
src={variables.constants.DDG_IMAGE_PROXY + item.icon_url}
/>
<div className="card-details">
<span className="card-title">{item.display_name || item.name}</span>
<span className="card-subtitle">{item.author}</span>
</div>
</div>
</div>
))}
</div>
))}
</div>
</>
);
}

View File

@ -8,11 +8,12 @@ export default function SideloadFailedModal({ modalClose, reason }) {
<>
<h1>{getMessage('modals.main.error_boundary.title')}</h1>
<span>{getMessage('modals.main.addons.sideload.failed')}</span>
<br/><br/>
<br />
<br />
<span>{reason}</span>
<div className='resetfooter'>
<button className='round import' style={{ marginLeft: '-30px' }} onClick={modalClose}>
<MdClose/>
<div className="resetFooter">
<button className="round import" style={{ marginLeft: '-30px' }} onClick={modalClose}>
<MdClose />
</button>
</div>
</>

View File

@ -17,48 +17,53 @@ export default class Added extends PureComponent {
this.state = {
installed: JSON.parse(localStorage.getItem('installed')),
item: {},
button: ''
button: '',
};
this.buttons = {
uninstall: <button className='removeFromMue' onClick={() => this.uninstall()}>{this.getMessage('modals.main.marketplace.product.buttons.remove')}</button>,
uninstall: (
<button className="removeFromMue" onClick={() => this.uninstall()}>
{this.getMessage('modals.main.marketplace.product.buttons.remove')}
</button>
),
};
}
toggle(type, data) {
if (type === 'item') {
const installed = JSON.parse(localStorage.getItem('installed'));
const info = installed.find((i) => i.name === data);
const info = {
data: installed.find((i) => i.name === data.name),
};
this.setState({
item: {
type: info.type,
name: data,
display_name: info.name,
author: info.author,
description: urlParser(info.description.replace(/\n/g, '<br>')),
type: info.data.type,
display_name: info.data.name,
author: info.data.author,
description: urlParser(info.data.description.replace(/\n/g, '<br>')),
//updated: info.updated,
version: info.version,
icon: info.screenshot_url,
quote_api: info.quote_api || null
version: info.data.version,
icon: info.data.screenshot_url,
data: info.data,
},
button: this.buttons.uninstall
button: this.buttons.uninstall,
});
variables.stats.postEvent('marketplace', 'Item viewed');
} else {
this.setState({
item: {}
item: {},
});
}
}
uninstall() {
uninstall(this.state.item.type, this.state.item.display_name);
toast(this.getMessage('toasts.uninstalled'));
this.setState({
button: '',
installed: JSON.parse(localStorage.getItem('installed'))
installed: JSON.parse(localStorage.getItem('installed')),
});
variables.stats.postEvent('marketplace', 'Uninstall');
@ -84,7 +89,7 @@ export default class Added extends PureComponent {
}
this.setState({
installed: installed
installed: installed,
});
if (sendEvent) {
@ -95,16 +100,20 @@ export default class Added extends PureComponent {
updateCheck() {
let updates = 0;
this.state.installed.forEach(async (item) => {
const data = await (await fetch(variables.constants.MARKETPLACE_URL + '/item/' + item.name)).json();
const data = await (
await fetch(variables.constants.MARKETPLACE_URL + '/item/' + item.name)
).json();
if (data.version !== item.version) {
updates++;
}
});
if (updates > 0) {
toast(this.getMessage('modals.main.addons.updates_available', {
amount: updates
}));
if (updates > 0) {
toast(
this.getMessage('modals.main.addons.updates_available', {
amount: updates,
}),
);
} else {
toast(this.getMessage('modals.main.addons.no_updates'));
}
@ -117,31 +126,48 @@ export default class Added extends PureComponent {
render() {
if (this.state.installed.length === 0) {
return (
<div className='emptyitems'>
<div className='emptyMessage'>
<MdLocalMall/>
<h1>{this.getMessage('modals.main.addons.empty.title')}</h1>
<p className='description'>{this.getMessage('modals.main.addons.empty.description')}</p>
<div className="emptyItems">
<div className="emptyMessage">
<MdLocalMall />
<span className="title">{this.getMessage('modals.main.addons.empty.title')}</span>
<span className="subtitle">
{this.getMessage('modals.main.addons.empty.description')}
</span>
</div>
</div>
);
}
if (this.state.item.display_name) {
return <Item data={this.state.item} button={this.state.button} toggleFunction={() => this.toggle()} />;
return (
<Item
data={this.state.item}
button={this.state.button}
toggleFunction={() => this.toggle()}
/>
);
}
return (
<>
<Dropdown label={this.getMessage('modals.main.addons.sort.title')} name='sortAddons' onChange={(value) => this.sortAddons(value)}>
<option value='newest'>{this.getMessage('modals.main.addons.sort.newest')}</option>
<option value='oldest'>{this.getMessage('modals.main.addons.sort.oldest')}</option>
<option value='a-z'>{this.getMessage('modals.main.addons.sort.a_z')}</option>
<option value='z-a'>{this.getMessage('modals.main.addons.sort.z_a')}</option>
<Dropdown
label={this.getMessage('modals.main.addons.sort.title')}
name="sortAddons"
onChange={(value) => this.sortAddons(value)}
>
<option value="newest">{this.getMessage('modals.main.addons.sort.newest')}</option>
<option value="oldest">{this.getMessage('modals.main.addons.sort.oldest')}</option>
<option value="a-z">{this.getMessage('modals.main.addons.sort.a_z')}</option>
<option value="z-a">{this.getMessage('modals.main.addons.sort.z_a')}</option>
</Dropdown>
<button className='addToMue sideload updateCheck' onClick={() => this.updateCheck()}>{this.getMessage('modals.main.addons.check_updates')}</button>
<br/>
<Items items={this.state.installed} toggleFunction={(input) => this.toggle('item', input)} />
<button className="addToMue sideload updateCheck" onClick={() => this.updateCheck()}>
{this.getMessage('modals.main.addons.check_updates')}
</button>
<br />
<Items
items={this.state.installed}
toggleFunction={(input) => this.toggle('item', input)}
/>
</>
);
}

View File

@ -5,12 +5,17 @@ import {
MdOutlineInsertPhoto as Photos,
MdOutlineFormatQuote as Quotes,
MdUpload as ImportIcon,
MdDownload as ExportIcon
MdDownload as ExportIcon,
MdArrowBack,
MdDownload,
MdOpenInNew,
} from 'react-icons/md';
import { TextField } from '@mui/material';
import { toast } from 'react-toastify';
import SettingsItem from '../../../main/settings/SettingsItem';
import { saveFile } from 'modules/helpers/settings/modals';
import Tooltip from '../../../../helpers/tooltip/Tooltip';
import FileUpload from '../../settings/FileUpload';
import Dropdown from '../../settings/Dropdown';
@ -29,37 +34,48 @@ export default class Create extends PureComponent {
version: '',
author: '',
icon_url: '',
screenshot_url: ''
screenshot_url: '',
},
addonData: '',
settingsClasses: {
current: 'toggle lightTheme',
json: 'toggle lightTheme'
}
json: 'toggle lightTheme',
},
};
}
changeTab(tab, type) {
changeTab(tab, type) {
if (type) {
return this.setState({
return this.setState({
currentTab: tab,
addonMetadata: {
type: type
}
type: type,
},
});
} else {
this.setState({
currentTab: tab,
});
}
this.setState({
currentTab: tab
});
}
importSettings(input) {
const data = input || localStorage;
let settings = {};
Object.keys(data).forEach((key) => {
if (key === 'statsData' || key === 'firstRun' || key === 'showWelcome' || key === 'language' || key === 'installed' || key === 'stats' || key === 'backup_settings' || key === 'showReminder'
|| key === 'experimental' || key === 'debugtimeout' || key === 'quotelanguage') {
if (
key === 'statsData' ||
key === 'firstRun' ||
key === 'showWelcome' ||
key === 'language' ||
key === 'installed' ||
key === 'stats' ||
key === 'backup_settings' ||
key === 'showReminder' ||
key === 'experimental' ||
key === 'debugtimeout' ||
key === 'quotelanguage'
) {
return;
}
settings[key] = localStorage.getItem(key);
@ -69,60 +85,57 @@ export default class Create extends PureComponent {
addonData: settings,
settingsClasses: {
current: input ? 'toggle lightTheme active' : 'toggle lightTheme',
json: input ? 'toggle lightTheme active' : 'toggle lightTheme'
}
json: input ? 'toggle lightTheme active' : 'toggle lightTheme',
},
});
toast(variables.language.getMessage(variables.languagecode, 'toasts.imported'));
}
updateQuotePackType(type) {
const addonMetadata = {
type,
name: this.state.addonMetadata.name,
description: this.state.addonMetadata.description,
version: this.state.addonMetadata.version,
author: this.state.addonMetadata.author,
icon_url: this.state.addonMetadata.icon_url,
screenshot_url: this.state.addonMetadata.screenshot_url,
};
if (type === 'quotePack') {
this.setState({
this.setState({
addonMetadata: {
type,
name: this.state.addonMetadata.name,
description: this.state.addonMetadata.description,
version: this.state.addonMetadata.version,
author: this.state.addonMetadata.author,
icon_url: this.state.addonMetadata.icon_url,
screenshot_url: this.state.addonMetadata.screenshot_url,
quotes: []
}
addonMetadata,
quotes: [],
},
});
} else {
this.setState({
this.setState({
addonMetadata: {
type,
name: this.state.addonMetadata.name,
description: this.state.addonMetadata.description,
version: this.state.addonMetadata.version,
author: this.state.addonMetadata.author,
icon_url: this.state.addonMetadata.icon_url,
screenshot_url: this.state.addonMetadata.screenshot_url
addonMetadata,
},
addonData: {
url: '',
name: '',
author: ''
}
author: '',
},
});
}
}
updateQuotePackAPI(type, data) {
this.setState({
addonData: {
url: (type === 'url') ? data : this.state.addonData.url || '',
name: (type === 'name') ? data : this.state.addonData.name || '',
author: (type === 'author') ? data : this.state.addonData.author || '',
}
url: type === 'url' ? data : this.state.addonData.url || '',
name: type === 'name' ? data : this.state.addonData.name || '',
author: type === 'author' ? data : this.state.addonData.author || '',
},
});
}
importQuotes() {
this.setState({
addonData: JSON.parse(localStorage.getItem('customQuote')) || []
addonData: JSON.parse(localStorage.getItem('customQuote')) || [],
});
toast(variables.language.getMessage(variables.languagecode, 'toasts.imported'));
@ -137,9 +150,9 @@ export default class Create extends PureComponent {
photographer: '???',
location: '???',
url: {
default: item
}
}
default: item,
},
};
});
toast(variables.language.getMessage(variables.languagecode, 'toasts.imported'));
} catch (e) {
@ -148,43 +161,57 @@ export default class Create extends PureComponent {
}
this.setState({
addonData: data
addonData: data,
});
}
downloadAddon() {
saveFile({
name: this.state.addonMetadata.name,
description: this.state.addonMetadata.description,
type: (this.state.addonMetadata.type === 'quote_api') ? 'quotes' : this.state.addonMetadata.type,
version: this.state.addonMetadata.version,
author: this.state.addonMetadata.author,
icon_url: this.state.addonMetadata.icon_url,
screenshot_url: this.state.addonMetadata.screenshot_url,
[this.state.addonMetadata.type]: this.state.addonData
}, this.state.addonMetadata.name + '.json');
saveFile(
{
name: this.state.addonMetadata.name,
description: this.state.addonMetadata.description,
type:
this.state.addonMetadata.type === 'quote_api' ? 'quotes' : this.state.addonMetadata.type,
version: this.state.addonMetadata.version,
author: this.state.addonMetadata.author,
icon_url: this.state.addonMetadata.icon_url,
screenshot_url: this.state.addonMetadata.screenshot_url,
[this.state.addonMetadata.type]: this.state.addonData,
},
this.state.addonMetadata.name + '.json',
);
}
render() {
let tabContent;
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const chooseType = (
<>
<h3>{getMessage('modals.main.settings.sections.time.type')}</h3>
<div className='themesToggleArea'>
<div className='options'>
<div className='toggle lightTheme' onClick={() => this.changeTab(2, 'photos')}>
<Photos/>
<div className="smallBanner">
<div className="content">
<span className="title">Help Centre</span>
<span className="subtitle">
Home of all docs and guides on creating addons for Mue's marketplace
</span>
</div>
<button>
Open Site
<MdOpenInNew />
</button>
</div>
<div className="themesToggleArea">
<div className="options">
<div className="toggle lightTheme" onClick={() => this.changeTab(2, 'photos')}>
<Photos />
<span>{getMessage('modals.main.marketplace.photo_packs')}</span>
</div>
<div className='toggle lightTheme' onClick={() => this.changeTab(2, 'quotes')}>
<Quotes/>
<div className="toggle lightTheme" onClick={() => this.changeTab(2, 'quotes')}>
<Quotes />
<span>{getMessage('modals.main.marketplace.quote_packs')}</span>
</div>
<div className='toggle lightTheme' onClick={() => this.changeTab(2, 'settings')}>
<Settings/>
</div>
<div className="toggle lightTheme" onClick={() => this.changeTab(2, 'settings')}>
<Settings />
<span>{getMessage('modals.main.marketplace.preset_settings')}</span>
</div>
</div>
@ -193,142 +220,371 @@ export default class Create extends PureComponent {
);
// todo: find a better way to do all this
const nextDescriptionDisabled = (this.state.addonMetadata.name !== undefined &&
const nextDescriptionDisabled = !(
this.state.addonMetadata.name !== undefined &&
this.state.addonMetadata.description !== undefined &&
this.state.addonMetadata.version !== undefined && this.state.addonMetadata.author !== undefined &&
this.state.addonMetadata.icon_url !== undefined && this.state.addonMetadata.screenshot_url !== undefined)
? false : true;
this.state.addonMetadata.version !== undefined &&
this.state.addonMetadata.author !== undefined &&
this.state.addonMetadata.icon_url !== undefined &&
this.state.addonMetadata.screenshot_url !== undefined
);
const setMetadata = (data, type) => {
this.setState({
addonMetadata: {
name: (type === 'name') ? data : this.state.addonMetadata.name,
description: (type === 'description') ? data : this.state.addonMetadata.description,
version: (type === 'version') ? data : this.state.addonMetadata.version,
author: (type === 'author') ? data : this.state.addonMetadata.author,
icon_url: (type === 'icon_url') ? data : this.state.addonMetadata.icon_url,
screenshot_url: (type === 'screenshot_url') ? data : this.state.addonMetadata.screenshot_url,
type: this.state.addonMetadata.type
}
name: type === 'name' ? data : this.state.addonMetadata.name,
description: type === 'description' ? data : this.state.addonMetadata.description,
version: type === 'version' ? data : this.state.addonMetadata.version,
author: type === 'author' ? data : this.state.addonMetadata.author,
icon_url: type === 'icon_url' ? data : this.state.addonMetadata.icon_url,
screenshot_url:
type === 'screenshot_url' ? data : this.state.addonMetadata.screenshot_url,
type: this.state.addonMetadata.type,
},
});
};
const writeDescription = (
const writeDescription = (
<>
<h3>{getMessage('modals.main.marketplace.product.information')}</h3>
<TextField label={getMessage('modals.main.addons.create.metadata.name')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonMetadata.name} onInput={(e) => setMetadata(e.target.value, 'name')}/>
<TextField label={getMessage('modals.main.marketplace.product.version')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonMetadata.version} onInput={(e) => setMetadata(e.target.value, 'version')}/>
<TextField label={getMessage('modals.main.marketplace.product.author')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonMetadata.author} onInput={(e) => setMetadata(e.target.value, 'author')}/>
<TextField label={getMessage('modals.main.addons.create.metadata.icon_url')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonMetadata.icon_url} onInput={(e) => setMetadata(e.target.value, 'icon_url')}/>
<TextField label={getMessage('modals.main.addons.create.metadata.screenshot_url')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonMetadata.screenshot_url} onInput={(e) => setMetadata(e.target.value, 'screenshot_url')}/>
<TextField label={getMessage('modals.main.addons.create.metadata.description')} varient='outlined' InputLabelProps={{ shrink: true }} multiline spellCheck={false} rows={4} value={this.state.addonMetadata.description} onInput={(e) => setMetadata(e.target.value, 'description')}/>
<br/>
<button onClick={() => this.changeTab(1)} className='uploadbg' style={{ marginRight: '10px' }}>{getMessage('modals.welcome.buttons.previous')}</button>
<button onClick={() => this.changeTab(this.state.addonMetadata.type)} className='uploadbg' disabled={nextDescriptionDisabled}>{getMessage('modals.welcome.buttons.next')}</button>
<div className="smallBanner">
<div className="content">
<span className="title" style={{ textTransform: 'capitalize' }}>
Create {this.state.addonMetadata.type} Pack
</span>
<span className="subtitle">Description of what is being made</span>
</div>
<button>
Example
<MdDownload />
</button>
</div>
<SettingsItem title={getMessage('modals.main.addons.create.metadata.name')}>
<TextField
label={getMessage('modals.main.addons.create.metadata.name')}
varient="outlined"
InputLabelProps={{ shrink: true }}
value={this.state.addonMetadata.name}
onInput={(e) => setMetadata(e.target.value, 'name')}
/>
</SettingsItem>
<SettingsItem title={getMessage('modals.main.marketplace.product.version')}>
<TextField
label={getMessage('modals.main.marketplace.product.version')}
varient="outlined"
InputLabelProps={{ shrink: true }}
value={this.state.addonMetadata.version}
onInput={(e) => setMetadata(e.target.value, 'version')}
/>
</SettingsItem>
<SettingsItem title={getMessage('modals.main.marketplace.product.author')}>
<TextField
label={getMessage('modals.main.marketplace.product.author')}
varient="outlined"
InputLabelProps={{ shrink: true }}
value={this.state.addonMetadata.author}
onInput={(e) => setMetadata(e.target.value, 'author')}
/>
</SettingsItem>
<SettingsItem title={getMessage('modals.main.addons.create.metadata.icon_url')}>
<TextField
label={getMessage('modals.main.addons.create.metadata.icon_url')}
varient="outlined"
InputLabelProps={{ shrink: true }}
value={this.state.addonMetadata.icon_url}
onInput={(e) => setMetadata(e.target.value, 'icon_url')}
/>
</SettingsItem>
<SettingsItem title={getMessage('modals.main.addons.create.metadata.screenshot_url')}>
<TextField
label={getMessage('modals.main.addons.create.metadata.screenshot_url')}
varient="outlined"
InputLabelProps={{ shrink: true }}
value={this.state.addonMetadata.screenshot_url}
onInput={(e) => setMetadata(e.target.value, 'screenshot_url')}
/>
</SettingsItem>
<SettingsItem title={getMessage('modals.main.addons.create.metadata.description')}>
<TextField
label={getMessage('modals.main.addons.create.metadata.description')}
varient="outlined"
InputLabelProps={{ shrink: true }}
multiline
spellCheck={false}
rows={4}
value={this.state.addonMetadata.description}
onInput={(e) => setMetadata(e.target.value, 'description')}
/>
</SettingsItem>
<div className="createButtons">
<button onClick={() => this.changeTab(1)} className="uploadbg">
{getMessage('modals.welcome.buttons.previous')}
</button>
<button
onClick={() => this.changeTab(this.state.addonMetadata.type)}
className="uploadbg"
disabled={nextDescriptionDisabled}
>
{getMessage('modals.welcome.buttons.next')}
</button>
</div>
</>
);
// settings
const nextSettingsDisabled = (this.state.addonData === '') ? true : false;
const nextSettingsDisabled = this.state.addonData === '';
const importSettings = (
<>
<h3>{getMessage('modals.welcome.sections.settings.title')}</h3>
<div className='themesToggleArea' >
<div className='options' style={{ maxWidth: '512px' }}>
<div className={this.state.settingsClasses.current} onClick={() => this.importSettings()}>
<ExportIcon/>
<div className="themesToggleArea">
<div className="options" style={{ maxWidth: '512px' }}>
<div
className={this.state.settingsClasses.current}
onClick={() => this.importSettings()}
>
<ExportIcon />
<span>{getMessage('modals.main.addons.create.settings.current')}</span>
</div>
<div className={this.state.settingsClasses.json} onClick={() => document.getElementById('file-input').click()}>
<ImportIcon/>
<div
className={this.state.settingsClasses.json}
onClick={() => document.getElementById('file-input').click()}
>
<ImportIcon />
<span>{getMessage('modals.main.addons.create.settings.json')}</span>
</div>
</div>
</div>
<FileUpload id='file-input' type='settings' accept='application/json' loadFunction={(e) => this.importSettings(JSON.parse(e.target.result))} />
<br/><br/>
<button onClick={() => this.changeTab(2)} className='uploadbg' style={{ margin: '10px' }}>{getMessage('modals.welcome.buttons.previous')}</button>
<button onClick={() => this.changeTab(3)} className='uploadbg' style={{ margin: '10px' }} disabled={nextSettingsDisabled}>{getMessage('modals.welcome.buttons.next')}</button>
<FileUpload
id="file-input"
type="settings"
accept="application/json"
loadFunction={(e) => this.importSettings(JSON.parse(e.target.result))}
/>
<br />
<br />
<button onClick={() => this.changeTab(2)} className="uploadbg" style={{ margin: '10px' }}>
{getMessage('modals.welcome.buttons.previous')}
</button>
<button
onClick={() => this.changeTab(3)}
className="uploadbg"
style={{ margin: '10px' }}
disabled={nextSettingsDisabled}
>
{getMessage('modals.welcome.buttons.next')}
</button>
</>
);
// quotes
const nextQuotesDisabled = ((this.state.addonMetadata.type === 'quote_api' && this.state.addonData.url !== '' && this.state.addonData.name !== '' && this.state.addonData.author !== '')
|| (this.state.addonMetadata.type === 'quotes' && this.state.addonData.quotes !== '')) ? false : true;
const nextQuotesDisabled = !(
(this.state.addonMetadata.type === 'quote_api' &&
this.state.addonData.url !== '' &&
this.state.addonData.name !== '' &&
this.state.addonData.author !== '') ||
(this.state.addonMetadata.type === 'quotes' && this.state.addonData.quotes !== '')
);
const addQuotes = (
<>
<h3>{getMessage('modals.main.addons.create.quotes.title')}</h3>
<Dropdown label={getMessage('modals.main.settings.sections.time.type')} noSetting onChange={(e) => this.updateQuotePackType(e)}>
<option value='quotes'>{getMessage('modals.main.addons.create.quotes.local.title')}</option>
<option value='quote_api'>{getMessage('modals.main.addons.create.quotes.api.title')}</option>
<Dropdown
label={getMessage('modals.main.settings.sections.time.type')}
noSetting
onChange={(e) => this.updateQuotePackType(e)}
>
<option value="quotes">
{getMessage('modals.main.addons.create.quotes.local.title')}
</option>
<option value="quote_api">
{getMessage('modals.main.addons.create.quotes.api.title')}
</option>
</Dropdown>
{this.state.addonMetadata.type === 'quote_api' ? <>
<TextField label={getMessage('modals.main.addons.create.quotes.api.url')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonData.url} onInput={(e) => this.updateQuotePack(e.target.value, 'url')}/>
<TextField label={getMessage('modals.main.addons.create.quotes.api.name')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonData.name} onInput={(e) => this.updateQuotePack(e.target.value, 'name')}/>
<TextField label={getMessage('modals.main.addons.create.quotes.api.author')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonData.author} onInput={(e) => this.updateQuotePack(e.target.value, 'author')}/>
<br/><br/>
</> : <>
<div className='themesToggleArea'>
<div className='options'>
<div onClick={() => this.importQuotes()} className='toggle lightTheme' style={{ width: '60%', margin: '10px 0 10px 0' }}>
<ExportIcon/>
<span>{getMessage('modals.main.addons.create.settings.current')}</span>
{this.state.addonMetadata.type === 'quote_api' ? (
<>
<TextField
label={getMessage('modals.main.addons.create.quotes.api.url')}
varient="outlined"
InputLabelProps={{ shrink: true }}
value={this.state.addonData.url}
onInput={(e) => this.updateQuotePack(e.target.value, 'url')}
/>
<TextField
label={getMessage('modals.main.addons.create.quotes.api.name')}
varient="outlined"
InputLabelProps={{ shrink: true }}
value={this.state.addonData.name}
onInput={(e) => this.updateQuotePack(e.target.value, 'name')}
/>
<TextField
label={getMessage('modals.main.addons.create.quotes.api.author')}
varient="outlined"
InputLabelProps={{ shrink: true }}
value={this.state.addonData.author}
onInput={(e) => this.updateQuotePack(e.target.value, 'author')}
/>
<br />
<br />
</>
) : (
<>
<div className="themesToggleArea">
<div className="options">
<div
onClick={() => this.importQuotes()}
className="toggle lightTheme"
style={{ width: '60%', margin: '10px 0 10px 0' }}
>
<ExportIcon />
<span>{getMessage('modals.main.addons.create.settings.current')}</span>
</div>
</div>
</div>
</div>
</div>
<br/>
</>}
<button onClick={() => this.changeTab(2)} className='uploadbg'>{getMessage('modals.welcome.buttons.previous')}</button>
<button onClick={() => this.changeTab(3)} className='uploadbg' style={{ margin: '10px' }} disabled={nextQuotesDisabled}>{getMessage('modals.welcome.buttons.next')}</button>
<br />
</>
)}
<button onClick={() => this.changeTab(2)} className="uploadbg">
{getMessage('modals.welcome.buttons.previous')}
</button>
<button
onClick={() => this.changeTab(3)}
className="uploadbg"
style={{ margin: '10px' }}
disabled={nextQuotesDisabled}
>
{getMessage('modals.welcome.buttons.next')}
</button>
</>
);
// photos
const nextPhotosDisabled = (this.state.addonData.photos !== '' && this.state.addonData.photos !== []) ? false : true;
const nextPhotosDisabled = !(
this.state.addonData.photos !== '' && this.state.addonData.photos !== []
);
const addPhotos = (
<>
<h3>{getMessage('modals.main.addons.create.photos.title')}</h3>
<div className='themesToggleArea'>
<div className='options'>
<div onClick={() => this.importPhotos()} className='toggle lightTheme' style={{ width: '60%', margin: '10px 0 10px 0' }}>
<ExportIcon/>
<div className="themesToggleArea">
<div className="options">
<div
onClick={() => this.importPhotos()}
className="toggle lightTheme"
style={{ width: '60%', margin: '10px 0 10px 0' }}
>
<ExportIcon />
<span>{getMessage('modals.main.addons.create.settings.current')}</span>
</div>
</div>
</div>
<br/>
<button onClick={() => this.changeTab(2)} className='uploadbg'>{getMessage('modals.welcome.buttons.previous')}</button>
<button onClick={() => this.changeTab(3)} className='uploadbg' style={{ margin: '10px' }} disabled={nextPhotosDisabled}>{getMessage('modals.welcome.buttons.next')}</button>
<br />
<button onClick={() => this.changeTab(2)} className="uploadbg">
{getMessage('modals.welcome.buttons.previous')}
</button>
<button
onClick={() => this.changeTab(3)}
className="uploadbg"
style={{ margin: '10px' }}
disabled={nextPhotosDisabled}
>
{getMessage('modals.welcome.buttons.next')}
</button>
</>
);
const downloadAddon = (
<>
<div className='themesToggleArea'>
<div className='options'>
<div onClick={() => this.downloadAddon()} className='toggle lightTheme' style={{ width: '60%', margin: '10px 0 10px 0' }}>
<ExportIcon/>
<span>{getMessage('modals.main.addons.create.finish.download')}</span>
<div className="smallBanner">
<div className="content">
<span className="title" style={{ textTransform: 'capitalize' }}>
Next step, Publishing...
</span>
<span className="subtitle">
Visit the Mue Knowledgebase on information on how to publish your newly created addon.
</span>
</div>
<button>
Learn More
<MdOpenInNew />
</button>
</div>
<SettingsItem final={true}>
<div className="themesToggleArea">
<div className="options">
<div
onClick={() => this.downloadAddon()}
className="toggle lightTheme"
style={{ width: '60%', margin: '10px 0 10px 0' }}
>
<ExportIcon />
<span>{getMessage('modals.main.addons.create.finish.download')}</span>
</div>
</div>
</div>
</SettingsItem>
<div className="createButtons">
<button
onClick={() =>
this.changeTab(
this.state.addonMetadata.type === 'quote_api'
? 'quotes'
: this.state.addonMetadata.type,
)
}
className="uploadbg"
disabled={nextDescriptionDisabled}
>
{getMessage('modals.welcome.buttons.previous')}
</button>
</div>
<br/>
<button onClick={() => this.changeTab((this.state.addonMetadata.type === 'quote_api') ? 'quotes' : this.state.addonMetadata.type)} className='uploadbg' style={{ marginRight: '10px' }}>{getMessage('modals.welcome.buttons.previous')}</button>
{/*<button
onClick={() =>
this.changeTab(
this.state.addonMetadata.type === 'quote_api'
? 'quotes'
: this.state.addonMetadata.type,
)
}
className="uploadbg"
style={{ marginRight: '10px' }}
>
{getMessage('modals.welcome.buttons.previous')}
</button>*/}
</>
);
switch (this.state.currentTab) {
case 2: tabContent = writeDescription; break;
case 'settings': tabContent = importSettings; break;
case 'quotes': tabContent = addQuotes; break;
case 'photos': tabContent = addPhotos; break;
case 3: tabContent = downloadAddon; break;
default: tabContent = chooseType;
case 2:
tabContent = writeDescription;
break;
case 'settings':
tabContent = importSettings;
break;
case 'quotes':
tabContent = addQuotes;
break;
case 'photos':
tabContent = addPhotos;
break;
case 3:
tabContent = downloadAddon;
break;
default:
tabContent = chooseType;
}
return (
<>
<h2>{getMessage('modals.main.addons.create.other_title')}</h2>
<div className="flexTopMarketplace">
{this.state.currentTab !== 1 && (
<div className="returnButton">
<Tooltip title="back" key="cheese">
<MdArrowBack
className="backArrow"
onClick={() => this.changeTab(this.state.currentTab - 1)}
/>
</Tooltip>
</div>
)}
<span className="mainTitle">{getMessage('modals.main.addons.create.other_title')}</span>
</div>
{tabContent}
</>
);

View File

@ -1,7 +1,9 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import { MdWifiOff, MdLocalMall } from 'react-icons/md';
import { MdWifiOff, MdLocalMall, MdArrowBack } from 'react-icons/md';
import Tooltip from '../../../../helpers/tooltip/Tooltip';
import Item from '../Item';
import Items from '../Items';
@ -19,11 +21,20 @@ export default class Marketplace extends PureComponent {
button: '',
featured: {},
done: false,
item: {}
item: {},
collection: false,
};
this.buttons = {
uninstall: <button className='removeFromMue' onClick={() => this.manage('uninstall')}>{this.getMessage('modals.main.marketplace.product.buttons.remove')}</button>,
install: <button className='addToMue' onClick={() => this.manage('install')}>{this.getMessage('modals.main.marketplace.product.buttons.addtomue')}</button>
uninstall: (
<button onClick={() => this.manage('uninstall')}>
{this.getMessage('modals.main.marketplace.product.buttons.remove')}
</button>
),
install: (
<button onClick={() => this.manage('install')}>
{this.getMessage('modals.main.marketplace.product.buttons.addtomue')}
</button>
),
};
this.controller = new AbortController();
}
@ -33,7 +44,15 @@ export default class Marketplace extends PureComponent {
let info;
// get item info
try {
info = await (await fetch(`${variables.constants.MARKETPLACE_URL}/item/${this.props.type}/${data}`, { signal: this.controller.signal })).json();
let type = this.props.type;
if (type === 'all') {
type = data.type;
}
info = await (
await fetch(`${variables.constants.MARKETPLACE_URL}/item/${type}/${data.name}`, {
signal: this.controller.signal,
})
).json();
} catch (e) {
if (this.controller.signal.aborted === false) {
return toast(this.getMessage('toasts.error'));
@ -73,22 +92,50 @@ export default class Marketplace extends PureComponent {
icon: info.data.screenshot_url,
data: info.data,
addonInstalled,
addonInstalledVersion
addonInstalledVersion,
api_name: data.name,
},
button: button
button: button,
});
variables.stats.postEvent('marketplace-item', `${this.state.item.display_name} viewed`);
} else if (type === 'collection') {
this.setState({
done: false,
});
const collection = await (
await fetch(`${variables.constants.MARKETPLACE_URL}/collection/${data}`, {
signal: this.controller.signal,
})
).json();
this.setState({
items: collection.data.items,
collection: true,
done: true,
});
} else {
this.setState({
item: {}
item: {},
});
}
}
async getItems() {
const { data } = await (await fetch(variables.constants.MARKETPLACE_URL + '/items/' + this.props.type, { signal: this.controller.signal })).json();
const featured = await (await fetch(variables.constants.MARKETPLACE_URL + '/featured', { signal: this.controller.signal })).json();
const { data } = await (
await fetch(variables.constants.MARKETPLACE_URL + '/items/' + this.props.type, {
signal: this.controller.signal,
})
).json();
const featured = await (
await fetch(variables.constants.MARKETPLACE_URL + '/featured', {
signal: this.controller.signal,
})
).json();
const collections = await (
await fetch(variables.constants.MARKETPLACE_URL + '/collections', {
signal: this.controller.signal,
})
).json();
if (this.controller.signal.aborted === true) {
return;
@ -98,7 +145,8 @@ export default class Marketplace extends PureComponent {
items: data,
oldItems: data,
featured: featured.data,
done: true
collections: collections.data,
done: true,
});
this.sortMarketplace(localStorage.getItem('sortMarketplace'), false);
@ -113,11 +161,14 @@ export default class Marketplace extends PureComponent {
toast(this.getMessage('toasts.' + type + 'ed'));
this.setState({
button: (type === 'install') ? this.buttons.uninstall : this.buttons.install
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'));
variables.stats.postEvent(
'marketplace-item',
`${this.state.item.display_name} ${type === 'install' ? 'installed' : 'uninstalled'}`,
);
variables.stats.postEvent('marketplace', type === 'install' ? 'Install' : 'Uninstall');
}
sortMarketplace(value, sendEvent) {
@ -140,7 +191,7 @@ export default class Marketplace extends PureComponent {
this.setState({
items: items,
sortType: value
sortType: value,
});
if (sendEvent) {
@ -148,6 +199,13 @@ export default class Marketplace extends PureComponent {
}
}
returnToMain() {
this.setState({
items: this.state.oldItems,
collection: false,
});
}
componentDidMount() {
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
return;
@ -164,24 +222,31 @@ export default class Marketplace extends PureComponent {
render() {
const errorMessage = (msg) => {
return (
<div className='emptyitems'>
<div className='emptyMessage'>
{msg}
</div>
<div className="emptyItems">
<div className="emptyMessage">{msg}</div>
</div>
);
};
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
return errorMessage(<>
<MdWifiOff/>
<h1>{this.getMessage('modals.main.marketplace.offline.title')}</h1>
<p className='description'>{this.getMessage('modals.main.marketplace.offline.description')}</p>
</>);
return errorMessage(
<>
<MdWifiOff />
<h1>{this.getMessage('modals.main.marketplace.offline.title')}</h1>
<p className="description">
{this.getMessage('modals.main.marketplace.offline.description')}
</p>
</>,
);
}
if (this.state.done === false) {
return errorMessage(<h1>{this.getMessage('modals.main.loading')}</h1>);
return errorMessage(
<div className="loaderHolder">
<div id="loader"></div>
<span className="subtitle">Just be a sec.</span>
</div>,
);
}
const featured = () => {
@ -191,40 +256,79 @@ export default class Marketplace extends PureComponent {
};
return (
<div className='featured' style={{ backgroundColor: this.state.featured.colour }}>
<div className="featured" style={{ backgroundColor: this.state.featured.colour }}>
<p>{this.state.featured.title}</p>
<h1>{this.state.featured.name}</h1>
<button className='addToMue' onClick={() => openFeatured()}>{this.state.featured.buttonText}</button>
<button className="addToMue" onClick={() => openFeatured()}>
{this.state.featured.buttonText}
</button>
</div>
);
}
};
if (this.state.items.length === 0) {
return (
<>
{featured()}
{errorMessage(<>
<MdLocalMall/>
<h1>{this.getMessage('modals.main.addons.empty.title')}</h1>
<p className='description'>{this.getMessage('modals.main.marketplace.no_items')}</p>
</>)}
{errorMessage(
<>
<MdLocalMall />
<h1>{this.getMessage('modals.main.addons.empty.title')}</h1>
<p className="description">{this.getMessage('modals.main.marketplace.no_items')}</p>
</>,
)}
</>
);
}
if (this.state.item.display_name) {
return <Item data={this.state.item} button={this.state.button} toggleFunction={() => this.toggle()} addonInstalled={this.state.item.addonInstalled} addonInstalledVersion={this.state.item.addonInstalledVersion}/>;
return (
<Item
data={this.state.item}
button={this.state.button}
toggleFunction={() => this.toggle()}
addonInstalled={this.state.item.addonInstalled}
addonInstalledVersion={this.state.item.addonInstalledVersion}
icon={this.state.item.screenshot_url}
/>
);
}
return (
<>
{featured()}
<br/>
<Dropdown label={this.getMessage('modals.main.addons.sort.title')} name='sortMarketplace' onChange={(value) => this.sortMarketplace(value)}>
<option value='a-z'>{this.getMessage('modals.main.addons.sort.a_z')}</option>
<option value='z-a'>{this.getMessage('modals.main.addons.sort.z_a')}</option>
</Dropdown>
<Items items={this.state.items} toggleFunction={(input) => this.toggle('item', input)} />
{this.state.collection === true ? (
<>
<div className="flexTopMarketplace">
<div className="returnButton">
<Tooltip title="back" key="cheese">
<MdArrowBack className="backArrow" onClick={() => this.returnToMain()} />
</Tooltip>
</div>
<span className="mainTitle">Marketplace</span>
</div>
</>
) : (
<span className="mainTitle">Marketplace</span>
)}
{/*{featured()}*/}
<div className="filter">
<Dropdown
label={this.getMessage('modals.main.addons.sort.title')}
name="sortMarketplace"
onChange={(value) => this.sortMarketplace(value)}
>
<option value="a-z">{this.getMessage('modals.main.addons.sort.a_z')}</option>
<option value="z-a">{this.getMessage('modals.main.addons.sort.z_a')}</option>
</Dropdown>
</div>
<Items
type={this.props.type}
items={this.state.items}
collections={this.state.collections}
onCollection={this.state.collection}
toggleFunction={(input) => this.toggle('item', input)}
collectionFunction={(input) => this.toggle('collection', input)}
/>
</>
);
}

View File

@ -17,8 +17,8 @@ export default class Sideload extends PureComponent {
super(props);
this.state = {
showFailed: false,
failedReason: ''
}
failedReason: '',
};
}
installAddon(input) {
@ -31,16 +31,27 @@ export default class Sideload extends PureComponent {
failedReason = this.getMessage('modals.main.addons.sideload.errors.no_type');
} else if (!input.version) {
failedReason = this.getMessage('modals.main.addons.sideload.errors.no_version');
} else if (input.type === 'photos' && (!input.photos || !input.photos.length || !input.photos[0].url || !input.photos[0].url.default || !input.photos[0].photographer || !input.photos[0].location)) {
} else if (
input.type === 'photos' &&
(!input.photos ||
!input.photos.length ||
!input.photos[0].url ||
!input.photos[0].url.default ||
!input.photos[0].photographer ||
!input.photos[0].location)
) {
failedReason = this.getMessage('modals.main.addons.sideload.errors.invalid_photos');
} else if (input.type === 'quotes' && (!input.quotes || !input.quotes.length || !input.quotes[0].quote || !input.quotes[0].author)) {
} else if (
input.type === 'quotes' &&
(!input.quotes || !input.quotes.length || !input.quotes[0].quote || !input.quotes[0].author)
) {
failedReason = this.getMessage('modals.main.addons.sideload.errors.invalid_quotes');
}
if (failedReason !== '') {
return this.setState({
return this.setState({
failedReason,
showFailed: true
showFailed: true,
});
}
@ -48,18 +59,39 @@ export default class Sideload extends PureComponent {
toast(this.getMessage('toasts.installed'));
variables.stats.postEvent('marketplace', 'Sideload');
}
render() {
return (
<div className='emptyitems'>
<div className='emptyMessage'>
<FileUpload id='file-input' type='settings' accept='application/json' loadFunction={(e) => this.installAddon(JSON.parse(e.target.result))} />
<div className="emptyItems">
<div className="emptyMessage">
<FileUpload
id="file-input"
type="settings"
accept="application/json"
loadFunction={(e) => this.installAddon(JSON.parse(e.target.result))}
/>
<MdIntegrationInstructions />
<h1>{this.getMessage('modals.main.addons.sideload.title')}</h1>
<button className='addToMue sideload' onClick={() => document.getElementById('file-input').click()}>{this.getMessage('modals.main.settings.sections.background.source.upload')}</button>
<span className="title">{this.getMessage('modals.main.addons.sideload.title')}</span>
<span className="subtitle">idk something about it</span>
<button
className="addToMue sideload"
onClick={() => document.getElementById('file-input').click()}
>
{this.getMessage('modals.main.settings.sections.background.source.upload')}
</button>
</div>
<Modal closeTimeoutMS={100} onRequestClose={() => this.setState({ showFailed: false })} isOpen={this.state.showFailed} className='Modal resetmodal mainModal sideloadModal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}>
<SideloadFailedModal modalClose={() => this.setState({ showFailed: false })} reason={this.state.failedReason}/>
<Modal
closeTimeoutMS={100}
onRequestClose={() => this.setState({ showFailed: false })}
isOpen={this.state.showFailed}
className="Modal resetmodal mainModal sideloadModal"
overlayClassName="Overlay resetoverlay"
ariaHideApp={false}
>
<SideloadFailedModal
modalClose={() => this.setState({ showFailed: false })}
reason={this.state.failedReason}
/>
</Modal>
</div>
);

View File

@ -1,4 +1,4 @@
@import '../../../../scss/variables';
@import 'scss/variables';
@import 'modules/sidebar';
@import 'modules/navbar';
@ -11,42 +11,6 @@
@import 'marketplace/main';
.Modal {
color: var(--modal-text);
background-color: var(--background);
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
border: none;
opacity: 1;
z-index: -2;
transition-timing-function: ease-in;
border-radius: map-get($modal, 'border-radius');
user-select: none;
scrollbar-width: thin;
scrollbar-color: #34495e #bdc3c7;
position: relative;
&:focus {
outline: 0;
}
}
.closeModal {
position: absolute;
top: 1rem;
right: 2rem;
font-size: 4em;
cursor: pointer;
&:hover {
color: grey;
}
}
.ReactModal__Html--open,
.ReactModal__Body--open {
overflow: hidden;
}
.Overlay {
position: fixed;
z-index: 100;
@ -56,18 +20,53 @@
bottom: 0;
width: 100vw;
height: 100vh;
display: flex;
align-items: baseline;
justify-content: center;
display: grid;
place-items: center;
}
.ReactModal__Content {
box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.25);
overflow-y: auto;
position: relative;
.Modal {
@include themed() {
color: t($color);
}
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
opacity: 1;
z-index: -2;
transition-timing-function: ease-in;
border-radius: map-get($modal, 'border-radius');
user-select: none;
overflow-y: auto;
transform: scale(0);
transition: all 300ms cubic-bezier(0.47, 1.64, 0.41, 0.8);
transition: all 0.3s cubic-bezier(0.47, 1.64, 0.41, 0.8);
&:focus {
outline: 0;
}
}
.closePositioning {
position: absolute;
top: 3rem;
right: 3rem;
}
.closeModal {
display: grid;
place-items: center;
padding: 0.5em;
border-radius: 12px;
cursor: pointer;
svg {
font-size: 2em;
}
&:hover {
background: rgba(121, 121, 121, 0.226);
}
}
.ReactModal__Html--open,
.ReactModal__Body--open {
overflow: hidden;
}
/* modal transition */
@ -81,33 +80,9 @@
transform: scale(0);
}
@media only screen and (max-height: 700px) {
.ReactModal__Content {
min-height: 500px;
max-height: calc(100vh - 30vh);
}
}
/* main modal */
.mainModal {
padding: 25px;
}
#modal {
position: absolute;
margin: auto;
top: 0;
right: 0;
bottom: 0;
left: 0;
height: 80%;
width: 60%;
}
@media (max-width: 1700px) {
#modal {
width: 80% !important;
}
height: 80vh;
width: clamp(60vw, 1200px, 90vw);
}
/* fixes for font size on extension */
@ -130,12 +105,80 @@ h5 {
font-size: 0.8rem;
}
.tab-content {
hr {
height: 5px;
background: rgba(196, 196, 196, 0.74);
outline: none;
border: none;
margin: 50px 0 30px 0;
.loaderHolder {
display: flex;
gap: 15px;
flex-flow: column;
align-items: center;
}
#loader {
display: inline-block;
width: 50px;
height: 50px;
@include themed() {
border: 3px solid t($modal-sidebar);
border-radius: 50%;
border-top-color: t($modal-sidebarActive);
}
}
animation: spin 1s ease-in-out infinite;
-webkit-animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to {
-webkit-transform: rotate(360deg);
}
}
@-webkit-keyframes spin {
to {
-webkit-transform: rotate(360deg);
}
}
.headerExtras {
display: flex;
flex-flow: column;
gap: -1px;
.link {
display: flex;
flex-flow: row;
gap: 15px;
align-items: center;
&:hover {
opacity: .8;
cursor: pointer;
}
}
}
.languageSettings {
margin-bottom: 15px;
.MuiFormGroup-root {
gap: 5px;
}
.MuiFormControl-root {
width: 100% !important;
gap: 15px;
.MuiFormControlLabel-root {
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
padding: 5px 5px 5px 20px;
@include themed() {
background: t($modal-sidebar);
border-radius: t($borderRadius);
&:hover {
background: t($modal-sidebarActive);
}
}
}
}
}
.sliderTitle {
width: 100%;
display: flex;
justify-content: space-between;
padding: 15px 0 15px 0;
}

View File

@ -2,58 +2,157 @@
@import 'modules/buttons';
@import 'modules/featured';
@import 'modules/lightbox';
@import '../../../../../scss/variables';
.items {
display: inline-grid;
grid-template-columns: repeat(4, 1fr);
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 15px;
margin-top: 15px;
.item {
position: relative;
border-radius: 12px;
height: 80px;
height: 70px;
width: 260px;
background: var(--sidebar);
padding: 10px;
cursor: pointer;
margin-right: 20px;
margin-top: 20px;
img {
height: 80px;
width: 80px;
border-radius: 12px 0 0 12px;
background: white;
}
h4 {
font-size: 20px;
line-height: 20px;
font-weight: 600;
}
img,
.details {
display: inline;
}
.details {
position: absolute;
left: 85px;
top: -15px;
img {
margin-left: 10px;
height: 15px;
display: flex;
align-items: center;
gap: 15px;
transition: 0.5s;
@include themed() {
background: t($modal-sidebar);
box-shadow: 0 0 0 1px t($modal-sidebarActive);
&:hover {
background: t($modal-sidebarActive);
}
}
p {
margin-top: 5px;
line-height: 20px;
.tags {
margin-top: 7px;
}
img {
height: 80% !important;
width: auto !important;
border-radius: 12px;
@include themed() {
background: t($modal-sidebar);
}
padding: 5px;
}
.card-details {
display: flex;
flex-flow: column;
.card-title {
font-size: 18px;
}
.card-subtitle {
font-size: 12px;
@include themed() {
color: t($subColor);
}
}
}
}
}
.itemPage {
display: flex;
flex-flow: row;
justify-content: space-between;
.itemShowcase {
display: flex;
flex-flow: column;
gap: 15px;
width: 50%;
.description {
max-lines: 3;
font-size: 16px;
}
img {
width: 100%;
height: auto;
}
}
.itemInfo {
display: flex;
flex-flow: column;
gap: 15px;
width: 170px;
img {
width: 100%;
height: auto;
border-radius: 12px;
@include themed() {
box-shadow: 0 0 0 3px t($modal-sidebarActive);
}
}
.divider {
text-transform: uppercase;
@include themed() {
color: t($subColor);
}
}
.iconButtons {
display: flex;
flex-flow: row;
justify-content: space-between;
svg {
@include basicIconButton(11px, 1.3rem, modal);
}
}
}
}
.tags {
display: flex;
flex-flow: row;
flex-wrap: wrap;
gap: 15px;
align-items: center;
}
.tag {
padding: 2px 10px 2px 10px;
border-radius: 12px;
font-size: 12px;
display: grid;
place-items: center;
@include themed() {
background: t($modal-sidebar);
box-shadow: 0 0 0 3px t($modal-sidebarActive);
span {
&:before {
content: '#';
color: t($subColor);
margin-right: 5px;
}
}
&:hover {
background: var(--tab-active);
background: t($modal-sidebarActive);
}
}
}
.moreTag {
padding: 2px 10px 2px 10px;
border-radius: 12px;
font-size: 12px;
display: grid;
place-items: center;
@include themed() {
background: t($modal-sidebar);
box-shadow: 0 0 0 3px t($modal-sidebarActive);
span {
&:before {
content: '+';
color: t($subColor);
margin-right: 5px;
}
}
&:hover {
background: t($modal-sidebarActive);
}
}
}
@ -76,29 +175,27 @@
}
}
.emptyitems {
width: 25vw;
display: flex;
justify-content: center;
margin-top: 90px;
.emptyItems {
width: 100%;
height: 100%;
display: grid;
place-items: center;
}
.emptyMessage {
text-align: center;
background: var(--sidebar);
padding: 25px;
border-radius: 30px;
box-shadow: 0 0 10px rgb(0 0 0 / 30%);
position: absolute;
width: 300px;
h1 {
font-size: 2rem;
}
svg {
font-size: 50px;
margin-bottom: -20px;
display: flex;
flex-flow: column;
align-items: center;
gap: 15px;
@include themed() {
padding: 45px;
border-radius: t($borderRadius);
background: t($modal-sidebar);
box-shadow: 0 0 0 4px t($modal-sidebarActive);
svg {
font-size: 50px;
color: t($subColor);
}
}
}
@ -106,9 +203,9 @@ p.author {
margin-top: -5px;
}
#item>img,
.updateimage,
.updatechangelog>p>img {
#item > img,
.updateImage,
.updateChangelog > p > img {
border-radius: 12px;
height: 200px;
width: auto;
@ -122,8 +219,142 @@ p.author {
overflow-x: hidden;
}
@media (max-height: 1080px) {
@media (max-height: 1080px) {
.dropdownsortAddons {
margin-top: 40px !important;
}
}
.returnButton {
display: grid;
place-items: center;
width: 48px;
height: 48px;
border-radius: 12px;
cursor: pointer;
margin-right: 25px;
svg {
font-size: 2em;
}
&:hover {
background: rgba(121, 121, 121, 0.226);
}
}
.flexTopMarketplace {
display: flex;
}
.itemWarning {
display: flex;
flex-direction: column;
align-items: center;
@include themed() {
background: t($modal-sidebar);
box-shadow: 0 0 0 4px t($modal-sidebarActive);
border-radius: t($borderRadius);
padding: 15px;
}
.topRow {
display: flex;
flex-flow: column;
align-items: center;
}
.subtitle {
text-align: justify;
}
}
.truncate {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.filter {
display: flex;
flex-flow: row;
padding: 15px;
gap: 15px;
justify-content: space-between;
@include themed() {
background: t($modal-sidebar);
border-radius: t($borderRadius);
box-shadow: 0 0 0 4px t($modal-sidebarActive);
}
.tags {
max-width: 50%;
}
}
.collection {
display: flex;
justify-content: space-between;
padding: 15px;
margin-top: 15px;
background-image: linear-gradient(to left, transparent, #000),
url('https://www.gonefullgeek.com/wp-content/uploads/2017/09/RDR2-Banner.jpg');
align-items: center;
@include themed() {
box-shadow: 0 0 0 4px t($modal-sidebarActive);
border-radius: t($borderRadius);
}
.content {
display: flex;
flex-flow: column;
gap: 15px;
max-width: 250px;
.title {
color: #fff !important;
}
.subtitle {
color: #ccc !important;
}
}
.items {
justify-content: center;
}
}
.smallBanner {
button {
padding: 0 15px 0 15px;
}
display: flex;
justify-content: space-between;
padding: 15px;
margin-top: 15px;
align-items: center;
@include themed() {
box-shadow: 0 0 0 4px t($modal-sidebarActive);
border-radius: t($borderRadius);
background: t($modal-sidebar);
}
.content {
display: flex;
flex-flow: column;
gap: 15px;
max-width: 250px;
}
}
.inCollection {
background-image: linear-gradient(to left, transparent, #000),
url('https://www.gonefullgeek.com/wp-content/uploads/2017/09/RDR2-Banner.jpg');
display: flex;
flex-flow: column;
gap: 15px;
padding: 15px;
@include themed() {
box-shadow: 0 0 0 4px t($modal-sidebarActive);
border-radius: t($borderRadius);
}
}
.starWars {
background-image: linear-gradient(to left, transparent, #000),
url(https://www.broadway.org.uk/sites/default/files/styles/banner_crop/public/2019-10/star-wars-the-rise-of-skywalker-banner-min.jpg?h=639d4ef1&itok=z4KZ-3Tt);
background-size: cover !important;
background-repeat: no-repeat !important;
background-position: center !important;
}

View File

@ -1,4 +1,4 @@
%storebutton {
%storeButton {
cursor: pointer;
font-size: 18px;
display: block;
@ -14,7 +14,7 @@
}
}
.dark %storebutton {
.dark %storeButton {
border: 2px solid map-get($theme-colours, 'main');
color: map-get($theme-colours, 'main');
@ -24,42 +24,11 @@
}
}
.removeFromMue {
@extend %storebutton;
border: 2px solid #ff4757;
color: #ff4757;
float: right;
margin-top: -10px;
&:hover {
background: #ff4757;
color: map-get($theme-colours, 'main');
}
}
.addToMue {
@extend %storebutton;
float: right;
margin-top: -10px;
}
.sideload {
display: inline;
margin-top: 0px;
margin-top: 0;
float: none !important;
}
button.round {
margin-left: 5px;
width: 30px;
height: 30px;
border-radius: 50%;
text-align: center;
line-height: 3px;
vertical-align: middle;
padding: 10px;
width: 200px;
}
.updateCheck {

View File

@ -1,10 +1,9 @@
.featured {
margin-top: 40px;
border-radius: 15px;
padding: 50px;
color: #fff;
box-shadow: 0 0 10px rgb(0 0 0 / 30%);
width: 85%;
width: calc(100% - 6rem);
margin-top: 13px;
button {
float: left;

View File

@ -1,90 +1,84 @@
#item {
h1 {
font-size: 40px;
line-height: 20px;
}
img {
float: left;
}
a {
color: var(--modal-link);
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
}
.side {
float: right;
margin-left: 20px;
}
#item>h1,
#item>svg {
display: inline;
}
p.description {
margin-top: 0px;
margin-top: 0;
max-width: 800px;
}
.backArrow {
cursor: pointer;
width: 2rem !important;
height: 2rem !important;
&:hover {
color: grey;
}
}
.informationContainer {
margin-top: 150px;
position: absolute;
}
.productInformation {
padding: 10px;
background: var(--sidebar);
width: 350px;
border-radius: 12px;
min-height: 180px;
h4 {
cursor: initial !important;
.moreInfo {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
width: calc(100% - 30px);
gap: 15px;
.items {
margin-top: 0 !important;
}
li {
margin-left: -4px;
list-style: none;
font-size: 16px;
cursor: initial !important;
&.header {
.item {
flex: 1 0 40% !important;
}
@include themed() {
background: t($modal-sidebar);
box-shadow: 0 0 0 4px t($modal-sidebarActive);
border-radius: t($borderRadius);
padding: 15px;
.infoItem {
display: flex;
flex-flow: row;
align-items: center;
gap: 15px;
flex: 1 0 44%;
svg {
font-size: 25px;
color: t($subColor);
}
.text {
display: flex;
flex-flow: column;
}
}
.header {
text-transform: uppercase;
color: #787878;
margin-left: -5px;
color: t($subColor);
}
span {
color: t($color);
}
}
}
@media only screen and (max-width: 1200px) {
.side {
margin-top: 222px;
float: none !important;
}
.overview {
margin-top: -160px !important;
.itemTitle {
font-size: 38px;
font-weight: 600;
@include themed() {
color: t($color);
}
}
.overview {
font-size: 30px !important;
margin-top: 33px;
.titleTop {
display: flex;
flex-flow: column;
gap: 3px;
}
.showMore {
display: flex;
align-items: center;
gap: 5px;
transition: 0.5s;
cursor: pointer;
@include themed() {
&:hover {
color: t($subColor);
}
}
}

View File

@ -1,4 +1,4 @@
.lightboxmodal {
.lightBoxModal {
margin: auto;
max-width: 60%;
background: none !important;

View File

@ -1,17 +1,12 @@
.navbar-item {
font-size: 22px;
font-weight: 500;
display: flex;
flex-direction: column;
align-items: center;
color: var(--photo-info);
flex-flow: row !important;
padding: 0 15px 0 15px;
&:hover {
svg {
background: var(--tab-active);
}
color: var(--modal-text)
color: var(--modal-text);
}
span,
@ -21,9 +16,6 @@
svg {
font-size: 1.2em !important;
width: 60px;
padding: 5px;
border-radius: 20px;
color: var(--photo-info);
}
}
@ -35,38 +27,21 @@
}
}
.modalNavbar {
position: absolute;
left: 20rem;
top: 1rem;
justify-content: center;
#modal {
display: flex;
svg {
margin-right: 0.5rem;
padding: 3px;
vertical-align: middle;
}
align-items: flex-start;
justify-content: flex-start;
}
@media only screen and (max-width: 1200px) {
.modalNavbar {
left: 6rem;
}
}
@media only screen and (max-width: 800px) {
li.navbar-item {
span {
display: none;
}
}
.modalNavbar {
display: flex;
flex-flow: row;
gap: 25px;
margin-bottom: 1rem;
}
.navbar-item-active {
color: var(--modal-text);
svg {
background: var(--sidebar);
@include themed() {
background: t($modal-sidebarActive) !important;
}
}

View File

@ -1,12 +1,17 @@
@import '../.../../../../../././../scss/variables';
::-webkit-scrollbar {
width: 6px;
height: 6px;
border-top-right-radius: map-get($modal, 'border-radius');
border-bottom-right-radius: map-get($modal, 'border-radius');
border-radius: 12px;
@include themed() {
background: t($modal-sidebar);
}
}
::-webkit-scrollbar-thumb {
background: #636e72;
border-top-right-radius: map-get($modal, 'border-radius');
border-bottom-right-radius: map-get($modal, 'border-radius');
@include themed() {
background: t($modal-sidebarActive);
}
border-radius: 12px;
}

View File

@ -1,87 +1,66 @@
ul.sidebar {
position: absolute;
top: 0;
left: 0;
margin: 0;
padding-left: 0;
background: var(--sidebar);
border-radius: 12px 0 0 12px;
text-align: left;
font-size: 24px;
min-height: 110vh;
@import '../.../../../../../././../scss/variables';
h1 {
text-align: center;
font-size: 1.8em;
}
.sidebar {
@include themed() {
top: 0;
left: 0;
position: sticky;
margin: 0;
padding: 0;
background: t($modal-sidebar);
border-radius: 12px 0 0 12px;
overflow-y: auto;
height: 80vh;
min-width: 250px;
overflow-x: hidden;
svg {
vertical-align: middle;
padding: 5px;
}
hr {
height: 3px;
background: rgba(196, 196, 196, 0.74);
width: 75%;
outline: none;
border: none;
}
}
@media (max-height: 999px) and (min-height: 920px) {
ul.sidebar {
min-height: 160vh;
}
}
@media (max-height: 919px) and (min-height: 700px) {
ul.sidebar {
min-height: 200vh;
}
}
@media (max-height: 699px) and (min-height: 400px) {
ul.sidebar {
min-height: 260vh;
}
}
@media only screen and (min-width: 1200px) {
ul.sidebar {
width: 310px;
align-items: center;
}
}
li {
list-style: none;
font-size: 24px;
padding: 5px 30px 5px 30px;
cursor: pointer;
margin-top: 2px;
}
.tab-list-active {
background: var(--tab-active);
}
@media only screen and (max-width: 1200px) {
li.tab-list-item {
span {
display: none;
svg {
margin-left: 20px;
margin-right: 20px;
color: t($subColor);
font-size: 17px;
}
}
ul.sidebar {
h1 {
display: none;
hr {
height: 1px;
background: #ccc;
margin: 0 1.75rem 0 1.75rem;
border: none;
}
button {
color: t($color);
font-size: 18px;
list-style: none;
cursor: pointer;
border-radius: 12px;
display: flex;
align-items: center;
margin: 0.2rem;
padding: 0.5rem;
transition: 0.5s;
outline: none;
border: none;
background: none;
min-width: calc(100% - 1.2em);
&:hover {
background: t($modal-sidebarActive);
}
&:active {
background: t($modal-sidebarActive);
box-shadow: 0 0 0 0.5px t($color);
}
&:focus {
background: t($modal-sidebarActive);
box-shadow: 0 0 0 0.5px t($color);
}
}
.tab-list-active {
background: t($modal-sidebarActive);
}
}
}
.tab-list-item {
&:hover {
background: var(--tab-active);
}
}

View File

@ -1,43 +1,95 @@
@import '../../../../../scss/variables';
.tab-content {
position: absolute;
button {
@include modal-button(standard);
}
@include themed() {
padding: 1rem 3rem 3rem 3rem;
display: flex;
flex-direction: column;
width: 100%;
background: t($modal-background);
@extend %tabText;
hr {
width: 100%;
background: rgba(196, 196, 196, 0.74);
outline: none;
}
.settingsRow {
display: flex;
align-items: center;
min-height: 100px;
justify-content: space-between;
/*border-top: 1px solid #ccc;*/
border-bottom: 1px solid #ccc;
padding-top: 1rem;
padding-bottom: 1rem;
h3 {
font-size: 1.5rem;
margin-bottom: 0;
&.settingsNoBorder {
border-bottom: none;
}
.content {
display: flex;
flex-flow: column;
max-width: 50%;
}
.action {
display: flex;
flex-flow: column;
align-items: flex-end;
width: 300px;
button {
margin-top: 10px;
width: 283px;
}
}
}
}
}
@media only screen and (min-width: 2300px) {
.tab-content {
left: 350px;
top: 7%;
.activityButtons {
flex-wrap: wrap !important;
justify-content: space-between !important;
align-items: flex-end !important;
align-content: space-between !important;
flex-wrap: wrap !important;
flex-direction: row !important;
button:not(:first-child) {
width: 40% !important;
height: 99px !important;
flex-flow: column-reverse !important;
}
button {
@include modal-button(standard);
}
}
@media only screen and (max-width: 1920px) {
.tab-content {
left: 120px;
top: 60px;
}
}
@media only screen and (min-width: 1920px) {
.tab-content {
left: 350px;
top: 9%;
}
}
@media only screen and (max-width: 1400px),
(min-width: 1400px) {
.tab-content {
left: 350px;
}
}
@media only screen and (max-width: 1200px) {
.tab-content {
left: 125px;
top: 90px;
table {
@include themed() {
margin-top: 20px;
tr:first-child {
background: t($modal-sidebarActive);
th {
padding: 1rem;
}
}
tr {
th:last-child {
display: grid;
place-items: center;
}
}
tr:not(:first-child) {
background: t($modal-sidebar);
textarea {
width: 90%;
margin: 10px;
color: t($color);
::placeholder {
color: t($color);
}
}
}
}
}

View File

@ -1,5 +1,5 @@
@import '../../../../../scss/modules/buttons';
@import 'scss/modules/buttons';
/*
.refresh {
@extend %settingsButton;
@ -84,12 +84,8 @@
width: 10px !important;
}
.data-buttons-row {
width: 350px;
display: flex;
flex-direction: row;
button {
/* button {
background: var(--sidebar);
text-align: center;
border-radius: 20px;
@ -102,20 +98,16 @@
flex-direction: column-reverse;
align-items: center;
color: var(--modal-text);
svg {
font-size: 2em;
}
transition: 0.5s;
&:hover {
background: var(--tab-active);
cursor: pointer;
}
}
}
.customvideoicon {
position: absolute;
margin-bottom: 45px;
font-size: 3em !important;
}
*/

View File

@ -1,4 +1,5 @@
@import 'modules/resetmodal';
@import 'scss/variables';
@import 'modules/material-ui';
@import 'modules/reminder';
@ -8,7 +9,7 @@
input {
/* colour picker */
&[type=color] {
&[type='color'] {
border-radius: 100%;
height: 30px;
width: 30px;
@ -29,7 +30,7 @@ input {
}
/* firefox fixes for colour picker (using "," didn't work) */
&[type=color]::-moz-color-swatch {
&[type='color']::-moz-color-swatch {
border-radius: 100%;
height: 30px;
width: 30px;
@ -50,11 +51,13 @@ input {
}
/* date picker */
&[type=date] {
width: 280px;
color: var(--modal-text);
background: var(--background);
border: solid var(--modal-text) 1px;
&[type='date'] {
width: 260px;
@include themed() {
background: t($modal-sidebar);
border: 3px solid t($modal-sidebarActive);
color: t($color);
}
padding: 15px 20px;
border-radius: 4px;
display: flex !important;
@ -84,3 +87,72 @@ h4 {
padding-right: 10px;
}
}
.dropzone {
@include themed() {
background: t($modal-sidebar);
border: 3px solid t($modal-sidebarActive);
color: t($color);
border-radius: 12px;
font-size: 1rem;
display: flex;
align-items: center;
flex-flow: column;
justify-content: center;
gap: 10px;
transition: 0.5s;
padding: 45px 25px 45px 25px;
svg {
font-size: 48px;
}
.title {
font-size: 18px;
}
&:hover {
border-style: dashed;
}
button {
width: 80% !important;
}
}
}
.statsGrid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
column-gap: 100px;
row-gap: 100px;
padding: 25px;
div {
padding: 25px;
@include themed() {
padding: 45px;
border-radius: t($borderRadius);
background: t($modal-sidebar);
box-shadow: 0 0 0 4px t($modal-sidebarActive);
svg {
font-size: 50px;
color: t($subColor);
}
}
}
div:nth-child(1) {
grid-area: 1 / 1 / 3 / 3;
}
div:nth-child(2) {
grid-area: 3 / 1 / 4 / 3;
}
div:nth-child(3) {
grid-area: 1 / 3 / 4 / 4;
display: flex;
flex-flow: column;
gap: 10px;
.subtitle {
text-transform: capitalize;
}
span {
font-size: 20px;
}
}
}

View File

@ -1,4 +1,7 @@
/* these are overrides for the material ui default styles */
@import '../../../../../../scss/variables';
.MuiCheckbox-colorPrimary.Mui-checked,
.MuiSwitch-colorPrimary.Mui-checked,
.MuIconButton-colorPrimary.Mui-checked,
@ -9,19 +12,23 @@
.aboutLink,
.MuiSlider-colorPrimary,
legend {
color: var(--modal-text) !important;
@include themed() {
color: t($color) !important;
}
}
.MuiFormControlLabel-labelPlacementStart {
margin-left: 0px !important;
margin-left: 0 !important;
}
.MuiSwitch-colorPrimary.Mui-checked+.MuiSwitch-track {
.MuiSwitch-colorPrimary.Mui-checked + .MuiSwitch-track {
background: darkgray !important;
}
.MuiIconButton-label>svg.MuiSvgIcon-root {
color: var(--modal-text) !important;
.MuiIconButton-label > svg.MuiSvgIcon-root {
@include themed() {
color: t($color) !important;
}
}
.MuiTouchRipple-root {
@ -33,7 +40,9 @@ legend {
}
.checkbox svg {
fill: var(--modal-text) !important;
@include themed() {
fill: t($color) !important;
}
}
.radio-title {
@ -51,11 +60,15 @@ legend {
}
.MuiOutlinedInput-notchedOutline {
border-color: var(--modal-text) !important;
@include themed() {
border-color: t($color) !important;
}
}
.MuiFormLabel-root-MuiInputLabel-root {
color: var(--modal-text) !important;
@include themed() {
color: t($color) !important;
}
}
.MuiInputLabel-root,
@ -66,23 +79,28 @@ legend {
.Mui-focused,
legend,
.MuiOutlinedInput-input {
color: var(--modal-text) !important;
@include themed() {
color: t($color) !important;
}
}
.MuiMenu-list {
background-color: var(--background) !important;
color: var(--modal-text) !important;
@include themed() {
background-color: t($modal-background);
color: t($color);
}
}
.Mui-selected {
background-color: var(--tab-active) !important;
@include themed() {
background-color: t($modal-sidebarActive) !important;
}
}
.MuiTextField-root,
.MuiFormControl-root,
.MuiSlider-root {
width: 300px !important;
display: flex !important;
}
.Mui-disabled {
@ -95,9 +113,31 @@ legend,
}
.MuiPaper-root {
background-color: var(--background) !important;
@include themed() {
background-color: t($modal-background) !important;
}
}
.MuiSlider-valueLabel {
background-color: var(--tab-active) !important;
@include themed() {
background-color: t($modal-sidebarActive) !important;
}
}
.MuiFormControlLabel-labelPlacementStart {
justify-content: flex-end;
}
.settingsRow {
.MuiFormControlLabel-root {
flex-direction: row-reverse;
margin-right: 0;
display: flex;
justify-content: space-between;
width: 100%;
margin-left: 0;
}
.MuiFormControlLabel-root {
width: 100%;
}
}

View File

@ -1,14 +1,20 @@
@import '../../../../../../scss/variables';
.reminder-info {
position: absolute;
bottom: 20px;
right: 20px;
bottom: 0;
padding: 15px;
color: var(--modal-text);
background: var(--sidebar);
max-width: 300px;
border-radius: 0.7em;
h1 {
font-size: 1rem;
flex-flow: column;
gap: 10px;
margin: 5px;
@extend %tabText;
@include themed() {
color: t($color);
background: t($modal-sidebar);
box-shadow: 0 0 0 4px t($modal-sidebarActive);
border-radius: t($borderRadius);
}
button {
@include modal-button(standard);
}
}

View File

@ -1,27 +1,29 @@
.resetmodal {
min-height: 300px !important;
max-width: 300px !important;
margin: auto;
font-size: 1rem;
@import '../../../../../../scss/variables';
h4 {
cursor: initial;
font-size: 1.1rem;
}
}
.resetfooter {
position: absolute;
bottom: 20px;
width: 300px;
justify-content: center;
.resetModal {
@extend %tabText;
display: flex;
button.reset {
margin-right: 43px;
flex-flow: column;
padding: 2rem;
justify-content: center;
gap: 15px;
@include themed() {
background: t($modal-background);
}
button {
display: flex;
align-items: center;
gap: 15px;
}
button:nth-child(1) {
@include basicIconButton(11px, 1.3rem, modal-text);
}
button:nth-child(2) {
@include basicIconButton(11px, 1.3rem, modal);
}
}
.resetoverlay {
background-color: rgba(0, 0, 0, 0.5);
}
.resetFooter {
display: flex;
flex-flow: row;
justify-content: space-around;
}

View File

@ -1,16 +1,3 @@
.aboutIcon {
color: var(--modal-text) !important;
padding-right: 10px;
&:hover {
opacity: 0.8;
}
svg {
font-size: 1.5em;
}
}
.aboutLink {
&:hover {
opacity: 0.8;
@ -20,17 +7,28 @@
.aboutLogo {
height: 100px;
width: auto;
margin-left: -15px;
margin: calc(1rem - 15px) 0 1rem 0;
align-self: center;
}
.abouticon {
width: 96px;
height: auto;
border-radius: 50%;
padding-right: 5px;
.aboutContact {
flex-flow: row;
display: flex;
gap: 15px;
a {
@include basicIconButton(11px, 1.5rem, modal);
}
}
.contacth3 {
font-size: 1.5rem;
margin-bottom: 0.8em !important;
.contributorImages {
display: flex;
flex-wrap: wrap;
gap: 15px;
img {
width: 75px;
height: auto;
@include themed() {
border-radius: t($borderRadius);
}
}
}

View File

@ -1,4 +1,4 @@
.updatechangelog {
.updateChangelog {
max-width: 75%;
li {
@ -26,7 +26,7 @@
}
h5 {
line-height: 0px !important;
line-height: 0 !important;
}
img {

View File

@ -1,54 +1,57 @@
.sortableitem {
background: var(--sidebar) !important;
padding: 10px 80px;
padding-left: 10px;
border-radius: 15px;
margin-bottom: 10px;
font-size: 1.325rem;
color: var(--modal-text) !important;
cursor: move;
width: 150px;
z-index: 999 !important;
@import '../../../../../../../scss/variables';
svg {
font-size: 1.3rem;
.sortableItem {
@include themed() {
background: t($modal-sidebar) !important;
padding: 10px 80px 10px 10px;
border-radius: 15px;
margin-bottom: 10px;
font-size: 1.325rem;
color: t($color) !important;
cursor: move;
width: 150px;
z-index: 999 !important;
svg {
font-size: 1.3rem;
}
&:hover {
background: t($modal-sidebarActive) !important;
}
}
&:hover {
background: var(--tab-active) !important;
}
}
ul {
padding-left: 0;
margin: 0;
ul {
padding-left: 0px;
margin: 0;
>label {
vertical-align: middle;
> label {
vertical-align: middle;
}
}
}
.images-row {
display: flex;
flex-wrap: wrap;
width: 500px;
div {
width: 100px;
height: 100px;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
background-color: var(--sidebar);
border: 20px solid var(--sidebar);
border-radius: 20px;
margin: 0 20px 20px 0;
@include themed() {
display: flex;
align-items: flex-end;
justify-content: center;
svg {
font-size: 1.9em;
flex-wrap: wrap;
gap: 15px;
div {
background: t($modal-sidebar);
padding: 10px;
box-shadow: 0 0 0 4px t($modal-sidebarActive);
border-radius: t($borderRadius);
gap: 10px;
img {
width: auto;
height: 100px;
border-radius: t($borderRadius);
}
}
.iconButton {
width: calc(100% - 22px);
margin-top: 10px;
@include basicIconButton(11px, 1.3rem, modal);
}
}
}

View File

@ -32,7 +32,16 @@ div.color-preview-area > div > div:nth-child(5) {
border: 2px solid var(--modal-text) !important;
}
.text-input, .number-input {
.text-input,
.number-input {
background-color: var(--sidebar) !important;
color: var(--modal-text) !important;
}
.colourInput {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
flex-flow: row-reverse;
}

View File

@ -13,7 +13,7 @@ export default class Checkbox extends PureComponent {
}
handleChange = () => {
const value = (this.state.checked === true) ? false : true;
const value = (this.state.checked !== true);
localStorage.setItem(this.props.name, value);
this.setState({
@ -28,7 +28,7 @@ export default class Checkbox extends PureComponent {
if (this.props.element) {
if (!document.querySelector(this.props.element)) {
document.querySelector('.reminder-info').style.display = 'block';
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
}
}
@ -43,7 +43,6 @@ export default class Checkbox extends PureComponent {
control={<CheckboxUI name={this.props.name} color='primary' className='checkbox' checked={this.state.checked} onChange={this.handleChange} disabled={this.props.disabled || false} />}
label={this.props.text}
/>
<br/>
</>
);
}

View File

@ -37,7 +37,7 @@ export default class Dropdown extends PureComponent {
if (this.props.element) {
if (!document.querySelector(this.props.element)) {
document.querySelector('.reminder-info').style.display = 'block';
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
}
}

View File

@ -1,9 +1,11 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { MdHelpOutline, MdFlag } from 'react-icons/md';
import Slider from './Slider';
import Switch from './Switch';
import SettingsItem from './SettingsItem';
import { values } from 'modules/helpers/settings/modals';
@ -13,11 +15,57 @@ export default class Header extends PureComponent {
return (
<>
<h2>{this.props.title}</h2>
<Switch name={this.props.setting} text={getMessage('modals.main.settings.enabled')} category={this.props.category} element={this.props.element || null} />
{this.props.zoomSetting ?
<><Slider title={getMessage('modals.main.settings.sections.appearance.accessibility.widget_zoom')} name={this.props.zoomSetting} min='10' max='400' default='100' display='%' marks={values('zoom')} category={this.props.zoomCategory || this.props.category}/></>
: <br/>}
<span className="mainTitle">{this.props.title}</span>
<div className="headerExtras">
<span className="link">
<MdHelpOutline /> More Info
</span>
<span
className="link"
onClick={() =>
window.open(
variables.constants.BUG_REPORT + this.props.title.split(' ').join('+'),
'_blank',
)
}
>
<MdFlag /> Report Issue
</span>
</div>
{this.props.switch ? (
<SettingsItem
title={getMessage('modals.main.settings.enabled')}
subtitle={getMessage('modals.main.settings.enabled')}
>
<Switch
name={this.props.setting}
text={getMessage('modals.main.settings.enabled')}
category={this.props.category}
element={this.props.element || null}
/>
</SettingsItem>
) : null}
{this.props.zoomSetting ? (
<div className="settingsRow">
<div className="content">
<span className="title">
{getMessage('modals.main.settings.sections.appearance.accessibility.widget_zoom')}
</span>
<span className="subtitle">eeeh course</span>
</div>
<div className="action">
<Slider
name={this.props.zoomSetting}
min="10"
max="400"
default="100"
display="%"
marks={values('zoom')}
category={this.props.zoomCategory || this.props.category}
/>
</div>
</div>
) : null}
</>
);
}

View File

@ -7,7 +7,7 @@ export default function KeybindInput(props) {
const getButton = () => {
if (!value) {
return <button className='cleanButton' style={{ visibility: 'hidden' }} onClick={() => props.action('reset', props.setting)}><Cancel/></button>;;
return <button className='cleanButton' style={{ visibility: 'hidden' }} onClick={() => props.action('reset', props.setting)}><MdCancel/></button>;;
} else if (value === variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.keybinds.recording')) {
return <button className='cleanButton' onClick={() => props.action('cancel', props.setting)}><MdCancel/></button>;
} else {

View File

@ -40,7 +40,7 @@ export default class Radio extends PureComponent {
if (this.props.element) {
if (!document.querySelector(this.props.element)) {
document.querySelector('.reminder-info').style.display = 'block';
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
}
}

View File

@ -10,19 +10,35 @@ export default function ResetModal({ modalClose }) {
};
return (
<>
<h1 style={{ textAlign: 'center' }}>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.advanced.reset_modal.title')}</h1>
<span>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.advanced.reset_modal.question')}</span>
<br/><br/>
<span>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.advanced.reset_modal.information')}</span>
<div className='resetfooter'>
<button className='round reset' style={{ marginLeft: 0 }} onClick={() => reset()}>
<MdRestartAlt/>
<div className="resetModal">
<span className="mainTitle" style={{ textAlign: 'center' }}>
{variables.language.getMessage(
variables.languagecode,
'modals.main.settings.sections.advanced.reset_modal.title',
)}
</span>
<span className="title">
{variables.language.getMessage(
variables.languagecode,
'modals.main.settings.sections.advanced.reset_modal.question',
)}
</span>
<span style={{ maxWidth: '450px' }} className="subtitle">
{variables.language.getMessage(
variables.languagecode,
'modals.main.settings.sections.advanced.reset_modal.information',
)}
</span>
<div className="resetFooter">
<button onClick={modalClose}>
<MdClose />
Close
</button>
<button className='round add' style={{ marginLeft: '5px' }} onClick={modalClose}>
<MdClose/>
<button onClick={() => reset()}>
<MdRestartAlt />
Reset
</button>
</div>
</>
</div>
);
}

View File

@ -0,0 +1,15 @@
import variables from 'modules/variables';
export default function SettingsItem(props) {
/*const getMessage = (text) => variables.language.getMessage(variables.languageCode, text);*/
return (
<div className={props.final ? 'settingsRow settingsNoBorder' : 'settingsRow'}>
<div className="content">
<span className="title">{props.title}</span>
<span className="subtitle">{props.subtitle}</span>
{/*<span className='link'>{getMessage('modals.main.settings.buttons.reset')}</span>*/}
</div>
<div className="action">{props.children}</div>
</div>
);
}

View File

@ -9,7 +9,7 @@ export default class SliderComponent extends PureComponent {
constructor(props) {
super(props);
this.state = {
value: localStorage.getItem(this.props.name) || this.props.default
value: localStorage.getItem(this.props.name) || this.props.default,
};
}
@ -20,56 +20,64 @@ export default class SliderComponent extends PureComponent {
if (text) {
if (value === '') {
return this.setState({
value: 0
value: 0,
});
}
if (value > this.props.max) {
value = this.props.max;
}
if (value < this.props.min) {
value = this.props.min;
}
}
localStorage.setItem(this.props.name, value);
this.setState({
value
value,
});
if (this.props.element) {
if (!document.querySelector(this.props.element)) {
document.querySelector('.reminder-info').style.display = 'block';
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
}
}
EventBus.dispatch('refresh', this.props.category);
}
};
resetItem = () => {
this.handleChange({
target: {
value: this.props.default || ''
}
value: this.props.default || '',
},
});
toast(variables.language.getMessage(variables.languagecode, 'toasts.reset'));
}
};
render() {
return (
<>
<p>{this.props.title}<span className='modalLink' onClick={this.resetItem}>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.buttons.reset')}</span></p>
<Slider
value={Number(this.state.value)}
onChange={this.handleChange}
valueLabelDisplay='auto'
default={Number(this.props.default)}
min={Number(this.props.min)}
max={Number(this.props.max)}
step={Number(this.props.step) || 1}
getAriaValueText={(value) => `${value}`}
<span className={'sliderTitle'}>
{this.props.title}
<span className="link" onClick={this.resetItem}>
{variables.language.getMessage(
variables.languagecode,
'modals.main.settings.buttons.reset',
)}
</span>
</span>
<Slider
value={Number(this.state.value)}
onChange={this.handleChange}
valueLabelDisplay="auto"
default={Number(this.props.default)}
min={Number(this.props.min)}
max={Number(this.props.max)}
step={Number(this.props.step) || 1}
getAriaValueText={(value) => `${value}`}
marks={this.props.marks || []}
/>
</>

View File

@ -13,7 +13,7 @@ export default class Switch extends PureComponent {
}
handleChange = () => {
const value = (this.state.checked === true) ? false : true;
const value = (this.state.checked !== true);
localStorage.setItem(this.props.name, value);
this.setState({
@ -24,7 +24,7 @@ export default class Switch extends PureComponent {
if (this.props.element) {
if (!document.querySelector(this.props.element)) {
document.querySelector('.reminder-info').style.display = 'block';
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
}
}

View File

@ -28,7 +28,7 @@ export default class Text extends PureComponent {
if (this.props.element) {
if (!document.querySelector(this.props.element)) {
document.querySelector('.reminder-info').style.display = 'block';
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
}
}
@ -52,7 +52,7 @@ export default class Text extends PureComponent {
<TextField label={this.props.title} value={this.state.value} onChange={this.handleChange} varient='outlined' multiline spellCheck={false} minRows={4} maxRows={10} InputLabelProps={{ shrink: true }} />
: <TextField label={this.props.title} value={this.state.value} onChange={this.handleChange} varient='outlined' InputLabelProps={{ shrink: true }} />
}
<span className='modalLink' onClick={this.resetItem}>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.buttons.reset')}</span>
<span className='link' onClick={this.resetItem}>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.buttons.reset')}</span>
</>
);
}

View File

@ -1,11 +1,13 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { MdEmail } from 'react-icons/md';
import { FaDiscord, FaTwitter } from 'react-icons/fa';
import { SiGithubsponsors, SiLiberapay, SiKofi, SiPatreon } from 'react-icons/si';
import { FaDiscord, FaTwitter, FaPatreon, FaGithub } from 'react-icons/fa';
import { SiKofi } from 'react-icons/si';
import Tooltip from 'components/helpers/tooltip/Tooltip';
import SettingsItem from '../SettingsItem';
const other_contributors = require('modules/other_contributors.json');
export default class About extends PureComponent {
@ -19,7 +21,11 @@ export default class About extends PureComponent {
other_contributors: [],
photographers: this.getMessage('modals.main.loading'),
update: this.getMessage('modals.main.settings.sections.about.version.checking_update'),
loading: this.getMessage('modals.main.loading')
loading: this.getMessage('modals.main.loading'),
image:
localStorage.getItem('theme') === 'dark'
? './././icons/mue_dark.svg'
: './././icons/mue_light.svg',
};
this.controller = new AbortController();
}
@ -28,10 +34,40 @@ export default class About extends PureComponent {
let contributors, sponsors, photographers, versionData;
try {
versionData = await (await fetch(variables.constants.GITHUB_URL + '/repos/' + variables.constants.ORG_NAME + '/' + variables.constants.REPO_NAME + '/releases', { signal: this.controller.signal })).json();
contributors = await (await fetch(variables.constants.GITHUB_URL + '/repos/'+ variables.constants.ORG_NAME + '/' + variables.constants.REPO_NAME + '/contributors', { signal: this.controller.signal })).json();
sponsors = (await (await fetch(variables.constants.SPONSORS_URL + '/list', { signal: this.controller.signal })).json()).sponsors;
photographers = await (await fetch(variables.constants.API_URL + '/images/photographers', { signal: this.controller.signal })).json();
versionData = await (
await fetch(
variables.constants.GITHUB_URL +
'/repos/' +
variables.constants.ORG_NAME +
'/' +
variables.constants.REPO_NAME +
'/releases',
{ signal: this.controller.signal },
)
).json();
contributors = await (
await fetch(
variables.constants.GITHUB_URL +
'/repos/' +
variables.constants.ORG_NAME +
'/' +
variables.constants.REPO_NAME +
'/contributors',
{ signal: this.controller.signal },
)
).json();
sponsors = (
await (
await fetch(variables.constants.SPONSORS_URL + '/list', {
signal: this.controller.signal,
})
).json()
).sponsors;
photographers = await (
await fetch(variables.constants.API_URL + '/images/photographers', {
signal: this.controller.signal,
})
).json();
} catch (e) {
if (this.controller.signal.aborted === true) {
return;
@ -39,11 +75,11 @@ export default class About extends PureComponent {
return this.setState({
update: this.getMessage('modals.main.settings.sections.about.version.error.title'),
loading: this.getMessage('modals.main.settings.sections.about.version.error.description')
loading: this.getMessage('modals.main.settings.sections.about.version.error.description'),
});
}
if (sponsors.length === 0) {
if (sponsors.length === 0) {
sponsors = [{ handle: 'empty' }];
}
@ -54,8 +90,13 @@ export default class About extends PureComponent {
const newVersion = versionData[0].tag_name;
let update = this.getMessage('modals.main.settings.sections.about.version.no_update');
if (Number(variables.constants.VERSION.replaceAll('.', '')) < Number(newVersion.replaceAll('.', ''))) {
update = `${this.getMessage('modals.main.settings.sections.about.version.update_available')}: ${newVersion}`;
if (
Number(variables.constants.VERSION.replaceAll('.', '')) <
Number(newVersion.replaceAll('.', ''))
) {
update = `${this.getMessage(
'modals.main.settings.sections.about.version.update_available',
)}: ${newVersion}`;
}
this.setState({
@ -64,8 +105,8 @@ export default class About extends PureComponent {
sponsors,
update,
other_contributors,
photographers: photographers.sort().join(', '),
loading: null
photographers: photographers.sort().join(', '),
loading: null,
});
}
@ -73,7 +114,7 @@ export default class About extends PureComponent {
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
this.setState({
update: this.getMessage('modals.main.settings.sections.about.version.checking_update'),
loading: this.getMessage('modals.main.marketplace.offline.description')
loading: this.getMessage('modals.main.marketplace.offline.description'),
});
return;
}
@ -89,59 +130,189 @@ export default class About extends PureComponent {
render() {
return (
<>
<h2>{this.getMessage('modals.main.settings.sections.about.title')}</h2>
<img draggable='false' className='aboutLogo' src='./././icons/logo_horizontal.png' alt='Logo'></img>
<p>{this.getMessage('modals.main.settings.sections.about.copyright')} {variables.constants.COPYRIGHT_YEAR}-{new Date().getFullYear()} <a href={'https://github.com/' + variables.constants.ORG_NAME + '/' + variables.constants.REPO_NAME + '/graphs/contributors'} className='aboutLink' target='_blank' rel='noopener noreferrer'>{variables.constants.COPYRIGHT_NAME}</a> ({variables.constants.COPYRIGHT_LICENSE})</p>
<p>{this.getMessage('modals.main.settings.sections.about.version.title')} {variables.constants.VERSION} ({this.state.update})</p>
<a href={variables.constants.PRIVACY_URL} className='aboutLink' target='_blank' rel='noopener noreferrer' style={{ fontSize: '1rem' }}>{this.getMessage('modals.welcome.sections.privacy.links.privacy_policy')}</a>
<span className="mainTitle">
{this.getMessage('modals.main.settings.sections.about.title')}
</span>
<SettingsItem>
<div style={{ display: 'flex', flexFlow: 'column', gap: '5px' }}>
<img draggable="false" className="aboutLogo" src={this.state.image} alt="Logo" />
<span className="title">
{this.getMessage('modals.main.settings.sections.about.version.title')}{' '}
{variables.constants.VERSION}
</span>
<span className="subtitle">({this.state.update})</span>
<span className="subtitle">
{this.getMessage('modals.main.settings.sections.about.copyright')}{' '}
{variables.constants.COPYRIGHT_YEAR}-{new Date().getFullYear()}{' '}
<a
className="link"
href={
'https://github.com/' +
variables.constants.ORG_NAME +
'/' +
variables.constants.REPO_NAME +
'/graphs/contributors'
}
target="_blank"
rel="noopener noreferrer"
>
{variables.constants.COPYRIGHT_NAME}
</a>{' '}
({variables.constants.COPYRIGHT_LICENSE})
</span>
<span className="subtitle">
<a
href={variables.constants.PRIVACY_URL}
className="link"
target="_blank"
rel="noopener noreferrer"
>
{this.getMessage('modals.welcome.sections.privacy.links.privacy_policy')}
</a>
</span>
</div>
</SettingsItem>
<h3 className='contacth3'>{this.getMessage('modals.main.settings.sections.about.contact_us')}</h3>
<a href={'mailto:' + variables.constants.EMAIL} className='aboutIcon' target='_blank' rel='noopener noreferrer'><MdEmail/></a>
<a href={'https://twitter.com/' + variables.constants.TWITTER_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><FaTwitter/></a>
<a href={'https://discord.gg/' + variables.constants.DISCORD_SERVER} className='aboutIcon' target='_blank' rel='noopener noreferrer'><FaDiscord/></a>
<h3 className='contacth3'>{this.getMessage('modals.main.settings.sections.about.support_mue')}</h3>
<a href={'https://github.com/sponsors/' + variables.constants.SPONSORS_USERNAME} className='aboutIcon' target='_blank' rel='noopener noreferrer'><SiGithubsponsors/></a>
<a href={'https://liberapay.com/' + variables.constants.LIBERAPAY_USERNAME} className='aboutIcon' target='_blank' rel='noopener noreferrer'><SiLiberapay/></a>
<a href={'https://ko-fi.com/' + variables.constants.KOFI_USERNAME} className='aboutIcon' target='_blank' rel='noopener noreferrer'><SiKofi/></a>
<a href={'https://patreon.com/' + variables.constants.PATREON_USERNAME} className='aboutIcon' target='_blank' rel='noopener noreferrer'><SiPatreon/></a>
<h3>{this.getMessage('modals.main.settings.sections.about.resources_used.title')}</h3>
<p>
<a href='https://www.pexels.com' className='aboutLink' target='_blank' rel='noopener noreferrer'>Pexels</a>
, <a href='https://unsplash.com' className='aboutLink' target='_blank' rel='noopener noreferrer'>Unsplash</a> ({this.getMessage('modals.main.settings.sections.about.resources_used.bg_images')})
</p>
<h3>{this.getMessage('modals.main.settings.sections.about.contributors')}</h3>
<p>{this.state.loading}</p>
{this.state.contributors.map(({ login, id }) => (
<Tooltip title={login} key={login}>
<a href={'https://github.com/' + login} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={'https://avatars.githubusercontent.com/u/' + id + '?s=128'} alt={login}></img></a>
</Tooltip>
))}
{ // for those who contributed without opening a pull request
this.state.other_contributors.map(({ login, avatar_url }) => (
<Tooltip title={login} key={login}>
<a href={'https://github.com/' + login} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={avatar_url + '&s=128'} alt={login}></img></a>
</Tooltip>
))}
<h3>{this.getMessage('modals.main.settings.sections.about.supporters')}</h3>
<p>{this.state.loading}</p>
{this.state.sponsors.map(({ handle, avatar }) => {
if (handle === 'empty') {
return <p>{this.getMessage('modals.main.settings.sections.about.no_supporters')}</p>;
}
return (
<Tooltip title={handle} key={handle}>
<a href={'https://github.com/' + handle} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={avatar.split('?')[0] + '?s=128'} alt={handle}></img></a>
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
<span className="title">
{this.getMessage('modals.main.settings.sections.about.contact_us')}
</span>
<div className="aboutContact">
<Tooltip title={'Email'}>
<a
href={'mailto:' + variables.constants.EMAIL}
target="_blank"
rel="noopener noreferrer"
>
<MdEmail />
</a>
</Tooltip>
)
})}
<Tooltip title={'Twitter'}>
<a
href={'https://twitter.com/' + variables.constants.TWITTER_HANDLE}
target="_blank"
rel="noopener noreferrer"
>
<FaTwitter />
</a>
</Tooltip>
<Tooltip title={'Discord'}>
<a
href={'https://discord.gg/' + variables.constants.DISCORD_SERVER}
target="_blank"
rel="noopener noreferrer"
>
<FaDiscord />
</a>
</Tooltip>
</div>
</div>
<h3>{this.getMessage('modals.main.settings.sections.about.photographers')}</h3>
<p>{this.state.photographers}</p>
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
<span className="title">
{this.getMessage('modals.main.settings.sections.about.support_mue')}
</span>
<p>As Mue is entirely free, we rely on donations to cover pay the server bills and fund development</p>
<div className="aboutContact">
<a class='button' href={variables.constants.DONATE_LINK}>Donate</a>
</div>
</div>
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
<span className="title">
{this.getMessage('modals.main.settings.sections.about.resources_used.title')}
</span>
<span className="subtitle">
<a
href="https://www.pexels.com"
className="link"
target="_blank"
rel="noopener noreferrer"
>
Pexels
</a>
,{' '}
<a
href="https://unsplash.com"
className="link"
target="_blank"
rel="noopener noreferrer"
>
Unsplash
</a>{' '}
({this.getMessage('modals.main.settings.sections.about.resources_used.bg_images')})
</span>
<span className="subtitle">
<a href="https://undraw.co" className="link" target="_blank" rel="noopener noreferrer">
Undraw
</a>{' '}
({this.getMessage('modals.main.settings.sections.about.resources_used.welcome_img')})
</span>
</div>
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
<span className="title">
{this.getMessage('modals.main.settings.sections.about.contributors')}
</span>
<p>{this.state.loading}</p>
<div className="contributorImages">
{this.state.contributors.map(({ login, id }) => (
<Tooltip title={login} key={login}>
<a href={'https://github.com/' + login} target="_blank" rel="noopener noreferrer">
<img
draggable="false"
src={'https://avatars.githubusercontent.com/u/' + id + '?s=128'}
alt={login}
></img>
</a>
</Tooltip>
))}
{
// for those who contributed without opening a pull request
this.state.other_contributors.map(({ login, avatar_url }) => (
<Tooltip title={login} key={login}>
<a href={'https://github.com/' + login} target="_blank" rel="noopener noreferrer">
<img draggable="false" src={avatar_url + '&s=128'} alt={login}></img>
</a>
</Tooltip>
))
}
</div>
</div>
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
<span className="title">
{this.getMessage('modals.main.settings.sections.about.supporters')}
</span>
<p>{this.state.loading}</p>
<div className="contributorImages">
{this.state.sponsors.map(({ handle, avatar }) => {
if (handle === 'empty') {
return (
<p>{this.getMessage('modals.main.settings.sections.about.no_supporters')}</p>
);
}
return (
<Tooltip title={handle} key={handle}>
<a
href={'https://github.com/' + handle}
target="_blank"
rel="noopener noreferrer"
>
<img draggable="false" src={avatar.split('?')[0] + '?s=128'} alt={handle}></img>
</a>
</Tooltip>
);
})}
</div>
</div>
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
<span className="title">
{this.getMessage('modals.main.settings.sections.about.photographers')}
</span>
<span className="subtitle">{this.state.photographers}</span>
</div>
</>
);
}

View File

@ -1,73 +1,169 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import Modal from 'react-modal';
import { MenuItem } from '@mui/material';
import { MdUpload as ImportIcon, MdDownload as ExportIcon, MdRestartAlt as ResetIcon } from 'react-icons/md';
import variables from "modules/variables";
import { PureComponent } from "react";
import Modal from "react-modal";
import { MenuItem } from "@mui/material";
import {
MdUpload as ImportIcon,
MdDownload as ExportIcon,
MdRestartAlt as ResetIcon,
} from "react-icons/md";
import { exportSettings, importSettings } from 'modules/helpers/settings/modals';
import {
exportSettings,
importSettings,
} from "modules/helpers/settings/modals";
import Checkbox from '../Checkbox';
import FileUpload from '../FileUpload';
import Text from '../Text';
import Switch from '../Switch';
import ResetModal from '../ResetModal';
import Dropdown from '../Dropdown';
import Checkbox from "../Checkbox";
import FileUpload from "../FileUpload";
import Text from "../Text";
import Switch from "../Switch";
import ResetModal from "../ResetModal";
import Dropdown from "../Dropdown";
import SettingsItem from "../SettingsItem";
const time_zones = require('components/widgets/time/timezones.json');
const time_zones = require("components/widgets/time/timezones.json");
export default class AdvancedSettings extends PureComponent {
constructor() {
super();
this.state = {
resetModal: false
resetModal: false,
};
}
render() {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const getMessage = (text) =>
variables.language.getMessage(variables.languagecode, text);
return (
<>
<h2>{getMessage('modals.main.settings.sections.advanced.title')}</h2>
<Checkbox name='offlineMode' text={getMessage('modals.main.settings.sections.advanced.offline_mode')} element='.other' />
<Dropdown name='timezone' label={getMessage('modals.main.settings.sections.advanced.timezone.title')} category='timezone' manual={true}>
<MenuItem value='auto'>{getMessage('modals.main.settings.sections.advanced.timezone.automatic')}</MenuItem>
{time_zones.map((timezone) => (
<MenuItem value={timezone} key={timezone}>{timezone}</MenuItem>
))}
</Dropdown>
<span className="mainTitle">
{getMessage("modals.main.settings.sections.advanced.title")}
</span>
<SettingsItem
title={getMessage(
"modals.main.settings.sections.advanced.offline_mode"
)}
>
<Switch
name="offlineMode"
text={getMessage(
"modals.main.settings.sections.advanced.offline_mode"
)}
element=".other"
/>
</SettingsItem>
{localStorage.getItem("welcomePreview") !== "true" ? (
<div className="settingsRow">
<div className="content">
<span className="title">
{getMessage("modals.main.settings.sections.advanced.data")}
</span>
<span className="subtitle">
{getMessage(
"modals.main.settings.sections.advanced.experimental_warning"
)}
</span>
</div>
<div className="action activityButtons">
<button onClick={() => this.setState({ resetModal: true })}>
{getMessage("modals.main.settings.buttons.reset")}
<ResetIcon />
</button>
<button onClick={() => exportSettings()}>
{getMessage("modals.main.settings.buttons.export")}
<ExportIcon />
</button>
<button
onClick={() => document.getElementById("file-input").click()}
>
{getMessage("modals.main.settings.buttons.import")}
<ImportIcon />
</button>
</div>
</div>
) : null}
<SettingsItem title="This should really be in time">
<Dropdown
name="timezone"
label={getMessage(
"modals.main.settings.sections.advanced.timezone.title"
)}
category="timezone"
manual={true}
>
<MenuItem value="auto">
{getMessage(
"modals.main.settings.sections.advanced.timezone.automatic"
)}
</MenuItem>
{time_zones.map((timezone) => (
<MenuItem value={timezone} key={timezone}>
{timezone}
</MenuItem>
))}
</Dropdown>
</SettingsItem>
<SettingsItem
title={getMessage("modals.main.settings.sections.advanced.tab_name")}
>
<Text
title={getMessage(
"modals.main.settings.sections.advanced.tab_name"
)}
name="tabName"
default={getMessage("tabname")}
category="other"
/>
</SettingsItem>
<FileUpload
id="file-input"
accept="application/json"
type="settings"
loadFunction={(e) => importSettings(e)}
/>
<SettingsItem
title={getMessage(
"modals.main.settings.sections.advanced.custom_css"
)}
>
<Text
title={getMessage(
"modals.main.settings.sections.advanced.custom_css"
)}
name="customcss"
textarea={true}
category="other"
/>
</SettingsItem>
<div className="settingsRow">
<div className="content">
<span className="title">
{getMessage("modals.main.settings.sections.experimental.title")}
</span>
<span className="subtitle">
{getMessage(
"modals.main.settings.sections.advanced.experimental_warning"
)}
</span>
</div>
<div className="action">
<Switch
name="experimental"
text={getMessage("modals.main.settings.enabled")}
element=".other"
/>
</div>
</div>
{localStorage.getItem('welcomePreview') !== 'true' ?
<>
<h3>{getMessage('modals.main.settings.sections.advanced.data')}</h3>
<br/>
<div className='data-buttons-row'>
<button onClick={() => this.setState({ resetModal: true })}>
{getMessage('modals.main.settings.buttons.reset')}
<ResetIcon/>
</button>
<button onClick={() => exportSettings()}>
{getMessage('modals.main.settings.buttons.export')}
<ExportIcon/>
</button>
<button onClick={() => document.getElementById('file-input').click()}>
{getMessage('modals.main.settings.buttons.import')}
<ImportIcon/>
</button>
</div>
</>
: null}
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => importSettings(e)}/>
<h3>{getMessage('modals.main.settings.sections.advanced.customisation')}</h3>
<Text title={getMessage('modals.main.settings.sections.advanced.tab_name')} name='tabName' default={getMessage('tabname')} category='other'/>
<Text title={getMessage('modals.main.settings.sections.advanced.custom_css')} name='customcss' textarea={true} category='other'/>
<h3>{getMessage('modals.main.settings.sections.experimental.title')}</h3>
<p style={{ maxWidth: '75%' }}>{getMessage('modals.main.settings.sections.advanced.experimental_warning')}</p>
<Switch name='experimental' text={getMessage('modals.main.settings.enabled')} element='.other'/>
<Modal closeTimeoutMS={100} onRequestClose={() => this.setState({ resetModal: false })} isOpen={this.state.resetModal} className='Modal resetmodal mainModal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}>
<Modal
closeTimeoutMS={100}
onRequestClose={() => this.setState({ resetModal: false })}
isOpen={this.state.resetModal}
className="Modal resetmodal mainModal"
overlayClassName="Overlay resetoverlay"
ariaHideApp={false}
>
<ResetModal modalClose={() => this.setState({ resetModal: false })} />
</Modal>
</>

View File

@ -1,62 +1,216 @@
import variables from 'modules/variables';
import variables from "modules/variables";
import Checkbox from '../Checkbox';
import Dropdown from '../Dropdown';
import Radio from '../Radio';
import Slider from '../Slider';
import Text from '../Text';
import Checkbox from "../Checkbox";
import Dropdown from "../Dropdown";
import Radio from "../Radio";
import Slider from "../Slider";
import Text from "../Text";
import SettingsItem from "../SettingsItem";
import { values } from 'modules/helpers/settings/modals';
import { values } from "modules/helpers/settings/modals";
export default function AppearanceSettings() {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const getMessage = (text) =>
variables.language.getMessage(variables.languagecode, text);
const themeOptions = [
{
name: getMessage('modals.main.settings.sections.appearance.theme.auto'),
value: 'auto'
name: getMessage("modals.main.settings.sections.appearance.theme.auto"),
value: "auto",
},
{
name: getMessage('modals.main.settings.sections.appearance.theme.light'),
value: 'light'
},
name: getMessage("modals.main.settings.sections.appearance.theme.light"),
value: "light",
},
{
name: getMessage('modals.main.settings.sections.appearance.theme.dark'),
value: 'dark'
}
name: getMessage("modals.main.settings.sections.appearance.theme.dark"),
value: "dark",
},
];
const styleOptions = [
{
name: "Legacy",
value: "legacy",
},
{
name: "New",
value: "new",
},
];
return (
<>
<h2>{getMessage('modals.main.settings.sections.appearance.title')}</h2>
<Radio name='theme' title={getMessage('modals.main.settings.sections.appearance.theme.title')} options={themeOptions} category='other' />
<span className="mainTitle">
{getMessage("modals.main.settings.sections.appearance.title")}
</span>
<div className="settingsRow">
<div className="content">
<span className="title">
{getMessage("modals.main.settings.sections.appearance.theme.title")}
</span>
<span className="subtitle">subtitle</span>
</div>
<div className="action">
<Radio name="theme" options={themeOptions} category="other" />
</div>
</div>
<div className="settingsRow">
<div className="content">
<span className="title">
{getMessage("modals.main.settings.sections.appearance.font.title")}
</span>
<span className="subtitle">subtitle</span>
</div>
<div className="action">
<Checkbox
name="fontGoogle"
text={getMessage(
"modals.main.settings.sections.appearance.font.google"
)}
category="other"
/>
<Text
title={getMessage(
"modals.main.settings.sections.appearance.font.custom"
)}
name="font"
upperCaseFirst={true}
category="other"
/>
<Dropdown
label={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">
{getMessage(
"modals.main.settings.sections.appearance.font.weight.thin"
)}
</option>
<option value="200">
{getMessage(
"modals.main.settings.sections.appearance.font.weight.extra_light"
)}
</option>
<option value="300">
{getMessage(
"modals.main.settings.sections.appearance.font.weight.light"
)}
</option>
<option value="400">
{getMessage(
"modals.main.settings.sections.appearance.font.weight.normal"
)}
</option>
<option value="500">
{getMessage(
"modals.main.settings.sections.appearance.font.weight.medium"
)}
</option>
<option value="600">
{getMessage(
"modals.main.settings.sections.appearance.font.weight.semi_bold"
)}
</option>
<option value="700">
{getMessage(
"modals.main.settings.sections.appearance.font.weight.bold"
)}
</option>
<option value="800">
{getMessage(
"modals.main.settings.sections.appearance.font.weight.extra_bold"
)}
</option>
</Dropdown>
<Dropdown
label={getMessage(
"modals.main.settings.sections.appearance.font.style.title"
)}
name="fontstyle"
category="other"
>
<option value="normal">
{getMessage(
"modals.main.settings.sections.appearance.font.style.normal"
)}
</option>
<option value="italic">
{getMessage(
"modals.main.settings.sections.appearance.font.style.italic"
)}
</option>
<option value="oblique">
{getMessage(
"modals.main.settings.sections.appearance.font.style.oblique"
)}
</option>
</Dropdown>
</div>
</div>
<SettingsItem
title="Widget Style"
subtitle="Choose between the two styles, legacy (enabled for pre 7.0 users) and our slick modern styling."
>
<Radio name="widgetStyle" options={styleOptions} category="widgets" />
</SettingsItem>
<h3>{getMessage('modals.main.settings.sections.appearance.font.title')}</h3>
<Text title={getMessage('modals.main.settings.sections.appearance.font.custom')} name='font' upperCaseFirst={true} category='other' />
<br/>
<Checkbox name='fontGoogle' text={getMessage('modals.main.settings.sections.appearance.font.google')} category='other' />
<Dropdown label={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'>{getMessage('modals.main.settings.sections.appearance.font.weight.thin')}</option>
<option value='200'>{getMessage('modals.main.settings.sections.appearance.font.weight.extra_light')}</option>
<option value='300'>{getMessage('modals.main.settings.sections.appearance.font.weight.light')}</option>
<option value='400'>{getMessage('modals.main.settings.sections.appearance.font.weight.normal')}</option>
<option value='500'>{getMessage('modals.main.settings.sections.appearance.font.weight.medium')}</option>
<option value='600'>{getMessage('modals.main.settings.sections.appearance.font.weight.semi_bold')}</option>
<option value='700'>{getMessage('modals.main.settings.sections.appearance.font.weight.bold')}</option>
<option value='800'>{getMessage('modals.main.settings.sections.appearance.font.weight.extra_bold')}</option>
</Dropdown>
<Dropdown label={getMessage('modals.main.settings.sections.appearance.font.style.title')} name='fontstyle' category='other'>
<option value='normal'>{getMessage('modals.main.settings.sections.appearance.font.style.normal')}</option>
<option value='italic'>{getMessage('modals.main.settings.sections.appearance.font.style.italic')}</option>
<option value='oblique'>{getMessage('modals.main.settings.sections.appearance.font.style.oblique')}</option>
</Dropdown>
<h3>{getMessage('modals.main.settings.sections.appearance.accessibility.title')}</h3>
{/*<h3>{getMessage('modals.main.settings.sections.appearance.accessibility.title')}</h3>
<Checkbox text={getMessage('modals.main.settings.sections.appearance.accessibility.text_shadow')} name='textBorder' category='other'/>
<Checkbox text={getMessage('modals.main.settings.sections.appearance.accessibility.animations')} name='animations' category='other'/>
<Slider title={getMessage('modals.main.settings.sections.appearance.accessibility.toast_duration')} name='toastDisplayTime' default='2500' step='100' min='500' max='5000' marks={values('toast')} toast={true}
display={' ' + getMessage('modals.main.settings.sections.appearance.accessibility.milliseconds')} />
<Slider title={getMessage('modals.main.settings.sections.appearance.accessibility.toast_duration')} name='toastDisplayTime' default='2500' step='100' min='500' max='5000' marks={values('toast')}
display={' ' + getMessage('modals.main.settings.sections.appearance.accessibility.milliseconds')} />*/}
<div className="settingsRow">
<div className="content">
<span className="title">
{getMessage(
"modals.main.settings.sections.appearance.accessibility.title"
)}
</span>
<span className="subtitle">subtitle</span>
</div>
<div className="action">
<Dropdown
label={getMessage(
"modals.main.settings.sections.appearance.accessibility.text_shadow"
)}
name="textBorder"
category="other"
>
<option value="new">New</option> {/* default */}
<option value="true">Old</option> {/* old checkbox setting */}
<option value="none">None</option>
</Dropdown>
<Checkbox
text={getMessage(
"modals.main.settings.sections.appearance.accessibility.animations"
)}
name="animations"
category="other"
/>
<Slider
title={getMessage(
"modals.main.settings.sections.appearance.accessibility.toast_duration"
)}
name="toastDisplayTime"
default="2500"
step="100"
min="500"
max="5000"
marks={values("toast")}
display={
" " +
getMessage(
"modals.main.settings.sections.appearance.accessibility.milliseconds"
)
}
/>
</div>
</div>
</>
);
}

View File

@ -1,9 +1,9 @@
import variables from 'modules/variables';
import { PureComponent, createRef } from 'react';
import { MdOutlineWifiOff } from 'react-icons/md';
import Modal from 'react-modal';
import variables from "modules/variables";
import { PureComponent, createRef } from "react";
import { MdOutlineWifiOff } from "react-icons/md";
import Modal from "react-modal";
import Lightbox from '../../marketplace/Lightbox';
import Lightbox from "../../marketplace/Lightbox";
export default class Changelog extends PureComponent {
constructor() {
@ -11,63 +11,71 @@ export default class Changelog extends PureComponent {
this.state = {
title: null,
showLightbox: false,
lightboxImg: null
lightboxImg: null,
};
this.offlineMode = (localStorage.getItem('offlineMode') === 'true');
this.offlineMode = localStorage.getItem("offlineMode") === "true";
this.controller = new AbortController();
this.changelog = createRef();
}
async getUpdate() {
const data = await (await fetch(variables.constants.BLOG_POST + '/index.json', { signal: this.controller.signal })).json();
const data = await (
await fetch(variables.constants.BLOG_POST + "/index.json", {
signal: this.controller.signal,
})
).json();
if (this.controller.signal.aborted === true) {
return;
}
let date = new Date(data.date.split(' ')[0]);
date = date.toLocaleDateString(variables.languagecode.replace('_', '-'), {
year: 'numeric',
month: 'long',
day: 'numeric'
let date = new Date(data.date.split(" ")[0]);
date = date.toLocaleDateString(variables.languagecode.replace("_", "-"), {
year: "numeric",
month: "long",
day: "numeric",
});
this.setState({
title: data.title,
date,
image: data.featured_image || null,
author: variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.changelog.by', {
author: data.authors.join(', ')
}),
html: data.html
author: variables.language.getMessage(
variables.languagecode,
"modals.main.settings.sections.changelog.by",
{
author: data.authors.join(", "),
}
),
html: data.html,
});
// lightbox etc
const images = this.changelog.current.getElementsByTagName('img');
const links = this.changelog.current.getElementsByTagName('a');
const images = this.changelog.current.getElementsByTagName("img");
const links = this.changelog.current.getElementsByTagName("a");
for (const img of images) {
img.draggable = false;
img.onclick = () => {
this.setState({
showLightbox: true,
lightboxImg: img.src
lightboxImg: img.src,
});
};
}
// open in new tab
for (let link = 0; link < links.length; link++) {
links[link].target = '_blank';
links[link].rel = 'noopener noreferrer';
links[link].target = "_blank";
links[link].rel = "noopener noreferrer";
}
}
componentDidMount() {
if (navigator.onLine === false || this.offlineMode) {
return;
}
this.getUpdate();
}
@ -77,38 +85,68 @@ export default class Changelog extends PureComponent {
}
render() {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const getMessage = (text) =>
variables.language.getMessage(variables.languagecode, text);
const errorMessage = (msg) => {
return (
<div className='emptyitems'>
<div className='emptyMessage'>
{msg}
</div>
<div className="emptyItems">
<div className="emptyMessage">{msg}</div>
</div>
);
};
if (navigator.onLine === false || this.offlineMode) {
return errorMessage(<>
<MdOutlineWifiOff/>
<h1>{getMessage('modals.main.marketplace.offline.title')}</h1>
<p className='description'>{getMessage('modals.main.marketplace.offline.description')}</p>
</>);
if (navigator.onLine === false || this.offlineMode) {
return errorMessage(
<>
<MdOutlineWifiOff />
<h1>{getMessage("modals.main.marketplace.offline.title")}</h1>
<p className="description">
{getMessage("modals.main.marketplace.offline.description")}
</p>
</>
);
}
if (!this.state.title) {
return errorMessage(<h1>{getMessage('modals.main.loading')}</h1>);
return errorMessage(
<div className="loaderHolder">
<div id="loader"></div>
<span className="subtitle">Just be a sec.</span>
</div>
);
}
return (
<div className='changelogtab' ref={this.changelog}>
<div className="changelogtab" ref={this.changelog}>
<h1>{this.state.title}</h1>
<h5>{this.state.author} {this.state.date}</h5>
{this.state.image ? <img draggable='false' src={this.state.image} alt={this.state.title} className='updateimage'/> : null}
<div className='updatechangelog' dangerouslySetInnerHTML={{ __html: this.state.html }}/>
<Modal closeTimeoutMS={100} onRequestClose={() => this.setState({ showLightbox: false })} isOpen={this.state.showLightbox} className='Modal lightboxmodal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}>
<Lightbox modalClose={() => this.setState({ showLightbox: false })} img={this.state.lightboxImg}/>
<h5>
{this.state.author} {this.state.date}
</h5>
{this.state.image ? (
<img
draggable="false"
src={this.state.image}
alt={this.state.title}
className="updateImage"
/>
) : null}
<div
className="updateChangelog"
dangerouslySetInnerHTML={{ __html: this.state.html }}
/>
<Modal
closeTimeoutMS={100}
onRequestClose={() => this.setState({ showLightbox: false })}
isOpen={this.state.showLightbox}
className="Modal lightBoxModal"
overlayClassName="Overlay resetoverlay"
ariaHideApp={false}
>
<Lightbox
modalClose={() => this.setState({ showLightbox: false })}
img={this.state.lightboxImg}
/>
</Modal>
</div>
);

View File

@ -1,49 +1,93 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import variables from "modules/variables";
import { PureComponent } from "react";
import Header from '../Header';
import Checkbox from '../Checkbox';
import Dropdown from '../Dropdown';
import Header from "../Header";
import Checkbox from "../Checkbox";
import Dropdown from "../Dropdown";
import SettingsItem from "../SettingsItem";
export default class DateSettings extends PureComponent {
constructor() {
super();
this.state = {
dateType: localStorage.getItem('dateType') || 'long'
dateType: localStorage.getItem("dateType") || "long",
};
}
render() {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const getMessage = (text) =>
variables.language.getMessage(variables.languagecode, text);
let dateSettings;
const longSettings = (
<>
<Checkbox name='dayofweek' text={getMessage('modals.main.settings.sections.date.day_of_week')} category='date' />
<Checkbox name='datenth' text={getMessage('modals.main.settings.sections.date.datenth')} category='date' />
<Dropdown
label="Long Format"
name="longFormat"
category="date"
>
<option value="DMY">DMY</option>
<option value="MDY">MDY</option>
<option value="YMD">YMD</option>
</Dropdown>
<Checkbox
name="dayofweek"
text={getMessage("modals.main.settings.sections.date.day_of_week")}
category="date"
/>
<Checkbox
name="datenth"
text={getMessage("modals.main.settings.sections.date.datenth")}
category="date"
/>
</>
);
const shortSettings = (
<>
<Dropdown label={getMessage('modals.main.settings.sections.date.short_format')} name='dateFormat' category='date'>
<option value='DMY'>DMY</option>
<option value='MDY'>MDY</option>
<option value='YMD'>YMD</option>
<Dropdown
label={getMessage("modals.main.settings.sections.date.short_format")}
name="dateFormat"
category="date"
>
<option value="DMY">DMY</option>
<option value="MDY">MDY</option>
<option value="YMD">YMD</option>
</Dropdown>
<Dropdown
label={getMessage(
"modals.main.settings.sections.date.short_separator.title"
)}
name="shortFormat"
category="date"
>
<option value="dash">
{getMessage(
"modals.main.settings.sections.date.short_separator.dash"
)}
</option>
<option value="dots">
{getMessage(
"modals.main.settings.sections.date.short_separator.dots"
)}
</option>
<option value="gaps">
{getMessage(
"modals.main.settings.sections.date.short_separator.gaps"
)}
</option>
<option value="slashes">
{getMessage(
"modals.main.settings.sections.date.short_separator.slashes"
)}
</option>
</Dropdown>
<Dropdown label={getMessage('modals.main.settings.sections.date.short_separator.title')} name='shortFormat' category='date'>
<option value='dash'>{getMessage('modals.main.settings.sections.date.short_separator.dash')}</option>
<option value='dots'>{getMessage('modals.main.settings.sections.date.short_separator.dots')}</option>
<option value='gaps'>{getMessage('modals.main.settings.sections.date.short_separator.gaps')}</option>
<option value='slashes'>{getMessage('modals.main.settings.sections.date.short_separator.slashes')}</option>
</Dropdown>
</>
);
if (this.state.dateType === 'long') {
if (this.state.dateType === "long") {
dateSettings = longSettings;
} else {
dateSettings = shortSettings;
@ -51,15 +95,42 @@ export default class DateSettings extends PureComponent {
return (
<>
<Header title={getMessage('modals.main.settings.sections.date.title')} setting='date' category='date' element='.date' zoomSetting='zoomDate'/>
<Checkbox name='weeknumber' text={getMessage('modals.main.settings.sections.date.week_number')} category='date'/>
<Dropdown label={getMessage('modals.main.settings.sections.time.type')} name='dateType' onChange={(value) => this.setState({ dateType: value })} category='date'>
<option value='long'>{getMessage('modals.main.settings.sections.date.type.long')}</option>
<option value='short'>{getMessage('modals.main.settings.sections.date.type.short')}</option>
</Dropdown>
<Checkbox name='datezero' text={getMessage('modals.main.settings.sections.time.digital.zero')} category='date'/>
{dateSettings}
<Header
title={getMessage("modals.main.settings.sections.date.title")}
setting="date"
category="date"
element=".date"
zoomSetting="zoomDate"
switch={true}
/>
<SettingsItem title="Date Type">
<Dropdown
label={getMessage("modals.main.settings.sections.time.type")}
name="dateType"
onChange={(value) => this.setState({ dateType: value })}
category="date"
>
<option value="long">
{getMessage("modals.main.settings.sections.date.type.long")}
</option>
<option value="short">
{getMessage("modals.main.settings.sections.date.type.short")}
</option>
</Dropdown>
</SettingsItem>
<SettingsItem title="Extra Options">
<Checkbox
name="weeknumber"
text={getMessage("modals.main.settings.sections.date.week_number")}
category="date"
/>
<Checkbox
name="datezero"
text={getMessage("modals.main.settings.sections.time.digital.zero")}
category="date"
/>
{dateSettings}
</SettingsItem>
</>
);
}

View File

@ -1,31 +1,69 @@
import variables from 'modules/variables';
import { useState } from 'react';
import Checkbox from '../Checkbox';
import Slider from '../Slider';
import { TextField } from '@mui/material';
import variables from "modules/variables";
import { useState } from "react";
import Checkbox from "../Checkbox";
import Slider from "../Slider";
import { TextField } from "@mui/material";
import EventBus from 'modules/helpers/eventbus';
import { values } from 'modules/helpers/settings/modals';
import EventBus from "modules/helpers/eventbus";
import { values } from "modules/helpers/settings/modals";
export default function ExperimentalSettings() {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const getMessage = (text) =>
variables.language.getMessage(variables.languagecode, text);
const [eventType, setEventType] = useState();
const [eventName, setEventName] = useState();
return (
<>
<h2>{getMessage('modals.main.settings.sections.experimental.title')}</h2>
<p>{getMessage('modals.main.settings.sections.experimental.warning')}</p>
<h3>{getMessage('modals.main.settings.sections.experimental.developer')}</h3>
<Checkbox name='debug' text='Debug hotkey (Ctrl + #)' element='.other'/>
<Slider title='Debug timeout' name='debugtimeout' min='0' max='5000' default='0' step='100' marks={values('experimental')} element='.other' />
<h2>{getMessage("modals.main.settings.sections.experimental.title")}</h2>
<p>{getMessage("modals.main.settings.sections.experimental.warning")}</p>
<h3>
{getMessage("modals.main.settings.sections.experimental.developer")}
</h3>
<Checkbox name="debug" text="Debug hotkey (Ctrl + #)" element=".other" />
<Slider
title="Debug timeout"
name="debugtimeout"
min="0"
max="5000"
default="0"
step="100"
marks={values("experimental")}
element=".other"
/>
<p>Send Event</p>
<TextField label={'Type'} value={eventType} onChange={(e) => setEventType(e.target.value)} spellCheck={false} varient='outlined' InputLabelProps={{ shrink: true }} />
<TextField label={'Name'} value={eventName} onChange={(e) => setEventName(e.target.value)} spellCheck={false} varient='outlined' InputLabelProps={{ shrink: true }} />
<br/>
<button className='uploadbg' onClick={() => EventBus.dispatch(eventType, eventName)}>Send</button>
<br/><br/>
<button className='reset' style={{ marginLeft: '0px' }} onClick={() => localStorage.clear()}>Clear LocalStorage</button>
<TextField
label={"Type"}
value={eventType}
onChange={(e) => setEventType(e.target.value)}
spellCheck={false}
varient="outlined"
InputLabelProps={{ shrink: true }}
/>
<TextField
label={"Name"}
value={eventName}
onChange={(e) => setEventName(e.target.value)}
spellCheck={false}
varient="outlined"
InputLabelProps={{ shrink: true }}
/>
<br />
<button
className="uploadbg"
onClick={() => EventBus.dispatch(eventType, eventName)}
>
Send
</button>
<br />
<br />
<button
className="reset"
style={{ marginLeft: "0px" }}
onClick={() => localStorage.clear()}
>
Clear LocalStorage
</button>
</>
);
}

View File

@ -5,6 +5,7 @@ import Header from '../Header';
import Checkbox from '../Checkbox';
import Switch from '../Switch';
import Text from '../Text';
import SettingsItem from '../SettingsItem';
export default class GreetingSettings extends PureComponent {
constructor() {
@ -27,17 +28,24 @@ export default class GreetingSettings extends PureComponent {
return (
<>
<Header title={getMessage('modals.main.settings.sections.greeting.title')} setting='greeting' category='greeting' element='.greeting' zoomSetting='zoomGreeting'/>
<Header title={getMessage('modals.main.settings.sections.greeting.title')} setting='greeting' category='greeting' element='.greeting' zoomSetting='zoomGreeting' switch={true}/>
<SettingsItem title={getMessage('modals.main.settings.sections.greeting.birthday')} subtitle={getMessage('modals.main.settings.enabled')}>
<Switch name='birthdayenabled' text={getMessage('modals.main.settings.enabled')} category='greeting'/>
<Checkbox name='birthdayage' text={getMessage('modals.main.settings.sections.greeting.birthday_age')} category='greeting'/>
<p>{getMessage('modals.main.settings.sections.greeting.birthday_date')}</p>
<input type='date' onChange={this.changeDate} value={this.state.birthday.toISOString().substr(0, 10)} required/>
</SettingsItem>
<SettingsItem title="Additional Settings" subtitle={getMessage('modals.main.settings.enabled')}>
<Checkbox name='events' text={getMessage('modals.main.settings.sections.greeting.events')} category='greeting'/>
<Checkbox name='defaultGreetingMessage' text={getMessage('modals.main.settings.sections.greeting.default')} category='greeting'/>
<Text title={getMessage('modals.main.settings.sections.greeting.name')} name='greetingName' category='greeting'/>
<h3>{getMessage('modals.main.settings.sections.greeting.birthday')}</h3>
</SettingsItem>
{/*<h3>{getMessage('modals.main.settings.sections.greeting.birthday')}</h3>
<Switch name='birthdayenabled' text={getMessage('modals.main.settings.enabled')} category='greeting'/>
<br/>
<Checkbox name='birthdayage' text={getMessage('modals.main.settings.sections.greeting.birthday_age')} category='greeting'/>
<p>{getMessage('modals.main.settings.sections.greeting.birthday_date')}</p>
<input type='date' onChange={this.changeDate} value={this.state.birthday.toISOString().substr(0, 10)} required/>
<input type='date' onChange={this.changeDate} value={this.state.birthday.toISOString().substr(0, 10)} required/>*/}
</>
);
}

View File

@ -11,16 +11,22 @@ export default class LanguageSettings extends PureComponent {
constructor() {
super();
this.state = {
quoteLanguages: [{
name: this.getMessage('modals.main.loading'),
value: 'loading'
}]
quoteLanguages: [
{
name: this.getMessage('modals.main.loading'),
value: 'loading',
},
],
};
this.controller = new AbortController();
}
async getQuoteLanguages() {
const data = await (await fetch(variables.constants.API_URL + '/quotes/languages', { signal: this.controller.signal })).json();
const data = await (
await fetch(variables.constants.API_URL + '/quotes/languages', {
signal: this.controller.signal,
})
).json();
if (this.controller.signal.aborted === true) {
return;
@ -30,22 +36,24 @@ export default class LanguageSettings extends PureComponent {
data.forEach((item) => {
quoteLanguages.push({
name: item,
value: item
value: item,
});
});
this.setState({
quoteLanguages
quoteLanguages,
});
}
componentDidMount() {
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
return this.setState({
quoteLanguages: [{
name: this.getMessage('modals.main.marketplace.offline.description'),
value: 'loading'
}]
quoteLanguages: [
{
name: this.getMessage('modals.main.marketplace.offline.description'),
value: 'loading',
},
],
});
}
@ -60,10 +68,18 @@ export default class LanguageSettings extends PureComponent {
render() {
return (
<>
<h2>{this.getMessage('modals.main.settings.sections.language.title')}</h2>
<Radio name='language' options={languages} element='.other' />
<h3>{this.getMessage('modals.main.settings.sections.language.quote')}</h3>
<Radio name='quotelanguage' options={this.state.quoteLanguages} category='quote' />
<span className="mainTitle">
{this.getMessage('modals.main.settings.sections.language.title')}
</span>
<div className={'languageSettings'}>
<Radio name="language" options={languages} element=".other" />
</div>
<span className={'mainTitle'}>
{this.getMessage('modals.main.settings.sections.language.quote')}
</span>
<div className={'languageSettings'}>
<Radio name="quotelanguage" options={this.state.quoteLanguages} category="quote" />
</div>
</>
);
}

View File

@ -2,7 +2,8 @@ import variables from 'modules/variables';
import { PureComponent } from 'react';
import { MdCancel, MdAdd } from 'react-icons/md';
import { toast } from 'react-toastify';
import { TextField } from '@mui/material';
import { TextField, TextareaAutosize } from '@mui/material';
import SettingsItem from '../SettingsItem';
import Header from '../Header';
@ -21,22 +22,22 @@ export default class Message extends PureComponent {
reset = () => {
localStorage.setItem('messages', '[""]');
this.setState({
messages: ['']
messages: [''],
});
toast(this.getMessage(this.languagecode, 'toasts.reset'));
EventBus.dispatch('refresh', 'message');
}
};
modifyMessage(type, index) {
const messages = this.state.messages;
if (type === 'add') {
messages.push('');
messages.push(' ');
} else {
messages.splice(index, 1);
}
this.setState({
messages
messages,
});
this.forceUpdate();
@ -44,37 +45,67 @@ export default class Message extends PureComponent {
}
message(e, text, index) {
const result = (text === true) ? e.target.value : e.target.result;
const result = text === true ? e.target.value : e.target.result;
const messages = this.state.messages;
messages[index] = result;
this.setState({
messages
messages,
});
this.forceUpdate();
localStorage.setItem('messages', JSON.stringify(messages));
document.querySelector('.reminder-info').style.display = 'block';
document.querySelector('.reminder-info').style.display = 'flex';
localStorage.setItem('showReminder', true);
}
render() {
return (
<>
<Header title={this.getMessage('modals.main.settings.sections.message.title')} setting='message' category='message' element='.message' zoomSetting='zoomMessage'/>
<p>{this.getMessage('modals.main.settings.sections.message.text')}</p>
<div className='data-buttons-row'>
<button onClick={() => this.modifyMessage('add')}>{this.getMessage('modals.main.settings.sections.message.add')} <MdAdd/></button>
</div>
{this.state.messages.map((_url, index) => (
<div style={{ display: 'flex' }} key={index}>
<TextField value={this.state.messages[index]} onChange={(e) => this.message(e, true, index)} varient='outlined' />
{this.state.messages.length > 1 ? <button className='cleanButton' onClick={() => this.modifyMessage('remove', index)}>
<MdCancel/>
</button> : null}
</div>
))}
<br/>
<Header
title={this.getMessage('modals.main.settings.sections.message.title')}
setting="message"
category="message"
element=".message"
zoomSetting="zoomMessage"
switch={true}
/>
<SettingsItem
title={this.getMessage('modals.main.settings.sections.message.text')}
subtitle=""
>
<button onClick={() => this.modifyMessage('add')}>
{this.getMessage('modals.main.settings.sections.message.add')} <MdAdd />
</button>
</SettingsItem>
<table style={{ width: '100%' }}>
<tr>
<th>Messages</th>
<th>Buttons</th>
</tr>
{this.state.messages.map((_url, index) => (
<tr>
<th>
<TextareaAutosize
value={this.state.messages[index]}
placeholder="Message"
onChange={(e) => this.message(e, true, index)}
varient="outlined"
/>
</th>
<th>
{this.state.messages.length > 1 ? (
<button
className='deleteButton'
onClick={() => this.modifyMessage('remove', index)}>
<MdCancel />
</button>
) : null}
</th>
</tr>
))}
</table>
<br />
</>
);
}

View File

@ -1,34 +1,76 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { useState } from 'react';
import Checkbox from '../Checkbox';
import Dropdown from '../Dropdown';
import Slider from '../Slider';
import { values } from 'modules/helpers/settings/modals';
import SettingsItem from '../SettingsItem';
import Header from '../Header';
export default class Navbar extends PureComponent {
render() {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
export default function Navbar() {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const [showRefreshOptions, setShowRefreshOptions] = useState(
localStorage.getItem('refresh') === 'true',
);
return (
<>
<h2>{getMessage('modals.main.settings.sections.appearance.navbar.title')}</h2>
<Slider title={getMessage('modals.main.settings.sections.appearance.accessibility.widget_zoom')} name='zoomNavbar' min='10' max='400' default='100' display='%' marks={values('zoom')} category='navbar' />
<Checkbox name='navbarHover' text={getMessage('modals.main.settings.sections.appearance.navbar.hover')} category='navbar'/>
<Checkbox name='notesEnabled' text={getMessage('modals.main.settings.sections.appearance.navbar.notes')} category='navbar' />
<Checkbox name='view' text={getMessage('modals.main.settings.sections.background.buttons.view')} category='navbar' />
<Checkbox name='favouriteEnabled' text={getMessage('modals.main.settings.sections.background.buttons.favourite')} category='navbar' />
<Dropdown label={getMessage('modals.main.settings.sections.appearance.navbar.refresh')} name='refresh' category='navbar'>
<option value='false'>{getMessage('modals.main.settings.sections.appearance.navbar.refresh_options.none')}</option>
<option value='background'>{getMessage('modals.main.settings.sections.background.title')}</option>
<option value='quote'>{getMessage('modals.main.settings.sections.quote.title')}</option>
<option value='quotebackground'>{getMessage('modals.main.settings.sections.quote.title')} + {getMessage('modals.main.settings.sections.background.title')}</option>
{/* before it was just a checkbox */}
<option value='true'>{getMessage('modals.main.settings.sections.appearance.navbar.refresh_options.page')}</option>
</Dropdown>
</>
);
}
return (
<>
<Header
title={getMessage('modals.main.settings.sections.appearance.navbar.title')}
setting="navbar"
category="widgets"
zoomSetting="zoomNavbar"
zoomCategory="navbar"
/>
<SettingsItem title="Extra Options" subtitle="subtitle" final={!showRefreshOptions}>
<Checkbox
name="navbarHover"
text={getMessage('modals.main.settings.sections.appearance.navbar.hover')}
category="navbar"
/>
<Checkbox
name="notesEnabled"
text={getMessage('modals.main.settings.sections.appearance.navbar.notes')}
category="navbar"
/>
<Checkbox
name="view"
text={getMessage('modals.main.settings.sections.background.buttons.view')}
category="navbar"
/>
<Checkbox
name="refresh"
text={getMessage('modals.main.settings.sections.appearance.navbar.refresh')}
category="navbar"
onChange={setShowRefreshOptions}
/>
<Checkbox name="todo" text="Todos" category="navbar" />
</SettingsItem>
{showRefreshOptions ? (
<SettingsItem
title={getMessage('modals.main.settings.sections.appearance.navbar.refresh')}
final={true}
>
<Dropdown
label={getMessage('modals.main.settings.sections.appearance.navbar.refresh')}
name="refreshOption"
category="navbar"
>
<option value="page">
{getMessage('modals.main.settings.sections.appearance.navbar.refresh_options.page')}
</option>
<option value="background">
{getMessage('modals.main.settings.sections.background.title')}
</option>
<option value="quote">{getMessage('modals.main.settings.sections.quote.title')}</option>
<option value="quotebackground">
{getMessage('modals.main.settings.sections.quote.title')} +{' '}
{getMessage('modals.main.settings.sections.background.title')}
</option>
</Dropdown>
</SettingsItem>
) : null}
</>
);
}

View File

@ -13,25 +13,26 @@ const widget_name = {
quicklinks: getMessage('modals.main.settings.sections.quicklinks.title'),
quote: getMessage('modals.main.settings.sections.quote.title'),
date: getMessage('modals.main.settings.sections.date.title'),
message: getMessage('modals.main.settings.sections.message.title')
message: getMessage('modals.main.settings.sections.message.title'),
reminder: 'reminder',
};
const SortableItem = sortableElement(({ value }) => (
<li className='sortableitem'>
<li className="sortableItem">
<MdOutlineDragIndicator style={{ verticalAlign: 'middle' }} />
{widget_name[value]}
</li>
));
const SortableContainer = sortableContainer(({ children }) => (
<ul className='sortablecontainer'>{children}</ul>
<ul className="sortablecontainer">{children}</ul>
));
export default class OrderSettings extends PureComponent {
constructor() {
super();
this.state = {
items: JSON.parse(localStorage.getItem('order'))
items: JSON.parse(localStorage.getItem('order')),
};
}
@ -39,34 +40,37 @@ export default class OrderSettings extends PureComponent {
const result = Array.from(array);
const [removed] = result.splice(oldIndex, 1);
result.splice(newIndex, 0, removed);
return result;
}
onSortEnd = ({ oldIndex, newIndex }) => {
this.setState({
items: this.arrayMove(this.state.items, oldIndex, newIndex)
items: this.arrayMove(this.state.items, oldIndex, newIndex),
});
}
};
reset = () => {
localStorage.setItem('order', JSON.stringify(['greeting', 'time', 'quicklinks', 'quote', 'date', 'message']));
localStorage.setItem(
'order',
JSON.stringify(['greeting', 'time', 'quicklinks', 'quote', 'date', 'message', 'reminder']),
);
this.setState({
items: JSON.parse(localStorage.getItem('order'))
items: JSON.parse(localStorage.getItem('order')),
});
toast(getMessage('toasts.reset'));
}
};
enabled = (setting) => {
switch (setting) {
case 'quicklinks':
return (localStorage.getItem('quicklinksenabled') === 'true');
return localStorage.getItem('quicklinksenabled') === 'true';
default:
return (localStorage.getItem(setting) === 'true');
return localStorage.getItem(setting) === 'true';
}
}
};
componentDidUpdate() {
localStorage.setItem('order', JSON.stringify(this.state.items));
@ -77,17 +81,22 @@ export default class OrderSettings extends PureComponent {
render() {
return (
<>
<h2>{getMessage('modals.main.settings.sections.order.title')}</h2>
<span className='modalLink' onClick={this.reset}>{getMessage('modals.main.settings.buttons.reset')}</span>
<SortableContainer onSortEnd={this.onSortEnd} lockAxis='y' lockToContainerEdges disableAutoscroll>
<span className="mainTitle">{getMessage('modals.main.settings.sections.order.title')}</span>
<span className="link" onClick={this.reset}>
{getMessage('modals.main.settings.buttons.reset')}
</span>
<SortableContainer
onSortEnd={this.onSortEnd}
lockAxis="y"
lockToContainerEdges
disableAutoscroll
>
{this.state.items.map((value, index) => {
if (!this.enabled(value)) {
if (!this.enabled(value)) {
return null;
}
return (
<SortableItem key={`item-${value}`} index={index} value={value} />
);
return <SortableItem key={`item-${value}`} index={index} value={value} />;
})}
</SortableContainer>
</>

View File

@ -4,17 +4,21 @@ import { useState } from 'react';
import Header from '../Header';
import Checkbox from '../Checkbox';
import SettingsItem from '../SettingsItem';
export default function QuickLinks() {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const [textOnly, setTextOnly] = useState(localStorage.getItem('quicklinksText') === 'true');
return (
<>
<Header title={getMessage('modals.main.settings.sections.quicklinks.title')} setting='quicklinksenabled' category='quicklinks' element='.quicklinks-container' zoomSetting='zoomQuicklinks'/>
<Checkbox name='quicklinksText' text={getMessage('modals.main.settings.sections.quicklinks.text_only')} category='quicklinks' onChange={(value) => setTextOnly(value)}/>
<Checkbox name='quicklinksddgProxy' text={getMessage('modals.main.settings.sections.background.ddg_image_proxy')} category='quicklinks' disabled={textOnly}/>
<Checkbox name='quicklinksnewtab' text={getMessage('modals.main.settings.sections.quicklinks.open_new')} category='quicklinks'/>
<Checkbox name='quicklinkstooltip' text={getMessage('modals.main.settings.sections.quicklinks.tooltip')} category='quicklinks' disabled={textOnly}/>
<Header title={getMessage('modals.main.settings.sections.quicklinks.title')} setting='quicklinksenabled' category='quicklinks' element='.quicklinks-container' zoomSetting='zoomQuicklinks' switch={true}/>
<SettingsItem title="Extra Options" subtitle="subtitle">
<Checkbox name='quicklinksText' text={getMessage('modals.main.settings.sections.quicklinks.text_only')} category='quicklinks' onChange={(value) => setTextOnly(value)}/>
<Checkbox name='quicklinksddgProxy' text={getMessage('modals.main.settings.sections.background.ddg_image_proxy')} category='quicklinks' disabled={textOnly}/>
<Checkbox name='quicklinksnewtab' text={getMessage('modals.main.settings.sections.quicklinks.open_new')} category='quicklinks'/>
<Checkbox name='quicklinkstooltip' text={getMessage('modals.main.settings.sections.quicklinks.tooltip')} category='quicklinks' disabled={textOnly}/>
</SettingsItem>
</>
);
}

View File

@ -1,11 +1,13 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import React, { PureComponent } from 'react';
import { MdCancel, MdAdd } from 'react-icons/md';
import { TextField } from '@mui/material';
import TextareaAutosize from '@mui/material/TextareaAutosize';
import Header from '../Header';
import Checkbox from '../Checkbox';
import Dropdown from '../Dropdown';
import SettingsItem from '../SettingsItem';
import { toast } from 'react-toastify';
import EventBus from 'modules/helpers/eventbus';
@ -17,40 +19,44 @@ export default class QuoteSettings extends PureComponent {
super();
this.state = {
quoteType: localStorage.getItem('quoteType') || 'api',
customQuote: this.getCustom()
customQuote: this.getCustom(),
};
}
marketplaceType = () => {
if (localStorage.getItem('quote_packs')) {
return <option value='quote_pack'>{this.getMessage('modals.main.navbar.marketplace')}</option>;
return (
<option value="quote_pack">{this.getMessage('modals.main.navbar.marketplace')}</option>
);
}
}
};
resetCustom = () => {
localStorage.setItem('customQuote', '[{"quote": "", "author": ""}]');
this.setState({
customQuote: [{
quote: '',
author: ''
}]
customQuote: [
{
quote: '',
author: '',
},
],
});
toast(this.getMessage('toasts.reset'));
EventBus.dispatch('refresh', 'background');
}
};
customQuote(e, text, index, type) {
const result = (text === true) ? e.target.value : e.target.result;
const result = text === true ? e.target.value : e.target.result;
const customQuote = this.state.customQuote;
customQuote[index][type] = result;
this.setState({
customQuote
customQuote,
});
this.forceUpdate();
localStorage.setItem('customQuote', JSON.stringify(customQuote));
document.querySelector('.reminder-info').style.display = 'block';
document.querySelector('.reminder-info').style.display = 'flex';
localStorage.setItem('showReminder', true);
}
@ -59,14 +65,14 @@ export default class QuoteSettings extends PureComponent {
if (type === 'add') {
customQuote.push({
quote: '',
author: ''
author: '',
});
} else {
customQuote.splice(index, 1);
}
this.setState({
customQuote
customQuote,
});
this.forceUpdate();
@ -76,10 +82,12 @@ export default class QuoteSettings extends PureComponent {
getCustom() {
let data = JSON.parse(localStorage.getItem('customQuote'));
if (data === null) {
data = [{
quote: localStorage.getItem('customQuote') || '',
author: localStorage.getItem('customQuoteAuthor') || ''
}];
data = [
{
quote: localStorage.getItem('customQuote') || '',
author: localStorage.getItem('customQuoteAuthor') || '',
},
];
}
return data;
}
@ -89,53 +97,146 @@ export default class QuoteSettings extends PureComponent {
if (this.state.quoteType === 'custom') {
customSettings = (
<>
<p>{this.getMessage('modals.main.settings.sections.quote.custom')} <span className='modalLink' onClick={this.resetCustom}>{this.getMessage('modals.main.settings.buttons.reset')}</span></p>
<div className='data-buttons-row'>
<button onClick={() => this.modifyCustomQuote('add')}>{this.getMessage('modals.main.settings.sections.quote.add')} <MdAdd/></button>
</div>
{this.state.customQuote.map((_url, index) => (
<div style={{ display: 'flex' }} key={index}>
<TextField value={this.state.customQuote[index].quote} placeholder='Quote' onChange={(e) => this.customQuote(e, true, index, 'quote')} varient='outlined' style={{ marginRight: '10px' }} />
<TextField value={this.state.customQuote[index].author} placeholder='Author' onChange={(e) => this.customQuote(e, true, index, 'author')} varient='outlined' />
{this.state.customQuote.length > 1 ? <button className='cleanButton' onClick={() => this.modifyCustomQuote('remove', index)} style={{ marginBottom: '-14px' }}>
<MdCancel/>
</button> : null}
</div>
))}
<SettingsItem
title={this.getMessage('modals.main.settings.sections.quote.custom')}
subtitle="subtitle"
>
<button onClick={() => this.modifyCustomQuote('add')}>
{this.getMessage('modals.main.settings.sections.quote.add')} <MdAdd />
</button>
</SettingsItem>
<table style={{ width: '100%' }}>
<tr>
<th>Quote</th>
<th>Author</th>
<th>Buttons</th>
</tr>
{this.state.customQuote.map((_url, index) => (
<tr>
<th>
<TextareaAutosize
value={this.state.customQuote[index].quote}
placeholder="Quote"
onChange={(e) => this.customQuote(e, true, index, 'quote')}
varient="outlined"
style={{ marginRight: '10px' }}
/>
</th>
<th>
<TextareaAutosize
value={this.state.customQuote[index].author}
placeholder="Author"
onChange={(e) => this.customQuote(e, true, index, 'author')}
varient="outlined"
/>
</th>
<th>
{this.state.customQuote.length > 1 ? (
<button
className="deleteButton"
onClick={() => this.modifyCustomQuote('remove', index)}
style={{}}
>
<MdCancel />
</button>
) : null}
</th>
</tr>
))}
</table>
</>
);
} else {
// api
customSettings = (
<>
<Dropdown label={this.getMessage('modals.main.settings.sections.background.interval.title')} name='quotechange'>
<option value='refresh'>{this.getMessage('tabname')}</option>
<option value='60000'>{this.getMessage('modals.main.settings.sections.background.interval.minute')}</option>
<option value='1800000'>{this.getMessage('modals.main.settings.sections.background.interval.half_hour')}</option>
<option value='3600000'>{this.getMessage('modals.main.settings.sections.background.interval.hour')}</option>
<option value='86400000'>{this.getMessage('modals.main.settings.sections.background.interval.day')}</option>
<option value='604800000'>{this.getMessage('widgets.date.week')}</option>
<option value='2628000000'>{this.getMessage('modals.main.settings.sections.background.interval.month')}</option>
<SettingsItem title="Additional Options">
<Dropdown
label={this.getMessage('modals.main.settings.sections.background.interval.title')}
name="quotechange"
>
<option value="refresh">{this.getMessage('tabname')}</option>
<option value="60000">
{this.getMessage('modals.main.settings.sections.background.interval.minute')}
</option>
<option value="1800000">
{this.getMessage('modals.main.settings.sections.background.interval.half_hour')}
</option>
<option value="3600000">
{this.getMessage('modals.main.settings.sections.background.interval.hour')}
</option>
<option value="86400000">
{this.getMessage('modals.main.settings.sections.background.interval.day')}
</option>
<option value="604800000">{this.getMessage('widgets.date.week')}</option>
<option value="2628000000">
{this.getMessage('modals.main.settings.sections.background.interval.month')}
</option>
</Dropdown>
</>
</SettingsItem>
);
}
return (
<>
<Header title={this.getMessage('modals.main.settings.sections.quote.title')} setting='quote' category='quote' element='.quotediv' zoomSetting='zoomQuote'/>
<Checkbox name='authorLink' text={this.getMessage('modals.main.settings.sections.quote.author_link')} element='.other' />
<Dropdown label={this.getMessage('modals.main.settings.sections.background.type.title')} name='quoteType' onChange={(value) => this.setState({ quoteType: value })} category='quote'>
{this.marketplaceType()}
<option value='api'>{this.getMessage('modals.main.settings.sections.background.type.api')}</option>
<option value='custom'>{this.getMessage('modals.main.settings.sections.quote.custom')}</option>
</Dropdown>
<Header
title={this.getMessage('modals.main.settings.sections.quote.title')}
setting="quote"
category="quote"
element=".quotediv"
zoomSetting="zoomQuote"
switch={true}
/>
<SettingsItem
title={this.getMessage('modals.main.settings.sections.quote.buttons.title')}
subtitle="subtitle"
>
<Checkbox
name="copyButton"
text={this.getMessage('modals.main.settings.sections.quote.buttons.copy')}
category="quote"
/>
<Checkbox
name="quoteShareButton"
text={this.getMessage('modals.main.settings.sections.quote.buttons.tweet')}
category="quote"
/>
<Checkbox
name="favouriteQuoteEnabled"
text={this.getMessage('modals.main.settings.sections.quote.buttons.favourite')}
category="quote"
/>
<Checkbox
name="authorLink"
text={this.getMessage('modals.main.settings.sections.quote.author_link')}
element=".other"
/>
</SettingsItem>
<SettingsItem
title={this.getMessage('modals.main.settings.sections.background.type.title')}
subtitle="subtitle"
>
<Checkbox name="quoteModern" text="Use modern style" />
</SettingsItem>
<SettingsItem
title={this.getMessage('modals.main.settings.sections.background.type.title')}
subtitle="subtitle"
>
<Dropdown
label={this.getMessage('modals.main.settings.sections.background.type.title')}
name="quoteType"
onChange={(value) => this.setState({ quoteType: value })}
category="quote"
>
{this.marketplaceType()}
<option value="api">
{this.getMessage('modals.main.settings.sections.background.type.api')}
</option>
<option value="custom">
{this.getMessage('modals.main.settings.sections.quote.custom')}
</option>
</Dropdown>
</SettingsItem>
{customSettings}
<h3>{this.getMessage('modals.main.settings.sections.quote.buttons.title')}</h3>
<Checkbox name='copyButton' text={this.getMessage('modals.main.settings.sections.quote.buttons.copy')} category='quote'/>
<Checkbox name='tweetButton' text={this.getMessage('modals.main.settings.sections.quote.buttons.tweet')} category='quote'/>
<Checkbox name='favouriteQuoteEnabled' text={this.getMessage('modals.main.settings.sections.quote.buttons.favourite')} category='quote'/>
</>
);
}

View File

@ -0,0 +1,29 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import Header from '../Header';
export default class ReminderSettings extends PureComponent {
constructor() {
super();
this.state = {
colour: localStorage.getItem('reminderColour') || '#ffa500'
};
}
updateColour(event) {
const colour = event.target.value;
this.setState({ colour });
localStorage.setItem('reminderColour', colour);
}
render() {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
return (
<>
<Header title='Reminder' setting='reminder' category='reminder' element='.reminder' zoomSetting='zoomReminder' switch={true}/>
<input type='color' name='colour' className='colour' onChange={(event) => this.updateColour(event)} value={this.state.colour}></input>
<label htmlFor={'colour'} className='customBackgroundHex'>{this.state.colour}</label>
</>
);
}
}

View File

@ -7,6 +7,7 @@ import Header from '../Header';
import Dropdown from '../Dropdown';
import Checkbox from '../Checkbox';
import Radio from '../Radio';
import SettingsItem from '../SettingsItem';
import EventBus from 'modules/helpers/eventbus';
@ -73,24 +74,31 @@ export default class SearchSettings extends PureComponent {
render() {
return (
<>
<Header title={this.getMessage('modals.main.settings.sections.search.title')} setting='searchBar' category='widgets'/>
<Header title={this.getMessage('modals.main.settings.sections.search.title')} setting='searchBar' category='widgets' switch={true}/>
<SettingsItem title="Extra Options" subtitle="eeeee">
{/* not supported on firefox */}
{(navigator.userAgent.includes('Chrome') && typeof InstallTrigger === 'undefined') ?
<Checkbox name='voiceSearch' text={this.getMessage('modals.main.settings.sections.search.voice_search')} category='search'/>
: null}
<Checkbox name='searchDropdown' text={this.getMessage('modals.main.settings.sections.search.dropdown')} category='search' element='.other'/>
<Checkbox name='searchFocus' text={this.getMessage('modals.main.settings.sections.search.focus')} category='search' element='.other'/>
<ul style={{ display: this.state.customDisplay }}>
<p style={{ marginTop: '0px' }}><span className='link' onClick={() => this.resetSearch()}>{this.getMessage('modals.main.settings.buttons.reset')}</span></p>
<TextField label={this.getMessage('modals.main.settings.sections.search.custom')} value={this.state.customValue} onInput={(e) => this.setState({ customValue: e.target.value })} varient='outlined' InputLabelProps={{ shrink: true }} />
</ul>
<Checkbox name='autocomplete' text={this.getMessage('modals.main.settings.sections.search.autocomplete')} category='search' />
</SettingsItem>
<SettingsItem title={this.getMessage('modals.main.settings.sections.search.search_engine')} subtitle="cheese is gucci tbf">
<Dropdown label={this.getMessage('modals.main.settings.sections.search.search_engine')} name='searchEngine' onChange={(value) => this.setSearchEngine(value)} manual={true}>
{searchEngines.map((engine) => (
<MenuItem key={engine.name} value={engine.settingsName}>{engine.name}</MenuItem>
))}
<MenuItem value='custom'>{this.getMessage('modals.main.settings.sections.search.custom').split(' ')[0]}</MenuItem>
</Dropdown>
<ul style={{ display: this.state.customDisplay }}>
<p style={{ marginTop: '0px' }}><span className='modalLink' onClick={() => this.resetSearch()}>{this.getMessage('modals.main.settings.buttons.reset')}</span></p>
<TextField label={this.getMessage('modals.main.settings.sections.search.custom')} value={this.state.customValue} onInput={(e) => this.setState({ customValue: e.target.value })} varient='outlined' InputLabelProps={{ shrink: true }} />
</ul>
<Checkbox name='autocomplete' text={this.getMessage('modals.main.settings.sections.search.autocomplete')} category='search' />
<Radio title={this.getMessage('modals.main.settings.sections.search.autocomplete_provider')} options={autocompleteProviders} name='autocompleteProvider' category='search'/>
</SettingsItem>
<SettingsItem title={this.getMessage('modals.main.settings.sections.search.autocomplete_provider')} subtitle="cheese">
<Radio options={autocompleteProviders} name='autocompleteProvider' category='search'/>
</SettingsItem>
</>
);
}

View File

@ -1,7 +1,9 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { MdShowChart } from 'react-icons/md';
import Switch from '../Switch';
import SettingsItem from '../SettingsItem';
import EventBus from 'modules/helpers/eventbus';
@ -9,17 +11,17 @@ export default class Stats extends PureComponent {
constructor() {
super();
this.state = {
stats: JSON.parse(localStorage.getItem('statsData')) || {}
stats: JSON.parse(localStorage.getItem('statsData')) || {},
};
}
componentDidMount() {
EventBus.on('refresh', (data) => {
componentDidMount() {
EventBus.on('refresh', (data) => {
if (data === 'stats') {
if (localStorage.getItem('stats') === 'false') {
localStorage.setItem('statsData', JSON.stringify({}));
return this.setState({
stats: {}
stats: {},
});
}
this.forceUpdate();
@ -37,24 +39,95 @@ export default class Stats extends PureComponent {
if (localStorage.getItem('stats') === 'false') {
return (
<>
<h2>{getMessage('modals.main.settings.reminder.title')}</h2>
<p>{getMessage('modals.main.settings.sections.stats.warning')}</p>
<Switch name='stats' text={getMessage('modals.main.settings.sections.stats.usage')} category='stats'/>
<span className="mainTitle">
{getMessage('modals.main.settings.sections.stats.title')}
</span>
<SettingsItem
title={getMessage('modals.main.settings.reminder.title')}
subtitle={getMessage('modals.main.settings.sections.stats.warning')}
>
<Switch
name="stats"
text={getMessage('modals.main.settings.sections.stats.usage')}
category="stats"
/>
</SettingsItem>
</>
);
}
return (
<>
<h2>{getMessage('modals.main.settings.sections.stats.title')}</h2>
<Switch name='stats' text={getMessage('modals.main.settings.sections.stats.usage')} category='stats'/>
<p>{getMessage('modals.main.settings.sections.stats.sections.tabs_opened')}: {this.state.stats['tabs-opened'] || 0}</p>
<p>{getMessage('modals.main.settings.sections.stats.sections.backgrounds_favourited')}: {this.state.stats.feature ? this.state.stats.feature['background-favourite'] || 0 : 0}</p>
<p>{getMessage('modals.main.settings.sections.stats.sections.backgrounds_downloaded')}: {this.state.stats.feature ? this.state.stats.feature['background-download'] || 0 : 0}</p>
<p>{getMessage('modals.main.settings.sections.stats.sections.quotes_favourited')}: {this.state.stats.feature ? this.state.stats.feature['quoted-favourite'] || 0 : 0}</p>
<p>{getMessage('modals.main.settings.sections.stats.sections.quicklinks_added')}: {this.state.stats.feature ? this.state.stats.feature['quicklink-add'] || 0 : 0}</p>
<p>{getMessage('modals.main.settings.sections.stats.sections.settings_changed')}: {this.state.stats.setting ? Object.keys(this.state.stats.setting).length : 0}</p>
<p>{getMessage('modals.main.settings.sections.stats.sections.addons_installed')}: {this.state.stats.marketplace ? this.state.stats.marketplace['install'] : 0}</p>
{/*<h2>{getMessage('modals.main.settings.sections.stats.title')}</h2>
<Switch name='stats' text={getMessage('modals.main.settings.sections.stats.usage')} category='stats'/>*/}
<span className="mainTitle">{getMessage('modals.main.settings.sections.stats.title')}</span>
<SettingsItem
title={getMessage('modals.main.settings.reminder.title')}
subtitle={getMessage('modals.main.settings.sections.stats.warning')}
>
<Switch
name="stats"
text={getMessage('modals.main.settings.sections.stats.usage')}
category="stats"
/>
</SettingsItem>
<div className="statsGrid">
<div>
<span>
{getMessage('modals.main.settings.sections.stats.sections.tabs_opened')}:{' '}
{this.state.stats['tabs-opened'] || 0}
</span>
</div>
<div>
<span>
{getMessage('modals.main.settings.sections.stats.sections.tabs_opened')}:{' '}
{this.state.stats['tabs-opened'] || 0}
</span>
</div>
<div>
<MdShowChart />
<span className="subtitle">
{getMessage('modals.main.settings.sections.stats.sections.tabs_opened')}{' '}
</span>
<span>{this.state.stats['tabs-opened'] || 0}</span>
<span className="subtitle">
{getMessage('modals.main.settings.sections.stats.sections.backgrounds_favourited')}{' '}
</span>
<span>
{this.state.stats.feature ? this.state.stats.feature['background-favourite'] || 0 : 0}
</span>
<span className="subtitle">
{getMessage('modals.main.settings.sections.stats.sections.backgrounds_downloaded')}{' '}
</span>
<span>
{this.state.stats.feature ? this.state.stats.feature['background-download'] || 0 : 0}
</span>
<span className="subtitle">
{getMessage('modals.main.settings.sections.stats.sections.quotes_favourited')}{' '}
</span>
<span>
{this.state.stats.feature ? this.state.stats.feature['quoted-favourite'] || 0 : 0}
</span>
<span className="subtitle">
{getMessage('modals.main.settings.sections.stats.sections.quicklinks_added')}{' '}
</span>
<span>
{this.state.stats.feature ? this.state.stats.feature['quicklink-add'] || 0 : 0}
</span>
<span className="subtitle">
{getMessage('modals.main.settings.sections.stats.sections.settings_changed')}{' '}
</span>
<span>
{this.state.stats.setting ? Object.keys(this.state.stats.setting).length : 0}
</span>
<span className="subtitle">
{getMessage('modals.main.settings.sections.stats.sections.addons_installed')}{' '}
</span>
<span>
{this.state.stats.marketplace ? this.state.stats.marketplace['install'] : 0}
</span>
</div>
</div>
</>
);
}

View File

@ -6,11 +6,13 @@ import Checkbox from '../Checkbox';
import Dropdown from '../Dropdown';
import Radio from '../Radio';
import SettingsItem from '../SettingsItem';
export default class TimeSettings extends PureComponent {
constructor() {
super();
this.state = {
timeType: localStorage.getItem('timeType') || 'digital'
timeType: localStorage.getItem('timeType') || 'digital',
};
}
@ -22,32 +24,64 @@ export default class TimeSettings extends PureComponent {
const digitalOptions = [
{
name: getMessage('modals.main.settings.sections.time.digital.twentyfourhour'),
value: 'twentyfourhour'
value: 'twentyfourhour',
},
{
name: getMessage('modals.main.settings.sections.time.digital.twelvehour'),
value: 'twelvehour'
}
value: 'twelvehour',
},
];
const digitalSettings = (
<>
<h3>{getMessage('modals.main.settings.sections.time.digital.title')}</h3>
<Radio title={getMessage('modals.main.settings.sections.time.format')} name='timeformat' options={digitalOptions} smallTitle={true} category='clock' />
<Checkbox name='seconds' text={getMessage('modals.main.settings.sections.time.digital.seconds')} category='clock' />
<Checkbox name='zero' text={getMessage('modals.main.settings.sections.time.digital.zero')} category='clock' />
</>
<SettingsItem
title={getMessage('modals.main.settings.sections.time.digital.title')}
subtitle={getMessage('modals.main.settings.sections.time.format')}
>
<Radio name="timeformat" options={digitalOptions} smallTitle={true} category="clock" />
<Checkbox
name="seconds"
text={getMessage('modals.main.settings.sections.time.digital.seconds')}
category="clock"
/>
<Checkbox
name="zero"
text={getMessage('modals.main.settings.sections.time.digital.zero')}
category="clock"
/>
</SettingsItem>
);
const analogSettings = (
<>
<h3>{getMessage('modals.main.settings.sections.time.analogue.title')}</h3>
<Checkbox name='secondHand' text={getMessage('modals.main.settings.sections.time.analogue.second_hand')} category='clock' />
<Checkbox name='minuteHand' text={getMessage('modals.main.settings.sections.time.analogue.minute_hand')} category='clock' />
<Checkbox name='hourHand' text={getMessage('modals.main.settings.sections.time.analogue.hour_hand')} category='clock' />
<Checkbox name='hourMarks' text={getMessage('modals.main.settings.sections.time.analogue.hour_marks')} category='clock' />
<Checkbox name='minuteMarks' text={getMessage('modals.main.settings.sections.time.analogue.minute_marks')} category='clock' />
</>
<SettingsItem
title={getMessage('modals.main.settings.sections.time.analogue.title')}
subtitle="subtitle"
>
<Checkbox
name="secondHand"
text={getMessage('modals.main.settings.sections.time.analogue.second_hand')}
category="clock"
/>
<Checkbox
name="minuteHand"
text={getMessage('modals.main.settings.sections.time.analogue.minute_hand')}
category="clock"
/>
<Checkbox
name="hourHand"
text={getMessage('modals.main.settings.sections.time.analogue.hour_hand')}
category="clock"
/>
<Checkbox
name="hourMarks"
text={getMessage('modals.main.settings.sections.time.analogue.hour_marks')}
category="clock"
/>
<Checkbox
name="minuteMarks"
text={getMessage('modals.main.settings.sections.time.analogue.minute_marks')}
category="clock"
/>
</SettingsItem>
);
if (this.state.timeType === 'digital') {
@ -58,12 +92,35 @@ export default class TimeSettings extends PureComponent {
return (
<>
<Header title={getMessage('modals.main.settings.sections.time.title')} setting='time' category='clock' element='.clock-container' zoomSetting='zoomClock'/>
<Dropdown label={getMessage('modals.main.settings.sections.time.type')} name='timeType' onChange={(value) => this.setState({ timeType: value })} category='clock'>
<option value='digital'>{getMessage('modals.main.settings.sections.time.digital.title')}</option>
<option value='analogue'>{getMessage('modals.main.settings.sections.time.analogue.title')}</option>
<option value='percentageComplete'>{getMessage('modals.main.settings.sections.time.percentage_complete')}</option>
</Dropdown>
<Header
title={getMessage('modals.main.settings.sections.time.title')}
setting="time"
category="clock"
element=".clock-container"
zoomSetting="zoomClock"
switch={true}
/>
<SettingsItem
title={getMessage('modals.main.settings.sections.time.type')}
subtitle="subtitle"
>
<Dropdown
label={getMessage('modals.main.settings.sections.time.type')}
name="timeType"
onChange={(value) => this.setState({ timeType: value })}
category="clock"
>
<option value="digital">
{getMessage('modals.main.settings.sections.time.digital.title')}
</option>
<option value="analogue">
{getMessage('modals.main.settings.sections.time.analogue.title')}
</option>
<option value="percentageComplete">
{getMessage('modals.main.settings.sections.time.percentage_complete')}
</option>
</Dropdown>
</SettingsItem>
{timeSettings}
</>
);

View File

@ -3,8 +3,11 @@ import { PureComponent } from 'react';
import Header from '../Header';
import Radio from '../Radio';
import Dropdown from '../Dropdown';
import Checkbox from '../Checkbox';
import { TextField } from '@mui/material';
import SettingsItem from '../SettingsItem';
export default class TimeSettings extends PureComponent {
constructor() {
@ -20,13 +23,13 @@ export default class TimeSettings extends PureComponent {
}
showReminder() {
document.querySelector('.reminder-info').style.display = 'block';
document.querySelector('.reminder-info').style.display = 'flex';
localStorage.setItem('showReminder', true);
}
changeLocation(e) {
this.setState({
location: e.target.value
this.setState({
location: e.target.value
});
this.showReminder();
@ -43,8 +46,8 @@ export default class TimeSettings extends PureComponent {
}, (error) => {
// firefox requires this 2nd function
console.log(error);
}, {
enableHighAccuracy: true
}, {
enableHighAccuracy: true
});
}
@ -65,24 +68,50 @@ export default class TimeSettings extends PureComponent {
value: 'kelvin'
}
];
return (
<>
<Header title={getMessage('modals.main.settings.sections.weather.title')} setting='weatherEnabled' category='widgets' zoomSetting='zoomWeather' zoomCategory='weather'/>
<TextField label={getMessage('modals.main.settings.sections.weather.location')} value={this.state.location} onChange={(e) => this.changeLocation(e)} placeholder='London' varient='outlined' InputLabelProps={{ shrink: true }} />
<span className='modalLink' onClick={() => this.getAuto()}>{getMessage('modals.main.settings.sections.weather.auto')}</span>
<Radio name='tempformat' title={getMessage('modals.main.settings.sections.weather.temp_format.title')} options={tempFormat} category='weather'/>
<h3>{getMessage('modals.main.settings.sections.weather.extra_info.title')}</h3>
<Header title={getMessage('modals.main.settings.sections.weather.title')} setting='weatherEnabled' category='widgets' zoomSetting='zoomWeather' zoomCategory='weather' switch={true}/>
<SettingsItem title="Widget Type">
<Dropdown label="Type" name="weatherType">
<option value='1'>Basic</option>
<option value='2'>Standard</option>
<option value='3'>Expanded</option>
</Dropdown>
</SettingsItem>
<SettingsItem title={getMessage('modals.main.settings.sections.weather.location')}>
<TextField label={getMessage('modals.main.settings.sections.weather.location')} value={this.state.location} onChange={(e) => this.changeLocation(e)} placeholder='London' varient='outlined' InputLabelProps={{ shrink: true }} />
<span className='link' onClick={() => this.getAuto()}>{getMessage('modals.main.settings.sections.weather.auto')}</span>
</SettingsItem>
<SettingsItem title={getMessage('modals.main.settings.sections.weather.temp_format.title')}>
<Radio name='tempformat' options={tempFormat} category='weather'/>
</SettingsItem>
{ localStorage.getItem('weatherType') > 1 &&
<SettingsItem title="Active bit" subtitle="idk a better word for it sorry">
<Dropdown label="Type" name="weatherActiveBit" category='weather'>
<option value='weatherdescription'>{getMessage('modals.main.settings.sections.weather.extra_info.show_description')} </option>
<option value='cloudiness'>{getMessage('modals.main.settings.sections.weather.extra_info.cloudiness')}</option>
<option value='humidity'>{getMessage('modals.main.settings.sections.weather.extra_info.humidity')}</option>
<option value='visibility'>{getMessage('modals.main.settings.sections.weather.extra_info.visibility')}</option>
<option value='windspeed' onChange={() => this.setState({ windSpeed: (localStorage.getItem('windspeed') !== 'true') })}>{getMessage('modals.main.settings.sections.weather.extra_info.wind_speed')}</option>
<option value='windDirection' disabled={this.state.windSpeed}>{getMessage('modals.main.settings.sections.weather.extra_info.wind_direction')}</option>
<option value='mintemp'>{getMessage('modals.main.settings.sections.weather.extra_info.min_temp')}</option>
<option value='maxtemp'>{getMessage('modals.main.settings.sections.weather.extra_info.max_temp')}</option>
<option value='feelsliketemp'>Feels like temperature</option>
<option value='atmosphericpressure'>{getMessage('modals.main.settings.sections.weather.extra_info.atmospheric_pressure')}</option>
</Dropdown>
</SettingsItem>
}
<Checkbox name='showlocation' text={getMessage('modals.main.settings.sections.weather.extra_info.show_location')} category='weather'/>
<Checkbox name='weatherdescription' text={getMessage('modals.main.settings.sections.weather.extra_info.show_description')} category='weather'/>
<Checkbox name='cloudiness' text={getMessage('modals.main.settings.sections.weather.extra_info.cloudiness')} category='weather'/>
<Checkbox name='humidity' text={getMessage('modals.main.settings.sections.weather.extra_info.humidity')} category='weather'/>
<Checkbox name='visibility' text={getMessage('modals.main.settings.sections.weather.extra_info.visibility')} category='weather'/>
<Checkbox name='windspeed' text={getMessage('modals.main.settings.sections.weather.extra_info.wind_speed')} category='weather' onChange={() => this.setState({ windSpeed: (localStorage.getItem('windspeed') !== 'true') })}/>
<Checkbox name='windDirection' text={getMessage('modals.main.settings.sections.weather.extra_info.wind_direction')} category='weather' disabled={this.state.windSpeed}/>
<Checkbox name='windDirection' text={getMessage('modals.main.settings.sections.weather.extra_info.wind_direction')} category='weather' disabled={this.state.windSpeed}/>
<Checkbox name='mintemp' text={getMessage('modals.main.settings.sections.weather.extra_info.min_temp')} category='weather'/>
<Checkbox name='maxtemp' text={getMessage('modals.main.settings.sections.weather.extra_info.max_temp')} category='weather'/>
<Checkbox name='feelsliketemp' text={'Feels like temperature'} category='weather'/>
<Checkbox name='atmosphericpressure' text={getMessage('modals.main.settings.sections.weather.extra_info.atmospheric_pressure')} category='weather'/>
</>
);

View File

@ -1,49 +1,58 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { MenuItem } from '@mui/material';
import variables from "modules/variables";
import { PureComponent } from "react";
import { MenuItem } from "@mui/material";
import Header from '../../Header';
import Checkbox from '../../Checkbox';
import Dropdown from '../../Dropdown';
import Slider from '../../Slider';
import Radio from '../../Radio';
import Header from "../../Header";
import Checkbox from "../../Checkbox";
import Dropdown from "../../Dropdown";
import Slider from "../../Slider";
import Radio from "../../Radio";
import SettingsItem from "../../SettingsItem";
import ColourSettings from './Colour';
import CustomSettings from './Custom';
import ColourSettings from "./Colour";
import CustomSettings from "./Custom";
import { values } from 'modules/helpers/settings/modals';
import { values } from "modules/helpers/settings/modals";
export default class BackgroundSettings extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
getMessage = (text) =>
variables.language.getMessage(variables.languagecode, text);
constructor() {
super();
this.state = {
backgroundType: localStorage.getItem('backgroundType') || 'api',
backgroundFilter: localStorage.getItem('backgroundFilter') || 'none',
backgroundCategories: [this.getMessage('modals.main.loading')],
backgroundAPI: localStorage.getItem('backgroundAPI') || 'mue',
marketplaceEnabled: localStorage.getItem('photo_packs')
backgroundType: localStorage.getItem("backgroundType") || "api",
backgroundFilter: localStorage.getItem("backgroundFilter") || "none",
backgroundCategories: [this.getMessage("modals.main.loading")],
backgroundAPI: localStorage.getItem("backgroundAPI") || "mue",
marketplaceEnabled: localStorage.getItem("photo_packs"),
};
this.controller = new AbortController();
}
async getBackgroundCategories() {
const data = await (await fetch(variables.constants.API_URL + '/images/categories', { signal: this.controller.signal })).json();
const data = await (
await fetch(variables.constants.API_URL + "/images/categories", {
signal: this.controller.signal,
})
).json();
if (this.controller.signal.aborted === true) {
return;
}
this.setState({
backgroundCategories: data
backgroundCategories: data,
});
}
componentDidMount() {
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
if (
navigator.onLine === false ||
localStorage.getItem("offlineMode") === "true"
) {
return this.setState({
backgroundCategories: [this.getMessage('modals.update.offline.title')]
backgroundCategories: [this.getMessage("modals.update.offline.title")],
});
}
@ -62,117 +71,374 @@ export default class BackgroundSettings extends PureComponent {
const apiOptions = [
{
name: 'Mue',
value: 'mue'
name: "Mue",
value: "mue",
},
{
name: 'Unsplash',
value: 'unsplash'
name: "Unsplash",
value: "unsplash",
},
{
name: 'Pexels',
value: 'pexels'
}
name: "Pexels",
value: "pexels",
},
];
const interval = (
<Dropdown label={getMessage('modals.main.settings.sections.background.interval.title')} name='backgroundchange'>
<option value='refresh'>{getMessage('tabname')}</option>
<option value='60000'>{getMessage('modals.main.settings.sections.background.interval.minute')}</option>
<option value='1800000'>{getMessage('modals.main.settings.sections.background.interval.half_hour')}</option>
<option value='3600000'>{getMessage('modals.main.settings.sections.background.interval.hour')}</option>
<option value='86400000'>{getMessage('modals.main.settings.sections.background.interval.day')}</option>
<option value='604800000'>{getMessage('widgets.date.week')}</option>
<option value='2628000000'>{getMessage('modals.main.settings.sections.background.interval.month')}</option>
</Dropdown>
<SettingsItem>
<Dropdown
label={getMessage(
"modals.main.settings.sections.background.interval.title"
)}
name="backgroundchange"
>
<option value="refresh">{getMessage("tabname")}</option>
<option value="60000">
{getMessage(
"modals.main.settings.sections.background.interval.minute"
)}
</option>
<option value="1800000">
{getMessage(
"modals.main.settings.sections.background.interval.half_hour"
)}
</option>
<option value="3600000">
{getMessage(
"modals.main.settings.sections.background.interval.hour"
)}
</option>
<option value="86400000">
{getMessage(
"modals.main.settings.sections.background.interval.day"
)}
</option>
<option value="604800000">{getMessage("widgets.date.week")}</option>
<option value="2628000000">
{getMessage(
"modals.main.settings.sections.background.interval.month"
)}
</option>
</Dropdown>
</SettingsItem>
);
const APISettings = (
<>
<Radio title={getMessage('modals.main.settings.sections.background.source.api')} options={apiOptions} name='backgroundAPI' category='background' element='#backgroundImage' onChange={(e) => this.setState({ backgroundAPI: e })}/>
{this.state.backgroundCategories[0] === getMessage('modals.main.loading') ?
<>
<Dropdown label={getMessage('modals.main.settings.sections.background.category')} name='apiCategory'>
<MenuItem value='loading' key='loading'>{getMessage('modals.main.loading')}</MenuItem>
<MenuItem value='loading' key='loading'>{getMessage('modals.main.loading')}</MenuItem>
<SettingsItem>
<Radio
title={getMessage(
"modals.main.settings.sections.background.source.api"
)}
options={apiOptions}
name="backgroundAPI"
category="background"
element="#backgroundImage"
onChange={(e) => this.setState({ backgroundAPI: e })}
/>
{this.state.backgroundCategories[0] ===
getMessage("modals.main.loading") ? (
<>
<Dropdown
label={getMessage(
"modals.main.settings.sections.background.category"
)}
name="apiCategory"
>
<MenuItem value="loading" key="loading">
{getMessage("modals.main.loading")}
</MenuItem>
<MenuItem value="loading" key="loading">
{getMessage("modals.main.loading")}
</MenuItem>
</Dropdown>
</>
) : (
<Dropdown
label={getMessage(
"modals.main.settings.sections.background.category"
)}
name="apiCategory"
>
{this.state.backgroundCategories.map((category) => (
<MenuItem value={category} key={category}>
{category.charAt(0).toUpperCase() + category.slice(1)}
</MenuItem>
))}
</Dropdown>
</> :
<Dropdown label={getMessage('modals.main.settings.sections.background.category')} name='apiCategory' >
{this.state.backgroundCategories.map((category) => (
<MenuItem value={category} key={category}>{category.charAt(0).toUpperCase() + category.slice(1)}</MenuItem>
))}
)}
<Dropdown
label={getMessage(
"modals.main.settings.sections.background.source.quality.title"
)}
name="apiQuality"
element=".other"
>
<option value="original">
{getMessage(
"modals.main.settings.sections.background.source.quality.original"
)}
</option>
<option value="high">
{getMessage(
"modals.main.settings.sections.background.source.quality.high"
)}
</option>
<option value="normal">
{getMessage(
"modals.main.settings.sections.background.source.quality.normal"
)}
</option>
<option value="datasaver">
{getMessage(
"modals.main.settings.sections.background.source.quality.datasaver"
)}
</option>
</Dropdown>
}
<Dropdown label={getMessage('modals.main.settings.sections.background.source.quality.title')} name='apiQuality' element='.other'>
<option value='original'>{getMessage('modals.main.settings.sections.background.source.quality.original')}</option>
<option value='high'>{getMessage('modals.main.settings.sections.background.source.quality.high')}</option>
<option value='normal'>{getMessage('modals.main.settings.sections.background.source.quality.normal')}</option>
<option value='datasaver'>{getMessage('modals.main.settings.sections.background.source.quality.datasaver')}</option>
</Dropdown>
</SettingsItem>
{interval}
</>
);
switch (this.state.backgroundType) {
case 'custom': backgroundSettings = <CustomSettings interval={interval}/>; break;
case 'colour': backgroundSettings = <ColourSettings/>; break;
case 'random_colour': backgroundSettings = <></>; break;
case 'random_gradient': backgroundSettings = <></>; break;
default: backgroundSettings = APISettings; break;
case "custom":
backgroundSettings = <CustomSettings interval={interval} />;
break;
case "colour":
backgroundSettings = <ColourSettings />;
break;
case "random_colour":
backgroundSettings = <></>;
break;
case "random_gradient":
backgroundSettings = <></>;
break;
default:
backgroundSettings = APISettings;
break;
}
if (localStorage.getItem('photo_packs') && this.state.backgroundType !== 'custom' && this.state.backgroundType !== 'colour' && this.state.backgroundType !== 'api') {
if (
localStorage.getItem("photo_packs") &&
this.state.backgroundType !== "custom" &&
this.state.backgroundType !== "colour" &&
this.state.backgroundType !== "api"
) {
backgroundSettings = null;
}
const usingImage = this.state.backgroundType !== 'colour' && this.state.backgroundType !== 'random_colour' && this.state.backgroundType !== 'random_gradient';
const usingImage =
this.state.backgroundType !== "colour" &&
this.state.backgroundType !== "random_colour" &&
this.state.backgroundType !== "random_gradient";
return (
<>
<Header title={getMessage('modals.main.settings.sections.background.title')} setting='background' category='background' element='#backgroundImage'/>
<Checkbox name='ddgProxy' text={getMessage('modals.main.settings.sections.background.ddg_image_proxy')} element='.other' disabled={!usingImage} />
<Checkbox name='bgtransition' text={getMessage('modals.main.settings.sections.background.transition')} element='.other' disabled={!usingImage} />
<Checkbox name='photoInformation' text={getMessage('modals.main.settings.sections.background.photo_information')} element='.other' disabled={this.state.backgroundType !== 'api' && this.state.backgroundType !== 'marketplace'} />
<Checkbox name='photoMap' text={getMessage('modals.main.settings.sections.background.show_map')} element='.other' disabled={this.state.backgroundAPI !== 'unsplash'}/>
<h3>{getMessage('modals.main.settings.sections.background.source.title')}</h3>
<Dropdown label={getMessage('modals.main.settings.sections.background.type.title')} name='backgroundType' onChange={(value) => this.setState({ backgroundType: value })} category='background'>
{this.state.marketplaceEnabled ? <option value='photo_pack'>{this.getMessage('modals.main.navbar.marketplace')}</option> : null}
<option value='api'>{getMessage('modals.main.settings.sections.background.type.api')}</option>
<option value='custom'>{getMessage('modals.main.settings.sections.background.type.custom_image')}</option>
<option value='colour'>{getMessage('modals.main.settings.sections.background.type.custom_colour')}</option>
<option value='random_colour'>{getMessage('modals.main.settings.sections.background.type.random_colour')}</option>
<option value='random_gradient'>{getMessage('modals.main.settings.sections.background.type.random_gradient')}</option>
</Dropdown>
<Header
title={getMessage("modals.main.settings.sections.background.title")}
setting="background"
category="background"
element="#backgroundImage"
/>
<SettingsItem title="cheese" subtitle="cheese">
<Checkbox
name="ddgProxy"
text={getMessage(
"modals.main.settings.sections.background.ddg_image_proxy"
)}
element=".other"
disabled={!usingImage}
/>
<Checkbox
name="bgtransition"
text={getMessage(
"modals.main.settings.sections.background.transition"
)}
element=".other"
disabled={!usingImage}
/>
<Checkbox
name="photoInformation"
text={getMessage(
"modals.main.settings.sections.background.photo_information"
)}
element=".other"
disabled={
this.state.backgroundType !== "api" &&
this.state.backgroundType !== "marketplace"
}
/>
<Checkbox
name="photoMap"
text={getMessage(
"modals.main.settings.sections.background.show_map"
)}
element=".other"
disabled={this.state.backgroundAPI !== "unsplash"}
/>
</SettingsItem>
<SettingsItem
title={getMessage(
"modals.main.settings.sections.background.buttons.title"
)}
subtitle="cheese"
>
<Checkbox
name="favouriteEnabled"
text={getMessage(
"modals.main.settings.sections.background.buttons.favourite"
)}
category="navbar"
/>
{this.state.backgroundType === "api" &&
APISettings &&
this.state.backgroundAPI === "mue" ? (
<Checkbox
name="downloadbtn"
text={getMessage(
"modals.main.settings.sections.background.buttons.download"
)}
element=".other"
/>
) : null}
</SettingsItem>
<SettingsItem
title={getMessage(
"modals.main.settings.sections.background.source.title"
)}
subtitle="mucho gracias"
>
<Dropdown
label={getMessage(
"modals.main.settings.sections.background.type.title"
)}
name="backgroundType"
onChange={(value) => this.setState({ backgroundType: value })}
category="background"
>
{this.state.marketplaceEnabled ? (
<option value="photo_pack">
{this.getMessage("modals.main.navbar.marketplace")}
</option>
) : null}
<option value="api">
{getMessage("modals.main.settings.sections.background.type.api")}
</option>
<option value="custom">
{getMessage(
"modals.main.settings.sections.background.type.custom_image"
)}
</option>
<option value="colour">
{getMessage(
"modals.main.settings.sections.background.type.custom_colour"
)}
</option>
<option value="random_colour">
{getMessage(
"modals.main.settings.sections.background.type.random_colour"
)}
</option>
<option value="random_gradient">
{getMessage(
"modals.main.settings.sections.background.type.random_gradient"
)}
</option>
</Dropdown>
</SettingsItem>
{backgroundSettings}
{this.state.backgroundType === 'api' && APISettings && this.state.backgroundAPI === 'mue' ?
<>
<h3>{getMessage('modals.main.settings.sections.background.buttons.title')}</h3>
<Checkbox name='downloadbtn' text={getMessage('modals.main.settings.sections.background.buttons.download')} element='.other' />
</>
: null}
{this.state.backgroundType === 'api' || this.state.backgroundType === 'custom' || this.state.marketplaceEnabled ?
<>
<h3>{getMessage('modals.main.settings.sections.background.effects.title')}</h3>
<Slider title={getMessage('modals.main.settings.sections.background.effects.blur')} name='blur' min='0' max='100' default='0' display='%' marks={values('background')} category='background' element='#backgroundImage' />
<Slider title={getMessage('modals.main.settings.sections.background.effects.brightness')} name='brightness' min='0' max='100' default='90' display='%' marks={values('background')} category='background' element='#backgroundImage' />
<br/>
<Dropdown label={getMessage('modals.main.settings.sections.background.effects.filters.title')} name='backgroundFilter' onChange={(value) => this.setState({ backgroundFilter: value })} category='background' element='#backgroundImage'>
<option value='none'>{getMessage('modals.main.settings.sections.appearance.navbar.refresh_options.none')}</option>
<option value='grayscale'>{getMessage('modals.main.settings.sections.background.effects.filters.grayscale')}</option>
<option value='sepia'>{getMessage('modals.main.settings.sections.background.effects.filters.sepia')}</option>
<option value='invert'>{getMessage('modals.main.settings.sections.background.effects.filters.invert')}</option>
<option value='saturate'>{getMessage('modals.main.settings.sections.background.effects.filters.saturate')}</option>
<option value='contrast'>{getMessage('modals.main.settings.sections.background.effects.filters.contrast')}</option>
{this.state.backgroundType === "api" ||
this.state.backgroundType === "custom" ||
this.state.marketplaceEnabled ? (
<SettingsItem
title={getMessage(
"modals.main.settings.sections.background.effects.title"
)}
subtitle="cheese"
>
<Slider
title={getMessage(
"modals.main.settings.sections.background.effects.blur"
)}
name="blur"
min="0"
max="100"
default="0"
display="%"
marks={values("background")}
category="background"
element="#backgroundImage"
/>
<Slider
title={getMessage(
"modals.main.settings.sections.background.effects.brightness"
)}
name="brightness"
min="0"
max="100"
default="90"
display="%"
marks={values("background")}
category="background"
element="#backgroundImage"
/>
<Dropdown
label={getMessage(
"modals.main.settings.sections.background.effects.filters.title"
)}
name="backgroundFilter"
onChange={(value) => this.setState({ backgroundFilter: value })}
category="background"
element="#backgroundImage"
>
<option value="none">
{getMessage(
"modals.main.settings.sections.appearance.navbar.refresh_options.none"
)}
</option>
<option value="grayscale">
{getMessage(
"modals.main.settings.sections.background.effects.filters.grayscale"
)}
</option>
<option value="sepia">
{getMessage(
"modals.main.settings.sections.background.effects.filters.sepia"
)}
</option>
<option value="invert">
{getMessage(
"modals.main.settings.sections.background.effects.filters.invert"
)}
</option>
<option value="saturate">
{getMessage(
"modals.main.settings.sections.background.effects.filters.saturate"
)}
</option>
<option value="contrast">
{getMessage(
"modals.main.settings.sections.background.effects.filters.contrast"
)}
</option>
</Dropdown>
{this.state.backgroundFilter !== 'none' ?
<Slider title={getMessage('modals.main.settings.sections.background.effects.filters.amount')} name='backgroundFilterAmount' min='0' max='100' default='0' display='%' marks={values('background')} category='background' element='#backgroundImage' />
: null}
</>
: null}
{this.state.backgroundFilter !== "none" ? (
<Slider
title={getMessage(
"modals.main.settings.sections.background.effects.filters.amount"
)}
name="backgroundFilterAmount"
min="0"
max="100"
default="0"
display="%"
marks={values("background")}
category="background"
element="#backgroundImage"
/>
) : null}
</SettingsItem>
) : null}
</>
);
}

View File

@ -1,37 +1,43 @@
import variables from 'modules/variables';
import { PureComponent, Fragment } from 'react';
import { ColorPicker } from 'react-color-gradient-picker';
import { toast } from 'react-toastify';
import variables from "modules/variables";
import { PureComponent, Fragment } from "react";
import { ColorPicker } from "react-color-gradient-picker";
import { toast } from "react-toastify";
import SettingsItem from "../../SettingsItem";
import hexToRgb from 'modules/helpers/background/hexToRgb';
import rgbToHex from 'modules/helpers/background/rgbToHex';
import hexToRgb from "modules/helpers/background/hexToRgb";
import rgbToHex from "modules/helpers/background/rgbToHex";
import 'react-color-gradient-picker/dist/index.css';
import '../../../scss/settings/react-color-picker-gradient-picker-custom-styles.scss';
import "react-color-gradient-picker/dist/index.css";
import "../../../scss/settings/react-color-picker-gradient-picker-custom-styles.scss";
export default class ColourSettings extends PureComponent {
DefaultGradientSettings = { angle: '180', gradient: [{ colour: '#ffb032', stop: 0 }], type: 'linear' };
GradientPickerInitalState = undefined;
DefaultGradientSettings = {
angle: "180",
gradient: [{ colour: "#ffb032", stop: 0 }],
type: "linear",
};
GradientPickerInitialState = undefined;
getMessage = (text) =>
variables.language.getMessage(variables.languagecode, text);
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
constructor() {
super();
this.state = {
gradientSettings: this.DefaultGradientSettings
gradientSettings: this.DefaultGradientSettings,
};
}
resetColour() {
localStorage.setItem('customBackgroundColour', '');
localStorage.setItem("customBackgroundColour", "");
this.setState({
gradientSettings: this.DefaultGradientSettings
gradientSettings: this.DefaultGradientSettings,
});
toast(this.getMessage('toasts.reset'));
toast(this.getMessage("toasts.reset"));
}
initialiseColourPickerState(gradientSettings) {
this.GradientPickerInitalState = {
this.GradientPickerInitialState = {
points: gradientSettings.gradient.map((g) => {
const rgb = hexToRgb(g.colour);
return {
@ -39,19 +45,19 @@ export default class ColourSettings extends PureComponent {
red: rgb.red,
green: rgb.green,
blue: rgb.blue,
alpha: 1
alpha: 1,
};
}),
degree: + gradientSettings.angle,
type: gradientSettings.type
degree: +gradientSettings.angle,
type: gradientSettings.type,
};
}
componentDidMount() {
const hex = localStorage.getItem('customBackgroundColour');
const hex = localStorage.getItem("customBackgroundColour");
let gradientSettings = undefined;
if (hex !== '') {
if (hex !== "") {
try {
gradientSettings = JSON.parse(hex);
} catch (e) {
@ -64,58 +70,91 @@ export default class ColourSettings extends PureComponent {
}
this.setState({
gradientSettings
gradientSettings,
});
}
componentDidUpdate() {
localStorage.setItem('customBackgroundColour', this.currentGradientSettings());
localStorage.setItem(
"customBackgroundColour",
this.currentGradientSettings()
);
}
onGradientChange = (event, index) => {
const newValue = event.target.value;
const name = event.target.name;
this.setState((s) => {
const newState = {
return (newState = {
gradientSettings: {
...s.gradientSettings,
gradient: s.gradientSettings.gradient.map((g, i) => i === index ? { ...g, [name]: newValue } : g)
}
};
return newState;
gradient: s.gradientSettings.gradient.map((g, i) =>
i === index ? { ...g, [name]: newValue } : g
),
},
});
});
this.showReminder();
}
};
addColour = () => {
this.setState((s) => {
const [lastGradient, ...initGradients] = s.gradientSettings.gradient.reverse();
const newState = {
const [lastGradient, ...initGradients] =
s.gradientSettings.gradient.reverse();
return {
gradientSettings: {
...s.gradientSettings,
gradient: [...initGradients, lastGradient, { colour: localStorage.getItem('theme') === 'dark' ? '#000000' : '#ffffff', stop: 100 }].sort((a, b) => (a.stop > b.stop) ? 1 : -1)
}
gradient: [
...initGradients,
lastGradient,
{
colour:
localStorage.getItem("theme") === "dark"
? "#000000"
: "#ffffff",
stop: 100,
},
].sort((a, b) => (a.stop > b.stop ? 1 : -1)),
},
};
return newState;
});
variables.stats.postEvent('setting', 'Changed backgroundtype from colour to gradient');
}
variables.stats.postEvent(
"setting",
"Changed backgroundType from colour to gradient"
);
};
currentGradientSettings = () => {
if (typeof this.state.gradientSettings === 'object' && this.state.gradientSettings.gradient.every(g => g.colour !== this.getMessage('modals.main.settings.sections.background.source.disabled'))) {
const clampNumber = (num, a, b) => Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
if (
typeof this.state.gradientSettings === "object" &&
this.state.gradientSettings.gradient.every(
(g) =>
g.colour !==
this.getMessage(
"modals.main.settings.sections.background.source.disabled"
)
)
) {
const clampNumber = (num, a, b) =>
Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
return JSON.stringify({
...this.state.gradientSettings,
gradient: [...this.state.gradientSettings.gradient.map(g => { return { ...g, stop: clampNumber(+g.stop, 0, 100) } })].sort((a, b) => (a.stop > b.stop) ? 1 : -1)
gradient: [
...this.state.gradientSettings.gradient.map((g) => {
return { ...g, stop: clampNumber(+g.stop, 0, 100) };
}),
].sort((a, b) => (a.stop > b.stop ? 1 : -1)),
});
}
return this.getMessage('modals.main.settings.sections.background.source.disabled');
}
return this.getMessage(
"modals.main.settings.sections.background.source.disabled"
);
};
onColourPickerChange = (attrs, name) => {
if (process.env.NODE_ENV === 'development') {
if (process.env.NODE_ENV === "development") {
console.log(attrs, name);
}
@ -124,49 +163,63 @@ export default class ColourSettings extends PureComponent {
angle: attrs.degree,
gradient: attrs.points.map((p) => {
return {
colour: '#' + rgbToHex(p.red, p.green, p.blue),
stop: p.left
}}),
type: attrs.type
}
colour: "#" + rgbToHex(p.red, p.green, p.blue),
stop: p.left,
};
}),
type: attrs.type,
},
});
this.showReminder();
};
showReminder() {
const reminderInfo = document.querySelector('.reminder-info');
if (reminderInfo.style.display !== 'block') {
reminderInfo.style.display = 'block';
localStorage.setItem('showReminder', true);
const reminderInfo = document.querySelector(".reminder-info");
if (reminderInfo.style.display !== "block") {
reminderInfo.style.display = "block";
localStorage.setItem("showReminder", true);
}
}
render() {
let colourSettings = null;
if (typeof this.state.gradientSettings === 'object') {
const gradientHasMoreThanOneColour = this.state.gradientSettings.gradient.length > 1;
if (typeof this.state.gradientSettings === "object") {
const gradientHasMoreThanOneColour =
this.state.gradientSettings.gradient.length > 1;
let gradientInputs;
if (gradientHasMoreThanOneColour) {
if (this.GradientPickerInitalState === undefined) {
if (this.GradientPickerInitialState === undefined) {
this.initialiseColourPickerState(this.state.gradientSettings);
}
gradientInputs = (
<ColorPicker
onStartChange={(colour) => this.onColourPickerChange(colour, 'start')}
onChange={(colour) => this.onColourPickerChange(colour, 'change')}
onEndChange={(colour) => this.onColourPickerChange(colour, 'end')}
gradient={this.GradientPickerInitalState}
isGradient/>
onStartChange={(colour) =>
this.onColourPickerChange(colour, "start")
}
onChange={(colour) => this.onColourPickerChange(colour, "change")}
onEndChange={(colour) => this.onColourPickerChange(colour, "end")}
gradient={this.GradientPickerInitialState}
isGradient
/>
);
} else {
gradientInputs = this.state.gradientSettings.gradient.map((g, i) => {
return (
<Fragment key={i}>
<input id={'colour_' + i} type='color' name='colour' className='colour' onChange={(event) => this.onGradientChange(event, i)} value={g.colour}></input>
<label htmlFor={'colour_' + i} className='customBackgroundHex'>{g.colour}</label>
<input
id={"colour_" + i}
type="color"
name="colour"
className="colour"
onChange={(event) => this.onGradientChange(event, i)}
value={g.colour}
></input>
<label htmlFor={"colour_" + i} className="customBackgroundHex">
{g.colour}
</label>
</Fragment>
);
});
@ -174,16 +227,32 @@ export default class ColourSettings extends PureComponent {
colourSettings = (
<>
{gradientInputs}
{!gradientHasMoreThanOneColour ? (<><br/><br/><button type='button' className='add' onClick={this.addColour}>{this.getMessage('modals.main.settings.sections.background.source.add_colour')}</button></>) : null}
<div className="colourInput">{gradientInputs}</div>
{!gradientHasMoreThanOneColour ? (
<>
<button type="button" className="add" onClick={this.addColour}>
{this.getMessage(
"modals.main.settings.sections.background.source.add_colour"
)}
</button>
</>
) : null}
</>
);
}
return (
<>
<p>{this.getMessage('modals.main.settings.sections.background.source.custom_colour')} <span className='modalLink' onClick={() => this.resetColour()}>{this.getMessage('modals.main.settings.buttons.reset')}</span></p>
{colourSettings}
<SettingsItem
title={this.getMessage(
"modals.main.settings.sections.background.source.custom_colour"
)}
>
<span className="link" onClick={() => this.resetColour()}>
{this.getMessage("modals.main.settings.buttons.reset")}
</span>
{colourSettings}
</SettingsItem>
</>
);
}

View File

@ -1,141 +1,228 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import { MdCancel, MdAddLink, MdAddPhotoAlternate, MdPersonalVideo } from 'react-icons/md';
import EventBus from 'modules/helpers/eventbus';
import variables from "modules/variables";
import { PureComponent } from "react";
import { toast } from "react-toastify";
import {
MdCancel,
MdAddLink,
MdAddPhotoAlternate,
MdPersonalVideo,
} from "react-icons/md";
import EventBus from "modules/helpers/eventbus";
import Checkbox from '../../Checkbox';
import FileUpload from '../../FileUpload';
import Checkbox from "../../Checkbox";
import FileUpload from "../../FileUpload";
import SettingsItem from "../../SettingsItem";
import Modal from 'react-modal';
import Modal from "react-modal";
import CustomURLModal from './CustomURLModal';
import CustomURLModal from "./CustomURLModal";
export default class CustomSettings extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
getMessage = (text) =>
variables.language.getMessage(variables.languagecode, text);
constructor() {
super();
this.state = {
customBackground: this.getCustom(),
customURLModal: false
customURLModal: false,
};
}
resetCustom = () => {
localStorage.setItem('customBackground', '[]');
localStorage.setItem("customBackground", "[]");
this.setState({
customBackground: []
customBackground: [],
});
toast(this.getMessage('toasts.reset'));
EventBus.dispatch('refresh', 'background');
}
toast(this.getMessage("toasts.reset"));
EventBus.dispatch("refresh", "background");
};
customBackground(e, text, index) {
const result = (text === true) ? e.target.value : e.target.result;
const result = text === true ? e.target.value : e.target.result;
const customBackground = this.state.customBackground;
customBackground[index] = result;
this.setState({
customBackground
customBackground,
});
this.forceUpdate();
localStorage.setItem('customBackground', JSON.stringify(customBackground));
document.querySelector('.reminder-info').style.display = 'block';
localStorage.setItem('showReminder', true);
localStorage.setItem("customBackground", JSON.stringify(customBackground));
document.querySelector(".reminder-info").style.display = "flex";
localStorage.setItem("showReminder", true);
}
modifyCustomBackground(type, index) {
const customBackground = this.state.customBackground;
if (type === 'add') {
customBackground.push('');
if (type === "add") {
customBackground.push("");
} else {
customBackground.splice(index, 1);
}
this.setState({
customBackground
customBackground,
});
this.forceUpdate();
localStorage.setItem('customBackground', JSON.stringify(customBackground));
document.querySelector('.reminder-info').style.display = 'block';
localStorage.setItem('showReminder', true);
localStorage.setItem("customBackground", JSON.stringify(customBackground));
document.querySelector(".reminder-info").style.display = "flex";
localStorage.setItem("showReminder", true);
}
videoCheck(url) {
return url.startsWith('data:video/') || url.endsWith('.mp4') || url.endsWith('.webm') || url.endsWith('.ogg');
return (
url.startsWith("data:video/") ||
url.endsWith(".mp4") ||
url.endsWith(".webm") ||
url.endsWith(".ogg")
);
}
videoCustomSettings = () => {
const hasVideo = this.state.customBackground.filter(bg => this.videoCheck(bg));
if (hasVideo.length > 0) {
videoCustomSettings = () => {
const hasVideo = this.state.customBackground.filter((bg) =>
this.videoCheck(bg)
);
if (hasVideo.length > 0) {
return (
<>
<Checkbox name='backgroundVideoLoop' text={this.getMessage('modals.main.settings.sections.background.source.loop_video')}/>
<Checkbox name='backgroundVideoMute' text={this.getMessage('modals.main.settings.sections.background.source.mute_video')}/>
<Checkbox
name="backgroundVideoLoop"
text={this.getMessage(
"modals.main.settings.sections.background.source.loop_video"
)}
/>
<Checkbox
name="backgroundVideoMute"
text={this.getMessage(
"modals.main.settings.sections.background.source.mute_video"
)}
/>
</>
);
} else {
return null;
}
}
};
getCustom() {
let data;
try {
data = JSON.parse(localStorage.getItem('customBackground'));
data = JSON.parse(localStorage.getItem("customBackground"));
} catch (e) {
data = [localStorage.getItem('customBackground')];
data = [localStorage.getItem("customBackground")];
}
return data;
}
uploadCustomBackground() {
document.getElementById('bg-input').setAttribute('index', this.state.customBackground.length);
document.getElementById('bg-input').click();
document
.getElementById("bg-input")
.setAttribute("index", this.state.customBackground.length);
document.getElementById("bg-input").click();
// to fix loadFunction
this.setState({
currentBackgroundIndex: this.state.customBackground.length
currentBackgroundIndex: this.state.customBackground.length,
});
}
addCustomURL(e) {
this.setState({
customURLModal: false,
currentBackgroundIndex: this.state.customBackground.length
currentBackgroundIndex: this.state.customBackground.length,
});
this.customBackground({ target: { value: e }}, true, this.state.customBackground.length);
this.customBackground(
{ target: { value: e } },
true,
this.state.customBackground.length
);
}
render() {
return (
<ul>
<p>{this.getMessage('modals.main.settings.sections.background.source.custom_background')} <span className='modalLink' onClick={this.resetCustom}>{this.getMessage('modals.main.settings.buttons.reset')}</span></p>
<div className='data-buttons-row'>
<button onClick={() => this.uploadCustomBackground()}>{this.getMessage('modals.main.settings.sections.background.source.add_background')} <MdAddPhotoAlternate/></button>
<button onClick={() => this.setState({ customURLModal: true })}>{this.getMessage('modals.main.settings.sections.background.source.add_url')} <MdAddLink/></button>
</div>
<div className='images-row'>
{this.state.customBackground.map((url, index) => (
<div style={{ backgroundImage: `url(${!this.videoCheck(url) ? this.state.customBackground[index] : ''})` }} key={index}>
{this.videoCheck(url) ? <MdPersonalVideo className='customvideoicon'/> : null}
{this.state.customBackground.length > 0 ? <button className='cleanButton' onClick={() => this.modifyCustomBackground('remove', index)}>
<MdCancel/>
</button> : null}
</div>
))}
</div>
<FileUpload id='bg-input' accept='image/jpeg, image/png, image/webp, image/webm, image/gif, video/mp4, video/webm, video/ogg' loadFunction={(e) => this.customBackground(e, false, this.state.currentBackgroundIndex)} />
<>
{this.props.interval}
<div className="settingsRow" style={{ alignItems: "flex-start" }}>
<div className="content">
<div className="images-row">
{this.state.customBackground.map((url, index) => (
<div key={index}>
<img
alt={"Custom background " + (index || 0)}
src={`${
!this.videoCheck(url)
? this.state.customBackground[index]
: ""
}`}
/>
{this.videoCheck(url) ? (
<MdPersonalVideo className="customvideoicon" />
) : null}
{this.state.customBackground.length > 0 ? (
<button
className="iconButton"
onClick={() =>
this.modifyCustomBackground("remove", index)
}
>
<MdCancel />
</button>
) : null}
</div>
))}
</div>
</div>
<div className="action">
{/*<button onClick={() => this.uploadCustomBackground()}>{this.getMessage('modals.main.settings.sections.background.source.add_background')} <MdAddPhotoAlternate/></button>*/}
<div className="dropzone">
<MdAddPhotoAlternate />
<span className="title">Drop to upload</span>
<span className="subtitle">
Available formats, jpeg, png, webp, webm, gif, mp4, webm, ogg
</span>
<button onClick={() => this.uploadCustomBackground()}>
Or Select
</button>
</div>
<button onClick={() => this.setState({ customURLModal: true })}>
{this.getMessage(
"modals.main.settings.sections.background.source.add_url"
)}{" "}
<MdAddLink />
</button>
{/*<span className='subtitle'>
{this.getMessage('modals.main.settings.sections.background.source.custom_background')}{' '}
<span className="link" onClick={this.resetCustom}>
{this.getMessage('modals.main.settings.buttons.reset')}
</span>
</span>*/}
</div>
</div>
<FileUpload
id="bg-input"
accept="image/jpeg, image/png, image/webp, image/webm, image/gif, video/mp4, video/webm, video/ogg"
loadFunction={(e) =>
this.customBackground(e, false, this.state.currentBackgroundIndex)
}
/>
{this.videoCustomSettings()}
<Modal closeTimeoutMS={100} onRequestClose={() => this.setState({ customURLModal: false })} isOpen={this.state.customURLModal} className='Modal resetmodal mainModal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}>
<CustomURLModal modalClose={(e) => this.addCustomURL(e)} modalCloseOnly={() => this.setState({ customURLModal: false })} />
<Modal
closeTimeoutMS={100}
onRequestClose={() => this.setState({ customURLModal: false })}
isOpen={this.state.customURLModal}
className="Modal resetmodal mainModal"
overlayClassName="Overlay resetoverlay"
ariaHideApp={false}
>
<CustomURLModal
modalClose={(e) => this.addCustomURL(e)}
modalCloseOnly={() => this.setState({ customURLModal: false })}
/>
</Modal>
</ul>
</>
);
}
}

View File

@ -1,19 +1,34 @@
import variables from 'modules/variables';
import { useState } from 'react';
import { MdAdd } from 'react-icons/md';
import { TextField } from '@mui/material';
import variables from "modules/variables";
import { useState } from "react";
import { MdAdd } from "react-icons/md";
import { TextField } from "@mui/material";
export default function CustomURLModal({ modalClose, modalCloseOnly }) {
const [url, setURL] = useState();
return (
<>
<span className='closeModal' onClick={modalCloseOnly}>&times;</span>
<h1>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.background.source.add_url')}</h1>
<TextField value={url} onChange={(e) => setURL(e.target.value)} varient='outlined'/>
<div className='resetfooter'>
<button className='round import' style={{ marginLeft: '5px' }} onClick={() => modalClose(url)}>
<MdAdd/>
<span className="closeModal" onClick={modalCloseOnly}>
&times;
</span>
<h1>
{variables.language.getMessage(
variables.languagecode,
"modals.main.settings.sections.background.source.add_url"
)}
</h1>
<TextField
value={url}
onChange={(e) => setURL(e.target.value)}
varient="outlined"
/>
<div className="resetFooter">
<button
className="round import"
style={{ marginLeft: "5px" }}
onClick={() => modalClose(url)}
>
<MdAdd />
</button>
</div>
</>

View File

@ -5,11 +5,11 @@ import Added from '../marketplace/sections/Added';
import Sideload from '../marketplace/sections/Sideload';
import Create from '../marketplace/sections/Create';
export default function Addons() {
export default function Addons(props) {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
return (
<Tabs>
<Tabs changeTab={(type) => props.changeTab(type)} current='addons'>
<div label={getMessage('modals.main.addons.added')} name='added'><Added/></div>
<div label={getMessage('modals.main.addons.sideload.title')} name='sideload'><Sideload/></div>
<div label={getMessage('modals.main.addons.create.title')} name='create'><Create/></div>

View File

@ -3,11 +3,12 @@ import Tabs from './backend/Tabs';
import MarketplaceTab from '../marketplace/sections/Marketplace';
export default function Marketplace() {
export default function Marketplace(props) {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
return (
<Tabs>
<Tabs changeTab={(type) => props.changeTab(type)} current='marketplace'>
<div label='All' name='all'><MarketplaceTab type='all'/></div>
<div label={getMessage('modals.main.marketplace.photo_packs')} name='photo_packs'><MarketplaceTab type='photo_packs'/></div>
<div label={getMessage('modals.main.marketplace.quote_packs')} name='quote_packs'><MarketplaceTab type='quote_packs'/></div>
<div label={getMessage('modals.main.marketplace.preset_settings')} name='preset_settings'><MarketplaceTab type='preset_settings'/></div>

View File

@ -7,6 +7,7 @@ import Time from '../settings/sections/Time';
import QuickLinks from '../settings/sections/QuickLinks';
import Quote from '../settings/sections/Quote';
import Date from '../settings/sections/Date';
import Reminder from '../settings/sections/Reminder';
import Message from '../settings/sections/Message';
import Background from '../settings/sections/background/Background';
import Search from '../settings/sections/Search';
@ -21,22 +22,23 @@ import Experimental from '../settings/sections/Experimental';
import Changelog from '../settings/sections/Changelog';
import About from '../settings/sections/About';
export default function Settings() {
export default function Settings(props) {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
return (
<>
<Tabs>
<Tabs changeTab={(type) => props.changeTab(type)} current='settings'>
<div label={getMessage('modals.main.settings.sections.appearance.navbar.title')} name='navbar'><Navbar/></div>
<div label={getMessage('modals.main.settings.sections.greeting.title')} name='greeting'><Greeting/></div>
<div label={getMessage('modals.main.settings.sections.time.title')} name='time'><Time/></div>
<div label={getMessage('modals.main.settings.sections.quicklinks.title')} name='quicklinks'><QuickLinks/></div>
<div label={getMessage('modals.main.settings.sections.quote.title')} name='quote'><Quote/></div>
<div label={getMessage('modals.main.settings.sections.date.title')} name='date'><Date/></div>
<div label='Reminder' name='reminder'><Reminder/></div>
<div label={getMessage('modals.main.settings.sections.message.title')} name='message'><Message/></div>
<div label={getMessage('modals.main.settings.sections.background.title')} name='background'><Background/></div>
<div label={getMessage('modals.main.settings.sections.search.title')} name='search'><Search/></div>
<div label={getMessage('modals.main.settings.sections.weather.title')} name='weather'><Weather/></div>
<div label={getMessage('modals.main.settings.sections.order.title')} name='order'><Order/></div>
<div label={getMessage('modals.main.settings.sections.appearance.title')} name='appearance'><Appearance/></div>
<div label={getMessage('modals.main.settings.sections.language.title')} name='language'><Language/></div>
@ -47,6 +49,5 @@ export default function Settings() {
<div label={getMessage('modals.main.settings.sections.changelog.title')} name='changelog'><Changelog/></div>
<div label={getMessage('modals.main.settings.sections.about.title')} name='about'><About/></div>
</Tabs>
</>
);
}

View File

@ -29,7 +29,6 @@ import {
MdAddCircleOutline as Create
} from 'react-icons/md';
function Tab({ label, currentTab, onClick, navbarTab }) {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
@ -62,6 +61,7 @@ function Tab({ label, currentTab, onClick, navbarTab }) {
case getMessage('modals.main.settings.sections.search.title'): icon = <MdSearch/>; break;
case getMessage('modals.main.settings.sections.weather.title'): icon = <Weather/>; divider = true; break;
case getMessage('modals.main.settings.sections.order.title'): icon = <Order/>; break;
case 'Reminder': icon = <Reminder/>; break;
case getMessage('modals.main.settings.sections.appearance.title'): icon = <Appearance/>; break;
case getMessage('modals.main.settings.sections.language.title'): icon = <Language/>; divider = true; break;
@ -76,6 +76,7 @@ function Tab({ label, currentTab, onClick, navbarTab }) {
case getMessage('modals.main.addons.sideload.title'): icon = <Sideload/>; break;
case getMessage('modals.main.addons.create.title'): icon = <Create/>; break;
case 'All': icon = <Addons/>; divider = true; break;
case getMessage('modals.main.marketplace.photo_packs'): icon = <Background/>; break;
case getMessage('modals.main.marketplace.quote_packs'): icon = <Quote/>; break;
case getMessage('modals.main.marketplace.preset_settings'): icon = <Advanced/>; break;
@ -91,9 +92,9 @@ function Tab({ label, currentTab, onClick, navbarTab }) {
return (
<>
<li className={className} onClick={() => onClick(label)}>
<button className={className} onClick={() => onClick(label)}>
{icon} <span>{label}</span>
</li>
</button>
{(divider === true) ? <hr/> : null}
</>
);

View File

@ -1,8 +1,12 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import Tab from './Tab';
import ErrorBoundary from '../../../ErrorBoundary';
import variables from "modules/variables";
import { PureComponent } from "react";
import {
MdSettings,
MdOutlineShoppingBasket,
MdOutlineExtension,
} from "react-icons/md";
import Tab from "./Tab";
import ErrorBoundary from "../../../ErrorBoundary";
export default class Tabs extends PureComponent {
constructor(props) {
@ -10,36 +14,67 @@ export default class Tabs extends PureComponent {
this.state = {
currentTab: this.props.children[0].props.label,
currentName: this.props.children[0].props.name
currentName: this.props.children[0].props.name,
};
}
onClick = (tab, name) => {
if (name !== this.state.currentName) {
variables.stats.postEvent('tab', `Opened ${name}`);
variables.stats.postEvent("tab", `Opened ${name}`);
}
this.setState({
this.setState({
currentTab: tab,
currentName: name
currentName: name,
});
};
render() {
let className = 'sidebar';
let tabClass = 'tab-content';
let optionsText = (<h1>{variables.language.getMessage(variables.languagecode, 'modals.main.title')}</h1>);
const reminderInfo = (
<div className="reminder-info" style={{ display: "none" }}>
<span className="title">
{variables.language.getMessage(
variables.languagecode,
"modals.main.settings.reminder.title"
)}
</span>
<span className="subtitle">
{variables.language.getMessage(
variables.languagecode,
"modals.main.settings.reminder.message"
)}
</span>
<button onClick={() => window.location.reload()}>
{variables.language.getMessage(
variables.languagecode,
"modals.main.error_boundary.refresh"
)}
</button>
</div>
);
if (this.props.navbar) {
className = 'modalNavbar';
tabClass = '';
optionsText = '';
let settingsActive = "";
let addonsActive = "";
let marketplaceActive = "";
switch (this.props.current) {
case "settings":
settingsActive = "navbar-item-active";
break;
case "addons":
addonsActive = "navbar-item-active";
break;
case "marketplace":
marketplaceActive = "navbar-item-active";
break;
default:
break;
}
return (
<>
<ul className={className}>
{optionsText}
<div style={{ display: "flex", width: "100%" }}>
<ul className="sidebar">
{reminderInfo}
{this.props.children.map((tab, index) => (
<Tab
currentTab={this.state.currentTab}
@ -50,8 +85,31 @@ export default class Tabs extends PureComponent {
/>
))}
</ul>
<div className={tabClass}>
<div className="tab-content" style={{ width: "100%" }}>
<ErrorBoundary>
<div className="modalNavbar">
<button
className={"navbar-item" + settingsActive}
onClick={() => this.props.changeTab("settings")}
>
<MdSettings />
<span>Settings</span>
</button>
<button
className={"navbar-item" + addonsActive}
onClick={() => this.props.changeTab("addons")}
>
<MdOutlineExtension />
<span>Add-ons</span>
</button>
<button
className={"navbar-item" + marketplaceActive}
onClick={() => this.props.changeTab("marketplace")}
>
<MdOutlineShoppingBasket />
<span>Marketplace</span>
</button>
</div>
{this.props.children.map((tab) => {
if (tab.props.label !== this.state.currentTab) {
return undefined;
@ -61,7 +119,7 @@ export default class Tabs extends PureComponent {
})}
</ErrorBoundary>
</div>
</>
</div>
);
}
}

View File

@ -1,6 +1,6 @@
export default function ProgressBar({ count, currentTab, switchTab }) {
return (
<div className='progressbar'>
<div className="progressbar">
{count.map((num) => {
let className = 'step';
@ -9,7 +9,7 @@ export default function ProgressBar({ count, currentTab, switchTab }) {
className = 'step active';
}
return <div className={className} key={index} onClick={() => switchTab(index)}></div>;
return <div className={className} key={index} onClick={() => switchTab(index)} />;
})}
</div>
);

View File

@ -17,15 +17,15 @@ export default class WelcomeModal extends PureComponent {
image: './././icons/undraw_celebration.svg',
currentTab: 0,
finalTab: 4,
buttonText: this.getMessage('modals.welcome.buttons.next')
buttonText: this.getMessage('modals.welcome.buttons.next'),
};
this.images = [
'./././icons/undraw_celebration.svg',
'./././icons/undraw_around_the_world_modified.svg',
'./././icons/undraw_add_files_modified.svg',
'./././icons/undraw_celebration.svg',
'./././icons/undraw_around_the_world_modified.svg',
'./././icons/undraw_add_files_modified.svg',
'./././icons/undraw_dark_mode.svg',
'./././icons/undraw_private_data_modified.svg',
'./././icons/undraw_upgrade_modified.svg'
'./././icons/undraw_private_data_modified.svg',
'./././icons/undraw_upgrade_modified.svg',
];
}
@ -37,7 +37,7 @@ export default class WelcomeModal extends PureComponent {
return this.setState({
currentTab: this.state.currentTab - 1,
image: this.images[this.state.currentTab - 1],
buttonText: this.getMessage('modals.welcome.buttons.next')
buttonText: this.getMessage('modals.welcome.buttons.next'),
});
}
@ -48,7 +48,10 @@ export default class WelcomeModal extends PureComponent {
this.setState({
currentTab: this.state.currentTab + 1,
image: this.images[this.state.currentTab + 1],
buttonText: (this.state.currentTab !== this.state.finalTab) ? this.getMessage('modals.welcome.buttons.next') : this.getMessage('modals.main.addons.create.finish.title')
buttonText:
this.state.currentTab !== this.state.finalTab
? this.getMessage('modals.welcome.buttons.next')
: this.getMessage('modals.main.addons.create.finish.title'),
});
}
@ -57,7 +60,10 @@ export default class WelcomeModal extends PureComponent {
this.setState({
currentTab: tab,
image: this.images[tab],
buttonText: (tab !== this.state.finalTab + 1) ? this.getMessage('modals.welcome.buttons.next') : this.getMessage('modals.main.addons.create.finish.title')
buttonText:
tab !== this.state.finalTab + 1
? this.getMessage('modals.welcome.buttons.next')
: this.getMessage('modals.main.addons.create.finish.title'),
});
localStorage.setItem('bgtransition', true);
@ -70,7 +76,10 @@ export default class WelcomeModal extends PureComponent {
this.setState({
currentTab: Number(welcomeTab),
image: this.images[Number(welcomeTab)],
buttonText: (Number(welcomeTab) !== this.state.finalTab + 1) ? this.getMessage('modals.welcome.buttons.next') : this.getMessage('modals.main.addons.create.finish.title')
buttonText:
Number(welcomeTab) !== this.state.finalTab + 1
? this.getMessage('modals.welcome.buttons.next')
: this.getMessage('modals.main.addons.create.finish.title'),
});
}
@ -89,19 +98,49 @@ export default class WelcomeModal extends PureComponent {
render() {
return (
<div className='welcomeContent'>
<div className="welcomeContent">
<section>
<img className='showcaseimg' alt='sidebar icon' draggable={false} src={this.state.image} />
<ProgressBar count={this.images} currentTab={this.state.currentTab} switchTab={(tab) => this.switchTab(tab)}/>
<img
className="showcaseimg"
alt="sidebar icon"
draggable={false}
src={this.state.image}
/>
<ProgressBar
count={this.images}
currentTab={this.state.currentTab}
switchTab={(tab) => this.switchTab(tab)}
/>
</section>
<section>
<div className='content'>
<WelcomeSections currentTab={this.state.currentTab} switchTab={(tab) => this.switchTab(tab)}/>
<div className="content">
<WelcomeSections
currentTab={this.state.currentTab}
switchTab={(tab) => this.switchTab(tab)}
/>
</div>
<div className='buttons'>
{(this.state.currentTab === 0) ? <button className='close' style={{ marginRight: '20px' }} onClick={() => this.props.modalSkip()}>{this.getMessage('modals.welcome.buttons.preview')}</button> : null}
{(this.state.currentTab !== 0) ? <button className='close' style={{ marginRight: '20px' }} onClick={() => this.changeTab(true)}>{this.getMessage('modals.welcome.buttons.previous')}</button> : null}
<button className='close' onClick={() => this.changeTab()}>{this.state.buttonText}</button>
<div className="buttons">
{this.state.currentTab === 0 ? (
<button
className="close"
style={{ marginRight: '20px' }}
onClick={() => this.props.modalSkip()}
>
{this.getMessage('modals.welcome.buttons.preview')}
</button>
) : null}
{this.state.currentTab !== 0 ? (
<button
className="close"
style={{ marginRight: '20px' }}
onClick={() => this.changeTab(true)}
>
{this.getMessage('modals.welcome.buttons.previous')}
</button>
) : null}
<button className="close" onClick={() => this.changeTab()}>
{this.state.buttonText}
</button>
</div>
</section>
</div>

View File

@ -1,31 +1,37 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { MdCloudUpload, MdAutoAwesome, MdLightMode, MdDarkMode } from 'react-icons/md';
import variables from "modules/variables";
import { PureComponent } from "react";
import {
MdCloudUpload,
MdAutoAwesome,
MdLightMode,
MdDarkMode,
} from "react-icons/md";
import Radio from '../main/settings/Radio';
import Checkbox from '../main/settings/Checkbox';
import FileUpload from '../main/settings/FileUpload';
import Radio from "../main/settings/Radio";
import Checkbox from "../main/settings/Checkbox";
import FileUpload from "../main/settings/FileUpload";
import { loadSettings } from 'modules/helpers/settings';
import { importSettings } from 'modules/helpers/settings/modals';
import { loadSettings } from "modules/helpers/settings";
import { importSettings } from "modules/helpers/settings/modals";
const languages = require('modules/languages.json');
const default_settings = require('modules/default_settings.json');
const languages = require("modules/languages.json");
const default_settings = require("modules/default_settings.json");
export default class WelcomeSections extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
getMessage = (text) =>
variables.language.getMessage(variables.languagecode, text);
constructor() {
super();
this.state = {
// themes
autoClass: 'toggle auto active',
lightClass: 'toggle lightTheme',
darkClass: 'toggle darkTheme',
autoClass: "toggle auto active",
lightClass: "toggle lightTheme",
darkClass: "toggle darkTheme",
// welcome
welcomeImage: 0,
// final
importedSettings: []
importedSettings: [],
};
this.changeWelcomeImg = this.changeWelcomeImg.bind(this);
this.welcomeImages = 4;
@ -33,17 +39,22 @@ export default class WelcomeSections extends PureComponent {
changeTheme(type) {
this.setState({
autoClass: (type === 'auto') ? 'toggle auto active' : 'toggle auto',
lightClass: (type === 'light') ? 'toggle lightTheme active' : 'toggle lightTheme',
darkClass: (type === 'dark') ? 'toggle darkTheme active': 'toggle darkTheme'
autoClass: type === "auto" ? "toggle auto active" : "toggle auto",
lightClass:
type === "light" ? "toggle lightTheme active" : "toggle lightTheme",
darkClass:
type === "dark" ? "toggle darkTheme active" : "toggle darkTheme",
});
localStorage.setItem('theme', type);
localStorage.setItem("theme", type);
loadSettings(true);
}
getSetting(name) {
const value = localStorage.getItem(name).replace('false', 'Off').replace('true', 'On');
const value = localStorage
.getItem(name)
.replace("false", "Off")
.replace("true", "On");
return value.charAt(0).toUpperCase() + value.slice(1);
}
@ -54,7 +65,13 @@ export default class WelcomeSections extends PureComponent {
const data = JSON.parse(e.target.result);
Object.keys(data).forEach((setting) => {
// language and theme already shown, the others are only used internally
if (setting === 'language' || setting === 'theme'|| setting === 'firstRun' || setting === 'showWelcome' || setting === 'showReminder') {
if (
setting === "language" ||
setting === "theme" ||
setting === "firstRun" ||
setting === "showWelcome" ||
setting === "showReminder"
) {
return;
}
@ -67,12 +84,12 @@ export default class WelcomeSections extends PureComponent {
settings.push({
name: setting,
value: data[setting]
value: data[setting],
});
});
this.setState({
importedSettings: settings
importedSettings: settings,
});
this.props.switchTab(5);
@ -82,7 +99,7 @@ export default class WelcomeSections extends PureComponent {
let welcomeImage = this.state.welcomeImage;
this.setState({
welcomeImage: ++welcomeImage % this.welcomeImages
welcomeImage: ++welcomeImage % this.welcomeImages.length,
});
this.timeout = setTimeout(this.changeWelcomeImg, 3000);
@ -111,104 +128,246 @@ export default class WelcomeSections extends PureComponent {
}
}
render() {
render() {
const intro = (
<>
<h1>{this.getMessage('modals.welcome.sections.intro.title')}</h1>
<p>{this.getMessage('modals.welcome.sections.intro.description')}</p>
<h3 className='quicktip'>#shareyourmue</h3>
<div className='examples'>
<img src={`./welcome-images/example${[this.state.welcomeImage + 1]}.webp`} alt='Example Mue setup' draggable={false}/>
<span className="mainTitle">
{this.getMessage("modals.welcome.sections.intro.title")}
</span>
<p>{this.getMessage("modals.welcome.sections.intro.description")}</p>
<h3 className="quicktip">#shareyourmue</h3>
<div className="examples">
<img
src={this.welcomeImages[this.state.welcomeImage]}
alt="Example Mue setup"
draggable={false}
/>
</div>
</>
);
const chooseLanguage = (
<>
<h1>{this.getMessage('modals.welcome.sections.language.title')}</h1>
<p>{this.getMessage('modals.welcome.sections.language.description')} <a href={variables.constants.TRANSLATIONS_URL} className='resetLink' target='_blank' rel='noopener noreferrer'>GitHub</a>!</p>
<Radio name='language' options={languages} category='welcomeLanguage'/>
<span className="mainTitle">
{this.getMessage("modals.welcome.sections.language.title")}
</span>
<p>
{this.getMessage("modals.welcome.sections.language.description")}{" "}
<a
href={variables.constants.TRANSLATIONS_URL}
className="resetLink"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
!
</p>
<Radio name="language" options={languages} category="welcomeLanguage" />
</>
);
const theme = (
<>
<h1>{this.getMessage('modals.welcome.sections.theme.title')}</h1>
<p>{this.getMessage('modals.welcome.sections.theme.description')}</p>
<div className='themesToggleArea'>
<div className={this.state.autoClass} onClick={() => this.changeTheme('auto')}>
<MdAutoAwesome/>
<span>{this.getMessage('modals.main.settings.sections.appearance.theme.auto')}</span>
<span className="mainTitle">
{this.getMessage("modals.welcome.sections.theme.title")}
</span>
<p>{this.getMessage("modals.welcome.sections.theme.description")}</p>
<div className="themesToggleArea">
<div
className={this.state.autoClass}
onClick={() => this.changeTheme("auto")}
>
<MdAutoAwesome />
<span>
{this.getMessage(
"modals.main.settings.sections.appearance.theme.auto"
)}
</span>
</div>
<div className='options'>
<div className={this.state.lightClass} onClick={() => this.changeTheme('light')}>
<MdLightMode/>
<span>{this.getMessage('modals.main.settings.sections.appearance.theme.light')}</span>
<div className="options">
<div
className={this.state.lightClass}
onClick={() => this.changeTheme("light")}
>
<MdLightMode />
<span>
{this.getMessage(
"modals.main.settings.sections.appearance.theme.light"
)}
</span>
</div>
<div className={this.state.darkClass} onClick={() => this.changeTheme('dark')}>
<MdDarkMode/>
<span>{this.getMessage('modals.main.settings.sections.appearance.theme.dark')}</span>
<div
className={this.state.darkClass}
onClick={() => this.changeTheme("dark")}
>
<MdDarkMode />
<span>
{this.getMessage(
"modals.main.settings.sections.appearance.theme.dark"
)}
</span>
</div>
</div>
<h3 className='quicktip'>{this.getMessage('modals.welcome.tip')}</h3>
<p>{this.getMessage('modals.welcome.sections.theme.tip')}</p>
<h3 className="quicktip">{this.getMessage("modals.welcome.tip")}</h3>
<p>{this.getMessage("modals.welcome.sections.theme.tip")}</p>
</div>
</>
);
const settings = (
<>
<h1>{this.getMessage('modals.welcome.sections.settings.title')}</h1>
<p>{this.getMessage('modals.welcome.sections.settings.description')}</p>
<button className='upload' onClick={() => document.getElementById('file-input').click()}>
<MdCloudUpload/>
<br/>
<span>{this.getMessage('modals.main.settings.buttons.import')}</span>
<span className="mainTitle">
{this.getMessage("modals.welcome.sections.settings.title")}
</span>
<p>{this.getMessage("modals.welcome.sections.settings.description")}</p>
<button
className="upload"
onClick={() => document.getElementById("file-input").click()}
>
<MdCloudUpload />
<br />
<span>{this.getMessage("modals.main.settings.buttons.import")}</span>
</button>
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => this.importSettings(e)}/>
<h3 className='quicktip'>{this.getMessage('modals.welcome.tip')}</h3>
<p>{this.getMessage('modals.welcome.sections.settings.tip')}</p>
<FileUpload
id="file-input"
accept="application/json"
type="settings"
loadFunction={(e) => this.importSettings(e)}
/>
<h3 className="quicktip">{this.getMessage("modals.welcome.tip")}</h3>
<p>{this.getMessage("modals.welcome.sections.settings.tip")}</p>
</>
);
const privacy = (
<>
<h1>{this.getMessage('modals.welcome.sections.privacy.title')}</h1>
<p>{this.getMessage('modals.welcome.sections.privacy.description')}</p>
<Checkbox name='offlineMode' text={this.getMessage('modals.main.settings.sections.advanced.offline_mode')} element='.other' />
<p>{this.getMessage('modals.welcome.sections.privacy.offline_mode_description')}</p>
<Checkbox name='quicklinksddgProxy' text={this.getMessage('modals.main.settings.sections.background.ddg_image_proxy') + ' (' + this.getMessage('modals.main.settings.sections.quicklinks.title') + ')'}/>
<Checkbox name='ddgProxy' text={this.getMessage('modals.main.settings.sections.background.ddg_image_proxy') + ' (' +this.getMessage('modals.main.settings.sections.background.title') + ')'}/>
<p>{this.getMessage('modals.welcome.sections.privacy.ddg_proxy_description')}</p>
<h3 className='quicktip'>{this.getMessage('modals.welcome.sections.privacy.links.title')}</h3>
<a className='privacy' href={variables.constants.PRIVACY_URL} target='_blank' rel='noopener noreferrer'>{this.getMessage('modals.welcome.sections.privacy.links.privacy_policy')}</a>
<br/><br/>
<a className='privacy' href={'https://github.com/' + variables.constants.ORG_NAME} target='_blank' rel='noopener noreferrer'>{this.getMessage('modals.welcome.sections.privacy.links.source_code')}</a>
<span className="mainTitle">
{this.getMessage("modals.welcome.sections.privacy.title")}
</span>
<p>{this.getMessage("modals.welcome.sections.privacy.description")}</p>
<Checkbox
name="offlineMode"
text={this.getMessage(
"modals.main.settings.sections.advanced.offline_mode"
)}
element=".other"
/>
<p>
{this.getMessage(
"modals.welcome.sections.privacy.offline_mode_description"
)}
</p>
<Checkbox
name="quicklinksddgProxy"
text={
this.getMessage(
"modals.main.settings.sections.background.ddg_image_proxy"
) +
" (" +
this.getMessage("modals.main.settings.sections.quicklinks.title") +
")"
}
/>
<Checkbox
name="ddgProxy"
text={
this.getMessage(
"modals.main.settings.sections.background.ddg_image_proxy"
) +
" (" +
this.getMessage("modals.main.settings.sections.background.title") +
")"
}
/>
<p>
{this.getMessage(
"modals.welcome.sections.privacy.ddg_proxy_description"
)}
</p>
<h3 className="quicktip">
{this.getMessage("modals.welcome.sections.privacy.links.title")}
</h3>
<a
className="privacy"
href={variables.constants.PRIVACY_URL}
target="_blank"
rel="noopener noreferrer"
>
{this.getMessage(
"modals.welcome.sections.privacy.links.privacy_policy"
)}
</a>
<br />
<br />
<a
className="privacy"
href={"https://github.com/" + variables.constants.ORG_NAME}
target="_blank"
rel="noopener noreferrer"
>
{this.getMessage("modals.welcome.sections.privacy.links.source_code")}
</a>
</>
);
const final = (
<>
<h1>{this.getMessage('modals.welcome.sections.final.title')}</h1>
<p>{this.getMessage('modals.welcome.sections.final.description')}</p>
<h3 className='quicktip'>{this.getMessage('modals.welcome.sections.final.changes')}</h3>
<p>{this.getMessage('modals.welcome.sections.final.changes_description')}</p>
<div className='themesToggleArea'>
<div className='toggle' onClick={() => this.props.switchTab(1)}><span>{this.getMessage('modals.main.settings.sections.language.title')}: {languages.find((i) => i.value === localStorage.getItem('language')).name}</span></div>
<div className='toggle' onClick={() => this.props.switchTab(3)}><span>{this.getMessage('modals.main.settings.sections.appearance.theme.title')}: {this.getSetting('theme')}</span></div>
{(this.state.importedSettings.length !== 0) ? <div className='toggle' onClick={() => this.props.switchTab(2)}>{this.getMessage('modals.main.settings.sections.final.imported', { amount: this.state.importedSettings.length })} {this.state.importedSettings.length}</div> : null}
<span className="mainTitle">
{this.getMessage("modals.welcome.sections.final.title")}
</span>
<p>{this.getMessage("modals.welcome.sections.final.description")}</p>
<h3 className="quicktip">
{this.getMessage("modals.welcome.sections.final.changes")}
</h3>
<p>
{this.getMessage("modals.welcome.sections.final.changes_description")}
</p>
<div className="themesToggleArea">
<div className="toggle" onClick={() => this.props.switchTab(1)}>
<span>
{this.getMessage("modals.main.settings.sections.language.title")}:{" "}
{
languages.find(
(i) => i.value === localStorage.getItem("language")
).name
}
</span>
</div>
<div className="toggle" onClick={() => this.props.switchTab(3)}>
<span>
{this.getMessage(
"modals.main.settings.sections.appearance.theme.title"
)}
: {this.getSetting("theme")}
</span>
</div>
{this.state.importedSettings.length !== 0 ? (
<div className="toggle" onClick={() => this.props.switchTab(2)}>
{this.getMessage("modals.main.settings.sections.final.imported", {
amount: this.state.importedSettings.length,
})}{" "}
{this.state.importedSettings.length}
</div>
) : null}
</div>
</>
);
switch (this.props.currentTab) {
case 1: return chooseLanguage;
case 2: return settings;
case 3: return theme;
case 4: return privacy;
case 5: return final;
case 1:
return chooseLanguage;
case 2:
return settings;
case 3:
return theme;
case 4:
return privacy;
case 5:
return final;
// 0
default: return intro;
default:
return intro;
}
}
}

View File

@ -1,4 +1,5 @@
@import '../main/scss/index.scss';
@import 'scss/variables';
.welcomemodal {
position: absolute;
@ -19,7 +20,9 @@
section:nth-child(1) {
float: left;
background: var(--sidebar);
@include themed() {
background: t($modal-sidebar);
}
display: flex;
justify-content: center;
align-items: center;
@ -27,15 +30,27 @@
section:nth-child(2) {
float: right;
@include themed() {
background: t($modal-background);
}
.content {
padding: 20px;
.mainTitle {
font-size: 38px;
font-weight: 600;
margin-bottom: 15px;
}
}
.buttons {
position: absolute;
bottom: 2rem;
right: 2rem;
display: flex;
button {
@include modal-button(standard);
width: 150px;
}
}
h1 {
@ -78,57 +93,61 @@
}
.themesToggleArea {
.active {
background: var(--tab-active);
}
.toggle {
background: var(--sidebar);
text-align: center;
border-radius: 20px;
padding: 20px;
margin: 10px;
border: 3px solid var(--tab-active);
display: flex;
flex-direction: column;
align-items: center;
align-content: center;
justify-content: center;
cursor: pointer;
&:hover {
background: var(--tab-active);
@include themed() {
.active {
background: t($modal-sidebarActive);
}
span {
font-size: 1rem;
}
.toggle {
background: t($modal-sidebar);
text-align: center;
border-radius: 20px;
padding: 20px;
border: 3px solid t($modal-sidebarActive);
transition: 0.33s;
svg {
font-size: 2.5em;
}
}
display: flex;
flex-direction: column;
align-items: center;
align-content: center;
justify-content: center;
.auto {
svg {
font-size: 12px;
padding-right: 5px;
}
}
cursor: pointer;
.options {
display: flex;
justify-content: space-between;
.lightTheme,
.darkTheme {
width: 40%;
padding: 50px;
&:hover {
background: t($modal-sidebarActive);
}
span {
display: block;
font-size: 1rem;
}
svg {
font-size: 2.5em;
}
}
.auto {
svg {
font-size: 12px;
padding-right: 5px;
}
}
.options {
display: flex;
justify-content: space-between;
gap: 25px;
margin-top: 25px;
.lightTheme,
.darkTheme {
width: 40%;
padding: 50px;
span {
display: block;
}
}
}
}
@ -141,13 +160,14 @@
border: none;
outline: none;
padding: 50px;
background: var(--sidebar);
color: var(--modal-text);
cursor: pointer;
border: 3px solid var(--tab-active);
&:hover {
background: var(--tab-active);
@include themed() {
background: t($modal-sidebar);
color: t($color);
cursor: pointer;
border: 3px solid t($modal-sidebarActive);
&:hover {
background: t($modal-sidebarActive);
}
}
svg {
@ -220,14 +240,14 @@ a.privacy {
}
}
@media (max-width: 1190px) {
@media (max-width: 1190px) {
.welcomemodal {
width: 90%;
height: 90%;
}
}
@media (max-width: 800px) {
@media (max-width: 800px) {
.welcomemodal {
width: 100%;
height: 100%;
@ -241,3 +261,14 @@ a.privacy {
height: 140vh !important;
}
}
.createButtons {
display: flex;
flex-flow: row;
justify-content: space-between;
margin-top: 15px;
button {
width: 150px;
height: 40px;
}
}

View File

@ -1,62 +1,67 @@
import { PureComponent, Fragment, Suspense, lazy } from 'react';
import { PureComponent, Fragment, Suspense, lazy } from "react";
import Clock from './time/Clock';
import Greeting from './greeting/Greeting';
import Quote from './quote/Quote';
import Search from './search/Search';
import QuickLinks from './quicklinks/QuickLinks';
import Date from './time/Date';
import Message from './message/Message';
import Clock from "./time/Clock";
import Greeting from "./greeting/Greeting";
import Quote from "./quote/Quote";
import Search from "./search/Search";
import QuickLinks from "./quicklinks/QuickLinks";
import Date from "./time/Date";
import Message from "./message/Message";
import Reminder from "./reminder/Reminder";
import EventBus from 'modules/helpers/eventbus';
import EventBus from "modules/helpers/eventbus";
const Weather = lazy(() => import('./weather/Weather'));
const Weather = lazy(() => import("./weather/Weather"));
const renderLoader = () => <></>;
export default class Widgets extends PureComponent {
online = (localStorage.getItem('offlineMode') === 'false');
online = localStorage.getItem("offlineMode") === "false";
constructor() {
super();
this.state = {
order: JSON.parse(localStorage.getItem('order')),
welcome: localStorage.getItem('showWelcome')
order: JSON.parse(localStorage.getItem("order")),
welcome: localStorage.getItem("showWelcome"),
};
// widgets we can re-order
this.widgets = {
time: this.enabled('time') ? <Clock/> : null,
greeting: this.enabled('greeting') ? <Greeting/> : null,
quote: this.enabled('quote') ? <Quote/> : null,
date: this.enabled('date') ? <Date/> : null,
quicklinks: this.enabled('quicklinksenabled') && this.online ? <QuickLinks/> : null,
message: this.enabled('message') ? <Message/> : null
time: this.enabled("time") ? <Clock /> : null,
greeting: this.enabled("greeting") ? <Greeting /> : null,
quote: this.enabled("quote") ? <Quote /> : null,
date: this.enabled("date") ? <Date /> : null,
quicklinks:
this.enabled("quicklinksenabled") && this.online ? (
<QuickLinks />
) : null,
message: this.enabled("message") ? <Message /> : null,
reminder: this.enabled("reminder") ? <Reminder /> : null,
};
}
enabled(key) {
return (localStorage.getItem(key) === 'true');
return localStorage.getItem(key) === "true";
}
componentDidMount() {
EventBus.on('refresh', (data) => {
if (data === 'widgets') {
EventBus.on("refresh", (data) => {
if (data === "widgets") {
this.setState({
order: JSON.parse(localStorage.getItem('order'))
order: JSON.parse(localStorage.getItem("order")),
});
}
if (data === 'widgetsWelcome') {
if (data === "widgetsWelcome") {
this.setState({
welcome: localStorage.getItem('showWelcome')
welcome: localStorage.getItem("showWelcome"),
});
localStorage.setItem('showWelcome', true);
localStorage.setItem("showWelcome", true);
window.onbeforeunload = () => {
localStorage.clear();
}
};
}
if (data === 'widgetsWelcomeDone') {
if (data === "widgetsWelcomeDone") {
this.setState({
welcome: localStorage.getItem('showWelcome')
welcome: localStorage.getItem("showWelcome"),
});
window.onbeforeunload = null;
}
@ -65,8 +70,8 @@ export default class Widgets extends PureComponent {
render() {
// don't show when welcome is there
if (this.state.welcome !== 'false') {
return <div id='widgets'></div>;
if (this.state.welcome !== "false") {
return <div id="widgets"></div>;
}
// allow for re-ordering widgets
@ -74,19 +79,29 @@ export default class Widgets extends PureComponent {
if (this.state.order) {
this.state.order.forEach((element) => {
elements.push(<Fragment key={element}>{this.widgets[element]}</Fragment>);
elements.push(
<Fragment key={element}>{this.widgets[element]}</Fragment>
);
});
} else {
// prevent error
elements = [<Greeting/>, <Clock/>, <QuickLinks/>, <Quote/>, <Date/>, <Message/>];
elements = [
<Greeting />,
<Clock />,
<QuickLinks />,
<Quote />,
<Date />,
<Message />,
<Reminder />,
];
}
return (
<div id='widgets'>
<div id="widgets">
<Suspense fallback={renderLoader()}>
{this.enabled('searchBar') ? <Search/> : null}
{this.enabled("searchBar") ? <Search /> : null}
{elements}
{this.enabled('weatherEnabled') && this.online ? <Weather/> : null}
{this.enabled("weatherEnabled") && this.online ? <Weather /> : null}
</Suspense>
</div>
);

View File

@ -6,7 +6,12 @@ import PhotoInformation from './PhotoInformation';
import EventBus from 'modules/helpers/eventbus';
import Interval from 'modules/helpers/interval';
import { videoCheck, offlineBackground, getGradient, randomColourStyleBuilder } from 'modules/helpers/background/widget';
import {
videoCheck,
offlineBackground,
getGradient,
randomColourStyleBuilder,
} from 'modules/helpers/background/widget';
import './scss/index.scss';
@ -21,8 +26,8 @@ export default class Background extends PureComponent {
hidden: false,
offline: false,
photographerURL: '',
photoURL: ''
}
photoURL: '',
},
};
}
@ -31,7 +36,11 @@ export default class Background extends PureComponent {
if (this.state.url !== '') {
let url = this.state.url;
if (localStorage.getItem('ddgProxy') === 'true' && this.state.photoInfo.offline !== true && !this.state.url.startsWith('data:')) {
if (
localStorage.getItem('ddgProxy') === 'true' &&
this.state.photoInfo.offline !== true &&
!this.state.url.startsWith('data:')
) {
url = variables.constants.DDG_IMAGE_PROXY + this.state.url;
}
@ -39,8 +48,8 @@ export default class Background extends PureComponent {
// just set the background
if (localStorage.getItem('bgtransition') === 'false') {
photoInformation?.[photoInformation.style.display = 'block'];
return backgroundImage.style.background = `url(${url})`;
photoInformation?.[(photoInformation.style.display = 'block')];
return (backgroundImage.style.background = `url(${url})`);
}
// firstly we set the background as hidden and make sure there is no background set currently
@ -50,7 +59,7 @@ export default class Background extends PureComponent {
// same with photo information if not using custom background
photoInformation?.classList.add('backgroundPreload');
// preloader for background transition, required so it loads in nice
// preloader for background transition, required, so it loads in nice
const preloader = document.createElement('img');
preloader.src = url;
@ -76,17 +85,17 @@ export default class Background extends PureComponent {
// Main background getting function
async getBackground() {
let offline = (localStorage.getItem('offlineMode') === 'true');
let offline = localStorage.getItem('offlineMode') === 'true';
if (localStorage.getItem('showWelcome') === 'true') {
offline = true;
}
const setFavourited = ({ type, url, credit, location, camera }) => {
console.log(type)
if (type === 'random_colour' || type === 'random_gradient') {
return this.setState({
console.log(type);
if (type === 'random_colour' || type === 'random_gradient') {
return this.setState({
type: 'colour',
style: `background:${url}`
style: `background:${url}`,
});
}
this.setState({
@ -94,10 +103,10 @@ export default class Background extends PureComponent {
photoInfo: {
credit,
location,
camera
}
camera,
},
});
}
};
const favourited = JSON.parse(localStorage.getItem('favourite'));
if (favourited) {
@ -115,7 +124,7 @@ export default class Background extends PureComponent {
const backgroundAPI = localStorage.getItem('backgroundAPI');
const apiCategory = localStorage.getItem('apiCategory');
const apiQuality = localStorage.getItem('apiQuality');
const photoMap = (localStorage.getItem('photoMap') === 'true');
const photoMap = localStorage.getItem('photoMap') === 'true';
let requestURL, data;
switch (backgroundAPI) {
@ -160,8 +169,10 @@ export default class Background extends PureComponent {
latitude: data.latitude || null,
longitude: data.longitude || null,
// location map token from mapbox
maptoken: data.maptoken || null
}
maptoken: data.maptoken || null,
views: data.views || null,
downloads: data.downloads || null,
},
};
this.setState(object);
@ -180,7 +191,6 @@ export default class Background extends PureComponent {
case 'random_gradient':
this.setState(randomColourStyleBuilder(type));
break;
case 'custom':
let customBackground = [];
const customSaved = localStorage.getItem('customBackground');
@ -202,14 +212,19 @@ export default class Background extends PureComponent {
return this.setState(offlineBackground());
}
if (customBackground !== '' && customBackground !== 'undefined' && customBackground !== [''] && customBackground !== undefined) {
if (
customBackground !== '' &&
customBackground !== 'undefined' &&
customBackground !== [''] &&
customBackground !== undefined
) {
const object = {
url: customBackground,
type: 'custom',
video: videoCheck(customBackground),
photoInfo: {
hidden: true
}
hidden: true,
},
};
this.setState(object);
@ -237,8 +252,8 @@ export default class Background extends PureComponent {
photoInfo: {
hidden: false,
credit: randomPhoto.photographer,
location: randomPhoto.location || 'N/A'
}
location: randomPhoto.location || 'N/A',
},
});
}
break;
@ -259,8 +274,8 @@ export default class Background extends PureComponent {
type: '',
video: false,
photoInfo: {
hidden: true
}
hidden: true,
},
});
this.getBackground();
};
@ -279,9 +294,9 @@ export default class Background extends PureComponent {
// video backgrounds
if (this.state.video === true) {
return document.getElementById('backgroundVideo').style.display = 'none';
return (document.getElementById('backgroundVideo').style.display = 'none');
} else {
return element.style.display = 'none';
return (element.style.display = 'none');
}
}
@ -304,7 +319,13 @@ export default class Background extends PureComponent {
if (this.state.photoInfo.offline !== true) {
// basically check to make sure something has changed before we try getting another background
if (backgroundType !== this.state.type || (this.state.type === 'api' && localStorage.getItem('backgroundAPI') !== this.state.currentAPI) || (this.state.type === 'custom' && localStorage.getItem('customBackground') !== this.state.url)) {
if (
backgroundType !== this.state.type ||
(this.state.type === 'api' &&
localStorage.getItem('backgroundAPI') !== this.state.currentAPI) ||
(this.state.type === 'custom' &&
localStorage.getItem('customBackground') !== this.state.url)
) {
return refresh();
}
} else if (backgroundType !== this.state.type) {
@ -316,14 +337,38 @@ export default class Background extends PureComponent {
const backgroundFilter = backgroundFilterSetting && backgroundFilterSetting !== 'none';
if (this.state.video === true) {
document.getElementById('backgroundVideo').style.webkitFilter = `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%) ${backgroundFilter ? backgroundFilterSetting + '(' + localStorage.getItem('backgroundFilterAmount') + '%)' : ''}`;
document.getElementById(
'backgroundVideo',
).style.webkitFilter = `blur(${localStorage.getItem(
'blur',
)}px) brightness(${localStorage.getItem('brightness')}%) ${
backgroundFilter
? backgroundFilterSetting +
'(' +
localStorage.getItem('backgroundFilterAmount') +
'%)'
: ''
}`;
} else {
element.style.webkitFilter = `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%) ${backgroundFilter ? backgroundFilterSetting + '(' + localStorage.getItem('backgroundFilterAmount') + '%)' : ''}`;
element.style.webkitFilter = `blur(${localStorage.getItem(
'blur',
)}px) brightness(${localStorage.getItem('brightness')}%) ${
backgroundFilter
? backgroundFilterSetting +
'(' +
localStorage.getItem('backgroundFilterAmount') +
'%)'
: ''
}`;
}
}
// uninstall photo pack reverts your background to what you had previously
if (data === 'marketplacebackgrounduninstall' || data === 'backgroundwelcome' || data === 'backgroundrefresh') {
if (
data === 'marketplacebackgrounduninstall' ||
data === 'backgroundwelcome' ||
data === 'backgroundrefresh'
) {
refresh();
}
});
@ -337,16 +382,20 @@ export default class Background extends PureComponent {
const type = localStorage.getItem('backgroundType');
if (type === 'api' || type === 'custom') {
Interval(() => {
try {
document.getElementById('backgroundImage').classList.remove('fade-in');
document.getElementsByClassName('photoInformation')[0].classList.remove('fade-in');
} catch (e) {
// Disregard exception
}
this.getBackground();
}, Number(interval), 'background');
Interval(
() => {
try {
document.getElementById('backgroundImage').classList.remove('fade-in');
document.getElementsByClassName('photoInformation')[0].classList.remove('fade-in');
} catch (e) {
// Disregard exception
}
this.getBackground();
},
Number(interval),
'background',
);
try {
// todo: refactor this mess
const current = JSON.parse(localStorage.getItem('currentBackground'));
@ -390,12 +439,22 @@ export default class Background extends PureComponent {
render() {
if (this.state.video === true) {
const enabled = (setting) => {
return (localStorage.getItem(setting) === 'true');
return localStorage.getItem(setting) === 'true';
};
return (
<video autoPlay muted={enabled('backgroundVideoMute')} loop={enabled('backgroundVideoLoop')} style={{ WebkitFilter: `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%)` }} id='backgroundVideo'>
<source src={this.state.url}/>
<video
autoPlay
muted={enabled('backgroundVideoMute')}
loop={enabled('backgroundVideoLoop')}
style={{
WebkitFilter: `blur(${localStorage.getItem(
'blur',
)}px) brightness(${localStorage.getItem('brightness')}%)`,
}}
id="backgroundVideo"
>
<source src={this.state.url} />
</video>
);
}
@ -404,10 +463,25 @@ export default class Background extends PureComponent {
return (
<>
<div style={{ WebkitFilter: `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%) ${backgroundFilter && backgroundFilter !== 'none' ? (backgroundFilter + '(' + localStorage.getItem('backgroundFilterAmount') + '%)') : ''}` }} id='backgroundImage'/>
{(this.state.photoInfo.credit !== '') ?
<PhotoInformation info={this.state.photoInfo} api={this.state.currentAPI} url={this.state.url}/>
: null}
<div
style={{
WebkitFilter: `blur(${localStorage.getItem(
'blur',
)}px) brightness(${localStorage.getItem('brightness')}%) ${
backgroundFilter && backgroundFilter !== 'none'
? backgroundFilter + '(' + localStorage.getItem('backgroundFilterAmount') + '%)'
: ''
}`,
}}
id="backgroundImage"
/>
{this.state.photoInfo.credit !== '' ? (
<PhotoInformation
info={this.state.photoInfo}
api={this.state.currentAPI}
url={this.state.url}
/>
) : null}
</>
);
}

View File

@ -3,18 +3,18 @@ import { PureComponent } from 'react';
import { MdStar, MdStarBorder } from 'react-icons/md';
//import Hotkeys from 'react-hot-keys';
import Tooltip from 'components/helpers/tooltip/Tooltip';
export default class Favourite extends PureComponent {
buttons = {
favourited: <MdStar onClick={() => this.favourite()} className='topicons' />,
unfavourited: <MdStarBorder onClick={() => this.favourite()} className='topicons' />
}
favourited: <MdStar onClick={() => this.favourite()} className="topicons" />,
unfavourited: <MdStarBorder onClick={() => this.favourite()} className="topicons" />,
};
constructor() {
super();
this.state = {
favourited: (localStorage.getItem('favourite')) ? this.buttons.favourited : this.buttons.unfavourited
favourited: localStorage.getItem('favourite')
? this.buttons.favourited
: this.buttons.unfavourited,
};
}
@ -22,7 +22,7 @@ export default class Favourite extends PureComponent {
if (localStorage.getItem('favourite')) {
localStorage.removeItem('favourite');
this.setState({
favourited: this.buttons.unfavourited
favourited: this.buttons.unfavourited,
});
variables.stats.postEvent('feature', 'Background favourite');
} else {
@ -32,41 +32,54 @@ export default class Favourite extends PureComponent {
return;
case 'random_colour':
case 'random_gradient':
localStorage.setItem('favourite', JSON.stringify({
type: localStorage.getItem('backgroundType'),
url: document.getElementById('backgroundImage').style.background
}));
localStorage.setItem(
'favourite',
JSON.stringify({
type: localStorage.getItem('backgroundType'),
url: document.getElementById('backgroundImage').style.background,
}),
);
break;
default:
const url = document.getElementById('backgroundImage').style.backgroundImage.replace('url("', '').replace('")', '').replace(variables.constants.DDG_IMAGE_PROXY, '');
const url = document
.getElementById('backgroundImage')
.style.backgroundImage.replace('url("', '')
.replace('")', '')
.replace(variables.constants.DDG_IMAGE_PROXY, '');
if (!url) {
return;
}
if (type === 'custom') {
localStorage.setItem('favourite', JSON.stringify({
type,
url
}));
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: document.getElementById('credit').textContent || '',
location: location ? location.innerText : 'N/A',
camera: camera ? camera.innerText : 'N/A',
resolution: document.getElementById('infoResolution').textContent || '',
}));
localStorage.setItem(
'favourite',
JSON.stringify({
type,
url,
credit: document.getElementById('credit').textContent || '',
location: location ? location.innerText : 'N/A',
camera: camera ? camera.innerText : 'N/A',
resolution: document.getElementById('infoResolution').textContent || '',
}),
);
}
}
this.setState({
favourited: this.buttons.favourited
favourited: this.buttons.favourited,
});
variables.stats.postEvent('feature', 'Background unfavourite');
}
@ -79,10 +92,10 @@ export default class Favourite extends PureComponent {
}
return (
<Tooltip title={variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.background.buttons.favourite')}>
<>
{this.state.favourited}
{/*variables.keybinds.favouriteBackground && variables.keybinds.favouriteBackground !== '' ? <Hotkeys keyName={variables.keybinds.favouriteBackground} onKeyDown={() => this.favourite()} /> : null*/}
</Tooltip>
</>
);
}
}

View File

@ -9,14 +9,18 @@ export default class Maximise extends PureComponent {
constructor() {
super();
this.state = {
hidden: false
hidden: false,
};
}
setAttribute(blur, brightness, filter) {
// don't attempt to modify the background if it isn't an image
const backgroundType = localStorage.getItem('backgroundType');
if (backgroundType === 'colour' || backgroundType === 'random_colour' || backgroundType === 'random_gradient') {
if (
backgroundType === 'colour' ||
backgroundType === 'random_colour' ||
backgroundType === 'random_gradient'
) {
return;
}
@ -29,36 +33,51 @@ export default class Maximise extends PureComponent {
element.setAttribute(
'style',
`background-image: url(${element.style.backgroundImage.replace('url("', '').replace('")', '')}); -webkit-filter: blur(${blur}px) brightness(${brightness}%) ${backgroundFilter ? backgroundFilter + '(' + localStorage.getItem('backgroundFilterAmount') + '%)' : ''};`
`background-image: url(${element.style.backgroundImage
.replace('url("', '')
.replace('")', '')}); -webkit-filter: blur(${blur}px) brightness(${brightness}%) ${
backgroundFilter
? backgroundFilter + '(' + localStorage.getItem('backgroundFilterAmount') + '%)'
: ''
};`,
);
}
maximise = () => {
// hide widgets
const widgets = document.getElementById('widgets');
(this.state.hidden === false) ? widgets.style.display = 'none' : widgets.style.display = 'block';
this.state.hidden === false
? (widgets.style.display = 'none')
: (widgets.style.display = 'flex');
if (this.state.hidden === false) {
this.setState({
hidden: true
hidden: true,
});
this.setAttribute(0, 100);
variables.stats.postEvent('feature', 'Background maximise');
} else {
this.setState({
hidden: false
hidden: false,
});
this.setAttribute(localStorage.getItem('blur'), localStorage.getItem('brightness'), true);
variables.stats.postEvent('feature', 'Background unmaximise');
}
}
};
render() {
return (
<Tooltip title={variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.background.buttons.view')}>
<MdCropFree onClick={this.maximise} className='topicons' />
<Tooltip
title={variables.language.getMessage(
variables.languagecode,
'modals.main.settings.sections.background.buttons.view',
)}
>
<button>
<MdCropFree onClick={this.maximise} className="topicons" />
</button>
{/*variables.keybinds.maximiseBackground && variables.keybinds.maximiseBackground !== '' ? <Hotkeys keyName={variables.keybinds.maximiseBackground} onKeyDown={this.maximise} /> : null*/}
</Tooltip>
);

View File

@ -1,14 +1,20 @@
import variables from 'modules/variables';
import { useState, Fragment } from 'react';
import Favourite from './Favourite';
import {
MdInfo,
MdLocationOn,
MdPhotoCamera,
MdCrop as Resolution,
MdPerson as Photographer,
MdGetApp as Download
MdGetApp as Download,
MdVisibility as Views,
MdIosShare as Share,
} from 'react-icons/md';
import Tooltip from '../../helpers/tooltip/Tooltip';
import ShareModal from '../../helpers/sharemodal/ShareModal';
//import Hotkeys from 'react-hot-keys';
import { toast } from 'react-toastify';
const toDataURL = async (url) => {
const res = await fetch(url);
@ -29,17 +35,31 @@ const downloadImage = async (info) => {
variables.stats.postEvent('feature', 'Background download');
};
// todo: copy link to unsplash/pexels page not image url
const copyImage = (info) => {
variables.stats.postEvent('feature', 'Background copied');
navigator.clipboard.writeText(info.url);
toast('Background copied');
};
export default function PhotoInformation({ info, url, api }) {
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const [usePhotoMap, setPhotoMap] = useState(false);
const [setMapIcon] = useState(true);
const [showExtraInfo, setshowExtraInfo] = useState(false);
const [showOld, setShowOld] = useState(true);
const [other, setOther] = useState(false);
if (info.hidden === true || !info.credit) {
return null;
}
// remove unsplash and pexels text
const unsplash = variables.language.getMessage(variables.languagecode, 'widgets.background.unsplash');
const unsplash = variables.language.getMessage(
variables.languagecode,
'widgets.background.unsplash',
);
const pexels = variables.language.getMessage(variables.languagecode, 'widgets.background.pexels');
const photographer = info.credit.split(` ${unsplash}`)[0].split(` ${pexels}`);
@ -49,15 +69,41 @@ export default function PhotoInformation({ info, url, api }) {
// unsplash and pexels credit
if (info.photographerURL && info.photographerURL !== '' && !info.offline && api) {
if (api === 'unsplash') {
photo = <a href={info.photoURL + '?utm_source=mue'} target='_blank' rel='noopener noreferrer'>{photo}</a>;
credit = <><a href={info.photographerURL} target='_blank' rel='noopener noreferrer'>{info.credit}</a> <a href='https://unsplash.com?utm_source=mue' target='_blank' rel='noopener noreferrer'>{unsplash}</a></>;
photo = (
<a href={info.photoURL + '?utm_source=mue'} target="_blank" rel="noopener noreferrer">
{photo}
</a>
);
credit = (
<>
<a href={info.photographerURL} target="_blank" rel="noopener noreferrer">
{info.credit}
</a>{' '}
<a href="https://unsplash.com?utm_source=mue" target="_blank" rel="noopener noreferrer">
{unsplash}
</a>
</>
);
} else {
photo = <a href={info.photoURL} target='_blank' rel='noopener noreferrer'>{photo}</a>;
credit = <><a href={info.photographerURL} target='_blank' rel='noopener noreferrer'>{info.credit}</a> <a href='https://pexels.com' target='_blank' rel='noopener noreferrer'>{pexels}</a></>;
photo = (
<a href={info.photoURL} target="_blank" rel="noopener noreferrer">
{photo}
</a>
);
credit = (
<>
<a href={info.photographerURL} target="_blank" rel="noopener noreferrer">
{info.credit}
</a>{' '}
<a href="https://pexels.com" target="_blank" rel="noopener noreferrer">
{pexels}
</a>
</>
);
}
}
const ddgProxy = (localStorage.getItem('ddgProxy') === 'true');
const ddgProxy = localStorage.getItem('ddgProxy') === 'true';
// get resolution
const img = new Image();
@ -65,24 +111,32 @@ export default function PhotoInformation({ info, url, api }) {
setWidth(event.target.width);
setHeight(event.target.height);
};
img.src = (ddgProxy && !info.offline && !url.startsWith('data:')) ? variables.constants.DDG_IMAGE_PROXY + url : url;
img.src =
ddgProxy && !info.offline && !url.startsWith('data:')
? variables.constants.DDG_IMAGE_PROXY + url
: url;
// info is still there because we want the favourite button to work
if (localStorage.getItem('photoInformation') === 'false') {
return (
<div className='photoInformation'>
<h1>{photo} <span id='credit'>{credit}</span></h1>
<div className="photoInformation">
<h1>
{photo} <span id="credit">{credit}</span>
</h1>
<div style={{ display: 'none' }}>
<span id='infoLocation'>{info.location || 'N/A'}</span>
<span id='infoCamera'>{info.camera || 'N/A'}</span>
<span id='infoResolution'>{width}x{height}</span>
<span id="infoLocation">{info.location || 'N/A'}</span>
<span id="infoCamera">{info.camera || 'N/A'}</span>
<span id="infoResolution">
{width}x{height}
</span>
</div>
</div>
);
}
const downloadEnabled = (localStorage.getItem('downloadbtn') === 'true') && !info.offline && !info.photographerURL && api;
const downloadBackground = () => {
const downloadEnabled =
localStorage.getItem('downloadbtn') === 'true' && !info.offline && !info.photographerURL && api;
const downloadBackground = () => {
if (downloadEnabled) {
downloadImage(info);
}
@ -99,66 +153,181 @@ export default function PhotoInformation({ info, url, api }) {
}
};
let showingPhotoMap = false;
const photoMap = () => {
if (localStorage.getItem('photoMap') !== 'true' || !info.latitude || !info.longitude || usePhotoMap === false) {
if (
localStorage.getItem('photoMap') !== 'true' ||
!info.latitude ||
!info.longitude ||
usePhotoMap === false
) {
return null;
}
const zoom = 12;
const tile = `${variables.constants.MAPBOX_URL}/styles/v1/mapbox/streets-v11/static/pin-s+555555(${info.longitude},${info.latitude})/${info.longitude},${info.latitude},${zoom},0/300x100?access_token=${info.maptoken}`;
showingPhotoMap = true;
return (
<Fragment key='photomap'>
<a href={`${variables.constants.OPENSTREETMAP_URL}/?mlat=${info.latitude}&mlon=${info.longitude}`} target='_blank' rel='noopener noreferrer'>
<img className='locationMap' src={tile} alt='location' draggable={false}/>
<Fragment key="photomap">
<a
href={`${variables.constants.OPENSTREETMAP_URL}/?mlat=${info.latitude}&mlon=${info.longitude}`}
target="_blank"
rel="noopener noreferrer"
>
<img className="locationMap" src={tile} alt="location" draggable={false} />
</a>
<br/>
<span className='mapCopyright'>
<a href='https://www.mapbox.com/about/maps/' target='_blank' rel='noopener noreferrer'> © Mapbox</a>, <a href='https://www.openstreetmap.org/about/' target='_blank' rel='noopener noreferrer'>© OpenStreetMap</a>. <a href='https://www.mapbox.com/map-feedback/' target='_blank' rel='noopener noreferrer'>Improve this map</a>.
</span>
<br />
</Fragment>
);
}
};
// only request map image if the user looks at the photo information
// this is to reduce requests to the api
try {
document.getElementsByClassName('photoInformation')[0].onmouseover = () => {
setPhotoMap(true);
}
try {
setPhotoMap(true);
setMapIcon(false);
} catch (e) {}
};
} catch (e) {}
return (
<div className='photoInformation'>
<h1>{photo} <span id='credit'>{credit}</span></h1>
<MdInfo className='photoInformationHover'/>
<div className='infoCard'>
<MdInfo className='infoIcon'/>
<h1>{variables.language.getMessage(variables.languagecode, 'widgets.background.information')}</h1>
<hr/>
{photoMap()}
{/* fix console error by using fragment and key */}
{info.location && info.location !== 'N/A' ? <Fragment key='location'>
<MdLocationOn/>
<span id='infoLocation'>{info.location}</span>
</Fragment> : null}
{info.camera && info.camera !== 'N/A' ? <Fragment key='camera'>
<MdPhotoCamera/>
<span id='infoCamera'>{info.camera}</span>
</Fragment> : null}
<Resolution/>
<span id='infoResolution'>{width}x{height}</span>
<Photographer/>
<span>{photographer}</span>
{downloadEnabled ?
<>
<Download/>
<span className='download' onClick={() => downloadImage(info)}>{variables.language.getMessage(variables.languagecode, 'widgets.background.download')}</span>
</>
: null}
</div>
{/*variables.keybinds.downloadBackground && variables.keybinds.downloadBackground !== '' ? <Hotkeys keyName={variables.keybinds.downloadBackground} onKeyDown={() => downloadBackground()} /> : null*/}
{/*variables.keybinds.showBackgroundInformation && variables.keybinds.showBackgroundInformation !== '' ? <Hotkeys keyName={variables.keybinds.showBackgroundInformation} onKeyDown={() => showBackgroundInformation()} /> : null*/}
<div
className="photoInformationHolder"
onMouseEnter={() => setOther(true)}
onMouseLeave={() => setOther(false)}
>
{localStorage.getItem('widgetStyle') === 'legacy' && (
<div className="photoInformation-legacy">
<MdInfo />
<span className="title">
{photo} <span id="credit">{credit}</span>
</span>
</div>
)}
{localStorage.getItem('widgetStyle') !== 'legacy' || other ? (
<div
className="photoInformation orHover"
onMouseEnter={() => setshowExtraInfo(true)}
onMouseLeave={() => setshowExtraInfo(false)}
>
<div className="map-concept">
<MdLocationOn />
{photoMap()}
</div>
{showingPhotoMap ? (
<div className="concept-copyright">
<a
href="https://www.mapbox.com/about/maps/"
target="_blank"
rel="noopener noreferrer"
>
{' '}
© Mapbox{' '}
</a>{' '}
{' '}
<a
href="https://www.openstreetmap.org/about/"
target="_blank"
rel="noopener noreferrer"
>
{' '}
© OpenStreetMap{' '}
</a>{' '}
{' '}
<a
href="https://www.mapbox.com/map-feedback/"
target="_blank"
rel="noopener noreferrer"
>
{' '}
Improve this map{' '}
</a>
</div>
) : null}
<div className="photoInformation-content">
<span className="title">{info.location}</span>
<span className="subtitle" id="credit">
{credit}
</span>
{info.views && info.downloads !== null ? (
<div className="concept-stats">
<div>
<Views />
<span>{info.views.toLocaleString()}</span>
</div>
<div>
<Download />
<span>{info.downloads.toLocaleString()}</span>
</div>
</div>
) : null}
</div>
{showExtraInfo || other ? (
<>
<div className="concept-buttons">
<Tooltip title="Share" key="other">
<Share onClick={() => copyImage(info)} />
</Tooltip>
<Tooltip title="Favourite" key="other">
<Favourite />
</Tooltip>
<Tooltip title="Download" key="other">
<Download onClick={() => downloadImage(info)} />
</Tooltip>
</div>
<div className="extra-content">
<span className="subtitle">
{variables.language.getMessage(
variables.languagecode,
'widgets.background.information',
)}
</span>
{info.location && info.location !== 'N/A' ? (
<Fragment key="location">
<div className="concept-row">
<MdLocationOn />
<span id="infoLocation">{info.location}</span>
</div>
</Fragment>
) : null}
{info.camera && info.camera !== 'N/A' ? (
<Fragment key="camera">
<div className="concept-row">
<MdPhotoCamera />
<span id="infoCamera">{info.camera}</span>
</div>
</Fragment>
) : null}
<div className="concept-row">
<Resolution />
<span id="infoResolution">
{width}x{height}
</span>
</div>
<div className="concept-row">
<Photographer />
<span>{photographer}</span>
</div>
{downloadEnabled ? (
<>
<Download />
<span className="download" onClick={() => downloadImage(info)}>
{variables.language.getMessage(
variables.languagecode,
'widgets.background.download',
)}
</span>
</>
) : null}
</div>
</>
) : null}
</div>
) : null}
</div>
);
}

View File

@ -1,26 +1,86 @@
.photoInformation {
@import 'scss/variables';
.photoInformationHolder {
position: absolute;
bottom: 1rem;
left: 1rem;
font-size: calc(10px + 0.1vmin) !important;
flex-flow: column-reverse;
display: flex;
&:hover {
.photoInformation {
height: auto;
align-items: flex-start;
flex-flow: column;
.concept-buttons {
padding: 0 0 20px;
display: flex;
}
.extra-content {
display: flex;
flex-flow: column;
gap: 10px;
transition: 0.8s;
}
.concept-stats {
display: flex;
gap: 15px;
@include themed() {
color: t($subColor);
}
div {
display: flex;
align-items: center;
align-content: center;
flex-direction: row;
gap: 10px;
svg {
font-size: 1em;
}
}
}
.concept-copyright {
display: flex;
margin-top: 10px;
gap: 10px;
a {
text-decoration: none;
@include themed() {
color: t($subColor);
}
}
}
.photoInformation-content {
padding-left: 0;
}
.map-concept {
width: 100%;
}
}
}
}
.photoInformation-legacy {
font-size: 1.36em;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
color: #fff;
z-index: 99;
user-select: none;
cursor: initial;
font-weight: bolder;
svg {
float: left;
margin-right: 1rem;
font-size: calc(10px + 2vmin);
cursor: pointer;
font-size: 1.36em;
}
svg,
h1 {
display: inline;
&:hover .infoCard {
display: block !important;
}
svg:hover+.infoCard {
display: flex;
align-items: center;
gap: 15px;
svg:hover + .infoCard {
display: block;
}
@ -78,7 +138,7 @@
}
h1,
svg {
.MuiSvgIcon-root {
user-select: none;
cursor: initial;
}
@ -117,3 +177,152 @@
font-size: 0.9em;
}
}
.photoInformation {
@extend %basic;
font-size: 0.8em;
font-weight: 300;
z-index: 99;
padding: 20px;
display: flex;
flex-flow: row;
align-items: center;
transition: 0.8s cubic-bezier(0.075, 0.82, 0.165, 1);
width: 380px;
&:hover {
height: auto;
align-items: flex-start;
flex-flow: column;
.concept-buttons {
padding: 0 0 20px;
display: flex;
}
.extra-content {
display: flex;
flex-flow: column;
gap: 10px;
transition: 0.8s;
}
.concept-stats {
display: flex;
gap: 15px;
@include themed() {
color: t($subColor);
}
div {
display: flex;
align-items: center;
align-content: center;
flex-direction: row;
gap: 10px;
svg {
font-size: 1em;
}
}
}
.concept-copyright {
display: flex;
margin-top: 10px;
gap: 10px;
a {
text-decoration: none;
@include themed() {
color: t($subColor);
}
}
}
.photoInformation-content {
padding-left: 0;
}
.map-concept {
width: 100%;
}
}
.map-concept {
@extend %basic;
height: 80px;
width: 80px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
border: none !important;
svg {
font-size: 25px;
}
img {
width: 100%;
height: 100%;
background-repeat: no-repeat;
background-size: cover;
object-fit: cover;
border-radius: 12px;
}
}
.concept-location {
font-size: 1.9em;
}
.concept-credit {
font-size: 1.4em;
@include themed() {
color: t($subColor);
}
a {
@include themed() {
color: t($subColor);
}
&:nth-child(2) {
color: var(--modal-link);
}
}
}
}
.photoInformation-content {
display: flex;
flex-flow: column;
padding: 20px;
a {
color: #5352ed;
text-decoration: none;
&:hover {
opacity: 0.9;
}
}
}
.extra-content {
display: none;
flex-flow: column;
width: 100%;
transition: 1s;
.concept-row {
display: flex;
align-items: center;
gap: 20px;
svg {
@include themed() {
color: t($subColor);
font-size: 18px;
}
}
}
}
.concept-copyright {
display: none;
}
.concept-stats {
display: none;
}
.concept-buttons {
align-items: center;
gap: 10px;
padding: 20px 20px 20px 0;
display: none;
svg {
@include basicIconButton(11px, 1.3rem, ui);
}
}

View File

@ -1,5 +1,5 @@
@import 'photoinformation';
@import '../../../../scss/mixins';
@import 'scss/mixins';
#backgroundImage {
height: 100vh;

View File

@ -10,7 +10,7 @@ export default class Greeting extends PureComponent {
constructor() {
super();
this.state = {
greeting: ''
greeting: '',
};
this.timer = undefined;
this.greeting = createRef();
@ -28,10 +28,10 @@ export default class Greeting extends PureComponent {
// If it's December 25th, set the greeting string to "Merry Christmas"
if (month === 11 && date === 25) {
message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.christmas');
// If the date is January 1st, set the greeting string to "Happy new year"
// If the date is January 1st, set the greeting string to "Happy new year"
} else if (month === 0 && date === 1) {
message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.newyear');
// If it's October 31st, set the greeting string to "Happy Halloween"
// If it's October 31st, set the greeting string to "Happy Halloween"
} else if (month === 9 && date === 31) {
message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.halloween');
}
@ -45,7 +45,7 @@ export default class Greeting extends PureComponent {
return Math.abs(birthday.getUTCFullYear() - 1970);
}
getGreeting(time = (60000 - Date.now() % 60000)) {
getGreeting(time = 60000 - (Date.now() % 60000)) {
this.timer = setTimeout(() => {
let now = new Date();
const timezone = localStorage.getItem('timezone');
@ -56,13 +56,19 @@ export default class Greeting extends PureComponent {
const hour = now.getHours();
// Set the default greeting string to "Good evening"
let message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.evening');
let message = variables.language.getMessage(
variables.languagecode,
'widgets.greeting.evening',
);
// If it's before 12am, set the greeting string to "Good morning"
if (hour < 12) {
message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.morning');
// If it's before 6pm, set the greeting string to "Good afternoon"
// If it's before 6pm, set the greeting string to "Good afternoon"
} else if (hour < 18) {
message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.afternoon');
message = variables.language.getMessage(
variables.languagecode,
'widgets.greeting.afternoon',
);
}
// Events and custom
@ -95,19 +101,24 @@ export default class Greeting extends PureComponent {
if (birth.getDate() === now.getDate() && birth.getMonth() === now.getMonth()) {
if (localStorage.getItem('birthdayage') === 'true') {
const text = variables.language.getMessage(variables.languagecode, 'widgets.greeting.birthday').split(' ');
const text = variables.language
.getMessage(variables.languagecode, 'widgets.greeting.birthday')
.split(' ');
message = `${text[0]} ${nth(this.calculateAge(birth))} ${text[1]}`;
} else {
message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.birthday');
message = variables.language.getMessage(
variables.languagecode,
'widgets.greeting.birthday',
);
}
}
}
// Set the state to the greeting string
this.setState({
greeting: `${message}${name}`
greeting: `${message}${name}`,
});
this.getGreeting();
}, time);
}
@ -116,20 +127,24 @@ export default class Greeting extends PureComponent {
EventBus.on('refresh', (data) => {
if (data === 'greeting' || data === 'timezone') {
if (localStorage.getItem('greeting') === 'false') {
return this.greeting.current.style.display = 'none';
return (this.greeting.current.style.display = 'none');
}
this.timer = null;
this.getGreeting(0);
this.greeting.current.style.display = 'block';
this.greeting.current.style.fontSize = `${1.6 * Number((localStorage.getItem('zoomGreeting') || 100) / 100)}em`;
this.greeting.current.style.fontSize = `${
1.6 * Number((localStorage.getItem('zoomGreeting') || 100) / 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 = `${1.6 * Number((localStorage.getItem('zoomGreeting') || 100) / 100)}em`;
// 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 = `${
1.6 * Number((localStorage.getItem('zoomGreeting') || 100) / 100)
}em`;
this.getGreeting(0);
}
@ -140,9 +155,9 @@ export default class Greeting extends PureComponent {
render() {
return (
<h1 className='greeting' ref={this.greeting}>
<span className="greeting" ref={this.greeting}>
{this.state.greeting}
</h1>
</span>
);
}
}

View File

@ -3,6 +3,7 @@
font-size: 1.6em;
cursor: initial;
user-select: none;
font-weight: 600;
--shadow-shift: 0.2rem;
}

View File

@ -8,7 +8,7 @@ export default class Message extends PureComponent {
constructor(props) {
super(props);
this.state = {
messageText: ''
messageText: '',
};
this.message = createRef();
}
@ -17,24 +17,28 @@ export default class Message extends PureComponent {
EventBus.on('refresh', (data) => {
if (data === 'message') {
if (localStorage.getItem('message') === 'false') {
return this.message.current.style.display = 'none';
return (this.message.current.style.display = 'none');
}
this.message.current.style.display = 'block';
this.message.current.style.fontSize = `${1.6 * Number((localStorage.getItem('zoomMessage') || 100) / 100)}em`;
this.message.current.style.fontSize = `${
1.6 * Number((localStorage.getItem('zoomMessage') || 100) / 100)
}em`;
}
});
this.message.current.style.fontSize = `${1.6 * Number((localStorage.getItem('zoomMessage') || 100) / 100)}em`;
this.message.current.style.fontSize = `${
1.6 * Number((localStorage.getItem('zoomMessage') || 100) / 100)
}em`;
const messages = JSON.parse(localStorage.getItem('messages')) || [];
this.setState({
messageText: messages[Math.floor(Math.random() * messages.length)]
messageText: messages[Math.floor(Math.random() * messages.length)],
});
}
render() {
return (
<h2 className='message' ref={this.message}>
<h2 className="message" ref={this.message}>
{this.state.messageText.split('\\n').map((item, i) => (
<span key={i}>
{item}

View File

@ -3,19 +3,25 @@ import { PureComponent, createRef } from 'react';
import { MdRefresh, MdSettings, MdAssignment } from 'react-icons/md';
import Notes from './Notes';
import Todo from './Todo';
import Maximise from '../background/Maximise';
import Favourite from '../background/Favourite';
import Tooltip from 'components/helpers/tooltip/Tooltip';
import InfoTooltip from 'components/helpers/tooltip/infoTooltip';
import EventBus from 'modules/helpers/eventbus';
import './scss/index.scss';
import { FaThemeisle } from 'react-icons/fa';
export default class Navbar extends PureComponent {
constructor() {
super();
this.navbarContainer = createRef();
this.refreshValue = localStorage.getItem('refresh');
this.refreshEnabled = localStorage.getItem('refresh');
this.refreshValue = localStorage.getItem('refreshOption');
this.state = {
classList: localStorage.getItem('widgetStyle') === 'legacy' ? 'navbar old' : 'navbar new',
};
}
setZoom() {
@ -26,7 +32,8 @@ export default class Navbar extends PureComponent {
componentDidMount() {
EventBus.on('refresh', (data) => {
if (data === 'navbar' || data === 'background') {
this.refreshValue = localStorage.getItem('refresh');
this.refreshEnabled = localStorage.getItem('refresh');
this.refreshValue = localStorage.getItem('refreshOption');
this.forceUpdate();
this.setZoom();
}
@ -36,7 +43,7 @@ export default class Navbar extends PureComponent {
}
refresh() {
switch (this.refreshValue) {
switch (this.refreshValue) {
case 'background':
EventBus.dispatch('refresh', 'backgroundrefresh');
break;
@ -53,36 +60,53 @@ export default class Navbar extends PureComponent {
}
render() {
const backgroundEnabled = (localStorage.getItem('background') === 'true');
const backgroundEnabled = localStorage.getItem('background') === 'true';
const navbar = (
<div className='navbar-container' ref={this.navbarContainer}>
{(localStorage.getItem('view') === 'true' && backgroundEnabled) ? <Maximise/> : null}
{(localStorage.getItem('favouriteEnabled') === 'true' && backgroundEnabled) ? <Favourite/> : null}
{(localStorage.getItem('notesEnabled') === 'true') ?
<div className='notes'>
<MdAssignment className='topicons'/>
<Notes/>
</div>
: null}
<div className="navbar-container">
<div className={this.state.classList} ref={this.navbarContainer}>
{localStorage.getItem('view') === 'true' && backgroundEnabled ? <Maximise /> : null}
{localStorage.getItem('notesEnabled') === 'true' ? <Notes /> : null}
{localStorage.getItem('todo') === 'true' ? <Todo /> : null}
{(this.refreshValue !== 'false') ?
<Tooltip title={variables.language.getMessage(variables.languagecode, 'widgets.navbar.tooltips.refresh')}>
<MdRefresh className='refreshicon topicons' onClick={() => this.refresh()}/>
</Tooltip>
: null}
<Tooltip title={variables.language.getMessage(variables.languagecode, 'modals.main.navbar.settings')}>
<MdSettings className='settings-icon topicons' onClick={() => this.props.openModal('mainModal')}/>
</Tooltip>
{this.refreshEnabled !== 'false' ? (
<Tooltip
title={variables.language.getMessage(
variables.languagecode,
'widgets.navbar.tooltips.refresh',
)}
>
<button onClick={() => this.refresh()}>
<MdRefresh className="refreshicon topicons" />
</button>
</Tooltip>
) : null}
<InfoTooltip
title="You can now sync your settings"
subtitle={'All settings, such as theme can now be synced across clients'}
linkURL={'https://www.youtube.com/watch?v=dQw4w9WgXcQ'}
linkText={'Learn more'}
>
<button onClick={() => this.props.openModal('mainModal')}>
<MdSettings className="settings-icon topicons" />
</button>
</InfoTooltip>
</div>
{/*<div className="notification">
<span className="title">New Update</span>
<span className="subtitle">
The newest update includes a lot of cheese plus urmm donate to david ralph on github.
</span>
<button>Learn More</button>
</div>*/}
</div>
);
if (localStorage.getItem('navbarHover') === 'true') {
return <div className='navbar-hover'>{navbar}</div>;
} else {
return navbar;
}
return localStorage.getItem('navbarHover') === 'true' ? (
<div className="navbar-hover">{navbar}</div>
) : (
navbar
);
}
}

View File

@ -1,39 +1,58 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { MdFileCopy, MdAssignment, MdPushPin } from 'react-icons/md';
import { PureComponent, useRef } from 'react';
import { MdContentCopy, MdAssignment, MdPushPin, MdDownload } from 'react-icons/md';
import { useFloating, shift } from '@floating-ui/react-dom';
import TextareaAutosize from '@mui/material/TextareaAutosize';
import { toast } from 'react-toastify';
//import Hotkeys from 'react-hot-keys';
import Tooltip from '../../helpers/tooltip/Tooltip'; //import Hotkeys from 'react-hot-keys';
import { saveFile } from 'modules/helpers/settings/modals';
export default class Notes extends PureComponent {
class Notes extends PureComponent {
constructor() {
super();
this.state = {
notes: localStorage.getItem('notes') || '',
visibility: (localStorage.getItem('notesPinned') === 'true') ? 'visible' : 'hidden',
marginLeft: (localStorage.getItem('refresh') === 'false') ? '-200px' : '-150px'
visibility: localStorage.getItem('notesPinned') === 'true' ? 'visible' : 'hidden',
showNotes: localStorage.getItem('notesPinned') === 'true' ? true : false,
};
}
setNotes = (e) => {
localStorage.setItem('notes', e.target.value);
this.setState({
notes: e.target.value
notes: e.target.value,
});
};
showNotes() {
this.setState({
showNotes: true,
});
}
hideNotes() {
if (localStorage.getItem('notesPinned') === 'true') {
this.setState({
showNotes: true,
});
} else {
this.setState({
showNotes: false,
});
}
}
pin() {
variables.stats.postEvent('feature', 'Notes pin');
if (localStorage.getItem('notesPinned') === 'true') {
localStorage.setItem('notesPinned', false);
this.setState({
visibility: 'hidden'
showNotes: false,
});
} else {
localStorage.setItem('notesPinned', true);
this.setState({
visibility: 'visible'
showNotes: true,
});
}
}
@ -44,19 +63,97 @@ export default class Notes extends PureComponent {
toast(variables.language.getMessage(variables.languagecode, 'toasts.notes'));
}
download() {
const notes = localStorage.getItem('notes');
if (!notes || notes === '') {
return;
}
variables.stats.postEvent('feature', 'Notes download');
saveFile(this.state.notes, 'mue-notes.txt', 'text/plain');
}
render() {
return (
<span className='notescontainer' style={{ visibility: this.state.visibility, marginLeft: this.state.marginLeft }}>
<div className='topbarnotes'>
<MdAssignment/>
<h3>{variables.language.getMessage(variables.languagecode, 'widgets.navbar.notes.title')}</h3>
</div>
<TextareaAutosize placeholder={variables.language.getMessage(variables.languagecode, 'widgets.navbar.notes.placeholder')} value={this.state.notes} onChange={this.setNotes}/>
<button onClick={() => this.pin()} className='pinNote'><MdPushPin/></button>
<button onClick={() => this.copy()} className='copyNote'><MdFileCopy/></button>
{/*variables.keybinds.pinNotes && variables.keybinds.pinNotes !== '' ? <Hotkeys keyName={variables.keybinds.pinNotes} onKeyDown={() => this.pin()}/> : null*/}
{/*variables.keybinds.copyNotes && variables.keybinds.copyNotes !== '' ? <Hotkeys keyName={variables.keybinds.copyNotes} onKeyDown={() => this.copy()}/> : null*/}
</span>
<div className="notes" onMouseLeave={() => this.hideNotes()} onFocus={() => this.showNotes()}>
<button
className="first"
onMouseEnter={() => this.showNotes()}
onFocus={() => this.showNotes()}
onBlur={() => this.hideNotes()}
ref={this.props.notesRef}
>
<MdAssignment className="topicons" />
</button>
{this.state.showNotes && (
<span
className="notesContainer"
ref={this.props.floatRef}
style={{
position: this.props.position,
top: this.props.yPosition ?? '44',
left: this.props.xPosition ?? '',
}}
>
<div className="flexNotes">
<div className="topBarNotes" style={{ display: 'flex' }}>
<MdAssignment />
<span>
{variables.language.getMessage(
variables.languagecode,
'widgets.navbar.notes.title',
)}
</span>
</div>
<div className="notes-buttons">
<Tooltip title="Pin">
<button onClick={() => this.pin()}>
<MdPushPin />
</button>
</Tooltip>
<Tooltip title="Copy">
<button onClick={() => this.copy()}>
<MdContentCopy />
</button>
</Tooltip>
<Tooltip title="Download">
<button onClick={() => this.download()}>
<MdDownload />
</button>
</Tooltip>
</div>
<TextareaAutosize
placeholder={variables.language.getMessage(
variables.languagecode,
'widgets.navbar.notes.placeholder',
)}
value={this.state.notes}
onChange={this.setNotes}
minRows={5}
/>
{/*variables.keybinds.pinNotes && variables.keybinds.pinNotes !== '' ? <Hotkeys keyName={variables.keybinds.pinNotes} onKeyDown={() => this.pin()}/> : null*/}
{/*variables.keybinds.copyNotes && variables.keybinds.copyNotes !== '' ? <Hotkeys keyName={variables.keybinds.copyNotes} onKeyDown={() => this.copy()}/> : null*/}
</div>
</span>
)}
</div>
);
}
}
export default function NotesWrapper() {
const { x, y, reference, floating, strategy } = useFloating({
placement: 'bottom',
middleware: [shift()],
});
return (
<Notes
notesRef={reference}
floatRef={floating}
position={strategy}
xPosition={x}
yPosition={y}
/>
);
}

View File

@ -0,0 +1,181 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { MdChecklist, MdPushPin, MdDelete, MdPlaylistAdd } from 'react-icons/md';
import TextareaAutosize from '@mui/material/TextareaAutosize';
import Tooltip from '../../helpers/tooltip/Tooltip';
import Checkbox from '@mui/material/Checkbox';
import { shift, useFloating } from '@floating-ui/react-dom';
//import Hotkeys from 'react-hot-keys';
class Todo extends PureComponent {
constructor() {
super();
this.state = {
todo: JSON.parse(localStorage.getItem('todoContent')) || [
{
value: '',
done: false,
},
],
visibility: localStorage.getItem('todoPinned') === 'true' ? 'visible' : 'hidden',
marginLeft: localStorage.getItem('refresh') === 'false' ? '-200px' : '-130px',
showTodo: localStorage.getItem('todoPinned') === 'true',
};
}
showTodo() {
this.setState({
showTodo: true,
});
}
hideTodo() {
if (localStorage.getItem('todoPinned') === 'true') {
this.setState({
showTodo: true,
});
} else {
this.setState({
showTodo: false,
});
}
}
updateTodoState(todoContent) {
localStorage.setItem('todoContent', JSON.stringify(todoContent));
this.setState({
todo: todoContent,
});
this.forceUpdate();
}
setTodo(index, data) {
let todoContent = this.state.todo;
todoContent[index] = {
value: data.target.value,
done: todoContent[index].done,
};
this.updateTodoState(todoContent);
}
addTodo() {
let todoContent = this.state.todo;
todoContent.push({
value: '',
done: false,
});
this.updateTodoState(todoContent);
}
removeTodo(index) {
let todoContent = this.state.todo;
todoContent.splice(index, 1);
if (todoContent.length === 0) {
todoContent.push({
value: '',
done: false,
});
}
this.updateTodoState(todoContent);
}
doneTodo(index) {
let todoContent = this.state.todo;
todoContent[index].done = !todoContent[index].done;
this.updateTodoState(todoContent);
}
pin() {
variables.stats.postEvent('feature', 'Todo pin');
if (localStorage.getItem('todoPinned') === 'true') {
localStorage.setItem('todoPinned', false);
this.setState({
showTodo: false,
});
} else {
localStorage.setItem('todoPinned', true);
this.setState({
showTodo: true,
});
}
}
render() {
return (
<div className="notes" onMouseLeave={() => this.hideTodo()} onFocus={() => this.showTodo()}>
<button
className="first"
onMouseEnter={() => this.showTodo()}
onFocus={() => this.hideTodo()}
onBlur={() => this.showTodo()}
ref={this.props.todoRef}
>
<MdChecklist className="topicons" />
</button>
{this.state.showTodo && (
<span
className="notesContainer"
ref={this.props.floatRef}
style={{
position: this.props.position,
top: this.props.yPosition ?? '44px',
left: this.props.xPosition ?? '',
}}
>
<div className="flexTodo">
<div className="topBarNotes" style={{ display: 'flex' }}>
<MdChecklist />
<span>Todo</span>
</div>
<div className="notes-buttons">
<Tooltip title="Pin">
<button onClick={() => this.pin()}>
<MdPushPin />
</button>
</Tooltip>
<Tooltip title={'Add'}>
<button onClick={() => this.addTodo()}>
<MdPlaylistAdd />
</button>
</Tooltip>
</div>
<div className={'todoRows'}>
{this.state.todo.map((value, index) => (
<div
className={'todoRow' + (this.state.todo[index].done ? ' done' : '')}
key={index}
>
<Checkbox
checked={this.state.todo[index].done}
onClick={() => this.doneTodo(index)}
/>
<TextareaAutosize
placeholder={variables.language.getMessage(
variables.languagecode,
'widgets.navbar.notes.placeholder',
)}
value={this.state.todo[index].value}
onChange={(data) => this.setTodo(index, data)}
readOnly={this.state.todo[index].done}
/>
<MdDelete onClick={() => this.removeTodo(index)} />
</div>
))}
</div>
</div>
</span>
)}
</div>
);
}
}
export default function TodoWrapper() {
const { x, y, reference, floating, strategy } = useFloating({
placement: 'bottom',
middleware: [shift()],
});
return (
<Todo todoRef={reference} floatRef={floating} position={strategy} xPosition={x} yPosition={y} />
);
}

View File

@ -1,3 +1,5 @@
@import 'scss/variables';
.notes {
position: relative;
@ -8,32 +10,68 @@
user-select: none;
}
&:hover .notescontainer {
&:hover .notesContainer {
visibility: visible !important;
}
&:active .notesContainer {
visibility: visible !important;
}
&:focus .notesContainer {
visibility: visible !important;
}
}
.notescontainer {
.notesContainer {
@extend %basic;
padding: 15px;
visibility: hidden;
background-color: var(--background);
color: var(--modal-text);
text-align: center;
border-radius: 12px;
position: absolute;
top: 80%;
margin-left: -150px;
max-height: 80vh !important;
font-size: 1rem !important;
/*top: 100%;
left: 50%;
margin-left: -130px;*/
textarea {
max-height: 65vh !important;
overflow-y: visible !important;
.notes-buttons {
button {
@include basicIconButton(11px, 1.3rem, ui);
@include themed() {
background: t($btn-background) !important;
&:hover {
background: t($btn-backgroundHover) !important;
}
}
flex-grow: 1;
display: grid;
place-items: center;
svg {
font-size: 1.3rem;
}
}
}
svg {
float: left;
font-size: 1em !important;
.flexNotes {
display: flex;
flex-flow: column;
gap: 15px;
textarea {
max-height: 65vh !important;
overflow-y: visible !important;
@extend %basic;
border: none;
padding: 15px;
border-radius: 12px;
width: 200px;
}
}
::placeholder {
@ -46,24 +84,29 @@ textarea {
border: none;
resize: none;
background: none;
width: 200px;
height: 100px;
margin: 10px;
}
.topbarnotes {
.topBarNotes {
@extend %basic;
display: flex;
align-items: center;
justify-content: center;
flex-flow: row;
height: 50px;
border-radius: 12px;
flex-flow: row;
gap: 5px;
svg {
font-size: 46px !important;
padding: 9px;
}
h3 {
font-size: 46px;
margin-right: 20px;
font-size: 1.5rem;
}
}
.dark textarea {
background-color: #2f3542;
color: white;
.notes-buttons {
display: flex !important;
gap: 10px;
.tooltip {
flex: 1 !important;
}
}

View File

@ -0,0 +1,64 @@
.notesContainer {
.flexTodo {
display: flex;
flex-flow: column;
gap: 15px;
width: 223px;
.todoRows {
max-height: 65vh !important;
overflow-y: visible !important;
display: flex;
flex-flow: column;
gap: 15px;
overflow-x: hidden;
}
}
}
.todoRow {
@include basicIconButton(1px, 16px, ui);
display: flex;
flex-flow: row;
align-items: center;
padding-right: 10px;
margin: 1px;
@include themed() {
color: t($color) !important;
}
textarea {
width: 120px;
@include themed() {
color: t($color) !important;
}
}
svg {
padding: 10px;
place-items: center;
display: grid;
cursor: pointer;
@include themed() {
color: t($color) !important;
}
&:hover {
@include themed() {
background: t($modal-sidebar);
border-radius: t($borderRadius);
}
}
}
}
.done {
text-decoration: line-through;
textarea {
cursor: not-allowed;
}
}

View File

@ -1,52 +1,87 @@
@import 'notes';
@import '../../../../scss/variables';
@import 'todo';
@import 'scss/variables';
.navbar-container {
position: absolute;
top: 1rem;
right: 1rem;
div {
display: inline;
}
img {
height: 1em;
width: auto;
margin: 0.5rem;
}
svg.topicons {
color: map-get($theme-colours, 'main-text-color');
.navbar {
display: flex;
flex-flow: row;
gap: 10px;
animation: fadeIn 2s;
button {
-webkit-font-smoothing: subpixel-antialiased;
font-size: 1em;
margin: 0.5rem;
filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.3));
/*filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.3));*/
cursor: pointer;
transition: 0.2s ease;
// transition: 0.2s ease;
margin-top: 0;
&:hover {
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
/* &:hover {
color: map-get($theme-colours, 'main-text-color');
transform: translateZ(0);
transform: scale(1.3);
}*/
}
}
.old {
.tooltip {
button {
@include basicIconButton(12px, 20px, legacy);
}
}
.first {
@include basicIconButton(12px, 20px, legacy);
}
}
.new {
button {
@include basicIconButton(12px, 20px, ui);
}
}
.navbar-hover {
position: absolute;
top: 0rem;
right: 0rem;
top: 0;
right: 0;
height: 50px;
width: 500px;
.navbar-container {
.navbar {
visibility: hidden;
}
&:hover {
.navbar-container {
.navbar {
visibility: visible;
}
}
}
.navbar-container {
position: absolute;
top: 1rem;
right: 1rem;
display: flex;
flex-flow: column;
.notification {
@extend %basic;
display: flex;
flex-flow: column;
gap: 15px;
padding: 15px;
text-align: left;
width: 200px;
button {
@include basicIconButton(10px, 14px, ui);
gap: 20px;
}
}
}

View File

@ -1,6 +1,7 @@
import variables from 'modules/variables';
import { PureComponent, createRef } from 'react';
import { TextareaAutosize } from '@mui/material';
import { MdAddToPhotos } from 'react-icons/md';
//import Hotkeys from 'react-hot-keys';
import Tooltip from 'components/helpers/tooltip/Tooltip';
@ -18,8 +19,9 @@ export default class QuickLinks extends PureComponent {
items: JSON.parse(localStorage.getItem('quicklinks')),
name: '',
url: '',
showAddLink: 'hidden',
urlError: ''
showAddLink: 'none',
nameError: '',
urlError: '',
};
this.quicklinksContainer = createRef();
}
@ -32,7 +34,7 @@ export default class QuickLinks extends PureComponent {
localStorage.setItem('quicklinks', JSON.stringify(data));
this.setState({
items: data
items: data,
});
variables.stats.postEvent('feature', 'Quicklink delete');
@ -41,18 +43,22 @@ export default class QuickLinks extends PureComponent {
addLink = () => {
const data = JSON.parse(localStorage.getItem('quicklinks'));
let url = this.state.url;
let urlError;
// regex: https://ihateregex.io/expr/url/
// eslint-disable-next-line no-useless-escape
if (url.length <= 0 || /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/.test(url) === false) {
if (
url.length <= 0 ||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/.test(
url,
) === false
) {
urlError = this.getMessage('widgets.quicklinks.url_error');
}
if (urlError) {
return this.setState({
urlError
urlError,
});
}
@ -64,7 +70,7 @@ export default class QuickLinks extends PureComponent {
name: this.state.name || url,
url: url,
icon: this.state.icon || '',
key: Math.random().toString(36).substring(7) + 1
key: Math.random().toString(36).substring(7) + 1,
});
localStorage.setItem('quicklinks', JSON.stringify(data));
@ -72,7 +78,7 @@ export default class QuickLinks extends PureComponent {
this.setState({
items: data,
name: '',
url: ''
url: '',
});
variables.stats.postEvent('feature', 'Quicklink add');
@ -81,31 +87,25 @@ export default class QuickLinks extends PureComponent {
// make sure image is correct size
this.setZoom(this.quicklinksContainer.current);
}
};
toggleAdd = () => {
this.setState({
showAddLink: (this.state.showAddLink === 'hidden') ? 'visible' : 'hidden'
showAddLink: this.state.showAddLink === 'none' ? 'flex' : 'none',
});
}
};
// widget zoom
setZoom(element) {
const zoom = localStorage.getItem('zoomQuicklinks') || 100;
if (localStorage.getItem('quicklinksText')) {
const links = element.getElementsByTagName('a');
for (const link of links) {
link.style.fontSize = `${0.87 * Number(zoom / 100)}em`;
for (const link of element.getElementsByTagName('a')) {
link.style.fontSize = `${1.4 * Number(zoom / 100)}em`;
}
} else {
for (const img of element.getElementsByTagName('img')) {
img.style.height = `${1.4 * Number(zoom / 100)}em`;
}
return;
}
const images = element.getElementsByTagName('img');
for (const img of images) {
img.style.height = `${0.87 * Number(zoom / 100)}em`;
}
}
@ -113,14 +113,14 @@ export default class QuickLinks extends PureComponent {
EventBus.on('refresh', (data) => {
if (data === 'quicklinks') {
if (localStorage.getItem('quicklinksenabled') === 'false') {
return this.quicklinksContainer.current.style.display = 'none';
return (this.quicklinksContainer.current.style.display = 'none');
}
this.quicklinksContainer.current.style.display = 'block';
this.setZoom(this.quicklinksContainer.current);
this.setState({
items: JSON.parse(localStorage.getItem('quicklinks'))
items: JSON.parse(localStorage.getItem('quicklinks')),
});
}
});
@ -136,66 +136,118 @@ export default class QuickLinks extends PureComponent {
this.addLink();
e.preventDefault();
}
}
};
componentWillUnmount() {
EventBus.off('refresh');
}
render() {
let target, rel = null;
let target,
rel = null;
if (localStorage.getItem('quicklinksnewtab') === 'true') {
target = '_blank';
rel = 'noopener noreferrer';
}
const tooltipEnabled = localStorage.getItem('quicklinkstooltip');
const useProxy = (localStorage.getItem('quicklinksddgProxy') !== 'false');
const useText = (localStorage.getItem('quicklinksText') === 'true');
const useProxy = localStorage.getItem('quicklinksddgProxy') !== 'false';
const useText = localStorage.getItem('quicklinksText') === 'true';
const quickLink = (item) => {
if (useText) {
return <a className='quicklinkstext' key={item.key} onContextMenu={(e) => this.deleteLink(item.key, e)} href={item.url} target={target} rel={rel} draggable={false}>{item.name}</a>;
return (
<a
className="quicklinkstext"
key={item.key}
onContextMenu={(e) => this.deleteLink(item.key, e)}
href={item.url}
target={target}
rel={rel}
draggable={false}
>
{item.name}
</a>
);
}
const url = useProxy ? 'https://icons.duckduckgo.com/ip2/' : 'https://www.google.com/s2/favicons?sz=32&domain=';
const img = item.icon || url + item.url.replace('https://', '').replace('http://', '') + (useProxy ? '.ico' : '');
const url = useProxy
? 'https://icons.duckduckgo.com/ip2/'
: 'https://www.google.com/s2/favicons?sz=32&domain=';
const img =
item.icon ||
url + item.url.replace('https://', '').replace('http://', '') + (useProxy ? '.ico' : '');
const link = (
<a key={item.key} onContextMenu={(e) => this.deleteLink(item.key, e)} href={item.url} target={target} rel={rel} draggable={false}>
<img src={img} alt={item.name} draggable={false}/>
<a
key={item.key}
onContextMenu={(e) => this.deleteLink(item.key, e)}
href={item.url}
target={target}
rel={rel}
draggable={false}
>
<img src={img} alt={item.name} draggable={false} />
</a>
);
if (tooltipEnabled === 'true') {
return <Tooltip title={item.name}>{link}</Tooltip>;
} else {
return link;
}
return tooltipEnabled === 'true' ? (
<Tooltip title={item.name} placement="top">
{link}
</Tooltip>
) : (
link
);
};
const marginTop = (this.state.items.length > 0) ? '9vh' : '4vh';
return (
<div className='quicklinks-container' ref={this.quicklinksContainer}>
{this.state.items.map((item) => (
quickLink(item)
))}
<button className='quicklinks' onClick={this.toggleAdd}>+</button>
<span className='quicklinkscontainer' style={{ visibility: this.state.showAddLink, marginTop }}>
<div className='topbarquicklinks' onKeyDown={this.topbarEnter}>
<h4>{this.getMessage('widgets.quicklinks.new')}</h4>
<TextareaAutosize rowsmax={1} placeholder={this.getMessage('widgets.quicklinks.name')} value={this.state.name} onChange={(e) => this.setState({ name: e.target.value })} />
<p/>
<TextareaAutosize rowsmax={10} placeholder={this.getMessage('widgets.quicklinks.url')} value={this.state.url} onChange={(e) => this.setState({ url: e.target.value })} />
<p>{this.state.urlError}</p>
<TextareaAutosize rowsmax={10} placeholder={this.getMessage('widgets.quicklinks.icon')} value={this.state.icon} onChange={(e) => this.setState({ icon: e.target.value })} />
<p></p>
<button className='pinNote' onClick={this.addLink}>{this.getMessage('widgets.quicklinks.add')}</button>
<>
<div className="quicklinkscontainer" ref={this.quicklinksContainer}>
{this.state.items.map((item) => quickLink(item))}
</div>
<div className="quicklinkscontainer">
<button className="quicklinks" onClick={this.toggleAdd}>
<MdAddToPhotos /> {this.getMessage('widgets.quicklinks.add')}
</button>
</div>
<div className="quicklinkscontainer">
<div
className="quicklinksdropdown"
onKeyDown={this.topbarEnter}
style={{ display: this.state.showAddLink }}
>
<span className="dropdown-title">{this.getMessage('widgets.quicklinks.new')}</span>
<span className="dropdown-subtitle">
{this.getMessage('widgets.quicklinks.new')} Description
</span>
<TextareaAutosize
maxRows={1}
placeholder={this.getMessage('widgets.quicklinks.name')}
value={this.state.name}
onChange={(e) => this.setState({ name: e.target.value })}
/>
<span className="dropdown-error" />
<TextareaAutosize
maxRows={10}
placeholder={this.getMessage('widgets.quicklinks.url')}
value={this.state.url}
onChange={(e) => this.setState({ url: e.target.value })}
/>
<span className="dropdown-error">{this.state.urlError}</span>
<TextareaAutosize
maxRows={10}
placeholder={this.getMessage('widgets.quicklinks.icon')}
value={this.state.icon}
onChange={(e) => this.setState({ icon: e.target.value })}
/>
<span className="dropdown-error" />
<button onClick={this.addLink}>
<MdAddToPhotos /> {this.getMessage('widgets.quicklinks.add')}
</button>
</div>
</span>
{/*variables.keybinds.toggleQuicklinks && variables.keybinds.toggleQuicklinks !== '' ? <Hotkeys keyName={variables.keybinds.toggleQuicklinks} onKeyDown={this.toggleAdd} /> : null*/}
</div>
{/*variables.keybinds.toggleQuicklinks && variables.keybinds.toggleQuicklinks !== '' ? <Hotkeys keyName={variables.keybinds.toggleQuicklinks} onKeyDown={this.toggleAdd} /> : null*/}
</div>
</>
);
}
}

View File

@ -1,48 +1,42 @@
.quicklinks {
position: relative;
user-select: none;
border: none;
color: #fff;
font-size: 42px;
background: none;
cursor: pointer;
text-shadow: 0 0 10px rgb(0 0 0 / 60%);
@import 'scss/variables';
h3 {
text-shadow: none;
margin: 0;
}
.quicklinks {
@include basicIconButton(10px, 14px, ui);
outline: none;
border: none;
box-shadow: 0 0 0 1px #484848;
border-radius: 12px;
color: #fff;
display: flex;
align-items: center;
justify-content: space-evenly;
font-size: 0.5em;
padding: 10px 40px 10px 40px;
gap: 15px;
}
.quicklinkscontainer {
padding: 15px;
background-color: var(--background);
color: var(--modal-text);
text-align: center;
border-radius: 12px;
position: absolute;
margin-left: -140px;
margin-top: 50px;
z-index: 1;
display: flex;
align-content: center;
justify-content: center;
gap: 12px;
svg {
float: left;
textarea {
@extend %basic;
border: none;
padding: 15px;
border-radius: 12px;
}
::placeholder {
color: #636e72;
opacity: 1;
}
}
textarea {
border: none;
width: 200px;
resize: none;
height: 100px;
margin: 10px;
}
.topbarquicklinks {
svg {
font-size: 46px;
@ -64,13 +58,8 @@ textarea {
}
}
.dark textarea {
background-color: #2f3542;
color: white;
}
.quicklinks-container>a,
.quicklinks-container>.quicklinks>button {
.quicklinks-container > a,
.quicklinks-container > .quicklinks > button {
display: inline;
}
@ -94,9 +83,65 @@ textarea {
.quicklinkstext {
text-decoration: none;
color: white;
font-size: smaller;
font-size: 0.8em;
&:hover {
text-decoration: underline;
}
}
/* background-color: var(--background);
color: var(--modal-text);*/
.quicklinksdropdown {
@extend %basic;
position: absolute;
z-index: 99;
display: none;
flex-flow: column;
padding: 15px;
box-shadow: 0 0 0 1px #484848;
gap: 5px;
&:hover {
.quicklinksdropdown {
display: block;
}
}
.dropdown-title {
font-size: 0.9em;
}
.dropdown-subtitle {
font-size: 0.4em;
@include themed() {
color: t($subColor);
}
}
button {
@include basicIconButton(10px, 0.9rem, ui);
border-radius: 12px;
border: none;
outline: none;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
}
}
.dropdown-error {
font-size: 0.4em;
}
button.quicklinks {
cursor: pointer;
}
.outOfScreen {
bottom: 515px;
}

View File

@ -1,10 +1,20 @@
import variables from 'modules/variables';
import { PureComponent, createRef } from 'react';
import { MdContentCopy, MdStarBorder, MdStar } from 'react-icons/md';
import { FaTwitter } from 'react-icons/fa';
import {
MdContentCopy,
MdStarBorder,
MdStar,
MdPerson,
MdOpenInNew,
MdIosShare,
} from 'react-icons/md';
import { toast } from 'react-toastify';
//import Hotkeys from 'react-hot-keys';
import Tooltip from '../../helpers/tooltip/Tooltip';
import ShareModal from '../../helpers/sharemodal/ShareModal';
import Interval from 'modules/helpers/interval';
import EventBus from 'modules/helpers/eventbus';
@ -12,22 +22,48 @@ import './quote.scss';
export default class Quote extends PureComponent {
buttons = {
tweet: <FaTwitter className='copyButton' onClick={() => this.tweetQuote()} />,
copy: <MdContentCopy className='copyButton' onClick={() => this.copyQuote()} />,
unfavourited: <MdStarBorder className='copyButton' onClick={() => this.favourite()} />,
favourited: <MdStar className='copyButton' onClick={() => this.favourite()} />
}
share: (
<Tooltip title="Share">
<button onClick={() => this.shareQuote()}>
<MdIosShare className="copyButton" />
</button>
</Tooltip>
),
copy: (
<Tooltip title="Copy">
<button onClick={() => this.copyQuote()}>
<MdContentCopy className="copyButton" />
</button>
</Tooltip>
),
unfavourited: (
<Tooltip title="Favourite">
<button onClick={() => this.favourite()}>
<MdStarBorder className="copyButton" />
</button>
</Tooltip>
),
favourited: (
<Tooltip title="Unfavourite">
<button onClick={() => this.favourite()}>
<MdStar className="copyButton" />
</button>
</Tooltip>
),
};
constructor() {
super();
this.state = {
quote: '',
author: '',
authorOccupation: '',
favourited: this.useFavourite(),
tweet: (localStorage.getItem('tweetButton') === 'false') ? null : this.buttons.tweet,
copy: (localStorage.getItem('copyButton') === 'false') ? null : this.buttons.copy,
share: localStorage.getItem('quoteShareButton') === 'false' ? null : this.buttons.share,
copy: localStorage.getItem('copyButton') === 'false' ? null : this.buttons.copy,
quoteLanguage: '',
type: localStorage.getItem('quoteType') || 'api'
type: localStorage.getItem('quoteType') || 'api',
shareModal: false
};
this.quote = createRef();
this.quotediv = createRef();
@ -35,11 +71,13 @@ export default class Quote extends PureComponent {
}
useFavourite() {
let favouriteQuote = null;
if (localStorage.getItem('favouriteQuoteEnabled') === 'true') {
favouriteQuote = localStorage.getItem('favouriteQuote') ? this.buttons.favourited : this.buttons.unfavourited;
return localStorage.getItem('favouriteQuote')
? this.buttons.favourited
: this.buttons.unfavourited;
} else {
return null;
}
return favouriteQuote;
}
doOffline() {
@ -51,28 +89,77 @@ export default class Quote extends PureComponent {
this.setState({
quote: '"' + quote.quote + '"',
author: quote.author,
authorlink: this.getAuthorLink(quote.author)
authorlink: this.getAuthorLink(quote.author),
});
}
getAuthorLink(author) {
let authorlink = `https://${variables.languagecode.split('_')[0]}.wikipedia.org/wiki/${author.split(' ').join('_')}`;
if (localStorage.getItem('authorLink') === 'false' || author === 'Unknown') {
authorlink = null;
return localStorage.getItem('authorLink') === 'false' || author === 'Unknown'
? null
: `https://${variables.languagecode.split('_')[0]}.wikipedia.org/wiki/${author
.split(' ')
.join('_')}`;
}
async getAuthorImg(author) {
const authorimgdata = await (
await fetch(
`https://en.wikipedia.org/w/api.php?action=query&titles=${author}&origin=*&prop=pageimages&format=json&pithumbsize=100`,
)
).json();
let authorimg, authorimglicense;
try {
authorimg =
authorimgdata.query.pages[Object.keys(authorimgdata.query.pages)[0]].thumbnail.source;
const authorimglicensedata = await (
await fetch(
`https://en.wikipedia.org/w/api.php?action=query&prop=imageinfo&iiprop=extmetadata&titles=File:${
authorimgdata.query.pages[Object.keys(authorimgdata.query.pages)[0]].pageimage
}&origin=*&format=json`,
)
).json();
const license =
authorimglicensedata.query.pages[Object.keys(authorimglicensedata.query.pages)[0]]
.imageinfo[0].extmetadata.LicenseShortName;
const photographer =
authorimglicensedata.query.pages[Object.keys(authorimglicensedata.query.pages)[0]]
.imageinfo[0].extmetadata.Attribution || 'Unknown';
authorimglicense = `© ${photographer.value}. ${license.value}`;
if (license.value === 'Public domain') {
authorimglicense = null;
} else if (photographer.value === 'Unknown' || !photographer) {
authorimglicense = null;
authorimg = null;
}
} catch (e) {
authorimg = null;
authorimglicense = null;
}
return authorlink;
if (author === 'Unknown') {
authorimg = null;
authorimglicense = null;
}
return {
authorimg,
authorimglicense,
};
}
async getQuote() {
const offline = (localStorage.getItem('offlineMode') === 'true');
const offline = localStorage.getItem('offlineMode') === 'true';
const favouriteQuote = localStorage.getItem('favouriteQuote');
if (favouriteQuote) {
let author = favouriteQuote.split(' - ')[1];
const authorimgdata = await this.getAuthorImg(author);
return this.setState({
quote: favouriteQuote.split(' - ')[0],
author: favouriteQuote.split(' - ')[1],
authorlink: this.getAuthorLink(favouriteQuote.split(' - ')[1])
author,
authorlink: this.getAuthorLink(author),
authorimg: authorimgdata.authorimg,
authorimglicense: authorimgdata.authorimglicense,
});
}
@ -83,21 +170,31 @@ export default class Quote extends PureComponent {
customQuote = JSON.parse(localStorage.getItem('customQuote'));
} catch (e) {
// move to new format
customQuote = [{
quote: localStorage.getItem('customQuote'),
author: localStorage.getItem('customQuoteAuthor')
}];
customQuote = [
{
quote: localStorage.getItem('customQuote'),
author: localStorage.getItem('customQuoteAuthor'),
},
];
localStorage.setItem('customQuote', JSON.stringify(customQuote));
}
// pick random
customQuote = customQuote ? customQuote[Math.floor(Math.random() * customQuote.length)] : null;
customQuote = customQuote
? customQuote[Math.floor(Math.random() * customQuote.length)]
: null;
if (customQuote && customQuote !== '' && customQuote !== 'undefined' && customQuote !== ['']) {
if (
customQuote &&
customQuote !== '' &&
customQuote !== 'undefined' &&
customQuote !== ['']
) {
return this.setState({
quote: '"' + customQuote.quote + '"',
author: customQuote.author,
authorlink: this.getAuthorLink(customQuote.author)
authorlink: this.getAuthorLink(customQuote.author),
authorimg: await this.getAuthorImg(customQuote.author),
});
}
break;
@ -111,28 +208,34 @@ export default class Quote extends PureComponent {
try {
const data = await (await fetch(quotePackAPI.url)).json();
const author = data[quotePackAPI.author] || quotePackAPI.author;
const authorimgdata = await this.getAuthorImg(author);
return this.setState({
quote: '"' + data[quotePackAPI.quote] + '"',
author: author,
authorlink: this.getAuthorLink(author)
author,
authorimg: authorimgdata.authorimg,
authorimglicense: authorimgdata.authorimglicense,
});
} catch (e) {
return this.doOffline();
}
}
let quotePack = localStorage.getItem('quote_packs');
if (quotePack !== null) {
quotePack = JSON.parse(quotePack);
if (quotePack) {
const data = quotePack[Math.floor(Math.random() * quotePack.length)];
const authorimgdata = await this.getAuthorImg(data.author);
return this.setState({
quote: '"' + data.quote + '"',
author: data.author,
authorlink: this.getAuthorLink(data.author)
authorlink: this.getAuthorLink(data.author),
authorimg: authorimgdata.authorimg,
authorimglicense: authorimgdata.authorimglicense,
});
} else {
return this.doOffline();
@ -147,24 +250,31 @@ export default class Quote extends PureComponent {
// First we try and get a quote from the API...
try {
const quotelanguage = localStorage.getItem('quotelanguage');
const data = await (await fetch(variables.constants.API_URL + '/quotes/random?language=' + quotelanguage)).json();
const data = await (
await fetch(variables.constants.API_URL + '/quotes/random?language=' + quotelanguage)
).json();
// If we hit the ratelimit, we fallback to local quotes
// If we hit the ratelimit, we fall back to local quotes
if (data.statusCode === 429) {
return this.doOffline();
}
const authorimgdata = await this.getAuthorImg(data.author);
const object = {
quote: '"' + data.quote + '"',
author: data.author,
authorlink: this.getAuthorLink(data.author),
quoteLanguage: quotelanguage
authorimg: authorimgdata.authorimg,
authorimglicense: authorimgdata.authorimglicense,
quoteLanguage: quotelanguage,
authorOccupation: data.author_occupation,
};
this.setState(object);
localStorage.setItem('currentQuote', JSON.stringify(object));
} catch (e) {
// ..and if that fails we load one locally
// ...and if that fails we load one locally
this.doOffline();
}
break;
@ -181,19 +291,24 @@ export default class Quote extends PureComponent {
tweetQuote() {
variables.stats.postEvent('feature', 'Quote tweet');
window.open(`https://twitter.com/intent/tweet?text=${this.state.quote} - ${this.state.author} on @getmue`, '_blank').focus();
window
.open(
`https://twitter.com/intent/tweet?text=${this.state.quote} - ${this.state.author} on @getmue`,
'_blank',
)
.focus();
}
favourite() {
if (localStorage.getItem('favouriteQuote')) {
localStorage.removeItem('favouriteQuote');
this.setState({
favourited: this.buttons.unfavourited
favourited: this.buttons.unfavourited,
});
} else {
localStorage.setItem('favouriteQuote', this.state.quote + ' - ' + this.state.author);
this.setState({
favourited: this.buttons.favourited
favourited: this.buttons.favourited,
});
}
@ -205,8 +320,12 @@ export default class Quote extends PureComponent {
const quoteType = localStorage.getItem('quoteType');
if (this.state.type !== quoteType || localStorage.getItem('quotelanguage') !== this.state.quoteLanguage || (quoteType === 'custom' && this.state.quote !== localStorage.getItem('customQuote'))
|| (quoteType === 'custom' && this.state.author !== localStorage.getItem('customQuoteAuthor'))) {
if (
this.state.type !== quoteType ||
localStorage.getItem('quotelanguage') !== this.state.quoteLanguage ||
(quoteType === 'custom' && this.state.quote !== localStorage.getItem('customQuote')) ||
(quoteType === 'custom' && this.state.author !== localStorage.getItem('customQuoteAuthor'))
) {
this.getQuote();
}
}
@ -221,7 +340,7 @@ export default class Quote extends PureComponent {
EventBus.on('refresh', (data) => {
if (data === 'quote') {
if (localStorage.getItem('quote') === 'false') {
return this.quotediv.current.style.display = 'none';
return (this.quotediv.current.style.display = 'none');
}
this.quotediv.current.style.display = 'block';
@ -230,8 +349,8 @@ export default class Quote extends PureComponent {
// buttons hot reload
this.setState({
favourited: this.useFavourite(),
tweet: (localStorage.getItem('tweetButton') === 'false') ? null : this.buttons.tweet,
copy: (localStorage.getItem('copyButton') === 'false') ? null : this.buttons.copy
share: localStorage.getItem('quoteShareButton') === 'false' ? null : this.buttons.share,
copy: localStorage.getItem('copyButton') === 'false' ? null : this.buttons.copy,
});
}
@ -247,10 +366,14 @@ export default class Quote extends PureComponent {
const interval = localStorage.getItem('quotechange');
if (interval && interval !== 'refresh' && localStorage.getItem('quoteType') === 'api') {
Interval(() => {
this.setZoom();
this.getQuote();
}, Number(interval), 'quote');
Interval(
() => {
this.setZoom();
this.getQuote();
},
Number(interval),
'quote',
);
try {
this.setState(JSON.parse(localStorage.getItem('currentQuote')));
@ -271,13 +394,62 @@ export default class Quote extends PureComponent {
render() {
return (
<div className='quotediv' ref={this.quotediv}>
<h1 className='quote' ref={this.quote}>{this.state.quote}</h1>
<h1 className='quoteauthor' ref={this.quoteauthor}>
<a href={this.state.authorlink} className='quoteauthorlink' target='_blank' rel='noopener noreferrer'>{this.state.author}</a>
<br/>
{this.state.copy} {this.state.tweet} {this.state.favourited}
</h1>
<div className="quotediv" ref={this.quotediv}>
<span className="quote" ref={this.quote}>
{this.state.quote}
</span>
{localStorage.getItem('widgetStyle') === 'legacy' ? (
<>
<div>
<h1 className="quoteauthor" ref={this.quoteauthor}>
<a
href={this.state.authorlink}
className="quoteAuthorLink"
target="_blank"
rel="noopener noreferrer"
>
{this.state.author}
</a>
</h1>
</div>
<div style={{ display: 'flex', justifyContent: 'center', gap: '20px' }}>
{this.state.copy} {this.state.share} {this.state.favourited}
</div>
</>
) : (
<div className="author-holder">
<div className="author">
<div
className="author-img"
style={{ backgroundImage: `url(${this.state.authorimg})` }}
>
{this.state.authorimg === undefined || this.state.authorimg ? '' : <MdPerson />}
</div>
<div className="author-content" ref={this.quoteauthor}>
<span className="title">{this.state.author}</span>
{this.state.authorOccupation !== 'Unknown' ? (
<span className="subtitle">{this.state.authorOccupation}</span>
) : null}
<span className="author-license">{this.state.authorimglicense}</span>
</div>
<div className="quote-buttons">
{this.state.authorOccupation !== 'Unknown' ? (
<Tooltip title="Open">
<a
href={this.state.authorlink}
className="quoteAuthorLink"
target="_blank"
rel="noopener noreferrer"
>
<MdOpenInNew />
</a>{' '}
</Tooltip>
) : null}
{this.state.copy} {this.state.share} {this.state.favourited}
</div>
</div>
</div>
)}
{/*variables.keybinds.favouriteQuote && variables.keybinds.favouriteQuote !== '' ? <Hotkeys keyName={variables.keybinds.favouriteQuote} onKeyDown={() => this.favourite()} /> : null*/}
{/*variables.keybinds.tweetQuote && variables.keybinds.tweetQuote !== '' ? <Hotkeys keyName={variables.keybinds.tweetQuote} onKeyDown={() => this.tweetQuote()} /> : null*/}
{/*variables.keybinds.copyQuote && variables.keybinds.copyQuote !== '' ? <Hotkeys keyName={variables.keybinds.copyQuote} onKeyDown={() => this.copyQuote()} /> : null*/}

Some files were not shown because too many files have changed in this diff Show More