Skip to content

Conversation

@mvollmer
Copy link
Member

@mvollmer mvollmer commented Nov 27, 2025

And introduce the framework at the same time.

Some noteworthy things:

  • It might be hard to see from the diff, but: the rewrite was very straightforward and it's nice that the "hairy" parts have disappeared, especially the untypable onValuesChanged methods. The code has become much less strongly coupled by that. Reusing components like NetworkModelRow is now feasible and doesn't expect the parent component to have a specific state structure.

  • This is as far as I think I want to go in this PR.

    There is more to be done to make them "state of the art", but that requires actual changes to behavior. For example, the "Mac" text input in the Edit dialog could be a clean DialogTextInput, but it is sometimes disabled with a funny tooltip on the whole text input. I don't think this is a "best practice", it looks funny, I haven't seen it anywhere else, and I think we should reduce the use of tooltips in general. So instead of making DialogTextInput configurable for all these one-off "experiences", I think we should change the dialog to use a helper text to explain why the input is disabled. Likewise, I think we should change how the dialog handles types that have no sources. Etc. I'll continue in nics: Improve the dialog experience with new framework components #2435.

Here is the 1000 meter view of the framework API:

  • Define a new type for the values worked on by the dialog:
interface MyValues {
   name: string;
   flag: boolean;
}
  • Get a DialogState<MyValues> object with the initial values:
const dlg = useDialogState({ name: "Bob", flag: false });
  • Create a React modal thing as usual, but use DialogInputText etc in the body:
<ModalBody>
  <DialogErrorMessage dialog={dlg} />
  <Form>
    <DialogTextInput label="Name" value={dlg.value("name")} />
    <DialogCheckbox label="Flag" value={dlg.value("flag")} />
  </Form>
</ModalBody>
  • Use DialogActionButton in the footer:
<ModalFooter>
  <DialogActionButton dialog={dlg} action={some_action}>Apply</DialogActionButton>
  <DialogCancelButton dialog={dlg} />
</ModalFooter>
  • The action function gets the values and can throw errors:
async function some_action(values: MyValues) {
    ....
}
  • If you want to do input validation, write a function for that and pass it to use_DialogState. Don't worry too much why it needs to be written like this.
function validate() {
  dlg.value("name").validate(v => {
     if (v == "Bob")
        return _("No Bobs");
   });
}

Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

@mvollmer mvollmer force-pushed the passt-dialog-with-framework branch from 9a5d9ae to c463979 Compare November 27, 2025 11:34
Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

@mvollmer mvollmer force-pushed the passt-dialog-with-framework branch from c463979 to 35fcd29 Compare November 27, 2025 12:21
Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

@mvollmer mvollmer force-pushed the passt-dialog-with-framework branch 2 times, most recently from e565eab to aeac367 Compare November 27, 2025 13:12
Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

@mvollmer
Copy link
Member Author

Things that stick out in the dialogs that I would like to improve:

  • The excessive ceremony for giving things IDs.
  • The fact that NetworkTypeAndSource takes three values instead of just one, and thus doesn't follow the usual pattern.

The IDs are used by the tests but also to associate labels for input elements. Clicking the FormGroup label will focus the corresponding text input, and clicking a label for a checkbox should toggle that checkbox.

Using IDs for each and every thing in a dialog feels too much, but the alternative would be to use "data-ouia" things or similar, and that isn't really any better, is it?

So let's have a method for generating IDs for dialog values, and some Python helpers to work with them from the tests.

@mvollmer mvollmer force-pushed the passt-dialog-with-framework branch 2 times, most recently from 1fff60d to 899c38e Compare December 1, 2025 07:49
Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

@mvollmer mvollmer force-pushed the passt-dialog-with-framework branch from bbe24e2 to 0ece715 Compare December 1, 2025 11:54
@mvollmer mvollmer marked this pull request as ready for review December 1, 2025 12:00
Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

@mvollmer mvollmer force-pushed the passt-dialog-with-framework branch 3 times, most recently from 4545de5 to 26d2c33 Compare December 15, 2025 15:33
Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

To have only one dialog value instead of three (plus assorted props).

The goal is to put the initialization knowledge in the component
itself, and to not have any extra arguments for the validation
function. This gives this component the "standard" API.
@mvollmer mvollmer force-pushed the passt-dialog-with-framework branch from 26d2c33 to e6e7919 Compare December 16, 2025 07:44
Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

Comment on lines +643 to +644
set_aux(val: T): void {
this.#setter(val, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 2 added lines are not executed by any test. Details

if (Array.isArray(val)) {
return val.map((_, i) => func(this.sub(i as keyof T) as DialogValue<ArrayElement<T>>, i));
} else
return [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

const val = this.get();
if (Array.isArray(val)) {
for (let j = index; j < val.length - 1; j++)
this.#dialog._rename_validation_state(this.#path, j + 1, j);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

Comment on lines +699 to +705
at<TT extends T>(witness: TT): DialogValue<TT> {
cockpit.assert(Object.is(witness, this.get()));
return new DialogValue<TT>(
this.#dialog,
() => this.get() as TT,
(val, is_aux) => {
this.#setter(val, is_aux);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 7 added lines are not executed by any test. Details

(val, is_aux) => {
this.#setter(val, is_aux);
},
this.#path,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

Comment on lines +720 to +727
validate_async(debounce: number, func: (val: T) => Promise<string | undefined>): void {
const val = this.get();
if (!this.#dialog._probe_validation_cache(this.#path, val)) {
console.log("START VALIDATE ASYNC", this.#path, val);
this.#dialog._set_validation_timeout(
this.#path,
val,
debounce,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 8 added lines are not executed by any test. Details

Comment on lines +729 to +735
console.log("BEGIN VALIDATE ASYNC", this.#path, val);
const prom =
func(val)
.catch(
ex => {
console.error(ex);
return undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 7 added lines are not executed by any test. Details

Comment on lines +738 to +742
.then(
result => {
if (this.#dialog._validation_state_is_current(this.#path, prom)) {
console.log("DONE VALIDATE ASYNC", this.#path, result);
this.#dialog._set_validation_result(this.#path, val, result);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 5 added lines are not executed by any test. Details

console.log("DONE VALIDATE ASYNC", this.#path, result);
this.#dialog._set_validation_result(this.#path, val, result);
} else {
console.log("OUTDATED", this.#path);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

}
}
);
this.#dialog._set_validation_promise(this.#path, val, prom);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

const Dialogs = useDialogs();
function init(): LoggerValues = {
text: "",
Copy link
Member Author

@mvollmer mvollmer Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, "return" missing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants