0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-06 03:32:40 +00:00

Merge branch 'master' into addAdminFixScriptTool-#3801

This commit is contained in:
G.Ambatte
2024-11-07 23:31:04 +13:00
committed by GitHub
16 changed files with 396 additions and 59 deletions

View File

@@ -0,0 +1,91 @@
import React, { useState, useRef, forwardRef, useEffect, cloneElement, Children } from 'react';
import './Anchored.less';
// Anchored is a wrapper component that must have as children an <AnchoredTrigger> and a <AnchoredBox> component.
// AnchoredTrigger must have a unique `id` prop, which is passed up to Anchored, saved in state on mount, and
// then passed down through props into AnchoredBox. The `id` is used for the CSS Anchor Positioning properties.
// **The Anchor Positioning API is not available in Firefox yet**
// So in Firefox the positioning isn't perfect but is likely sufficient, and FF team seems to be working on the API quickly.
const Anchored = ({ children })=>{
const [visible, setVisible] = useState(false);
const [anchorId, setAnchorId] = useState(null);
const boxRef = useRef(null);
const triggerRef = useRef(null);
// promote trigger id to Anchored id (to pass it back down to the box as "anchorId")
useEffect(()=>{
if(triggerRef.current){
setAnchorId(triggerRef.current.id);
}
}, []);
// close box on outside click or Escape key
useEffect(()=>{
const handleClickOutside = (evt)=>{
if(
boxRef.current &&
!boxRef.current.contains(evt.target) &&
triggerRef.current &&
!triggerRef.current.contains(evt.target)
) {
setVisible(false);
}
};
const handleEscapeKey = (evt)=>{
if(evt.key === 'Escape') setVisible(false);
};
window.addEventListener('click', handleClickOutside);
window.addEventListener('keydown', handleEscapeKey);
return ()=>{
window.removeEventListener('click', handleClickOutside);
window.removeEventListener('keydown', handleEscapeKey);
};
}, []);
const toggleVisibility = ()=>setVisible((prev)=>!prev);
// Map children to inject necessary props
const mappedChildren = Children.map(children, (child)=>{
if(child.type === AnchoredTrigger) {
return cloneElement(child, { ref: triggerRef, toggleVisibility, visible });
}
if(child.type === AnchoredBox) {
return cloneElement(child, { ref: boxRef, visible, anchorId });
}
return child;
});
return <>{mappedChildren}</>;
};
// forward ref for AnchoredTrigger
const AnchoredTrigger = forwardRef(({ toggleVisibility, visible, children, className, ...props }, ref)=>(
<button
ref={ref}
className={`anchored-trigger${visible ? ' active' : ''} ${className}`}
onClick={toggleVisibility}
style={{ anchorName: `--${props.id}` }} // setting anchor properties here allows greater recyclability.
{...props}
>
{children}
</button>
));
// forward ref for AnchoredBox
const AnchoredBox = forwardRef(({ visible, children, className, anchorId, ...props }, ref)=>(
<div
ref={ref}
className={`anchored-box${visible ? ' active' : ''} ${className}`}
style={{ positionAnchor: `--${anchorId}` }} // setting anchor properties here allows greater recyclability.
{...props}
>
{children}
</div>
));
export { Anchored, AnchoredTrigger, AnchoredBox };

View File

@@ -0,0 +1,13 @@
.anchored-box {
position:absolute;
@supports (inset-block-start: anchor(bottom)){
inset-block-start: anchor(bottom);
}
justify-self: anchor-center;
visibility: hidden;
&.active {
visibility: visible;
}
}

View File

