sqlui 0.1.59 → 0.1.60

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b06af3454b65600eacd42b1008e186c473736411fedaed471162af97d98f39f
4
- data.tar.gz: 38c46eb311a518ea328acd959ca34c8e5d850c83fa9c1b98c663daef2356c334
3
+ metadata.gz: 45285cfe6706fdfa5bae7abf73f1a287e0238709f2f83a18eda8386c71f4de63
4
+ data.tar.gz: 9d3eacf4a4306697c4c34d91b51303dd88b376bec29526124d18ec5c4fbe43d7
5
5
  SHA512:
6
- metadata.gz: 320e438a5101b83a59691168f2673f82ffb35559b547a9e75e6278b9735c4880a3906b298ae576d89346218f3ff3543e7cd57e034dbbfa2c525b4a069ad337ab
7
- data.tar.gz: 03d5d65cad39f143c56f205c1f55280d2ab269390191b0ea026300e1c422b896876c2e5cf0c9f0764ca712a50c9702fb222f93a584960bfe11762fbc4663a78f
6
+ metadata.gz: f94e45bf232683013994f63a46eb18257aeaf9d81dd906ccffdbdced023be1743ebca28ff87f34a3b45932757b80516fae979d74a9ab1002affa78a7adb412dc
7
+ data.tar.gz: 4151e2f3f7d795931abfee848f040e9b6066c4ef4ec9503d0f3cdb586e82d3ced59f50c3eb8744c2adc2847324d275d642e313c0ba825768e452e7a0f8af7b75
data/.release-version CHANGED
@@ -1 +1 @@
1
- 0.1.59
1
+ 0.1.60
data/app/server.rb CHANGED
@@ -143,31 +143,60 @@ class Server < Sinatra::Base
143
143
  status 200
144
144
  headers 'Content-Type' => 'application/json; charset=utf-8'
145
145
 
146
- database.with_client do |client|
147
- query_result = execute_query(client, variables, queries)
148
- stream do |out|
146
+ stream do |out|
147
+ database.with_client do |client|
148
+ begin
149
+ query_result = execute_query(client, variables, queries)
150
+ rescue Mysql2::Error => e
151
+ stacktrace = e.full_message(highlight: false)
152
+ message = "ERROR #{e.error_number} (#{e.sql_state}): #{e.message.lines.first&.strip || 'unknown error'}"
153
+ out << { error: message, stacktrace: stacktrace }.compact.to_json
154
+ break
155
+ rescue StandardError => e
156
+ stacktrace = e.full_message(highlight: false)
157
+ message = e.message.lines.first&.strip || 'unknown error'
158
+ out << { error: message, stacktrace: stacktrace }.compact.to_json
159
+ break
160
+ end
161
+
149
162
  if query_result
150
163
  json = <<~RES.chomp
151
164
  {
152
165
  "columns": #{query_result.fields.to_json},
153
166
  "column_types": #{MysqlTypes.map_to_google_charts_types(query_result.field_types).to_json},
154
- "total_rows": #{query_result.size.to_json},
155
167
  "selection": #{params[:selection].to_json},
156
168
  "query": #{params[:sql].to_json},
157
169
  "rows": [
158
170
  RES
159
171
  out << json
160
- bytes = json.bytesize
172
+ bytes_written = json.bytesize
173
+ max_rows_written = false
174
+ rows_written = 0
175
+ total_rows = 0
161
176
  query_result.each_with_index do |row, i|
177
+ total_rows += 1
178
+ next if max_rows_written
179
+
162
180
  json = "#{i.zero? ? '' : ','}\n #{row.map { |v| big_decimal_to_float(v) }.to_json}"
163
- bytes += json.bytesize
164
- break if i == Sqlui::MAX_ROWS || bytes > Sqlui::MAX_BYTES
181
+ bytesize = json.bytesize
182
+ if bytes_written + bytesize > Sqlui::MAX_BYTES
183
+ max_rows_written = true
184
+ next
185
+ end
165
186
 
166
187
  out << json
188
+ bytes_written += bytesize
189
+ rows_written += 1
190
+
191
+ if rows_written == Sqlui::MAX_ROWS
192
+ max_rows_written = true
193
+ next
194
+ end
167
195
  end
168
196
  out << <<~RES
169
197
 
170
- ]
198
+ ],
199
+ "total_rows": #{total_rows}
171
200
  }
172
201
  RES
173
202
  else
@@ -198,9 +227,21 @@ class Server < Sinatra::Base
198
227
  attachment 'result.csv'
199
228
  status 200
200
229
 
201
- database.with_client do |client|
202
- query_result = execute_query(client, variables, queries)
203
- stream do |out|
230
+ stream do |out|
231
+ database.with_client do |client|
232
+ begin
233
+ query_result = execute_query(client, variables, queries)
234
+ rescue Mysql2::Error => e
235
+ stacktrace = e.full_message(highlight: false)
236
+ message = "ERROR #{e.error_number} (#{e.sql_state}): #{e.message.lines.first&.strip || 'unknown error'}"
237
+ out << { error: message, stacktrace: stacktrace }.compact.to_json
238
+ break
239
+ rescue StandardError => e
240
+ stacktrace = e.full_message(highlight: false)
241
+ message = e.message.lines.first&.strip || 'unknown error'
242
+ out << { error: message, stacktrace: stacktrace }.compact.to_json
243
+ break
244
+ end
204
245
  out << CSV::Row.new(query_result.fields, query_result.fields, header_row: true).to_s.strip
205
246
  query_result.each do |row|
206
247
  out << "\n#{CSV::Row.new(query_result.fields, row.map { |v| big_decimal_to_float(v) }).to_s.strip}"
@@ -230,7 +271,7 @@ class Server < Sinatra::Base
230
271
  stacktrace = exception&.full_message(highlight: false)
231
272
  if request.env['HTTP_ACCEPT'] == 'application/json'
232
273
  headers 'Content-Type' => 'application/json; charset=utf-8'
233
- message = exception&.message&.lines&.first&.strip || 'unexpected error'
274
+ message = "error: #{exception&.message&.lines&.first&.strip || 'unexpected error'}"
234
275
  json = { error: message, stacktrace: stacktrace }.compact.to_json
235
276
  body json
236
277
  else
@@ -274,7 +315,10 @@ class Server < Sinatra::Base
274
315
  variables.each do |name, value|
275
316
  client.query("SET @#{name} = #{value};")
276
317
  end
277
- queries.map { |current| client.query(current) }.last
318
+ queries[0..-2].map do |current|
319
+ client.query(current, stream: true)&.free
320
+ end
321
+ client.query(queries[-1], stream: true)
278
322
  end
279
323
 
280
324
  def big_decimal_to_float(maybe_big_decimal)
@@ -233,16 +233,15 @@ p {
233
233
  }
234
234
 
235
235
  #status-message {
236
- display: flex;
237
- justify-content: center;
238
- align-content: center;
239
- flex-direction: row;
236
+ min-width: 0;
237
+ justify-content: left;
240
238
  font-family: Helvetica, sans-serif;
241
239
  white-space: nowrap;
242
240
  overflow: hidden;
243
241
  font-size: 16px;
244
242
  color: #333;
245
- margin-left: 5px;
243
+ margin: 0 5px;
244
+ text-overflow: ellipsis;
246
245
  }
247
246
 
248
247
  #result-box, #fetch-sql-box, #saved-box, #graph-box, #structure-box {
@@ -255,6 +254,10 @@ table tbody tr td {
255
254
  height: 21px;
256
255
  }
257
256
 
257
+ #result-table td, #result-table th {
258
+ cursor: default;
259
+ }
260
+
258
261
  #result-table tbody tr td abbr a {
259
262
  color: #555;
260
263
  cursor: pointer;
@@ -264,6 +267,7 @@ table tbody tr td {
264
267
  border: 1px dotted #555;
265
268
  font-size: 12px;
266
269
  display: inline-block;
270
+ user-select: none;
267
271
  }
268
272
 
269
273
  #result-table tbody tr td abbr {
@@ -358,7 +362,8 @@ thead {
358
362
  }
359
363
 
360
364
  #status-box {
361
- padding: 5px;
365
+ width: 100%;
366
+ padding: 5px 0;
362
367
  display: flex;
363
368
  flex-direction: row;
364
369
  border-top: 1px solid #ddd;
@@ -485,11 +490,13 @@ select {
485
490
  #pagination-box {
486
491
  display: flex;
487
492
  flex-direction: row;
493
+ margin: 0 5px;
488
494
  }
489
495
 
490
496
  #page-count-box {
491
497
  align-self: center;
492
498
  font-size: 16px;
499
+ white-space: nowrap;
493
500
  }
494
501
 
