mbeditor 0.12.9 → 0.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +150 -0
  3. data/app/assets/javascripts/mbeditor/application.js +7 -1
  4. data/app/assets/javascripts/mbeditor/collaboration_service.js +31 -0
  5. data/app/assets/javascripts/mbeditor/color_provider.js +5 -0
  6. data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +3 -1
  7. data/app/assets/javascripts/mbeditor/components/EditorPanel.js +102 -25
  8. data/app/assets/javascripts/mbeditor/components/FileTree.js +6 -1
  9. data/app/assets/javascripts/mbeditor/components/GitPanel.js +10 -1
  10. data/app/assets/javascripts/mbeditor/components/ImportConflictModal.js +15 -8
  11. data/app/assets/javascripts/mbeditor/components/ImportDialog.js +216 -0
  12. data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +598 -130
  13. data/app/assets/javascripts/mbeditor/components/ModelGraph.js +25 -6
  14. data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +162 -6
  15. data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +39 -33
  16. data/app/assets/javascripts/mbeditor/components/TabBar.js +11 -2
  17. data/app/assets/javascripts/mbeditor/components/TestRunPanel.js +312 -0
  18. data/app/assets/javascripts/mbeditor/editor_plugins.js +427 -111
  19. data/app/assets/javascripts/mbeditor/file_import.js +78 -0
  20. data/app/assets/javascripts/mbeditor/file_service.js +122 -18
  21. data/app/assets/javascripts/mbeditor/git_service.js +130 -8
  22. data/app/assets/javascripts/mbeditor/history_service.js +33 -38
  23. data/app/assets/javascripts/mbeditor/log_service.js +4 -0
  24. data/app/assets/javascripts/mbeditor/search_service.js +30 -2
  25. data/app/assets/javascripts/mbeditor/tab_manager.js +59 -6
  26. data/app/assets/javascripts/mbeditor/websocket_service.js +88 -4
  27. data/app/assets/stylesheets/mbeditor/editor.css +268 -53
  28. data/app/channels/mbeditor/channel_authentication.rb +7 -0
  29. data/app/channels/mbeditor/editor_channel.rb +6 -3
  30. data/app/controllers/mbeditor/application_controller.rb +5 -0
  31. data/app/controllers/mbeditor/editors_controller.rb +173 -29
  32. data/app/controllers/mbeditor/git_controller.rb +3 -3
  33. data/app/controllers/mbeditor/logs_controller.rb +3 -1
  34. data/app/services/mbeditor/collaboration_doc_store.rb +7 -0
  35. data/app/services/mbeditor/editor_state_service.rb +36 -26
  36. data/app/services/mbeditor/exclusion_matcher.rb +12 -10
  37. data/app/services/mbeditor/file_operation_service.rb +38 -3
  38. data/app/services/mbeditor/file_tree_service.rb +30 -2
  39. data/app/services/mbeditor/git_combined_diff_service.rb +13 -3
  40. data/app/services/mbeditor/git_commit_detail_service.rb +20 -16
  41. data/app/services/mbeditor/git_diff_service.rb +5 -1
  42. data/app/services/mbeditor/git_info_service.rb +20 -8
  43. data/app/services/mbeditor/git_line_diff_service.rb +6 -2
  44. data/app/services/mbeditor/git_service.rb +65 -15
  45. data/app/services/mbeditor/js_definition_service.rb +3 -1
  46. data/app/services/mbeditor/js_globals_service.rb +7 -4
  47. data/app/services/mbeditor/js_members_service.rb +3 -2
  48. data/app/services/mbeditor/js_program_service.rb +15 -5
  49. data/app/services/mbeditor/js_syntax_check_service.rb +15 -4
  50. data/app/services/mbeditor/process_runner.rb +42 -12
  51. data/app/services/mbeditor/ri_definition_service.rb +8 -1
  52. data/app/services/mbeditor/route_service.rb +45 -8
  53. data/app/services/mbeditor/rubocop_run_service.rb +86 -0
  54. data/app/services/mbeditor/ruby_definition_service.rb +23 -4
  55. data/app/services/mbeditor/schema_service.rb +65 -64
  56. data/app/services/mbeditor/search_replace_service.rb +31 -5
  57. data/app/services/mbeditor/test_runner_service.rb +98 -10
  58. data/lib/mbeditor/cable_log_filter.rb +8 -2
  59. data/lib/mbeditor/configuration.rb +12 -1
  60. data/lib/mbeditor/editor_bootstrap.rb +18 -12
  61. data/lib/mbeditor/engine.rb +22 -3
  62. data/lib/mbeditor/exception_log.rb +2 -2
  63. data/lib/mbeditor/pending_migrations.rb +33 -0
  64. data/lib/mbeditor/rack/handle_pending_migrations.rb +25 -15
  65. data/lib/mbeditor/rack/pending_migration_bypass.rb +104 -0
  66. data/lib/mbeditor/rack/silence_ping_request.rb +10 -2
  67. data/lib/mbeditor/route_map.rb +2 -0
  68. data/lib/mbeditor/ruby_lsp_client.rb +71 -12
  69. data/lib/mbeditor/version.rb +1 -1
  70. metadata +7 -2
