mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 12:07:08 +00:00
Make Vue component reusable.
This converts <dougal-fixed-width-format/> into a more reusable <dougal-fixed-string-decoder/> component.
This commit is contained in:
@@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<v-row dense no-gutters>
|
||||||
|
|
||||||
|
<v-col cols="1">
|
||||||
|
<slot name="prepend"></slot>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="2">
|
||||||
|
<v-chip outlined label small :color="colour">{{name}}</v-chip>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="2">
|
||||||
|
<v-text-field
|
||||||
|
class="ml-3"
|
||||||
|
dense
|
||||||
|
label="From"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
v-model.number="value.offset"
|
||||||
|
:readonly="readonly"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="2">
|
||||||
|
<v-text-field
|
||||||
|
class="ml-3"
|
||||||
|
dense
|
||||||
|
label="Length"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
v-model.number="value.length"
|
||||||
|
:readonly="readonly"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="2">
|
||||||
|
<dougal-field-content-dialog
|
||||||
|
:readonly="readonly"
|
||||||
|
:value="value"
|
||||||
|
@input="$emit('input', $event)"
|
||||||
|
></dougal-field-content-dialog>
|
||||||
|
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="1">
|
||||||
|
<slot name="append"></slot>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.input {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 8px 0 8px;
|
||||||
|
min-height: 32px;
|
||||||
|
max-height: 32px;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input >>> .chunk {
|
||||||
|
padding-inline: 1px;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input >>> .chunk-empty {
|
||||||
|
padding-inline: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input >>> .chunk-overlap {
|
||||||
|
padding-inline: 1px;
|
||||||
|
border: 1px solid grey;
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DougalFieldContentDialog from '../fields/field-content-dialog'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "DougalFixedStringDecoderField",
|
||||||
|
|
||||||
|
components: {
|
||||||
|
DougalFieldContentDialog
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
value: Object,
|
||||||
|
name: String,
|
||||||
|
colour: String,
|
||||||
|
readonly: Boolean,
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
name_: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
|
||||||
|
name () {
|
||||||
|
if (this.name != this.name_) {
|
||||||
|
this.name_ = this.name;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
addField () {
|
||||||
|
if (!this.fieldNameErrors) {
|
||||||
|
this.$emit("update:fields", {
|
||||||
|
...this.fields,
|
||||||
|
[this.fieldName]: { offset: 0, length: 0 }
|
||||||
|
});
|
||||||
|
this.fieldName = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset () {
|
||||||
|
this.text_ = this.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted () {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
<template>
|
||||||
|
<v-card flat elevation="0">
|
||||||
|
<v-card-title v-if="title">{{ title }}</v-card-title>
|
||||||
|
<v-card-subtitle v-if="subtitle">{{ subtitle }}</v-card-subtitle>
|
||||||
|
<v-card-text>
|
||||||
|
<v-form>
|
||||||
|
<div v-if="isMultiline"
|
||||||
|
class="multiline mb-5"
|
||||||
|
:style="multilineElementStyle"
|
||||||
|
v-html="html"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<v-input v-else
|
||||||
|
class="v-text-field"
|
||||||
|
:hint="hint"
|
||||||
|
persistent-hint
|
||||||
|
v-model="text_"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="v-label"
|
||||||
|
:class="[ $vuetify.theme.isDark && 'theme--dark', text_ && text_.length && 'v-label--active' ]"
|
||||||
|
style="left: 0px; right: auto; position: absolute;"
|
||||||
|
>{{ label }}</label>
|
||||||
|
<div class="input"
|
||||||
|
:class="isMultiline ? 'multiline' : ''"
|
||||||
|
v-html="html"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</v-input>
|
||||||
|
|
||||||
|
<v-container>
|
||||||
|
|
||||||
|
<dougal-fixed-string-decoder-field v-for="(field, key) in fields" :key="key"
|
||||||
|
v-model="fields[key]"
|
||||||
|
:name="key"
|
||||||
|
:colour="getHSLColourFor(key)"
|
||||||
|
:readonly="readonly"
|
||||||
|
>
|
||||||
|
<template v-slot:append v-if="editableFieldList && !readonly">
|
||||||
|
<v-btn
|
||||||
|
class="ml-3"
|
||||||
|
fab
|
||||||
|
text
|
||||||
|
small
|
||||||
|
title="Remove this property"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
color="error"
|
||||||
|
@click="removeField(key)"
|
||||||
|
>mdi-minus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</dougal-fixed-string-decoder-field>
|
||||||
|
|
||||||
|
<v-row dense no-gutters v-if="editableFieldList && !readonly">
|
||||||
|
<v-col cols="3">
|
||||||
|
<v-text-field
|
||||||
|
label="Add new field"
|
||||||
|
hint="Enter the name of a new field"
|
||||||
|
:error-messages="fieldNameErrors"
|
||||||
|
v-model="fieldName"
|
||||||
|
append-outer-icon="mdi-plus-circle"
|
||||||
|
@keydown.enter.prevent="addField"
|
||||||
|
>
|
||||||
|
<template v-slot:append-outer>
|
||||||
|
<v-icon
|
||||||
|
color="primary"
|
||||||
|
:disabled="fieldName && !!fieldNameErrors"
|
||||||
|
@click="addField"
|
||||||
|
>mdi-plus</v-icon>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.input {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 8px 0 8px;
|
||||||
|
min-height: 32px;
|
||||||
|
max-height: 32px;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiline {
|
||||||
|
font-family: mono;
|
||||||
|
white-space: pre;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiline >>> .line-number {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 75%;
|
||||||
|
width: 5ex;
|
||||||
|
margin-inline-end: 1ex;
|
||||||
|
text-align: right;
|
||||||
|
border: none;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input, .multiline >>> .chunk {
|
||||||
|
padding-inline: 1px;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input, .multiline >>> .chunk-empty {
|
||||||
|
padding-inline: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input, .multiline >>> .chunk-overlap {
|
||||||
|
padding-inline: 1px;
|
||||||
|
border: 1px solid grey;
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getHSLColourFor } from '@/lib/hsl'
|
||||||
|
import DougalFixedStringDecoderField from './fixed-string-decoder-field'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "DougalFixedStringDecoder",
|
||||||
|
|
||||||
|
components: {
|
||||||
|
DougalFixedStringDecoderField
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [
|
||||||
|
{
|
||||||
|
methods: {
|
||||||
|
getHSLColourFor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
text: String,
|
||||||
|
fields: Object,
|
||||||
|
multiline: Boolean,
|
||||||
|
numberedLines: [ Boolean, Number ],
|
||||||
|
maxHeight: String,
|
||||||
|
editableFieldList: { type: Boolean, default: true },
|
||||||
|
readonly: Boolean,
|
||||||
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
|
label: String,
|
||||||
|
hint: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
//< The reason for not using this.text directly is that at some point
|
||||||
|
//< we might extend this component to allow editing the sample text.
|
||||||
|
text_: "",
|
||||||
|
//< The name of a new field to add.
|
||||||
|
fieldName: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
/** Whether to treat the sample text as multiline.
|
||||||
|
*/
|
||||||
|
isMultiline () {
|
||||||
|
return this.multiline === true || this.text.includes("\n");
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Return the fields as an array sorted by offset
|
||||||
|
*/
|
||||||
|
parts () {
|
||||||
|
return Object.entries(this.fields).sort( (a, b) => a.offset - b.offset );
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Transform this.parts into {start, end} intervals.
|
||||||
|
*/
|
||||||
|
chunks () {
|
||||||
|
const chunks = [];
|
||||||
|
const chunk_num = 0;
|
||||||
|
for (const [name, part] of this.parts) {
|
||||||
|
const chunk = {};
|
||||||
|
chunk.start = part.offset;
|
||||||
|
chunk.end = part.offset + part.length - 1;
|
||||||
|
//chunk.text = this.text_.slice(chunk.start, chunk.end);
|
||||||
|
chunk.colour = this.getHSLColourFor(name)
|
||||||
|
|
||||||
|
chunks.push(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
},
|
||||||
|
|
||||||
|
multilineElementStyle () {
|
||||||
|
if (this.maxHeight) {
|
||||||
|
return `max-height: ${this.maxHeight};`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Return a colourised HTML version of this.text.
|
||||||
|
*/
|
||||||
|
html () {
|
||||||
|
if (!this.text_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isMultiline) {
|
||||||
|
if (typeof this.numberedLines == "number" || this.numberedLines) {
|
||||||
|
const offset = typeof this.numberedLines == "number" ? Math.abs(this.numberedLines) : 0;
|
||||||
|
return this.text_.split("\n").map( (line, idx) =>
|
||||||
|
this.numberLine(offset+idx, this.renderTextLine(line))).join("<br/>");
|
||||||
|
} else {
|
||||||
|
return this.text_.split("\n").map(this.renderTextLine).join("<br/>");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.renderTextLine(this.text_);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
fieldNameErrors () {
|
||||||
|
return this.parts.find( i => i[0] == this.fieldName )
|
||||||
|
? "A field with this name already exists"
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
|
||||||
|
text () {
|
||||||
|
if (this.text != this.text_) {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
addField () {
|
||||||
|
if (!this.fieldNameErrors) {
|
||||||
|
this.$emit("update:fields", {
|
||||||
|
...this.fields,
|
||||||
|
[this.fieldName]: { offset: 0, length: 0 }
|
||||||
|
});
|
||||||
|
this.fieldName = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// NOTE Not used
|
||||||
|
updateField (field, key, value) {
|
||||||
|
const fields = {
|
||||||
|
...this.fields,
|
||||||
|
[field]: {
|
||||||
|
...this.fields[field],
|
||||||
|
[key]: value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.$emit("update:fields", fields);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeField (key) {
|
||||||
|
const fields = {...this.fields};
|
||||||
|
delete fields[key];
|
||||||
|
this.$emit("update:fields", fields);
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Return an HSL colour as a function of an input value
|
||||||
|
* `str`.
|
||||||
|
*/
|
||||||
|
xgetHSLColourFor () {
|
||||||
|
console.log("WILL BE DEFINED ON MOUNT");
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Return a `<span>` opening tag.
|
||||||
|
*/
|
||||||
|
style (name, colour) {
|
||||||
|
return colour
|
||||||
|
? `<span class="${name}" style="color:${colour};border-color:${colour}">`
|
||||||
|
: `<span class="${name}">`;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Return an array of the intervals that intersect `pos`.
|
||||||
|
* May be empty.
|
||||||
|
*/
|
||||||
|
chunksFor (pos) {
|
||||||
|
return this.chunks.filter( chunk =>
|
||||||
|
pos >= chunk.start &&
|
||||||
|
pos <= chunk.end
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Algorithm:
|
||||||
|
*
|
||||||
|
* Go through every character of one line of text and determine in which
|
||||||
|
* part(s) it falls in, if any. Collect adjacent same parts into <span/>
|
||||||
|
* elements.
|
||||||
|
*/
|
||||||
|
renderTextLine (text) {
|
||||||
|
const parts = [];
|
||||||
|
|
||||||
|
let prevStyle;
|
||||||
|
|
||||||
|
for (const pos in text) {
|
||||||
|
const chunks = this.chunksFor(pos);
|
||||||
|
const isEmpty = chunks.length == 0;
|
||||||
|
const isOverlap = chunks.length > 1;
|
||||||
|
|
||||||
|
const style = isEmpty
|
||||||
|
? this.style("chunk-empty")
|
||||||
|
: isOverlap
|
||||||
|
? this.style("chunk-overlap")
|
||||||
|
: this.style("chunk", chunks[0].colour);
|
||||||
|
|
||||||
|
if (style != prevStyle) {
|
||||||
|
if (prevStyle) {
|
||||||
|
parts.push("</span>");
|
||||||
|
}
|
||||||
|
parts.push(style);
|
||||||
|
}
|
||||||
|
parts.push(text[pos]);
|
||||||
|
prevStyle = style;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts.length) {
|
||||||
|
parts.push("</span>");
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join("");
|
||||||
|
},
|
||||||
|
|
||||||
|
numberLine (number, line) {
|
||||||
|
return `<span class="line-number">${number}</span>${line}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
setText (v) {
|
||||||
|
//console.log(v);
|
||||||
|
this.text_ = v;
|
||||||
|
},
|
||||||
|
|
||||||
|
reset () {
|
||||||
|
this.text_ = this.text.replaceAll("\r", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted () {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,322 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card flat>
|
|
||||||
<v-card-text>
|
|
||||||
<div class="sample" v-html="sampleHtml" ref="sample">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-divider></v-divider>
|
|
||||||
|
|
||||||
<v-form>
|
|
||||||
<v-container>
|
|
||||||
|
|
||||||
<v-row no-gutters v-for="(field, key) in {line, point, easting, northing}" :key="key">
|
|
||||||
<v-col>
|
|
||||||
<v-chip outlined :color="field.colour">{{field.label}}</v-chip>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-text-field
|
|
||||||
class="ml-3"
|
|
||||||
dense
|
|
||||||
label="From"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
:ref="key"
|
|
||||||
v-model.number="field.offset"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-text-field
|
|
||||||
class="ml-3"
|
|
||||||
dense
|
|
||||||
label="Width"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
v-model.number="field.width"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
</v-container>
|
|
||||||
</v-form>
|
|
||||||
|
|
||||||
</v-card-text>
|
|
||||||
<v-card-actions>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.sample {
|
|
||||||
font-family: mono;
|
|
||||||
white-space: pre;
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
|
|
||||||
function arraysAreEqual(arr0, arr1) {
|
|
||||||
return (arr0 && arr1 && arr0.length == arr1.length) &&
|
|
||||||
arr0.reduce( (acc, cur, idx) =>
|
|
||||||
acc && (arr1[idx] == cur), true );
|
|
||||||
}
|
|
||||||
|
|
||||||
function repeatsLength(arr, comparator=(a, b) => a == b) {
|
|
||||||
function checkRepeat(arr) {
|
|
||||||
let idx = 0;
|
|
||||||
if (arr.length) {
|
|
||||||
while (comparator(arr[idx], arr[++idx]));
|
|
||||||
}
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
const repeats = [];
|
|
||||||
let offset = 0;
|
|
||||||
let count;
|
|
||||||
while (count = checkRepeat(arr.slice(offset))) {
|
|
||||||
repeats.push(count);
|
|
||||||
offset += count;
|
|
||||||
}
|
|
||||||
|
|
||||||
return repeats;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "DougalProjectSettingsFixedWidthFormat",
|
|
||||||
|
|
||||||
props: [ "value", "sample" ],
|
|
||||||
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
line: {
|
|
||||||
name: "line_name",
|
|
||||||
type: "int",
|
|
||||||
label: "Line",
|
|
||||||
offset: 0,
|
|
||||||
width: 4,
|
|
||||||
colour: "green"
|
|
||||||
},
|
|
||||||
point: {
|
|
||||||
name: "point_number",
|
|
||||||
type: "int",
|
|
||||||
label: "Point",
|
|
||||||
offset: 4,
|
|
||||||
width: 4,
|
|
||||||
colour: "blue"
|
|
||||||
},
|
|
||||||
easting: {
|
|
||||||
name: "easting",
|
|
||||||
type: "float",
|
|
||||||
label: "Easting",
|
|
||||||
offset: 8,
|
|
||||||
width: 12,
|
|
||||||
colour: "red"
|
|
||||||
},
|
|
||||||
northing: {
|
|
||||||
name: "northing",
|
|
||||||
type: "float",
|
|
||||||
label: "Northing",
|
|
||||||
offset: 20,
|
|
||||||
width: 12,
|
|
||||||
colour: "orange"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
value (newValue) {
|
|
||||||
this.reset();
|
|
||||||
},
|
|
||||||
|
|
||||||
fields: {
|
|
||||||
handler () {
|
|
||||||
if (this.overlappingFields) {
|
|
||||||
this.$emit("input", null);
|
|
||||||
} else {
|
|
||||||
this.$emit("input", {
|
|
||||||
names: this.names,
|
|
||||||
types: this.types,
|
|
||||||
widths: this.widths
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
|
|
||||||
sampleHtml () {
|
|
||||||
if (!this.sample) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = this.fieldsDescending;
|
|
||||||
|
|
||||||
const partsForColumn = (col) => {
|
|
||||||
return parts.filter(part =>
|
|
||||||
part.offset <= col && part.offset+part.width > col
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const partsForLine = (line) => {
|
|
||||||
return Array.from({length: line.length}, (_, idx) => partsForColumn(idx));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDecorators(arr, comparator, specialValue) {
|
|
||||||
const repeats = repeatsLength(arr, comparator);
|
|
||||||
let sum = 0;
|
|
||||||
const res = [];
|
|
||||||
repeats.slice(0, -1).forEach( count => {
|
|
||||||
const idx = sum;
|
|
||||||
sum += count;
|
|
||||||
const el = arr[idx];
|
|
||||||
if (el.length) {
|
|
||||||
if (el.length == 1) {
|
|
||||||
// Only one field, return it
|
|
||||||
res.push({
|
|
||||||
...el[0],
|
|
||||||
offset: idx,
|
|
||||||
width: Math.min(Math.min(el[0].offset+el[0].width, sum+idx) - idx, sum-idx)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// More than one element, return special value
|
|
||||||
res.push({
|
|
||||||
...specialValue,
|
|
||||||
offset: idx,
|
|
||||||
width: sum-idx
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.sort( (a, b) => b.offset-a.offset );
|
|
||||||
}
|
|
||||||
|
|
||||||
const lines = this.sample.split("\n").map( line => {
|
|
||||||
const decorators = getDecorators(partsForLine(line), arraysAreEqual, {name: "Overlap", colour: "grey"});
|
|
||||||
let s = line;
|
|
||||||
for (const part of decorators) {
|
|
||||||
const s0 = s.slice(0, part.offset);
|
|
||||||
const s1 = s.slice(part.offset, part.offset+part.width);
|
|
||||||
const s2 = s.slice(part.offset+part.width);
|
|
||||||
s = s0+`<span class="${part.colour}--text" title="${part.name}" style="border: 1px solid;">${s1}</span>${s2}`;
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
});
|
|
||||||
|
|
||||||
return lines.join("<br/>");
|
|
||||||
},
|
|
||||||
|
|
||||||
overlappingFields () {
|
|
||||||
function isOverlapping (a, b) {
|
|
||||||
return a.offset < b.offset+b.width && b.offset < a.offset+a.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const field of this.fields) {
|
|
||||||
for (const otherField of this.fields) {
|
|
||||||
if (field != otherField && isOverlapping(field, otherField)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
names () {
|
|
||||||
return this.fieldsAscending.map(f => f.name);
|
|
||||||
},
|
|
||||||
|
|
||||||
types () {
|
|
||||||
return this.fieldsAscending.map(f => f.type);
|
|
||||||
},
|
|
||||||
|
|
||||||
widths () {
|
|
||||||
if (this.overlappingFields) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const fields = this.fieldsAscending;
|
|
||||||
const w = [ ];
|
|
||||||
|
|
||||||
if (fields[0].offset) {
|
|
||||||
w.push(-fields[0].offset);
|
|
||||||
}
|
|
||||||
w.push(fields[0].width);
|
|
||||||
|
|
||||||
for (let idx=1; idx<fields.length; idx++) {
|
|
||||||
const f0 = fields[idx-1];
|
|
||||||
const f1 = fields[idx];
|
|
||||||
const gap = f0.offset+f0.width-f1.offset;
|
|
||||||
|
|
||||||
if (gap) {
|
|
||||||
w.push(gap);
|
|
||||||
}
|
|
||||||
w.push(f1.width);
|
|
||||||
}
|
|
||||||
return w;
|
|
||||||
},
|
|
||||||
|
|
||||||
fields () {
|
|
||||||
return [ this.line, this.point, this.easting, this.northing ];
|
|
||||||
},
|
|
||||||
|
|
||||||
fieldsAscending () {
|
|
||||||
return Object.values(this.fields).sort( (a, b) => a.offset - b.offset );
|
|
||||||
},
|
|
||||||
|
|
||||||
fieldsDescending () {
|
|
||||||
return Object.values(this.fields).sort( (a, b) => b.offset - a.offset );
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
reset () {
|
|
||||||
if (!this.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldFor = (index) => {
|
|
||||||
return this.fields.find(field => field.name == this.value.names[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset=0;
|
|
||||||
let index=0;
|
|
||||||
|
|
||||||
for (const width of this.value.widths) {
|
|
||||||
if (width < 0) {
|
|
||||||
offset -= width;
|
|
||||||
} else {
|
|
||||||
fieldFor(index).offset = offset;
|
|
||||||
fieldFor(index).width = width;
|
|
||||||
offset += width;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
save () {
|
|
||||||
this.$emit('input', {...this.$data.values});
|
|
||||||
},
|
|
||||||
|
|
||||||
back () {
|
|
||||||
this.$emit('close');
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted () {
|
|
||||||
this.reset();
|
|
||||||
document.addEventListener("selectionchange", this.handleSelection);
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeUnmount () {
|
|
||||||
document.removeEventListener("selectionchange", this.handleSelection);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
Reference in New Issue
Block a user