fix (form): enhance form app

This commit is contained in:
MickaelK 2025-09-01 18:31:09 +10:00
parent 2babd4205b
commit 8d80cabba0
7 changed files with 75 additions and 32 deletions

View file

@ -6,6 +6,11 @@
line-height: 1em; line-height: 1em;
text-align: justify; text-align: justify;
} }
.formbuilder a {
text-decoration: underline;
text-decoration-style: wavy;
text-decoration-color: rgba(0, 0, 0, 0.3);
}
.formbuilder input::placeholder, .formbuilder textarea::placeholder { .formbuilder input::placeholder, .formbuilder textarea::placeholder {
opacity: 0.6; opacity: 0.6;
} }
@ -46,3 +51,11 @@
border-bottom: 2px solid rgba(70, 99, 114, 0.1); border-bottom: 2px solid rgba(70, 99, 114, 0.1);
transition: border-color 0.2s ease-out; transition: border-color 0.2s ease-out;
} }
.formbuilder .banner {
background: var(--bg-color);
border: 2px solid rgba(0, 0, 0, 0.05);
border-radius: 3px;
margin-bottom: 20px;
padding: 10px;
}

View file

@ -49,7 +49,7 @@ async function createFormNodes(node, { renderNode, renderLeaf, renderInput, path
else { else {
const currentPath = path.concat(key); const currentPath = path.concat(key);
const $leaf = renderLeaf({ const $leaf = renderLeaf({
...withMarkdown(node[key]), ...node[key],
path: currentPath, path: currentPath,
label: key, label: key,
}); });
@ -122,15 +122,3 @@ export async function createForm(node, opts) {
}); });
return $container; return $container;
} }
function withMarkdown(obj) {
if (!("description" in obj)) return obj;
obj["description"] = toMarkdown(obj["description"]);
return obj;
}
function toMarkdown(str = "") {
str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<a href=\"$2\">$1</a>");
str = str.replaceAll("\n", "<br>");
return str;
}

View file

