terrier-engine 4.11.0 → 4.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,12 +20,18 @@ const log = new Logger("Queries")
20
20
  // Query
21
21
  ////////////////////////////////////////////////////////////////////////////////
22
22
 
23
+ export type OrderBy = {
24
+ column: string
25
+ dir: string
26
+ }
27
+
23
28
  export type Query = {
24
29
  id: string
25
30
  name: string
26
31
  notes: string
27
32
  from: TableRef
28
33
  columns?: string[]
34
+ order_by?: OrderBy[]
29
35
  }
30
36
 
31
37
 
@@ -11,6 +11,7 @@ import {TabContainerPart} from "../../terrier/tabs"
11
11
  import Messages from "tuff-core/messages"
12
12
  import Validation, {QueryClientValidation} from "./validation"
13
13
  import ColumnOrderModal from "./column-order-modal"
14
+ import RowOrderModal from "./row-order-modal"
14
15
 
15
16
  const log = new Logger("QueryEditor")
16
17
 
@@ -78,8 +79,18 @@ class SortingPart extends ContentPart<SubEditorState> {
78
79
  })
79
80
 
80
81
  this.onClick(this.sortRowsKey, _ => {
81
- this.showToast("I said it was coming soon!", {color: 'alert', icon: 'glyp-developer'})
82
+ log.info("Sorting rows")
83
+ this.app.showModal(RowOrderModal, {
84
+ query: this.state.query,
85
+ onSorted: (newOrderBys) => {
86
+ log.info(`New row sort order`, newOrderBys)
87
+ this.state.query.order_by = newOrderBys
88
+ this.state.editor.dirty()
89
+ this.emitMessage(DiveEditor.diveChangedKey, {})
90
+ }
91
+ })
82
92
  })
93
+
83
94
  }
84
95
 
85
96
  renderContent(parent: PartTag): void {
@@ -104,8 +115,19 @@ class SortingPart extends ContentPart<SubEditorState> {
104
115
  })
