From ec9f0689d0ec0d5ed492b5a1ea8b29b2bce9d53a Mon Sep 17 00:00:00 2001 From: neCo <50079394+neCo2@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:55:40 +0200 Subject: [PATCH 01/25] added draggable ui element --- mikupad.html | 241 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 239 insertions(+), 2 deletions(-) diff --git a/mikupad.html b/mikupad.html index bdc7661..a63069b 100644 --- a/mikupad.html +++ b/mikupad.html @@ -344,6 +344,54 @@ font-size: 0.8rem; } +.widget-title { + -webkit-user-select: none; /* Safari */ + -ms-user-select: none; /* IE 10 and IE 11 */ + user-select: none; /* Standard syntax */ + padding:0.2em; + font-weight: bold; + font-size: 110%; + background-color: var(--color-base-40); + border-radius:2px; + width: calc(100% - 2em); +} + +#searchAndReplace { + position: absolute; + width: 70%; + background: var(--color-base-50); + top:.75em; + left:.75em; + border-radius:3px; + padding:0.25em; +} + +#searchAndReplace .widget-container { + position: relative; +} +#searchAndReplace .button-widget-top { + position: absolute; + top:0.1em; + right:0.1em; +} + +.searchAndReplace-inputs { + display: flex; + flex-direction: row; + gap: 8px; + padding: .2em; +} +.searchAndReplace-inputs .InputBox { + width: 100%; +} +.searchAndReplace-inputs .SelectBox { + width: 40%; +} + +/* .widget-body { + +} */ + .modal-overlay { position: fixed; top: 0; @@ -1975,8 +2023,6 @@ `; } - - function Modal({ isOpen, onClose, title, description, children, ...props }) { if (!isOpen) { return null; @@ -2014,6 +2060,168 @@ `; } +//https://stackoverflow.com/questions/20926551/recommended-way-of-making-react-component-div-draggable +function Draggable({ isOpen, onClose, title, id, children, ...props }) { + function getDefaultProps() { + return { + // allow the initial position to be passed in as a prop + initialPos: {x: 0, y: 0} + } + } + function getInitialState() { + return { + pos: this.props.initialPos, + dragging: false, + rel: null // position relative to the cursor + } + } + // we could get away with not having this (and just having the listeners on + // our div), but then the experience would be possibly be janky. If there's + // anything w/ a higher z-index that gets in the way, then you're toast, + // etc. + function componentDidUpdate(props, state) { + if (this.state.dragging && !state.dragging) { + document.addEventListener('mousemove', this.onMouseMove) + document.addEventListener('mouseup', this.onMouseUp) + } else if (!this.state.dragging && state.dragging) { + document.removeEventListener('mousemove', this.onMouseMove) + document.removeEventListener('mouseup', this.onMouseUp) + } + } + + // calculate relative position of the mouse and set dragging=true + function onMouseDown(e) { + // only left mouse button + if (e.button !== 0) return + var pos = $(this.getDOMNode()).offset() + this.setState({ + dragging: true, + rel: { + x: e.pageX - pos.left, + y: e.pageY - pos.top + } + }) + e.stopPropagation() + e.preventDefault() + } + function onMouseUp(e) { + this.setState({dragging: false}) + e.stopPropagation() + e.preventDefault() + } + function onMouseMove(e) { + if (!this.state.dragging) return + this.setState({ + pos: { + x: e.pageX - this.state.rel.x, + y: e.pageY - this.state.rel.y + } + }) + e.stopPropagation() + e.preventDefault() + } + function render() { + // transferPropsTo will merge style & other props passed into our + // component to also be on the child DIV. + return this.transferPropsTo(React.DOM.div({ + onMouseDown: this.onMouseDown, + style: { + left: this.state.pos.x + 'px', + top: this.state.pos.y + 'px' + } + }, this.props.children)) + } +} + + +function Widget({ isOpen, onClose, title, id, children, ...props }) { + if (!isOpen) { + return null; + } + + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [isDragging, setIsDragging] = useState(false); + const dragRef = useRef(null); + + useEffect(() => { + const handleMouseMove = (e) => { + if (!isDragging) return; + const deltaX = e.clientX - dragRef.current.startX; + const deltaY = e.clientY - dragRef.current.startY; + setPosition({ + x: dragRef.current.initialX + deltaX, + y: dragRef.current.initialY + deltaY, + }); + }; + + const handleMouseUp = () => { + setIsDragging(false); + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + }, [isDragging]); + + const handleMouseDown = (e) => { + setIsDragging(true); + dragRef.current = { + startX: e.clientX, + startY: e.clientY, + initialX: position.x, + initialY: position.y, + }; + }; + + useEffect(() => { + const onKeyDown = (event) => { + if (event.key === 'Escape') { + onClose(); + } + }; + document.addEventListener('keydown', onKeyDown); + return () => { + document.removeEventListener('keydown', onKeyDown); + }; + }, []); + + return html` +
+
+
{}} + onMouseUp=${() => {}} + onMouseLeave=${() => {}}> + ${title} +
+
+ ${children} +
+ +
+
`; +} +function SearchAndReplaceWidget({ isOpen, closeWidget, id, children, ...props }) { + return html` + <${Widget} isOpen=${isOpen} onClose=${closeWidget} + title="Search and Replace" + id="${id}"> + ${children} + `; +} + function EditorPreferencesModal({ isOpen, closeModal, children }) { return html` <${Modal} isOpen=${isOpen} onClose=${closeModal} @@ -3747,6 +3955,9 @@ const [authorNoteTokens, setAuthorNoteTokens] = useSessionState('authorNoteTokens', defaultPresets.authorNoteTokens); const [authorNoteDepth, setAuthorNoteDepth] = useSessionState('authorNoteDepth', defaultPresets.authorNoteDepth); const [worldInfo, setWorldInfo] = useSessionState('worldInfo', defaultPresets.worldInfo); + const [searchAndReplaceMode, setSearchAndReplaceMode] = usePersistentState('searchAndReplaceMode', 0); + const [searchTerm, setSearchTerm] = usePersistentState('searchTerm', ''); + const [replaceTerm, setReplaceTerm] = usePersistentState('replaceTerm', ''); function replacePlaceholders(string,placeholders) { @@ -4952,6 +5163,29 @@ `; })}` : null} + <${SearchAndReplaceWidget} + isOpen=${modalState.searchAndReplace} + closeWidget=${() => closeModal("searchAndReplace")} + id="searchAndReplace" + > +
+ <${SelectBox} + label="Mode" + value=${searchAndReplaceMode} + onValueChange=${setSearchAndReplaceMode} + options=${[ + { name: 'Plaintext', value: 0 }, + { name: 'Regex', value: 1 }, + { name: 'Template', value: 2 }, + ]}/> + ${searchAndReplaceMode == 0 && html` + <${InputBox} label="Search This" type="text" + readOnly=${!!cancel} value=${searchTerm} onValueChange=${setSearchTerm}/> + <${InputBox} label="Replace With" type="text" + readOnly=${!!cancel} value=${replaceTerm} onValueChange=${setReplaceTerm}/> + `} +
+ ${probs ? html`
+ ${!!lastError && html` ${lastError}`} From 0c50b1e6a5910ac5908608f092b971ae3dd22b8c Mon Sep 17 00:00:00 2001 From: neCo <50079394+neCo2@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:55:37 +0200 Subject: [PATCH 02/25] widget component functional, plaintext replace functional --- mikupad.html | 115 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 85 insertions(+), 30 deletions(-) diff --git a/mikupad.html b/mikupad.html index a63069b..b9cbc01 100644 --- a/mikupad.html +++ b/mikupad.html @@ -344,6 +344,10 @@ font-size: 0.8rem; } +.widget-body { + z-index: 99; +} + .widget-title { -webkit-user-select: none; /* Safari */ -ms-user-select: none; /* IE 10 and IE 11 */ @@ -369,17 +373,25 @@ #searchAndReplace .widget-container { position: relative; } -#searchAndReplace .button-widget-top { + +.button-widget-top { + all:unset; position: absolute; - top:0.1em; - right:0.1em; + top: 0.25em; + right: 0.15em; + width:1.25em; + height:1.25em; + border-radius: 3px; +} +#searchAndReplace .widget-content { + padding: .2em; } .searchAndReplace-inputs { display: flex; flex-direction: row; gap: 8px; - padding: .2em; + margin-bottom:8px; } .searchAndReplace-inputs .InputBox { width: 100%; @@ -388,9 +400,6 @@ width: 40%; } -/* .widget-body { - -} */ .modal-overlay { position: fixed; @@ -2213,12 +2222,77 @@ `; } -function SearchAndReplaceWidget({ isOpen, closeWidget, id, children, ...props }) { +function SearchAndReplaceWidget({ isOpen, closeWidget, id, children, promptArea, cancel, ...props }) { + const [searchAndReplaceMode, setSearchAndReplaceMode] = usePersistentState('searchAndReplaceMode', 0); + const [searchTerm, setSearchTerm] = usePersistentState('searchTerm', ''); + const [replaceTerm, setReplaceTerm] = usePersistentState('replaceTerm', ''); + const [inputElement, setInputElement] = useState(null); + + useEffect(() => { + if (promptArea.current) { + setInputElement(promptArea.current); + } + }, [promptArea]); + + + console.log(promptArea) + console.log(promptArea.current) + + + function handleSearchAndReplace(mode,search,replace) { + // console.log(prompt) + if (!search) + return + + switch(mode) { + case 0: + plaintextReplace(search,replace,inputElement) + break; + case 1: + regexReplace(search,replace,inputElement) + break; + case 2: + templateReplace(search,replace,inputElement) + break; + } + } + + function plaintextReplace(search,replace,elem) { + // console.log(elem.value) + // need to figure out a smart way to keep the cursor position + elem.value = elem.value.replaceAll(search,replace) + } + return html` <${Widget} isOpen=${isOpen} onClose=${closeWidget} title="Search and Replace" id="${id}"> ${children} +
+ <${SelectBox} + label="Mode" + value=${searchAndReplaceMode} + onValueChange=${setSearchAndReplaceMode} + options=${[ + { name: 'Plaintext', value: 0 }, + { name: 'Regex', value: 1 }, + { name: 'Template', value: 2 }, + ]}/> + ${searchAndReplaceMode == 0 && html` + <${InputBox} label="Search This" type="text" + placeholder="Hatusne Miku" + readOnly=${!!cancel} value=${searchTerm} onValueChange=${setSearchTerm}/> + <${InputBox} label="Replace With" type="text" + placeholder="Makise Kurisu" + readOnly=${!!cancel} value=${replaceTerm} onValueChange=${setReplaceTerm}/> + `} +
+ `; } @@ -3955,9 +4029,7 @@ const [authorNoteTokens, setAuthorNoteTokens] = useSessionState('authorNoteTokens', defaultPresets.authorNoteTokens); const [authorNoteDepth, setAuthorNoteDepth] = useSessionState('authorNoteDepth', defaultPresets.authorNoteDepth); const [worldInfo, setWorldInfo] = useSessionState('worldInfo', defaultPresets.worldInfo); - const [searchAndReplaceMode, setSearchAndReplaceMode] = usePersistentState('searchAndReplaceMode', 0); - const [searchTerm, setSearchTerm] = usePersistentState('searchTerm', ''); - const [replaceTerm, setReplaceTerm] = usePersistentState('replaceTerm', ''); + function replacePlaceholders(string,placeholders) { @@ -5167,25 +5239,8 @@ isOpen=${modalState.searchAndReplace} closeWidget=${() => closeModal("searchAndReplace")} id="searchAndReplace" - > -
- <${SelectBox} - label="Mode" - value=${searchAndReplaceMode} - onValueChange=${setSearchAndReplaceMode} - options=${[ - { name: 'Plaintext', value: 0 }, - { name: 'Regex', value: 1 }, - { name: 'Template', value: 2 }, - ]}/> - ${searchAndReplaceMode == 0 && html` - <${InputBox} label="Search This" type="text" - readOnly=${!!cancel} value=${searchTerm} onValueChange=${setSearchTerm}/> - <${InputBox} label="Replace With" type="text" - readOnly=${!!cancel} value=${replaceTerm} onValueChange=${setReplaceTerm}/> - `} -
- + promptArea=${promptArea} + cancel=${cancel}/> ${probs ? html`
Date: Fri, 7 Jun 2024 17:04:53 +0200 Subject: [PATCH 03/25] fixed serif light theme --- mikupad.html | 1 + 1 file changed, 1 insertion(+) diff --git a/mikupad.html b/mikupad.html index b9cbc01..6d45186 100644 --- a/mikupad.html +++ b/mikupad.html @@ -346,6 +346,7 @@ .widget-body { z-index: 99; + color: var(--color-light) } .widget-title { From 2c431447393a06c041a557b794db33b426629934 Mon Sep 17 00:00:00 2001 From: neCo <50079394+neCo2@users.noreply.github.com> Date: Fri, 7 Jun 2024 17:17:35 +0200 Subject: [PATCH 04/25] added error display --- mikupad.html | 86 +++++----------------------------------------------- 1 file changed, 7 insertions(+), 79 deletions(-) diff --git a/mikupad.html b/mikupad.html index 6d45186..b76908a 100644 --- a/mikupad.html +++ b/mikupad.html @@ -2070,80 +2070,6 @@
`; } -//https://stackoverflow.com/questions/20926551/recommended-way-of-making-react-component-div-draggable -function Draggable({ isOpen, onClose, title, id, children, ...props }) { - function getDefaultProps() { - return { - // allow the initial position to be passed in as a prop - initialPos: {x: 0, y: 0} - } - } - function getInitialState() { - return { - pos: this.props.initialPos, - dragging: false, - rel: null // position relative to the cursor - } - } - // we could get away with not having this (and just having the listeners on - // our div), but then the experience would be possibly be janky. If there's - // anything w/ a higher z-index that gets in the way, then you're toast, - // etc. - function componentDidUpdate(props, state) { - if (this.state.dragging && !state.dragging) { - document.addEventListener('mousemove', this.onMouseMove) - document.addEventListener('mouseup', this.onMouseUp) - } else if (!this.state.dragging && state.dragging) { - document.removeEventListener('mousemove', this.onMouseMove) - document.removeEventListener('mouseup', this.onMouseUp) - } - } - - // calculate relative position of the mouse and set dragging=true - function onMouseDown(e) { - // only left mouse button - if (e.button !== 0) return - var pos = $(this.getDOMNode()).offset() - this.setState({ - dragging: true, - rel: { - x: e.pageX - pos.left, - y: e.pageY - pos.top - } - }) - e.stopPropagation() - e.preventDefault() - } - function onMouseUp(e) { - this.setState({dragging: false}) - e.stopPropagation() - e.preventDefault() - } - function onMouseMove(e) { - if (!this.state.dragging) return - this.setState({ - pos: { - x: e.pageX - this.state.rel.x, - y: e.pageY - this.state.rel.y - } - }) - e.stopPropagation() - e.preventDefault() - } - function render() { - // transferPropsTo will merge style & other props passed into our - // component to also be on the child DIV. - return this.transferPropsTo(React.DOM.div({ - onMouseDown: this.onMouseDown, - style: { - left: this.state.pos.x + 'px', - top: this.state.pos.y + 'px' - } - }, this.props.children)) - } -} - - function Widget({ isOpen, onClose, title, id, children, ...props }) { if (!isOpen) { return null; @@ -2224,6 +2150,7 @@ `; } function SearchAndReplaceWidget({ isOpen, closeWidget, id, children, promptArea, cancel, ...props }) { + const [searchAndReplaceError, setSearchAndReplaceError] = useState(undefined); const [searchAndReplaceMode, setSearchAndReplaceMode] = usePersistentState('searchAndReplaceMode', 0); const [searchTerm, setSearchTerm] = usePersistentState('searchTerm', ''); const [replaceTerm, setReplaceTerm] = usePersistentState('replaceTerm', ''); @@ -2235,12 +2162,8 @@ } }, [promptArea]); - - console.log(promptArea) - console.log(promptArea.current) - - function handleSearchAndReplace(mode,search,replace) { + setSearchAndReplaceError(undefined) // console.log(prompt) if (!search) return @@ -2262,6 +2185,9 @@ // console.log(elem.value) // need to figure out a smart way to keep the cursor position elem.value = elem.value.replaceAll(search,replace) + // TODO + // THIS DOESN'T SAVE REPLACEMENTS IF YOU DO NOT ADD ANY MANUAL EDITS + // BEFORE PREDICTING OR RELOADING } return html` @@ -2294,6 +2220,8 @@ onClick=${() => handleSearchAndReplace(searchAndReplaceMode,searchTerm,replaceTerm)}> Replace + ${!!searchAndReplaceError && html` +
${searchAndReplaceError}
`} `; } From bc5a4eecf11ed09c86cdd564212b5e31fbdc5d0d Mon Sep 17 00:00:00 2001 From: neCo <50079394+neCo2@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:38:57 +0200 Subject: [PATCH 05/25] regex replace functional --- mikupad.html | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/mikupad.html b/mikupad.html index b76908a..f19fdbd 100644 --- a/mikupad.html +++ b/mikupad.html @@ -2152,8 +2152,8 @@ function SearchAndReplaceWidget({ isOpen, closeWidget, id, children, promptArea, cancel, ...props }) { const [searchAndReplaceError, setSearchAndReplaceError] = useState(undefined); const [searchAndReplaceMode, setSearchAndReplaceMode] = usePersistentState('searchAndReplaceMode', 0); - const [searchTerm, setSearchTerm] = usePersistentState('searchTerm', ''); - const [replaceTerm, setReplaceTerm] = usePersistentState('replaceTerm', ''); + const [searchTerm, setSearchTerm] = usePersistentState('searchTerm',''); + const [replaceTerm, setReplaceTerm] = usePersistentState('replaceTerm',''); const [inputElement, setInputElement] = useState(null); useEffect(() => { @@ -2188,6 +2188,18 @@ // TODO // THIS DOESN'T SAVE REPLACEMENTS IF YOU DO NOT ADD ANY MANUAL EDITS // BEFORE PREDICTING OR RELOADING + // TODO + // Add this to undo/redo + } + function regexReplace(search,replace,elem) { + let regexComponents = search.match(/\/([^\/]+)\/(\w+)?/) + try { + let re = new RegExp(String.raw`${regexComponents[1]}`, `${regexComponents[2] ?? ""}`); + elem.value = elem.value.replace(re,replace) + } + catch { + setSearchAndReplaceError("Error: Not a RegEx. Is it wrapped in '/'?") + } } return html` @@ -2203,7 +2215,7 @@ options=${[ { name: 'Plaintext', value: 0 }, { name: 'Regex', value: 1 }, - { name: 'Template', value: 2 }, + // { name: 'Template', value: 2 }, ]}/> ${searchAndReplaceMode == 0 && html` <${InputBox} label="Search This" type="text" @@ -2213,6 +2225,14 @@ placeholder="Makise Kurisu" readOnly=${!!cancel} value=${replaceTerm} onValueChange=${setReplaceTerm}/> `} + ${searchAndReplaceMode == 1 && html` + <${InputBox} label="Search This" type="text" + placeholder="/(\\w+) Miku/gi" + readOnly=${!!cancel} value=${searchTerm} onValueChange=${setSearchTerm}/> + <${InputBox} label="Replace With" type="text" + placeholder="$1 Kurisu" + readOnly=${!!cancel} value=${replaceTerm} onValueChange=${setReplaceTerm}/> + `} - - `; -} -function SearchAndReplaceWidget({ isOpen, closeWidget, id, children, promptArea, cancel, ...props }) { - const [searchAndReplaceError, setSearchAndReplaceError] = useState(undefined); - const [searchAndReplaceMode, setSearchAndReplaceMode] = usePersistentState('searchAndReplaceMode', 0); - const [searchTerm, setSearchTerm] = usePersistentState('searchTerm',''); - const [replaceTerm, setReplaceTerm] = usePersistentState('replaceTerm',''); - const [inputElement, setInputElement] = useState(null); - - useEffect(() => { - if (promptArea.current) { - setInputElement(promptArea.current); - } - }, [promptArea]); - - function handleSearchAndReplace(mode,search,replace) { - setSearchAndReplaceError(undefined) - // console.log(prompt) - if (!search) - return - - switch(mode) { - case 0: - plaintextReplace(search,replace,inputElement) - break; - case 1: - regexReplace(search,replace,inputElement) - break; - case 2: - templateReplace(search,replace,inputElement) - break; - } - } - - function plaintextReplace(search,replace,elem) { - // console.log(elem.value) - // need to figure out a smart way to keep the cursor position - elem.value = elem.value.replaceAll(search,replace) - // TODO - // THIS DOESN'T SAVE REPLACEMENTS IF YOU DO NOT ADD ANY MANUAL EDITS - // BEFORE PREDICTING OR RELOADING - // TODO - // Add this to undo/redo - } - function regexReplace(search,replace,elem) { - let regexComponents = search.match(/\/([^\/]+)\/(\w+)?/) - try { - let re = new RegExp(String.raw`${regexComponents[1]}`, `${regexComponents[2] ?? ""}`); - elem.value = elem.value.replace(re,replace) - } - catch { - setSearchAndReplaceError("Error: Not a RegEx. Is it wrapped in '/'?") - } - } - - return html` - <${Widget} isOpen=${isOpen} onClose=${closeWidget} - title="Search and Replace" - id="${id}"> - ${children} -
- <${SelectBox} - label="Mode" - value=${searchAndReplaceMode} - onValueChange=${setSearchAndReplaceMode} - options=${[ - { name: 'Plaintext', value: 0 }, - { name: 'Regex', value: 1 }, - // { name: 'Template', value: 2 }, - ]}/> - ${searchAndReplaceMode == 0 && html` - <${InputBox} label="Search This" type="text" - placeholder="Hatsune Miku" - readOnly=${!!cancel} value=${searchTerm} onValueChange=${setSearchTerm}/> - <${InputBox} label="Replace With" type="text" - placeholder="Makise Kurisu" - readOnly=${!!cancel} value=${replaceTerm} onValueChange=${setReplaceTerm}/> - `} - ${searchAndReplaceMode == 1 && html` - <${InputBox} label="Search This" type="text" - placeholder="/(\\w+) Miku/gi" - readOnly=${!!cancel} value=${searchTerm} onValueChange=${setSearchTerm}/> - <${InputBox} label="Replace With" type="text" - placeholder="$1 Kurisu" - readOnly=${!!cancel} value=${replaceTerm} onValueChange=${setReplaceTerm}/> - `} -
- - ${!!searchAndReplaceError && html` -
${searchAndReplaceError}
`} - `; -} - function EditorPreferencesModal({ isOpen, closeModal, children }) { return html` <${Modal} isOpen=${isOpen} onClose=${closeModal} @@ -3206,6 +3030,182 @@ `; } +function Widget({ isOpen, onClose, title, id, children, ...props }) { + if (!isOpen) { + return null; + } + + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [isDragging, setIsDragging] = useState(false); + const dragRef = useRef(null); + + useEffect(() => { + const handleMouseMove = (e) => { + if (!isDragging) return; + const deltaX = e.clientX - dragRef.current.startX; + const deltaY = e.clientY - dragRef.current.startY; + setPosition({ + x: dragRef.current.initialX + deltaX, + y: dragRef.current.initialY + deltaY, + }); + }; + + const handleMouseUp = () => { + setIsDragging(false); + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + }, [isDragging]); + + const handleMouseDown = (e) => { + setIsDragging(true); + dragRef.current = { + startX: e.clientX, + startY: e.clientY, + initialX: position.x, + initialY: position.y, + }; + }; + + useEffect(() => { + const onKeyDown = (event) => { + if (event.key === 'Escape') { + onClose(); + } + }; + document.addEventListener('keydown', onKeyDown); + return () => { + document.removeEventListener('keydown', onKeyDown); + }; + }, []); + + return html` +
+
+
{}} + onMouseUp=${() => {}} + onMouseLeave=${() => {}}> + + ${title} +
+
+ ${children} +
+ +
+
`; +} +function SearchAndReplaceWidget({ isOpen, closeWidget, id, children, promptArea, cancel, ...props }) { + const [searchAndReplaceError, setSearchAndReplaceError] = useState(undefined); + const [searchAndReplaceMode, setSearchAndReplaceMode] = usePersistentState('searchAndReplaceMode', 0); + const [searchTerm, setSearchTerm] = usePersistentState('searchTerm',''); + const [replaceTerm, setReplaceTerm] = usePersistentState('replaceTerm',''); + const [inputElement, setInputElement] = useState(null); + + useEffect(() => { + if (promptArea.current) { + setInputElement(promptArea.current); + } + }, [promptArea]); + + function handleSearchAndReplace(mode,search,replace) { + setSearchAndReplaceError(undefined) + // console.log(prompt) + if (!search) + return + + switch(mode) { + case 0: + plaintextReplace(search,replace,inputElement) + break; + case 1: + regexReplace(search,replace,inputElement) + break; + case 2: + templateReplace(search,replace,inputElement) + break; + } + } + + function plaintextReplace(search,replace,elem) { + // console.log(elem.value) + // need to figure out a smart way to keep the cursor position + elem.value = elem.value.replaceAll(search,replace) + // TODO + // THIS DOESN'T SAVE REPLACEMENTS IF YOU DO NOT ADD ANY MANUAL EDITS + // BEFORE PREDICTING OR RELOADING + // TODO + // Add this to undo/redo + } + function regexReplace(search,replace,elem) { + let regexComponents = search.match(/\/([^\/]+)\/(\w+)?/) + try { + let re = new RegExp(String.raw`${regexComponents[1]}`, `${regexComponents[2] ?? ""}`); + elem.value = elem.value.replace(re,replace) + } + catch { + setSearchAndReplaceError("Error: Not a RegEx. Is it wrapped in '/'?") + } + } + + return html` + <${Widget} isOpen=${isOpen} onClose=${closeWidget} + title="Search and Replace" + id="${id}"> + ${children} +
+ <${SelectBox} + label="Mode" + value=${searchAndReplaceMode} + onValueChange=${setSearchAndReplaceMode} + options=${[ + { name: 'Plaintext', value: 0 }, + { name: 'Regex', value: 1 }, + // { name: 'Template', value: 2 }, + ]}/> + ${searchAndReplaceMode == 0 && html` + <${InputBox} label="Search This" type="text" + placeholder="Hatsune Miku" + readOnly=${!!cancel} value=${searchTerm} onValueChange=${setSearchTerm}/> + <${InputBox} label="Replace With" type="text" + placeholder="Makise Kurisu" + readOnly=${!!cancel} value=${replaceTerm} onValueChange=${setReplaceTerm}/> + `} + ${searchAndReplaceMode == 1 && html` + <${InputBox} label="Search This" type="text" + placeholder="/(\\w+) Miku/gi" + readOnly=${!!cancel} value=${searchTerm} onValueChange=${setSearchTerm}/> + <${InputBox} label="Replace With" type="text" + placeholder="$1 Kurisu" + readOnly=${!!cancel} value=${replaceTerm} onValueChange=${setReplaceTerm}/> + `} +
+ + ${!!searchAndReplaceError && html` +
${searchAndReplaceError}
`} + `; +} + class IndexedDBAdapter { constructor() { this.dbName = 'MikuPad'; From 0524cd213a47a4df6cdb4e90be8e72c42691c57c Mon Sep 17 00:00:00 2001 From: neCo <50079394+neCo2@users.noreply.github.com> Date: Sat, 8 Jun 2024 00:20:27 +0200 Subject: [PATCH 09/25] added search and replace button to prompt-container --- mikupad.html | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/mikupad.html b/mikupad.html index cf025ed..13e9ecc 100644 --- a/mikupad.html +++ b/mikupad.html @@ -5153,11 +5153,27 @@ return html`
+