Skip to content
Snippets Groups Projects
Commit c42a7618 authored by jensilo's avatar jensilo
Browse files

eiffel parser ui foundation

parent 0d0a09f1
No related branches found
No related tags found
No related merge requests found
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-three-dots-vertical" viewBox="0 0 16 16">
<path d="M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0"/>
</svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/>
</svg>
\ No newline at end of file
......@@ -72,6 +72,14 @@ type BasicRule struct {
// Optional means a rule is not required to be parsed without template.ParsingLogLevelError (parsing error).
// By that parsing an invalid requirement for an optional rule will not result in a parsing error.
Optional bool `json:"optional"`
// Size is the expected size of the rule's value. It is optional. Possible values are:
// - "small" (default): The value is expected to be a short string. 1/4 of the input field width.
// - "medium": The value is expected to be a medium-sized string. 2/4 of the input field width.
// - "large": The value is expected to be a large string. 3/4 of the input field width.
// - "full": The value is expected to be a very large string. 4/4 of the input field (this will be a textarea) width.
// For now this should not be overcomplicated.
// TODO add adaptive sizing => make more convenient for the user
Size string `json:"size"`
// Extra is an optional map of additional data that can be used by the rule parser.
Extra map[string]any `json:"extra"`
}
......
......
......@@ -3,16 +3,28 @@ package eiffel
import (
"encoding/json"
"github.com/org-harmony/harmony/src/app/template"
"github.com/org-harmony/harmony/src/app/user"
"github.com/org-harmony/harmony/src/core/event"
"github.com/org-harmony/harmony/src/core/hctx"
"github.com/org-harmony/harmony/src/core/validation"
"github.com/org-harmony/harmony/src/core/web"
"net/http"
"strings"
)
func RegisterController(appCtx *hctx.AppCtx, webCtx *web.Ctx) {
// TODO move this to module init when module manager is implemented (see subscribeEvents)
subscribeEvents(appCtx)
registerNavigation(appCtx, webCtx)
router := webCtx.Router.With(user.LoggedInMiddleware(appCtx))
router.Get("/eiffel", eiffelElicitationPage(appCtx, webCtx).ServeHTTP)
router.Get("/eiffel/elicitation/templates/search/modal", searchModal(appCtx, webCtx).ServeHTTP)
router.Get("/eiffel/elicitation/{templateID}/{variant}", elicitationTemplate(appCtx, webCtx).ServeHTTP)
router.Post("/eiffel/elicitation/{templateID}/{variant}", parseRequirement(appCtx, webCtx).ServeHTTP)
router.Get("/eiffel/elicitation/output/file/form", outputFileForm(appCtx, webCtx).ServeHTTP)
}
func subscribeEvents(appCtx *hctx.AppCtx) {
......@@ -48,3 +60,56 @@ func subscribeEvents(appCtx *hctx.AppCtx) {
return nil
}, event.DefaultEventPriority)
}
func registerNavigation(appCtx *hctx.AppCtx, webCtx *web.Ctx) {
webCtx.Navigation.Add("eiffel.elicitation", web.NavItem{
URL: "/eiffel",
Name: "harmony.menu.eiffel",
Display: func(io web.IO) (bool, error) {
return true, nil
},
Position: 100,
})
}
func eiffelElicitationPage(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
return web.NewController(appCtx, webCtx, func(io web.IO) error {
return io.Render(
nil,
"eiffel.elicitation.page",
"eiffel/elicitation-page.go.html",
"eiffel/elicitation-template.go.html",
"eiffel/_form-elicitation.go.html",
"eiffel/_form-output-file.go.html",
)
})
}
func searchModal(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
return web.NewController(appCtx, webCtx, func(io web.IO) error {
return io.Render(nil, "eiffel.template.search.modal", "eiffel/_modal-template-search.go.html")
})
}
func elicitationTemplate(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
return web.NewController(appCtx, webCtx, func(io web.IO) error {
return io.Render(
nil,
"eiffel.elicitation.template",
"eiffel/elicitation-template.go.html",
"eiffel/_form-elicitation.go.html",
)
})
}
func parseRequirement(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
return web.NewController(appCtx, webCtx, func(io web.IO) error {
return io.Render(nil, "eiffel.elicitation.form", "eiffel/_form-elicitation.go.html")
})
}
func outputFileForm(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
return web.NewController(appCtx, webCtx, func(io web.IO) error {
return io.Render(nil, "eiffel.elicitation.output-file.form", "eiffel/_form-output-file.go.html")
})
}
......@@ -46,10 +46,10 @@ func RegisterController(appCtx *hctx.AppCtx, webCtx *web.Ctx) {
}
func registerNavigation(appCtx *hctx.AppCtx, webCtx *web.Ctx) {
webCtx.Navigation.Add("web", web.NavItem{
webCtx.Navigation.Add("template.set.list", web.NavItem{
URL: "/template-set/list",
Name: "harmony.menu.template-sets",
Position: 100,
Position: 150,
})
}
......
......
{{ define "eiffel.elicitation.form" }}
<h4>{{ t "eiffel.elicitation.template.form.title" }}</h4>
<form
hx-post="/eiffel/elicitation/{id}"
hx-target="#eiffelElicitationForm"
hx-disabled-elt=".eiffel-elicitation-form-fieldset"
hx-swap="outerHTML"
hx-trigger="submit"
id="eiffelElicitationForm"
>
<fieldset class="eiffel-elicitation-form-fieldset">
<input name="test" placeholder="test" />
</fieldset>
</form>
{{ end }}
\ No newline at end of file
{{ define "eiffel.elicitation.output-file.form" }}
output file: requirements here
{{ end }}
\ No newline at end of file
{{ define "eiffel.template.search.modal" }}
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="eiffelTemplateSearchLabel">{{ t "eiffel.elicitation.template.search.title" }}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ t "harmony.generic.close" }}"></button>
</div>
<div class="modal-body">
\here goes the search form\
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t "harmony.generic.close" }}</button>
</div>
</div>
</div>
{{ end }}
\ No newline at end of file
{{ define "eiffel.elicitation.page" }}
{{ template "index" . }}
{{ end }}
{{ block "content-container" . }}
<section class="section content-section mt-3">
<div class="content-container container content">
{{ template "content" . }}
</div>
</section>
{{ end }}
{{ define "content" }}
{{ template "eiffel.elicitation" . }}
{{ end }}
{{ define "eiffel.elicitation" }}
<div class="row">
<div class="col-8 eiffel-elicitation">
<div class="eiffel-elicitation-template">
<div class="eiffel-elicitation-template-search-wrapper bg-light rounded p-3 row w-100 m-auto border border-light-subtle">
<div class="eiffel-elicitation-template-search col-3">
<button
hx-get="/eiffel/elicitation/templates/search/modal"
hx-target="#eiffelTemplateSearch"
data-bs-toggle="modal"
data-bs-target="#eiffelTemplateSearch"
class="btn w-100 h-100 p-0"
>
<img class="m-auto align-baseline w-25 d-block" src="{{ asset "icons/search.svg" }}" alt="{{ t "harmony.generic.search" }}">
<span class="badge shadow rounded-pill text-bg-secondary mt-3">{{ t "eiffel.elicitation.template.search.shortcut" }}</span>
</button>
<div
id="eiffelTemplateSearch"
class="modal fade"
tabindex="-1"
aria-labelledby="eiffelTemplateSearchLabel">
<div class="modal-dialog" role="document">
<div class="modal-content"></div>
</div>
</div>
</div>
<div class="eiffel-elicitation-template-current col">
Aktuelle Schablone <span class="eiffel-elicitation-template-current-name"><b>ESFA</b></span>
<br/>
<span class="eiffel-elicitation-template-current-description fst-italic">
PARIS Schablone für funktionale Anforderungen. Die Schablone folgt...
</span>
</div>
{{/*<div class="eiffel-elicitation-template-options col-1">
<button class="btn p-0 w-100 h-100">
<img class="align-baseline" src="{{ asset "icons/dots-vertical.svg" }}" alt="{{ t "harmony.generic.options" }}">
</button>
</div>*/}}
</div>
{{ template "eiffel.elicitation.template" . }}
</div>
</div>
<div class="col eiffel-requirements">
<p>Requirements here</p>
</div>
</div>
{{ end }}
\ No newline at end of file
{{ define "eiffel.elicitation.template" }}
<div class="accordion mt-3 eiffel-elicitation-template-info" id="eiffelTemplateInfoAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="headingOne">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
{{ t "eiffel.elicitation.template.construction" }}
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#eiffelTemplateInfoAccordion">
<div class="accordion-body">
Hier stehen mehr Informationen zur Schablone. Lorem Ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, nisl quis aliquam ultricies, nunc nisl aliquet nunc, sit amet aliquam nisl nunc sit amet nisl. Sed euismod, nisl quis aliquam ultricies, nunc nisl aliquet nunc, sit amet aliquam nisl nunc sit amet nisl. Sed euismod, nisl quis aliquam ultricies, nunc nisl aliquet nunc, sit amet aliquam nisl nunc sit amet nisl. Sed euismod, nisl quis aliquam ultricies, nunc nisl aliquet nunc, sit amet aliquam nisl nunc sit amet nisl.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingTwo">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
{{ t "eiffel.elicitation.template.example" }}
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#eiffelTemplateInfoAccordion">
<div class="accordion-body">
Hier stehen mehr Informationen zur Schablone. Lorem Ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, nisl quis aliquam ultricies, nunc nisl aliquet nunc, sit amet aliquam nisl nunc sit amet nisl. Sed euismod, nisl quis aliquam ultricies, nunc nisl aliquet nunc, sit amet aliquam nisl nunc sit amet nisl. Sed euismod, nisl quis aliquam ultricies, nunc nisl aliquet nunc, sit amet aliquam nisl nunc sit amet nisl. Sed euismod, nisl quis aliquam ultricies, nunc nisl aliquet nunc, sit amet aliquam nisl nunc sit amet nisl.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingThree">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
{{ t "eiffel.elicitation.template.settings" }}
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#eiffelTemplateInfoAccordion">
<div class="accordion-body">
Hier ein paar kleinere Einstellungen.
</div>
</div>
</div>
</div>
{{/*
Hier muss von Anfang eine Variante als selektiert reingegeben werden. Bei initialem Laden einfach die 1. Variante nehmen.
Ebenfalls beim Suchen und Auswählen der Schablone. (Gleichzusetzen mit Neuladen der Seite.)
Beim Nachladen/Verändern der Variante wird hier die gewählte Variante übergeben und vorausgewählt.
Klicken der Variante wechselt damit die Variante und erzeugt einen HTMX Request, der die Variante wechselt.
Damit wird auch die URL für das Formular verändert, diese enthält Schablone und Variante.
Beim Abschicken und Validieren des Forms (Parsing) muss die Variante übergeben werden und im Gegenzug wird lediglich das Formular
zurückgegeben. Die aktuell ausgewählte Schablone und Variante sollten mit übergeben werden (über die URL).
Die ausgewählte Datei sollte über einen Input außerhalb des Formulars übergeben werden. (Hier kann das Input-Attribut "form" verwendet werden.)
*/}}
<div class="eiffel-elicitation-template-variant mt-5 bg-light rounded p-3 w-100 m-auto border border-light-subtle">
<div class="row">
<div class="col">
<input type="radio" class="btn-check" name="options-base" id="option1" autocomplete="off"/>
<label class="btn" for="option1">Variante 1</label>
<input type="radio" class="btn-check" name="options-base" id="option2" autocomplete="off"/>
<label class="btn" for="option2">Variante 2</label>
<input type="radio" class="btn-check" name="options-base" id="option3" autocomplete="off" checked/>
<label class="btn" for="option3">Variante 3</label>
</div>
</div>
<div class="row mt-2">
<div class="col d-flex justify-content-between">
<span class="badge shadow rounded-pill text-bg-secondary">{{ t "eiffel.elicitation.template.variant.left.shortcut" }}</span>
<span class="badge shadow rounded-pill text-bg-secondary">{{ t "eiffel.elicitation.template.variant.right.shortcut" }}</span>
</div>
</div>
</div>
<div class="eiffel-elicitation-template-variant-form mt-3 w-100">
{{ template "eiffel.elicitation.form" . }}
</div>
{{ end }}
\ No newline at end of file
......@@ -38,7 +38,7 @@
<td>
{{/* edit button + modal */}}
<span hx-get="/template-set/edit/{{ .ID }}" hx-target="#edit-form-for-{{ .ID }}" hx-swap="outerHTML" data-bs-toggle="modal" data-bs-target="#edit-modal-for-{{ .ID }}" class="edit-icon mx-2" role="button">
<img src="{{ asset "icons/edit.svg" }}" alt="{{ "template.set.action.edit" | t }}" title="{{ "template.set.action.edit" | t }}" />
<img src="{{ asset "icons/edit.svg" }}" alt="{{ "template.set.action.edit" | t }}" title="{{ "template.set.action.edit" | t }}" class="align-baseline" />
</span>
<div class="modal fade" id="edit-modal-for-{{ .ID }}" tabindex="-1" role="dialog" aria-labelledby="edit-modal-for-{{ .ID }}-label" aria-hidden="true">
<div class="modal-dialog" role="document">
......@@ -62,7 +62,7 @@
{{/* delete button + modal */}}
<span data-bs-toggle="modal" data-bs-target="#delete-modal-for-{{ .ID }}" class="delete-icon" role="button">
<img src="{{ asset "icons/x.svg" }}" alt="{{ "template.set.action.delete" | t }}" title="{{ "template.set.action.delete" | t }}" />
<img src="{{ asset "icons/x.svg" }}" alt="{{ "template.set.action.delete" | t }}" title="{{ "template.set.action.delete" | t }}" class="align-baseline" />
</span>
<div class="modal fade" id="delete-modal-for-{{ .ID }}" tabindex="-1" role="dialog" aria-labelledby="delete-modal-for-{{ .ID }}-label" aria-hidden="true">
<div class="modal-dialog" role="document">
......
......
......@@ -80,12 +80,12 @@
{{ end }}
<td>
<a hx-boost="true" href="/template/{{ .ID }}/edit" hx-target="body" class="edit-icon mx-2 text-decoration-none" role="button">
<img src="{{ asset "icons/edit.svg" }}" alt="{{ "template.set.action.edit" | t }}" title="{{ "template.set.action.edit" | t }}" />
<img src="{{ asset "icons/edit.svg" }}" alt="{{ "template.set.action.edit" | t }}" title="{{ "template.set.action.edit" | t }}" class="align-baseline" />
</a>
{{/* delete button + modal */}}
<span data-bs-toggle="modal" data-bs-target="#delete-modal-for-{{ .ID }}" class="delete-icon" role="button">
<img src="{{ asset "icons/x.svg" }}" alt="{{ "template.action.delete" | t }}" title="{{ "template.action.delete" | t }}" />
<img src="{{ asset "icons/x.svg" }}" alt="{{ "template.action.delete" | t }}" title="{{ "template.action.delete" | t }}" class="align-baseline" />
</span>
<div class="modal fade" id="delete-modal-for-{{ .ID }}" tabindex="-1" role="dialog" aria-labelledby="delete-modal-for-{{ .ID }}-label" aria-hidden="true">
<div class="modal-dialog" role="document">
......
......
......@@ -91,6 +91,24 @@
"missing-segment": "Eine Eingabe für die Regel {{ .name }} ({{ .technicalName }}) fehlt.",
"invalid-rule-value": "Der Wert \"value\" für die Regel {{ .rule }} vom Typ {{ .type }} ist ungültig. Bitte überprüfen Sie die Schablonen-Dokumentation."
}
},
"elicitation": {
"template": {
"search": {
"title": "Schablone suchen",
"shortcut": "Strg + F"
},
"variant": {
"left.shortcut": "<- + Strg",
"right.shortcut": "Strg + ->"
},
"form": {
"title": "Anforderung erfassen"
},
"construction": "Schablonenaufbau",
"example": "Beispielsatz",
"settings": "Einstellungen"
}
}
},
"harmony": {
......@@ -106,6 +124,7 @@
},
"menu": {
"home": "Startseite",
"eiffel": "EIFFEL",
"template-sets": "Schablonen",
"user": "Profil",
"login": "Anmelden",
......@@ -139,7 +158,8 @@
"close": "Schließen",
"refresh": "Aktualisieren",
"save": "Speichern",
"create": "Erstellen"
"create": "Erstellen",
"search": "Suchen"
}
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment