sqlui 0.1.59 → 0.1.60

Sign up to get free protection for your applications and to get access to all the features.
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