Add reset and styling
Change-Id: I5f18d0c07c4da3ee03dccb5c06b9510a0fc62772
diff --git a/.dockerignore b/.dockerignore
index 52f7ccb..a1cf873 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -12,6 +12,8 @@
!mapper/
!matcher/
!mappings/
+!testdata/
+testdata/sandbox
!/LICENSE
!.dockerignore
!go.mod
diff --git a/cmd/koralmapper/main_test.go b/cmd/koralmapper/main_test.go
index b4dfc02..c53cd54 100644
--- a/cmd/koralmapper/main_test.go
+++ b/cmd/koralmapper/main_test.go
@@ -1948,8 +1948,8 @@
assert.Contains(t, htmlContent, "anno-mapper")
assert.Contains(t, htmlContent, `data-id="anno-mapper"`)
assert.Contains(t, htmlContent, `data-type="annotation"`)
- assert.Contains(t, htmlContent, `value="opennlp"`)
- assert.Contains(t, htmlContent, `value="upos"`)
+ assert.Contains(t, htmlContent, `placeholder="opennlp"`)
+ assert.Contains(t, htmlContent, `placeholder="upos"`)
assert.Contains(t, htmlContent, "Annotation mapping")
// Corpus mapping entries
@@ -1964,6 +1964,10 @@
assert.Contains(t, htmlContent, `class="response-fieldA"`)
assert.Contains(t, htmlContent, `class="response-fieldB"`)
assert.Contains(t, htmlContent, "Corpus mapping")
+
+ // Reset button
+ assert.Contains(t, htmlContent, `id="reset-btn"`)
+ assert.Contains(t, htmlContent, "Reset all")
}
func TestConfigPageAnnotationMappingHasFoundryInputs(t *testing.T) {
@@ -2068,8 +2072,8 @@
assert.Contains(t, htmlContent, `class="request-fieldB"`)
assert.Contains(t, htmlContent, `class="response-fieldA"`)
assert.Contains(t, htmlContent, `class="response-fieldB"`)
- assert.Contains(t, htmlContent, `value="genre"`)
- assert.Contains(t, htmlContent, `value="topic"`)
+ assert.Contains(t, htmlContent, `placeholder="genre"`)
+ assert.Contains(t, htmlContent, `placeholder="topic"`)
}
func TestConfigPageBackwardCompatibility(t *testing.T) {
@@ -2169,6 +2173,89 @@
assert.Equal(t, "First corpus", data.CorpusMappings[0].Description)
}
+func TestConfigPageDefaultsAsPlaceholdersOnly(t *testing.T) {
+ lists := []tmconfig.MappingList{
+ {
+ ID: "anno-mapper",
+ FoundryA: "opennlp",
+ LayerA: "p",
+ FoundryB: "upos",
+ LayerB: "pos",
+ Mappings: []tmconfig.MappingRule{"[A] <> [B]"},
+ },
+ {
+ ID: "corpus-mapper",
+ Type: "corpus",
+ FieldA: "genre",
+ FieldB: "topic",
+ Mappings: []tmconfig.MappingRule{
+ "textClass=science <> textClass=akademisch",
+ },
+ },
+ }
+ m, err := mapper.NewMapper(lists)
+ require.NoError(t, err)
+
+ mockConfig := &tmconfig.MappingConfig{Lists: lists}
+ tmconfig.ApplyDefaults(mockConfig)
+
+ app := fiber.New()
+ setupRoutes(app, m, mockConfig)
+
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ resp, err := app.Test(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ require.NoError(t, err)
+ htmlContent := string(body)
+
+ // Placeholders are present
+ assert.Contains(t, htmlContent, `placeholder="opennlp"`)
+ assert.Contains(t, htmlContent, `placeholder="upos"`)
+ assert.Contains(t, htmlContent, `placeholder="genre"`)
+ assert.Contains(t, htmlContent, `placeholder="topic"`)
+
+ // Value attributes must NOT appear on mapping inputs (defaults shown as
+ // placeholders only). We check that the combined string value="opennlp"
+ // etc. is absent.
+ assert.NotContains(t, htmlContent, `value="opennlp"`)
+ assert.NotContains(t, htmlContent, `value="upos"`)
+ assert.NotContains(t, htmlContent, `value="genre"`)
+ assert.NotContains(t, htmlContent, `value="topic"`)
+}
+
+func TestConfigPageHasResetButton(t *testing.T) {
+ lists := []tmconfig.MappingList{
+ {
+ ID: "test-mapper",
+ Mappings: []tmconfig.MappingRule{"[A] <> [B]"},
+ },
+ }
+ m, err := mapper.NewMapper(lists)
+ require.NoError(t, err)
+
+ mockConfig := &tmconfig.MappingConfig{Lists: lists}
+ tmconfig.ApplyDefaults(mockConfig)
+
+ app := fiber.New()
+ setupRoutes(app, m, mockConfig)
+
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ resp, err := app.Test(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ require.NoError(t, err)
+ htmlContent := string(body)
+
+ assert.Contains(t, htmlContent, `id="reset-btn"`)
+ assert.Contains(t, htmlContent, "Reset all")
+ assert.Contains(t, htmlContent, `type="button"`)
+}
+
func TestConfigPagePreservesOrderOfMappings(t *testing.T) {
lists := []tmconfig.MappingList{
{
diff --git a/cmd/koralmapper/static/config.html b/cmd/koralmapper/static/config.html
index ef682f5..ac22063 100644
--- a/cmd/koralmapper/static/config.html
+++ b/cmd/koralmapper/static/config.html
@@ -25,9 +25,9 @@
<input type="checkbox" id="check-{{.ID}}-{{$section.Mode}}" class="checkbox {{$section.CheckboxClass}}" name="{{$section.CheckboxName}}">
<label for="check-{{.ID}}-{{$section.Mode}}"><span></span><span class="data-mode">query</span> <strong>{{.ID}}</strong></label>
<div class="mapping-fields {{$section.FieldsClass}}">
- <input type="text" class="{{$section.Mode}}-foundryA" value="{{.FoundryA}}" placeholder="{{.FoundryA}}" size="8">/<input type="text" class="{{$section.Mode}}-layerA" value="{{.LayerA}}" placeholder="{{.LayerA}}" size="4">
+ <input type="text" class="{{$section.Mode}}-foundryA" placeholder="{{.FoundryA}}" size="8">/<input type="text" class="{{$section.Mode}}-layerA" placeholder="{{.LayerA}}" size="4">
<button type="button" class="{{$section.ArrowClass}}" data-dir="{{$section.ArrowDirection}}">{{$section.ArrowLabel}}</button>
- <input type="text" class="{{$section.Mode}}-foundryB" value="{{.FoundryB}}" placeholder="{{.FoundryB}}" size="8">/<input type="text" class="{{$section.Mode}}-layerB" value="{{.LayerB}}" placeholder="{{.LayerB}}" size="4">
+ <input type="text" class="{{$section.Mode}}-foundryB" placeholder="{{.FoundryB}}" size="8">/<input type="text" class="{{$section.Mode}}-layerB" placeholder="{{.LayerB}}" size="4">
</div>
</div>
</div>
@@ -39,9 +39,9 @@
<input type="checkbox" id="check-{{.ID}}-{{$section.Mode}}" class="checkbox {{$section.CheckboxClass}}" name="{{$section.CheckboxName}}">
<label for="check-{{.ID}}-{{$section.Mode}}"><span></span><span class="data-mode">corpus</span> <strong>{{.ID}}</strong></label>
<div class="mapping-fields {{$section.FieldsClass}}">
- <input type="text" class="{{$section.Mode}}-fieldA" value="{{.FieldA}}" placeholder="{{.FieldA}}" size="10">
+ <input type="text" class="{{$section.Mode}}-fieldA" placeholder="{{.FieldA}}" size="10">
<button type="button" class="{{$section.ArrowClass}}" data-dir="{{$section.ArrowDirection}}">{{$section.ArrowLabel}}</button>
- <input type="text" class="{{$section.Mode}}-fieldB" value="{{.FieldB}}" placeholder="{{.FieldB}}" size="10">
+ <input type="text" class="{{$section.Mode}}-fieldB" placeholder="{{.FieldB}}" size="10">
</div>
</div>
</div>
@@ -50,7 +50,7 @@
</section>
{{end}}
- <section>
+ <section id="mapping-info">
<dl>
{{range .AnnotationMappings}}
<dt>{{.ID}}:</dt>
@@ -61,6 +61,8 @@
{{if .Description}}<dd>{{.Description}}</dd>{{end}}
{{end}}
</dl>
+
+ <button type="button" id="reset-btn">Reset all</button>
</section>
<section class="mapping-section" id="pipe-info">
@@ -73,7 +75,7 @@
</section>
<footer class="version">
- <p><tt>v{{.Version}} - {{.Date}} {{.Hash}}</tt></p>
+ <p><tt>{{.Version}} - {{.Date}} {{.Hash}}</tt></p>
</footer>
</div>
<script src="/static/config.js"></script>
diff --git a/cmd/koralmapper/static/config.js b/cmd/koralmapper/static/config.js
index a98aa61..9f6a92c 100644
--- a/cmd/koralmapper/static/config.js
+++ b/cmd/koralmapper/static/config.js
@@ -38,6 +38,10 @@
document.cookie = cookieName + "=" + value + "; path=/; SameSite=Lax; max-age=31536000";
}
+ function deleteCookie() {
+ document.cookie = cookieName + "=; path=/; SameSite=Lax; max-age=0";
+ }
+
// Form state
function reverseDir(dir) {
@@ -320,5 +324,36 @@
})(arrows[i]);
}
+ // Reset button
+
+ function resetForm() {
+ for (var i = 0; i < checkboxes.length; i++) {
+ checkboxes[i].checked = false;
+ }
+
+ for (var i = 0; i < textInputs.length; i++) {
+ textInputs[i].value = "";
+ }
+
+ var requestArrows = container.querySelectorAll(".request-dir-arrow");
+ for (var i = 0; i < requestArrows.length; i++) {
+ requestArrows[i].dataset.dir = "atob";
+ requestArrows[i].textContent = "\u2192";
+ }
+ var responseArrows = container.querySelectorAll(".response-dir-arrow");
+ for (var i = 0; i < responseArrows.length; i++) {
+ responseArrows[i].dataset.dir = "btoa";
+ responseArrows[i].textContent = "\u2190";
+ }
+
+ deleteCookie();
+ registerPipes();
+ }
+
+ var resetBtn = container.querySelector("#reset-btn");
+ if (resetBtn) {
+ resetBtn.addEventListener("click", resetForm);
+ }
+
registerPipes();
})();
diff --git a/cmd/koralmapper/static/style.css b/cmd/koralmapper/static/style.css
index 4fd7230..d639d93 100644
--- a/cmd/koralmapper/static/style.css
+++ b/cmd/koralmapper/static/style.css
@@ -17,14 +17,30 @@
margin:.2em 0;
}
+#mapping-info {
+ display: flex;
+ flex-direction: row;
+ align-content: space-between;
+ align-items:flex-start;
+}
+
+#mapping-info > dl {
+ width: 100%;
+}
+
+#mapping-info > button {
+ white-space: nowrap
+}
+
.data-mode {
display: inline-block;
font-size:70%;
+ padding: .2em 0;
width: 6em;
text-align: center;
- border: 1px solid grey;
- background-color: #ffcc55;
- border-radius: 5px;
+ color: var(--nearly-white-color);
+ background-color: var(--light-green-color);
+ border-radius: 1em;
}
.mapping-fields > * {
@@ -38,7 +54,42 @@
.mapping-fields input[type="text"] { font-family: monospace; }
.request-fields, .response-fields { flex-wrap: wrap; }
-.request-dir-arrow, .response-dir-arrow { cursor: pointer; border: 1px solid #bbb; background: #f8f8f8; border-radius: 0.25rem; min-width: 2rem; }
.cfg-line-label { display: block; margin: 0.35rem 0 0.1rem; }
.cfg-preview { width: 100%; box-sizing: border-box; font-family: monospace; }
-footer { background-color: #aaa; font-size:50%; text-align: right; padding: 0.25rem; }
\ No newline at end of file
+
+.request-dir-arrow,
+.response-dir-arrow,
+#reset-btn {
+ cursor: pointer;
+ border-radius: 0.25rem;
+ border: 1px solid;
+ color:var(--dark-grey-color);
+ background-color:var(--light-grey-color);
+ border-color:var(--dark-grey-color);
+ text-shadow:1px 1px hsla(0,0%,100%,.5);
+}
+.request-dir-arrow:hover,
+.response-dir-arrow:hover,
+#reset-btn:hover {
+ color:var(--nearly-white-color);
+ background-color:var(--dark-orange-color);
+ border-color: var(--darkest-orange-color);
+ text-shadow:none
+}
+
+.request-dir-arrow,
+.response-dir-arrow {
+ min-width: 2rem;
+}
+#reset-btn {
+ float:right;
+ padding: 0.3rem 1rem;
+}
+
+footer {
+ background-color: var(--light-grey-color);
+ font-size:60%;
+ text-align: right;
+ padding: 0.25rem;
+ padding-right:1em;
+}
\ No newline at end of file