@houstonp/rubiks-cube 1.2.2 → 1.4.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/README.md CHANGED
@@ -21,7 +21,7 @@ import '@houstonp/rubiks-cube';
21
21
  <meta charset="utf-8" />
22
22
  </head>
23
23
  <body>
24
- <rubiks-cube animation-speed="1000" animation-style="exponential" piece-gap="1.04"> </rubiks-cube>
24
+ <rubiks-cube animation-speed="1000" animation-style="exponential" piece-gap="1.04" camera-speed="100"> </rubiks-cube>
25
25
  <script type="module" src="index.js"></script>
26
26
  </body>
27
27
  </html>
@@ -31,57 +31,92 @@ import '@houstonp/rubiks-cube';
31
31
 
32
32
  | attribute | accepted values | Description |
33
33
  | --------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
- | animation-speed | integer greater than 0 | sets the speed of the animations in milliseconds |
34
+ | animation-speed | integer greater than or equal to 0 | sets the speed of the animations in milliseconds |
35
35
  | animation-style | "exponetial", "next", "fixed", "match" | fixed: fixed animation lengths, next: skips to next animation, exponential: speeds up successive animations, match: matches the speed the frequency of events |
36
36
  | piece-gap | greater than 1 | sets the gap between rubiks cube pieces |
37
+ | camera-speed | greater than or equal to 0 | sets the speed of camera animations in milliseconds |
37
38
 
38
39
  ## state of the component
39
40
 
40
- A state event occurs when a movement animation is completed. The event details contains the current state of the cube. The state is an object containing the stickers of each face. A sticker is either "up", "down", "left", "right", "front" or "back".
41
+ A state event occurs when a movement animation is completed. The event details contains the current state of the cube along with the eventId of the animation. The state is an object containing the stickers of each face. A sticker is either "up", "down", "left", "right", "front" or "back".
41
42
 
42
43
  To listen for the state event, add an event listener to the rubiks-cube element.
43
44
 
44
45
  ```js
45
46
  const cube = document.querySelector('rubiks-cube');
46
47
  cube.addEventListener('state', (e) => {
47
- console.log(e.detail.state);
48
+ console.log(e.detail);
48
49
  });
49
50
  /*
50
51
  {
51
- up: [
52
- [sticker, sticker, sticker],
53
- [sticker, sticker, sticker],
54
- [sticker, sticker, sticker],
55
- ],
56
- down: [
57
- [sticker, sticker, sticker],
58
- [sticker, sticker, sticker],
59
- [sticker, sticker, sticker],
60
- ],
61
- left: [
62
- [sticker, sticker, sticker],
63
- [sticker, sticker, sticker],
64
- [sticker, sticker, sticker],
65
- ],
66
- right: [
67
- [sticker, sticker, sticker],
68
- [sticker, sticker, sticker],
69
- [sticker, sticker, sticker],
70
- ],
71
- front: [
72
- [sticker, sticker, sticker],
73
- [sticker, sticker, sticker],
74
- [sticker, sticker, sticker],
75
- ],
76
- back: [
77
- [sticker, sticker, sticker],
78
- [sticker, sticker, sticker],
79
- [sticker, sticker, sticker],
80
- ],
52
+ eventId: "guid-guid-guid-guid-guid",
53
+ state: {
54
+ up: [
55
+ [sticker, sticker, sticker],
56
+ [sticker, sticker, sticker],
57
+ [sticker, sticker, sticker],
58
+ ],
59
+ down: [
60
+ [sticker, sticker, sticker],
61
+ [sticker, sticker, sticker],
62
+ [sticker, sticker, sticker],
63
+ ],
64
+ left: [
65
+ [sticker, sticker, sticker],
66
+ [sticker, sticker, sticker],
67
+ [sticker, sticker, sticker],
68
+ ],
69
+ right: [
70
+ [sticker, sticker, sticker],
71
+ [sticker, sticker, sticker],
72
+ [sticker, sticker, sticker],
73
+ ],
74
+ front: [
75
+ [sticker, sticker, sticker],
76
+ [sticker, sticker, sticker],
77
+ [sticker, sticker, sticker],
78
+ ],
79
+ back: [
80
+ [sticker, sticker, sticker],
81
+ [sticker, sticker, sticker],
82
+ [sticker, sticker, sticker],
83
+ ],
84
+ }
81
85
  }
82
86
  */
83
87
  ```