@@ -0,0 +1,216 @@
1
+ 'use strict';
2
+
3
+ // Upload dialog: the pointed-at half of the drag-and-drop import.
4
+ //
5
+ // Everything past "pick files" is the existing import pipeline — the same
6
+ // entries shape, the same /import call, the same ImportConflictModal for
7
+ // replace/keep-both/skip. The only thing this adds is *where* the files land,
8
+ // which drag-and-drop answers by what you dropped onto and a file picker
9
+ // cannot answer at all.
10
+ //
11
+ // Destination suggestions come from the file tree already in memory
12
+ // (SearchService's index), so picking a folder that lives at
13
+ // app/assets/javascripts/ux/component offers that prefix rather than dumping
14
+ // `ux/component/` at the workspace root. The root stays on offer as the
15
+ // "create the structure from scratch" option.
16
+ var ImportDialog = function ImportDialog(_ref) {
17
+ var initialFolder = _ref.initialFolder || '';
18
+ var docs = _ref.docs || [];
19
+ var onCancel = _ref.onCancel;
20
+ var onImport = _ref.onImport;
21
+
22
+ var modalRef = React.useRef(null);
23
+ var filesInputRef = React.useRef(null);
24
+ var folderInputRef = React.useRef(null);
25
+
26
+ var _picked = React.useState(null);
27
+ var picked = _picked[0];
28
+ var setPicked = _picked[1];
29
+
30
+ var _dest = React.useState(initialFolder);
31
+ var dest = _dest[0];
32
+ var setDest = _dest[1];
33
+
34
+ window.useModalFocusTrap(modalRef, onCancel);
35
+
36
+ // webkitdirectory has no JSX/React prop, and React strips unknown lowercase
37
+ // attributes on <input>, so it is set on the DOM node directly.
38
+ React.useEffect(function () {
39
+ var el = folderInputRef.current;
40
+ if (!el) return;
41
+ el.setAttribute('webkitdirectory', '');
42
+ el.setAttribute('directory', '');
43
+ }, []);
44
+
45
+ var entries = (picked && picked.entries) || [];
46
+
47
+ var suggestions = React.useMemo(function () {
48
+ if (entries.length === 0) return [];
49
+ return FileImport.suggestDestinations(entries, docs);
50
+ }, [picked, docs]);
51
+
52
+ var existingFiles = React.useMemo(function () {
53
+ var set = new Set();
54
+ docs.forEach(function (d) { if (d.type === 'file') set.add(d.path); });
55
+ return set;
56
+ }, [docs]);
57
+
58
+ var onPick = function (e) {
59
+ var result = FileImport.entriesFromFileList(e.target.files);
60
+ e.target.value = ''; // so picking the same folder twice still fires change
61
+ if (result.entries.length === 0) return;
62
+ setPicked(result);
63
+ };
64
+
65
+ var normalisedDest = String(dest || '').trim().replace(/^\/+|\/+$/g, '');
66
+
67
+ var destOption = function (prefix, label, hint) {
68
+ var active = normalisedDest === prefix;
69
+ return React.createElement(
70
+ 'button',
71
+ {
72
+ key: '$' + prefix,
73
+ type: 'button',
74
+ className: 'import-dest-option' + (active ? ' active' : ''),
75
+ onClick: function () { setDest(prefix); }
76
+ },
77
+ React.createElement('i', { className: (active ? 'fas fa-dot-circle' : 'far fa-circle') + ' import-dest-radio' }),
78
+ React.createElement('span', { className: 'import-dest-label' }, label),
79
+ hint && React.createElement('span', { className: 'import-dest-hint' }, hint)
80
+ );
81
+ };
82
+
83
+ var offered = {};
84
+ var options = [];
85
+ var offer = function (prefix, label, hint) {
86
+ if (offered['$' + prefix]) return;
87
+ offered['$' + prefix] = true;
88
+ options.push(destOption(prefix, label, hint));
89
+ };
90
+
91
+ if (initialFolder) offer(initialFolder, initialFolder, 'selected folder');
92
+ suggestions.forEach(function (s) {
93
+ var bits = [];
94
+ if (s.files) bits.push(s.files + ' matching file' + (s.files === 1 ? '' : 's'));
95
+ if (s.dirs) bits.push(s.dirs + ' matching folder' + (s.dirs === 1 ? '' : 's'));
96
+ offer(s.prefix, s.prefix, bits.join(', '));
97
+ });
98
+ offer('', 'Workspace root', 'create the folders from the root');
99
+
100
+ var targets = entries.map(function (e) {
101
+ return FileImport.joinPath(normalisedDest, e.relativePath);
102
+ });
103
+ var replacing = targets.filter(function (t) { return existingFiles.has(t); }).length;
104
+
105
+ return React.createElement(
106
+ 'div',
107
+ { className: 'schema-modal-overlay', onClick: onCancel },
108
+ React.createElement(
109
+ 'div',
110
+ {
111
+ ref: modalRef,
112
+ className: 'schema-modal import-dialog',
113
+ role: 'dialog',
114
+ 'aria-modal': 'true',
115
+ 'aria-label': 'Upload files',
116
+ tabIndex: -1,
117
+ onClick: function (e) { e.stopPropagation(); }
118
+ },
119
+ React.createElement(
120
+ 'div', { className: 'schema-modal-header' },
121
+ React.createElement('div', { className: 'schema-modal-title' },
122
+ React.createElement('i', { className: 'fas fa-upload', style: { marginRight: '8px', opacity: 0.7 } }),
123
+ 'Upload files'),
124
+ React.createElement(
125
+ 'button',
126
+ { className: 'schema-modal-close', onClick: onCancel, title: 'Close' },
127
+ React.createElement('i', { className: 'fas fa-times' })
128
+ )
129
+ ),
130
+ React.createElement(
131
+ 'div', { className: 'schema-modal-body' },
132
+
133
+ React.createElement(
134
+ 'div', { className: 'import-pick-row' },
135
+ React.createElement(
136
+ 'button',
137
+ { type: 'button', className: 'import-pick-btn',
138
+ onClick: function () { filesInputRef.current && filesInputRef.current.click(); } },
139
+ React.createElement('i', { className: 'far fa-file' }), ' Choose files…'
140
+ ),
141
+ React.createElement(
142
+ 'button',
143
+ { type: 'button', className: 'import-pick-btn',
144
+ onClick: function () { folderInputRef.current && folderInputRef.current.click(); } },
145
+ React.createElement('i', { className: 'far fa-folder' }), ' Choose folder…'
146
+ ),
147
+ React.createElement('input', {
148
+ ref: filesInputRef, type: 'file', multiple: true,
149
+ style: { display: 'none' }, onChange: onPick
150
+ }),
151
+ React.createElement('input', {
152
+ ref: folderInputRef, type: 'file', multiple: true,
153
+ style: { display: 'none' }, onChange: onPick
154
+ })
155
+ ),
156
+
157
+ entries.length === 0 && React.createElement(
158
+ 'div', { className: 'import-dialog-empty' },
159
+ 'Pick files or a folder to upload. You can also drag them onto the explorer.'
160
+ ),
161
+
162
+ picked && picked.truncated && React.createElement(
163
+ 'div', { className: 'import-dialog-warning' },
164
+ 'Only the first ' + FileImport.MAX_ENTRIES + ' files will be uploaded.'
165
+ ),
166
+
167
+ entries.length > 0 && React.createElement(
168
+ React.Fragment, null,
169
+ React.createElement('div', { className: 'import-dialog-section-title' }, 'Destination'),
170
+ React.createElement('div', { className: 'import-dest-options' }, options),
171
+ React.createElement('input', {
172
+ className: 'import-dest-input',
173
+ type: 'text',
174
+ value: dest,
175
+ placeholder: 'workspace root',
176
+ spellCheck: false,
177
+ autoComplete: 'off',
178
+ 'aria-label': 'Destination folder',
179
+ onChange: function (e) { setDest(e.target.value); }
180
+ }),
181
+
182
+ React.createElement('div', { className: 'import-dialog-section-title' },
183
+ entries.length + ' file' + (entries.length === 1 ? '' : 's') +
184
+ (replacing > 0 ? ' — ' + replacing + ' will replace an existing file' : '')),
185
+ React.createElement(
186
+ 'ul', { className: 'import-conflict-list' },
187
+ targets.map(function (t, i) {
188
+ return React.createElement(
189
+ 'li', { key: t + '#' + i, title: t },
190
+ t,
191
+ existingFiles.has(t) && React.createElement(
192
+ 'span', { className: 'import-target-replaces' }, 'replaces')
193
+ );
194
+ })
195
+ )
196
+ )
197
+ ),
198
+ React.createElement(
199
+ 'div', { className: 'import-conflict-actions' },
200
+ React.createElement('button', { className: 'import-conflict-btn', onClick: onCancel }, 'Cancel'),
201
+ React.createElement(
202
+ 'button',
203
+ {
204
+ className: 'import-conflict-btn import-conflict-btn-primary',
205
+ disabled: entries.length === 0,
206
+ onClick: function () { onImport(entries, normalisedDest, picked); }
207
+ },
208
+ entries.length === 0 ? 'Upload' : 'Upload ' + entries.length + ' file' + (entries.length === 1 ? '' : 's')
209
+ )
210
+ )
211
+ )
212
+ );
213
+ };
214
+
215
+ // Expose globally for sprockets require
216
+ window.ImportDialog = ImportDialog;