Change config view

Change-Id: I43d4d941dcc7513473e9edf5e59bf64c8f0d8106
diff --git a/cmd/koralmapper/main.go b/cmd/koralmapper/main.go
index e456537..9b9b95d 100644
--- a/cmd/koralmapper/main.go
+++ b/cmd/koralmapper/main.go
@@ -79,11 +79,26 @@
 	LayerB   string
 }
 
+// MappingSectionData contains per-section UI metadata so request and response
+// rows can be rendered from one shared template block.
+type MappingSectionData struct {
+	Title           string
+	Mode            string
+	CheckboxClass   string
+	CheckboxName    string
+	FieldsClass     string
+	ArrowClass      string
+	ArrowDirection  string
+	ArrowLabel      string
+	AnnotationLabel string
+}
+
 // ConfigPageData holds all data passed to the configuration page template.
 type ConfigPageData struct {
 	BasePageData
 	AnnotationMappings []config.MappingList
 	CorpusMappings     []config.MappingList
+	MappingSections    []MappingSectionData
 }
 
 func parseConfig() *appConfig {
@@ -362,6 +377,32 @@
 			data.AnnotationMappings = append(data.AnnotationMappings, normalized)
 		}
 	}
+
+	data.MappingSections = []MappingSectionData{
+		{
+			Title:           "Request",
+			Mode:            "request",
+			CheckboxClass:   "request-cb",
+			CheckboxName:    "request",
+			FieldsClass:     "request-fields",
+			ArrowClass:      "request-dir-arrow",
+			ArrowDirection:  "atob",
+			ArrowLabel:      "\u2192",
+			AnnotationLabel: "(query)",
+		},
+		{
+			Title:           "Response",
+			Mode:            "response",
+			CheckboxClass:   "response-cb",
+			CheckboxName:    "response",
+			FieldsClass:     "response-fields",
+			ArrowClass:      "response-dir-arrow",
+			ArrowDirection:  "btoa",
+			ArrowLabel:      "\u2190",
+			AnnotationLabel: "(query)",
+		},
+	}
+
 	return data
 }
 
diff --git a/cmd/koralmapper/main_test.go b/cmd/koralmapper/main_test.go
index 067dd4c..ca287f0 100644
--- a/cmd/koralmapper/main_test.go
+++ b/cmd/koralmapper/main_test.go
@@ -1938,11 +1938,12 @@
 	assert.Contains(t, htmlContent, `/static/config.js`)
 
 	// Request/response sections
-	assert.Contains(t, htmlContent, "<h2>Request</h2>")
-	assert.Contains(t, htmlContent, "<h2>Response</h2>")
+	assert.Contains(t, htmlContent, "<legend>Request</legend>")
+	assert.Contains(t, htmlContent, "<legend>Response</legend>")
 
 	// Annotation mapping entries
-	assert.Contains(t, htmlContent, "(query) anno-mapper")
+	assert.Contains(t, htmlContent, "(query)")
+	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"`)
@@ -2008,8 +2009,8 @@
 	assert.Contains(t, htmlContent, `data-dir="btoa"`)
 
 	// Request and response checkboxes
