terrier-engine 4.14.2 → 4.16.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.
@@ -1,7 +1,7 @@
1
1
  import Schema, {SchemaDef} from "../../terrier/schema"
2
2
  import {PartTag} from "tuff-core/parts"
3
3
  import Dives from "./dives"
4
- import {Query, QueryModelPicker} from "../queries/queries"
4
+ import Queries, {Query, QueryModelPicker} from "../queries/queries"
5
5
  import QueryEditor from "../queries/query-editor"
6
6
  import {Logger} from "tuff-core/logging"
7
7
  import QueryForm from "../queries/query-form"
@@ -17,6 +17,8 @@ import {DiveRunModal} from "./dive-runs"
17
17
  import Nav from "tuff-core/nav"
18
18
  import Messages from "tuff-core/messages"
19
19
  import Arrays from "tuff-core/arrays"
20
+ import {FormFields} from "tuff-core/forms";
21
+ import Fragments from "../../terrier/fragments";
20
22
 
21
23
  const log = new Logger("DiveEditor")
22
24
 
@@ -36,6 +38,7 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
36
38
  tabs!: TabContainerPart
37
39
 
38
40
  newQueryKey = Messages.untypedKey()
41
+ duplicateQueryKey = Messages.untypedKey()
39
42
 
40
43
  static readonly diveChangedKey = Messages.untypedKey()
41
44
 
@@ -54,15 +57,22 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
54
57
  tooltip: "Each query represents a separate tab in the resulting spreadsheet",
55
58
  click: {key: this.newQueryKey}
56
59
  })
60
+ this.tabs.addAfterAction({
61
+ icon: 'glyp-copy',
62
+ classes: ['duplicate-query'],
63
+ tooltip: "Duplicate this query",
64
+ click: {key: this.duplicateQueryKey}
65
+ })
57
66
  this.tabs.addAfterAction({
58
67
  icon: 'glyp-plus_outline',
59
68
  classes: ['new-query'],
69
+ tooltip: "Add a new query to this Dive",
60
70
  click: {key: this.newQueryKey}
61
71
  })
62
72
 
63
73
  this.queries = this.state.dive.query_data?.queries || []
64
74
  for (const query of this.queries) {
65
- this.addQueryTag(query)
75
+ this.addQueryTab(query)
66
76
  }
67
77
 
68
78
  this.listenMessage(QueryForm.settingsChangedKey, m => {
@@ -75,6 +85,26 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
75
85
  this.app.showModal(NewQueryModal, {editor: this as DiveEditor, schema: this.state.schema})
76
86
  })
77
87
 
