Merge branch 'master' into Language-Attribute
@@ -135,7 +135,8 @@ const BrewRenderer = createClass({
|
||||
|
||||
renderStyle : function() {
|
||||
if(!this.props.style) return;
|
||||
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.props.style} </style>` }} />;
|
||||
//return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>@layer styleTab {\n${this.props.style}\n} </style>` }} />;
|
||||
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>\n${this.props.style}\n</style>` }} />;
|
||||
},
|
||||
|
||||
renderPage : function(pageText, index){
|
||||
|
||||
@@ -32,6 +32,7 @@ const Editor = createClass({
|
||||
onTextChange : ()=>{},
|
||||
onStyleChange : ()=>{},
|
||||
onMetaChange : ()=>{},
|
||||
reportError : ()=>{},
|
||||
|
||||
renderer : 'legacy'
|
||||
};
|
||||
@@ -139,10 +140,10 @@ const Editor = createClass({
|
||||
|
||||
// Highlight injectors {style}
|
||||
if(line.includes('{') && line.includes('}')){
|
||||
const regex = /(?<!{){(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/g;
|
||||
const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm;
|
||||
let match;
|
||||
while ((match = regex.exec(line)) != null) {
|
||||
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'injection' });
|
||||
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' });
|
||||
}
|
||||
}
|
||||
// Highlight inline spans {{content}}
|
||||
@@ -291,7 +292,8 @@ const Editor = createClass({
|
||||
rerenderParent={this.rerenderParent} />
|
||||
<MetadataEditor
|
||||
metadata={this.props.brew}
|
||||
onChange={this.props.onMetaChange} />
|
||||
onChange={this.props.onMetaChange}
|
||||
reportError={this.props.reportError}/>
|
||||
</>;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,18 +4,24 @@ const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require('superagent');
|
||||
const request = require('../../utils/request-middleware.js');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Combobox = require('client/components/combobox.jsx');
|
||||
const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx');
|
||||
|
||||
const Themes = require('themes/themes.json');
|
||||
const validations = require('./validations.js')
|
||||
const validations = require('./validations.js');
|
||||
|
||||
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder'];
|
||||
|
||||
const homebreweryThumbnail = require('../../thumbnail.png');
|
||||
|
||||
const callIfExists = (val, fn, ...args)=>{
|
||||
if(val[fn]) {
|
||||
val[fn](...args);
|
||||
}
|
||||
};
|
||||
|
||||
const MetadataEditor = createClass({
|
||||
displayName : 'MetadataEditor',
|
||||
getDefaultProps : function() {
|
||||
@@ -33,7 +39,8 @@ const MetadataEditor = createClass({
|
||||
theme : '5ePHB',
|
||||
lang : 'en'
|
||||
},
|
||||
onChange : ()=>{}
|
||||
onChange : ()=>{},
|
||||
reportError : ()=>{}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -55,15 +62,13 @@ const MetadataEditor = createClass({
|
||||
},
|
||||
|
||||
handleFieldChange : function(name, e){
|
||||
e.persist();
|
||||
|
||||
// load validation rules, and check input value against them
|
||||
const inputRules = validations[name] ?? [];
|
||||
const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean);
|
||||
|
||||
// if no validation rules, save to props
|
||||
if(validationErr.length === 0){
|
||||
e.target.setCustomValidity('');
|
||||
callIfExists(e.target, 'setCustomValidity', '');
|
||||
this.props.onChange({
|
||||
...this.props.metadata,
|
||||
[name] : e.target.value
|
||||
@@ -73,10 +78,14 @@ const MetadataEditor = createClass({
|
||||
const errMessage = validationErr.map((err)=>{
|
||||
return `- ${err}`;
|
||||
}).join('\n');
|
||||
console.log(`Input error ${errMessage}`);
|
||||
e.target.setCustomValidity(errMessage);
|
||||
e.target.reportValidity();
|
||||
};
|
||||
|
||||
// console.log(`Input error ${errMessage}`);
|
||||
// e.target.setCustomValidity(errMessage);
|
||||
// e.target.reportValidity();
|
||||
// };
|
||||
callIfExists(e.target, 'setCustomValidity', errMessage);
|
||||
callIfExists(e.target, 'reportValidity');
|
||||
}
|
||||
},
|
||||
|
||||
handleSystem : function(system, e){
|
||||
@@ -125,8 +134,12 @@ const MetadataEditor = createClass({
|
||||
|
||||
request.delete(`/api/${this.props.metadata.googleId ?? ''}${this.props.metadata.editId}`)
|
||||
.send()
|
||||
.end(function(err, res){
|
||||
window.location.href = '/';
|
||||
.end((err, res)=>{
|
||||
if(err) {
|
||||
this.props.reportError(err);
|
||||
} else {
|
||||
window.location.href = '/';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -188,6 +201,10 @@ const MetadataEditor = createClass({
|
||||
return <div className='item' key={''} onClick={()=>this.handleTheme(theme)} title={''}>
|
||||
{`${theme.renderer} : ${theme.name}`}
|
||||
<img src={`/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`}/>
|
||||
<div className='preview'>
|
||||
<h6>{`${theme.name}`} preview</h6>
|
||||
<img src={`/themes/${theme.renderer}/${theme.path}/dropdownPreview.png`}/>
|
||||
</div>
|
||||
</div>;
|
||||
});
|
||||
};
|
||||
@@ -197,14 +214,14 @@ const MetadataEditor = createClass({
|
||||
|
||||
if(this.props.metadata.renderer == 'legacy') {
|
||||
dropdown =
|
||||
<Nav.dropdown className='disabled' trigger='disabled'>
|
||||
<Nav.dropdown className='disabled value' trigger='disabled'>
|
||||
<div>
|
||||
{`Themes are not supported in the Legacy Renderer`} <i className='fas fa-caret-down'></i>
|
||||
</div>
|
||||
</Nav.dropdown>;
|
||||
} else {
|
||||
dropdown =
|
||||
<Nav.dropdown trigger='click'>
|
||||
<Nav.dropdown className='value' trigger='click'>
|
||||
<div>
|
||||
{`${_.upperFirst(currentTheme.renderer)} : ${currentTheme.name}`} <i className='fas fa-caret-down'></i>
|
||||
</div>
|
||||
@@ -290,6 +307,8 @@ const MetadataEditor = createClass({
|
||||
|
||||
render : function(){
|
||||
return <div className='metadataEditor'>
|
||||
<h1 className='sectionHead'>Brew</h1>
|
||||
|
||||
<div className='field title'>
|
||||
<label>title</label>
|
||||
<input type='text' className='value'
|
||||
@@ -323,8 +342,6 @@ const MetadataEditor = createClass({
|
||||
values={this.props.metadata.tags}
|
||||
onChange={(e)=>this.handleFieldChange('tags', e)}/>
|
||||
|
||||
{this.renderAuthors()}
|
||||
|
||||
<div className='field systems'>
|
||||
<label>systems</label>
|
||||
<div className='value'>
|
||||
@@ -338,6 +355,23 @@ const MetadataEditor = createClass({
|
||||
|
||||
{this.renderRenderOptions()}
|
||||
|
||||
<hr/>
|
||||
|
||||
<h1 className='sectionHead'>Authors</h1>
|
||||
|
||||
{this.renderAuthors()}
|
||||
|
||||
<StringArrayEditor label='invited authors' valuePatterns={[/.+/]}
|
||||
validators={[(v)=>!this.props.metadata.authors?.includes(v)]}
|
||||
placeholder='invite author' unique={true}
|
||||
values={this.props.metadata.invitedAuthors}
|
||||
notes={['Invited authors are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']}
|
||||
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}/>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h1 className='sectionHead'>Privacy</h1>
|
||||
|
||||
<div className='field publish'>
|
||||
<label>publish</label>
|
||||
<div className='value'>
|
||||
|
||||
@@ -10,6 +10,15 @@
|
||||
height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this.
|
||||
overflow-y : auto;
|
||||
|
||||
.sectionHead {
|
||||
font-weight: 1000;
|
||||
margin: 20px 0;
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
& > div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -26,13 +35,13 @@
|
||||
flex-direction: column;
|
||||
flex: 5 0 200px;
|
||||
gap: 10px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.field{
|
||||
display : flex;
|
||||
flex-wrap : wrap;
|
||||
width : 100%;
|
||||
min-width : 200px;
|
||||
position : relative;
|
||||
@@ -89,6 +98,10 @@
|
||||
&.language .language-dropdown {
|
||||
max-width:150px;
|
||||
}
|
||||
small {
|
||||
font-size : 0.6em;
|
||||
font-style : italic;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -139,10 +152,6 @@
|
||||
button.unpublish{
|
||||
.button(@silver);
|
||||
}
|
||||
small{
|
||||
font-size : 0.6em;
|
||||
font-style : italic;
|
||||
}
|
||||
}
|
||||
|
||||
.delete.field .value{
|
||||
@@ -159,7 +168,6 @@
|
||||
font-size : 13.33px;
|
||||
.navDropdownContainer {
|
||||
background-color : white;
|
||||
width : 100%;
|
||||
position : relative;
|
||||
z-index : 500;
|
||||
&.disabled {
|
||||
@@ -182,24 +190,51 @@
|
||||
}
|
||||
.navDropdown {
|
||||
box-shadow : 0px 5px 10px rgba(0, 0, 0, 0.3);
|
||||
position : absolute;
|
||||
width : 100%;
|
||||
position : absolute;
|
||||
width : 100%;
|
||||
.item {
|
||||
padding : 3px 3px;
|
||||
border-top : 1px solid rgb(118, 118, 118);
|
||||
position : relative;
|
||||
overflow : hidden;
|
||||
padding : 3px 3px;
|
||||
border-top : 1px solid rgb(118, 118, 118);
|
||||
position : relative;
|
||||
overflow : visible;
|
||||
background-color : white;
|
||||
.preview {
|
||||
display : flex;
|
||||
flex-direction: column;
|
||||
background : #ccc;
|
||||
border-radius : 5px;
|
||||
box-shadow : 0 0 5px black;
|
||||
width : 200px;
|
||||
color :black;
|
||||
position : absolute;
|
||||
top : 0;
|
||||
right : 0;
|
||||
opacity : 0;
|
||||
transition : opacity 250ms ease;
|
||||
z-index : 1;
|
||||
overflow :hidden;
|
||||
h6 {
|
||||
font-weight : 900;
|
||||
padding-inline:1em;
|
||||
padding-block :.5em;
|
||||
border-bottom :2px solid hsl(0,0%,40%);
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background-color : @blue;
|
||||
color : white;
|
||||
color : white;
|
||||
}
|
||||
img {
|
||||
mask-image : linear-gradient(90deg, transparent, black 20%);
|
||||
&:hover > .preview {
|
||||
opacity: 1;
|
||||
}
|
||||
>img {
|
||||
mask-image : linear-gradient(90deg, transparent, black 20%);
|
||||
-webkit-mask-image : linear-gradient(90deg, transparent, black 20%);
|
||||
position : absolute;
|
||||
left : ~"max(100px, 100% - 300px)";
|
||||
top : 0px;
|
||||
position : absolute;
|
||||
right : 0;
|
||||
top : 0px;
|
||||
width : 50%;
|
||||
height : 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,6 +242,7 @@
|
||||
}
|
||||
.field .list {
|
||||
display: flex;
|
||||
flex: 1 0;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> * {
|
||||
|
||||
@@ -163,15 +163,23 @@ const SnippetGroup = createClass({
|
||||
onSnippetClick : function(){},
|
||||
};
|
||||
},
|
||||
handleSnippetClick : function(snippet){
|
||||
handleSnippetClick : function(e, snippet){
|
||||
e.stopPropagation();
|
||||
this.props.onSnippetClick(execute(snippet.gen, this.props.brew));
|
||||
},
|
||||
renderSnippets : function(){
|
||||
return _.map(this.props.snippets, (snippet)=>{
|
||||
return <div className='snippet' key={snippet.name} onClick={()=>this.handleSnippetClick(snippet)}>
|
||||
renderSnippets : function(snippets){
|
||||
return _.map(snippets, (snippet)=>{
|
||||
return <div className='snippet' key={snippet.name} onClick={(e)=>this.handleSnippetClick(e, snippet)}>
|
||||
<i className={snippet.icon} />
|
||||
{snippet.name}
|
||||
<span className='name'>{snippet.name}</span>
|
||||
{snippet.experimental && <span className='beta'>beta</span>}
|
||||
{snippet.subsnippets && <>
|
||||
<i className='fas fa-caret-right'></i>
|
||||
<div className='dropdown side'>
|
||||
{this.renderSnippets(snippet.subsnippets)}
|
||||
</div></>}
|
||||
</div>;
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
@@ -182,7 +190,7 @@ const SnippetGroup = createClass({
|
||||
<span className='groupName'>{this.props.groupName}</span>
|
||||
</div>
|
||||
<div className='dropdown'>
|
||||
{this.renderSnippets()}
|
||||
{this.renderSnippets(this.props.snippets)}
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
@import (less) './client/icons/customIcons.less';
|
||||
.snippetBar{
|
||||
@menuHeight : 25px;
|
||||
position : relative;
|
||||
@@ -83,7 +83,7 @@
|
||||
.snippetGroup{
|
||||
border-right : 1px solid black;
|
||||
&:hover{
|
||||
.dropdown{
|
||||
&>.dropdown{
|
||||
visibility : visible;
|
||||
}
|
||||
}
|
||||
@@ -97,15 +97,45 @@
|
||||
background-color : #ddd;
|
||||
.snippet{
|
||||
.animate(background-color);
|
||||
padding : 5px;
|
||||
cursor : pointer;
|
||||
font-size : 10px;
|
||||
display : flex;
|
||||
align-items : center;
|
||||
min-width : max-content;
|
||||
padding : 5px;
|
||||
cursor : pointer;
|
||||
font-size : 10px;
|
||||
i{
|
||||
margin-right : 8px;
|
||||
font-size : 1.2em;
|
||||
height : 1.2em;
|
||||
&~i{
|
||||
margin-right: 0;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
.name {
|
||||
margin-right : auto;
|
||||
}
|
||||
.beta {
|
||||
color : white;
|
||||
padding : 4px 6px;
|
||||
line-height : 1em;
|
||||
margin-left : 5px;
|
||||
align-self : center;
|
||||
background : grey;
|
||||
border-radius : 12px;
|
||||
font-family : monospace;
|
||||
}
|
||||
&:hover{
|
||||
background-color : #999;
|
||||
&>.dropdown{
|
||||
visibility : visible;
|
||||
&.side {
|
||||
left: 100%;
|
||||
top: 0%;
|
||||
margin-left:0;
|
||||
box-shadow: -1px 1px 2px 0px #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ const StringArrayEditor = createClass({
|
||||
label : '',
|
||||
values : [],
|
||||
valuePatterns : null,
|
||||
validators : [],
|
||||
placeholder : '',
|
||||
notes : [],
|
||||
unique : false,
|
||||
cannotEdit : [],
|
||||
onChange : ()=>{}
|
||||
@@ -83,7 +85,8 @@ const StringArrayEditor = createClass({
|
||||
}
|
||||
const matchesPatterns = !this.props.valuePatterns || this.props.valuePatterns.some((pattern)=>!!(value || '').match(pattern));
|
||||
const uniqueIfSet = !this.props.unique || !values.includes(value);
|
||||
return matchesPatterns && uniqueIfSet;
|
||||
const passesValidators = !this.props.validators || this.props.validators.every((validator)=>validator(value));
|
||||
return matchesPatterns && uniqueIfSet && passesValidators;
|
||||
},
|
||||
|
||||
handleValueInputKeyDown : function(event, index) {
|
||||
@@ -123,17 +126,21 @@ const StringArrayEditor = createClass({
|
||||
</div>
|
||||
);
|
||||
|
||||
return <div className='field values'>
|
||||
return <div className='field'>
|
||||
<label>{this.props.label}</label>
|
||||
<div className='list'>
|
||||
{valueElements}
|
||||
<div className='input-group'>
|
||||
<input type='text' className={`value ${this.valueIsValid(this.state.temporaryValue) ? '' : 'invalid'}`} placeholder={this.props.placeholder}
|
||||
value={this.state.temporaryValue}
|
||||
onKeyDown={(e)=>this.handleValueInputKeyDown(e)}
|
||||
onChange={(e)=>this.setState({ temporaryValue: e.target.value })}/>
|
||||
{this.valueIsValid(this.state.temporaryValue) ? <div className='icon steel' onClick={(e)=>{ e.stopPropagation(); this.addValue(this.state.temporaryValue); }}><i className='fa fa-check fa-fw'/></div> : null}
|
||||
<div style={{ flex: '1 0' }}>
|
||||
<div className='list'>
|
||||
{valueElements}
|
||||
<div className='input-group'>
|
||||
<input type='text' className={`value ${this.valueIsValid(this.state.temporaryValue) ? '' : 'invalid'}`} placeholder={this.props.placeholder}
|
||||
value={this.state.temporaryValue}
|
||||
onKeyDown={(e)=>this.handleValueInputKeyDown(e)}
|
||||
onChange={(e)=>this.setState({ temporaryValue: e.target.value })}/>
|
||||
{this.valueIsValid(this.state.temporaryValue) ? <div className='icon steel' onClick={(e)=>{ e.stopPropagation(); this.addValue(this.state.temporaryValue); }}><i className='fa fa-check fa-fw'/></div> : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.props.notes ? this.props.notes.map((n)=><p><small>{n}</small></p>) : null}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 305 KiB |
8
client/homebrew/googleDrive.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg viewBox="0 0 87.3 78" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m6.6 66.85 3.85 6.65c.8 1.4 1.95 2.5 3.3 3.3l13.75-23.8h-27.5c0 1.55.4 3.1 1.2 4.5z" fill="#0066da"/>
|
||||
<path d="m43.65 25-13.75-23.8c-1.35.8-2.5 1.9-3.3 3.3l-25.4 44a9.06 9.06 0 0 0 -1.2 4.5h27.5z" fill="#00ac47"/>
|
||||
<path d="m73.55 76.8c1.35-.8 2.5-1.9 3.3-3.3l1.6-2.75 7.65-13.25c.8-1.4 1.2-2.95 1.2-4.5h-27.502l5.852 11.5z" fill="#ea4335"/>
|
||||
<path d="m43.65 25 13.75-23.8c-1.35-.8-2.9-1.2-4.5-1.2h-18.5c-1.6 0-3.15.45-4.5 1.2z" fill="#00832d"/>
|
||||
<path d="m59.8 53h-32.3l-13.75 23.8c1.35.8 2.9 1.2 4.5 1.2h50.8c1.6 0 3.15-.45 4.5-1.2z" fill="#2684fc"/>
|
||||
<path d="m73.4 26.5-12.7-22c-.8-1.4-1.95-2.5-3.3-3.3l-13.75 23.8 16.15 28h27.45c0-1.55-.4-3.1-1.2-4.5z" fill="#ffba00"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 755 B |
|
Before Width: | Height: | Size: 17 KiB |
85
client/homebrew/navbar/error-navitem.jsx
Normal file
@@ -0,0 +1,85 @@
|
||||
require('./error-navitem.less');
|
||||
const React = require('react');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const createClass = require('create-react-class');
|
||||
|
||||
const ErrorNavItem = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
error : '',
|
||||
parent : null
|
||||
};
|
||||
},
|
||||
render : function() {
|
||||
const clearError = ()=>{
|
||||
const state = {
|
||||
error : null
|
||||
};
|
||||
if(this.props.parent.state.isSaving) {
|
||||
state.isSaving = false;
|
||||
}
|
||||
this.props.parent.setState(state);
|
||||
};
|
||||
|
||||
const error = this.props.error;
|
||||
const response = error.response;
|
||||
const status = response.status;
|
||||
const message = response.body?.message;
|
||||
let errMsg = '';
|
||||
try {
|
||||
errMsg += `${error.toString()}\n\n`;
|
||||
errMsg += `\`\`\`\n${error.stack}\n`;
|
||||
errMsg += `${JSON.stringify(response.error, null, ' ')}\n\`\`\``;
|
||||
console.log(errMsg);
|
||||
} catch (e){}
|
||||
|
||||
if(status === 409) {
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={clearError}>
|
||||
{message ?? 'Conflict: please refresh to get latest changes'}
|
||||
</div>
|
||||
</Nav.item>;
|
||||
} else if(status === 412) {
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={clearError}>
|
||||
{message ?? 'Your client is out of date. Please save your changes elsewhere and refresh.'}
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
if(response.req.url.match(/^\/api.*Google.*$/m)){
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={clearError}>
|
||||
Looks like your Google credentials have
|
||||
expired! Visit our log in page to sign out
|
||||
and sign back in with Google,
|
||||
then try saving again!
|
||||
<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>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
Looks like there was a problem saving. <br />
|
||||
Report the issue <a target='_blank' rel='noopener noreferrer' href={`https://github.com/naturalcrit/homebrewery/issues/new?template=save_issue.yml&error-code=${encodeURIComponent(errMsg)}`}>
|
||||
here
|
||||
</a>.
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ErrorNavItem;
|
||||
77
client/homebrew/navbar/error-navitem.less
Normal file
@@ -0,0 +1,77 @@
|
||||
.navItem {
|
||||
&.error {
|
||||
position : relative;
|
||||
background-color : @red;
|
||||
}
|
||||
|
||||
.errorContainer{
|
||||
animation-name: glideDown;
|
||||
animation-duration: 0.4s;
|
||||
position : absolute;
|
||||
top : 100%;
|
||||
left : 50%;
|
||||
z-index : 1000;
|
||||
width : 140px;
|
||||
padding : 3px;
|
||||
color : white;
|
||||
background-color : #333;
|
||||
border : 3px solid #444;
|
||||
border-radius : 5px;
|
||||
transform : translate(-50% + 3px, 10px);
|
||||
text-align : center;
|
||||
font-size : 10px;
|
||||
font-weight : 800;
|
||||
text-transform : uppercase;
|
||||
a{
|
||||
color : @teal;
|
||||
}
|
||||
&:before {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #444;
|
||||
left: 53px;
|
||||
top: -23px;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #333;
|
||||
left: 53px;
|
||||
top: -19px;
|
||||
}
|
||||
.deny {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
border-left : 1px solid #666;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : red;
|
||||
}
|
||||
}
|
||||
.confirm {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
color : white;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : teal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,6 @@ const AccountPage = createClass({
|
||||
},
|
||||
|
||||
renderUiItems : function() {
|
||||
// console.log(this.props.uiItems);
|
||||
return <>
|
||||
<div className='dataGroup'>
|
||||
<h1>Account Information <i className='fas fa-user'></i></h1>
|
||||
@@ -51,12 +50,16 @@ const AccountPage = createClass({
|
||||
</div>
|
||||
<div className='dataGroup'>
|
||||
<h3>Homebrewery Information <NaturalCritIcon /></h3>
|
||||
<p><strong>Brews on Homebrewery: </strong> {this.props.uiItems.mongoCount || '-'}</p>
|
||||
<p><strong>Brews on Homebrewery: </strong> {this.props.uiItems.mongoCount}</p>
|
||||
</div>
|
||||
<div className='dataGroup'>
|
||||
<h3>Google Information <i className='fab fa-google-drive'></i></h3>
|
||||
<p><strong>Linked to Google: </strong> {this.props.uiItems.googleId ? 'YES' : 'NO'}</p>
|
||||
{this.props.uiItems.googleId ? <p><strong>Brews on Google Drive: </strong> {this.props.uiItems.fileCount || '-'}</p> : '' }
|
||||
{this.props.uiItems.googleId &&
|
||||
<p>
|
||||
<strong>Brews on Google Drive: </strong> {this.props.uiItems.googleCount ?? <>Unable to retrieve files - <a href='https://github.com/naturalcrit/homebrewery/discussions/1580'>follow these steps to renew your Google credentials.</a></>}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</>;
|
||||
},
|
||||
|
||||
@@ -4,9 +4,9 @@ const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const moment = require('moment');
|
||||
const request = require('superagent');
|
||||
const request = require('../../../../utils/request-middleware.js');
|
||||
|
||||
const googleDriveIcon = require('../../../../googleDrive.png');
|
||||
const googleDriveIcon = require('../../../../googleDrive.svg');
|
||||
const dedent = require('dedent-tabs').default;
|
||||
|
||||
const BrewItem = createClass({
|
||||
@@ -18,7 +18,8 @@ const BrewItem = createClass({
|
||||
description : '',
|
||||
authors : [],
|
||||
stubbed : true
|
||||
}
|
||||
},
|
||||
reportError : ()=>{}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -33,8 +34,12 @@ const BrewItem = createClass({
|
||||
|
||||
request.delete(`/api/${this.props.brew.googleId ?? ''}${this.props.brew.editId}`)
|
||||
.send()
|
||||
.end(function(err, res){
|
||||
location.reload();
|
||||
.end((err, res)=>{
|
||||
if(err) {
|
||||
this.props.reportError(err);
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
}
|
||||
}
|
||||
.googleDriveIcon {
|
||||
height : 20px;
|
||||
height : 18px;
|
||||
padding : 0px;
|
||||
margin : -5px;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ const ListPage = createClass({
|
||||
brews : []
|
||||
}
|
||||
],
|
||||
navItems : <></>
|
||||
navItems : <></>,
|
||||
reportError : null
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
@@ -81,7 +82,7 @@ const ListPage = createClass({
|
||||
if(!brews || !brews.length) return <div className='noBrews'>No Brews.</div>;
|
||||
|
||||
return _.map(brews, (brew, idx)=>{
|
||||
return <BrewItem brew={brew} key={idx}/>;
|
||||
return <BrewItem brew={brew} key={idx} reportError={this.props.reportError}/>;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -218,12 +219,13 @@ const ListPage = createClass({
|
||||
|
||||
render : function(){
|
||||
return <div className='listPage sitePage'>
|
||||
{/*<style>@layer V3_5ePHB, bundle;</style>*/}
|
||||
<link href='/themes/V3/5ePHB/style.css' rel='stylesheet'/>
|
||||
{this.props.navItems}
|
||||
{this.renderSortOptions()}
|
||||
|
||||
<div className='content V3'>
|
||||
<div className='phb page'>
|
||||
<div className='page'>
|
||||
{this.renderBrewCollection(this.state.brewCollection)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
-moz-column-width : auto;
|
||||
-webkit-column-gap : auto;
|
||||
-moz-column-gap : auto;
|
||||
height : auto;
|
||||
min-height : 279.4mm;
|
||||
margin : 20px auto;
|
||||
}
|
||||
.listPage{
|
||||
.content{
|
||||
.phb{
|
||||
.noColumns();
|
||||
height : auto;
|
||||
min-height : 279.4mm;
|
||||
margin : 20px auto;
|
||||
.page{
|
||||
.noColumns() !important; //Needed to override PHB Theme since this is on a lower @layer
|
||||
&::after{
|
||||
display : none;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ require('./editPage.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const request = require('superagent');
|
||||
const request = require('../../utils/request-middleware.js');
|
||||
const { Meta } = require('vitreum/headtags');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
@@ -12,6 +12,7 @@ const Navbar = require('../../navbar/navbar.jsx');
|
||||
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
|
||||
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||
const PrintLink = require('../../navbar/print.navitem.jsx');
|
||||
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||
|
||||
@@ -21,8 +22,9 @@ const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
const Markdown = require('naturalcrit/markdown.js');
|
||||
|
||||
const googleDriveActive = require('../../googleDrive.png');
|
||||
const googleDriveInactive = require('../../googleDriveMono.png');
|
||||
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
|
||||
|
||||
const googleDriveIcon = require('../../googleDrive.svg');
|
||||
|
||||
const SAVE_TIMEOUT = 3000;
|
||||
|
||||
@@ -30,25 +32,7 @@ const EditPage = createClass({
|
||||
displayName : 'EditPage',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : {
|
||||
text : '',
|
||||
style : '',
|
||||
shareId : null,
|
||||
editId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
gDrive : false,
|
||||
trashed : false,
|
||||
|
||||
title : '',
|
||||
description : '',
|
||||
tags : '',
|
||||
published : false,
|
||||
authors : [],
|
||||
systems : [],
|
||||
renderer : 'legacy',
|
||||
lang : 'en'
|
||||
}
|
||||
brew : DEFAULT_BREW_LOAD
|
||||
};
|
||||
},
|
||||
|
||||
@@ -61,7 +45,7 @@ const EditPage = createClass({
|
||||
alertLoginToTransfer : false,
|
||||
saveGoogle : this.props.brew.googleId ? true : false,
|
||||
confirmGoogleTransfer : false,
|
||||
errors : null,
|
||||
error : null,
|
||||
htmlErrors : Markdown.validate(this.props.brew.text),
|
||||
url : '',
|
||||
autoSave : true,
|
||||
@@ -76,7 +60,6 @@ const EditPage = createClass({
|
||||
url : window.location.href
|
||||
});
|
||||
|
||||
|
||||
this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy
|
||||
|
||||
this.setState({ autoSave: JSON.parse(localStorage.getItem('AUTOSAVE_ON')) ?? true }, ()=>{
|
||||
@@ -173,7 +156,10 @@ const EditPage = createClass({
|
||||
this.setState((prevState)=>({
|
||||
confirmGoogleTransfer : !prevState.confirmGoogleTransfer
|
||||
}));
|
||||
this.clearErrors();
|
||||
this.setState({
|
||||
error : null,
|
||||
isSaving : false
|
||||
});
|
||||
},
|
||||
|
||||
closeAlerts : function(event){
|
||||
@@ -189,24 +175,16 @@ const EditPage = createClass({
|
||||
this.setState((prevState)=>({
|
||||
saveGoogle : !prevState.saveGoogle,
|
||||
isSaving : false,
|
||||
errors : null
|
||||
error : null
|
||||
}), ()=>this.save());
|
||||
},
|
||||
|
||||
clearErrors : function(){
|
||||
this.setState({
|
||||
errors : null,
|
||||
isSaving : false
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
save : async function(){
|
||||
if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel();
|
||||
|
||||
this.setState((prevState)=>({
|
||||
isSaving : true,
|
||||
errors : null,
|
||||
error : null,
|
||||
htmlErrors : Markdown.validate(prevState.brew.text)
|
||||
}));
|
||||
|
||||
@@ -221,8 +199,9 @@ const EditPage = createClass({
|
||||
.send(brew)
|
||||
.catch((err)=>{
|
||||
console.log('Error Updating Local Brew');
|
||||
this.setState({ errors: err });
|
||||
this.setState({ error: err });
|
||||
});
|
||||
if(!res) return;
|
||||
|
||||
this.savedBrew = res.body;
|
||||
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`);
|
||||
@@ -231,7 +210,8 @@ const EditPage = createClass({
|
||||
brew : { ...prevState.brew,
|
||||
googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null,
|
||||
editId : this.savedBrew.editId,
|
||||
shareId : this.savedBrew.shareId
|
||||
shareId : this.savedBrew.shareId,
|
||||
version : this.savedBrew.version
|
||||
},
|
||||
isPending : false,
|
||||
isSaving : false,
|
||||
@@ -241,10 +221,7 @@ const EditPage = createClass({
|
||||
|
||||
renderGoogleDriveIcon : function(){
|
||||
return <Nav.item className='googleDriveStorage' onClick={this.handleGoogleClick}>
|
||||
{this.state.saveGoogle
|
||||
? <img src={googleDriveActive} alt='googleDriveActive'/>
|
||||
: <img src={googleDriveInactive} alt='googleDriveInactive'/>
|
||||
}
|
||||
<img src={googleDriveIcon} className={this.state.saveGoogle ? '' : 'inactive'} alt='Google Drive icon'/>
|
||||
|
||||
{this.state.confirmGoogleTransfer &&
|
||||
<div className='errorContainer' onClick={this.closeAlerts}>
|
||||
@@ -281,67 +258,6 @@ const EditPage = createClass({
|
||||
},
|
||||
|
||||
renderSaveButton : function(){
|
||||
if(this.state.errors){
|
||||
let errMsg = '';
|
||||
try {
|
||||
errMsg += `${this.state.errors.toString()}\n\n`;
|
||||
errMsg += `\`\`\`\n${this.state.errors.stack}\n`;
|
||||
errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``;
|
||||
console.log(errMsg);
|
||||
} catch (e){}
|
||||
|
||||
// if(this.state.errors.status == '401'){
|
||||
// return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
// Oops!
|
||||
// <div className='errorContainer' onClick={this.clearErrors}>
|
||||
// You must be signed in to a Google account
|
||||
// to save this to<br />Google Drive!<br />
|
||||
// <a target='_blank' rel='noopener noreferrer'
|
||||
// href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||
// <div className='confirm'>
|
||||
// Sign In
|
||||
// </div>
|
||||
// </a>
|
||||
// <div className='deny'>
|
||||
// Not Now
|
||||
// </div>
|
||||
// </div>
|
||||
// </Nav.item>;
|
||||
// }
|
||||
|
||||
if(this.state.errors.response.req.url.match(/^\/api.*Google.*$/m)){
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={this.clearErrors}>
|
||||
Looks like your Google credentials have
|
||||
expired! Visit our log in page to sign out
|
||||
and sign back in with Google,
|
||||
then try saving again!
|
||||
<a target='_blank' rel='noopener noreferrer'
|
||||
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||
<div className='confirm'>
|
||||
Sign In
|
||||
</div>
|
||||
</a>
|
||||
<div className='deny'>
|
||||
Not Now
|
||||
</div>
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
Looks like there was a problem saving. <br />
|
||||
Report the issue <a target='_blank' rel='noopener noreferrer'
|
||||
href={`https://github.com/naturalcrit/homebrewery/issues/new?template=save_issue.yml&error-code=${encodeURIComponent(errMsg)}`}>
|
||||
here
|
||||
</a>.
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
if(this.state.autoSaveWarning && this.hasChanges()){
|
||||
this.setAutosaveWarning();
|
||||
const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60);
|
||||
@@ -385,6 +301,12 @@ const EditPage = createClass({
|
||||
this.warningTimer;
|
||||
},
|
||||
|
||||
errorReported : function(error) {
|
||||
this.setState({
|
||||
error
|
||||
});
|
||||
},
|
||||
|
||||
renderAutoSaveButton : function(){
|
||||
return <Nav.item onClick={this.handleAutoSave}>
|
||||
Autosave <i className={this.state.autoSave ? 'fas fa-power-off active' : 'fas fa-power-off'}></i>
|
||||
@@ -429,10 +351,13 @@ const EditPage = createClass({
|
||||
|
||||
<Nav.section>
|
||||
{this.renderGoogleDriveIcon()}
|
||||
<Nav.dropdown className='save-menu'>
|
||||
{this.renderSaveButton()}
|
||||
{this.renderAutoSaveButton()}
|
||||
</Nav.dropdown>
|
||||
{this.state.error ?
|
||||
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
|
||||
<Nav.dropdown className='save-menu'>
|
||||
{this.renderSaveButton()}
|
||||
{this.renderAutoSaveButton()}
|
||||
</Nav.dropdown>
|
||||
}
|
||||
<NewBrew />
|
||||
<HelpNavItem/>
|
||||
<Nav.dropdown>
|
||||
@@ -470,6 +395,7 @@ const EditPage = createClass({
|
||||
onTextChange={this.handleTextChange}
|
||||
onStyleChange={this.handleStyleChange}
|
||||
onMetaChange={this.handleMetaChange}
|
||||
reportError={this.errorReported}
|
||||
renderer={this.state.brew.renderer}
|
||||
/>
|
||||
<BrewRenderer
|
||||
|
||||
@@ -13,87 +13,17 @@
|
||||
cursor : initial;
|
||||
color : #666;
|
||||
}
|
||||
&.error{
|
||||
position : relative;
|
||||
background-color : @red;
|
||||
}
|
||||
}
|
||||
.googleDriveStorage {
|
||||
position : relative;
|
||||
}
|
||||
.googleDriveStorage img{
|
||||
height : 20px;
|
||||
height : 18px;
|
||||
padding : 0px;
|
||||
margin : -5px;
|
||||
}
|
||||
.errorContainer{
|
||||
animation-name: glideDown;
|
||||
animation-duration: 0.4s;
|
||||
position : absolute;
|
||||
top : 100%;
|
||||
left : 50%;
|
||||
z-index : 500;
|
||||
width : 140px;
|
||||
padding : 3px;
|
||||
color : white;
|
||||
background-color : #333;
|
||||
border : 3px solid #444;
|
||||
border-radius : 5px;
|
||||
transform : translate(-50% + 3px, 10px);
|
||||
text-align : center;
|
||||
font-size : 10px;
|
||||
font-weight : 800;
|
||||
text-transform : uppercase;
|
||||
a{
|
||||
color : @teal;
|
||||
}
|
||||
&:before {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #444;
|
||||
left: 53px;
|
||||
top: -23px;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #333;
|
||||
left: 53px;
|
||||
top: -19px;
|
||||
}
|
||||
.deny {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
border-left : 1px solid #666;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : red;
|
||||
}
|
||||
}
|
||||
.confirm {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
color : white;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : teal;
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require('superagent');
|
||||
const request = require('../../utils/request-middleware.js');
|
||||
const { Meta } = require('vitreum/headtags');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
@@ -12,35 +12,38 @@ const NewBrewItem = require('../../navbar/newbrew.navitem.jsx');
|
||||
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
||||
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
||||
|
||||
|
||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||
const Editor = require('../../editor/editor.jsx');
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
|
||||
const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
|
||||
|
||||
const HomePage = createClass({
|
||||
displayName : 'HomePage',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : {
|
||||
text : '',
|
||||
},
|
||||
ver : '0.0.0'
|
||||
brew : DEFAULT_BREW,
|
||||
ver : '0.0.0'
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
return {
|
||||
brew : this.props.brew,
|
||||
welcomeText : this.props.brew.text
|
||||
welcomeText : this.props.brew.text,
|
||||
error : undefined
|
||||
};
|
||||
},
|
||||
handleSave : function(){
|
||||
request.post('/api')
|
||||
.send(this.state.brew)
|
||||
.end((err, res)=>{
|
||||
if(err) return;
|
||||
if(err) {
|
||||
this.setState({ error: err });
|
||||
return;
|
||||
}
|
||||
const brew = res.body;
|
||||
window.location = `/edit/${brew.editId}`;
|
||||
});
|
||||
@@ -56,6 +59,10 @@ const HomePage = createClass({
|
||||
renderNavbar : function(){
|
||||
return <Navbar ver={this.props.ver}>
|
||||
<Nav.section>
|
||||
{this.state.error ?
|
||||
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
|
||||
null
|
||||
}
|
||||
<NewBrewItem />
|
||||
<HelpNavItem />
|
||||
<RecentNavItem />
|
||||
|
||||
@@ -40,4 +40,11 @@
|
||||
right : 350px;
|
||||
}
|
||||
}
|
||||
|
||||
.navItem.save{
|
||||
background-color: @orange;
|
||||
&:hover{
|
||||
background-color: @green;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ After clicking the "Print" item in the navbar a new page will open and a print d
|
||||
If you want to save ink or have a monochrome printer, add the **PRINT → {{fas,fa-tint}} Ink Friendly** snippet to your brew!
|
||||
}}
|
||||
|
||||
 {position:absolute,bottom:20px,left:130px,width:220px}
|
||||
 {position:absolute,bottom:20px,left:130px,width:220px}
|
||||
|
||||
{{artist,bottom:160px,left:100px
|
||||
##### Homebrew Mug
|
||||
|
||||
@@ -3,13 +3,14 @@ require('./newPage.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const request = require('superagent');
|
||||
const request = require('../../utils/request-middleware.js');
|
||||
|
||||
const Markdown = require('naturalcrit/markdown.js');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
||||
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||
|
||||
@@ -17,6 +18,8 @@ const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||
const Editor = require('../../editor/editor.jsx');
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
|
||||
|
||||
const BREWKEY = 'homebrewery-new';
|
||||
const STYLEKEY = 'homebrewery-new-style';
|
||||
const METAKEY = 'homebrewery-new-meta';
|
||||
@@ -26,38 +29,18 @@ const NewPage = createClass({
|
||||
displayName : 'NewPage',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : {
|
||||
text : '',
|
||||
style : undefined,
|
||||
title : '',
|
||||
description : '',
|
||||
renderer : 'V3',
|
||||
theme : '5ePHB',
|
||||
lang : 'en'
|
||||
}
|
||||
brew : DEFAULT_BREW
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState : function() {
|
||||
let brew = this.props.brew;
|
||||
|
||||
if(this.props.brew.shareId) {
|
||||
brew = {
|
||||
text : brew.text ?? '',
|
||||
style : brew.style ?? undefined,
|
||||
title : brew.title ?? '',
|
||||
description : brew.description ?? '',
|
||||
renderer : brew.renderer ?? 'legacy',
|
||||
theme : brew.theme ?? '5ePHB',
|
||||
lang : brew.lang ?? 'en'
|
||||
};
|
||||
}
|
||||
const brew = this.props.brew;
|
||||
|
||||
return {
|
||||
brew : brew,
|
||||
isSaving : false,
|
||||
saveGoogle : (global.account && global.account.googleId ? true : false),
|
||||
errors : null,
|
||||
error : null,
|
||||
htmlErrors : Markdown.validate(brew.text)
|
||||
};
|
||||
},
|
||||
@@ -86,7 +69,8 @@ const NewPage = createClass({
|
||||
}
|
||||
|
||||
localStorage.setItem(BREWKEY, brew.text);
|
||||
localStorage.setItem(STYLEKEY, brew.style);
|
||||
if(brew.style)
|
||||
localStorage.setItem(STYLEKEY, brew.style);
|
||||
localStorage.setItem(METAKEY, JSON.stringify({ 'renderer': brew.renderer, 'theme': brew.theme, 'lang': brew.lang }));
|
||||
},
|
||||
componentWillUnmount : function() {
|
||||
@@ -143,14 +127,6 @@ const NewPage = createClass({
|
||||
;
|
||||
},
|
||||
|
||||
clearErrors : function(){
|
||||
this.setState({
|
||||
errors : null,
|
||||
isSaving : false
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
save : async function(){
|
||||
this.setState({
|
||||
isSaving : true
|
||||
@@ -173,7 +149,7 @@ const NewPage = createClass({
|
||||
.send(brew)
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
this.setState({ isSaving: false, errors: err });
|
||||
this.setState({ isSaving: false, error: err });
|
||||
});
|
||||
if(!res) return;
|
||||
|
||||
@@ -185,67 +161,6 @@ const NewPage = createClass({
|
||||
},
|
||||
|
||||
renderSaveButton : function(){
|
||||
if(this.state.errors){
|
||||
let errMsg = '';
|
||||
try {
|
||||
errMsg += `${this.state.errors.toString()}\n\n`;
|
||||
errMsg += `\`\`\`\n${this.state.errors.stack}\n`;
|
||||
errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``;
|
||||
console.log(errMsg);
|
||||
} catch (e){}
|
||||
|
||||
// if(this.state.errors.status == '401'){
|
||||
// return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
// Oops!
|
||||
// <div className='errorContainer' onClick={this.clearErrors}>
|
||||
// You must be signed in to a Google account
|
||||
// to save this to<br />Google Drive!<br />
|
||||
// <a target='_blank' rel='noopener noreferrer'
|
||||
// href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||
// <div className='confirm'>
|
||||
// Sign In
|
||||
// </div>
|
||||
// </a>
|
||||
// <div className='deny'>
|
||||
// Not Now
|
||||
// </div>
|
||||
// </div>
|
||||
// </Nav.item>;
|
||||
// }
|
||||
|
||||
if(this.state.errors.response.req.url.match(/^\/api.*Google.*$/m)){
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={this.clearErrors}>
|
||||
Looks like your Google credentials have
|
||||
expired! Visit our log in page to sign out
|
||||
and sign back in with Google,
|
||||
then try saving again!
|
||||
<a target='_blank' rel='noopener noreferrer'
|
||||
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||
<div className='confirm'>
|
||||
Sign In
|
||||
</div>
|
||||
</a>
|
||||
<div className='deny'>
|
||||
Not Now
|
||||
</div>
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
Looks like there was a problem saving. <br />
|
||||
Report the issue <a target='_blank' rel='noopener noreferrer'
|
||||
href={`https://github.com/naturalcrit/homebrewery/issues/new?body=${encodeURIComponent(errMsg)}`}>
|
||||
here
|
||||
</a>.
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
if(this.state.isSaving){
|
||||
return <Nav.item icon='fas fa-spinner fa-spin' className='save'>
|
||||
save...
|
||||
@@ -275,7 +190,10 @@ const NewPage = createClass({
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
{this.renderSaveButton()}
|
||||
{this.state.error ?
|
||||
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
|
||||
this.renderSaveButton()
|
||||
}
|
||||
{this.renderLocalPrintButton()}
|
||||
<HelpNavItem />
|
||||
<RecentNavItem />
|
||||
|
||||
@@ -4,79 +4,5 @@
|
||||
&:hover{
|
||||
background-color: @green;
|
||||
}
|
||||
&.error{
|
||||
position : relative;
|
||||
background-color : @red;
|
||||
}
|
||||
}
|
||||
.errorContainer{
|
||||
animation-name: glideDown;
|
||||
animation-duration: 0.4s;
|
||||
position : absolute;
|
||||
top : 100%;
|
||||
left : 50%;
|
||||
z-index : 100000;
|
||||
width : 140px;
|
||||
padding : 3px;
|
||||
color : white;
|
||||
background-color : #333;
|
||||
border : 3px solid #444;
|
||||
border-radius : 5px;
|
||||
transform : translate(-50% + 3px, 10px);
|
||||
text-align : center;
|
||||
font-size : 10px;
|
||||
font-weight : 800;
|
||||
text-transform : uppercase;
|
||||
a{
|
||||
color : @teal;
|
||||
}
|
||||
&:before {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #444;
|
||||
left: 53px;
|
||||
top: -23px;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #333;
|
||||
left: 53px;
|
||||
top: -19px;
|
||||
}
|
||||
.deny {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
border-left : 1px solid #666;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : red;
|
||||
}
|
||||
}
|
||||
.confirm {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
color : white;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : teal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,9 +29,10 @@ const PrintPage = createClass({
|
||||
getInitialState : function() {
|
||||
return {
|
||||
brew : {
|
||||
text : this.props.brew.text || '',
|
||||
style : this.props.brew.style || undefined,
|
||||
renderer : this.props.brew.renderer || 'legacy'
|
||||
text : this.props.brew.text || '',
|
||||
style : this.props.brew.style || undefined,
|
||||
renderer : this.props.brew.renderer || 'legacy',
|
||||
theme : this.props.brew.theme || '5ePHB'
|
||||
}
|
||||
};
|
||||
},
|
||||
@@ -48,7 +49,7 @@ const PrintPage = createClass({
|
||||
text : brewStorage,
|
||||
style : styleStorage,
|
||||
renderer : metaStorage?.renderer || 'legacy',
|
||||
theme : metaStorage?.theme || '5ePHB'
|
||||
theme : metaStorage?.theme || '5ePHB'
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -59,7 +60,8 @@ const PrintPage = createClass({
|
||||
|
||||
renderStyle : function() {
|
||||
if(!this.state.brew.style) return;
|
||||
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.state.brew.style} </style>` }} />;
|
||||
//return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>@layer styleTab {\n${this.state.brew.style}\n} </style>` }} />;
|
||||
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>\n${this.state.brew.style}\n</style>` }} />;
|
||||
},
|
||||
|
||||
renderPages : function(){
|
||||
|
||||
@@ -12,21 +12,13 @@ const Account = require('../../navbar/account.navitem.jsx');
|
||||
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
|
||||
|
||||
const SharePage = createClass({
|
||||
displayName : 'SharePage',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : {
|
||||
title : '',
|
||||
text : '',
|
||||
style : '',
|
||||
shareId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
views : 0,
|
||||
renderer : ''
|
||||
}
|
||||
brew : DEFAULT_BREW_LOAD
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
|
||||
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
||||
|
||||
const UserPage = createClass({
|
||||
displayName : 'UserPage',
|
||||
@@ -19,7 +20,8 @@ const UserPage = createClass({
|
||||
return {
|
||||
username : '',
|
||||
brews : [],
|
||||
query : ''
|
||||
query : '',
|
||||
error : null
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
@@ -50,10 +52,19 @@ const UserPage = createClass({
|
||||
brewCollection : brewCollection
|
||||
};
|
||||
},
|
||||
errorReported : function(error) {
|
||||
this.setState({
|
||||
error
|
||||
});
|
||||
},
|
||||
|
||||
navItems : function() {
|
||||
return <Navbar>
|
||||
<Nav.section>
|
||||
{this.state.error ?
|
||||
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
|
||||
null
|
||||
}
|
||||
<NewBrew />
|
||||
<HelpNavItem />
|
||||
<RecentNavItem />
|
||||
@@ -63,7 +74,7 @@ const UserPage = createClass({
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <ListPage brewCollection={this.state.brewCollection} navItems={this.navItems()} query={this.props.query}></ListPage>;
|
||||
return <ListPage brewCollection={this.state.brewCollection} navItems={this.navItems()} query={this.props.query} reportError={this.errorReported}></ListPage>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
12
client/homebrew/utils/request-middleware.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const request = require('superagent');
|
||||
|
||||
const addHeader = (request)=>request.set('Homebrewery-Version', global.version);
|
||||
|
||||
const requestMiddleware = {
|
||||
get : (path)=>addHeader(request.get(path)),
|
||||
put : (path)=>addHeader(request.put(path)),
|
||||
post : (path)=>addHeader(request.post(path)),
|
||||
delete : (path)=>addHeader(request.delete(path)),
|
||||
};
|
||||
|
||||
module.exports = requestMiddleware;
|
||||
48
client/icons/book-front-cover.svg
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 541.53217 512"
|
||||
version="1.1"
|
||||
id="svg22127"
|
||||
sodipodi:docname="book-front-cover.svg"
|
||||
width="541.53217"
|
||||
height="512"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs22131" />
|
||||
<sodipodi:namedview
|
||||
id="namedview22129"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.39257813"
|
||||
inkscape:cx="-263.64179"
|
||||
inkscape:cy="444.49751"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg22127" />
|
||||
<!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<g
|
||||
id="g20308"
|
||||
transform="matrix(3.7795276,0,0,3.7795276,-201.76367,-251.58203)">
|
||||
<path
|
||||
id="rect20232"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:17.9;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill;stop-color:#000000"
|
||||
d="m 78.783305,66.564412 c -14.022889,0 -25.4,11.377111 -25.4,25.4 v 84.666668 c 0,14.02289 11.377111,25.4 25.4,25.4 h 76.199995 8.46667 c 4.68312,0 8.46667,-3.78355 8.46667,-8.46667 0,-4.68311 -3.78355,-8.46666 -8.46667,-8.46666 v -16.93334 c 4.68312,0 8.46667,-3.78355 8.46667,-8.46666 v -1.9327 c -0.0322,-0.27545 -0.0652,-0.54693 -0.0946,-0.83923 -0.17511,-1.74441 -0.30542,-3.81626 -0.37672,-6.02909 -0.18285,-5.67612 -0.29322,-5.86808 -0.63459,-6.62698 -0.74838,-1.66366 -2.65792,-3.64941 -4.38681,-4.49844 -1.41973,-0.69716 -0.72585,-0.45434 -1.20923,-0.51934 -0.47548,-0.0639 -2.54581,-0.13856 -6.47454,-0.14056 -0.0907,2.9929 -0.0862,4.81682 -0.58601,7.244 -0.28023,1.36071 -0.97957,3.42078 -2.40812,5.10356 -1.42519,1.67884 -2.81498,2.35811 -3.28145,2.61896 -3.14428,1.76375 -5.09549,2.43427 -9.41597,1.33997 -2.05224,-0.5197 -2.32631,-0.92288 -2.76159,-1.19527 -0.43528,-0.27239 -0.71007,-0.47684 -0.97461,-0.67593 -0.52909,-0.39816 -0.97871,-0.77171 -1.48622,-1.20664 -1.015,-0.86987 -2.20927,-1.95397 -3.6096,-3.26182 -2.80065,-2.61568 -6.38094,-6.09226 -10.18335,-9.90844 -6.19117,-6.21357 -9.5466,-9.59164 -11.7874,-12.16412 -1.1204,-1.28623 -2.03413,-2.38181 -2.90576,-4.03127 -0.87162,-1.64948 -1.40664,-4.21493 -1.40664,-5.61103 0,-1.4012 0.54783,-3.99366 1.42989,-5.64668 0.88206,-1.65304 1.8039,-2.74855 2.94142,-4.04679 2.27504,-2.59646 5.70131,-6.03358 12.03699,-12.369267 7.37691,-7.376888 10.87768,-11.090687 14.75208,-13.810527 1.45289,-1.019939 3.46378,-2.249133 6.08386,-2.580204 0.87337,-0.110323 1.8133,-0.120299 2.82412,0.0098 4.0433,0.520471 6.12413,2.832857 7.01973,3.728454 1.29782,1.297845 3.1373,4.826955 3.46852,7.049182 0.29817,2.00025 0.26393,3.770666 0.25993,6.212541 0.57954,0.0034 0.50388,0.0217 1.17564,0.0217 4.54211,0 8.44363,0.111537 11.991,0.50953 v -21.41004 c 0,-4.683115 -3.78355,-8.466667 -8.46667,-8.466667 h -8.46667 z m 0,101.599998 h 67.733335 v 16.93334 H 78.783305 c -4.683115,0 -8.466667,-3.78357 -8.466667,-8.46667 0,-4.68313 3.783552,-8.46667 8.466667,-8.46667 z" />
|
||||
<path
|
||||
style="color:#000000;fill:#000000;stroke-width:17.9;stroke-linejoin:round;-inkscape-stroke:none;paint-order:stroke markers fill"
|
||||
d="m 186.69094,157.95633 c 2.67243,-2.24871 7.17957,-9.39389 8.63888,-13.69528 1.03796,-3.05942 1.31928,-5.13546 1.33362,-9.84167 0.0278,-9.1246 -2.25302,-14.5915 -8.79325,-21.07662 -6.8535,-6.79576 -12.35348,-8.46107 -27.94423,-8.46107 -8.05417,0 -9.45684,-0.12924 -9.75203,-0.89852 -0.18964,-0.49417 -0.34479,-3.81715 -0.34479,-7.384389 0,-5.728497 -0.13266,-6.618534 -1.13607,-7.621956 -2.57777,-2.57775 -3.29907,-2.07141 -18.02212,12.651595 -12.64444,12.64444 -13.78771,13.94921 -13.78771,15.73575 0,1.78396 1.13629,3.08846 13.49078,15.48766 7.47518,7.50224 14.10644,13.69554 14.8715,13.88928 0.78576,0.19902 2.0096,-0.002 2.84016,-0.46789 1.42969,-0.80092 1.46523,-0.97351 1.74346,-8.46583 l 0.28402,-7.64825 h 8.52049 c 8.16738,0 8.65373,0.0655 11.73586,1.579 3.72428,1.82893 6.9202,5.12058 8.60236,8.86006 0.94352,2.09748 1.22898,4.1112 1.41901,10.01012 0.13083,4.06143 0.49647,7.70394 0.81253,8.09446 0.94895,1.17251 3.64241,0.80611 5.48753,-0.74645 z"
|
||||
id="path20297" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
39
client/icons/customIcons.less
Normal file
@@ -0,0 +1,39 @@
|
||||
.fac {
|
||||
display : inline-block;
|
||||
}
|
||||
.position-top-left {
|
||||
content: url('../icons/position-top-left.svg');
|
||||
}
|
||||
.position-top-right {
|
||||
content: url('../icons/position-top-right.svg');
|
||||
}
|
||||
.position-bottom-left {
|
||||
content: url('../icons/position-bottom-left.svg');
|
||||
}
|
||||
.position-bottom-right {
|
||||
content: url('../icons/position-bottom-right.svg');
|
||||
}
|
||||
.position-top {
|
||||
content: url('../icons/position-top.svg');
|
||||
}
|
||||
.position-right {
|
||||
content: url('../icons/position-right.svg');
|
||||
}
|
||||
.position-bottom {
|
||||
content: url('../icons/position-bottom.svg');
|
||||
}
|
||||
.position-left {
|
||||
content: url('../icons/position-left.svg');
|
||||
}
|
||||
.mask-edge {
|
||||
content: url('../icons/mask-edge.svg');
|
||||
}
|
||||
.mask-corner {
|
||||
content: url('../icons/mask-corner.svg');
|
||||
}
|
||||
.fa-file-c {
|
||||
content: url('../icons/fa-file-c.svg');
|
||||
}
|
||||
.book-front-cover {
|
||||
content: url('../icons/book-front-cover.svg');
|
||||
}
|
||||
30
client/icons/fa-file-c.svg
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 610.4 816.5" style="enable-background:new 0 0 610.4 816.5;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#FFFFFF;stroke:#FFFFFF;stroke-width:20;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<title>fa-file-c</title>
|
||||
<g id="Layer_2_1_">
|
||||
<g id="Layer_1-2">
|
||||
<g id="page">
|
||||
<path id="page-2" d="M610.3,468.3c0,77.3,0.2,154.5,0,231.8s-39.8,116.5-116.8,116.4c-127.6,0-255.1,0-382.7,0
|
||||
c-68.1,0-110.5-41.7-110.6-109.8c-0.2-197.7-0.2-395.5,0-593.2c0-68.4,43.2-110.9,112.1-111c90-0.1,180,0.2,270-0.2
|
||||
c12.8,0,21.5,0.6,32.9,4c17.1,5,152.7,150.7,190.7,188.8c-0.7,18-6,5.7,1.4,35.1c0,6.8,3.1,11.2,3.1,18.1
|
||||
C610.2,320.8,610.3,395.7,610.3,468.3z"/>
|
||||
<path id="white_corner" class="st0" d="M364.1,0v200c0,9.3,1.7,25.6,13.1,36.8c12,11.7,28.8,12.1,37.5,12.2
|
||||
c119.8,1.3,195.6,0.4,195.6,0.4l0,0l-0.3-54.3l-197,1l3-192L364.1,0z"/>
|
||||
</g>
|
||||
<path class="st1" d="M317.7,719.8c-38.3,0-71-8.1-98.3-24.3c-27.2-16.2-48.1-39.2-62.7-69C142.3,596.8,135,561.2,135,520
|
||||
c0-30.9,4.1-58.6,12.4-83.1c8.3-24.5,20.2-45.3,35.9-62.4c15.6-17.1,34.9-30.4,57.7-39.8s48.4-14.1,76.7-14.1
|
||||
c22.1-0.1,44,3.1,65.1,9.7c20.6,6.4,38.4,15.9,53.5,28.4c4.8,3.7,8,7.8,9.7,12.4c1.6,4.2,1.8,8.9,0.6,13.2
|
||||
c-1.2,4.1-3.5,7.7-6.6,10.5c-3.1,2.8-7.2,4.2-11.3,4.1c-4.4,0-9.4-1.8-14.9-5.5c-13-10.5-27.7-18.6-43.6-23.7
|
||||
c-16.6-5.3-33.9-7.9-51.3-7.7c-29.1,0-53.7,6.2-74,18.5s-35.5,30.3-45.8,53.8c-10.3,23.6-15.4,52.1-15.4,85.5s5.1,62.1,15.4,85.9
|
||||
c10.3,23.7,25.6,41.8,45.8,54.1c20.2,12.3,44.9,18.5,74,18.5c17.4,0.1,34.8-2.6,51.3-8c16.2-5.3,31.3-13.5,44.7-24
|
||||
c5.5-3.7,10.5-5.4,14.9-5.3c4,0.1,7.9,1.5,11,4.1c3,2.7,5.2,6.1,6.4,9.9c1.3,4.1,1.3,8.6,0,12.7c-1.3,4.4-4.1,8.3-8.6,11.6
|
||||
c-15.5,13.3-33.6,23.3-54.4,30.1C362.7,716.6,340.3,720,317.7,719.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
63
client/icons/mask-corner.svg
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="mask-corner.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139"><pattern
|
||||
inkscape:collect="always"
|
||||
xlink:href="#Strips1_1"
|
||||
id="pattern3077"
|
||||
patternTransform="matrix(23.131931,-23.131931,19.25517,19.25517,26.214281,-26.952711)" /><pattern
|
||||
inkscape:collect="always"
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="2"
|
||||
height="1"
|
||||
patternTransform="translate(0,0) scale(10,10)"
|
||||
id="Strips1_1"
|
||||
inkscape:stockid="Stripes 1:1"><rect
|
||||
style="fill:black;stroke:none"
|
||||
x="0"
|
||||
y="-0.5"
|
||||
width="1"
|
||||
height="2"
|
||||
id="rect2097" /></pattern></defs><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.95758074"
|
||||
inkscape:cx="275.17262"
|
||||
inkscape:cy="306.50157"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="M 48,-5.2e-6 C 21.40803,-5.2e-6 1.98e-5,21.408025 1.98e-5,47.999995 V 464 C 1.98e-5,490.59197 21.40803,512 48,512 h 352 c 26.59198,0 48,-21.40803 48,-48 V 47.999995 C 448,21.408025 426.59198,-5.2e-6 400,-5.2e-6 Z M 64,63.999995 H 384 V 448 H 64 Z" /><rect
|
||||
style="fill:url(#pattern3077);fill-opacity:1;stroke:#000000;stroke-width:48;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206"
|
||||
width="208"
|
||||
height="240"
|
||||
x="32.000011"
|
||||
y="32.000011"
|
||||
rx="48"
|
||||
ry="48" /></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
69
client/icons/mask-edge.svg
Normal file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="mask-edge.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139"><pattern
|
||||
inkscape:collect="always"
|
||||
xlink:href="#Strips1_1"
|
||||
id="pattern3077"
|
||||
patternTransform="matrix(23.131931,-23.13193,19.25517,19.25517,26.214281,-26.952711)" /><pattern
|
||||
inkscape:collect="always"
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="2"
|
||||
height="1"
|
||||
patternTransform="translate(0,0) scale(10,10)"
|
||||
id="Strips1_1"
|
||||
inkscape:stockid="Stripes 1:1"><rect
|
||||
style="fill:black;stroke:none"
|
||||
x="0"
|
||||
y="-0.5"
|
||||
width="1"
|
||||
height="2"
|
||||
id="rect2097" /></pattern></defs><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.95758074"
|
||||
inkscape:cx="231.31209"
|
||||
inkscape:cy="171.78708"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="M 48,-5.2e-6 C 21.40803,-5.2e-6 1.98e-5,21.408025 1.98e-5,47.999995 V 464 C 1.98e-5,490.59197 21.40803,512 48,512 h 352 c 26.59198,0 48,-21.40803 48,-48 V 47.999995 C 448,21.408025 426.59198,-5.2e-6 400,-5.2e-6 Z M 64,63.999995 H 384 V 448 H 64 Z" /><rect
|
||||
style="fill:url(#pattern3077);fill-opacity:1;stroke:#000000;stroke-width:48;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206"
|
||||
width="208"
|
||||
height="447.99997"
|
||||
x="32.000011"
|
||||
y="32.000011"
|
||||
rx="48"
|
||||
ry="48" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:47.9999;stroke-linejoin:round;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect4640"
|
||||
width="48"
|
||||
height="512"
|
||||
x="216"
|
||||
y="0" /></svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
46
client/icons/position-bottom-left.svg
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-bottom-left.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.70792086"
|
||||
inkscape:cx="174.45453"
|
||||
inkscape:cy="325.60137"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="m 48,511.99998 c -26.59197,0 -48.00000035682677,-21.40803 -48.00000035682677,-48 v -416 C -3.5682677e-7,21.40801 21.40803,-1.9692461e-5 48,-1.9692461e-5 h 352 c 26.59198,0 48,21.408029692461 48,47.999999692461 v 416 c 0,26.59197 -21.40802,48 -48,48 z m 16,-64 h 320 v -384 H 64 Z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206"
|
||||
width="208"
|
||||
height="240"
|
||||
x="-3.5682677e-07"
|
||||
y="-512"
|
||||
rx="48"
|
||||
ry="48"
|
||||
transform="scale(1,-1)" /></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
46
client/icons/position-bottom-right.svg
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-bottom-right.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.70792086"
|
||||
inkscape:cx="174.45453"
|
||||
inkscape:cy="325.60137"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="m 400,511.99998 c 26.59197,0 48,-21.40803 48,-48 v -416 C 448,21.40801 426.59197,-1.9692461e-5 400,-1.9692461e-5 H 48 C 21.40802,-1.9692461e-5 -3.5682677e-7,21.40801 -3.5682677e-7,47.99998 v 416 c 0,26.59197 21.40802035682677,48 48.00000035682677,48 z m -16,-64 H 64 v -384 h 320 z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206"
|
||||
width="208"
|
||||
height="240"
|
||||
x="-448"
|
||||
y="-512"
|
||||
rx="48"
|
||||
ry="48"
|
||||
transform="scale(-1)" /></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
46
client/icons/position-bottom.svg
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-bottom.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="1.0011513"
|
||||
inkscape:cx="273.18549"
|
||||
inkscape:cy="216.25103"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201-2"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="m 48,512.00004 c -26.5919,0 -48,-21.4081 -48,-48 V 47.999996 C 0,21.408026 21.4081,-3.8146973e-6 48,-3.8146973e-6 h 352 c 26.592,0 48,21.4080298146973 48,47.9999998146973 V 464.00004 c 0,26.5919 -21.408,48 -48,48 z m 16,-64 H 384 V 63.999996 H 64 Z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30.0001;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206-8"
|
||||
width="447.99997"
|
||||
height="240"
|
||||
x="1.40625e-05"
|
||||
y="-512.00006"
|
||||
rx="48"
|
||||
ry="48"
|
||||
transform="scale(1,-1)" /></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
45
client/icons/position-left.svg
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-left.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.70792086"
|
||||
inkscape:cx="164.56642"
|
||||
inkscape:cy="243.6713"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201-0"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="M 48,0 C 21.4081,0 0,21.40803 0,48 v 416 c 0,26.59197 21.4081,48 48,48 h 352.0001 c 26.5919,0 48,-21.40803 48,-48 V 48 c 0,-26.59197 -21.4081,-48 -48,-48 z M 64,64 H 384.0001 V 448 H 64 Z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206-2"
|
||||
width="208"
|
||||
height="512.00006"
|
||||
x="7.0762391e-05"
|
||||
y="-8.8710935e-05"
|
||||
rx="48"
|
||||
ry="48.000004" /></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
46
client/icons/position-right.svg
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-right.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.70792086"
|
||||
inkscape:cx="164.56642"
|
||||
inkscape:cy="243.6713"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201-0"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="m 400.0001,0 c 26.5919,0 48,21.40803 48,48 v 416 c 0,26.59197 -21.4081,48 -48,48 H 48 C 21.4081,512 0,490.59197 0,464 V 48 C 0,21.40803 21.4081,0 48,0 Z m -16,64 H 64 v 384 h 320.0001 z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206-2"
|
||||
width="208"
|
||||
height="512.00006"
|
||||
x="-448.00003"
|
||||
y="-8.8710935e-05"
|
||||
rx="48"
|
||||
ry="48.000004"
|
||||
transform="scale(-1,1)" /></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
45
client/icons/position-top-left.svg
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-top-left.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.70792086"
|
||||
inkscape:cx="174.45453"
|
||||
inkscape:cy="325.60137"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="M 48,0 C 21.40803,0 0,21.40803 0,48 v 416 c 0,26.59197 21.40803,48 48,48 h 352 c 26.59198,0 48,-21.40803 48,-48 V 48 C 448,21.40803 426.59198,0 400,0 Z M 64,64 H 384 V 448 H 64 Z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206"
|
||||
width="208"
|
||||
height="240"
|
||||
x="-3.5682677e-07"
|
||||
y="-1.9692461e-05"
|
||||
rx="48"
|
||||
ry="48" /></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
46
client/icons/position-top-right.svg
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-top-right.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.70792086"
|
||||
inkscape:cx="174.45453"
|
||||
inkscape:cy="325.60137"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="m 400,0 c 26.59197,0 48,21.40803 48,48 v 416 c 0,26.59197 -21.40803,48 -48,48 H 48 C 21.40802,512 -3.5682677e-7,490.59197 -3.5682677e-7,464 V 48 C -3.5682677e-7,21.40803 21.40802,0 48,0 Z M 384,64 H 64 v 384 h 320 z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206"
|
||||
width="208"
|
||||
height="240"
|
||||
x="-448"
|
||||
y="-1.9692461e-05"
|
||||
rx="48"
|
||||
ry="48"
|
||||
transform="scale(-1,1)" /></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
45
client/icons/position-top.svg
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-top.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="1.0011513"
|
||||
inkscape:cx="273.18549"
|
||||
inkscape:cy="216.25103"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201-2"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="M 48,0 C 21.4081,0 0,21.4081 0,48 v 416.00004 c 0,26.59197 21.4081,48 48,48 h 352 c 26.592,0 48,-21.40803 48,-48 V 48 C 448,21.4081 426.592,0 400,0 Z M 64,64 H 384 V 448.00004 H 64 Z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30.0001;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206-8"
|
||||
width="447.99997"
|
||||
height="240"
|
||||
x="1.40625e-05"
|
||||
y="-3.8146973e-06"
|
||||
rx="48"
|
||||
ry="48" /></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |