@dxos/shell 0.3.11-main.ec7a2b3 → 0.3.11-main.ee2b64c

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 (85) hide show
  1. package/dist/bundle/shell.mjs +15272 -13589
  2. package/dist/bundle/shell.mjs.map +1 -1
  3. package/dist/bundle/shell.umd.js +81 -75
  4. package/dist/bundle/shell.umd.js.map +1 -1
  5. package/dist/bundle/style.css +1 -1
  6. package/dist/lib/browser/meta.json +1 -1
  7. package/dist/lib/browser/react.mjs +190 -174
  8. package/dist/lib/browser/react.mjs.map +4 -4
  9. package/dist/lib/browser/testing/index.mjs +35 -14
  10. package/dist/lib/browser/testing/index.mjs.map +3 -3
  11. package/dist/types/src/components/IdentityList/SpaceMemberList.js +1 -1
  12. package/dist/types/src/components/IdentityList/SpaceMemberList.js.map +1 -1
  13. package/dist/types/src/components/Panel/Action.d.ts.map +1 -1
  14. package/dist/types/src/components/Panel/Action.js +3 -18
  15. package/dist/types/src/components/Panel/Action.js.map +1 -1
  16. package/dist/types/src/panels/IdentityPanel/IdentityPanel.d.ts.map +1 -1
  17. package/dist/types/src/panels/IdentityPanel/IdentityPanel.js +6 -2
  18. package/dist/types/src/panels/IdentityPanel/IdentityPanel.js.map +1 -1
  19. package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts +9 -1
  20. package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts.map +1 -1
  21. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.d.ts +3 -0
  22. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.d.ts.map +1 -0
  23. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.js +26 -0
  24. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.js.map +1 -0
  25. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts.map +1 -1
  26. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js +1 -1
  27. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js.map +1 -1
  28. package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.js +2 -2
  29. package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.js.map +1 -1
  30. package/dist/types/src/panels/IdentityPanel/steps/index.d.ts +1 -1
  31. package/dist/types/src/panels/IdentityPanel/steps/index.d.ts.map +1 -1
  32. package/dist/types/src/panels/IdentityPanel/steps/index.js +1 -1
  33. package/dist/types/src/panels/IdentityPanel/steps/index.js.map +1 -1
  34. package/dist/types/src/panels/IdentityPanel/useAgentHandlers.d.ts +15 -0
  35. package/dist/types/src/panels/IdentityPanel/useAgentHandlers.d.ts.map +1 -0
  36. package/dist/types/src/panels/IdentityPanel/useAgentHandlers.js +78 -0
  37. package/dist/types/src/panels/IdentityPanel/useAgentHandlers.js.map +1 -0
  38. package/dist/types/src/panels/JoinPanel/joinMachine.d.ts +1 -1
  39. package/dist/types/src/panels/JoinPanel/joinMachine.d.ts.map +1 -1
  40. package/dist/types/src/panels/JoinPanel/joinMachine.js +14 -17
  41. package/dist/types/src/panels/JoinPanel/joinMachine.js.map +1 -1
  42. package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.js +3 -3
  43. package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.js.map +1 -1
  44. package/dist/types/src/panels/JoinPanel/steps/InvitationInput.d.ts.map +1 -1
  45. package/dist/types/src/panels/JoinPanel/steps/InvitationInput.js +13 -6
  46. package/dist/types/src/panels/JoinPanel/steps/InvitationInput.js.map +1 -1
  47. package/dist/types/src/panels/JoinPanel/steps/InvitationRescuer.js.map +1 -1
  48. package/dist/types/src/panels/JoinPanel/steps/ResetIdentity.js +1 -1
  49. package/dist/types/src/panels/JoinPanel/steps/ResetIdentity.js.map +1 -1
  50. package/dist/types/src/playwright/invitations-manager.d.ts +1 -0
  51. package/dist/types/src/playwright/invitations-manager.d.ts.map +1 -1
  52. package/dist/types/src/playwright/invitations-manager.js +4 -1
  53. package/dist/types/src/playwright/invitations-manager.js.map +1 -1
  54. package/dist/types/src/playwright/invitations.spec.js +15 -0
  55. package/dist/types/src/playwright/invitations.spec.js.map +1 -1
  56. package/dist/types/src/styles/invitationStatusStyles.d.ts.map +1 -1
  57. package/dist/types/src/styles/invitationStatusStyles.js.map +1 -1
  58. package/dist/types/src/testing/shell-manager.d.ts +5 -4
  59. package/dist/types/src/testing/shell-manager.d.ts.map +1 -1
  60. package/dist/types/src/testing/shell-manager.js +36 -12
  61. package/dist/types/src/testing/shell-manager.js.map +1 -1
  62. package/package.json +22 -22
  63. package/src/components/IdentityList/SpaceMemberList.tsx +1 -1
  64. package/src/components/Panel/Action.tsx +4 -19
  65. package/src/panels/IdentityPanel/IdentityPanel.tsx +7 -3
  66. package/src/panels/IdentityPanel/IdentityPanelProps.ts +11 -1
  67. package/src/panels/IdentityPanel/steps/AgentForm.tsx +61 -0
  68. package/src/panels/IdentityPanel/steps/IdentityActionChooser.tsx +5 -1
  69. package/src/panels/IdentityPanel/steps/SignOutChooser.tsx +2 -0
  70. package/src/panels/IdentityPanel/steps/index.ts +1 -1
  71. package/src/panels/IdentityPanel/useAgentHandlers.ts +88 -0
  72. package/src/panels/JoinPanel/joinMachine.ts +15 -18
  73. package/src/panels/JoinPanel/steps/AdditionMethodChooser.tsx +3 -3
  74. package/src/panels/JoinPanel/steps/InvitationInput.tsx +7 -1
  75. package/src/panels/JoinPanel/steps/InvitationRescuer.tsx +2 -2
  76. package/src/panels/JoinPanel/steps/ResetIdentity.tsx +1 -1
  77. package/src/playwright/invitations-manager.ts +5 -1
  78. package/src/playwright/invitations.spec.ts +19 -0
  79. package/src/styles/invitationStatusStyles.ts +6 -6
  80. package/src/testing/shell-manager.ts +40 -13
  81. package/dist/types/src/panels/IdentityPanel/steps/AgentPanel.d.ts +0 -19
  82. package/dist/types/src/panels/IdentityPanel/steps/AgentPanel.d.ts.map +0 -1
  83. package/dist/types/src/panels/IdentityPanel/steps/AgentPanel.js +0 -122
  84. package/dist/types/src/panels/IdentityPanel/steps/AgentPanel.js.map +0 -1
  85. package/src/panels/IdentityPanel/steps/AgentPanel.tsx +0 -165
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ShellManager = void 0;
7
7
  const async_1 = require("@dxos/async");