88
+ this.onClick(this.duplicateQueryKey, _ => {
89
+ const id = this.tabs.currentTagKey
90
+ if (id?.length) {
91
+ const query = this.queries.find(q => q.id == id)
92
+ if (query) {
93
+ this.app.showModal(DuplicateQueryModal, {
94
+ editor: this as DiveEditor,
95
+ schema: this.state.schema,
96
+ query
97
+ })
98
+ }
99
+ else {
100
+ this.app.alertToast(`No query with id ${id}`, 'glyp-alert')
101
+ }
102
+ }
103
+ else {
104
+ this.app.alertToast("No query shown", 'glyp-alert')
105
+ }
106
+ })
107
+
78
108
  this.onClick(DiveEditor.deleteQueryKey, m => {
79
109
  this.app.confirm({title: "Delete Query", icon: 'glyp-delete', body: "Are you sure you want to delete this query?"}, () => {
80
110
  this.deleteQuery(m.data.id)
@@ -88,14 +118,14 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
88
118
  */
89
119
  addQuery(query: Query) {
90
120
  this.queries.push(query)
91
- this.addQueryTag(query)
121
+ this.addQueryTab(query)
92
122
  }
93
123
 
94
124
  /**
95
125
  * Adds a tab for an existing query.
96
126
  * @param query
97
127
  */
98
- private addQueryTag(query: Query) {
128
+ private addQueryTab(query: Query) {
99
129
  const state = {...this.state, query}
100
130
  this.tabs.upsertTab({key: query.id, title: query.name}, QueryEditor, state)
101
131
  }
@@ -126,6 +156,10 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
126
156
  }
127
157
 
128
158
 
159
+ ////////////////////////////////////////////////////////////////////////////////
160
+ // Editor Page
161
+ ////////////////////////////////////////////////////////////////////////////////
162
+
129
163
  export class DiveEditorPage extends PagePart<{id: string}> {
130
164
 
131
165
  editor!: DiveEditor
@@ -304,4 +338,56 @@ class NewQueryModal extends ModalPart<NewQueryState> {
304
338
  this.pop()
305
339
  }
306
340
 
307
- }
341
+ }
342
+
343
+
344
+ ////////////////////////////////////////////////////////////////////////////////
345
+ // Duplicate Query Modal
346
+ ////////////////////////////////////////////////////////////////////////////////
347
+
348
+
349
+ type DuplicateQueryState = {
350
+ editor: DiveEditor
351
+ schema: SchemaDef
352
+ query: Query
353
+ }
354
+
355
+ class DuplicateQueryModal extends ModalPart<DuplicateQueryState> {
356
+
357
+ dupKey = Messages.untypedKey()
358
+ fields!: FormFields<Query>
359
+
360
+ async init() {
361
+ this.setIcon('glyp-data_dive_query')
362
+ this.setTitle("Duplicate Query")
363
+
364
+ const newQuery = {...this.state.query, name: `${this.state.query.name} Copy`}
365
+ this.fields = new FormFields<Query>(this, newQuery)
366
+
367
+ this.addAction({
368
+ title: "Duplicate",
369
+ icon: 'glyp-checkmark',
370
+ click: {key: this.dupKey}
371
+ })
372
+
373
+ this.onClick(this.dupKey, async _ => {
374
+ await this.save()
375
+ })
376
+ }
377
+
378
+ async save() {
379
+ const newName = this.fields.data.name
380
+ const query = {...Queries.duplicate(this.state.query), name: newName}
381
+ this.state.editor.addQuery(query)
382
+ this.state.editor.tabs.showTab(query.id)
383
+ this.pop()
384
+ this.app.successToast("Duplicated Query", 'glyp-copy')
385
+ }
386
+
387
+ renderContent(parent: PartTag): void {
388
+ parent.div(".tt-flex.column.padded.gap", col => {
389
+ Fragments.simpleHeading(col, this.theme, "Name")
390
+ this.fields.textInput(col, 'name')
391
+ })
392
+ }
393
+ }
@@ -12,6 +12,8 @@ import Messages from "tuff-core/messages"
12
12
  import Strings from "tuff-core/strings"
13
13
  import Arrays from "tuff-core/arrays"
14
14
  import {ColumnRef} from "./columns"
15
+ import Ids from "../../terrier/ids";
16
+ import Objects from "tuff-core/objects";
15
17
 
16
18
  const log = new Logger("Queries")
17
19
 
@@ -85,6 +87,17 @@ function eachColumn(query: Query, fn: ColumnFunction) {
85
87
  eachColumnForTable(query.from, fn)
86
88
  }
87
89
 
90
+ /**
91
+ * Duplicates a query, including all of the nested data structures.
92
+ * @param query the query to duplicate
93
+ * @return a completely new query
94
+ */
95
+ function duplicate(query: Query): Query {
96
+ const newQuery = Objects.deepCopy(query)
97
+ newQuery.id = Ids.makeUuid()
98
+ return newQuery
99
+ }
100
+
88
101
 
89
102
  ////////////////////////////////////////////////////////////////////////////////
90
103
  // Server-Side Validation
@@ -319,6 +332,7 @@ export class QueryModelPicker extends TerrierPart<QueryModelPickerState> {
319
332
  const Queries = {
320
333
  eachColumn,
321
334
  eachTable,
335
+ duplicate,
322
336
  validate,
323
337
  preview,
324
338
  renderPreview
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.14.2",
7
+ "version": "4.16.0",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
package/terrier/tabs.ts CHANGED
@@ -116,6 +116,13 @@ export class TabContainerPart extends TerrierPart<TabContainerState> {
116
116
  this.dirty()
117
117
  }
118
118
 
119
+ /**
120
+ * Gets the current tab key.
121
+ */
122
+ get currentTagKey(): string | undefined {
123
+ return this.state.currentTab || Object.keys(this.tabs)[0]
124
+ }
125
+
119
126
 
120
127
  _beforeActions: Action[] = []
121
128