84
88
 
89
+ ## Rubiks Cube Notation
90
+
91
+ Notations can include the number of roations of a face. For example, `U2` means rotate the upper face 180 degrees.
92
+
93
+ Noations can also include a prime symbol `'` to indicate a counter clockwise rotation. For example, `U'` means rotate the upper face counter clockwise. The direction is always determined relative to the face being moved.
94
+
95
+ When both a number and a prime symbol are included the number is stated before the prime symbol. For example, `U2'` means rotate the upper face 180 degrees counter clockwise. and `U'2` is invalid.
96
+
97
+ | Notation | Movement |
98
+ | -------- | ------------------------------------------------ |
99
+ | U | Top face clockwise |
100
+ | u | Top two layers clockwise |
101
+ | D | Bottom face clockwise |
102
+ | d | Bottom two layers clockwise |
103
+ | L | Left face clockwise |
104
+ | l | Left two layers clockwise |
105
+ | R | Right face clockwise |
106
+ | r | Right two layers clockwise |
107
+ | F | Front face clockwise |
108
+ | f | Front two layers clockwise |
109
+ | B | Back face clockwise |
110
+ | b | Back two layers clockwise |
111
+ | M | Middle layer clockwise (relative to L) |
112
+ | E | Equatorial layer clockwise (relative to D) |
113
+ | S | Standing layer clockwise (relative to F) |
114
+ | x | Rotate cube on x axis clockwise (direction of R) |
115
+ | y | Rotate cube on y axis clockwise (direction of U) |
116
+ | z | Rotate cube on z axis clockwise (direction of F) |
117
+
118
+ these symbols are used as actionIds for the action event below.
119
+
85
120
  ## Updating the component
86
121
 
87
122
  The Rubiks cube web component listens for custom events to perform twists, rotations and camera changes. As per convention, the starting rotation has green facing forward, white facing up and red facing to the right.
@@ -97,11 +132,29 @@ const cube = document.querySelector('rubiks-cube');
97
132
  cube.dispatchEvent(new CustomEvent('reset'));
98
133
  ```
99
134
 
100
- ### Camera events
135
+ ### Action event
101
136
 
102
- The rubiks-cube element listens for the `camera` custom event and moves the camera to the specified position.
137
+ The rubiks-cube element listens for the `action` custom event and moves the camera to the specified position.
138
+
139
+ ```js
140
+ {
141
+ eventId: string,
142
+ action: {
143
+ type: "movement" | "camera" | "rotation",
144
+ actionId: string
145
+ }
146
+ }
147
+ ```
148
+
149
+ ```js
150
+ var event = new CustomEvent('action', {
151
+ detail: { eventId: 'guid-guid-guid-guid-guid', action: { type: 'camera', actionId: 'peek-toggle-horizontal' } },
152
+ });
153
+ ```
103
154
 
104
- The camera position specified in the event details must be one of the following:
155
+ #### Camera action event
156
+
157
+ action IDs for camera actions are as follows
105
158
 
106
159
  - `peek-right` - Camera is moved to the right of the cube so that the right face is visible
107
160
  - `peek-left` - Camera is moved to the left of the cube so that the left face is visible
@@ -110,61 +163,94 @@ The camera position specified in the event details must be one of the following:
110
163
  - `peek-toggle-horizontal` - Camera is moved to the opposite side of the cube in the horizontal plane
111
164
  - `peek-toggle-vertical` - Camera is moved to the opposite side of the cube in the vertical plane
112
165
 
113
- Note: The camera position cannot change to perform an equivalent cube rotation.
114
-
115
166
  #### Example
116
167
 
117
168
  ```js
