7.x Structural Changes (#637)

* refactor(files): Initial commit on experimental file structure

* refactor(structure): New components system

* refactor(structure): Tidy settings' components

* Refactor(structure): Component exports and imports

* refactor(settings): Use new component imports

* feat: unified background.js script

* fix(build): Partially, distributions still not ready

* feat: critical error on noscript, light theme support for it

* fix(background): Critical issue of code making every background #000

* refactor(welcome): Partition into different files + shared components
- This took too long and destroyed my sanity

---------

Co-authored-by: alexsparkes <turbomarshmello@gmail.com>
Co-authored-by: alexsparkes <alexsparkes@gmail.com>
This commit is contained in:
David Ralph
2024-02-18 23:05:15 +00:00
committed by GitHub
parent 8fc6b1bf1b
commit 10f12b20c5
233 changed files with 979 additions and 426 deletions

View File

@@ -0,0 +1,516 @@
import variables from 'config/variables';
import { PureComponent, createRef } from 'react';
import {
MdContentCopy,
MdStarBorder,
MdStar,
MdPerson,
MdOpenInNew,
MdIosShare,
} from 'react-icons/md';
import { toast } from 'react-toastify';
import { Tooltip } from 'components/Elements';
import Modal from 'react-modal';
import { ShareModal } from 'components/Elements';
import offline_quotes from './offline_quotes.json';
import EventBus from 'modules/helpers/eventbus';
import './quote.scss';
export default class Quote extends PureComponent {
buttons = {
share: (
<Tooltip title={variables.getMessage('widgets.quote.share')}>
<button
onClick={() => this.setState({ shareModal: true })}
aria-label={variables.getMessage('widgets.quote.share')}
>
<MdIosShare className="copyButton" />
</button>
</Tooltip>
),
copy: (
<Tooltip title={variables.getMessage('widgets.quote.copy')}>
<button
onClick={() => this.copyQuote()}
aria-label={variables.getMessage('widgets.quote.copy')}
>
<MdContentCopy className="copyButton" />
</button>
</Tooltip>
),
unfavourited: (
<Tooltip title={variables.getMessage('widgets.quote.favourite')}>
<button
onClick={() => this.favourite()}
aria-label={variables.getMessage('widgets.quote.favourite')}
>
<MdStarBorder className="copyButton" />
</button>
</Tooltip>
),
favourited: (
<Tooltip title={variables.getMessage('widgets.quote.unfavourite')}>
<button
onClick={() => this.favourite()}
aria-label={variables.getMessage('widgets.quote.unfavourite')}
>
<MdStar className="copyButton" />
</button>
</Tooltip>
),
};
constructor() {
super();
this.state = {
quote: '',
author: '',
authorOccupation: '',
favourited: this.useFavourite(),
share: localStorage.getItem('quoteShareButton') === 'false' ? null : this.buttons.share,
copy: localStorage.getItem('copyButton') === 'false' ? null : this.buttons.copy,
quoteLanguage: '',
type: localStorage.getItem('quoteType') || 'api',
shareModal: false,
};
this.quote = createRef();
this.quotediv = createRef();
this.quoteauthor = createRef();
}
useFavourite() {
if (localStorage.getItem('favouriteQuoteEnabled') === 'true') {
return localStorage.getItem('favouriteQuote')
? this.buttons.favourited
: this.buttons.unfavourited;
} else {
return null;
}
}
doOffline() {
// Get a random quote from our local JSON
const quote = offline_quotes[Math.floor(Math.random() * offline_quotes.length)];
this.setState({
quote: '"' + quote.quote + '"',
author: quote.author,
authorlink: this.getAuthorLink(quote.author),
authorimg: '',
});
}
getAuthorLink(author) {
return localStorage.getItem('authorLink') === 'false' || author === 'Unknown'
? null
: `https://${variables.languagecode.split('_')[0]}.wikipedia.org/wiki/${author
.split(' ')
.join('_')}`;
}
stripHTML(html) {
const tmpdoc = new DOMParser().parseFromString(html, 'text/html');
return tmpdoc.body.textContent || '';
}
async getAuthorImg(author) {
if (localStorage.getItem('authorImg') === 'false') {
return {
authorimg: null,
authorimglicense: null,
};
}
const authorimgdata = await (
await fetch(
`https://${
variables.languagecode.split('_')[0]
}.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://${
variables.languagecode.split('_')[0]
}.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 metadata =
authorimglicensedata.query.pages[Object.keys(authorimglicensedata.query.pages)[0]]
.imageinfo[0].extmetadata;
const license = metadata.LicenseShortName;
const photographer =
metadata.Attribution.value ||
this.stripHTML(metadata.Artist?.value || '').replace(/ \(talk\)/, '') || // talk page link (if applicable) is only removed for English
'Unknown';
authorimglicense = `© ${photographer}. ${license.value}`;
authorimglicense = authorimglicense.replace(/copyright\s/i, '').replace(/©\s©\s/, '© ');
if (license.value === 'Public domain') {
authorimglicense = null;
} else if (photographer.value === 'Unknown' || !photographer) {
authorimglicense = null;
authorimg = null;
}
} catch (e) {
authorimg = null;
authorimglicense = null;
}
if (author === 'Unknown') {
authorimg = null;
authorimglicense = null;
}
return {
authorimg,
authorimglicense,
};
}
async getQuote() {
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,
authorlink: this.getAuthorLink(author),
authorimg: authorimgdata.authorimg,
authorimglicense: authorimgdata.authorimglicense,
});
}
switch (this.state.type) {
case 'custom':
let customQuote;
try {
customQuote = JSON.parse(localStorage.getItem('customQuote'));
} catch (e) {
// move to new format
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;
if (customQuote !== undefined) {
return this.setState({
quote: '"' + customQuote.quote + '"',
author: customQuote.author,
authorlink: this.getAuthorLink(customQuote.author),
authorimg: await this.getAuthorImg(customQuote.author),
noQuote: false,
});
} else {
this.setState({
noQuote: true,
});
}
break;
case 'quote_pack':
if (offline) {
return this.doOffline();
}
const quotePack = [];
const installed = JSON.parse(localStorage.getItem('installed'));
installed.forEach((item) => {
if (item.type === 'quotes') {
const quotes = item.quotes.map((quote) => ({
...quote,
fallbackauthorimg: item.icon_url,
}));
quotePack.push(...quotes);
}
});
if (quotePack) {
const data = quotePack[Math.floor(Math.random() * quotePack.length)];
return this.setState({
quote: '"' + data.quote + '"',
author: data.author,
authorlink: this.getAuthorLink(data.author),
authorimg: data.fallbackauthorimg,
});
} else {
return this.doOffline();
}
case 'api':
if (offline) {
return this.doOffline();
}
const getAPIQuoteData = async () => {
const quoteLanguage = localStorage.getItem('quoteLanguage');
const data = await (
await fetch(variables.constants.API_URL + '/quotes/random?language=' + quoteLanguage)
).json();
// If we hit the ratelimit, we fall back to local quotes
if (data.statusCode === 429) {
return null;
}
const authorimgdata = await this.getAuthorImg(data.author);
return {
quote: '"' + data.quote.replace(/\s+$/g, '') + '"',
author: data.author,
authorlink: this.getAuthorLink(data.author),
authorimg: authorimgdata.authorimg,
authorimglicense: authorimgdata.authorimglicense,
quoteLanguage: quoteLanguage,
authorOccupation: data.author_occupation,
};
};
// First we try and get a quote from the API...
try {
let data = JSON.parse(localStorage.getItem('nextQuote')) || (await getAPIQuoteData());
localStorage.setItem('nextQuote', null);
if (data) {
this.setState(data);
localStorage.setItem('currentQuote', JSON.stringify(data));
localStorage.setItem('nextQuote', JSON.stringify(await getAPIQuoteData())); // pre-fetch data about the next quote
} else {
this.doOffline();
}
} catch (e) {
// ...and if that fails we load one locally
this.doOffline();
}
break;
default:
break;
}
}
copyQuote() {
variables.stats.postEvent('feature', 'Quote copied');
navigator.clipboard.writeText(`${this.state.quote} - ${this.state.author}`);
toast(variables.getMessage('toasts.quote'));
}
favourite() {
if (localStorage.getItem('favouriteQuote')) {
localStorage.removeItem('favouriteQuote');
this.setState({
favourited: this.buttons.unfavourited,
});
} else {
localStorage.setItem('favouriteQuote', this.state.quote + ' - ' + this.state.author);
this.setState({
favourited: this.buttons.favourited,
});
}
variables.stats.postEvent('feature', 'Quote favourite');
}
init() {
this.setZoom();
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'))
) {
this.getQuote();
}
}
setZoom() {
const zoomQuote = Number((localStorage.getItem('zoomQuote') || 100) / 100);
this.quote.current.style.fontSize = `${0.8 * zoomQuote}em`;
this.quoteauthor.current.style.fontSize = `${0.9 * zoomQuote}em`;
}
componentDidMount() {
// const test = localStorage.getItem('quotechange');
// this.interval = setInterval(() => {
// if (test !== null) {
// const targetTime = Number(
// Number(localStorage.getItem('quoteStartTime')) +
// Number(localStorage.getItem('quotechange')),
// );
// const currentTime = Number(Date.now());
// if (currentTime >= targetTime) {
// this.setZoom();
// this.getQuote();
// localStorage.setItem('quoteStartTime', Date.now());
// } else {
// try {
// this.setState(JSON.parse(localStorage.getItem('currentQuote')));
// } catch (e) {
// this.setZoom();
// this.getQuote();
// }
// }
// }
// });
this.setZoom();
EventBus.on('refresh', (data) => {
if (data === 'quote') {
if (localStorage.getItem('quote') === 'false') {
return (this.quotediv.current.style.display = 'none');
}
this.quotediv.current.style.display = 'block';
this.init();
// buttons hot reload
this.setState({
favourited: this.useFavourite(),
share: localStorage.getItem('quoteShareButton') === 'false' ? null : this.buttons.share,
copy: localStorage.getItem('copyButton') === 'false' ? null : this.buttons.copy,
});
}
// uninstall quote pack reverts the quote to what you had previously
if (data === 'marketplacequoteuninstall') {
this.init();
}
if (data === 'quoterefresh') {
this.getQuote();
}
});
if (
localStorage.getItem('quotechange') === 'refresh' ||
localStorage.getItem('quotechange') === null
) {
this.setZoom();
this.getQuote();
localStorage.setItem('quoteStartTime', Date.now());
}
}
componentWillUnmount() {
EventBus.off('refresh');
clearInterval(this.interval);
}
render() {
if (this.state.noQuote === true) {
return <></>;
}
return (
<div className="quotediv" ref={this.quotediv}>
<Modal
closeTimeoutMS={300}
isOpen={this.state.shareModal}
className="Modal mainModal"
overlayClassName="Overlay"
ariaHideApp={false}
onRequestClose={() => this.setState({ shareModal: false })}
>
<ShareModal
data={`${this.state.quote} - ${this.state.author}`}
modalClose={() => this.setState({ shareModal: false })}
/>
</Modal>
<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"
aria-label="Learn about the author of the quote."
>
{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>
{this.state.author !== '' ? (
<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>
)}
<span className="author-license">
{this.state.authorimglicense &&
this.state.authorimglicense.replace(' undefined. ', ' ')}
</span>
</div>
) : (
<div className="author-content whileLoading" ref={this.quoteauthor}>
{/* these are placeholders for skeleton and as such don't need translating */}
<span className="title">loading</span>
<span className="subtitle">loading</span>
</div>
)}
<div className="quote-buttons">
{this.state.authorOccupation !== 'Unknown' && this.state.authorlink !== '' ? (
<Tooltip title={variables.getMessage('widgets.quote.link_tooltip')}>
<a
href={this.state.authorlink}
className="quoteAuthorLink"
target="_blank"
rel="noopener noreferrer"
aria-label="Learn about the author of the quote."
>
<MdOpenInNew />
</a>{' '}
</Tooltip>
) : null}
{this.state.copy} {this.state.share} {this.state.favourited}
</div>
</div>
</div>
)}
</div>
);
}
}

View File

@@ -0,0 +1,102 @@
[
{
"author": "Robert De Niro",
"quote": "Time goes on. So whatever youre going to do, do it. Do it now. Dont wait."
},
{
"author": "Walt Disney",
"quote": "All our dreams can come true, if we have the courage to pursue them."
},
{
"author": "Confucius",
"quote": "It does not matter how slowly you go as long as you do not stop."
},
{
"author": "Roy T. Bennett",
"quote": "Believe in yourself. You are braver than you think, more talented than you know, and capable of more than you imagine."
},
{
"author": "Wayne Dyer",
"quote": "If you believe it will work out, youll see opportunities. If you believe it wont, you will see obstacles."
},
{
"author": "George Addair",
"quote": "Everything youve ever wanted is on the other side of fear."
},
{
"author": "Winston Churchill",
"quote": "Success is not final, failure is not fatal: it is the courage to continue that counts."
},
{
"author": "Paulo Coelho",
"quote": "There is only one thing that makes a dream impossible to achieve: the fear of failure"
},
{
"author": "Brian Tracy",
"quote": "Your true success in life begins only when you make the commitment to become excellent at what you do."
},
{
"author": "Chantal Sutherland",
"quote": "Believe in yourself, take on your challenges, dig deep within yourself to conquer fears. Never let anyone bring you down. You got to keep going."
},
{
"author": "Les Brown",
"quote": "Too many of us are not living our dreams because we are living our fears."
},
{
"author": "Bob Riley",
"quote": "Hard times dont create heroes. It is during the hard times when the hero within us is revealed."
},
{
"author": "Jack Canfield",
"quote": "If you can tune into your purpose and really align with it, setting goals so that your vision is an expression of that purpose, then life flows much more easily."
},
{
"author": "Napolean Hill",
"quote": "Whatever the mind can conceive and believe, it can achieve."
},
{
"author": "Jim Rohn",
"quote": "Dont wish it were easier. Wish you were better."
},
{
"author": "Serena Williams",
"quote": "A champion is defined not by their wins but by how they can recover when they fall."
},
{
"author": "Sheryl Sandberg",
"quote": "Motivation comes from working on things we care about."
},
{
"author": "Reese Witherspoon",
"quote": "With the right kind of coaching and determination you can accomplish anything."
},
{
"author": "Hazrat Inayat Khan",
"quote": "Some people look for a beautiful place. Others make a place beautiful."
},
{
"author": "Albert Einstein",
"quote": "Life is like riding a bicycle. To keep your balance, you must keep moving."
},
{
"author": "Walt Disney",
"quote": "The way to get started is to quit talking and begin doing."
},
{
"author": "Winston Churchill",
"quote": "A pessimist sees the difficulty in every opportunity; an optimist sees the opportunity in every difficulty."
},
{
"author": "Will Rogers",
"quote": "Don't let yesterday take up too much of today."
},
{
"author": "Vince Lombardi",
"quote": "It's not whether you get knocked down, it's whether you get up."
},
{
"author": "Steve Jobs",
"quote": "If you are working on something that you really care about, you dont have to be pushed. The vision pulls you."
}
]

View File

@@ -0,0 +1,168 @@
@import 'scss/variables';
.quote {
font-size: 0.8em;
text-shadow: 0 0 10px rgb(0 0 0 / 30%);
cursor: initial;
user-select: none;
--shadow-shift: 0.125rem;
color: #fff;
font-weight: 600;
width: 40vw;
}
.quoteauthor {
font-size: 0.9em;
letter-spacing: 0.5px;
user-select: none;
--shadow-shift: 0.125rem;
svg {
margin-left: 10px;
filter: drop-shadow(0 0 6px rgb(0 0 0 / 30%));
}
}
i.material-icons,
h1.quoteauthor {
display: inline;
}
.quoteAuthorLink {
text-decoration: none;
color: white;
user-select: none;
}
.author {
@extend %basic;
display: flex;
flex-flow: row;
height: 70px;
font-weight: 300;
margin: 10px;
}
.author-name {
font-size: clamp(15px, 2.5vw, 0.6em);
}
.author-knownfor {
font-size: clamp(13px, 2.5vw, 0.4em);
@include themed {
color: t($subColor);
}
}
.author-license {
font-size: clamp(8px, 2.5vw, 0.1em);
@include themed {
color: t($subColor);
}
}
.author img {
height: 100%;
width: auto;
border-radius: 12px 0 0 12px;
}
.author-img {
@extend %basic;
height: 100%;
width: 70px;
border-radius: 12px 0 0 12px;
display: flex;
align-items: center;
justify-content: center;
background-size: cover !important;
background-repeat: no-repeat !important;
background-position: initial;
}
.author-content {
display: flex;
flex-flow: column;
justify-content: center;
align-items: flex-start;
padding: 20px;
text-align: left;
}
.author-holder {
display: flex;
align-items: center;
justify-content: center;
flex-flow: column;
animation: fadeIn 1s;
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.quote-buttons {
display: flex;
align-items: center;
gap: 10px;
padding: 20px 20px 20px 0;
a {
display: flex;
}
a {
@include basicIconButton(11px, 1.3rem, ui);
}
}
.quotediv {
animation: fadeIn 1s;
display: flex;
flex-direction: column;
gap: 10px;
align-items: center;
button {
@include basicIconButton(11px, 1.3rem, ui);
}
}
.deleteButton {
height: auto !important;
@include basicIconButton(11px, 1.3rem, modal);
padding: 10px 20px;
}
.author-content.whileLoading {
@include themed {
gap: 5px;
.title {
color: transparent;
width: 100px;
background: t($modal-sidebar);
}
.subtitle {
color: transparent;
width: 50px;
background: t($modal-sidebarActive);
}
}
}