Add option to wrap text (#20)
This commit is contained in:
@@ -40,6 +40,12 @@ export default function Editor({
|
||||
pref => pref >= 10 && pref <= 22
|
||||
);
|
||||
|
||||
const [wordWrap, setWordWrap] = usePreference<boolean>(
|
||||
'wordwrap',
|
||||
true,
|
||||
() => true
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (contentType) {
|
||||
setLanguage(contentType);
|
||||
@@ -66,6 +72,8 @@ export default function Editor({
|
||||
setReadOnly={setReadOnly}
|
||||
theme={theme}
|
||||
setTheme={setTheme}
|
||||
wordWrap={wordWrap}
|
||||
setWordWrap={setWordWrap}
|
||||
zoom={zoom}
|
||||
/>
|
||||
<EditorTextArea
|
||||
@@ -76,6 +84,7 @@ export default function Editor({
|
||||
language={language}
|
||||
fontSize={fontSize}
|
||||
readOnly={readOnly}
|
||||
wordWrap={wordWrap}
|
||||
resetFunction={resetFunction}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
|
||||
@@ -19,6 +19,8 @@ export interface EditorControlsProps {
|
||||
setReadOnly: (value: boolean) => void;
|
||||
theme: keyof Themes;
|
||||
setTheme: (value: keyof Themes) => void;
|
||||
wordWrap: boolean;
|
||||
setWordWrap: (value: boolean) => void;
|
||||
zoom: (delta: number) => void;
|
||||
}
|
||||
|
||||
@@ -31,6 +33,8 @@ export default function EditorControls({
|
||||
setReadOnly,
|
||||
theme,
|
||||
setTheme,
|
||||
wordWrap,
|
||||
setWordWrap,
|
||||
zoom,
|
||||
}: EditorControlsProps) {
|
||||
const [saving, setSaving] = useState<boolean>(false);
|
||||
@@ -113,6 +117,9 @@ export default function EditorControls({
|
||||
<Section>
|
||||
<Button onClick={() => zoom(1)}>[+ </Button>
|
||||
<Button onClick={() => zoom(-1)}> -]</Button>
|
||||
<Button onClick={() => setWordWrap(!wordWrap)}>
|
||||
[wrap:{wordWrap ? 'on' : 'off'}]
|
||||
</Button>
|
||||
<MenuButton
|
||||
label="theme"
|
||||
value={theme}
|
||||
@@ -144,13 +151,15 @@ const Header = styled.header`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
user-select: none;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const Section = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: 470px) {
|
||||
@media (max-width: 850px) {
|
||||
.optional {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ export interface EditorTextAreaProps {
|
||||
language: string;
|
||||
fontSize: number;
|
||||
readOnly: boolean;
|
||||
wordWrap: boolean;
|
||||
resetFunction: MutableRefObject<ResetFunction | null>;
|
||||
}
|
||||
|
||||
@@ -73,6 +74,7 @@ export default function EditorTextArea({
|
||||
language,
|
||||
fontSize,
|
||||
readOnly,
|
||||
wordWrap,
|
||||
resetFunction,
|
||||
}: EditorTextAreaProps) {
|
||||
const [editor, setEditor] = useState<editor.IStandaloneCodeEditor>();
|
||||
@@ -166,7 +168,7 @@ export default function EditorTextArea({
|
||||
fontFamily: 'JetBrains Mono',
|
||||
fontSize: fontSize,
|
||||
fontLigatures: true,
|
||||
wordWrap: 'on',
|
||||
wordWrap: wordWrap ? 'on' : 'off',
|
||||
renderLineHighlight: 'none',
|
||||
renderValidationDecorations: 'off',
|
||||
readOnly,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import styled from 'styled-components';
|
||||
import Button from './Button';
|
||||
|
||||
@@ -16,6 +17,11 @@ export default function MenuButton<T extends string>({
|
||||
setValue,
|
||||
}: MenuButtonProps<T>) {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [menuPosition, setMenuPosition] = useState<{
|
||||
top: number;
|
||||
left: number;
|
||||
}>({ top: 0, left: 0 });
|
||||
const buttonRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// close the menu when a click is made elsewhere
|
||||
useEffect(() => {
|
||||
@@ -26,6 +32,16 @@ export default function MenuButton<T extends string>({
|
||||
|
||||
function toggleOpen(e: React.MouseEvent) {
|
||||
e.stopPropagation();
|
||||
if (!buttonRef.current) {
|
||||
return;
|
||||
}
|
||||
if (!open) {
|
||||
const rect = buttonRef.current.getBoundingClientRect();
|
||||
setMenuPosition({
|
||||
top: rect.bottom + window.scrollY,
|
||||
left: rect.left + window.scrollX,
|
||||
});
|
||||
}
|
||||
setOpen(!open);
|
||||
}
|
||||
|
||||
@@ -63,22 +79,35 @@ export default function MenuButton<T extends string>({
|
||||
}
|
||||
|
||||
return (
|
||||
<Button onClick={toggleOpen}>
|
||||
[<span>{label}: </span>
|
||||
{value}]{open && <Menu>{items}</Menu>}
|
||||
</Button>
|
||||
<>
|
||||
<Button onClick={toggleOpen} ref={buttonRef}>
|
||||
[<span>{label}: </span>
|
||||
{value}]
|
||||
</Button>
|
||||
{open &&
|
||||
createPortal(
|
||||
<Menu style={{ top: menuPosition.top, left: menuPosition.left }}>
|
||||
{items}
|
||||
</Menu>,
|
||||
document.body
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const Menu = styled.ul`
|
||||
position: absolute;
|
||||
top: 2em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
background-color: ${props => props.theme.highlight};
|
||||
max-height: calc(100vh - 2em);
|
||||
overflow: auto;
|
||||
z-index: 20;
|
||||
|
||||
> li {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
> li.title {
|
||||
text-align: center;
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function usePreference<T>(
|
||||
const [value, setValue] = useState<T>(() => {
|
||||
const prefRaw = localStorage.getItem(id);
|
||||
const pref = prefRaw !== null ? (JSON.parse(prefRaw) as T) : undefined;
|
||||
if (pref && valid(pref)) {
|
||||
if (pref !== null && pref !== undefined && valid(pref)) {
|
||||
return pref;
|
||||
} else {
|
||||
return defaultValue;
|
||||
|
||||
Reference in New Issue
Block a user