Cleanup, refactor and new features

This commit is contained in:
David Ralph
2020-11-29 14:32:08 +00:00
parent 87dd07c45c
commit 82e1d7684a
37 changed files with 462 additions and 421 deletions

View File

@@ -19,6 +19,7 @@ export default class Checkbox extends React.PureComponent {
render() {
let text = this.props.text;
if (this.props.newFeature) text = <span>{this.props.text} <span className='newFeature'> NEW</span></span>;
else if (this.props.betaFeature) text = <span>{this.props.text} <span className='newFeature'> BETA</span></span>;
return (
<React.Fragment>

View File

@@ -12,7 +12,7 @@ export default class Section extends React.PureComponent {
}
toggleSection() {
const display = (this.state.display === 'none') ? 'block': 'none';
const display = (this.state.display === 'none') ? 'block' : 'none';
this.setState({
display: display,
@@ -26,7 +26,11 @@ export default class Section extends React.PureComponent {
let extraHTML, expandMore, slider, noDropdown;
if (this.props.children) {
extraHTML = <li className='extraSettings'>{this.props.children}</li>
extraHTML = (
<div style={{ display: this.state.display }}>
<li className='extraSettings'>{this.props.children}</li>
</div>
);
expandMore = (
<ExpandMore
style={{ 'transition': 'all 0.5s ease 0s', 'transform': this.state.transform }}
@@ -35,16 +39,14 @@ export default class Section extends React.PureComponent {
);
}
if (this.props.slider !== false) slider = <Slider name={this.props.name} />;
if (this.props.noDropdown) noDropdown = 'nodropdown';
if (this.props.dropdown === false) noDropdown = 'nodropdown';
return (
<div className='section'>
<h4 className={noDropdown} onClick={() => this.toggleSection()}>{this.props.title}</h4>
{expandMore}
{slider}
<div style={{ display: this.state.display }}>
{extraHTML}
</div>
{extraHTML}
</div>
);
}

View File

@@ -194,6 +194,7 @@ export default class BackgroundSettings extends React.PureComponent {
<Checkbox name='view' text={this.props.language.background.view} />
<Checkbox name='favouriteEnabled' text={this.props.language.background.favourite} />
<Checkbox name='refresh' text={this.props.language.background.refresh} />
<Checkbox name='webp' text={this.props.language.experimental.webp} betaFeature={true} />
</ul>
<ul>
<Dropdown

View File

@@ -3,8 +3,9 @@ import SettingsFunctions from '../../../../modules/helpers/settings';
import { toast } from 'react-toastify';
import Section from '../Section';
import Dropdown from '../Dropdown';
import Checkbox from '../Checkbox';
const searchEngines = require('../../../../modules/json/search_engines.json');
const searchEngines = require('../../../widgets/search/search_engines.json');
export default class SearchSettings extends React.PureComponent {
resetSearch() {
@@ -27,6 +28,9 @@ export default class SearchSettings extends React.PureComponent {
render() {
return (
<Section title={this.props.language.searchbar.title} name='searchBar'>
<ul>
<Checkbox name='voiceSearch' text={this.props.language.experimental.voice_search} />
</ul>
<ul>
<Dropdown label={this.props.language.searchbar.search_engine}
name='searchEngine'
@@ -39,7 +43,7 @@ export default class SearchSettings extends React.PureComponent {
</Dropdown>
</ul>
<ul id='searchEngineInput' style={{ display: 'none' }}>
<p style={{ "marginTop": "0px" }}>{this.props.language.searchbar.custom} <span className='modalLink' onClick={() => this.resetSearch()}>{this.props.language.reset}</span></p>
<p style={{ 'marginTop': '0px' }}>{this.props.language.searchbar.custom} <span className='modalLink' onClick={() => this.resetSearch()}>{this.props.language.reset}</span></p>
<input type='text' id='customSearchEngine'></input>
</ul>
</Section>

View File

@@ -9,8 +9,12 @@ import SearchSettings from '../settings/sections/SearchSettings';
import LanguageSettings from '../settings/sections/LanguageSettings';
export default class Settings extends React.PureComponent {
resetGreeting() {
document.getElementById('greetingName').value = '';
resetItem(type) {
switch (type) {
case 'greeting': document.getElementById('greetingName').value = ''; break;
case 'css': document.getElementById('customcss').value = ''; break;
default: break;
}
toast(this.props.toastLanguage.reset);
}
@@ -18,6 +22,7 @@ export default class Settings extends React.PureComponent {
// Settings
document.getElementById('greetingName').value = localStorage.getItem('greetingName');
document.getElementById('language').value = localStorage.getItem('language');
document.getElementById('customcss').value = localStorage.getItem('customcss');
if (document.getElementById('modal').classList.contains('dark')) { // Dark theme support for dropdowns
const choices = document.getElementsByClassName('choices');
@@ -45,13 +50,14 @@ export default class Settings extends React.PureComponent {
<Checkbox name='ampm' text={this.props.language.time.ampm} />
<Checkbox name='zero' text={this.props.language.time.zero} />
<Checkbox name='analog' text={this.props.language.time.analog} />
<Checkbox name='percentageComplete' text={this.props.language.time.percentageComplete} />
</Section>
<Section title={this.props.language.greeting.title} name='greeting'>
<Checkbox name='events' text={this.props.language.greeting.events} />
<Checkbox name='defaultGreetingMessage' text={this.props.language.greeting.default} />
<ul>
<p>{this.props.language.greeting.name} <span className='modalLink' onClick={() => this.resetGreeting()}>{this.props.language.reset}</span></p>
<p>{this.props.language.greeting.name} <span className='modalLink' onClick={() => this.resetItem('greeting')}>{this.props.language.reset}</span></p>
<input type='text' id='greetingName'></input>
</ul>
</Section>
@@ -63,17 +69,17 @@ export default class Settings extends React.PureComponent {
</Section>
<BackgroundSettings language={this.props.language} toastLanguage={this.props.toastLanguage} />
<SearchSettings language={this.props.language} toastLanguage={this.props.toastLanguage} />
<Section title={this.props.language.offline} name='offlineMode' dropdown={false} />
<Section title={this.props.language.offline} name='offlineMode' noDropdown={true} />
<Section title={this.props.language.dark} name='darkTheme' noDropdown={true} />
<Section title={this.props.language.experimental.title} name='experimental' slider={false}>
<Checkbox name='webp' text={this.props.language.experimental.webp} />
<Checkbox name='animations' text={this.props.language.experimental.animations} />
<Checkbox name='voiceSearch' text={this.props.language.experimental.voice_search} newFeature={true} />
<Checkbox name='brightnessTime' text={this.props.language.experimental.night_mode} newFeature={true} />
<Section title='Appearance' name='appearance' slider={false}>
<Checkbox name='animations' text={this.props.language.experimental.animations} betaFeature={true} />
<Checkbox name='darkTheme' text={this.props.language.dark} />
<Checkbox name='brightnessTime' text={this.props.language.experimental.night_mode} betaFeature={true} />
<ul>
<p>Custom CSS <span className='modalLink' onClick={() => this.resetItem('css')}>{this.props.language.reset}</span> <span className='newFeature'> NEW</span></p>
<textarea id='customcss'></textarea>
</ul>
</Section>
<LanguageSettings language={this.props.language} />

View File

@@ -1,5 +1,7 @@
import React from 'react';
import * as Constants from '../../modules/constants';
import * as Constants from '../../../modules/constants';
import './scss/index.scss';
export default class Background extends React.PureComponent {
gradientStyleBuilder(gradientSettings) {
@@ -46,7 +48,7 @@ export default class Background extends React.PureComponent {
}
doOffline() { // Handles setting the background if the user is offline
const offlineImages = require('../../modules/json/offline_images.json');
const offlineImages = require('./offline_images.json');
const photographers = Object.keys(offlineImages); // Get all photographers from the keys in offlineImages.json
const photographer = photographers[Math.floor(Math.random() * photographers.length)]; // Select a random photographer from the keys
const randomImage = offlineImages[photographer].photo[
@@ -67,7 +69,6 @@ export default class Background extends React.PureComponent {
const customBackgroundColour = localStorage.getItem('customBackgroundColour');
const customBackground = localStorage.getItem('customBackground');
const favourited = JSON.parse(localStorage.getItem('favourite'));
const customBackgroundVideo = localStorage.getItem('customBackgroundVideo');
if (favourited) {
if (offlineMode === 'true') return this.doOffline();
@@ -82,13 +83,13 @@ export default class Background extends React.PureComponent {
document.getElementById('location').textContent = randomPhoto.location;
}
else if (customBackgroundColour) this.setBackground(null, customBackgroundColour, 'false');
else if (customBackground !== '') this.setBackground(customBackground, null, 'false'); // Local
else if (customBackgroundVideo) {
document.getElementById('backgroundImage').innerHTML = `
<video autoplay muted loop id="backgroundVideo">
<source src="${customBackgroundVideo}"/>
</video>`;
document.querySelector('.photoInformation').style.display = 'none'; // Hide the credit
else if (customBackground !== '') {
if (customBackground.includes('.mp4') || customBackground.includes('.webm') || customBackground.includes('.ogg')) {
document.getElementById('backgroundImage').innerHTML = `
<video autoplay muted loop id='backgroundVideo'>
<source src='${customBackground}'/>
</video>`;
} else this.setBackground(customBackground, null, 'false'); // Local
} else { // Online
if (offlineMode === 'true') return this.doOffline();
try { // First we try and get an image from the API...
@@ -96,9 +97,7 @@ export default class Background extends React.PureComponent {
let requestURL;
switch (localStorage.getItem('backgroundAPI')) {
case 'unsplash':
requestURL = `${Constants.UNSPLASH_URL}/getImage`;
break;
case 'unsplash': requestURL = `${Constants.UNSPLASH_URL}/getImage`; break;
default: // Defaults to Mue
if (localStorage.getItem('supportswebp') === 'true' && enabled === 'true') requestURL = `${Constants.API_URL}/getImage?webp=true`;
else requestURL = `${Constants.API_URL}/getImage?category=Outdoors`;

View File

@@ -0,0 +1,20 @@
{
"Tirachard Kumtanom" : {
"photo": [1]
},
"Sohail Na": {
"photo": [4]
},
"Miriam Espacio": {
"photo": [7]
},
"Fabian Wiktor": {
"photo": [20]
},
"Pixabay": {
"photo": [2, 3, 9, 11, 13, 14, 15]
},
"Unknown" : {
"photo": [5, 6, 8, 10, 12, 16, 17, 18, 19]
}
}

View File

@@ -0,0 +1,86 @@
.photoInformation {
position: absolute;
bottom: 1rem;
left: 1rem;
font-size: calc(10px + 0.1vmin) !important;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
svg {
float: left;
vertical-align: middle;
margin-right: 1rem;
font-size: calc(10px + 2vmin);
}
span {
display: none;
}
h1 {
position: relative;
}
svg,
h1 {
display: inline;
}
svg:hover+.infoCard {
display: block;
}
}
.infoCard {
display: none;
background: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
color: #000;
position: fixed;
bottom: 3.25rem;
left: 0.7em;
padding: 1rem;
border-radius: 24px 24px 24px 0;
max-width: 500px;
text-align: left;
text-shadow: none;
span {
display: block;
}
svg {
margin-right: 0.5rem;
vertical-align: middle;
display: inline-flex;
padding: 2px;
}
span,
svg {
font-size: 2em;
}
h1,
.infoIcon {
font-size: 3em;
}
.infoIcon {
padding: 4px;
}
hr {
height: 3px;
background: #2d3436;
outline: none;
border: none;
}
}
.infoCard:hover {
display: block !important;
}
.photoInformationHover {
cursor: pointer;
}

View File

@@ -0,0 +1,69 @@
@import 'photoinformation';
@import '../../../../scss/mixins';
#backgroundImage {
height: 100vh;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
background-attachment: fixed;
}
#backgroundVideo {
position: fixed;
right: 0;
bottom: 0;
min-width: 100%;
min-height: 100%;
}
.fade-in {
@include animation('fadein 2s');
}
@include keyframes(fadein) {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.backgroundEffects {
opacity: 0.7;
transition: ease 0.6s;
}
.creditlink {
text-decoration: none;
color: white;
}
.view {
bottom: 10px;
right: 25px;
}
.favourite {
bottom: 10px;
right: 50px;
padding-right: 5px;
}
.view,
.favourite {
position: absolute;
transition: all 0.5s ease 0s;
&:hover {
transform: scale(1.1);
}
}
#viewButton,
#favouriteButton {
width: auto;
cursor: pointer;
}

View File

@@ -1,5 +1,7 @@
import React from 'react';
import './greeting.scss';
export default class Greeting extends React.PureComponent {
constructor(...args) {
super(...args);

View File

@@ -0,0 +1,4 @@
.greeting {
margin: 0;
font-size: 1.6em;
}

View File

@@ -5,9 +5,11 @@ import NewReleases from '@material-ui/icons/NewReleasesRounded';
import NotesIcon from '@material-ui/icons/AssignmentRounded';
import Tooltip from '@material-ui/core/Tooltip';
import Report from '@material-ui/icons/SmsFailed';
import * as Constants from '../modules/constants';
import * as Constants from '../../../modules/constants';
const Notes = React.lazy(() => import('./widgets/Notes')); // the user probably won't use the notes feature every time so we lazy load
import './scss/index.scss';
const Notes = React.lazy(() => import('./Notes')); // the user probably won't use the notes feature every time so we lazy load
const renderLoader = () => <div></div>;
export default class Navbar extends React.PureComponent {

View File

@@ -1,7 +1,7 @@
import React from 'react';
import TextareaAutosize from '@material-ui/core/TextareaAutosize';
import CopyIcon from '@material-ui/icons/FileCopyRounded';
import Pin from './icons/Pin';
import Pin from './Pin';
import NotesIcon from '@material-ui/icons/AssignmentRounded';
export default class Notes extends React.PureComponent {

View File

@@ -0,0 +1,80 @@
.notes {
position: relative;
display: inline-block;
h3 {
text-shadow: none;
margin: 0;
}
}
.notes .notescontainer {
text-align: center;
padding: 15px;
visibility: hidden;
background-color: #fff;
color: #000;
text-align: center;
border-radius: 12px;
position: absolute;
z-index: 1;
top: 80%;
left: 50%;
margin-left: -125px;
input[type=text] {
border: none;
color: #2d3436;
}
svg,
input[type=text] {
display: inline-flex;
}
svg {
float: left;
}
::placeholder {
color: #636e72;
opacity: 1;
}
}
.notes:hover .notescontainer {
visibility: visible;
}
textarea {
overflow: none;
border: none;
outline: none;
width: 200px;
resize: none;
height: 100px;
margin: 10px;
}
.noteIcon {
display: inline;
font-size: 1em;
float: none;
}
.topbarnotes {
text-align: center;
svg {
font-size: 48px;
float: left !important;
padding: 9px;
}
h3 {
font-size: 48px;
float: right;
margin-right: 20px;
}
}

View File

@@ -0,0 +1,71 @@
@import 'notes';
@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');
-webkit-font-smoothing: subpixel-antialiased;
font-size: 1em;
display: inline;
margin: 0.5rem;
filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.3));
cursor: pointer;
transition: .2s ease;
&:hover {
color: map-get($theme-colours, 'main-text-color');
transform: translateZ(0);
transform: scale(1.3);
}
}
}
.bottom-navbar {
position: absolute;
bottom: 1rem;
right: 1rem;
div {
display: inline;
}
img {
height: 1em;
width: auto;
margin: 0.5rem;
}
svg.topicons {
color: map-get($theme-colours, 'main-text-color');
-webkit-font-smoothing: subpixel-antialiased;
font-size: 1em;
display: inline;
margin: 0.5rem;
filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.3));
cursor: pointer;
transition: .2s ease;
&:hover {
transform: translateZ(0);
transform: scale(1.3);
}
}
}
.visibilityshow {
visibility: visible !important;
}

View File

@@ -2,11 +2,13 @@ import React from 'react';
import Quotes from '@muetab/quotes';
import FileCopy from '@material-ui/icons/FilterNone';
import { toast } from 'react-toastify';
import * as Constants from '../../modules/constants';
import * as Constants from '../../../modules/constants';
import TwitterIcon from '@material-ui/icons/Twitter';
import StarIcon from '@material-ui/icons/Star';
import StarIcon2 from '@material-ui/icons/StarBorder';
import './quote.scss';
export default class Quote extends React.PureComponent {
constructor(...args) {
super(...args);

View File

@@ -0,0 +1,59 @@
@import '../../../scss/variables';
.quote {
font-size: 0.8em;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
@media screen and (min-width: 600px) {
.quote {
margin-left: 30%;
margin-right: 30%;
}
}
.quoteauthor {
font-size: 0.9em;
letter-spacing: 0.5px;
margin: 0;
}
.quoteAuthor,
.copyButton {
display: inline;
font-size: 0.8em;
position: relative !important;
display: block;
margin: 0 auto;
}
.copyButton {
cursor: pointer;
vertical-align: middle;
float: middle;
margin: 0 auto;
text-align: right;
transition: ease 0.2s !important;
&:hover {
transform: scale(1.1);
}
}
i.material-icons,
h1.quoteauthor {
display: inline;
}
button.copyButton {
background: transparent;
border: none;
color: map-get($theme-colours, 'main');
padding: 20px 20px;
text-align: center;
text-decoration: none;
font-size: 20px;
cursor: pointer;
border-radius: 5px;
display: table-cell
}

View File

@@ -2,7 +2,9 @@ import React from 'react';
import SearchIcon from '@material-ui/icons/Search';
import MicIcon from '@material-ui/icons/Mic';
const searchEngines = require('../../modules/json/search_engines.json');
import './search.scss';
const searchEngines = require('./search_engines.json');
export default class Search extends React.PureComponent {
constructor(...args) {

View File

@@ -0,0 +1,48 @@
@import '../../../scss/variables';
.searchbar {
position: absolute;
left: 20px;
top: 20px;
display: flex;
flex-direction: row;
display: block;
color: map-get($theme-colours, 'main-text-color');
input[type=text] {
width: 140px;
margin-left: 12px;
border-radius: 24px;
font-size: calc(5px + 1.2vmin);
background: transparent;
border: none;
position: absolute;
background-color: rgba(0, 0, 0, 0.1);
-webkit-transition: width 0.5s ease-in-out;
transition: width 0.5s ease-in-out;
&:focus {
width: 400px;
background-color: rgba(0, 0, 0, .3);
}
}
.MuiSvgIcon-root {
margin-top: 4px;
font-size: 30px;
filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.3));
cursor: pointer;
}
}
.input.searchtext {
border: none;
}
.micIcon {
margin-right: 10px;
}
#searchEngine {
width: 130px;
}

View File

@@ -0,0 +1,48 @@
[
{
"name": "DuckDuckGo",
"settingsName": "duckduckgo",
"url": "https://duckduckgo.com"
},
{
"name": "Google",
"settingsName": "google",
"url": "https://google.com/search"
},
{
"name": "Bing",
"settingsName": "bing",
"url": "https://bing.com/search"
},
{
"name": "Yahoo",
"settingsName": "yahoo",
"url": "https://search.yahoo.com/search"
},
{
"name": "Ecosia",
"settingsName": "ecosia",
"url": "https://ecosia.org/search"
},
{
"name": "Яндекс",
"settingsName": "yandex",
"url": "https://yandex.ru/search",
"query": "text"
},
{
"name": "Qwant",
"settingsName": "qwant",
"url": "https://www.qwant.com/"
},
{
"name": "Ask",
"settingsName": "ask",
"url": "https://ask.com/web"
},
{
"name": "Startpage",
"settingsName": "startpage",
"url": "https://www.startpage.com/sp/search"
}
]

View File

@@ -1,6 +1,8 @@
import React from 'react';
import Analog from 'react-clock';
import './clock.scss';
export default class Clock extends React.PureComponent {
constructor(...args) {
super(...args);
@@ -17,14 +19,12 @@ export default class Clock extends React.PureComponent {
const now = new Date();
// Percentage
if (localStorage.getItem('percentageComplete') === 'true') {
return this.setState({ time: (now.getHours() / 24).toFixed(2) + '%'})
}
if (localStorage.getItem('percentageComplete') === 'true') return this.setState({ time: (now.getHours() / 24).toFixed(2).replace('0.', '') + '%'});
// Analog clock
if (localStorage.getItem('analog') === 'true') this.setState({ time: now });
else {
let sec = '';
let time, sec = '';
// Extra 0
const zero = localStorage.getItem('zero');
@@ -35,7 +35,6 @@ export default class Clock extends React.PureComponent {
}
if (localStorage.getItem('24hour') === 'true') {
let time = '';
if (zero === 'false') time = `${now.getHours()}:${now.getMinutes()}${sec}`;
else time = `${('00' + now.getHours()).slice(-2)}:${('00' + now.getMinutes()).slice(-2)}${sec}`;
@@ -49,7 +48,6 @@ export default class Clock extends React.PureComponent {
let ampm = now.getHours() > 11 ? 'PM' : 'AM';
if (localStorage.getItem('ampm') === 'false') ampm = '';
let time = '';
if (zero === 'false') time = `${hours}:${now.getMinutes()}${sec}`;
else time = `${('00' + hours).slice(-2)}:${('00' + now.getMinutes()).slice(-2)}${sec}`;

View File

@@ -11,15 +11,16 @@ export default class DateWidget extends React.PureComponent {
getDate() {
const date = new Date();
const dateFormat = localStorage.getItem('dateFormat');
if (localStorage.getItem('dateFormat') === 'short') {
if (dateFormat === 'short') {
const dateDay = date.getDate();
const dateMonth = date.getMonth() + 1;
const dateYear = date.getFullYear();
let day = dateDay, month = dateMonth, year = dateYear;
switch (localStorage.getItem('dateFormat')) {
switch (dateFormat) {
case 'MDY':
day = dateMonth;
month = dateDay;
@@ -28,8 +29,7 @@ export default class DateWidget extends React.PureComponent {
day = dateYear;
year = dateDay;
break;
default: // DMY
break;
default: break; // DMY
}
let format;
@@ -58,6 +58,6 @@ export default class DateWidget extends React.PureComponent {
}
render() {
return <span style={{ 'textTransform': 'capitalize', 'fontWeight': 'bold'}}>{this.state.date}</span>
return <span style={{ 'textTransform': 'capitalize', 'fontWeight': 'bold' }}>{this.state.date}</span>
}
}

View File

@@ -0,0 +1,24 @@
@import '../../../scss/variables';
.clock {
font-size: 4em;
margin: 0;
}
.ampm {
font-size: 0.5em;
}
.analogclock,
.react-clock__face {
margin: 0 auto;
border-radius: 100%;
box-shadow: inset 0 0 100px rgba(0, 0, 0, 0.3);
border: 1px solid map-get($theme-colours, "main");
}
.react-clock__hand__body {
background: map-get($theme-colours, "main");
;
box-shadow: 0 0 25px rgba(0, 0, 0, 0.3);
}