495
502
  .pagination-button {
@@ -1514,10 +1514,11 @@
1514
1514
  /**
1515
1515
  Create a selection range.
1516
1516
  */
1517
- static range(anchor, head, goalColumn) {
1518
- let goal = (goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* RangeFlag.NoGoalColumn */) << 5 /* RangeFlag.GoalColumnOffset */;
1519
- return head < anchor ? SelectionRange.create(head, anchor, 16 /* RangeFlag.Inverted */ | goal | 8 /* RangeFlag.AssocAfter */)
1520
- : SelectionRange.create(anchor, head, goal | (head > anchor ? 4 /* RangeFlag.AssocBefore */ : 0));
1517
+ static range(anchor, head, goalColumn, bidiLevel) {
1518
+ let flags = ((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* RangeFlag.NoGoalColumn */) << 5 /* RangeFlag.GoalColumnOffset */) |
1519
+ (bidiLevel == null ? 3 : Math.min(2, bidiLevel));
1520
+ return head < anchor ? SelectionRange.create(head, anchor, 16 /* RangeFlag.Inverted */ | 8 /* RangeFlag.AssocAfter */ | flags)
1521
+ : SelectionRange.create(anchor, head, (head > anchor ? 4 /* RangeFlag.AssocBefore */ : 0) | flags);
1521
1522
  }
1522
1523
  /**
1523
1524
  @internal
@@ -4346,6 +4347,26 @@
4346
4347
  }
4347
4348
  }
4348
4349
  }
4350
+ function scrollableParent(dom) {
4351
+ let doc = dom.ownerDocument;
4352
+ for (let cur = dom.parentNode; cur;) {
4353
+ if (cur == doc.body) {
4354
+ break;
4355
+ }
4356
+ else if (cur.nodeType == 1) {
4357
+ if (cur.scrollHeight > cur.clientHeight || cur.scrollWidth > cur.clientWidth)
4358
+ return cur;
4359
+ cur = cur.assignedSlot || cur.parentNode;
4360
+ }
4361
+ else if (cur.nodeType == 11) {
4362
+ cur = cur.host;
4363
+ }
4364
+ else {
4365
+ break;
4366
+ }
4367
+ }
4368
+ return null;
4369
+ }
4349
4370
  class DOMSelectionState {
4350
4371
  constructor() {
4351
4372
  this.anchorNode = null;
@@ -7544,22 +7565,30 @@
7544
7565
  this.compositionFirstChange = null;
7545
7566
  this.compositionEndedAt = 0;
7546
7567
  this.mouseSelection = null;
7568
+ let handleEvent = (handler, event) => {
7569
+ if (this.ignoreDuringComposition(event))
7570
+ return;
7571
+ if (event.type == "keydown" && this.keydown(view, event))
7572
+ return;
7573
+ if (this.mustFlushObserver(event))
7574
+ view.observer.forceFlush();
7575
+ if (this.runCustomHandlers(event.type, view, event))
7576
+ event.preventDefault();
7577
+ else
7578
+ handler(view, event);
7579
+ };
7547
7580
  for (let type in handlers) {
7548
7581
  let handler = handlers[type];
7549
- view.contentDOM.addEventListener(type, (event) => {
7550
- if (!eventBelongsToEditor(view, event) || this.ignoreDuringComposition(event))
7551
- return;
7552
- if (type == "keydown" && this.keydown(view, event))
7553
- return;
7554
- if (this.mustFlushObserver(event))
7555
- view.observer.forceFlush();
7556
- if (this.runCustomHandlers(type, view, event))
7557
- event.preventDefault();
7558
- else
7559
- handler(view, event);
7582
+ view.contentDOM.addEventListener(type, event => {
7583
+ if (eventBelongsToEditor(view, event))
7584
+ handleEvent(handler, event);
7560
7585
  }, handlerOptions[type]);
7561
7586
  this.registeredEvents.push(type);
7562
7587
  }
7588
+ view.scrollDOM.addEventListener("mousedown", (event) => {
7589
+ if (event.target == view.scrollDOM)
7590
+ handleEvent(handlers.mousedown, event);
7591
+ });
7563
7592
  if (browser.chrome && browser.chrome_version == 102) { // FIXME remove at some point
7564
7593
  // On Chrome 102, viewport updates somehow stop wheel-based
7565
7594
  // scrolling. Turning off pointer events during the scroll seems
@@ -7716,12 +7745,18 @@
7716
7745
  const EmacsyPendingKeys = "dthko";
7717
7746
  // Key codes for modifier keys
7718
7747
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
7748
+ function dragScrollSpeed(dist) {
7749
+ return dist * 0.7 + 8;
7750
+ }
7719
7751
  class MouseSelection {
7720
7752
  constructor(view, startEvent, style, mustSelect) {
7721
7753
  this.view = view;
7722
7754
  this.style = style;
7723
7755
  this.mustSelect = mustSelect;
7756
+ this.scrollSpeed = { x: 0, y: 0 };
7757
+ this.scrolling = -1;
7724
7758
  this.lastEvent = startEvent;
7759
+ this.scrollParent = scrollableParent(view.contentDOM);
7725
7760
  let doc = view.contentDOM.ownerDocument;
7726
7761
  doc.addEventListener("mousemove", this.move = this.move.bind(this));
7727
7762
  doc.addEventListener("mouseup", this.up = this.up.bind(this));
@@ -7737,11 +7772,24 @@
7737
7772
  }
7738
7773
  }
7739
7774
  move(event) {
7775
+ var _a;
7740
7776
  if (event.buttons == 0)
7741
7777
  return this.destroy();
7742
7778
  if (this.dragging !== false)
7743
7779
  return;
7744
7780
  this.select(this.lastEvent = event);
7781
+ let sx = 0, sy = 0;
7782
+ let rect = ((_a = this.scrollParent) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect())
7783
+ || { left: 0, top: 0, right: this.view.win.innerWidth, bottom: this.view.win.innerHeight };
7784
+ if (event.clientX <= rect.left)
7785
+ sx = -dragScrollSpeed(rect.left - event.clientX);
7786
+ else if (event.clientX >= rect.right)
7787
+ sx = dragScrollSpeed(event.clientX - rect.right);
7788
+ if (event.clientY <= rect.top)
7789
+ sy = -dragScrollSpeed(rect.top - event.clientY);
7790
+ else if (event.clientY >= rect.bottom)
7791
+ sy = dragScrollSpeed(event.clientY - rect.bottom);
7792
+ this.setScrollSpeed(sx, sy);
7745
7793
  }
7746
7794
  up(event) {
7747
7795
  if (this.dragging == null)
@@ -7751,19 +7799,41 @@
7751
7799
  this.destroy();
7752
7800
  }
7753
7801
  destroy() {
7802
+ this.setScrollSpeed(0, 0);
7754
7803
  let doc = this.view.contentDOM.ownerDocument;
7755
7804
  doc.removeEventListener("mousemove", this.move);
7756
7805
  doc.removeEventListener("mouseup", this.up);
7757
7806
  this.view.inputState.mouseSelection = null;
7758
7807
  }
7808
+ setScrollSpeed(sx, sy) {
7809
+ this.scrollSpeed = { x: sx, y: sy };
7810
+ if (sx || sy) {
7811
+ if (this.scrolling < 0)
7812
+ this.scrolling = setInterval(() => this.scroll(), 50);
7813
+ }
7814
+ else if (this.scrolling > -1) {
7815
+ clearInterval(this.scrolling);
7816
+ this.scrolling = -1;
7817
+ }
7818
+ }
7819
+ scroll() {
7820
+ if (this.scrollParent) {
7821
+ this.scrollParent.scrollLeft += this.scrollSpeed.x;
7822
+ this.scrollParent.scrollTop += this.scrollSpeed.y;
7823
+ }
7824
+ else {
7825
+ this.view.win.scrollBy(this.scrollSpeed.x, this.scrollSpeed.y);
7826
+ }
7827
+ if (this.dragging === false)
7828
+ this.select(this.lastEvent);
7829
+ }
7759
7830
  select(event) {
7760
7831
  let selection = this.style.get(event, this.extend, this.multiple);
7761
7832
  if (this.mustSelect || !selection.eq(this.view.state.selection) ||
7762
7833
  selection.main.assoc != this.view.state.selection.main.assoc)
7763
7834
  this.view.dispatch({
7764
7835
  selection,
7765
- userEvent: "select.pointer",
7766
- scrollIntoView: true
7836
+ userEvent: "select.pointer"
7767
7837
  });
7768
7838
  this.mustSelect = false;
7769
7839
  }
@@ -7954,23 +8024,15 @@
7954
8024
  function basicMouseSelection(view, event) {
7955
8025
  let start = queryPos(view, event), type = getClickType(event);
7956
8026
  let startSel = view.state.selection;
7957
- let last = start, lastEvent = event;
7958
8027
  return {
7959
8028
  update(update) {
7960
8029
  if (update.docChanged) {
7961
8030
  start.pos = update.changes.mapPos(start.pos);
7962
8031
  startSel = startSel.map(update.changes);
7963
- lastEvent = null;
7964
8032
  }
7965
8033
  },
7966
8034
  get(event, extend, multiple) {
7967
- let cur;
7968
- if (lastEvent && event.clientX == lastEvent.clientX && event.clientY == lastEvent.clientY)
7969
- cur = last;
7970
- else {
7971
- cur = last = queryPos(view, event);
7972
- lastEvent = event;
7973
- }
8035
+ let cur = queryPos(view, event);
7974
8036
  let range = rangeForClick(view, cur.pos, cur.bias, type);
7975
8037
  if (start.pos != cur.pos && !extend) {
7976
8038
  let startRange = rangeForClick(view, start.pos, start.bias, type);
@@ -16967,7 +17029,7 @@
16967
17029
  */
16968
17030
  const defaultHighlightStyle = /*@__PURE__*/HighlightStyle.define([
16969
17031
  { tag: tags.meta,
16970
- color: "#7a757a" },
17032
+ color: "#404740" },
16971
17033
  { tag: tags.link,
16972
17034
  textDecoration: "underline" },
16973
17035
  { tag: tags.heading,
@@ -17898,7 +17960,7 @@
17898
17960
  function extendSel(view, how) {
17899
17961
  let selection = updateSel(view.state.selection, range => {
17900
17962
  let head = how(range);
17901
- return EditorSelection.range(range.anchor, head.head, head.goalColumn);
17963
+ return EditorSelection.range(range.anchor, head.head, head.goalColumn, head.bidiLevel || undefined);
17902
17964
  });
17903
17965
  if (selection.eq(view.state.selection))
17904
17966
  return false;
@@ -20091,6 +20153,7 @@
20091
20153
  closeOnBlur: true,
20092
20154
  maxRenderedOptions: 100,
20093
20155
  defaultKeymap: true,
20156
+ tooltipClass: () => "",
20094
20157
  optionClass: () => "",
20095
20158
  aboveCursor: false,
20096
20159
  icons: true,
@@ -20101,6 +20164,7 @@
20101
20164
  defaultKeymap: (a, b) => a && b,
20102
20165
  closeOnBlur: (a, b) => a && b,
20103
20166
  icons: (a, b) => a && b,
20167
+ tooltipClass: (a, b) => c => joinClass(a(c), b(c)),
20104
20168
  optionClass: (a, b) => c => joinClass(a(c), b(c)),
20105
20169
  addToOptions: (a, b) => a.concat(b)
20106
20170
  });
@@ -20179,14 +20243,17 @@
20179
20243
  key: this
20180
20244
  };
20181
20245
  this.space = null;
20246
+ this.currentClass = "";
20182
20247
  let cState = view.state.field(stateField);
20183
20248
  let { options, selected } = cState.open;
20184
20249
  let config = view.state.facet(completionConfig);
20185
20250
  this.optionContent = optionContent(config);
20186
20251
  this.optionClass = config.optionClass;
20252
+ this.tooltipClass = config.tooltipClass;
20187
20253
  this.range = rangeAroundSelected(options.length, selected, config.maxRenderedOptions);
20188
20254
  this.dom = document.createElement("div");
20189
20255
  this.dom.className = "cm-tooltip-autocomplete";
20256
+ this.updateTooltipClass(view.state);
20190
20257
  this.dom.addEventListener("mousedown", (e) => {
20191
20258
  for (let dom = e.target, match; dom && dom != this.dom; dom = dom.parentNode) {
20192
20259
  if (dom.nodeName == "LI" && (match = /-(\d+)$/.exec(dom.id)) && +match[1] < options.length) {
@@ -20207,12 +20274,25 @@
20207
20274
  var _a, _b, _c;
20208
20275
  let cState = update.state.field(this.stateField);
20209
20276
  let prevState = update.startState.field(this.stateField);
20277
+ this.updateTooltipClass(update.state);
20210
20278
  if (cState != prevState) {
20211
20279
  this.updateSel();
20212
20280
  if (((_a = cState.open) === null || _a === void 0 ? void 0 : _a.disabled) != ((_b = prevState.open) === null || _b === void 0 ? void 0 : _b.disabled))
20213
20281
  this.dom.classList.toggle("cm-tooltip-autocomplete-disabled", !!((_c = cState.open) === null || _c === void 0 ? void 0 : _c.disabled));
20214
20282
  }
20215
20283
  }
20284
+ updateTooltipClass(state) {
20285
+ let cls = this.tooltipClass(state);
20286
+ if (cls != this.currentClass) {
20287
+ for (let c of this.currentClass.split(" "))
20288
+ if (c)
20289
+ this.dom.classList.remove(c);
20290
+ for (let c of cls.split(" "))
20291
+ if (c)
20292
+ this.dom.classList.add(c);
20293
+ this.currentClass = cls;
20294
+ }
20295
+ }
20216
20296
  positioned(space) {
20217
20297
  this.space = space;
20218
20298
  if (this.info)
@@ -20473,13 +20553,13 @@
20473
20553
  if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
20474
20554
  active = this.active;
20475
20555
  let open = this.open;
20556
+ if (open && tr.docChanged)
20557
+ open = open.map(tr.changes);
20476
20558
  if (tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
20477
20559
  !sameResults(active, this.active))
20478
- open = CompletionDialog.build(active, state, this.id, this.open, conf);
20560
+ open = CompletionDialog.build(active, state, this.id, open, conf);
20479
20561
  else if (open && open.disabled && !active.some(a => a.state == 1 /* State.Pending */))
20480
20562
  open = null;
20481
- else if (open && tr.docChanged)
20482
- open = open.map(tr.changes);
20483
20563
  if (!open && active.every(a => a.state != 1 /* State.Pending */) && active.some(a => a.hasResult()))
20484
20564
  active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* State.Inactive */) : a);
20485
20565
  for (let effect of tr.effects)
@@ -24310,6 +24390,7 @@
24310
24390
  let drag = new Drag();
24311
24391
 
24312
24392
  document.addEventListener('mousedown', (event) => {
24393
+ if (event.button !== 0) return
24313
24394
  if (!event.target.classList.contains('col-resizer')) return
24314
24395
 
24315
24396
  drag = new Drag();
@@ -24366,10 +24447,11 @@
24366
24447
  });
24367
24448
 
24368
24449
  class ResizeTable extends HTMLTableElement {
24369
- constructor (columns, rows, cellRenderer) {
24450
+ constructor (columns, rows, headerRenderer, cellRenderer) {
24370
24451
  super();
24371
24452
 
24372
24453
  this.columns = columns;
24454
+ this.headerRenderer = headerRenderer;
24373
24455
  this.cellRenderer = cellRenderer;
24374
24456
 
24375
24457
  this.style.tableLayout = 'auto';
@@ -24408,7 +24490,11 @@
24408
24490
  resizerElement.dataset.colId = colElement.dataset.colId;
24409
24491
  contentWrapperElement.appendChild(resizerElement);
24410
24492
 
24411
- nameElement.innerText = column;
24493
+ if (headerRenderer) {
24494
+ nameElement.appendChild(headerRenderer(headerElement, index, column));
24495
+ } else {
24496
+ nameElement.innerText = column;
24497
+ }
24412
24498
  });
24413
24499
 
24414
24500
  headerTrElement.appendChild(document.createElement('th'));
@@ -24475,21 +24561,21 @@
24475
24561
  this.tbodyElement = tbodyElement;
24476
24562
 
24477
24563
  let highlight = false;
24478
- rows.forEach(function (row) {
24564
+ rows.forEach(function (row, rowIndex) {
24479
24565
  const rowElement = document.createElement('tr');
24480
24566
  if (highlight) {
24481
24567
  rowElement.classList.add('highlighted-row');
24482
24568
  }
24483
24569
  highlight = !highlight;
24484
24570
  tbodyElement.appendChild(rowElement);
24485
- row.forEach(function (value, index) {
24571
+ row.forEach(function (value, columnIndex) {
24572
+ const cellElement = document.createElement('td');
24486
24573
  if (cellRenderer) {
24487
- cellRenderer(rowElement, index, value);
24574
+ cellElement.appendChild(cellRenderer(cellElement, rowIndex, columnIndex, value));
24488
24575
  } else {
24489
- const cellElement = document.createElement('td');
24490
24576
  cellElement.innerText = value;
24491
- rowElement.appendChild(cellElement);
24492
24577
  }
24578
+ rowElement.appendChild(cellElement);
24493
24579
  });
24494
24580
  rowElement.appendChild(document.createElement('td'));
24495
24581
  });
@@ -24502,6 +24588,96 @@
24502
24588
 
24503
24589
  customElements.define('resize-table', ResizeTable, { extends: 'table' });
24504
24590
 
24591
+ function styleInject(css, ref) {
24592
+ if ( ref === void 0 ) ref = {};
24593
+ var insertAt = ref.insertAt;
24594
+
24595
+ if (!css || typeof document === 'undefined') { return; }
24596
+
24597
+ var head = document.head || document.getElementsByTagName('head')[0];
24598
+ var style = document.createElement('style');
24599
+ style.type = 'text/css';
24600
+
24601
+ if (insertAt === 'top') {
24602
+ if (head.firstChild) {
24603
+ head.insertBefore(style, head.firstChild);
24604
+ } else {
24605
+ head.appendChild(style);
24606
+ }
24607
+ } else {
24608
+ head.appendChild(style);
24609
+ }
24610
+
24611
+ if (style.styleSheet) {
24612
+ style.styleSheet.cssText = css;
24613
+ } else {
24614
+ style.appendChild(document.createTextNode(css));
24615
+ }
24616
+ }
24617
+
24618
+ var css_248z = ".popup_popup-wrapper__huCTg {\n position: absolute;\n background: rgba(0, 0, 0, 0.3);\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n z-index: 2;\n}\n\n.popup_popup-content__iGYws {\n position: absolute;\n top: 50px;\n left: 50px;\n height: calc(100% - 100px);\n width: calc(100% - 100px);\n background: #fff;\n border: 1px solid #888;\n}\n\n.popup_popup-pre__gF3-Q {\n position: absolute;\n top: 40px;\n left: 0;\n height: calc(100% - 112px);\n width: calc(100% - 20px);\n overflow: auto;\n font-size: 18px;\n border-top: 1px solid #ddd;\n border-bottom: 1px solid #ddd;\n margin: 0px;\n padding: 10px;\n background: #fff;\n font-family: monospace;\n}\n\n.popup_popup-pre__gF3-Q:focus {\n outline: none;\n}\n.popup_popup-close__3GBw3 {\n position: absolute;\n right: 20px;\n bottom: 10px;\n width: 100px;\n height: 30px;\n cursor: pointer;\n font-family: Helvetica, sans-serif;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n color: #333;\n border: 1px solid #888;\n background: #fff;\n}\n\n.popup_popup-close__3GBw3:active {\n background-color: #e6e6e6;\n}\n\n.popup_popup-title__W2N2J {\n position: absolute;\n left: 60px;\n top: 60px;\n font-family: Helvetica, sans-serif;\n font-size: 18px;\n color: #333;\n font-weight: bold;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n width: calc(100% - 120px);\n}\n";
24619
+ var styles = {"popup-wrapper":"popup_popup-wrapper__huCTg","popup-content":"popup_popup-content__iGYws","popup-pre":"popup_popup-pre__gF3-Q","popup-close":"popup_popup-close__3GBw3","popup-title":"popup_popup-title__W2N2J"};
24620
+ styleInject(css_248z);
24621
+
24622
+ function createPopup (title, text) {
24623
+ const wrapperElement = document.createElement('div');
24624
+ wrapperElement.id = 'popup';
24625
+ wrapperElement.classList.add(styles['popup-wrapper']);
24626
+ document.body.appendChild(wrapperElement);
24627
+
24628
+ const contentElement = document.createElement('div');
24629
+ contentElement.classList.add(styles['popup-content']);
24630
+ wrapperElement.appendChild(contentElement);
24631
+
24632
+ const closeElement = document.createElement('input');
24633
+ closeElement.id = 'popup-close';
24634
+ closeElement.classList.add(styles['popup-close']);
24635
+ closeElement.type = 'button';
24636
+ closeElement.value = 'Close';
24637
+ contentElement.appendChild(closeElement);
24638
+
24639
+ closeElement.addEventListener('click', (event) => {
24640
+ document.body.removeChild(wrapperElement);
24641
+ });
24642
+
24643
+ const titleElement = document.createElement('div');
24644
+ titleElement.classList.add(styles['popup-title']);
24645
+ titleElement.innerText = title;
24646
+ wrapperElement.appendChild(titleElement);
24647
+
24648
+ const preElement = document.createElement('pre');
24649
+ preElement.id = 'popup-pre';
24650
+ preElement.classList.add(styles['popup-pre']);
24651
+ contentElement.appendChild(preElement);
24652
+ preElement.innerText = text;
24653
+
24654
+ wrapperElement.addEventListener('click', () => {
24655
+ document.body.removeChild(wrapperElement);
24656
+ });
24657
+
24658
+ contentElement.addEventListener('click', (event) => {
24659
+ event.stopPropagation();
24660
+ });
24661
+
24662
+ closeElement.focus();
24663
+ }
24664
+
24665
+ document.addEventListener('keydown', (event) => {
24666
+ if (event.code === 'Escape') {
24667
+ const wrapperElement = document.getElementById('popup');
24668
+ if (wrapperElement) {
24669
+ event.preventDefault();
24670
+ document.body.removeChild(wrapperElement);
24671
+ }
24672
+ } else if (event.code === 'Tab') {
24673
+ const wrapperElement = document.getElementById('popup');
24674
+ if (wrapperElement) {
24675
+ event.preventDefault();
24676
+ document.getElementById('popup-close').focus();
24677
+ }
24678
+ }
24679
+ });
24680
+
24505
24681
  /* global google */
24506
24682
 
24507
24683
  const PAGE_SIZE = 100;
@@ -24630,6 +24806,7 @@
24630
24806
  dropdownContent.classList.remove('submit-dropdown-content-show');
24631
24807
  }
24632
24808
  });
24809
+
24633
24810
  window.editorView = createEditor(parent, window.metadata, onSubmit, onShiftSubmit);
24634
24811
  }
24635
24812
 
@@ -24857,13 +25034,11 @@
24857
25034
  }
24858
25035
  rows.push(row);
24859
25036
  }
24860
- const cellRenderer = function (rowElement, _columnIndex, value) {
24861
- const cellElement = document.createElement('td');
25037
+ const cellRenderer = function (cellElement, _rowIndex, _columnIndex, value) {
24862
25038
  cellElement.style.textAlign = (typeof value) === 'string' ? 'left' : 'right';
24863
- cellElement.innerText = value;
24864
- rowElement.appendChild(cellElement);
25039
+ return document.createTextNode(value)
24865
25040
  };
24866
- columnsElement.appendChild(new ResizeTable(columns, rows, cellRenderer));
25041
+ columnsElement.appendChild(new ResizeTable(columns, rows, null, cellRenderer));
24867
25042
  }
24868
25043
 
24869
25044
  const indexEntries = Object.entries(table.indexes);
@@ -24883,13 +25058,11 @@
24883
25058
  rows.push(row);
24884
25059
  }
24885
25060
  }
24886
- const cellRenderer = function (rowElement, _columnIndex, value) {
24887
- const cellElement = document.createElement('td');
25061
+ const cellRenderer = function (cellElement, _rowIndex, _columnIndex, value) {
24888
25062
  cellElement.style.textAlign = (typeof value) === 'string' ? 'left' : 'right';
24889
- cellElement.innerText = value;
24890
- rowElement.appendChild(cellElement);
25063
+ return document.createTextNode(value)
24891
25064
  };
24892
- indexesElement.appendChild(new ResizeTable(columns, rows, cellRenderer));
25065
+ indexesElement.appendChild(new ResizeTable(columns, rows, null, cellRenderer));
24893
25066
  }
24894
25067
  });