118
169
  const cube = document.querySelector('rubiks-cube');
119
- cube.dispatchEvent(
120
- new CustomEvent('camera', {
121
- detail: { action: 'peek-right' },
122
- }),
123
- );
170
+ const event = new CustomEvent('action', {
171
+ detail: { eventId: 'guid-guid-guid-guid-guid', action: { type: 'camera', actionId: 'peek-toggle-horizontal' } },
172
+ });
173
+ cube.dispatchEvent(event);
124
174
  ```
125
175
 
126
- ### Rotation event
176
+ #### Rotation action event
127
177
 
128
- The rubiks-cube element listens for the `rotate` custom event and rotates a face or entire cube in the direction specified by the event details.
178
+ | Notation | Rotation |
179
+ | -------- | ------------------------------------------------ |
180
+ | x | Rotate cube on x axis clockwise (direction of R) |
181
+ | y | Rotate cube on y axis clockwise (direction of U) |
182
+ | z | Rotate cube on z axis clockwise (direction of F) |
129
183
 
130
- The rotation type specified in the event details must follow standard rubiks cube notation.
184
+ actionIDs for action type "rotation" are as follows
131
185
 
132
- #### Rubiks Cube Notation
186
+ - 'x',
187
+ - 'x2',
188
+ - "x'",
189
+ - 'y',
190
+ - 'y2',
191
+ - "y'",
192
+ - 'z',
193
+ - 'z2',
194
+ - "z'",
133
195
 
134
- Notations can include the number of roations of a face. For example, `U2` means rotate the upper face 180 degrees.
196
+ #### Example
135
197
 
136
- Noations can also include a prime symbol `'` to indicate a counter clockwise rotation. For example, `U'` means rotate the upper face counter clockwise. The direction is always determined relative to the face being moved.
198
+ ```js
199
+ const cube = document.querySelector('rubiks-cube');
200
+ const event = new CustomEvent('action', {
201
+ detail: { eventId: 'guid-guid-guid-guid-guid', action: { type: 'rotation', actionId: 'x' } },
202
+ });
203
+ cube.dispatchEvent(event);
204
+ ```
137
205
 
138
- When both a number and a prime symbol are included the number is stated before the prime symbol. For example, `U2'` means rotate the upper face 180 degrees counter clockwise. and `U'2` is invalid.
139
-
140
- | Notation | Movement |
141
- | -------- | ------------------------------------------------ |
142
- | U | Top face clockwise |
143
- | u | Top two layers clockwise |
144
- | D | Bottom face clockwise |
145
- | d | Bottom two layers clockwise |
146
- | L | Left face clockwise |
147
- | l | Left two layers clockwise |
148
- | R | Right face clockwise |
149
- | r | Right two layers clockwise |
150
- | F | Front face clockwise |
151
- | f | Front two layers clockwise |
152
- | B | Back face clockwise |
153
- | b | Back two layers clockwise |
154
- | M | Middle layer clockwise (relative to L) |
155
- | E | Equatorial layer clockwise (relative to D) |
156
- | S | Standing layer clockwise (relative to F) |
157
- | x | Rotate cube on x axis clockwise (direction of R) |
158
- | y | Rotate cube on y axis clockwise (direction of U) |
159
- | z | Rotate cube on z axis clockwise (direction of F) |
206
+ #### Movement action event
207
+
208
+ | Notation | Movement |
209
+ | -------- | ------------------------------------------ |
210
+ | U | Top face clockwise |
211
+ | u | Top two layers clockwise |
212
+ | D | Bottom face clockwise |
213
+ | d | Bottom two layers clockwise |
214
+ | L | Left face clockwise |
215
+ | l | Left two layers clockwise |
216
+ | R | Right face clockwise |
217
+ | r | Right two layers clockwise |
218
+ | F | Front face clockwise |
219
+ | f | Front two layers clockwise |
220
+ | B | Back face clockwise |
221
+ | b | Back two layers clockwise |
222
+ | M | Middle layer clockwise (relative to L) |
223
+ | E | Equatorial layer clockwise (relative to D) |
224
+ | S | Standing layer clockwise (relative to F) |
225
+
226
+ actionIDs for action type "movement" are as follows
227
+
228
+ - 'R',
229
+ - 'R2',
230
+ - "R'",
231
+ - 'L',
232
+ - 'L2',
233
+ - "L'",
234
+ - 'U',
235
+ - 'U2',
236
+ - "U'",
237
+ - 'D',
238
+ - 'D2',
239
+ - "D'",
240
+ - 'F',
241
+ - 'F2',
242
+ - "F'",
243
+ - 'B',
244
+ - 'B2',
245
+ - "B'",
246
+ - etc...
160
247
 
161
248
  #### Example
162
249
 
163
250
  ```js
