refactor(widgets): Continue move to feature based organisation

This commit is contained in:
alexsparkes
2024-02-27 16:38:16 +00:00
parent 8bf70eff81
commit 6041372860
157 changed files with 64 additions and 61 deletions

View File

@@ -0,0 +1,123 @@
import { PureComponent, createRef } from 'react';
import { Tooltip } from 'components/Elements';
import EventBus from 'utils/eventbus';
import './quicklinks.scss';
class QuickLinks extends PureComponent {
constructor() {
super();
this.state = {
items: JSON.parse(localStorage.getItem('quicklinks')),
};
this.quicklinksContainer = createRef();
}
// widget zoom
setZoom(element) {
const zoom = localStorage.getItem('zoomQuicklinks') || 100;
for (const link of element.getElementsByTagName('span')) {
link.style.fontSize = `${14 * Number(zoom / 100)}px`;
}
if (localStorage.getItem('quickLinksStyle') !== 'text') {
for (const img of element.getElementsByTagName('img')) {
img.style.height = `${30 * Number(zoom / 100)}px`;
}
}
}
componentDidMount() {
EventBus.on('refresh', (data) => {
if (data === 'quicklinks') {
if (localStorage.getItem('quicklinksenabled') === 'false') {
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')),
});
}
});
this.setZoom(this.quicklinksContainer.current);
}
componentWillUnmount() {
EventBus.off('refresh');
}
render() {
let target,
rel = null;
if (localStorage.getItem('quicklinksnewtab') === 'true') {
target = '_blank';
rel = 'noopener noreferrer';
}
const tooltipEnabled = localStorage.getItem('quicklinkstooltip');
const quickLink = (item) => {
if (localStorage.getItem('quickLinksStyle') === 'text') {
return (
<a
className="quicklinkstext"
key={item.key}
href={item.url}
target={target}
rel={rel}
draggable={false}
>
{item.name}
</a>
);
}
const img =
item.icon ||
'https://icon.horse/icon/ ' + item.url.replace('https://', '').replace('http://', '');
if (localStorage.getItem('quickLinksStyle') === 'metro') {
return (
<a
className="quickLinksMetro"
key={item.key}
href={item.url}
target={target}
rel={rel}
draggable={false}
>
<img src={img} alt={item.name} draggable={false} />
<span className="subtitle">{item.name}</span>
</a>
);
}
const link = (
<a key={item.key} href={item.url} target={target} rel={rel} draggable={false}>
<img src={img} alt={item.name} draggable={false} />
</a>
);
return tooltipEnabled === 'true' ? (
<Tooltip title={item.name} placement="bottom" key={item.key}>
{link}
</Tooltip>
) : (
link
);
};
return (
<div className="quicklinkscontainer" ref={this.quicklinksContainer}>
{this.state.items.map((item) => quickLink(item))}
</div>
);
}
}
export { QuickLinks as default, QuickLinks };

View File

@@ -0,0 +1,2 @@
export * from './options';
export * from './QuickLinks';

View File

@@ -0,0 +1,68 @@
import variables from 'config/variables';
import { MdEdit, MdCancel } from 'react-icons/md';
const QuickLink = ({ item, deleteLink, startEditLink }) => {
let target,
rel = null;
if (localStorage.getItem('quicklinksnewtab') === 'true') {
target = '_blank';
rel = 'noopener noreferrer';
}
const useText = localStorage.getItem('quicklinksText') === 'true';
if (useText) {
return (
<a
className="quicklinkstext"
onContextMenu={(e) => deleteLink(item.key, e)}
href={item.url}
target={target}
rel={rel}
draggable={false}
>
{item.name}
</a>
);
}
const img =
item.icon ||
'https://icon.horse/icon/ ' + item.url.replace('https://', '').replace('http://', '');
return (
<div className="messageMap">
<div className="icon">
<img
src={img}
alt={item.name}
draggable={false}
style={{ height: '30px', width: '30px' }}
/>
</div>
<div className="messageText">
<div className="title">{item.name}</div>
<div className="subtitle">
<a className="quicklinknostyle" target="_blank" rel="noopener noreferrer" href={item.url}>
{item.url}
</a>
</div>
</div>
<div>
<div className="messageAction">
<button className="deleteButton" onClick={() => startEditLink(item)}>
{variables.getMessage('modals.main.settings.sections.quicklinks.edit')}
<MdEdit />
</button>
<button className="deleteButton" onClick={(e) => deleteLink(item.key, e)}>
{variables.getMessage('modals.main.marketplace.product.buttons.remove')}
<MdCancel />
</button>
</div>
</div>
</div>
);
};
export { QuickLink as default, QuickLink };

View File