@ -4,7 +4,7 @@ import rxjs from "../../lib/rx.js";
export function renderLeaf({ format, label, description, type }) { export function renderLeaf({ format, label, description, type }) {
if (label === "banner") return createElement(` if (label === "banner") return createElement(`
<div class="banner"> <div class="banner">
${description} ${fromMarkdown(description)}
</div> </div>
`); `);
const $el = createElement(` const $el = createElement(`
@ -29,6 +29,12 @@ export function renderLeaf({ format, label, description, type }) {
return $el; return $el;
} }
function fromMarkdown(str = "") {
str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<a href=\"$2\">$1</a>");
str = str.replaceAll("\n", "<br>");
return str;
}
export function useForm$($inputNodeList) { export function useForm$($inputNodeList) {
return rxjs.pipe( return rxjs.pipe(
rxjs.mergeMap(() => $inputNodeList()), rxjs.mergeMap(() => $inputNodeList()),

View file

@ -98,9 +98,6 @@
} }
.component_page_admin .page_container a { .component_page_admin .page_container a {
color: var(--dark); color: var(--dark);
text-decoration: underline;
text-decoration-style: wavy;
text-decoration-color: rgba(0, 0, 0, 0.3);
} }
.component_page_admin .page_container a:hover { .component_page_admin .page_container a:hover {
opacity: 0.8; opacity: 0.8;
@ -181,13 +178,6 @@
.component_page_admin .formbuilder textarea::placeholder { .component_page_admin .formbuilder textarea::placeholder {
opacity: 0.6; opacity: 0.6;
} }
.component_page_admin .formbuilder .banner {
background: var(--bg-color);
border: 2px solid rgba(0, 0, 0, 0.05);
border-radius: 3px;
margin-bottom: 15px;
padding: 10px;
}
.component_page_admin .formbuilder .banner pre { .component_page_admin .formbuilder .banner pre {
background: inherit; background: inherit;
color: inherit; color: inherit;

View file

@ -58,9 +58,6 @@
.component_formviewer > .formviewer_container .box .formbuilder label.no-select > div > span span.mandatory { .component_formviewer > .formviewer_container .box .formbuilder label.no-select > div > span span.mandatory {
opacity: 0.6; opacity: 0.6;
} }
.component_formviewer > .formviewer_container .box .formbuilder label.no-select > div div.description a {
border-bottom: 1px dashed var(--color);
}
.component_formviewer .formbuilder .component_input[disabled], .component_formviewer .formbuilder .component_input[disabled],
.component_formviewer .formbuilder .component_input[readonly], .component_formviewer .formbuilder .component_input[readonly],

View file

@ -64,7 +64,12 @@ export default function(render, { acl$, getFilename, getDownloadUrl }) {
} }
return $el; return $el;
}, },
renderLeaf: ({ label, format, description, required }) => createElement(` renderLeaf: ({ label, format, description, required }) => label === "banner"
? createElement(`
<div class="banner">
${fromMarkdown(safe(description))}
</div>
`) : createElement(`
<label class="no-select"> <label class="no-select">
<div> <div>
<span class="ellipsis"> <span class="ellipsis">
@ -75,7 +80,7 @@ export default function(render, { acl$, getFilename, getDownloadUrl }) {
</div> </div>
<div> <div>
<span class="nothing"></span> <span class="nothing"></span>
<div class="description">${safe(description)}</div> <div class="description">${fromMarkdown(safe(description))}</div>
</div> </div>
</label> </label>
`), `),
@ -170,3 +175,9 @@ function readOnlyForm(formSpec) {
} }
return formSpec; return formSpec;
} }
function fromMarkdown(str = "") {
str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<a href=\"$2\">$1</a>");
str = str.replaceAll("\n", "<br>");
return str;
}

View file

@ -53,21 +53,34 @@ func (this PSQL) Cat(path string) (io.ReadCloser, error) {
forms := make([]FormElement, len(c)) forms := make([]FormElement, len(c))
for i, _ := range columns { for i, _ := range columns {
forms[i] = createFormElement(col[i], columns[i]) forms[i] = createFormElement(col[i], columns[i])
if columnComment := _findCommentColumn(this.ctx, this.db, l.table, columns[i].Name); columnComment != "" {
forms[i].Description = columnComment
}
if slices.Contains(columns[i].Constraint, "PRIMARY KEY") && forms[i].Value != nil { if slices.Contains(columns[i].Constraint, "PRIMARY KEY") && forms[i].Value != nil {
forms[i].ReadOnly = true forms[i].ReadOnly = true
} else if slices.Contains(columns[i].Constraint, "FOREIGN KEY") { } else if slices.Contains(columns[i].Constraint, "FOREIGN KEY") {
if link, err := _findRelation(this.ctx, this.db, columns[i]); err == nil { if link, err := _findRelation(this.ctx, this.db, columns[i]); err == nil {
forms[i].Description = _createDescription(columns[i], link)
if len(link.values) > 0 { if len(link.values) > 0 {
forms[i].Type = "select" forms[i].Type = "select"
forms[i].Opts = link.values forms[i].Opts = link.values
} }
if forms[i].Description == "" {
forms[i].Description = _createDescription(columns[i], link)
}
} }
} else if values, err := _findEnumValues(this.ctx, this.db, columns[i]); err == nil && len(values) > 0 { } else if values, err := _findEnumValues(this.ctx, this.db, columns[i]); err == nil && len(values) > 0 {
forms[i].Type = "select" forms[i].Type = "select"
forms[i].Opts = values forms[i].Opts = values
} }
}
if comment := _findCommentTable(this.ctx, this.db, l.table); comment != "" {
forms = append([]FormElement{
{
Name: "banner",
Type: "hidden",
Description: comment,
},
}, forms...)
} }
b, err := Form{Elmnts: forms}.MarshalJSON() b, err := Form{Elmnts: forms}.MarshalJSON()
if err != nil { if err != nil {
@ -78,11 +91,36 @@ func (this PSQL) Cat(path string) (io.ReadCloser, error) {
func _createDescription(el Column, link LocationColumn) string { func _createDescription(el Column, link LocationColumn) string {
if slices.Contains(el.Constraint, "FOREIGN KEY") { if slices.Contains(el.Constraint, "FOREIGN KEY") {
return fmt.Sprintf("points to <%s> → <%s>", link.table, link.column) return fmt.Sprintf("points to [<%s> → <%s>](/files/%s/)", link.table, link.column, link.table)
} }
return "" return ""
} }
func _findCommentTable(ctx context.Context, db *sql.DB, tableName string) string {
var comment string
if err := db.QueryRowContext(ctx, `
SELECT obj_description(c.oid)
FROM pg_class c
WHERE c.relname = $1 AND c.relkind = 'r'
`, tableName).Scan(&comment); err != nil {
return ""
}
return comment
}
func _findCommentColumn(ctx context.Context, db *sql.DB, tableName, columnName string) string {
var comment string
if err := db.QueryRowContext(ctx, `
SELECT col_description(c.oid, a.attnum)
FROM pg_class c
JOIN pg_attribute a ON a.attrelid = c.oid
WHERE c.relname = $1 AND a.attname = $2 AND c.relkind = 'r'
`, tableName, columnName).Scan(&comment); err != nil {
return ""
}
return comment
}
func _findRelation(ctx context.Context, db *sql.DB, el Column) (LocationColumn, error) { func _findRelation(ctx context.Context, db *sql.DB, el Column) (LocationColumn, error) {
l := LocationColumn{} l := LocationColumn{}
rows, err := db.QueryContext(ctx, ` rows, err := db.QueryContext(ctx, `