24895
25068
  window.structureLoaded = true;
@@ -25130,6 +25303,9 @@
25130
25303
  }
25131
25304
  }
25132
25305
  displaySqlFetch(sqlFetch);
25306
+ }).catch(function (error) {
25307
+ setSqlFetchError(sqlFetch, error);
25308
+ displaySqlFetch(sqlFetch);
25133
25309
  });
25134
25310
  } else {
25135
25311
  response.text().then((result) => {
@@ -25137,20 +25313,28 @@
25137
25313
  sqlFetch.errorMessage = 'failed to execute query';
25138
25314
  sqlFetch.errorDetails = result;
25139
25315
  displaySqlFetch(sqlFetch);
25316
+ }).catch(function (error) {
25317
+ setSqlFetchError(sqlFetch, error);
25318
+ displaySqlFetch(sqlFetch);
25140
25319
  });
25141
25320
  }
25142
25321
  })
25143
25322
  .catch(function (error) {
25144
- if (sqlFetch.state === 'pending') {
25145
- sqlFetch.endedAt = window.performance.now();
25146
- sqlFetch.state = 'error';
25147
- sqlFetch.errorMessage = 'failed to execute query';
25148
- sqlFetch.errorDetails = error;
25149
- }
25323
+ setSqlFetchError(sqlFetch, error);
25150
25324
  displaySqlFetch(sqlFetch);
25151
25325
  });
