Upgrade `param-input` to Octane (#196)

* Convert `param-input` to glimmer components
This commit is contained in:
Isaac Janzen 2022-12-16 11:41:03 -06:00 committed by GitHub
parent bd602b02a0
commit 8028b9f16a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 196 additions and 108 deletions

View File

@ -0,0 +1,55 @@
<div class="param {{if this.valid 'valid' 'invalid'}}">
{{#if (eq this.type "boolean")}}
{{#if @info.nullable}}
<ComboBox
@valueAttribute="id"
@value={{this.nullableBoolValue}}
@nameProperty="name"
@content={{this.boolTypes}}
@onChange={{this.updateNullableBoolValue}}
/>
{{else}}
<Input
@type="checkbox"
@checked={{this.boolvalue}}
{{on "change" this.updateBoolValue}}
/>
{{/if}}
<span class="param-name">{{@info.identifier}}</span>
{{else if (eq this.type "int")}}
<Input
@type="number"
@value={{this.value}}
{{on "change" this.updateValue}}
/>
<span class="param-name">{{@info.identifier}}</span>
{{else if (eq this.type "string")}}
<TextField
@value={{this.value}}
@type="text"
@onChange={{this.updateValue}}
/>
<span class="param-name">{{@info.identifier}}</span>
{{else if (eq this.type "user_id")}}
<EmailGroupUserChooser
@value={{this.value}}
@options={{(hash maximum=1)}}
@onChange={{this.updateValue}}
/>
<span class="param-name">{{@info.identifier}}</span>
{{else if (eq this.type "user_list")}}
<EmailGroupUserChooser
@value={{this.value}}
@onChange={{this.updateValue}}
/>
<span class="param-name">{{@info.identifier}}</span>
{{else}}
<TextField @value={{this.value}} @onChange={{this.updateValue}} />
<span class="param-name">{{@info.identifier}}</span>
{{/if}}
</div>

View File

@ -1,10 +1,11 @@
import Component from "@ember/component";
import Component from "@glimmer/component";
import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
import Category from "discourse/models/category";
import { dasherize } from "@ember/string";
import { isEmpty } from "@ember/utils";
import { computed } from "@ember/object";
import { action } from "@ember/object";
import { tracked } from "@glimmer/tracking";
import { inject as service } from "@ember/service";
const layoutMap = {
int: "int",
@ -26,58 +27,73 @@ const layoutMap = {
user_list: "user_list",
};
function allowsInputTypeTime() {
try {
const inp = document.createElement("input");
inp.attributes.type = "time";
inp.attributes.type = "date";
return true;
} catch (e) {
return false;
}
}
export default class ParamInput extends Component {
@service site;
export default Component.extend({
classNameBindings: ["valid:valid:invalid", ":param"],
@tracked value;
@tracked boolValue;
@tracked nullableBoolValue;
boolTypes: [
boolTypes = [
{ name: I18n.t("explorer.types.bool.true"), id: "Y" },
{ name: I18n.t("explorer.types.bool.false"), id: "N" },
{ name: I18n.t("explorer.types.bool.null_"), id: "#null" },
],
initialValues: null,
];
init() {
this._super(...arguments);
constructor() {
super(...arguments);
if (this.initialValues && this.info.identifier in this.initialValues) {
this.set("value", this.initialValues[this.info.identifier]);
const identifier = this.args.info.identifier;
const initialValues = this.args.initialValues;
// access parsed params if present to update values to previously ran values
if (initialValues && identifier in initialValues) {
const initialValue = initialValues[identifier];
if (this.type === "boolean") {
if (this.args.info.nullable) {
this.nullableBoolValue = initialValue;
} else {
this.boolValue = initialValue !== "false";
}
} else {
this.value =
this.args.info.type === "category_id"
? this.dasherizeCategoryId(initialValue)
: initialValue;
}
} else {
// if no parsed params then get and set default values
const params = this.args.params;
this.value =
this.args.info.type === "category_id"
? this.dasherizeCategoryId(params[identifier])
: params[identifier];
this.boolValue = params[identifier] !== "false";
this.nullableBoolValue = params[identifier];
}
},
}
value: computed("params", "info.identifier", {
get() {
return this.params[this.get("info.identifier")];
},
set(key, value) {
this.params[this.get("info.identifier")] = value?.toString();
return value;
},
}),
get type() {
const type = this.args.info.type;
if ((type === "time" || type === "date") && !allowsInputTypeTime()) {
return "string";
}
return layoutMap[type] || "generic";
}
valueBool: computed("params", "info.identifier", {
get() {
return this.params[this.get("info.identifier")] !== "false";
},
set(key, value) {
value = !!value;
this.params[this.get("info.identifier")] = value.toString();
return value;
},
}),
get valid() {
const nullable = this.args.info.nullable;
// intentionally use 'this.args' here instead of 'this.type'
// to get the original key instead of the translated value from the layoutMap
const type = this.args.info.type;
let value;
if (type === "boolean") {
value = nullable ? this.nullableBoolValue : this.boolValue;
} else {
value = this.value;
}
@discourseComputed("value", "info.type", "info.nullable")
valid(value, type, nullable) {
if (isEmpty(value)) {
return nullable;
}
@ -104,10 +120,6 @@ export default Component.extend({
case "post_id":
return isPositiveInt || /\d+\/\d+(\?u=.*)?$/.test(value);
case "category_id":
if (!isPositiveInt && value !== dasherize(value)) {
this.set("value", dasherize(value));
}
if (isPositiveInt) {
return !!this.site.categories.find((c) => c.id === intVal);
} else if (/\//.test(value)) {
@ -132,21 +144,55 @@ export default Component.extend({
}
}
return true;
},
}
@discourseComputed("info.type")
layoutType(type) {
if ((type === "time" || type === "date") && !allowsInputTypeTime()) {
return "string";
dasherizeCategoryId(value) {
const isPositiveInt = /^\d+$/.test(value);
if (!isPositiveInt && value !== dasherize(value)) {
return dasherize(value);
}
if (layoutMap[type]) {
return layoutMap[type];
}
return "generic";
},
return value;
}
@discourseComputed("layoutType")
layoutName(layoutType) {
return `admin/components/q-params/${layoutType}`;
},
});
@action
updateValue(input) {
// handle selectKit inputs as well as traditional inputs
const value = input.target ? input.target.value : input;
if (value.length) {
this.value =
this.args.info.type === "category_id"
? this.dasherizeCategoryId(value.toString())
: value.toString();
} else {
this.value = value;
}
this.args.updateParams(this.args.info.identifier, this.value);
}
@action
updateBoolValue(input) {
this.boolValue = input.target.checked;
this.args.updateParams(
this.args.info.identifier,
this.boolValue.toString()
);
}
@action
updateNullableBoolValue(input) {
this.nullableBoolValue = input;
this.args.updateParams(this.args.info.identifier, this.nullableBoolValue);
}
}
function allowsInputTypeTime() {
try {
const input = document.createElement("input");
input.attributes.type = "time";
input.attributes.type = "date";
return true;
} catch (e) {
return false;
}
}

View File

@ -0,0 +1,12 @@
{{#if @hasParams}}
<div class="query-params">
{{#each @paramInfo as |pinfo|}}
<ParamInput
@params={{@params}}
@initialValues={{@initialValues}}
@info={{pinfo}}
@updateParams={{@updateParams}}
/>
{{/each}}
</div>
{{/if}}

View File

@ -312,6 +312,12 @@ export default Controller.extend({
});
},
// This is necessary with glimmer's one way data stream to get the child's
// changes of 'params' to bubble up.
updateParams(identifier, value) {
this.selectedItem.set(`params.${identifier}`, value);
},
run() {
if (this.get("selectedItem.dirty")) {
return;
@ -325,6 +331,7 @@ export default Controller.extend({
showResults: false,
params: JSON.stringify(this.selectedItem.params),
});
ajax(
"/admin/plugins/explorer/queries/" +
this.get("selectedItem.id") +

View File

@ -1,12 +0,0 @@
{{#if info.nullable}}
{{combo-box
valueAttribute="id"
value=value
nameProperty="name"
content=boolTypes
}}
{{else}}
{{input type="checkbox" checked=valueBool}}
{{/if}}
<span class="param-name">{{info.identifier}}</span>

View File

@ -1,2 +0,0 @@
{{text-field value=value}}
<span class="param-name">{{info.identifier}}</span>

View File

@ -1,2 +0,0 @@
{{input type="number" value=value}}
<span class="param-name">{{info.identifier}}</span>

View File

@ -1,2 +0,0 @@
{{text-field value=value type="text"}}
<span class="param-name">{{info.identifier}}</span>

View File

@ -1,7 +0,0 @@
{{email-group-user-chooser
value=value
options=(hash maximum=1)
onChange=(action (mut value))
}}
<span class="param-name">{{info.identifier}}</span>

View File

@ -1,2 +0,0 @@
{{email-group-user-chooser value=value onChange=(action (mut value))}}
<span class="param-name">{{info.identifier}}</span>

View File

@ -212,17 +212,13 @@
</div>
<form class="query-run" {{action "run" on="submit"}}>
{{#if selectedItem.hasParams}}
<div class="query-params">
{{#each selectedItem.param_info as |pinfo|}}
{{param-input
params=selectedItem.params
initialValues=parsedParams
info=pinfo
}}
{{/each}}
</div>
{{/if}}
<ParamInputsWrapper
@hasParams={{selectedItem.hasParams}}
@params={{selectedItem.params}}
@initialValues={{parsedParams}}
@paramInfo={{selectedItem.param_info}}
@updateParams={{action "updateParams"}}
/>
{{#if runDisabled}}
{{#if saveDisabled}}

View File

@ -3,13 +3,12 @@
<p>{{model.description}}</p>
<form class="query-run" {{action "run" on="submit"}}>
{{#if hasParams}}
<div class="query-params">
{{#each model.param_info as |pinfo|}}
{{param-input params=model.params info=pinfo}}
{{/each}}
</div>
{{/if}}
<ParamInputsWrapper
@hasParams={{hasParams}}
@params={{model.params}}
@paramInfo={{model.param_info}}
@updateParams={{action "updateParams"}}
/>
{{d-button
action=(action "run")