Merge branch 'master' of https://github.com/naturalcrit/homebrewery into change-metadata-ui-theme

This commit is contained in:
Víctor Losada Hernández
2026-09-05 23:53:29 +02:00
6 changed files with 629 additions and 597 deletions
@@ -83,7 +83,7 @@ const insertTab = (view)=>{
changes,
selection : EditorSelection.create(
view.state.selection.ranges.map((range)=>EditorSelection.cursor(
mappedChanges.changes.mapPos(range.from, 1) + 2
mappedChanges.changes.mapPos(range.from, -1) + 2
)
)
)
@@ -247,57 +247,57 @@ const Snippetbar = createReactClass({
return (
<div className='editors'>
{this.props.view !== 'meta' && <><div className='historyTools'>
<div className={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`}
<button className={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`}
onClick={this.toggleHistoryMenu} >
<i className='fas fa-clock-rotate-left' />
{ this.state.showHistory && this.renderHistoryItems() }
</div>
<div className={`editorTool undo ${this.props.historySize.done ? 'active' : ''}`}
</button>
<button className={`editorTool undo ${this.props.historySize.done ? 'active' : ''}`}
onClick={this.props.undo} >
<i className='fas fa-undo' />
</div>
<div className={`editorTool redo ${this.props.historySize.undone ? 'active' : ''}`}
</button>
<button className={`editorTool redo ${this.props.historySize.undone ? 'active' : ''}`}
onClick={this.props.redo} >
<i className='fas fa-redo' />
</div>
</button>
</div>
<div className='codeTools'>
<div className={`editorTool foldAll ${this.props.foldCode ? 'active' : ''}`}
<button className={`editorTool foldAll ${this.props.foldCode ? 'active' : ''}`}
onClick={this.props.foldCode} >
<i className='fas fa-compress-alt' />
</div>
<div className={`editorTool unfoldAll ${this.props.unfoldCode ? 'active' : ''}`}
</button>
<button className={`editorTool unfoldAll ${this.props.unfoldCode ? 'active' : ''}`}
onClick={this.props.unfoldCode} >
<i className='fas fa-expand-alt' />
</div>
<div className={`editorTool formatCode ${this.props.formatCode ? 'active' : ''}`}
</button>
<button className={`editorTool formatCode ${this.props.formatCode ? 'active' : ''}`}
onClick={this.props.formatCode} >
<i className='fas fa-wand-magic-sparkles' />
</div>
<div className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
</button>
<button className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' />
{this.state.themeSelector && this.renderThemeSelector()}
</div>
</button>
</div></>}
<div className='tabs'>
<div className={cx('text', { selected: this.props.view === 'text' })}
<button className={cx('text', { selected: this.props.view === 'text' })}
onClick={()=>this.props.onViewChange('text')}>
<i className='fa fa-beer' />
</div>
<div className={cx('style', { selected: this.props.view === 'style' })}
</button>
<button className={cx('style', { selected: this.props.view === 'style' })}
onClick={()=>this.props.onViewChange('style')}>
<i className='fa fa-paint-brush' />
</div>
<div className={cx('snippet', { selected: this.props.view === 'snippet' })}
</button>
<button className={cx('snippet', { selected: this.props.view === 'snippet' })}
onClick={()=>this.props.onViewChange('snippet')}>
<i className='fas fa-th-list' />
</div>
<div className={cx('meta', { selected: this.props.view === 'meta' })}
</button>
<button className={cx('meta', { selected: this.props.view === 'meta' })}
onClick={()=>this.props.onViewChange('meta')}>
<i className='fas fa-info-circle' />
</div>
</button>
</div>
</div>
@@ -13,6 +13,7 @@
position : relative;
display : flex;
flex-wrap : wrap-reverse;
reading-flow : flex-visual;
justify-content : space-between;
height : auto;
color : var(--textColor);
@@ -28,6 +29,7 @@
min-width : 275px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
font-size: .85rem;
&:only-child {min-width : unset; margin-left : auto;}
reading-order : 2;
>div {
display : flex;
@@ -36,7 +38,7 @@
&:first-child { border-left : none; }
& > div {
& > button {
position : relative;
width : @menuHeight;
height : @menuHeight;
@@ -136,6 +138,7 @@
display : flex;
justify-content : flex-start;
min-width : 565.95px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
reading-order : 1;
}
// removed caret for top level items, by request (makes buttons too wide).
@@ -223,10 +226,12 @@
flex : 1;
justify-content : space-between;
border-bottom : 1px solid;
reading-order : 1;
}
.snippets {
flex : 1;
justify-content : space-evenly;
reading-order : 2;
}
.editors > div.history > .dropdown { right : unset; }
}
@@ -1,9 +1,8 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
import './listPage.less';
import React from 'react';
import createReactClass from 'create-react-class';
import _ from 'lodash';
import React, { useEffect, useState, useRef, useMemo } from 'react';
import moment from 'moment';
import _ from 'lodash';
import BrewItem from './brewItem/brewItem.jsx';
@@ -14,132 +13,116 @@ const USERPAGE_GROUP_VISIBILITY_PREFIX = 'HB_listPage_visibility_group';
const DEFAULT_SORT_TYPE = 'alpha';
const DEFAULT_SORT_DIR = 'asc';
const ListPage = createReactClass({
displayName : 'ListPage',
getDefaultProps : function() {
return {
brewCollection : [
{
title : '',
class : '',
brews : []
}
],
navItems : <></>,
reportError : null
const ListPage = ({ brewCollection = [{ title: '', class: '', brews: [] }], navItems = <></>, reportError = null, query })=>{
const [filterString, setFilterString] = useState(query?.filter || '');
const [filterTags, setFilterTags] = useState([]);
const [sortType, setSortType] = useState(query?.sort || null);
const [sortDir, setSortDir] = useState(query?.dir || null);
const [groupVisibility, setGroupVisibility] = useState({});
const groupVisibilityRef = useRef(groupVisibility);
const sortTypeRef = useRef(sortType);
const sortDirRef = useRef(sortDir);
useEffect(()=>{
groupVisibilityRef.current = groupVisibility;
}, [groupVisibility]);
useEffect(()=>{
sortTypeRef.current = sortType;
}, [sortType]);
useEffect(()=>{
sortDirRef.current = sortDir;
}, [sortDir]);
useEffect(()=>{
window.onbeforeunload = saveToLocalStorage;
if(typeof window === 'undefined') return;
const newSortType = sortType ?? (localStorage.getItem(USERPAGE_SORT_TYPE) || DEFAULT_SORT_TYPE);
const newSortDir = sortDir ?? (localStorage.getItem(USERPAGE_SORT_DIR) || DEFAULT_SORT_DIR);
updateUrl(filterString, newSortType, newSortDir);
const namedBrewCollection = brewCollection.reduce((visibility, brewGroup)=>{
visibility[brewGroup.class] = (localStorage.getItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroup.class}`) ?? 'true') == 'true';
return visibility;
}, {});
setGroupVisibility(namedBrewCollection);
setSortType(newSortType);
setSortDir(newSortDir);
return ()=>{
window.onbeforeunload = null;
};
},
getInitialState : function() {
// HIDE ALL GROUPS UNTIL LOADED
const brewCollection = this.props.brewCollection.map((brewGroup)=>{
brewGroup.visible = false;
return brewGroup;
}, []);
const saveToLocalStorage = ()=>{
brewCollection.forEach((brewGroup)=>{
localStorage.setItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroup.class}`, `${groupVisibilityRef.current[brewGroup.class]}`);
});
localStorage.setItem(USERPAGE_SORT_TYPE, sortTypeRef.current);
localStorage.setItem(USERPAGE_SORT_DIR, sortDirRef.current);
};
return {
filterString : this.props.query?.filter || '',
filterTags : [],
sortType : this.props.query?.sort || null,
sortDir : this.props.query?.dir || null,
query : this.props.query,
brewCollection : brewCollection
};
},
componentDidMount : function() {
// SAVE TO LOCAL STORAGE WHEN LEAVING PAGE
window.onbeforeunload = this.saveToLocalStorage;
// LOAD FROM LOCAL STORAGE
if(typeof window !== 'undefined') {
const newSortType = (this.state.sortType ?? (localStorage.getItem(USERPAGE_SORT_TYPE) || DEFAULT_SORT_TYPE));
const newSortDir = (this.state.sortDir ?? (localStorage.getItem(USERPAGE_SORT_DIR) || DEFAULT_SORT_DIR));
this.updateUrl(this.state.filterString, newSortType, newSortDir);
const brewCollection = this.props.brewCollection.map((brewGroup)=>{
brewGroup.visible = (localStorage.getItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroup.class}`) ?? 'true')=='true';
return brewGroup;
});
this.setState({
brewCollection : brewCollection,
sortType : newSortType,
sortDir : newSortDir
});
};
},
componentWillUnmount : function() {
window.onbeforeunload = function(){};
},
saveToLocalStorage : function() {
this.state.brewCollection.map((brewGroup)=>{
localStorage.setItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroup.class}`, `${brewGroup.visible}`);
});
localStorage.setItem(USERPAGE_SORT_TYPE, this.state.sortType);
localStorage.setItem(USERPAGE_SORT_DIR, this.state.sortDir);
},
renderBrews : function(brews){
const renderBrews = (brews)=>{
if(!brews || !brews.length) return <div className='noBrews'>No Brews.</div>;
return _.map(brews, (brew, idx)=>{
return <BrewItem brew={brew} key={idx} reportError={this.props.reportError} updateListFilter={ (tag)=>{ this.updateUrl(this.state.filterString, this.state.sortType, this.state.sortDir, tag); }}/>;
});
},
return _.map(brews, (brew, idx)=>(
<BrewItem
brew={brew}
key={idx}
reportError={reportError}
updateListFilter={(tag)=>{
updateUrl(filterString, sortType, sortDir, tag);
}}
/>
));
};
sortBrewOrder : function(brew){
if(!brew.title){brew.title = 'No Title';}
const sortBrewOrder = (brew)=>{
const title = brew.title || 'No Title';
const mapping = {
'alpha' : _.deburr(brew.title.trim().toLowerCase()),
'alpha' : _.deburr(title.trim().toLowerCase()),
'created' : moment(brew.createdAt).format(),
'updated' : moment(brew.updatedAt).format(),
'views' : brew.views,
'latest' : moment(brew.lastViewed).format()
'latest' : moment(brew.lastViewed).format(),
};
return mapping[this.state.sortType];
},
return mapping[sortType];
};
handleSortOptionChange : function(event){
this.updateUrl(this.state.filterString, event.target.value, this.state.sortDir);
this.setState({
sortType : event.target.value
});
},
const handleSortOptionChange = (event)=>{
updateUrl(filterString, event.target.value, sortDir);
setSortType(event.target.value);
};
handleSortDirChange : function(event){
const newDir = this.state.sortDir == 'asc' ? 'desc' : 'asc';
const handleSortDirChange = (event)=>{
const newDir = sortDir == 'asc' ? 'desc' : 'asc';
this.updateUrl(this.state.filterString, this.state.sortType, newDir);
this.setState({
sortDir : newDir
});
},
updateUrl(filterString, sortType, newDir);
setSortDir(newDir);
};
renderSortOption : function(sortTitle, sortValue){
return <div className={`sort-option ${(this.state.sortType == sortValue ? 'active' : '')}`}>
<button
value={`${sortValue}`}
onClick={this.state.sortType == sortValue ? this.handleSortDirChange : this.handleSortOptionChange}
>
{`${sortTitle}`}
</button>
{this.state.sortType == sortValue &&
<i className={`sortDir fas ${this.state.sortDir == 'asc' ? 'fa-sort-up' : 'fa-sort-down'}`}></i>
}
</div>;
},
const renderSortOption = (sortTitle, sortValue)=>{
return (
<div className={`sort-option ${sortType == sortValue ? 'active' : ''}`}>
<button value={`${sortValue}`} onClick={sortType == sortValue ? handleSortDirChange : handleSortOptionChange}>
{`${sortTitle}`}
</button>
{sortType == sortValue && <i className={`sortDir fas ${sortDir == 'asc' ? 'fa-sort-up' : 'fa-sort-down'}`}></i>}
</div>
);
};
handleFilterTextChange : function(e){
this.setState({
filterString : e.target.value,
});
this.updateUrl(e.target.value, this.state.sortType, this.state.sortDir);
const handleFilterTextChange = (e)=>{
setFilterString(e.target.value);
updateUrl(e.target.value, sortType, sortDir);
return;
},
};
updateUrl : function(filterTerm, sortType, sortDir, filterTag=''){
const updateUrl = (filterTerm, sortType, sortDir, filterTag = '')=>{
const url = new URL(window.location.href);
const urlParams = new URLSearchParams(url.search);
@@ -148,135 +131,162 @@ const ListPage = createReactClass({
let filterTags = urlParams.getAll('tag');
if(filterTag != '') {
if(filterTags.findIndex((tag)=>{return tag.toLowerCase()==filterTag.toLowerCase();}) == -1){
if(
filterTags.findIndex((tag)=>{
return tag.toLowerCase() == filterTag.toLowerCase();
}) == -1
) {
filterTags.push(filterTag);
} else {
filterTags = filterTags.filter((tag)=>{ return tag.toLowerCase() != filterTag.toLowerCase(); });
filterTags = filterTags.filter((tag)=>{
return tag.toLowerCase() != filterTag.toLowerCase();
});
}
}
urlParams.delete('tag');
// Add tags to URL in the order they were clicked
filterTags.forEach((tag)=>{ urlParams.append('tag', tag); });
filterTags.forEach((tag)=>urlParams.append('tag', tag));
// Sort tags before updating state
filterTags.sort((a, b)=>{
return a.indexOf(':') - b.indexOf(':') != 0 ? a.indexOf(':') - b.indexOf(':') : a.toLowerCase().localeCompare(b.toLowerCase());
});
this.setState({
filterTags
});
setFilterTags(filterTags);
if(!filterTerm)
urlParams.delete('filter');
else
urlParams.set('filter', filterTerm);
if(!filterTerm) urlParams.delete('filter');
else urlParams.set('filter', filterTerm);
url.search = urlParams;
window.history.replaceState(null, null, url);
},
};
renderFilterOption : function(){
return <div className='filter-option'>
<label>
<i className='fas fa-search'></i>
<input
type='search'
placeholder='filter title/description'
onChange={this.handleFilterTextChange}
value={this.state.filterString}
/>
</label>
</div>;
},
const renderFilterOption = ()=>{
return (
<div className='filter-option'>
<label>
<i className='fas fa-search'></i>
<input type='search' placeholder='filter title/description/tags' onChange={handleFilterTextChange} value={filterString} />
</label>
</div>
);
};
renderTagsOptions : function(){
if(this.state.filterTags?.length == 0) return;
return <div className='tags-container'>
{_.map(this.state.filterTags, (tag, idx)=>{
const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/);
return <span key={idx} className={matches[1]} onClick={()=>{ this.updateUrl(this.state.filterString, this.state.sortType, this.state.sortDir, tag); }}>{matches[2]}</span>;
})}
</div>;
},
const renderTagsOptions = ()=>{
if(filterTags?.length == 0) return;
return (
<div className='tags-container'>
{_.map(filterTags, (tag, idx)=>{
const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/);
return (
<span
key={idx}
className={matches[1]}
onClick={()=>{
updateUrl(filterString, sortType, sortDir, tag);
}}>
{matches[2]}
</span>
);
})}
</div>
);
};
renderSortOptions : function(){
return <div className='sort-container'>
<h6>Sort by :</h6>
{this.renderSortOption('Title', 'alpha')}
{this.renderSortOption('Created Date', 'created')}
{this.renderSortOption('Updated Date', 'updated')}
{this.renderSortOption('Views', 'views')}
{/* {this.renderSortOption('Latest', 'latest')} */}
const renderSortOptions = ()=>{
return (
<div className='sort-container'>
<h6>Sort by :</h6>
{renderSortOption('Title', 'alpha')}
{renderSortOption('Created Date', 'created')}
{renderSortOption('Updated Date', 'updated')}
{renderSortOption('Views', 'views')}
{/* {renderSortOption('Latest', 'latest')} */}
{renderFilterOption()}
</div>
);
};
{this.renderFilterOption()}
</div>;
},
getSortedBrews : function(brews){
const testString = _.deburr(this.state.filterString).toLowerCase();
const getSortedBrews = (brews)=>{
const testString = _.deburr(filterString).toLowerCase();
brews = _.filter(brews, (brew)=>{
// Filter by user entered text
const brewStrings = _.deburr([
brew.title,
brew.description,
brew.tags].join('\n')
.toLowerCase());
const brewStrings = _.deburr([brew.title, brew.description, brew.tags].join('\n').toLowerCase());
const filterTextTest = brewStrings.includes(testString);
// Filter by user selected tags
let filterTagTest = true;
if(this.state.filterTags.length > 0){
filterTagTest = Array.isArray(brew.tags) && this.state.filterTags?.every((tag)=>{
return brew.tags.findIndex((brewTag)=>{
return brewTag.toLowerCase() == tag.toLowerCase();
}) >= 0;
});
if(filterTags.length > 0) {
filterTagTest =
Array.isArray(brew.tags) &&
filterTags?.every((tag)=>{
return (
brew.tags.findIndex((brewTag)=>{
return brewTag.toLowerCase() == tag.toLowerCase();
}) >= 0
);
});
}
return filterTextTest && filterTagTest;
});
return _.orderBy(brews, (brew)=>{ return this.sortBrewOrder(brew); }, this.state.sortDir);
},
return _.orderBy(
brews,
(brew)=>{
return sortBrewOrder(brew);
},
sortDir,
);
};
toggleBrewCollectionState : function(brewGroupClass) {
this.setState((prevState)=>({
brewCollection : prevState.brewCollection.map(
(brewGroup)=>brewGroup.class === brewGroupClass ? { ...brewGroup, visible: !brewGroup.visible } : brewGroup
)
}));
},
const sortedBrewCollection = useMemo(()=>{
return brewCollection.map((brewGroup)=>({ ...brewGroup, brews: getSortedBrews(brewGroup.brews) }));
}, [brewCollection, filterString, filterTags, sortType, sortDir]);
renderBrewCollection : function(brewCollection){
if(brewCollection == []) return <div className='brewCollection'>
<h1>No Brews</h1>
</div>;
const toggleBrewCollectionState = (brewGroupClass)=>{
setGroupVisibility((prevVisibility)=>({ ...prevVisibility, [brewGroupClass]: !prevVisibility[brewGroupClass] }));
};
const renderBrewCollection = (brewCollection)=>{
if(brewCollection.length === 0)
return (
<div className='brewCollection'>
<h1>No Brews</h1>
</div>
);
return _.map(brewCollection, (brewGroup, idx)=>{
return <div key={idx} className={`brewCollection ${brewGroup.class ?? ''}`}>
<h1 className={brewGroup.visible ? 'active' : 'inactive'} onClick={()=>{this.toggleBrewCollectionState(brewGroup.class);}}>{brewGroup.title || 'No Title'}</h1>
{brewGroup.visible ? this.renderBrews(this.getSortedBrews(brewGroup.brews)) : <></>}
</div>;
});
},
const sortedBrewGroup = sortedBrewCollection[idx];
const visible = groupVisibility[brewGroup.class];
render : function(){
return <div className='listPage sitePage'>
{/*<style>@layer V3_5ePHB, bundle;</style>*/}
<link href='/themes/V3/Blank/style.css' type='text/css' rel='stylesheet'/>
<link href='/themes/V3/5ePHB/style.css' type='text/css' rel='stylesheet'/>
{this.props.navItems}
{this.renderSortOptions()}
{this.renderTagsOptions()}
return (
<div key={idx} className={`brewCollection ${brewGroup.class ?? ''}`}>
<h1
className={visible ? 'active' : 'inactive'}
onClick={()=>{
toggleBrewCollectionState(brewGroup.class);
}}>
{brewGroup.title || 'No Title'}
</h1>
{visible ? renderBrews(sortedBrewGroup.brews) : <></>}
</div>
);
});
};
return (
<div className='listPage sitePage'>
<link href='/themes/V3/Blank/style.css' type='text/css' rel='stylesheet' />
<link href='/themes/V3/5ePHB/style.css' type='text/css' rel='stylesheet' />
{navItems}
{renderSortOptions()}
{renderTagsOptions()}
<div className='content V3'>
<div className='page'>
{this.renderBrewCollection(this.state.brewCollection)}
</div>
<div className='page'>{renderBrewCollection(brewCollection)}</div>
</div>
</div>;
}
});
</div>
);
};
export default ListPage;
+42 -25
View File
@@ -69,7 +69,8 @@ const EditPage = (props)=>{
const [themeBundle, setThemeBundle] = useState({});
const [unsavedChanges, setUnsavedChanges] = useState(false);
const [alertTrashedGoogleBrew, setAlertTrashedGoogleBrew] = useState(props.brew.trashed);
const [alertLoginToTransfer, setAlertLoginToTransfer] = useState(false);
const [alertNoGoogleToTransfer, setAlertNoGoogleToTransfer] = useState(false);
const [alertOwnershipToTransfer, setAlertOwnershipToTransfer] = useState(false);
const [confirmGoogleTransfer, setConfirmGoogleTransfer] = useState(false);
const [autoSaveEnabled, setAutoSaveEnabled] = useState(true);
const [warnUnsavedChanges, setWarnUnsavedChanges] = useState(true);
@@ -105,7 +106,7 @@ const EditPage = (props)=>{
};
return ()=>{
document.removeEventListener('keydown', handleControlKeys);
window.onBeforeUnload = null;
window.onbeforeunload = null;
};
}, []);
@@ -162,8 +163,12 @@ const EditPage = (props)=>{
};
const handleGoogleClick = ()=>{
if(global.account !== currentBrew.authors[0]) {
setalertOwnershipToTransfer(true);
return;
}
if(!global.account?.googleId) {
setAlertLoginToTransfer(true);
setAlertNoGoogleToTransfer(true);
return;
}
@@ -174,11 +179,13 @@ const EditPage = (props)=>{
const closeAlerts = (e)=>{
e.stopPropagation(); //Only handle click once so alert doesn't reopen
setAlertTrashedGoogleBrew(false);
setAlertLoginToTransfer(false);
setAlertNoGoogleToTransfer(false);
setConfirmGoogleTransfer(false);
setalertOwnershipToTransfer(false);
};
const toggleGoogleStorage = ()=>{
const toggleGoogleStorage = (e)=>{
closeAlerts(e);
const newSaveGoogle = !saveGoogle;
setSaveGoogle((prev)=>!prev);
setError(null);
@@ -260,32 +267,42 @@ const EditPage = (props)=>{
<Nav.item className='googleDriveStorage' onClick={handleGoogleClick}>
<img src={googleDriveIcon} className={saveGoogle ? '' : 'inactive'} alt='Google Drive icon' />
{alertOwnershipToTransfer && (
<div className='errorContainer'>
You must be the Owner to transfer between the Homebrewery and Google Drive!
The owner of this file is {currentBrew.authors[0]}
<div className='confirm' onClick={closeAlerts}> Okay </div>
</div>
)}
{alertNoGoogleToTransfer && (
<div className='errorContainer'>
You must be signed in to a Google account to transfer between the Homebrewery and Google Drive!
<a target='_blank' rel='noopener noreferrer' href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm' onClick={closeAlerts}> Sign In </div>
</a>
<div className='deny' onClick={closeAlerts}> Not Now </div>
</div>
)}
{alertTrashedGoogleBrew && (
<div className='errorContainer'>
This brew is currently in your Trash folder on Google Drive!<br />
If you want to keep it, make sure to move it before it is deleted permanently!<br />
<div className='confirm' onClick={toggleGoogleStorage}> Save my brew </div>
</div>
)}
{confirmGoogleTransfer && (
<div className='errorContainer' onClick={closeAlerts}>
<div className='errorContainer'>
{saveGoogle
? 'Would you like to transfer this brew from your Google Drive storage back to the Homebrewery?'
: 'Would you like to transfer this brew from the Homebrewery to your personal Google Drive storage?'}
<br />
<div className='confirm' onClick={toggleGoogleStorage}> Yes </div>
<div className='deny'> No </div>
</div>
)}
{alertLoginToTransfer && (
<div className='errorContainer' onClick={closeAlerts}>
You must be signed in to a Google account to transfer between the homebrewery and Google Drive!
<a target='_blank' rel='noopener noreferrer' href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'> Sign In </div>
</a>
<div className='deny'> Not Now </div>
</div>
)}
{alertTrashedGoogleBrew && (
<div className='errorContainer' onClick={closeAlerts}>
This brew is currently in your Trash folder on Google Drive!<br />
If you want to keep it, make sure to move it before it is deleted permanently!<br />
<div className='confirm'> OK </div>
<div className='deny' onClick={closeAlerts}> No </div>
</div>
)}
</Nav.item>
+345 -345
View File
File diff suppressed because it is too large Load Diff