diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 87d8133da..797a452fb 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -9,6 +9,7 @@ import MetadataEditor from './metadataEditor/metadataEditor.jsx'; import SettingsEditor from './settingsEditor/settingsEditor.jsx'; const EDITOR_THEME_KEY = 'HB_editor_theme'; +const EDITOR_SETTINGS_KEY = 'HB_edit_settings'; import defaultCM5Theme from '@themes/codeMirror/default.js'; import darkbrewery from '@themes/codeMirror/darkbrewery.js'; @@ -73,6 +74,12 @@ const Editor = forwardRef( const [currentEditorTheme, setEditorTheme] = useState(editorTheme); const [view, setView] = useState('text'); // 'text', 'style', 'meta', 'snippet' const [snippetBarHeight, setSnippetBarHeight] = useState(26); + const [editorSettings, setEditorSettings] = useState({ + blockShading: false, + activeLineShading: true, + lineNumbers: true, + fontSize: 14 + }) const editor = useRef(null); const codeEditor = useRef(null); @@ -82,6 +89,7 @@ const Editor = forwardRef( const isStyle = ()=>isView('style'); const isMeta = ()=>isView('meta'); const isSnip = ()=>isView('snippet'); + const isSettings = ()=>isView('settings'); const isView = (name)=>view === name; @@ -92,6 +100,8 @@ const Editor = forwardRef( const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY); if(editorTheme && EditorThemes.includes(editorTheme)) setEditorTheme(editorTheme); else setEditorTheme('default'); + const localEditorSettings = window.localStorage.getItem(EDITOR_SETTINGS_KEY); + if (localEditorSettings) setEditorSettings(JSON.parse(localEditorSettings)); const snippetBar = document.querySelector('.editor > .snippetBar'); if(!snippetBar) return; @@ -210,6 +220,11 @@ const Editor = forwardRef( window.localStorage.setItem(EDITOR_THEME_KEY, newTheme); setEditorTheme(newTheme); }; + + const updateEditorSettings = (newEditorSettings) => { + window.localStorage.setItem(EDITOR_SETTINGS_KEY, JSON.stringify(newEditorSettings)); + setEditorSettings(newEditorSettings); + } const renderEditor = ()=>{ if(isText()) { @@ -228,6 +243,7 @@ const Editor = forwardRef( editorTheme={currentEditorTheme} renderer={brew.renderer} style={{ height: `calc(100% - ${snippetBarHeight}px)` }} + settings={editorSettings} /> ); @@ -246,6 +262,7 @@ const Editor = forwardRef( editorTheme={currentEditorTheme} renderer={brew.renderer} style={{ height: `calc(100% - ${snippetBarHeight}px)` }} + settings={editorSettings} /> ); @@ -268,6 +285,7 @@ const Editor = forwardRef( editorTheme={currentEditorTheme} renderer={brew.renderer} style={{ height: `calc(100% - 25px)` }} + settings={editorSettings} /> ); @@ -275,7 +293,7 @@ const Editor = forwardRef( if(isMeta()) { return ( <> - + ); } + if(isSettings()){ + return ( + <> + + + + ); + } }; const redo = ()=>codeEditor.current?.redo(); @@ -305,7 +340,6 @@ const Editor = forwardRef( unfoldCode, historySize, })); - return (
{ - const [settings, setSettings] = useState({ - blockShading: false, - activeLineShading: true, - showLineNumbers: true, - }); +const SettingsEditor = ({settings, updateSettings = () => {} }) => { + const [currentSettings, setCurrentSettings] = useState(settings); + + const validations = { + fontSize: [ + (value) => { + const number = Number(value); + + if (number < 9 || number > 30) { + return 'Font size must be between 9 and 30.'; + } + + return null; + }, + ], + }; const handleFieldChange = (setting, e) => { - setSettings((prevSettings) => ({ - ...prevSettings, - [setting]: e.target.checked, - })); + const value = + e.target.type === 'checkbox' + ? e.target.checked + : e.target.value; + + const inputRules = validations[setting] ?? []; + + const validationErrors = inputRules + .map((rule) => rule(value)) + .filter(Boolean); + + if (validationErrors.length > 0) { + e.target.setCustomValidity(validationErrors.join('\n')); + e.target.reportValidity(); + return; + } + + e.target.setCustomValidity(''); + + const updatedSettings = { + ...currentSettings, + [setting]: e.target.type === 'number' + ? Number(value) + : value, + }; + + setCurrentSettings(updatedSettings); + updateSettings(updatedSettings); }; return ( -
+

Editor Settings

-
); }; -export default SettingsEditor; +export default SettingsEditor; \ No newline at end of file diff --git a/client/homebrew/editor/settingsEditor/settingsEditor.less b/client/homebrew/editor/settingsEditor/settingsEditor.less new file mode 100644 index 000000000..21e8e1af6 --- /dev/null +++ b/client/homebrew/editor/settingsEditor/settingsEditor.less @@ -0,0 +1,373 @@ +@import '@sharedStyles/core.less'; + + +.settingsEditor { + position : absolute; + box-sizing : border-box; + width : 100%; + height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this. + padding : 25px; + overflow-y : auto; + font-size : 13px; + background-color : #999999; + + h1 { + margin : 0 0 40px; + font-weight : bold; + text-transform : uppercase; + } + + h2 { + margin : 20px 0; + font-weight : bold; + color : #555555; + border-bottom : 2px solid gray; + } + + & > div { margin-bottom : 10px; } + + .field-group { + display : flex; + flex-wrap : wrap; + gap : 10px; + width : 100%; + } + + .field-column { + display : flex; + flex : 5 0 200px; + flex-direction : column; + gap : 10px; + } + + .field { + position : relative; + display : flex; + justify-content: space-between; + flex-wrap : wrap; + width : 100%; + min-width : 200px; + border-bottom:1px solid; + padding:5px; + & > label { + width : fit-content; + font-size : 0.9em; + font-weight : 800; + line-height : 1.8em; + text-transform : uppercase; + } + & > .value { + flex : 1 1 auto; + width : 50px; + &[data-tooltip-right] { max-width : 380px; } + &:invalid { background : #FFB9B9; } + small { + display : block; + font-size : 0.9em; + font-style : italic; + line-height : 1.4em; + } + } + input[type='text'], textarea { + border : 1px solid gray; + &:focus { outline : 1px solid #444444; } + } + + &.description { + flex : 1; + textarea.value { + height : auto; + font-family : 'Open Sans', sans-serif; + resize : none; + } + } + + &.thumbnail, &.themes { + label { line-height : 2.0em; } + .value { + overflow : hidden; + text-overflow : ellipsis; + } + button { + .colorButton(); + padding : 0px 5px; + color : white; + background-color : black; + border : 1px solid #999999; + &:hover { background-color : #777777; } + } + } + + &.tags .tagInput-dropdown { + z-index : 400; + max-width : 200px; + } + &.language .value { + z-index : 300; + max-width : 150px; + } + + &.themes { + .value { + overflow : visible; + text-overflow : auto; + } + button { + padding-right : 5px; + padding-left : 5px; + } + } + + &.invitedAuthors .value { + z-index : 100; + + .tagInput-dropdown { max-width : 200px; } + } + } + + .thumbnail-preview { + position : relative; + flex : 1 1; + justify-self : center; + width : 80px; + height : min-content; + max-height : 115px; + aspect-ratio : 1 / 1; + object-fit : contain; + background-color : #AAAAAA; + } + + .renderers.field .value { + label { + display : inline-flex; + align-items : center; + margin-right : 15px; + font-size : 0.9em; + font-weight : 800; + vertical-align : middle; + white-space : nowrap; + cursor : pointer; + user-select : none; + } + input { + margin : 3px; + vertical-align : middle; + cursor : pointer; + } + } + .publish.field .value { + position : relative; + margin-bottom : 15px; + button { width : 100%; } + button.publish { + .colorButton(@blueLight); + } + button.unpublish { + .colorButton(@silver); + } + } + + .delete.field .value { + button { + .colorButton(@red); + } + } + .authors.field { + .tag { + font-weight:300; + transition:background-color 0.2s; + + &.owner { + position: relative; + background-color:@silverLight; + min-width:25px; + display:grid; + place-items:center; + font-weight: 900; + + &::after { + content: "\f521"; + font-family: "Font Awesome 6 Free"; + color:gold; + position: absolute; + top: 0; + left: 0; + width:15px; + height:15px; + rotate:-25deg; + translate:-30% -50%; + transform: scaleY(0.7); + } + } + &:has(button) a { + padding-right:5px; + } + &:has(button:hover) { + background:#d97d7d; + } + + button { + color:@red; + } + + + } + a { + color:black; + text-decoration:unset; + } + } + + .themes.field { + & .dropdown-container { + position : relative; + z-index : 200; + background-color : white; + } + & .dropdown-options { overflow-y : visible; } + .disabled { + font-style : italic; + color : dimgray; + background-color : darkgray; + } + .item { + position : relative; + padding : 3px 3px; + overflow : visible; + background-color : white; + border-top : 1px solid rgb(118, 118, 118); + .preview { + position : absolute; + top : 0; + right : 0; + z-index : 1; + display : flex; + flex-direction : column; + width : 200px; + overflow : hidden; + color : black; + background : #CCCCCC; + border-radius : 5px; + box-shadow : 0 0 5px black; + opacity : 0; + transition : opacity 250ms ease; + h6 { + padding-block : 0.5em; + padding-inline : 1em; + font-weight : 900; + border-bottom : 2px solid hsl(0,0%,40%); + } + } + + .texture-container { + position : absolute; + top : 0; + left : 0; + width : 100%; + height : 100%; + min-height : 100%; + overflow : hidden; + > img { + position : absolute; + top : 0; + right : 0; + width : 50%; + min-height : 100%; + -webkit-mask-image : linear-gradient(90deg, transparent, black 20%); + mask-image : linear-gradient(90deg, transparent, black 20%); + } + } + + &:hover { + color : white; + background-color : @blue; + filter : unset; + } + &:hover > .preview { opacity : 1; } + } + } + + .field .list { + display : flex; + flex : 1 0; + flex-wrap : wrap; + + > * { flex : 0 0 auto; } + + #groupedIcon { + #backgroundColors; + position : relative; + top : -0.3em; + right : -0.3em; + display : inline-block; + min-width : 20px; + height : ~'calc(100% + 0.6em)'; + color : white; + text-align : center; + cursor : pointer; + + i { + position : relative; + top : 50%; + transform : translateY(-50%); + } + + &:not(:last-child) { border-right : 1px solid black; } + + &:last-child { border-radius : 0 0.5em 0.5em 0; } + } + + .tag { + padding : 0.35em; + margin : 2px; + font-size : 0.95em; + background-color : #DDDDDD; + border-radius : 0.5em; + + .icon { #groupedIcon; } + + button { + cursor : pointer; + } + } + + .input-group { + height : ~'calc(.9em + 4px + .6em)'; + + input { border-radius : 0.5em 0 0 0.5em; } + + input:last-child { border-radius : 0.5em; } + + .value { + width : 7.5vw; + min-width : 75px; + height : 100%; + } + + .input-group { + height : ~'calc(.9em + 4px + .6em)'; + + input { border-radius : 0.5em 0 0 0.5em; } + + input:last-child { border-radius : 0.5em; } + + .value { + width : 7.5vw; + min-width : 75px; + height : 100%; + } + + .invalid:focus { background-color : pink; } + + .icon { + #groupedIcon; + top : -0.54em; + right : 1px; + height : 97%; + + i { font-size : 1.125em; } + } + } + } + } +} \ No newline at end of file diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index 96edbd49c..ec4a583e5 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -245,7 +245,7 @@ const Snippetbar = createReactClass({ return (
- {this.props.view !== 'meta' && <>
+ {this.props.view !== 'meta' && this.props.view !== 'settings' && <>
@@ -293,6 +293,10 @@ const Snippetbar = createReactClass({ onClick={()=>this.props.onViewChange('meta')}>
+
this.props.onViewChange('settings')}> + +
diff --git a/client/homebrew/editor/snippetbar/snippetbar.less b/client/homebrew/editor/snippetbar/snippetbar.less index e49e02044..879693c2b 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.less +++ b/client/homebrew/editor/snippetbar/snippetbar.less @@ -21,7 +21,7 @@ .editors { display : flex; justify-content : flex-end; - min-width : 250px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied + min-width : 275px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied font-size: .85rem; &:only-child {min-width : unset; margin-left : auto;} @@ -208,7 +208,7 @@ } } } -@container editor (width < 816px) { +@container editor (width < 841px) { .snippetBar { .editors { flex : 1;