@@ -0,0 +1,275 @@
import variables from 'config/variables';
import { PureComponent, createRef } from 'react';
import { MdAddLink, MdLinkOff } from 'react-icons/md';
import { Header, Row, Content, Action, PreferencesWrapper } from 'components/Layout/Settings';
import { Checkbox, Dropdown } from 'components/Form/Settings';
import { Button } from 'components/Elements';
import Modal from 'react-modal';
import { AddModal } from 'components/Elements/AddModal';
import EventBus from 'utils/eventbus';
import { QuickLink } from './QuickLink';
import { getTitleFromUrl, isValidUrl } from 'utils/links';
class QuickLinksOptions extends PureComponent {
constructor() {
super();
this.state = {
items: JSON.parse(localStorage.getItem('quicklinks')),
showAddModal: false,
urlError: '',
iconError: '',
edit: false,
editData: '',
};
this.quicklinksContainer = createRef();
}
deleteLink(key, event) {
event.preventDefault();
// remove link from array
const data = JSON.parse(localStorage.getItem('quicklinks')).filter((i) => i.key !== key);
localStorage.setItem('quicklinks', JSON.stringify(data));
this.setState({
items: data,
});
variables.stats.postEvent('feature', 'Quicklink delete');
}
async addLink(name, url, icon) {
const data = JSON.parse(localStorage.getItem('quicklinks'));
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = 'http://' + url;
}
if (url.length <= 0 || isValidUrl(url) === false) {
return this.setState({
urlError: variables.getMessage('widgets.quicklinks.url_error'),
});
}
if (icon.length > 0 && isValidUrl(icon) === false) {
return this.setState({
iconError: variables.getMessage('widgets.quicklinks.url_error'),
});
}
data.push({
name: name || (await getTitleFromUrl(url)),
url,
icon: icon || '',
key: Math.random().toString(36).substring(7) + 1,
});
localStorage.setItem('quicklinks', JSON.stringify(data));
this.setState({
items: data,
showAddModal: false,
urlError: '',
iconError: '',
});
variables.stats.postEvent('feature', 'Quicklink add');
}
startEditLink(data) {
this.setState({
edit: true,
editData: data,
showAddModal: true,
});
}
async editLink(og, name, url, icon) {
const data = JSON.parse(localStorage.getItem('quicklinks'));
const dataobj = data.find((i) => i.key === og.key);
dataobj.name = name || (await getTitleFromUrl(url));
dataobj.url = url;
dataobj.icon = icon || '';
localStorage.setItem('quicklinks', JSON.stringify(data));
this.setState({
items: data,
showAddModal: false,
edit: false,
});
}
componentDidMount() {
EventBus.on('refresh', (data) => {
if (data === 'quicklinks') {
if (localStorage.getItem('quicklinksenabled') === 'false') {
return (this.quicklinksContainer.current.style.display = 'none');
}
this.quicklinksContainer.current.style.display = 'block';
this.setState({
items: JSON.parse(localStorage.getItem('quicklinks')),
});
}
});
}
componentWillUnmount() {
EventBus.off('refresh');
}
render() {
const QUICKLINKS_SECTION = 'modals.main.settings.sections.quicklinks';
const AdditionalSettings = () => {
return (
<Row>
<Content
title={variables.getMessage('modals.main.settings.additional_settings')}
subtitle={variables.getMessage(`${QUICKLINKS_SECTION}.additional`)}
/>
<Action>
<Checkbox
name="quicklinksnewtab"
text={variables.getMessage(`${QUICKLINKS_SECTION}.open_new`)}
category="quicklinks"
/>
<Checkbox
name="quicklinkstooltip"
text={variables.getMessage(`${QUICKLINKS_SECTION}.tooltip`)}
category="quicklinks"
/>
</Action>
</Row>
);
};
const StylingOptions = () => {
return (
<Row>
<Content
title={variables.getMessage(`${QUICKLINKS_SECTION}.styling`)}
subtitle={variables.getMessage(
'modals.main.settings.sections.quicklinks.styling_description',
)}
/>
<Action>
<Dropdown
label={variables.getMessage(`${QUICKLINKS_SECTION}.style`)}
name="quickLinksStyle"
category="quicklinks"
items={[
{
value: 'icon',
text: variables.getMessage(`${QUICKLINKS_SECTION}.options.icon`),
},
{
value: 'text',
text: variables.getMessage(`${QUICKLINKS_SECTION}.options.text_only`),
},
{
value: 'metro',
text: variables.getMessage(`${QUICKLINKS_SECTION}.options.metro`),
},
]}
/>
</Action>
</Row>
);
};
const AddLink = () => {
return (
<Row final={true}>
<Content title={variables.getMessage(`${QUICKLINKS_SECTION}.title`)} />
<Action>
<Button
type="settings"
onClick={() => this.setState({ showAddModal: true })}
icon={<MdAddLink />}
label={variables.getMessage(`${QUICKLINKS_SECTION}.add_link`)}
/>
</Action>
</Row>
);
};
return (
<>
<Header
title={variables.getMessage(`${QUICKLINKS_SECTION}.title`)}
setting="quicklinksenabled"
category="quicklinks"
element=".quicklinks-container"
zoomSetting="zoomQuicklinks"
visibilityToggle={true}
/>
<PreferencesWrapper
setting="quicklinksenabled"
visibilityToggle={true}
zoomSetting="zoomQuicklinks"
>
<AdditionalSettings />
<StylingOptions />
<AddLink />
{this.state.items.length === 0 && (
<div className="photosEmpty">
<div className="emptyNewMessage">
<MdLinkOff />
<span className="title">
{variables.getMessage(`${QUICKLINKS_SECTION}.no_quicklinks`)}
</span>
<span className="subtitle">
{variables.getMessage('modals.main.settings.sections.message.add_some')}
</span>
<Button
type="settings"
onClick={() => this.setState({ showAddModal: true })}
icon={<MdAddLink />}
label={variables.getMessage(`${QUICKLINKS_SECTION}.add_link`)}
/>
</div>
</div>
)}
</PreferencesWrapper>
<div className="messagesContainer" ref={this.quicklinksContainer}>
{this.state.items.map((item, i) => (
<QuickLink
key={i}
item={item}
startEditLink={() => this.startEditLink(item)}
deleteLink={(key, e) => this.deleteLink(key, e)}
/>
))}
</div>
<Modal
closeTimeoutMS={100}
onRequestClose={() => this.setState({ showAddModal: false, urlError: '', iconError: '' })}
isOpen={this.state.showAddModal}
className="Modal resetmodal mainModal"
overlayClassName="Overlay resetoverlay"
ariaHideApp={false}
>
<AddModal
urlError={this.state.urlError}
addLink={(name, url, icon) => this.addLink(name, url, icon)}
editLink={(og, name, url, icon) => this.editLink(og, name, url, icon)}
edit={this.state.edit}
editData={this.state.editData}
closeModal={() =>
this.setState({ showAddModal: false, urlError: '', iconError: '', edit: false })
}
/>
</Modal>
</>
);
}
}
export { QuickLinksOptions as default, QuickLinksOptions };

