Skip to content

Commit 1746fc8

Browse files
authored
Merge pull request #730 from lcnetdev/find-and-replace
Find and Replace, TitleCase
2 parents aa6125d + e4d2283 commit 1746fc8

9 files changed

Lines changed: 513 additions & 19 deletions

File tree

src/components/panels/edit/fields/Literal.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
@keyup="navKey"
8282
:ref="'input_' + lValue['@guid']"
8383
:data-guid="lValue['@guid']"
84+
:data-parent="guid"
8485
:disabled="readOnly"
8586
></textarea>
8687
</div>
@@ -110,6 +111,7 @@
110111
@keydown="keyDown"
111112
:ref="'input_' + lValue['@guid']"
112113
:data-guid="lValue['@guid']"
114+
:data-parent="guid"
113115
:disabled="readOnly"
114116
:readonly="structure.propertyLabel=='Local identifier'"
115117
></textarea>
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
<script>
2+
import { usePreferenceStore } from '@/stores/preference'
3+
// import { useConfigStore } from '@/stores/config'
4+
import { useProfileStore } from '@/stores/profile'
5+
6+
import { mapStores, mapState, mapWritableState } from 'pinia'
7+
import { VueFinalModal } from 'vue-final-modal'
8+
import VueDragResize from 'vue3-drag-resize'
9+
import "vue3-colorpicker/style.css";
10+
11+
export default {
12+
components: {
13+
VueFinalModal,
14+
VueDragResize,
15+
},
16+
17+
data() {
18+
return {
19+
width: 0,
20+
height: 0,
21+
top: 100,
22+
left: 0,
23+
24+
initialWidth: 900,
25+
initalHeight: 500,
26+
initalLeft: 400,
27+
28+
iconColor: '#000000',
29+
30+
customIcon: '',
31+
32+
presetIcon: null,
33+
useAsDefault: false,
34+
35+
36+
findTarget: '',
37+
replaceTarget: '',
38+
activeMatch: 0,
39+
matches: [],
40+
matchCase: false,
41+
42+
}
43+
},
44+
computed: {
45+
...mapStores(useProfileStore, usePreferenceStore),
46+
...mapWritableState(usePreferenceStore, ['showFindReplaceModal', 'panelDisplay', 'panelSizePresets']),
47+
...mapWritableState(useProfileStore, ['activeComponent', 'activeProfile', 'findLiterals', 'setValueLiteral', 'buildPropertyPath', 'returnStructureByGUID', 'returnLiteralValueFromProfile']),
48+
49+
50+
51+
},
52+
53+
watch: {
54+
matchCase(newVal, oldVal) {
55+
this.matchCase = newVal
56+
this.find()
57+
}
58+
59+
},
60+
61+
methods: {
62+
63+
dragResize: function (newRect) {
64+
this.width = newRect.width
65+
this.height = newRect.height
66+
this.top = newRect.top
67+
this.left = newRect.left
68+
},
69+
70+
onSelectElement(event) {
71+
const tagName = event.target.tagName
72+
if (tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'LABEL' || tagName === 'SELECT' || tagName === 'SPAN' || tagName == 'INPUT' || tagName === 'TD') {
73+
event.stopPropagation()
74+
}
75+
},
76+
77+
close() {
78+
this.matches = []
79+
this.showFindReplaceModal = false
80+
},
81+
82+
find() {
83+
this.matches = []
84+
let literals = document.getElementsByTagName('textarea')
85+
for (let field of literals) {
86+
let fieldGuid = field.getAttribute("data-guid")
87+
let targetGuid = field.getAttribute("data-parent")
88+
89+
let value = field.value
90+
if (!this.matchCase) {
91+
let reg = new RegExp(this.findTarget, "dig")
92+
let matches = value.matchAll(reg)
93+
for (let match of matches) {
94+
let structure = this.returnStructureByGUID(targetGuid)
95+
this.matches.push({ 'match': match, 'text': value, 'field': field, 'component': structure })
96+
}
97+
} else {
98+
let reg = new RegExp(this.findTarget, "dg")
99+
let matches = value.matchAll(reg)
100+
for (let match of matches) {
101+
let structure = this.returnStructureByGUID(targetGuid)
102+
this.matches.push({ 'match': match, 'text': value, 'field': field, 'component': structure })
103+
}
104+
}
105+
}
106+
},
107+
108+
replace(data) {
109+
let target = data.field
110+
let fieldGuid = target.getAttribute("data-guid")
111+
let targetGuid = target.getAttribute("data-parent")
112+
let newText = target.value.slice(0, data.match.indices[0][0]) + this.replaceTarget + target.value.slice(data.match.indices[0][1])
113+
114+
let pp
115+
try {
116+
let structure = this.returnStructureByGUID(targetGuid)
117+
pp = this.buildPropertyPath(structure, [], fieldGuid)
118+
} catch (err) {
119+
console.error("Error building PropertyPath: ", err)
120+
return
121+
}
122+
let currentValue = this.returnLiteralValueFromProfile(targetGuid, pp)
123+
124+
for (let val of currentValue) {
125+
if ((val['@language'] && val['@language'].toLowerCase().includes('latn')) || val['@language'] == null) {
126+
this.setValueLiteral(targetGuid, fieldGuid, pp, newText, val['@language'], false)
127+
target.value = newText
128+
}
129+
}
130+
},
131+
132+
loopLiteralsToReplace(all = false) {
133+
if (!this.replaceTarget) {
134+
let cont = confirm("There's no replacement text. Continuing will delete text.")
135+
if (!cont) { return }
136+
}
137+
if (!all) {
138+
let target = this.matches[this.activeMatch]
139+
this.replace(target)
140+
// if (this.activeMatch < this.matches.length-1){
141+
// this.activeMatch++
142+
// }
143+
} else {
144+
for (let target of this.matches) {
145+
this.replace(target)
146+
}
147+
}
148+
149+
150+
setTimeout(() => {
151+
this.find()
152+
}, 500)
153+
},
154+
155+
buildDisplay(text, details) {
156+
// wrap target text in HTML to style it
157+
let target = this.findTarget
158+
const tag = "<span class='match-bold'>"
159+
160+
const idxStart = details.match.indices[0][0]
161+
const idxEnd = Number(idxStart) + target.length + tag.length
162+
163+
if (idxStart >= 0 && idxEnd >= 0) {
164+
text = text.slice(0, idxStart) + tag + text.slice(idxStart);
165+
text = text.slice(0, idxStart + tag.length + target.length) + "</span>" + text.slice(idxEnd)
166+
}
167+
168+
return text
169+
},
170+
171+
jumpToComponent(pName, eName){
172+
this.showFindReplaceModal = false
173+
this.activeComponent = this.activeProfile.rt[pName].pt[eName]
174+
},
175+
176+
177+
},
178+
179+
180+
created() {
181+
182+
183+
},
184+
185+
async mounted() {
186+
this.panelSizePresets = this.preferenceStore.returnValue('--o-edit-main-splitpane-edit-panel-size-presets')
187+
}
188+
}
189+
190+
191+
192+
</script>
193+
194+
<template>
195+
196+
197+
<VueFinalModal display-directive="show" :hide-overlay="false" :overlay-transition="'vfm-fade'">
198+
<div
199+
>
200+
<VueDragResize :is-active="true" :w="initialWidth" :h="initalHeight" :x="initalLeft" :y="50" class="debug-modal"
201+
@resizing="dragResize" @dragging="dragResize" :sticks="['br']" :stickSize="22"
202+
:style="`${this.preferenceStore.styleModalBackgroundColor()}; ${this.preferenceStore.styleModalTextColor()}`"
203+
>
204+
<div id="panel-resize-content" ref="panelResizeContent" @mousedown="onSelectElement($event)"
205+
@touchstart="onSelectElement($event)">
206+
<div class="menu-buttons">
207+
<button class="close-button" @pointerup="close">X</button>
208+
</div>
209+
<h2>Find & Replace</h2>
210+
<div class="container-search">
211+
<form ref="urlToLoadForm" v-on:submit.prevent="">
212+
<div class="search-fields">
213+
<label for="input-find" onclick="" class="toggle-btn">Find: </label>
214+
<input id="input-find" type="text" class="search-mode-radio" v-model="findTarget"
215+
name="inputFind" autofocus />
216+
&nbsp;&nbsp;&nbsp;
217+
<label for="input-case" onclick="" class="toggle-btn">Match Case? </label>
218+
<input id="input-case" type="checkbox" class="search-mode-radio" v-model="matchCase"
219+
name="inputFind" />
220+
</div>
221+
222+
<div class="search-fields">
223+
<label for="input-replace" onclick="" class="toggle-btn">Replace: </label>
224+
<input id="input-replace" type="text" class="search-mode-radio" v-model="replaceTarget"
225+
name="inputReplace" />
226+
</div>
227+
<br>
228+
<button @click="find">Find</button>
229+
</form>
230+
231+
</div>
232+
233+
<div v-if="matches.length >= 0" class="container-results">
234+
<table>
235+
<thead>
236+
<tr>
237+
<th>Source</th>
238+
<th>Component</th>
239+
<th>Value</th>
240+
</tr>
241+
</thead>
242+
<tbody>
243+
<tr v-for="(match, idx) of matches">
244+
<td>{{ match.component.parentId.split(":").at(-1) }}</td>
245+
<td class="component-label" @click="jumpToComponent(match.component.parentId, match.component.id)">
246+
{{ match.component.propertyLabel }}
247+
<span class="material-icons">move_down</span>
248+
</td>
249+
<td :class="{ active: activeMatch === idx }" @click="activeMatch = idx"
250+
v-html="buildDisplay(match.text, match)">
251+
</td>
252+
</tr>
253+
</tbody>
254+
</table>
255+
256+
<button @click="loopLiteralsToReplace()">Replace</button>
257+
<button @click="loopLiteralsToReplace(true)">Replace All</button>
258+
</div>
259+
260+
</div>
261+
262+
263+
264+
265+
</VueDragResize>
266+
</div>
267+
</VueFinalModal>
268+
269+
270+
271+
272+
</template>
273+
<style>
274+
span.match-bold {
275+
font-weight: bolder;
276+
font-size: 16px;
277+
}
278+
279+
.content-container {
280+
padding: 10px;
281+
background-color: unset;
282+
}
283+
284+
.container-results {
285+
padding-top: 15px;
286+
max-height: 50%;
287+
width: 100%;
288+
overflow: scroll;
289+
}
290+
291+
.component-label {
292+
cursor: pointer;
293+
}
294+
</style>
295+
296+
<style scoped>
297+
298+
299+
.container-search {
300+
display: table;
301+
}
302+
.search-fields {
303+
display: table-row;
304+
}
305+
306+
label,
307+
input{
308+
display: table-cell
309+
}
310+
311+
.container-search {
312+
padding-top: 5px;
313+
}
314+
315+
.menu-buttons {
316+
margin-right: 5px;
317+
padding-top: 5px;
318+
padding-left: 15px;
319+
float: right;
320+
z-index: 99;
321+
}
322+
323+
table {
324+
border-collapse: collapse;
325+
border: 2px solid rgb(140 140 140);
326+
font-family: sans-serif;
327+
font-size: 0.8rem;
328+
letter-spacing: 1px;
329+
330+
width: 100%;
331+
margin-bottom: 15px;
332+
}
333+
334+
th {
335+
font-weight: bold;
336+
font-size: 14px;
337+
}
338+
339+
th,
340+
td {
341+
border: 1px solid rgb(160 160 160);
342+
padding: 8px 10px;
343+
text-align: center;
344+
}
345+
346+
.active {
347+
background-color: rgb(131, 131, 255);
348+
}
349+
</style>

src/components/panels/edit/modals/PanelSizeModal.vue

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@
163163
@dragging="dragResize"
164164
:sticks="['br']"
165165
:stickSize="22"
166+
:style="`${this.preferenceStore.styleModalBackgroundColor()}; ${this.preferenceStore.styleModalTextColor()}`"
166167
>
167168
<div id="panel-resize-content" ref="panelResizeContent" @mousedown="onSelectElement($event)" @touchstart="onSelectElement($event)">
168169
<div class="menu-buttons">
@@ -241,15 +242,18 @@
241242
</template>
242243
<style>
243244
244-
.content-container{
245-
246-
background-color: white;
247-
}
245+
/* .content-container{
246+
background-color: white !important;
247+
} */
248248
249249
</style>
250250

251251
<style scoped>
252252
253+
.content-container{
254+
background-color: white !important;
255+
}
256+
253257
.icon-size{
254258
font-size: 2.5em;
255259
}

0 commit comments

Comments
 (0)