@@ -37,7 +37,7 @@ const BrewPage = (props)=>{
...props ...props
}; };
const cleanText = safeHTML(props.contents); const cleanText = safeHTML(props.contents);
return <div className={props.className} id={`p${props.index + 1}`} > return <div className={props.className} id={`p${props.index + 1}`} style={props.style}>
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: cleanText }} /> <div className='columnWrapper' dangerouslySetInnerHTML={{ __html: cleanText }} />
</div>; </div>;
}; };
@@ -65,8 +65,14 @@ const BrewRenderer = (props)=>{
const [state, setState] = useState({ const [state, setState] = useState({
isMounted : false, isMounted : false,
visibility : 'hidden', visibility : 'hidden'
zoom : 100 });
const [displayOptions, setDisplayOptions] = useState({
zoomLevel : 100,
spread : 'single',
startOnRight : true,
pageShadows : true
}); });
const mainRef = useRef(null); const mainRef = useRef(null);
@@ -137,7 +143,13 @@ const BrewRenderer = (props)=>{
} else { } else {
pageText += `\n\n&nbsp;\n\\column\n&nbsp;`; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear) pageText += `\n\n&nbsp;\n\\column\n&nbsp;`; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
const html = Markdown.render(pageText, index); const html = Markdown.render(pageText, index);
return <BrewPage className='page' index={index} key={index} contents={html} />;
const styles = {
...(!displayOptions.pageShadows ? { boxShadow: 'none' } : {})
// Add more conditions as needed
};
return <BrewPage className='page' index={index} key={index} contents={html} style={styles} />;
} }
}; };
@@ -187,12 +199,14 @@ const BrewRenderer = (props)=>{
document.dispatchEvent(new MouseEvent('click')); document.dispatchEvent(new MouseEvent('click'));
}; };
//Toolbar settings: const handleDisplayOptionsChange = (newDisplayOptions)=>{
const handleZoom = (newZoom)=>{ setDisplayOptions(newDisplayOptions);
setState((prevState)=>({ };
...prevState,
zoom : newZoom const pagesStyle = {
})); zoom : `${displayOptions.zoomLevel}%`,
columnGap : `${displayOptions.columnGap}px`,
rowGap : `${displayOptions.rowGap}px`
}; };
const styleObject = {}; const styleObject = {};
@@ -221,7 +235,7 @@ const BrewRenderer = (props)=>{
<NotificationPopup /> <NotificationPopup />
</div> </div>
<ToolBar onZoomChange={handleZoom} currentPage={props.currentBrewRendererPageNum} totalPages={rawPages.length}/> <ToolBar displayOptions={displayOptions} currentPage={props.currentBrewRendererPageNum} totalPages={rawPages.length} onDisplayOptionsChange={handleDisplayOptionsChange} />
{/*render in iFrame so broken code doesn't crash the site.*/} {/*render in iFrame so broken code doesn't crash the site.*/}
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT} <Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
@@ -240,7 +254,8 @@ const BrewRenderer = (props)=>{
&& &&
<> <>
{renderedStyle} {renderedStyle}
<div className='pages' lang={`${props.lang || 'en'}`} style={{ zoom: `${state.zoom}%` }}> <div lang={`${props.lang || 'en'}`} style={pagesStyle} className={
`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}` } >
{renderedPages} {renderedPages}
</div> </div>
</> </>

View File

@@ -3,13 +3,45 @@
.brewRenderer { .brewRenderer {
overflow-y : scroll; overflow-y : scroll;
will-change : transform; will-change : transform;
padding-top : 30px; padding-top : 60px;
height : 100vh; height : 100vh;
&:has(.facing, .flow) {
padding : 60px 30px;
}
&.deployment { &.deployment {
background-color: darkred; background-color: darkred;
} }
:where(.pages) { :where(.pages) {
margin : 30px 0px; &.facing {
display: grid;
grid-template-columns: repeat(2, auto);
grid-template-rows: repeat(3, auto);
gap: 10px 10px;
justify-content: center;
&.recto .page:first-child {
// sets first page on 'right' ('recto') of the preview, as if for a Cover page.
// todo: add a checkbox to toggle this setting
grid-column-start: 2;
}
& :where(.page) {
margin-left: unset !important;
margin-right: unset !important;
}
}
&.flow {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: flex-start;
& :where(.page) {
flex: 0 0 auto;
margin-left: unset !important;
margin-right: unset !important;
}
}
& > :where(.page) { & > :where(.page) {
width : 215.9mm; width : 215.9mm;
height : 279.4mm; height : 279.4mm;

View File

@@ -3,26 +3,28 @@ const React = require('react');
const { useState, useEffect } = React; const { useState, useEffect } = React;
const _ = require('lodash'); const _ = require('lodash');
import { Anchored, AnchoredBox, AnchoredTrigger } from '../../../components/Anchored.jsx';
// import * as ZoomIcons from '../../../icons/icon-components/zoomIcons.jsx';
const MAX_ZOOM = 300; const MAX_ZOOM = 300;
const MIN_ZOOM = 10; const MIN_ZOOM = 10;
const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{ const ToolBar = ({ displayOptions, currentPage, totalPages, onDisplayOptionsChange })=>{
const [zoomLevel, setZoomLevel] = useState(100); const [pageNum, setPageNum] = useState(currentPage);
const [pageNum, setPageNum] = useState(currentPage);
const [toolsVisible, setToolsVisible] = useState(true); const [toolsVisible, setToolsVisible] = useState(true);
useEffect(()=>{
onZoomChange(zoomLevel);
}, [zoomLevel]);
useEffect(()=>{ useEffect(()=>{
setPageNum(currentPage); setPageNum(currentPage);
}, [currentPage]); }, [currentPage]);
const handleZoomButton = (zoom)=>{ const handleZoomButton = (zoom)=>{
setZoomLevel(_.round(_.clamp(zoom, MIN_ZOOM, MAX_ZOOM))); handleOptionChange('zoomLevel', _.round(_.clamp(zoom, MIN_ZOOM, MAX_ZOOM)));
};
const handleOptionChange = (optionKey, newValue)=>{
//setDisplayOptions(prevOptions => ({ ...prevOptions, [optionKey]: newValue }));
onDisplayOptionsChange({ ...displayOptions, [optionKey]: newValue });
}; };
const handlePageInput = (pageInput)=>{ const handlePageInput = (pageInput)=>{
@@ -63,47 +65,51 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{
const margin = 5; // extra space so page isn't edge to edge (not truly "to fill") const margin = 5; // extra space so page isn't edge to edge (not truly "to fill")
const deltaZoom = (desiredZoom - zoomLevel) - margin; const deltaZoom = (desiredZoom - displayOptions.zoomLevel) - margin;
return deltaZoom; return deltaZoom;
}; };
return ( return (
<div className={`toolBar ${toolsVisible ? 'visible' : 'hidden'}`}> <div id='preview-toolbar' className={`toolBar ${toolsVisible ? 'visible' : 'hidden'}`} role='toolbar'>
<button className='toggleButton' title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{setToolsVisible(!toolsVisible);}}><i className='fas fa-glasses' /></button> <button className='toggleButton' title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{setToolsVisible(!toolsVisible);}}><i className='fas fa-glasses' /></button>
{/*v=====----------------------< Zoom Controls >---------------------=====v*/} {/*v=====----------------------< Zoom Controls >---------------------=====v*/}
<div className='group'> <div className='group' role='group' aria-label='Zoom' aria-hidden={!toolsVisible}>
<button <button
id='fill-width' id='fill-width'
className='tool' className='tool'
onClick={()=>handleZoomButton(zoomLevel + calculateChange('fill'))} title='Set zoom to fill preview with one page'
onClick={()=>handleZoomButton(displayOptions.zoomLevel + calculateChange('fill'))}
> >
<i className='fac fit-width' /> <i className='fac fit-width' />
</button> </button>
<button <button
id='zoom-to-fit' id='zoom-to-fit'
className='tool' className='tool'
onClick={()=>handleZoomButton(zoomLevel + calculateChange('fit'))} title='Set zoom to fit entire page in preview'
onClick={()=>handleZoomButton(displayOptions.zoomLevel + calculateChange('fit'))}
> >
<i className='fac zoom-to-fit' /> <i className='fac zoom-to-fit' />
</button> </button>
<button <button
id='zoom-out' id='zoom-out'
className='tool' className='tool'
onClick={()=>handleZoomButton(zoomLevel - 20)} onClick={()=>handleZoomButton(displayOptions.zoomLevel - 20)}
disabled={zoomLevel <= MIN_ZOOM} disabled={displayOptions.zoomLevel <= MIN_ZOOM}
title='Zoom Out'
> >
<i className='fas fa-magnifying-glass-minus' /> <i className='fas fa-magnifying-glass-minus' />
</button> </button>
<input <input
id='zoom-slider' id='zoom-slider'
className='range-input tool' className='range-input tool hover-tooltip'
type='range' type='range'
name='zoom' name='zoom'
title='Set Zoom'
list='zoomLevels' list='zoomLevels'
min={MIN_ZOOM} min={MIN_ZOOM}
max={MAX_ZOOM} max={MAX_ZOOM}
step='1' step='1'
value={zoomLevel} value={displayOptions.zoomLevel}
onChange={(e)=>handleZoomButton(parseInt(e.target.value))} onChange={(e)=>handleZoomButton(parseInt(e.target.value))}
/> />
<datalist id='zoomLevels'> <datalist id='zoomLevels'>
@@ -113,18 +119,72 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{
<button <button
id='zoom-in' id='zoom-in'
className='tool' className='tool'
onClick={()=>handleZoomButton(zoomLevel + 20)} onClick={()=>handleZoomButton(displayOptions.zoomLevel + 20)}
disabled={zoomLevel >= MAX_ZOOM} disabled={displayOptions.zoomLevel >= MAX_ZOOM}
title='Zoom In'
> >
<i className='fas fa-magnifying-glass-plus' /> <i className='fas fa-magnifying-glass-plus' />
</button> </button>
</div> </div>
{/*v=====----------------------< Spread Controls >---------------------=====v*/}
<div className='group' role='group' aria-label='Spread' aria-hidden={!toolsVisible}>
<div className='radio-group' role='radiogroup'>
<button role='radio'
id='single-spread'
className='tool'
title='Single Page'
onClick={()=>{handleOptionChange('spread', 'active');}}
aria-checked={displayOptions.spread === 'single'}
><i className='fac single-spread' /></button>
<button role='radio'
id='facing-spread'
className='tool'
title='Facing Pages'
onClick={()=>{handleOptionChange('spread', 'facing');}}
aria-checked={displayOptions.spread === 'facing'}
><i className='fac facing-spread' /></button>
<button role='radio'
id='flow-spread'
className='tool'
title='Flow Pages'
onClick={()=>{handleOptionChange('spread', 'flow');}}
aria-checked={displayOptions.spread === 'flow'}
><i className='fac flow-spread' /></button>
</div>
<Anchored>
<AnchoredTrigger id='spread-settings' className='tool' title='Spread options'><i className='fas fa-gear' /></AnchoredTrigger>
<AnchoredBox title='Options'>
<h1>Options</h1>
<label title='Modify the horizontal space between pages.'>
Column gap
<input type='range' min={0} max={200} defaultValue={10} className='range-input' onChange={(evt)=>handleOptionChange('columnGap', evt.target.value)} />
</label>
<label title='Modify the vertical space between rows of pages.'>
Row gap
<input type='range' min={0} max={200} defaultValue={10} className='range-input' onChange={(evt)=>handleOptionChange('rowGap', evt.target.value)} />
</label>
<label title='Start 1st page on the right side, such as if you have cover page.'>
Start on right
<input type='checkbox' checked={displayOptions.startOnRight} onChange={()=>{handleOptionChange('startOnRight', !displayOptions.startOnRight);}}
title={displayOptions.spread !== 'facing' ? 'Switch to Facing to enable toggle.' : null} />
</label>
<label title='Toggle the page shadow on every page.'>
Page shadows
<input type='checkbox' checked={displayOptions.pageShadows} onChange={()=>{handleOptionChange('pageShadows', !displayOptions.pageShadows);}} />
</label>
</AnchoredBox>
</Anchored>
</div>
{/*v=====----------------------< Page Controls >---------------------=====v*/} {/*v=====----------------------< Page Controls >---------------------=====v*/}
<div className='group'> <div className='group' role='group' aria-label='Pages' aria-hidden={!toolsVisible}>
<button <button
id='previous-page' id='previous-page'
className='previousPage tool' className='previousPage tool'
type='button'
title='Previous Page(s)'
onClick={()=>scrollToPage(pageNum - 1)} onClick={()=>scrollToPage(pageNum - 1)}
disabled={pageNum <= 1} disabled={pageNum <= 1}
> >
@@ -137,6 +197,7 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{
className='text-input' className='text-input'
type='text' type='text'
name='page' name='page'
title='Current page(s) in view'
inputMode='numeric' inputMode='numeric'
pattern='[0-9]' pattern='[0-9]'
value={pageNum} value={pageNum}
@@ -145,12 +206,14 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{
onBlur={()=>scrollToPage(pageNum)} onBlur={()=>scrollToPage(pageNum)}
onKeyDown={(e)=>e.key == 'Enter' && scrollToPage(pageNum)} onKeyDown={(e)=>e.key == 'Enter' && scrollToPage(pageNum)}
/> />
<span id='page-count'>/ {totalPages}</span> <span id='page-count' title='Total Page Count'>/ {totalPages}</span>
</div> </div>
<button <button
id='next-page' id='next-page'
className='tool' className='tool'
type='button'
title='Next Page(s)'
onClick={()=>scrollToPage(pageNum + 1)} onClick={()=>scrollToPage(pageNum + 1)}
disabled={pageNum >= totalPages} disabled={pageNum >= totalPages}
> >

View File

@@ -16,8 +16,8 @@
color : #CCCCCC; color : #CCCCCC;
background-color : #555555; background-color : #555555;
& > *:not(.toggleButton) { & > *:not(.toggleButton) {
opacity: 1; opacity : 1;
transition: all .2s ease; transition : all 0.2s ease;
} }
.group { .group {
@@ -34,6 +34,70 @@
align-items : center; align-items : center;
} }
.active, [aria-checked='true'] { background-color : #444444; }
.anchored-trigger {
&.active { background-color : #444444; }
}
.anchored-box {
--box-color : #555555;
top : 30px;
display : flex;
flex-direction : column;
gap : 5px;
padding : 15px;
margin-top : 10px;
font-size : 0.8em;
color : #CCCCCC;
background-color : var(--box-color);
border-radius : 5px;
h1 {
padding-bottom : 0.3em;
margin-bottom : 0.5em;
border-bottom : 1px solid currentColor;
}
h2 {
padding-bottom : 0.3em;
margin : 1em 0 0.5em 0;
color : lightgray;
border-bottom : 1px solid currentColor;
}
label {
display : flex;
gap : 6px;
align-items : center;
justify-content : space-between;
}
input {
height : unset;
&[type='range'] { padding : 0; }
}
&::before {
position : absolute;
top : -20px;
left : 50%;
width : 0px;
height : 0px;
pointer-events : none;
content : '';
border : 10px solid transparent;
border-bottom : 10px solid var(--box-color);
transform : translateX(-50%);
}
}
.radio-group:has(button[role='radio']) {
display : flex;
height : 100%;
border : 1px solid #333333;
}
input { input {
position : relative; position : relative;
height : 1.5em; height : 1.5em;
@@ -57,7 +121,7 @@
outline : none; outline : none;
} }
&:hover::after { &.hover-tooltip[value]:hover::after {
position : absolute; position : absolute;
bottom : -30px; bottom : -30px;
left : 50%; left : 50%;
@@ -83,7 +147,7 @@
} }
button { button {
box-sizing : content-box; box-sizing : border-box;
display : flex; display : flex;
align-items : center; align-items : center;
justify-content : center; justify-content : center;
@@ -94,35 +158,36 @@
font-weight : unset; font-weight : unset;
color : inherit; color : inherit;
background-color : unset; background-color : unset;
&:not(button:has(i, svg)) { padding : 0 8px; }
&:hover { background-color : #444444; } &:hover { background-color : #444444; }
&:focus { outline : 1px solid #D3D3D3; } &:focus { border : 1px solid #D3D3D3;outline : none;}
&:disabled { &:disabled {
color : #777777; color : #777777;
background-color : unset !important; background-color : unset !important;
} }
i { i { font-size : 1.2em; }
font-size:1.2em;
}
} }
&.hidden { &.hidden {
width: 32px; flex-wrap : nowrap;
transition: all .3s ease; width : 32px;
flex-wrap:nowrap; overflow : hidden;
overflow: hidden; background-color : unset;
background-color: unset; opacity : 0.5;
opacity: .5; transition : all 0.3s ease;
& > *:not(.toggleButton) { & > *:not(.toggleButton) {
opacity: 0; opacity : 0;
transition: all .2s ease; transition : all 0.2s ease;
} }
} }
} }
button.toggleButton { button.toggleButton {
z-index : 5; position : absolute;
position:absolute; left : 0;
left: 0; z-index : 5;
width: 32px; width : 32px;
min-width: unset; min-width : unset;
} }

View File

@@ -430,6 +430,7 @@ const EditPage = createClass({
{this.renderNavbar()} {this.renderNavbar()}
{this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} />} {this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} />}
<div className="content">
<SplitPane onDragFinish={this.handleSplitMove}> <SplitPane onDragFinish={this.handleSplitMove}>
<Editor <Editor
ref={this.editor} ref={this.editor}
@@ -463,6 +464,7 @@ const EditPage = createClass({
allowPrint={true} allowPrint={true}
/> />
</SplitPane> </SplitPane>
</div>
</div>; </div>;
} }
}); });

View File

@@ -100,6 +100,7 @@ const HomePage = createClass({
return <div className='homePage sitePage'> return <div className='homePage sitePage'>
<Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' /> <Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' />
{this.renderNavbar()} {this.renderNavbar()}
<div className="content">
<SplitPane onDragFinish={this.handleSplitMove}> <SplitPane onDragFinish={this.handleSplitMove}>
<Editor <Editor
ref={this.editor} ref={this.editor}
@@ -125,6 +126,7 @@ const HomePage = createClass({
themeBundle={this.state.themeBundle} themeBundle={this.state.themeBundle}
/> />
</SplitPane> </SplitPane>
</div>
<div className={cx('floatingSaveButton', { show: this.state.welcomeText != this.state.brew.text })} onClick={this.handleSave}> <div className={cx('floatingSaveButton', { show: this.state.welcomeText != this.state.brew.text })} onClick={this.handleSave}>
Save current <i className='fas fa-save' /> Save current <i className='fas fa-save' />
</div> </div>

View File

@@ -223,6 +223,7 @@ const NewPage = createClass({
render : function(){ render : function(){
return <div className='newPage sitePage'> return <div className='newPage sitePage'>
{this.renderNavbar()} {this.renderNavbar()}
<div className="content">
<SplitPane onDragFinish={this.handleSplitMove}> <SplitPane onDragFinish={this.handleSplitMove}>
<Editor <Editor
ref={this.editor} ref={this.editor}
@@ -254,6 +255,7 @@ const NewPage = createClass({
allowPrint={true} allowPrint={true}
/> />
</SplitPane> </SplitPane>
</div>
</div>; </div>;
} }
}); });

View File

@@ -411,10 +411,11 @@ const VaultPage = (props)=>{
}; };
return ( return (
<div className='vaultPage'> <div className='sitePage vaultPage'>
<link href='/themes/V3/Blank/style.css' rel='stylesheet' /> <link href='/themes/V3/Blank/style.css' rel='stylesheet' />
<link href='/themes/V3/5ePHB/style.css' rel='stylesheet' /> <link href='/themes/V3/5ePHB/style.css' rel='stylesheet' />
{renderNavItems()} {renderNavItems()}
<div className="content">
<SplitPane showDividerButtons={false}> <SplitPane showDividerButtons={false}>
<div className='form dataGroup'>{renderForm()}</div> <div className='form dataGroup'>{renderForm()}</div>
<div className='resultsContainer dataGroup'> <div className='resultsContainer dataGroup'>
@@ -422,6 +423,7 @@ const VaultPage = (props)=>{
{renderFoundBrews()} {renderFoundBrews()}
</div> </div>
</SplitPane> </SplitPane>
</div>
</div> </div>
); );
}; };

View File

@@ -5,7 +5,7 @@
*:not(input) { user-select : none; } *:not(input) { user-select : none; }
.dataGroup { .content .dataGroup {
width : 100%; width : 100%;
height : 100%; height : 100%;
background : white; background : white;

View File

@@ -73,3 +73,12 @@
.fit-width { .fit-width {
mask-image: url('../icons/fit-width.svg'); mask-image: url('../icons/fit-width.svg');
} }
.single-spread {
mask-image: url('../icons/single-spread.svg');
}
.facing-spread {
mask-image: url('../icons/facing-spread.svg');
}
.flow-spread {
mask-image: url('../icons/flow-spread.svg');
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.979101,0,0,0.919064,-29.0748,1.98095)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
<g transform="matrix(0.979101,0,0,0.919064,23.058,1.98095)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.0781,0,0,1.0781,-3.90545,-3.90502)">
<g transform="matrix(0.590052,0,0,0.553871,-13.8993,-2.19227)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
<g transform="matrix(0.590052,0,0,0.553871,-13.8993,44.3152)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
<g transform="matrix(0.590052,0,0,0.553871,17.5184,-2.19227)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
<g transform="matrix(0.590052,0,0,0.553871,50.0095,-2.19227)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
<g transform="matrix(0.590052,0,0,0.553871,17.5184,44.3152)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
<g transform="matrix(0.590052,0,0,0.553871,50.0095,44.3152)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.41826,0,0,1.3313,-26.7845,-19.5573)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 777 B

View File

@@ -4,7 +4,7 @@
"version": "3.16.0", "version": "3.16.0",
"engines": { "engines": {
"npm": "^10.2.x", "npm": "^10.2.x",
"node": "^20.17.x" "node": "^20.18.x"
}, },
"repository": { "repository": {
"type": "git", "type": "git",