View File

@@ -0,0 +1 @@
export * from './QuickLinksOptions';

View File

@@ -0,0 +1,258 @@
@import 'scss/variables';
.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;
gap: 15px;
}
.quicklinkscontainer {
border-radius: 12px;
z-index: 1;
display: flex;
place-content: center center;
gap: 12px;
textarea {
@extend %basic;
border: none;
padding: 15px;
border-radius: 12px;
}
::placeholder {
color: #636e72;
opacity: 1;
}
}
.topbarquicklinks {
svg {
font-size: 46px;
padding: 9px;
}
h4 {
margin: 0;
cursor: initial;
user-select: none;
text-shadow: none;
}
p {
font-size: 16px;
cursor: initial;
user-select: none;
color: rgb(255 71 87 / 100%);
}
}
.quicklinks-container > a,
.quicklinks-container > .quicklinks > button {
display: inline;
}
.quicklinks-container {
img {
height: 32px;
width: auto;
transition: transform 0.2s;
user-select: none;
&:hover {
transform: scale(1.1);
}
}
a {
margin: 5px;
}
}
.quicklinkstext {
text-decoration: none;
color: white;
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;
}
.addLinkModal {
@extend %tabText;
padding: 15px;
@include themed {
background: t($modal-secondaryColour);
}
button {
@include modal-button(standard);
padding: 10px 30px;
float: right;
}
.addFooter {
display: flex;
flex-flow: row;
justify-content: space-between;
align-items: center;
}
.dropdown-error {
font-size: 16px;
padding-left: 5px;
color: #e74c3c;
}
}
.quicklinkModalTextbox {
display: grid;
grid-template-rows: auto 1fr; /* Two rows: first auto-sized, second filling remaining space */
grid-template-columns: repeat(2, 1fr); /* Two equal-width columns for the first row */
grid-gap: 10px; /* Optional gap between items */
padding: 15px 0;
button {
display: flex;
flex-flow: row;
gap: 20px;
justify-content: center;
}
.dropdown-error {
font-size: 30px;
padding-left: 5px;
color: #e74c3c;
}
@include themed {
textarea {
background: t($modal-sidebar);
border: 1px solid t($modal-sidebarActive);
color: t($color);
border-radius: t($borderRadius);
padding: 10px 20px;
&:hover {
box-shadow: 0 0 0 1px t($color);
}
&:active {
box-shadow: 0 0 0 1px t($color);
}
&:focus {
box-shadow: 0 0 0 1px t($color);
}
}
}
}
.quickLinksMetro {
@extend %basic;
text-decoration: none;
gap: 10px;
display: flex;
flex-flow: column;
align-items: center;
min-width: 100px;
background-image: linear-gradient(to left, rgb(0 0 0), transparent, rgb(0 0 0)),
url('https://media.cntraveller.com/photos/615ee85…/16:9/w_2580,c_limit/Best%20Cities%20in%20the%20World%20-%20Grid.jpg');
transition: 0.8s;
text-align: left;
padding: 20px 40px;
@include themed {
&:hover {
background: t($btn-backgroundHover);
}
}
img {
width: auto;
align-self: center;
}
}
.quicklinknostyle {
text-decoration: none;
font-size: 14px;
@include themed {
color: t($subColor);
}
}