105
116
  row.div(".tt-flex.gap.column.full-height", col => {
106
117
  col.h3(".glyp-rows").text("Rows")
107
- col.div(".dive-query-columns.stretch", orderList => {
108
- orderList.div(".text-center").text("Coming Soon")
118
+ col.div(".dive-query-order-bys.stretch", orderList => {
119
+ if (query.order_by?.length) {
120
+ for (const orderBy of query.order_by) {
121
+ orderList.div('.order-by', line => {
122
+ line.div(".column").text(orderBy.column)
123
+ const dir = orderBy.dir == 'asc' ? 'ascending' : 'descending'
124
+ line.div(`.dir.glyp-${dir}`).text(dir)
125
+ })
126
+ }
127
+ }
128
+ else {
129
+ orderList.div(".text-center").text("Unspecified")
130
+ }
109
131
  })
110
132
  col.a(".tt-button.shrink", button => {
111
133
  button.i(".glyp-edit")
@@ -0,0 +1,151 @@
1
+ import {ModalPart} from "../../terrier/modals"
2
+ import Queries, {OrderBy, Query} from "./queries"
3
+ import Messages from "tuff-core/messages"
4
+ import {PartTag} from "tuff-core/parts"
5
+ import {optionsForSelect, SelectOption} from "tuff-core/forms"
6
+ import {Logger} from "tuff-core/logging"
7
+ import Forms from "../../terrier/forms"
8
+ import SortablePlugin from "tuff-sortable/sortable-plugin";
9
+
10
+ const log = new Logger("RowOrderModal")
11
+
12
+ export type RowOrderState = {
13
+ query: Query
14
+ onSorted: (orderBys: OrderBy[]) => any
15
+ }
16
+
17
+ export default class RowOrderModal extends ModalPart<RowOrderState> {
18
+
19
+ submitKey = Messages.untypedKey()
20
+ newClauseKey = Messages.untypedKey()
21
+ changedKey = Messages.untypedKey()
22
+ orderBys: OrderBy[] = []
23
+ columnOptions: SelectOption[] = []
24
+ removeClauseKey = Messages.typedKey<{index: number}>()
25
+
26
+ async init() {
27
+ this.setTitle("Row Order")
28
+ this.setIcon("glyp-sort")
29
+
30
+ this.addAction({
31
+ title: "Apply",
32
+ icon: "glyp-checkmark",
33
+ click: {key: this.submitKey}
34
+ })
35
+
36
+ this.onClick(this.submitKey, _ => {
37
+ this.state.onSorted(this.orderBys)
38
+ this.pop()
39
+ })
40
+
41
+ this.addAction({
42
+ title: "New Clause",
43
+ icon: "glyp-plus",
44
+ click: {key: this.newClauseKey}
45
+ }, 'secondary')
46
+
47
+ this.onClick(this.newClauseKey, _ => {
48
+ this.addClause()
49
+ })
50
+
51
+ this.onClick(this.removeClauseKey, m => {
52
+ this.removeClause(m.data.index)
53
+ })
54
+
55
+ // collect the column options
56
+ const query = this.state.query
57
+ Queries.eachColumn(query, (_, col) => {
58
+ this.columnOptions.push({title: col.name, value: col.name})
59
+ })
60
+
61
+ // initialize the order=bys from the query, if present
62
+ if (query.order_by?.length) {
63
+ this.orderBys = query.order_by
64
+ }
65
+ else {
66
+ // start with something by default
67
+ this.addClause()
68
+ }
69
+
70
+ // serialize on input change
71
+ this.onChange(this.changedKey, _ => {
72
+ this.serialize()
73
+ })
74
+
75
+ // make the list sortable
76
+ this.makePlugin(SortablePlugin, {
77
+ zoneClass: 'dive-row-sort-zone',
78
+ targetClass: 'order-by',
79
+ onSorted: (_plugin, _evt) => {
80
+ this.serialize()
81
+ }
82
+ })
83
+ }
84
+
85
+ addClause() {
86
+ this.orderBys.push({column: this.columnOptions[0]?.value || '', dir: 'asc'})
87
+ log.info(`Added a line, orderBys is now ${this.orderBys.length} long`, this.orderBys)
88
+ this.dirty()
89
+ }
90
+
91
+ removeClause(index: number) {
92
+ log.info(`Removing clause ${index}`)
93
+ this.orderBys.splice(index, 1)
94
+ log.info(`Removed line ${index}, orderBys is now ${this.orderBys.length} long`, this.orderBys)
95
+ this.dirty()
96
+ }
97
+
98
+ serialize() {
99
+ log.info("Serializing...")
100
+ if (this.element) {
101
+ this.orderBys = []
102
+ this.element.querySelectorAll<HTMLElement>(".order-by").forEach(line => {
103
+ const column = line.querySelector<HTMLSelectElement>("select.column")?.value!!
104
+ const dir = Forms.getRadioValue(line, "input.dir") || "asc"
105
+ this.orderBys.push({column, dir})
106
+ })
107
+ log.info("Serialized", this.orderBys)
108
+ }
109
+ }
110
+
111
+ renderContent(parent: PartTag): void {
112
+ parent.div('.tt-flex.column.padded.gap.tt-form', container => {
113
+ container.p().text("Drag and drop the clauses to change their order:")
114
+ container.div(".dive-row-sort-zone", zone => {
115
+ let index = 0 // for making unique radio names
116
+ for (const orderBy of this.orderBys) {
117
+ zone.div(".order-by", {data: {index: index.toString()}}, line => {
118
+ line.a(".drag.glyp-navicon")
119
+ .data({tooltip: "Re-order this clause"})
120
+ line.select('.column', colSelect => {
121
+ optionsForSelect(colSelect, this.columnOptions, orderBy.column)
122
+ }).emitChange(this.changedKey)
123
+ line.label('.caption-size', label => {
124
+ label.input('.dir.dir-asc', {
125
+ type: "radio",
126
+ name: `sort-dir-${index}`,
127
+ value: "asc",
128
+ checked: orderBy.dir == 'asc'
129
+ }).emitChange(this.changedKey)
130
+ label.span().text("ascending")
131
+ })
132
+ line.label('.caption-size', label => {
133
+ label.input('.dir.dir-desc', {
134
+ type: "radio",
135
+ name: `sort-dir-${index}`,
136
+ value: "desc",
137
+ checked: orderBy.dir == 'desc'
138
+ }).emitChange(this.changedKey)
139
+ label.span().text("descending")
140
+ })
141
+ line.a(".remove.glyp-close")
142
+ .data({tooltip: "Remove this clause"})
143
+ .emitClick(this.removeClauseKey, {index})
144
+ })
145
+ index += 1
146
+ }
147
+ })
148
+ })
149
+ }
150
+
151
+ }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.11.0",
7
+ "version": "4.13.0",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
package/terrier/forms.ts CHANGED
@@ -11,6 +11,26 @@ import Strings from "tuff-core/strings"
11
11
 
12
12
  const log = new Logger("TerrierForms")
13
13
 
14
+ ////////////////////////////////////////////////////////////////////////////////
15
+ // Utilities
16
+ ////////////////////////////////////////////////////////////////////////////////
17
+
18
+ /**
19
+ * Get the value of the checked radio with the given selector.
20
+ * @param container contains the radios
21
+ * @param selector the CSS selector used to select the radios from the container
22
+ */
23
+ function getRadioValue(container: HTMLElement, selector: string): string | undefined {
24
+ let value: string | undefined = undefined
25
+ container.querySelectorAll<HTMLInputElement>(selector).forEach(radio => {
26
+ if (radio.checked) {
27
+ value = radio.value
28
+ }
29
+ })
30
+ return value
31
+ }
32
+
33
+
14
34
  ////////////////////////////////////////////////////////////////////////////////
15
35
  // Options
16
36
  ////////////////////////////////////////////////////////////////////////////////
@@ -112,7 +132,8 @@ export class TerrierFormFields<T extends FormPartData> extends FormFields<T> {
112
132
  ////////////////////////////////////////////////////////////////////////////////
113
133
 
114
134
  const Forms = {
115
- titleizeOptions
135
+ titleizeOptions,
136
+ getRadioValue
116
137
  }
117
138
 
118
139
  export default Forms