playroom 0.28.2 → 0.29.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # playroom
2
2
 
3
+ ## 0.29.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9fc8c0d: Adds VSCode-style keybindings for move line up/down and copy line up/down.
8
+ Works for selections as well as single lines.
9
+
10
+ See the VSCode keyboard shortcut reference for details ([Mac]/[Windows]).
11
+
12
+ [mac]: https://code.visualstudio.com/shortcuts/keyboard-shortcuts-macos.pdf
13
+ [windows]: https://code.visualstudio.com/shortcuts/keyboard-shortcuts-windows.pdf
14
+
3
15
  ## 0.28.2
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playroom",
3
- "version": "0.28.2",
3
+ "version": "0.29.0",
4
4
  "description": "Design with code, powered by your own component library",
5
5
  "main": "utils/index.js",
6
6
  "types": "utils/index.d.ts",
@@ -1,6 +1,6 @@
1
1
  import React, { useRef, useContext, useEffect, useCallback } from 'react';
2
2
  import { useDebouncedCallback } from 'use-debounce';
3
- import CodeMirror, { Editor } from 'codemirror';
3
+ import CodeMirror, { Editor, Pos } from 'codemirror';
4
4
  import 'codemirror/lib/codemirror.css';
5
5
  import 'codemirror/theme/neo.css';
6
6
 
@@ -25,6 +25,154 @@ import 'codemirror/addon/fold/foldcode';
25
25
  import 'codemirror/addon/fold/foldgutter';
26
26
  import 'codemirror/addon/fold/brace-fold';
27
27
 
28
+ const directionToMethod = {
29
+ up: 'to',
30
+ down: 'from',
31
+ } as const;
32
+
33
+ type DuplicationDirection = keyof typeof directionToMethod;
34
+
35
+ const getNewPosition = (
36
+ range: CodeMirror.Range,
37
+ direction: DuplicationDirection
38
+ ) => {
39
+ const currentLine = range[directionToMethod[direction]]().line;
40
+
41
+ const newLine = direction === 'up' ? currentLine + 1 : currentLine;
42
+ return new Pos(newLine, 0);
43
+ };
44
+
45
+ const duplicateLine = (direction: DuplicationDirection) => (cm: Editor) =>
46
+ cm.operation(function () {
47
+ const ranges = cm.listSelections();
48
+
49
+ if (ranges.length > 1) {
50
+ // eslint-disable-next-line no-console
51
+ console.warn(
52
+ "The duplicate line command doesn't support multiple cursors yet. Please ask for this feature."
53
+ );
54
+ }
55
+
56
+ const range = ranges[0];
57
+
58
+ const existingContent = cm.getRange(
59
+ new Pos(range.from().line, 0),
60
+ new Pos(range.to().line)
61
+ );
62
+
63
+ const newContentParts = [existingContent, '\n'];
64
+
65
+ // Copy up on the last line has some unusual behaviour
66
+ if (range.to().line === cm.lastLine() && direction === 'up') {
67
+ newContentParts.reverse();
68
+ }
69
+
70
+ const newContent = newContentParts.join('');
71
+
72
+ cm.replaceRange(newContent, getNewPosition(range, direction));
73
+
74
+ // Copy up doesn't always handle its cursors correctly
75
+ if (direction === 'up') {
76
+ cm.setSelection(range.anchor, range.head);
77
+ }
78
+
79
+ cm.scrollIntoView(null);
80
+ });
81
+
82
+ const swapLineUp = (cm: Editor) => {
83
+ if (cm.isReadOnly()) {
84
+ return CodeMirror.Pass;
85
+ }
86
+
87
+ const ranges = cm.listSelections();
88
+
89
+ if (ranges.length > 1) {
90
+ // eslint-disable-next-line no-console
91
+ console.warn(
92
+ "The swap line command doesn't support multiple cursors yet. Please ask for this feature."
93
+ );
94
+ }
95
+
96
+ const range = ranges[0];
97
+
98
+ // If we're already at the top, do nothing
99
+ if (range.from().line > 0) {
100
+ const switchLineNumber = range.from().line - 1;
101
+ const switchLineContent = cm.getLine(switchLineNumber);
102
+
103
+ // Expand to the end of the selected lines
104
+ const rangeStart = new Pos(range.from().line, 0);
105
+ const rangeEnd = new Pos(range.to().line, undefined);
106
+
107
+ const rangeContent = cm.getRange(rangeStart, rangeEnd);
108
+
109
+ cm.operation(() => {
110
+ // Switch the order of the range and the preceding line
111
+ const newContent = [rangeContent, switchLineContent].join('\n');
112
+
113
+ cm.replaceRange(
114
+ newContent,
115
+ new Pos(switchLineNumber, 0),
116
+ rangeEnd,
117
+ '+swapLine'
118
+ );
119
+
120
+ // Shift the selection up by one line to match the moved content
121
+ cm.setSelection(
122
+ new Pos(range.anchor.line - 1, range.anchor.ch),
123
+ new Pos(range.head.line - 1, range.head.ch)
124
+ );
125
+ });
126
+ }
127
+ };
128
+
129
+ const swapLineDown = (cm: Editor) => {
130
+ if (cm.isReadOnly()) {
131
+ return CodeMirror.Pass;
132
+ }
133
+
134
+ const ranges = cm.listSelections();
135
+
136
+ if (ranges.length > 1) {
137
+ // eslint-disable-next-line no-console
138
+ console.warn(
139
+ "The swap line command doesn't support multiple cursors yet. Please ask for this feature."
140
+ );
141
+ }
142
+
143
+ const range = ranges[0];
144
+
145
+ // If we're already at the bottom, do nothing
146
+ if (range.to().line < cm.lastLine()) {
147
+ const switchLineNumber = range.to().line + 1;
148
+ const switchLineContent = cm.getLine(switchLineNumber);
149
+
150
+ // Expand to the end of the selected lines
151
+ const rangeStart = new Pos(range.from().line, 0);
152
+ const rangeEnd = new Pos(range.to().line, undefined);
153
+
154
+ const rangeContent = cm.getRange(rangeStart, rangeEnd);
155
+
156
+ cm.operation(() => {
157
+ // Switch the order of the range and the preceding line
158
+ const newContent = [switchLineContent, rangeContent].join('\n');
159
+
160
+ cm.replaceRange(
161
+ newContent,
162
+ rangeStart,
163
+ new Pos(switchLineNumber),
164
+ '+swapLine'
165
+ );
166
+
167
+ // Shift the selection down by one line to match the moved content
168
+ cm.setSelection(
169
+ new Pos(range.anchor.line + 1, range.anchor.ch),
170
+ new Pos(range.head.line + 1, range.head.ch)
171
+ );
172
+ });
173
+ }
174
+ };
175
+
28
176
  const completeAfter = (cm: Editor, predicate?: () => boolean) => {
29
177
  if (!predicate || predicate()) {
30
178
  setTimeout(() => {
@@ -284,6 +432,10 @@ export const CodeEditor = ({ code, onChange, previewCode, hints }: Props) => {
284
432
  "'/'": completeIfAfterLt,
285
433
  "' '": completeIfInTag,
286
434
  "'='": completeIfInTag,
435
+ 'Alt-Up': swapLineUp,
436
+ 'Alt-Down': swapLineDown,
437
+ 'Shift-Alt-Up': duplicateLine('up'),
438
+ 'Shift-Alt-Down': duplicateLine('down'),
287
439
  },
288
440
  }}
289
441
  />