initial implementation of the dialog manager, with just enough smarts to render the login dialog

This commit is contained in:
2025-09-24 23:30:17 -06:00
parent ac3888f832
commit 201e549630
6 changed files with 224 additions and 3 deletions
+29
View File
@@ -0,0 +1,29 @@
/*
* Amsterdam Web Communities System
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// Package main contains the high-level Amsterdam logic.
package main
import "git.erbosoft.com/amy/amsterdam/ui"
/* LoginForm renders the Amsterdam login form.
* Parameters:
* ctxt - The AmContext for the request.
* Returns:
* Command string dictating what to be rendered.
* Data as a parameter for the command string.
* Standard Go error status.
*/
func LoginForm(ctxt ui.AmContext) (string, any, error) {
dlg, err := ui.AmLoadDialog("login")
if err == nil {
ctxt.VarMap().Set("amsterdam_pageTitle", "Log In")
return dlg.Render(ctxt)
}
return ui.ErrorPage(ctxt, err)
}
+6 -2
View File
@@ -35,10 +35,14 @@ func setupEcho() *echo.Echo {
e.Use(LogrusMiddleware)
e.Use(session.Middleware(ui.SessionStore))
e.GET("/TODO/*", ui.AmWrap(NotImplPage))
fn := ui.AmWrap(NotImplPage)
e.GET("/TODO/*", fn)
e.POST("/TODO/*", fn)
e.GET("/img/*", ui.AmWrap(ui.AmServeImage))
e.GET("/about", ui.AmWrap(AboutPage))
e.GET("/", ui.AmWrap(TopPage))
e.GET("/about", ui.AmWrap(AboutPage))
e.GET("/login", ui.AmWrap(LoginForm))
return e
}
+75
View File
@@ -0,0 +1,75 @@
/*
* Amsterdam Web Communities System
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// Package ui holds the support for the Amsterdam user interface, wrapping Echo and Jet templates.
package ui
import (
"embed"
"fmt"
"gopkg.in/yaml.v3"
)
// DialogItem holds the dialog item definition.
type DialogItem struct {
Type string `yaml:"type"`
Name string `yaml:"name"`
Caption string `yaml:"caption,omitempty"`
Size int `yaml:"size,omitempty"`
MaxLength int `yaml:"maxlength,omitempty"`
Value string `yaml:"value,omitempty"`
Tone string `yaml:"tone,omitempty"`
}
// Dialog holds the dialog definition.
type Dialog struct {
Name string `yaml:"name"`
FormName string `yaml:"formName"`
MenuSelector string `yaml:"menuSelector,omitempty"`
Title string `yaml:"title"`
Action string `yaml:"action"`
Instructions string `yaml:"instructions,omitempty"`
Fields []DialogItem `yaml:"fields"`
}
//go:embed dialogs/*
var dialogs embed.FS
/* AmLoadDialog loads a dialog definition.
* Parameters:
* name - The name of the dialog to load
*/
func AmLoadDialog(name string) (*Dialog, error) {
b, err := dialogs.ReadFile(fmt.Sprintf("dialogs/%s.yaml", name))
if err == nil {
var d Dialog
err = yaml.Unmarshal(b, &d)
if err == nil {
if d.MenuSelector == "" {
d.MenuSelector = "nochange"
}
return &d, nil
}
}
return nil, err
}
/* Render sets up the rendering parameters to send this dialog to the output.
* Parameters:
* ctxt - The AmContext for this request.
* Returns:
* Command string dictating what to be rendered.
* Data as a parameter for the command string.
* Standard Go error status.
*/
func (d *Dialog) Render(ctxt AmContext) (string, any, error) {
ctxt.VarMap().Set("amsterdam_dialog", d)
return "framed_template", "dialog.jet", nil
}
+45
View File
@@ -0,0 +1,45 @@
#
# Amsterdam Web Communities System
# Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
#
name: "login"
formName: "loginform"
menuSelector: "top"
title: "Log In"
action: "/TODO/login"
instructions: >
Forgot your password? Enter your user name and click the <b>Reminder</b> button to receive
a password reminder via E-mail.
fields:
- type: "hidden"
name: "tgt"
value: ""
- type: "veniceid"
name: "user"
caption: "User Name"
size: 32
maxlength: 64
- type: "password"
name: "pass"
caption: "Password"
size: 32
maxlength: 128
- type: "checkbox"
name: "saveme"
caption: "Remember me for next time so I can log in automatically"
- type: "button"
name: "login"
caption: "Log In"
tone: "blue"
- type: "button"
name: "remind"
caption: "Password Reminder"
tone: "gray"
- type: "button"
name: "cancel"
caption: "Cancel"
tone: "red"
+68
View File
@@ -0,0 +1,68 @@
{*
* Amsterdam Web Communities System
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*}
<div class="p-4">
<div class="mb-6">
<h1 class="text-blue-800 text-4xl font-bold mb-2">{{ amsterdam_dialog.Title }}</h1>
<hr class="border-2 border-gray-400 w-4/5 mb-4">
</div>
<form name="{{ amsterdam_dialog.FormName }}" method="POST" action="{{ amsterdam_dialog.Action }}" class="max-w-2xl">
{{ range amsterdam_dialog.Fields }}
{{ if .Type == "hidden" }}
<input type="hidden" name="{{ .Name }}" value="{{ .Value }}" />
{{ end }}
{{ end }}
{{ if amsterdam_dialog.Instructions != "" }}
<p class="text-black text-sm mb-6">{{ amsterdam_dialog.Instructions | raw }}</p>
{{ end }}
<div class="bg-gray-50 p-6 rounded-lg">
<div class="space-y-4">
{{ range amsterdam_dialog.Fields }}
{{ if .Type == "veniceid" }}
<div class="flex items-center">
<label for="{{ .Name }}" class="w-24 text-right pr-4 text-black text-sm">{{ .Caption }}:</label>
<input type="text" id="{{ .Name }}" name="{{ .Name }}"
{{ if .Size > 0 }}size="{{ .Size }}"{{ end }}
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
value="{{ .Value }}"
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
</div>
{{ else if .Type == "password" }}
<div class="flex items-center">
<label for="{{ .Name }}" class="w-24 text-right pr-4 text-black text-sm">{{ .Caption }}:</label>
<input type="password" id="{{ .Name }}" name="{{ .Name }}"
{{ if .Size > 0 }}size="{{ .Size }}"{{ end }}
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
value="{{ .Value }}"
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
</div>
{{ else if .Type == "checkbox" }}
<div class="flex items-center">
<div class="w-24 text-right pr-4">
<input type="checkbox" id="{{ .Name }}" name="{{ .Name }}"
value="Y" class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" />
</div>
<label for="{{ .Name }}" class="flex-1 text-black text-sm">{{ .Caption }}</label>
</div>
{{ end }}
{{ end }}
</div>
<div class="flex justify-center gap-4 mt-6">
{{ range amsterdam_dialog.Fields }}
{{ if .Type == "button" }}
{{ clstmp := "bg-" + .Tone + "-600 hover:bg-" + .Tone + "-700" }}
<button type="submit" name="{{ .Name }}"
class="{{ clstmp }} text-white px-6 py-2 rounded font-medium transition-colors">{{ .Caption }}</button>
{{ end }}
{{ end }}
</div>
</div>
</form>
</div>
+1 -1
View File
@@ -59,7 +59,7 @@
{{ if .CurrentUser().IsAnon }}
You are not logged in
<span class="mx-2">-</span>
<a href="/TODO/login" class="text-yellow-300 hover:text-yellow-400">Log In</a>
<a href="/login" class="text-yellow-300 hover:text-yellow-400">Log In</a>
<span class="mx-2">|</span>
<a href="/TODO/newaccount" class="text-yellow-300 hover:text-yellow-400">Create Account</a>
{{ else }}