25152
25326
  }
25153
25327
 
25328
+ function setSqlFetchError (sqlFetch, error) {
25329
+ // Ignore the error unless pending since the error may be the result of aborting.
25330
+ if (sqlFetch.state === 'pending') {
25331
+ sqlFetch.endedAt = window.performance.now();
25332
+ sqlFetch.state = 'error';
25333
+ sqlFetch.errorMessage = 'failed to execute query';
25334
+ sqlFetch.errorDetails = error;
25335
+ }
25336
+ }
25337
+
25154
25338
  function parseSqlVariables (params) {
25155
25339
  return Object.fromEntries(
25156
25340
  Array.from(params).filter(([key]) => {
@@ -25272,10 +25456,17 @@
25272
25456
  return abbrElement
25273
25457
  };
25274
25458
 
25275
- const resultCellRenderer = function (rowElement, columnIndex, value) {
25459
+ const resultCellRenderer = function (cellElement, rowIndex, columnIndex, value) {
25276
25460
  const column = window.sqlFetch.result.columns[columnIndex];
25277
25461
  const columnType = window.sqlFetch.result.column_types[columnIndex];
25278
25462
 
25463
+ cellElement.dataset.column = columnIndex.toString();
25464
+ cellElement.dataset.row = rowIndex.toString();
25465
+
25466
+ if (typeof value === 'string' && value.indexOf('\n') >= 0) {
25467
+ value = value.replaceAll('\n', '¶');
25468
+ }
25469
+
25279
25470
  if (value && window.metadata.columns[column]?.links?.length > 0) {
25280
25471
  const linksElement = document.createElement('div');
25281
25472
  window.metadata.columns[column].links.forEach((link) => {
@@ -25292,17 +25483,22 @@
25292
25483
  wrapperElement.appendChild(linksElement);
25293
25484
  wrapperElement.appendChild(textElement);
25294
25485
 
25295
- const columnElement = document.createElement('td');
25296
- columnElement.appendChild(wrapperElement);
25297
- rowElement.appendChild(columnElement);
25486
+ return wrapperElement
25298
25487
  } else {
25299
- const cellElement = document.createElement('td');
25300
25488
  cellElement.style.textAlign = columnType === 'string' ? 'left' : 'right';
25301
- cellElement.innerText = value;
25302
- rowElement.appendChild(cellElement);
25489
+ return document.createTextNode(value)
25303
25490
  }
25304
25491
  };
25305
25492
 
25493
+ function resultHeaderRenderer (headerElement, columnIndex, value) {
25494
+ headerElement.dataset.column = columnIndex.toString();
25495
+
25496
+ if (typeof value === 'string' && value.indexOf('\n') >= 0) {
25497
+ value = value.replaceAll('\n', '¶');
25498
+ }
25499
+ return document.createTextNode(value)
25500
+ }
25501
+
25306
25502
  function displaySqlFetchInResultTab (fetch) {
25307
25503
  if (fetch.state === 'pending' || fetch.spinner === 'always') {
25308
25504
  clearResultBox();
@@ -25352,13 +25548,45 @@
25352
25548
  } else {
25353
25549
  clearResultBox();
25354
25550
  const resultBoxElement = document.getElementById('result-box');
25355
- tableElement = new ResizeTable(fetch.result.columns, rows, resultCellRenderer);
25551
+ tableElement = new ResizeTable(fetch.result.columns, rows, resultHeaderRenderer, resultCellRenderer);
25356
25552
  tableElement.id = 'result-table';
25553
+ registerTableCellPopup(tableElement);
25357
25554
  resultBoxElement.appendChild(tableElement);
25358
25555
  }
25359
25556
  tableElement.setAttribute('data-page', fetch.page);
25360
25557
  }
25361
25558
 
25559
+ function registerTableCellPopup (tableElement) {
25560
+ const listener = (event) => {
25561
+ if (event.which === 1 && event.detail === 2) {
25562
+ let node = event.target;
25563
+ while (!['td', 'th', 'table'].includes(node.tagName.toLowerCase()) && node.parentNode) {
25564
+ node = node.parentNode;
25565
+ }
25566
+ if (node.tagName.toLowerCase() === 'td') {
25567
+ if (event.type === 'mousedown') {
25568
+ const row = parseInt(node.dataset.row);
25569
+ const column = parseInt(node.dataset.column);
25570
+ const title = window.sqlFetch.result.columns[column].replaceAll('\n', '¶');
25571
+ createPopup(title, window.sqlFetch.result.rows[row][column]);
25572
+ }
25573
+ event.preventDefault();
25574
+ } else if (node.tagName.toLowerCase() === 'th') {
25575
+ if (event.type === 'mousedown') {
25576
+ const column = parseInt(node.dataset.column);
25577
+ const value = window.sqlFetch.result.columns[column];
25578
+ const title = value.replaceAll('\n', '¶');
25579
+ createPopup(title, value);
25580
+ }
25581
+ event.preventDefault();
25582
+ }
25583
+ }
25584
+ };
25585
+ // We only open the popup on mouseup but we need to preventDefault on mousedown to avoid the clicked text from
25586
+ // being highlighted.
25587
+ addEventListener(tableElement, 'mouseup', listener);
25588
+ addEventListener(tableElement, 'mousedown', listener);
25589
+ }
25362
25590
  function disableDownloadButtons () {
25363
25591
  document.getElementById('submit-dropdown-button-download-csv').classList.add('disabled');
25364
25592
  document.getElementById('submit-dropdown-button-copy-csv').classList.add('disabled');
@@ -25389,15 +25617,10 @@
25389
25617
  }
25390
25618
 
25391
25619
  function displaySqlFetchError (message, details) {
25392
- let statusMessage = 'error: ' + message;
25393
- if (statusMessage.length > 90) {
25394
- statusMessage = statusMessage.substring(0, 90) + '…';
25395
- }
25396
25620
  if (details) {
25397
- console.log(`${message}\n${details}`);
25398
- statusMessage += ' (check console)';
25621
+ console.log(details);
25399
25622
  }
25400
- setStatus(statusMessage);
25623
+ setStatus(message);
25401
25624
  }
25402
25625
 
25403
25626
  function clearSpinner () {
@@ -25505,14 +25728,14 @@
25505
25728
  const elapsed = Math.round(100 * (sqlFetch.getDuration() / 1000.0)) / 100;
25506
25729
 
25507
25730
  let message;
25508
- if (result.total_rows === 1) {
25509
- message = `${result.total_rows} row (${elapsed}s)`;
25731
+ if (result.rows.length === 1) {
25732
+ message = `1 row returned after ${elapsed}s`;
25510
25733
  } else {
25511
- message = `${result.total_rows.toLocaleString()} rows (${elapsed}s)`;
25734
+ message = `${result.rows.length.toLocaleString()} rows returned after ${elapsed}s`;
25512
25735
  }
25513
25736
 
25514
25737
  if (result.total_rows > result.rows.length) {
25515
- message += ` (truncated to ${result.rows.length.toLocaleString()})`;
25738
+ message += ` (truncated from ${result.total_rows.toLocaleString()})`;
25516
25739
  }
25517
25740
  setStatus(message);
25518
25741
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqlui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.59
4
+ version: 0.1.60
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Dower
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-11 00:00:00.000000000 Z
11
+ date: 2023-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: airbrake