164
251
  const cube = document.querySelector('rubiks-cube');
165
- cube.dispatchEvent(
166
- new CustomEvent('rotate', {
167
- detail: { action: "u2'" },
168
- }),
169
- );
252
+ const event = new CustomEvent('action', {
253
+ detail: { eventId: 'guid-guid-guid-guid-guid', action: { type: 'movement', actionId: 'U' } },
254
+ });
255
+ cube.dispatchEvent(event);
170
256
  ```
package/index.js CHANGED
@@ -6,6 +6,7 @@ import getRotationDetailsFromNotation from './src/utils/rotation';
6
6
  import { debounce } from './src/utils/debouncer';
7
7
 
8
8
  const defaultAnimationSpeed = 100;
9
+ const defaultCameraSpeed = 100;
9
10
  const defaultAnimationStyle = 'fixed';
10
11
  const defaultGap = 1.04;
11
12
  const minimumGap = 1;
@@ -15,21 +16,20 @@ class RubiksCube extends HTMLElement {
15
16
  super();
16
17
  /** @type {number} */
17
18
  this.animationSpeed = defaultAnimationSpeed;
18
- /** @type {"exponential" | "instant"} */
19
- this.animationStyle = this.getAttribute('animation-style') || defaultAnimationStyle;
20
19
  this.attachShadow({ mode: 'open' });
21
20
  this.shadowRoot.innerHTML = `<canvas id="cube-canvas" style="display:block;"></canvas>`;
22
21
  this.canvas = this.shadowRoot.getElementById('cube-canvas');
23
- /** @type {{animationStyle: "exponential" | "next" | "fixed" | "match", animationSpeed: number, gap: number}} */
22
+ /** @type {{animationStyle: "exponential" | "next" | "fixed" | "match", animationSpeed: number, gap: number, cameraSpeed: number}} */
24
23
  this.settings = {
25
24
  animationSpeed: this.getAttribute('animation-speed') || defaultAnimationSpeed,
26
25
  animationStyle: this.getAttribute('animation-style') || defaultAnimationStyle,
27
26
  gap: this.getAttribute('piece-gap') || defaultGap,
27
+ cameraSpeed: this.getAttribute('camera-speed') || defaultCameraSpeed,
28
28
  };
29
29
  }
30
30
 
31
31
  static get observedAttributes() {
32
- return ['animation-style', 'animation-speed', 'piece-gap'];
32
+ return ['animation-style', 'animation-speed', 'piece-gap', 'camera-speed'];
33
33
  }
34
34
 
35
35
  attributeChangedCallback(name, oldVal, newVal) {
@@ -44,6 +44,10 @@ class RubiksCube extends HTMLElement {
44
44
  var gap = Number(newVal);
45
45
  this.settings.gap = gap < minimumGap ? minimumGap : gap;
46
46
  }
47
+ if (name === 'camera-speed') {
48
+ var speed = Number(newVal);
49
+ this.settings.cameraSpeed = speed > 0 ? speed : 0;
50
+ }
47
51
  }
48
52
  connectedCallback() {
49
53
  this.init();
@@ -110,9 +114,8 @@ class RubiksCube extends HTMLElement {
110
114
  const cameraAnimationGroup = new Group();
111
115
  cameraAnimationGroup.add(new Tween(camera.position).to({ x: 2.5, y: 2.5, z: 4 }, 1000).easing(Easing.Cubic.InOut).start());
112
116
 
113
- const sendState = () => {
114
- const state = cube.getStickerState();
115
- const event = new CustomEvent('state', { detail: { state } });
117
+ const sendState = (eventId) => {
118
+ const event = new CustomEvent('state', { detail: { eventId, state: cube.currentState } });
116
119
  this.dispatchEvent(event);
117
120
  };
118
121
 
@@ -121,9 +124,9 @@ class RubiksCube extends HTMLElement {
121
124
  cameraAnimationGroup.update();
122
125
  controls.update();
123
126
 
124
- var cubeState = cube.update();
125
- if (cubeState) {
126
- sendState();
127
+ var eventId = cube.update();
128
+ if (eventId) {
129
+ sendState(eventId);
127
130
  }
128
131
  renderer.render(scene, camera);
129
132
  }
@@ -131,28 +134,49 @@ class RubiksCube extends HTMLElement {
131
134
  // add event listeners for rotation and camera controls
132
135
  this.addEventListener('reset', () => {
133
136
  cube.reset();
134
- sendState();
137
+ sendState('reset');
135
138
  });
136
139
 
137
- this.addEventListener('rotate', (e) => {
138
- const action = getRotationDetailsFromNotation(e.detail.action);
139
- if (action !== undefined) {
140
- cube.rotate(action);
140
+ this.addEventListener('action', (e) => {
141
+ /** @type {{eventId: string, action: {type: "movement" | "camera" | "rotation", actionId: string }}} move */
142
+ var move = e.detail.move;
143
+ console.log(move);
144
+ if (move.action.type === 'camera') {
145
+ handleCameraAction(move.action.actionId);
146
+ return;
147
+ }
148
+ if (move.action.type === 'movement' || move.action.type === 'rotation') {
149
+ handleRotationAction(move.eventId, move.action.actionId);
150
+ return;
141
151
  }
142
152
  });
143
153
 
144
- this.addEventListener('camera', (e) => {
145
- if (e.detail.action === 'peek-toggle-horizontal') {
154
+ /**
155
+ * @param {string} eventId
156
+ * @param {string} actionId
157
+ */
158
+ const handleRotationAction = (eventId, actionId) => {
159
+ const rotationDetails = getRotationDetailsFromNotation(actionId);
160
+ if (rotationDetails !== undefined) {
161
+ cube.rotate(eventId, rotationDetails);
162
+ }
163
+ };
164
+
165
+ /**
166
+ * @param {'peek-toggle-horizontal' | 'peek-toggle-vertical' | 'peek-right' | 'peek-left' | 'peek-up' | 'peek-down'} actionId
167
+ */
168
+ const handleCameraAction = (actionId) => {
169
+ if (actionId === 'peek-toggle-horizontal') {
146
170
  cameraState.Right = !cameraState.Right;
147
- } else if (e.detail.action === 'peek-toggle-vertical') {
171
+ } else if (actionId === 'peek-toggle-vertical') {
148
172
  cameraState.Up = !cameraState.Up;
149
- } else if (e.detail.action === 'peek-right') {
173
+ } else if (actionId === 'peek-right') {
150
174
  cameraState.Right = true;
151
- } else if (e.detail.action === 'peek-left') {
175
+ } else if (actionId === 'peek-left') {
152
176
  cameraState.Right = false;
153
- } else if (e.detail.action === 'peek-up') {
177
+ } else if (actionId === 'peek-up') {
154
178
  cameraState.Up = true;
155
- } else if (e.detail.action === 'peek-down') {
179
+ } else if (actionId === 'peek-down') {
156
180
  cameraState.Up = false;
157
181
  }
158
182
  cameraAnimationGroup.add(
@@ -163,11 +187,11 @@ class RubiksCube extends HTMLElement {
163
187
  y: cameraState.Up ? cameraState.UpDistance : -cameraState.UpDistance,
164
188
  z: 4,
165
189
  },
166
- 200,
190
+ this.settings.cameraSpeed,
167
191
  )
168
192
  .start(),
169
193
  );
170
- });
194
+ };
171
195
  }
172
196
  }
173
197
  customElements.define('rubiks-cube', RubiksCube);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@houstonp/rubiks-cube",
3
- "version": "1.2.2",
3
+ "version": "1.4.0",
4
4
  "description": "Rubiks Cube Web Component built with threejs",
5
5
  "main": "index.js",
6
6
  "author": "Houston Pearse",
package/src/cube/cube.js CHANGED
@@ -18,6 +18,8 @@ export default class Cube {
18
18
  this.rotationQueue = [];
19
19
  /** @type {CubeRotation | undefined} */
20
20
  this.currentRotation = undefined;
21
+ /** @type {{ up: string[][], down: string[][], front: string[][], back: string[][], left: string[][], right: string[][] }} */
22
+ this.currentState = this.getStickerState();
21
23
  /** @type {number | undefined} */
22
24
  this._matchSpeed = undefined;
23
25
  /** @type {number} */
@@ -82,8 +84,10 @@ export default class Cube {
82
84
  }
83
85
  if (this.currentRotation.status === 'complete') {
84
86
  this.clearRotationGroup();
87
+ var eventId = this.currentRotation.eventId;
85
88
  this.currentRotation = undefined;
86
- return this.getStickerState();
89
+ this.currentState = this.getStickerState();
90
+ return eventId;
87
91
  }
88
92
  return undefined;
89
93
  }
@@ -194,13 +198,11 @@ export default class Cube {
194
198
  }
195
199
 
196
200
  /**
201
+ * @param {string} eventId
197
202
  * @param {{axis: "x"|"y"|"z", layers: (-1|0|1)[], direction: 1|-1|2|-2}} input
198
203
  */
199
- rotate(input) {
200
- var queueLength = this.rotationQueue.length;
201
- if (queueLength > 0 && this.rotationQueue[queueLength - 1].rotation.axis === input.axis) {
202
- }
203
- this.rotationQueue.push(new CubeRotation(input));
204
+ rotate(eventId, input) {
205
+ this.rotationQueue.push(new CubeRotation(eventId, input));
204
206
  }
205
207
 
206
208
  /**
@@ -2,11 +2,14 @@ import { Vector3, Group } from 'three';
2
2
 
3
3
  export class CubeRotation {
4
4
  /**
5
- * @param {{axis: "x"|"y"|"z", layers: (-1|0|1)[], direction: 1|-1|2|-2}} input
5
+ * @param {string} eventId
6
+ * @param {{axis: "x"|"y"|"z", layers: (-1|0|1)[], direction: 1|-1|2|-2}} rotationDetails
6
7
  */
7
- constructor(rotation) {
8
+ constructor(eventId, rotationDetails) {
9
+ /** @type {string} */
10
+ this.eventId = eventId;
8
11
  /** @type {{axis: "x"|"y"|"z", layers: (-1|0|1)[], direction: 1|-1|2|-2}} */
9
- this.rotation = rotation;
12
+ this.rotation = rotationDetails;
10
13
  /** @type {"pending" | "initialised" | "complete" | "disposed"} */
11
14
  this.status = 'pending';
12
15
  /** @type {number} */