-	assert.Contains(t, htmlContent, `class="request-cb"`)
-	assert.Contains(t, htmlContent, `class="response-cb"`)
+	assert.Contains(t, htmlContent, `class="checkbox request-cb"`)
+	assert.Contains(t, htmlContent, `class="checkbox response-cb"`)
 }
 
 func TestConfigPageCorpusMappingHasNoFoundryInputs(t *testing.T) {
diff --git a/cmd/koralmapper/static/config.html b/cmd/koralmapper/static/config.html
index 3eda7ee..b98254d 100644
--- a/cmd/koralmapper/static/config.html
+++ b/cmd/koralmapper/static/config.html
@@ -10,60 +10,38 @@
 </head>
 <body>
     <div class="container" data-service-url="{{.ServiceURL}}" data-cookie-name="{{.CookieName}}">
-        <h1>{{.Title}}</h1>
-        <section>
-            <p>{{.Description}}</p>
-        </section>
+        <h1>{{.Title}} - <span style="font-size: 0.8em;">{{.Description}}</span></h1>
 
+        {{range .MappingSections}}
         <section class="mapping-section">
-            <h2>Request</h2>
-            {{range .AnnotationMappings}}
-            <div class="mapping" data-id="{{.ID}}" data-type="annotation" data-mode="request"
+        <fieldset>
+            <legend>{{.Title}}</legend>
+            {{$section := .}}
+            {{range $.AnnotationMappings}}
+            <div class="mapping" data-id="{{.ID}}" data-type="annotation" data-mode="{{$section.Mode}}"
                  data-default-foundry-a="{{.FoundryA}}" data-default-layer-a="{{.LayerA}}"
                  data-default-foundry-b="{{.FoundryB}}" data-default-layer-b="{{.LayerB}}">
                 <div class="mapping-row">
-                    <label><input type="checkbox" class="request-cb" name="request"> (query) {{.ID}}</label>
-                    <div class="mapping-fields request-fields">
-                        <input type="text" class="request-foundryA" value="{{.FoundryA}}" placeholder="{{.FoundryA}}" size="8">/<input type="text" class="request-layerA" value="{{.LayerA}}" placeholder="{{.LayerA}}" size="4">
-                        <button type="button" class="request-dir-arrow" data-dir="atob">&rarr;</button>
-                        <input type="text" class="request-foundryB" value="{{.FoundryB}}" placeholder="{{.FoundryB}}" size="8">/<input type="text" class="request-layerB" value="{{.LayerB}}" placeholder="{{.LayerB}}" size="4">
+                    <input type="checkbox" id="check-{{.ID}}-{{$section.Mode}}" class="checkbox {{$section.CheckboxClass}}" name="{{$section.CheckboxName}}">
+                    <label for="check-{{.ID}}-{{$section.Mode}}"><span></span>{{$section.AnnotationLabel}} <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">
+                        <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">
                     </div>
                 </div>
             </div>
             {{end}}
-            {{range .CorpusMappings}}
-            <div class="mapping" data-id="{{.ID}}" data-type="corpus" data-mode="request">
+            {{range $.CorpusMappings}}
+            <div class="mapping" data-id="{{.ID}}" data-type="corpus" data-mode="{{$section.Mode}}">
                 <div class="mapping-row">
-                    <label><input type="checkbox" class="request-cb" name="request"> (corpus) {{.ID}}</label>
+                    <label><input type="checkbox" class="{{$section.CheckboxClass}}" name="{{$section.CheckboxName}}"> (corpus) {{.ID}}</label>
                 </div>
             </div>
             {{end}}
+        </fieldset>
         </section>
-
-        <section class="mapping-section">
-            <h2>Response</h2>
-            {{range .AnnotationMappings}}
-            <div class="mapping" data-id="{{.ID}}" data-type="annotation" data-mode="response"
-                 data-default-foundry-a="{{.FoundryA}}" data-default-layer-a="{{.LayerA}}"
-                 data-default-foundry-b="{{.FoundryB}}" data-default-layer-b="{{.LayerB}}">
-                <div class="mapping-row">
-                    <label><input type="checkbox" class="response-cb" name="response"> (query) {{.ID}}</label>
-                    <div class="mapping-fields response-fields">
-                        <input type="text" class="response-foundryA" value="{{.FoundryA}}" placeholder="{{.FoundryA}}" size="8">/<input type="text" class="response-layerA" value="{{.LayerA}}" placeholder="{{.LayerA}}" size="4">
-                        <button type="button" class="response-dir-arrow" data-dir="btoa">&larr;</button>
-                        <input type="text" class="response-foundryB" value="{{.FoundryB}}" placeholder="{{.FoundryB}}" size="8">/<input type="text" class="response-layerB" value="{{.LayerB}}" placeholder="{{.LayerB}}" size="4">
-                    </div>
-                </div>
-            </div>
-            {{end}}
-            {{range .CorpusMappings}}
-            <div class="mapping" data-id="{{.ID}}" data-type="corpus" data-mode="response">
-                <div class="mapping-row">
-                    <label><input type="checkbox" class="response-cb" name="response"> (corpus) {{.ID}}</label>
-                </div>
-            </div>
-            {{end}}
-        </section>
+        {{end}}
 
         <section>
           <h2>Available Mappings</h2>
@@ -80,11 +58,13 @@
         </section>
 
         <section class="mapping-section">
-            <label class="cfg-line-label" for="request-cfg-preview">Request:</label>
+            <fieldset style="font-size: 0.8em;">
+                <label class="cfg-line-label" for="request-cfg-preview">Request:</label>
             <input type="text" id="request-cfg-preview" class="cfg-preview request-cfg-preview" readonly value="">
             <label class="cfg-line-label" for="response-cfg-preview">Response:</label>
-            <input type="text" id="response-cfg-preview" class="cfg-preview response-cfg-preview" readonly value="">
-        </section>
+            <input type="text" id="response-cfg-preview" class="cfg-preview response-cfg-preview" readonly="readonly" value="">
+        </fieldset>
+    </section>
 
         <footer class="version">
             <p><tt>v{{.Version}} - {{.Date}} {{.Hash}}</tt></p>