8
8
  const scoped_shell_manager_1 = require("./scoped-shell-manager");
9
+ // TODO(wittjosiah): Normalize data-testids between snake and camel case.
9
10
  class ShellManager extends scoped_shell_manager_1.ScopedShellManager {
10
11
  // prettier-ignore
11
12
  constructor(page, inIFrame = true) {
@@ -20,6 +21,9 @@ class ShellManager extends scoped_shell_manager_1.ScopedShellManager {
20
21
  return this.inIFrame ? this.page.frameLocator('data-testid=dxos-shell') : this.page;
21
22
  }
22
23
  // Getters
24
+ async getDisplayName() {
25
+ return this.shell.getByTestId('identityHeading.displayName').textContent();
26
+ }
23
27
  async isCurrentSpaceViewVisible() {
24
28
  return await this.shell.getByTestId('current-space-view').isVisible();
25
29
  }
@@ -33,24 +37,44 @@ class ShellManager extends scoped_shell_manager_1.ScopedShellManager {
33
37
  await this.shell.getByTestId('create-identity-input-continue').click();
34
38
  await this.shell.getByTestId('halo-invitation-accepted-done').click();
35
39
  }
36
- async closeShell() {
37
- await this.page.keyboard.press('Escape');
40
+ async createDeviceInvitation() {
41
+ this._invitationCode = new async_1.Trigger();
42
+ this._authCode = new async_1.Trigger();
43
+ await this.shell.getByTestId('devices-panel.create-invitation').click();
44
+ return await this._invitationCode.wait();
38
45
  }
39
- // TODO(wittjosiah): These shortcuts are no longer within the SDK so these commands should live elsewhere.
40
- async openCurrentSpace() {
41
- await this.page.keyboard.press('Meta+.');
46
+ async resetDevice() {
47
+ const handleDialog = async (dialog) => {
48
+ if (dialog.type() === 'confirm') {
49
+ await dialog.accept();
50
+ }
51
+ };
52
+ this.page.on('dialog', handleDialog);
53
+ await this.shell.getByTestId('devices-panel.sign-out').click();
54
+ await this.shell.getByTestId('sign-out.reset-device').click();
55
+ this.page.off('dialog', handleDialog);
42
56
  }
43
- async openSpaceList() {
44
- await this.page.keyboard.press('Meta+Shift+>');
57
+ async joinNewIdentity(invitationCode) {
58
+ await this.shell.getByTestId('devices-panel.sign-out').click();
59
+ await this.shell.getByTestId('sign-out.join-new-identity').click();
60
+ await this.shell.getByTestId('reset-identity-input').fill('CONFIRM');
61
+ await this.shell.getByTestId('reset-identity-input-confirm').click();
62
+ await this.shell.getByTestId('identity-chooser.join-identity').click();
63
+ await this.inputInvitation('device', invitationCode, this.shell);
45
64
  }
46
- async showAllSpaces() {
47
- await this.shell.getByTestId('show-all-spaces').click();
65
+ async authenticateDevice(authCode, skipDone = true) {
66
+ // Wait for focus to shift before typing.
67
+ await (0, async_1.sleep)(1500);
68
+ await this.authenticateInvitation('device', authCode, this.shell);
69
+ // TODO(wittjosiah): When "signing out" and joinging a new identity, the done step seems to be skipped.
70
+ if (!skipDone) {
71
+ await this.doneInvitation('device', this.shell);
72
+ }
48
73
  }
49
- async showCurrentSpace() {
50
- await this.shell.getByTestId('show-current-space').click();
74
+ async closeShell() {
75
+ await this.page.keyboard.press('Escape');
51
76
  }
52
77
  async createSpaceInvitation() {
53
- await this.openCurrentSpace();
54
78
  this._invitationCode = new async_1.Trigger();
55
79
  this._authCode = new async_1.Trigger();
56
80
  await this.shell.getByTestId('spaces-panel.create-invitation').click();
@@ -1 +1 @@
1
- {"version":3,"file":"shell-manager.js","sourceRoot":"","sources":["../../../../src/testing/shell-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shell-manager.js","sourceRoot":"","sources":["../../../../src/testing/shell-manager.ts"],"names":[],"mappings":";AAAA,EAAE;AACF,0BAA0B;AAC1B,EAAE;;;AAIF,uCAA6C;AAE7C,iEAA4D;AAE5D,yEAAyE;AAEzE,MAAa,YAAa,SAAQ,yCAAkB;IAIlD,kBAAkB;IAClB,YACoB,IAAU,EACnB,WAAW,IAAI;QAExB,KAAK,EAAE,CAAC;QAHU,SAAI,GAAJ,IAAI,CAAM;QACnB,aAAQ,GAAR,QAAQ,CAAO;QANlB,oBAAe,GAAG,IAAI,eAAO,EAAU,CAAC;QACxC,cAAS,GAAG,IAAI,eAAO,EAAU,CAAC;QAQxC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IACtF,CAAC;IAED,UAAU;IAEV,KAAK,CAAC,cAAc;QAClB,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,6BAA6B,CAAC,CAAC,WAAW,EAAE,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,yBAAyB;QAC7B,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,oBAAoB,CAAC,CAAC,SAAS,EAAE,CAAC;IACxE,CAAC;IAED,KAAK,CAAC,sBAAsB;QAC1B,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC,SAAS,EAAE,CAAC;IACrE,CAAC;IAED,UAAU;IAEV,KAAK,CAAC,cAAc,CAAC,IAAY;QAC/B,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC,KAAK,EAAE,CAAC;QACxD,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,gCAAgC,CAAC,CAAC,KAAK,EAAE,CAAC;QACvE,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,+BAA+B,CAAC,CAAC,KAAK,EAAE,CAAC;IACxE,CAAC;IAED,KAAK,CAAC,sBAAsB;QAC1B,IAAI,CAAC,eAAe,GAAG,IAAI,eAAO,EAAU,CAAC;QAC7C,IAAI,CAAC,SAAS,GAAG,IAAI,eAAO,EAAU,CAAC;QACvC,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,iCAAiC,CAAC,CAAC,KAAK,EAAE,CAAC;QACxE,OAAO,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,WAAW;QACf,MAAM,YAAY,GAAG,KAAK,EAAE,MAAc,EAAE,EAAE;YAC5C,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,SAAS,EAAE;gBAC/B,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;aACvB;QACH,CAAC,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QACrC,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,wBAAwB,CAAC,CAAC,KAAK,EAAE,CAAC;QAC/D,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,uBAAuB,CAAC,CAAC,KAAK,EAAE,CAAC;QAC9D,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,cAAsB;QAC1C,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,wBAAwB,CAAC,CAAC,KAAK,EAAE,CAAC;QAC/D,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,4BAA4B,CAAC,CAAC,KAAK,EAAE,CAAC;QACnE,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrE,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,8BAA8B,CAAC,CAAC,KAAK,EAAE,CAAC;QACrE,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,gCAAgC,CAAC,CAAC,KAAK,EAAE,CAAC;QACvE,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACnE,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,QAAgB,EAAE,QAAQ,GAAG,IAAI;QACxD,yCAAyC;QACzC,MAAM,IAAA,aAAK,EAAC,IAAI,CAAC,CAAC;QAClB,MAAM,IAAI,CAAC,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAClE,uGAAuG;QACvG,IAAI,CAAC,QAAQ,EAAE;YACb,MAAM,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;SACjD;IACH,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,qBAAqB;QACzB,IAAI,CAAC,eAAe,GAAG,IAAI,eAAO,EAAU,CAAC;QAC7C,IAAI,CAAC,SAAS,GAAG,IAAI,eAAO,EAAU,CAAC;QACvC,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,gCAAgC,CAAC,CAAC,KAAK,EAAE,CAAC;QACvE,OAAO,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,qBAAqB,CAAC,cAAsB;QAChD,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,WAAW;QACf,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,QAAgB;QACjC,yCAAyC;QACzC,MAAM,IAAA,aAAK,EAAC,IAAI,CAAC,CAAC;QAClB,MAAM,IAAI,CAAC,sBAAsB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACjE,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACjD,CAAC;IAEO,KAAK,CAAC,iBAAiB,CAAC,OAAuB;QACrD,IAAI;YACF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;YACxC,IAAI,IAAI,CAAC,cAAc,EAAE;gBACvB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;aAChD;YACD,IAAI,IAAI,CAAC,QAAQ,EAAE;gBACjB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;aACpC;SACF;QAAC,WAAM,GAAE;IACZ,CAAC;CACF;AApHD,oCAoHC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/shell",
3
- "version": "0.3.11-main.ec7a2b3",
3
+ "version": "0.3.11-main.ee2b64c",
4
4
  "description": "DXOS’s OS experiences",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -28,7 +28,7 @@
28
28
  "src"
29
29
  ],
30
30
  "dependencies": {
31
- "@fluentui/react-tabster": "^9.16.1",
31
+ "@fluentui/react-tabster": "^9.18.0",
32
32
  "@radix-ui/react-accordion": "^1.1.1",
33
33
  "@radix-ui/react-context": "^1.0.0",
34
34
  "@radix-ui/react-radio-group": "^1.1.2",
@@ -39,35 +39,35 @@
39
39
  "react-qr-rounded": "^1.0.0",
40
40
  "url-join": "^5.0.0",
41
41
  "xstate": "^4.37.0",
42
- "@dxos/async": "0.3.11-main.ec7a2b3",
43
- "@dxos/client-protocol": "0.3.11-main.ec7a2b3",
44
- "@dxos/client-services": "0.3.11-main.ec7a2b3",
45
- "@dxos/debug": "0.3.11-main.ec7a2b3",
46
- "@dxos/display-name": "0.3.11-main.ec7a2b3",
47
- "@dxos/invariant": "0.3.11-main.ec7a2b3",
48
- "@dxos/log": "0.3.11-main.ec7a2b3",
49
- "@dxos/react-client": "0.3.11-main.ec7a2b3",
50
- "@dxos/react-ui": "0.3.11-main.ec7a2b3",
51
- "@dxos/react-ui-theme": "0.3.11-main.ec7a2b3",
52
- "@dxos/react-ui-types": "0.3.11-main.ec7a2b3",
53
- "@dxos/util": "0.3.11-main.ec7a2b3",
54
- "@dxos/rpc-tunnel": "0.3.11-main.ec7a2b3"
42
+ "@dxos/async": "0.3.11-main.ee2b64c",
43
+ "@dxos/client-protocol": "0.3.11-main.ee2b64c",
44
+ "@dxos/client-services": "0.3.11-main.ee2b64c",
45
+ "@dxos/debug": "0.3.11-main.ee2b64c",
46
+ "@dxos/display-name": "0.3.11-main.ee2b64c",
47
+ "@dxos/invariant": "0.3.11-main.ee2b64c",
48
+ "@dxos/log": "0.3.11-main.ee2b64c",
49
+ "@dxos/react-ui": "0.3.11-main.ee2b64c",
50
+ "@dxos/react-client": "0.3.11-main.ee2b64c",
51
+ "@dxos/react-ui-theme": "0.3.11-main.ee2b64c",
52
+ "@dxos/react-ui-types": "0.3.11-main.ee2b64c",
53
+ "@dxos/rpc-tunnel": "0.3.11-main.ee2b64c",
54
+ "@dxos/util": "0.3.11-main.ee2b64c"
55
55
  },
56
56
  "devDependencies": {
57
- "@faker-js/faker": "^8.0.2",
57
+ "@faker-js/faker": "^8.3.1",
58
58
  "@phosphor-icons/react": "^2.0.5",
59
59
  "@types/lodash.toarray": "^4.4.7",
60
60
  "@types/react": "^18.0.21",
61
61
  "@types/react-dom": "^18.0.6",
62
- "@vitejs/plugin-react": "^3.0.1",
62
+ "@vitejs/plugin-react": "^4.2.1",
63
63
  "react": "^18.2.0",
64
64
  "react-dom": "^18.2.0",
65
- "vite": "^4.3.9",
65
+ "vite": "^5.0.12",
66
66
  "vite-plugin-turbosnap": "^1.0.2",
67
- "@dxos/config": "0.3.11-main.ec7a2b3",
68
- "@dxos/react-async": "0.3.11-main.ec7a2b3",
69
- "@dxos/react-ui-theme": "0.3.11-main.ec7a2b3",
70
- "@dxos/storybook-utils": "0.3.11-main.ec7a2b3"
67
+ "@dxos/config": "0.3.11-main.ee2b64c",
68
+ "@dxos/react-async": "0.3.11-main.ee2b64c",
69
+ "@dxos/react-ui-theme": "0.3.11-main.ee2b64c",
70
+ "@dxos/storybook-utils": "0.3.11-main.ee2b64c"
71
71
  },
72
72
  "peerDependencies": {
73
73
  "@phosphor-icons/react": "^2.0.5",
@@ -45,7 +45,7 @@ export const SpaceMemberListImpl = ({ members, onSelect }: SpaceMemberListImplPr
45
45
  const { t } = useTranslation('os');
46
46
  const visibleMembers = members.filter((member) => member.identity);
47
47
  return visibleMembers.length > 0 ? (
48
- <List classNames='flex flex-col gap-2'>
48
+ <List classNames='flex flex-col gap-2' data-testid='space-members-list'>
49
49
  {visibleMembers.map((member) => {
50
50
  return (
51
51
  <IdentityListItem
@@ -6,7 +6,7 @@ import { CaretDown, Check, type IconProps, Placeholder } from '@phosphor-icons/r
6
6
  import { useControllableState } from '@radix-ui/react-use-controllable-state';
7
7
  import React, { type Dispatch, type FC, forwardRef, type SetStateAction } from 'react';
8
8
 
9
- import { type ButtonProps, Button, type ComponentFragment, DropdownMenu, useTranslation } from '@dxos/react-ui';
9
+ import { type ButtonProps, Button, DropdownMenu, useTranslation } from '@dxos/react-ui';
10
10
  import { descriptionText, getSize, mx } from '@dxos/react-ui-theme';
11
11
 
12
12
  export type LargeButtonProps = ButtonProps & {
@@ -26,21 +26,6 @@ export type BifurcatedActionProps = {
26
26
  defaultActiveAction?: string;
27
27
  } & Omit<LargeButtonProps, 'children' | 'onClick'>;
28
28
 
29
- const buttonColorFragment: ComponentFragment<LargeButtonProps> = (props: LargeButtonProps) => {
30
- const resolvedVariant = props.variant ?? 'default';
31
- return [
32
- !props.disabled
33
- ? resolvedVariant === 'default'
34
- ? 'bg-neutral-25 hover:bg-white hover:text-primary-500 active:bg-neutral-25 dark:active:bg-neutral-800'
35
- : resolvedVariant === 'ghost'
36
- ? 'active:bg-neutral-125 dark:active:bg-neutral-800'
37
- : resolvedVariant === 'primary'
38
- ? 'active:bg-primary-700 dark:active:bg-primary-500'
39
- : ''
40
- : '',
41
- ];
42
- };
43
-
44
29
  const defaultActions = {
45
30
  noopAction: {
46
31
  label: 'No-op',
@@ -76,7 +61,7 @@ export const BifurcatedAction = forwardRef<HTMLButtonElement, BifurcatedActionPr
76
61
  <div role='none' className={mx('mbs-2 flex gap-px items-center', isFull && 'is-full')}>
77
62
  <Button
78
63
  {...rest}
79
- classNames={['bs-11 flex-1 min-is-0 flex gap-2 rounded-ie-none', classNames, ...buttonColorFragment(props)]}
64
+ classNames={['bs-11 flex-1 min-is-0 flex gap-2 rounded-ie-none', classNames]}
80
65
  ref={forwardedRef}
81
66
  variant={variant}
82
67
  onClick={activeAction.onClick}
@@ -86,7 +71,7 @@ export const BifurcatedAction = forwardRef<HTMLButtonElement, BifurcatedActionPr
86
71
  </Button>
87
72
  <DropdownMenu.Root>
88
73
  <DropdownMenu.Trigger asChild>
89
- <Button classNames={['bs-11 flex-none rounded-is-none', classNames, ...buttonColorFragment(props)]}>
74
+ <Button classNames={['bs-11 flex-none rounded-is-none', classNames]}>
90
75
  <span className='sr-only'>{t('invite options label')}</span>
91
76
  <CaretDown />
92
77
  </Button>
@@ -132,7 +117,7 @@ export const Action = forwardRef<HTMLButtonElement, LargeButtonProps>((props, fo
132
117
  return (
133
118
  <Button
134
119
  {...rest}
135
- classNames={[isFull && 'is-full', 'bs-11 flex gap-2 mbs-2', ...buttonColorFragment(props), classNames]}
120
+ classNames={[isFull && 'is-full', 'bs-11 flex gap-2 mbs-2', classNames]}
136
121
  ref={forwardedRef}
137
122
  variant={variant}
138
123
  >
@@ -6,7 +6,7 @@ import React, { useEffect, useMemo } from 'react';
6
6
  import { generateName } from '@dxos/display-name';
7
7
  import { log } from '@dxos/log';
8
8
  import { useClient } from '@dxos/react-client';
9
- import { type Identity, useIdentity, useDevices } from '@dxos/react-client/halo';
9
+ import { type Identity, useIdentity, useDevices, useHaloInvitations } from '@dxos/react-client/halo';
10
10
  import { useInvitationStatus } from '@dxos/react-client/invitations';
11
11
  import type { CancellableInvitationObservable } from '@dxos/react-client/invitations';
12
12
  import { Avatar, DensityProvider, useId, useJdenticonHref, useTranslation } from '@dxos/react-ui';
@@ -18,6 +18,7 @@ import {
18
18
  } from './IdentityPanelProps';
19
19
  import { useIdentityMachine } from './identityMachine';
20
20
  import { AgentForm, DeviceManager, IdentityActionChooser, ProfileForm, SignOutChooser } from './steps';
21
+ import { useAgentHandlers } from './useAgentHandlers';
21
22
  import { Viewport, Heading, CloseButton } from '../../components';
22
23
  import { InvitationManager } from '../../steps';
23
24
 
@@ -31,7 +32,7 @@ const IdentityHeading = ({ titleId, title, identity, onDone }: IdentityPanelHead
31
32
  <Avatar.Frame classNames='block mbs-4 mbe-2 mli-auto chromatic-ignore'>
32
33
  <Avatar.Fallback href={fallbackHref} />
33
34
  </Avatar.Frame>
34
- <Avatar.Label classNames='block text-center font-light text-xl'>
35
+ <Avatar.Label classNames='block text-center font-light text-xl' data-testid='identityHeading.displayName'>
35
36
  {identity.profile?.displayName ?? generateName(identity.identityKey.toHex())}
36
37
  </Avatar.Label>
37
38
  </Avatar.Root>
@@ -99,7 +100,7 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
99
100
  />
100
101
  </Viewport.View>
101
102
  <Viewport.View classNames={viewStyles} id='agent manager'>
102
- <AgentForm send={rest.send} active={activeView === 'agent manager'} identity={identity} />
103
+ <AgentForm send={rest.send} active={activeView === 'agent manager'} {...rest} />
103
104
  </Viewport.View>
104
105
  </Viewport.Views>
105
106
  </Viewport.Root>
@@ -124,6 +125,8 @@ export const IdentityPanel = ({
124
125
  const client = useClient();
125
126
  const devices = useDevices();
126
127
  const identity = useIdentity();
128
+ const invitations = useHaloInvitations();
129
+ const agentProps = useAgentHandlers({ client, identity, invitations });
127
130
  if (!identity) {
128
131
  console.error('IdentityPanel rendered with no active identity.');
129
132
  return null;
@@ -171,6 +174,7 @@ export const IdentityPanel = ({
171
174
  titleId,
172
175
  createInvitationUrl,
173
176
  onUpdateProfile,
177
+ ...agentProps,
174
178
  } satisfies IdentityPanelImplProps;
175
179
 
176
180
  return identityState.context.invitation ? (
@@ -11,6 +11,15 @@ import type { InvitationStatus } from '@dxos/react-client/invitations';
11
11
  import type { IdentityEvent } from './identityMachine';
12
12
  import type { InvitationManagerProps } from '../../steps';
13
13
 
14
+ export type AgentFormProps = {
15
+ onAgentCreate?: () => Promise<void>;
16
+ onAgentDestroy?: () => Promise<void>;
17
+ agentStatus?: string;
18
+ agentActive?: boolean;
19
+ agentProviderDisabled?: boolean;
20
+ validationMessage?: string;
21
+ };
22
+
14
23
  export type IdentityPanelImplProps = {
15
24
  titleId: string;
16
25
  activeView:
@@ -32,7 +41,8 @@ export type IdentityPanelImplProps = {
32
41
  invitationUrl?: string;
33
42
  IdentityActionChooser?: React.FC<IdentityPanelStepProps>;
34
43
  InvitationManager?: React.FC<InvitationManagerProps>;
35
- } & Partial<InvitationStatus>;
44
+ } & Partial<InvitationStatus> &
45
+ Partial<AgentFormProps>;
36
46
 
37
47
  export type IdentityPanelProps = Partial<Omit<IdentityPanelImplProps, 'send' | 'activeView' | 'identity'>>;
38
48
 
@@ -0,0 +1,61 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { useTranslation } from '@dxos/react-ui';
8
+
9
+ import { Action, Actions, StepHeading, Input } from '../../../components';
10
+ import { type AgentFormProps, type IdentityPanelStepProps } from '../IdentityPanelProps';
11
+
12
+ export const AgentForm = (props: IdentityPanelStepProps & AgentFormProps) => {
13
+ const {
14
+ active,
15
+ onAgentCreate,
16
+ onAgentDestroy,
17
+ send,
18
+ validationMessage,
19
+ agentStatus,
20
+ agentActive,
21
+ agentProviderDisabled,
22
+ } = props;
23
+ const disabled = !active;
24
+ const { t } = useTranslation('os');
25
+ return (
26
+ <>
27
+ <div role='none' className='grow flex flex-col justify-center'>
28
+ <Input
29
+ {...{ validationMessage }}
30
+ label={<StepHeading>Agent Status</StepHeading>}
31
+ disabled={disabled}
32
+ data-testid='agent-status'
33
+ value={agentStatus}
34
+ />
35
+ </div>
36
+ <Actions>
37
+ <Action
38
+ disabled={agentActive || agentProviderDisabled}
39
+ onClick={() => onAgentCreate?.()}
40
+ data-testid={'create-agent'}
41
+ >
42
+ Create Agent
43
+ </Action>
44
+ <Action
45
+ disabled={!agentActive || agentProviderDisabled}
46
+ onClick={() => onAgentDestroy?.()}
47
+ data-testid={'destroy-agent'}
48
+ >
49
+ Destroy Agent
50
+ </Action>
51
+ <Action
52
+ disabled={disabled}
53
+ onClick={() => send?.({ type: 'unchooseAction' })}
54
+ data-testid={'update-profile-form-back'}
55
+ >
56
+ {t('back label')}
57
+ </Action>
58
+ </Actions>
59
+ </>
60
+ );
61
+ };
@@ -88,7 +88,11 @@ export const IdentityActionChooserImpl = ({
88
88
  <span className='grow mli-3'>{t('choose profile label')}</span>
89
89
  <CaretRight weight='bold' className={getSize(4)} />
90
90
  </Action>
91
- <Action data-testid='sign-out' onClick={() => send?.({ type: 'chooseSignOut' })} classNames='plb-4'>
91
+ <Action
92
+ data-testid='devices-panel.sign-out'
93
+ onClick={() => send?.({ type: 'chooseSignOut' })}
94
+ classNames='plb-4'
95
+ >
92
96
  <Power className={getSize(6)} />
93
97
  <span className='grow mli-3'>{t('choose sign out label')}</span>
94
98
  <CaretRight weight='bold' className={getSize(4)} />
@@ -62,6 +62,7 @@ const SignOutChooserImpl = ({ disabled, onResetDevice, onJoinNewIdentity, onBack
62
62
  </Action>
63
63
  <Action
64
64
  variant='primary'
65
+ data-testid='sign-out.join-new-identity'
65
66
  classNames='!bg-error-500 hover:!bg-error-550'
66
67
  disabled={disabled}
67
68
  onClick={onJoinNewIdentity}
@@ -70,6 +71,7 @@ const SignOutChooserImpl = ({ disabled, onResetDevice, onJoinNewIdentity, onBack
70
71
  </Action>
71
72
  <Action
72
73
  variant='primary'
74
+ data-testid='sign-out.reset-device'
73
75
  classNames='!bg-error-500 hover:!bg-error-550'
74
76
  disabled={disabled}
75
77
  onClick={onResetDevice}
@@ -2,7 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './AgentPanel';
5
+ export * from './AgentForm';
6
6
  export * from './DeviceManager';
7
7
  export * from './IdentityActionChooser';
8
8
  export * from './ProfileForm';
@@ -0,0 +1,88 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { useCallback, useEffect, useState } from 'react';
6
+
7
+ import { type CancellableInvitation } from '@dxos/client-protocol';
8
+ import { invariant } from '@dxos/invariant';
9
+ import { useAgentHostingProviderClient, type Client } from '@dxos/react-client';
10
+ import { type Identity } from '@dxos/react-client/halo';
11
+ import { Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
12
+
13
+ export const useAgentHandlers = ({
14
+ client,
15
+ invitations,
16
+ identity,
17
+ }: {
18
+ client: Client;
19
+ invitations: CancellableInvitation[];
20
+ identity: Identity | null;
21
+ }) => {
22
+ const [validationMessage, setValidationMessage] = useState('');
23
+ const [agentStatus, setAgentStatus] = useState('');
24
+ const [agentActive, setAgentActive] = useState(false);
25
+ const agentHostingProviderClient = useAgentHostingProviderClient(client.config);
26
+
27
+ useEffect(() => {
28
+ const fetchAgentStatus = async () => {
29
+ // TODO(nf): if agent exists, subscribe to updates instead of oneshot status
30
+ if (agentHostingProviderClient) {
31
+ const agentStatus = await agentHostingProviderClient?.getAgent(identity?.identityKey.truncate() ?? 'foo');
32
+ if (agentStatus) {
33
+ setAgentActive(true);
34
+ setAgentStatus(agentStatus);
35
+ } else {
36
+ setAgentActive(false);
37
+ setValidationMessage('No agent deployed via provider.');
38
+ }
39
+ } else {
40
+ setValidationMessage('No agent provider configured.');
41
+ }
42
+ };
43
+ void fetchAgentStatus();
44
+ }, []);
45
+
46
+ const handleAgentCreate = useCallback(async (invitation: Invitation) => {
47
+ const invitationCode = InvitationEncoder.encode(invitation);
48
+ if (invitation.state === Invitation.State.CONNECTING) {
49
+ console.log(JSON.stringify({ invitationCode, authCode: invitation.authCode }));
50
+ invariant(identity, 'Identity not found');
51
+ if (agentHostingProviderClient) {
52
+ try {
53
+ const res = await agentHostingProviderClient?.createAgent(invitationCode, identity.identityKey.truncate());
54
+ // TODO(nf): human-consumable response from API
55
+ setAgentStatus(res);
56
+ setAgentActive(true);
57
+ setValidationMessage('');
58
+ } catch (err: any) {
59
+ setValidationMessage(`error creating agent: ${err.message}`);
60
+ }
61
+ }
62
+ }
63
+ }, []);
64
+
65
+ const onAgentCreate = async () => {
66
+ invitations.forEach((invitation) => invitation.cancel());
67
+
68
+ // TODO(nf): do this work in the hosting provider client?
69
+ const invitation = client.halo.share({ type: Invitation.Type.MULTIUSE, authMethod: Invitation.AuthMethod.NONE });
70
+
71
+ invitation.subscribe(handleAgentCreate);
72
+ };
73
+
74
+ const onAgentDestroy = async () => {
75
+ const identity = client.halo.identity.get();
76
+ invariant(identity, 'Identity not found');
77
+ try {
78
+ await agentHostingProviderClient?.destroyAgent(identity.identityKey.truncate());
79
+ setValidationMessage('No agent deployed via provider.');
80
+ setAgentStatus('');
81
+ setAgentActive(false);
82
+ } catch (err: any) {
83
+ setValidationMessage(`error destroying agent: ${err.message}`);
84
+ }
85
+ };
86
+
87
+ return { onAgentCreate, onAgentDestroy, validationMessage, agentStatus, agentActive };
88
+ };
@@ -15,7 +15,7 @@ import {
15
15
  } from 'xstate';
16
16
 
17
17
  import { log } from '@dxos/log';
18
- import type { Client } from '@dxos/react-client';
18
+ import { type Client, AlreadyJoinedError } from '@dxos/react-client';
19
19
  import { type Identity } from '@dxos/react-client/halo';
20
20
  import { type AuthenticatingInvitationObservable, Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
21
21
 
@@ -133,6 +133,12 @@ const getInvitationSubscribable = (
133
133
  }
134
134
  },
135
135
  (error: any) => {
136
+ if (error instanceof AlreadyJoinedError) {
137
+ log('[already joined]', { Kind, error });
138
+ next({ type: `success${Kind}Invitation`, invitation: invitation.get() });
139
+ return complete?.();
140
+ }
141
+
136
142
  log.error('[invitation errored]', { Kind, error });
137
143
  next({ type: `fail${Kind}Invitation`, reason: 'error' });
138
144
  return onError?.(error);
@@ -437,9 +443,8 @@ const useJoinMachine = (
437
443
  ({ halo }: JoinMachineContext) => {
438
444
  if (halo.unredeemedCode) {
439
445
  try {
440
- const invitationObservable = client.halo.join(
441
- InvitationEncoder.decode(defaultCodeFromUrl('halo', halo.unredeemedCode)),
442
- );
446
+ const invitation = InvitationEncoder.decode(defaultCodeFromUrl('halo', halo.unredeemedCode));
447
+ const invitationObservable = client.halo.join(invitation);
443
448
  return {
444
449
  ...halo,
445
450
  invitationObservable,
@@ -461,20 +466,12 @@ const useJoinMachine = (
461
466
  if (space.unredeemedCode) {
462
467
  try {
463
468
  const invitation = InvitationEncoder.decode(defaultCodeFromUrl('space', space.unredeemedCode));
464
- // Don’t join with the invitation if it has already succeeded.
465
- if (space.succeededKeys?.has(invitation.spaceKey?.toHex() ?? 'never')) {
466
- return {
467
- ...space,
468
- invitation,
469
- };
470
- } else {
471
- const invitationObservable = client.spaces.join(invitation);
472
- return {
473
- ...space,
474
- invitationObservable,
475
- invitationSubscribable: getInvitationSubscribable('Space', invitationObservable),
476
- };
477
- }
469
+ const invitationObservable = client.spaces.join(invitation);
470
+ return {
471
+ ...space,
472
+ invitationObservable,
473
+ invitationSubscribable: getInvitationSubscribable('Space', invitationObservable),
474
+ };
478
475
  } catch (err) {
479
476
  log.error('Could not redeem space invitation code', err);
480
477
  return space;
@@ -35,7 +35,7 @@ export const AdditionMethodChooser = (viewStateProps: AdditionMethodChooserProps
35
35
  before={<Plus className={getSize(6)} />}
36
36
  onClick={() => send({ type: 'createIdentity' })}
37
37
  data-autofocus='choosingAuthMethod'
38
- data-testid='create-identity'
38
+ data-testid='identity-chooser.create-identity'
39
39
  >
40
40
  {t('create identity label')}
41
41
  </CompoundButton>
@@ -44,7 +44,7 @@ export const AdditionMethodChooser = (viewStateProps: AdditionMethodChooserProps
44
44
  description={t('join identity description')}
45
45
  before={<QrCode className={getSize(6)} />}
46
46
  onClick={() => send({ type: 'acceptHaloInvitation' })}
47
- data-testid='join-identity'
47
+ data-testid='identity-chooser.join-identity'
48
48
  >
49
49
  {t('join identity label')}
50
50
  </CompoundButton>
@@ -55,7 +55,7 @@ export const AdditionMethodChooser = (viewStateProps: AdditionMethodChooserProps
55
55
  description={t('recover identity description')}
56
56
  before={<Textbox className={getSize(6)} />}
57
57
  onClick={() => send({ type: 'recoverIdentity' })}
58
- data-testid='recover-identity'
58
+ data-testid='identity-chooser.recover-identity'
59
59
  >
60
60
  {t('recover identity label')}
61
61
  </CompoundButton>
@@ -39,12 +39,17 @@ export const InvitationInput = (props: InvitationInputProps) => {
39
39
  unredeemedCode && setInputValue(unredeemedCode ?? '');
40
40
  }, [unredeemedCode]);
41
41
 
42
- const handleNext = () =>
42
+ const handleNext = () => {
43
+ if (inputValue.trim().length === 0) {
44
+ return;
45
+ }
46
+
43
47
  send({
44
48
  type: `set${Kind}InvitationCode`,
45
49
  code: invitationCodeFromUrl(inputValue),
46
50
  ...(Kind === 'Space' && { succeededKeys }),
47
51
  });
52
+ };
48
53
 
49
54
  const exitAction = (
50
55
  <Action
@@ -62,6 +67,7 @@ export const InvitationInput = (props: InvitationInputProps) => {
62
67
  <div role='none' className='grow flex flex-col justify-center'>
63
68
  <Input
64
69
  label={<StepHeading>{t('invitation input label')}</StepHeading>}
70
+ // TODO(burdon): Translation.
65
71
  placeholder='Type an invitation code'
66
72
  disabled={disabled}
67
73
  value={inputValue}
@@ -39,8 +39,8 @@ const InvitationActions = ({
39
39
  failReason === 'timeout'
40
40
  ? 'timeout status label'
41
41
  : failReason === 'cancelled'
42
- ? 'cancelled status label'
43
- : 'error status label',
42
+ ? 'cancelled status label'
43
+ : 'error status label',
44
44
  )}
45
45
  </StepHeading>
46
46
  <div role='none' className='grow' />
@@ -50,7 +50,7 @@ export const ResetIdentityImpl = ({ disabled, validationMessage, onConfirm }: Re
50
50
  {...{ validationMessage }}
51
51
  label={<StepHeading>{t('reset identity input label', { confirmationValue })}</StepHeading>}
52
52
  disabled={disabled}
53
- data-testid='identity-input'
53
+ data-testid='reset-identity-input'
54
54
  placeholder={confirmationValue}
55
55
  onChange={({ target: { value } }) => setInputValue(value)}
56
56
  />