@mpgd/cli 0.9.0 → 0.10.1

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.
@@ -1,57 +1,132 @@
1
1
  import {
2
2
  requestDevvitExpandedMode,
3
- startDevvitWebSurface,
3
+ startDevvitWebView,
4
+ type DevvitInlineModeContext,
4
5
  } from '@mpgd/adapter-devvit/web';
5
6
 
6
- await startDevvitWebSurface({
7
- async mountInlinePreview() {
8
- await import('./devvitInlinePreview.css');
9
- renderInlinePreview();
7
+ await startDevvitWebView({
8
+ async mountInlineMode(context) {
9
+ await import('./devvitInlineMode.css');
10
+ renderInlineLaunchScreen(context);
10
11
  },
11
- async loadExpandedGame() {
12
+ async loadGameplay() {
12
13
  await import('../main');
13
14
  },
14
15
  onModeUnavailable(error) {
15
16
  if (!(error instanceof ReferenceError)) {
16
- console.warn('[devvit] web view mode unavailable; loading the game surface.', error);
17
+ console.warn('[devvit] web view mode unavailable; loading gameplay.', error);
17
18
  }
18
19
  },
19
20
  });
20
21
 
21
- function renderInlinePreview(): void {
22
- const preview = document.createElement('main');
23
- preview.className = 'devvit-preview';
22
+ function renderInlineLaunchScreen(
23
+ context: DevvitInlineModeContext,
24
+ errorMessage?: string,
25
+ ): void {
26
+ const launchScreen = document.createElement('main');
27
+ launchScreen.className = 'devvit-launch-screen';
24
28
 
25
29
  const eyebrow = document.createElement('p');
26
- eyebrow.className = 'devvit-preview__eyebrow';
27
- eyebrow.textContent = 'Ready to play';
30
+ eyebrow.className = 'devvit-launch-screen__eyebrow';
31
+ eyebrow.textContent = 'Inline mode';
28
32
 
29
33
  const title = document.createElement('h1');
30
34
  title.textContent = '__GAME_TITLE__';
31
35
 
32
36
  const description = document.createElement('p');
33
- description.className = 'devvit-preview__description';
34
- description.textContent = 'Open the expanded view to start the game.';
35
-
36
- const button = document.createElement('button');
37
- button.className = 'devvit-preview__button';
38
- button.type = 'button';
39
- button.textContent = 'Play';
40
- button.addEventListener('click', async (event) => {
41
- try {
42
- await requestDevvitExpandedMode(event, 'game');
43
- } catch (error) {
44
- console.error('[devvit] expanded game surface request failed.', error);
45
- }
37
+ description.className = 'devvit-launch-screen__description';
38
+ description.textContent = 'Play directly in the post or open expanded mode.';
39
+
40
+ const actions = document.createElement('div');
41
+ actions.className = 'devvit-launch-screen__actions';
42
+
43
+ const playInlineButton = document.createElement('button');
44
+ playInlineButton.className = 'devvit-launch-screen__button';
45
+ playInlineButton.type = 'button';
46
+ playInlineButton.textContent = 'Play here';
47
+
48
+ const expandButton = document.createElement('button');
49
+ expandButton.className =
50
+ 'devvit-launch-screen__button devvit-launch-screen__button--secondary';
51
+ expandButton.type = 'button';
52
+ expandButton.textContent = 'Open expanded mode';
53
+
54
+ const status = document.createElement('p');
55
+ status.className = 'devvit-launch-screen__status';
56
+ status.setAttribute('aria-live', 'polite');
57
+ status.textContent = errorMessage ?? '';
58
+
59
+ playInlineButton.addEventListener('click', () => {
60
+ setBusy(true, 'Loading gameplay…');
61
+ const loading = mountGameplayDocument();
62
+
63
+ void context.startGameplay()
64
+ .then(() => {
65
+ loading.remove();
66
+ })
67
+ .catch((error: unknown) => {
68
+ console.error('[devvit] inline mode gameplay failed to load.', error);
69
+ renderInlineLaunchScreen(context, 'Gameplay could not start. Try again.');
70
+ });
46
71
  });
72
+ expandButton.addEventListener('click', (event) => {
73
+ setBusy(true, 'Opening expanded mode…');
74
+
75
+ void requestDevvitExpandedMode(event, 'game')
76
+ .then(() => {
77
+ setBusy(false, '');
78
+ })
79
+ .catch((error: unknown) => {
80
+ console.error('[devvit] expanded mode request failed.', error);
81
+ setBusy(false, 'Expanded mode is unavailable. Try again.');
82
+ });
83
+ });
84
+
85
+ actions.append(playInlineButton, expandButton);
86
+ launchScreen.append(eyebrow, title, description, actions, status);
87
+ const body = requireDocumentBody();
88
+
89
+ body.classList.remove('devvit-inline-mode-gameplay');
90
+ body.classList.add('devvit-inline-mode-host');
91
+ delete body.dataset.mpgdPreserveBrowserTouchGestures;
92
+ body.replaceChildren(launchScreen);
93
+
94
+ function setBusy(busy: boolean, message: string): void {
95
+ playInlineButton.disabled = busy;
96
+ expandButton.disabled = busy;
97
+ status.textContent = message;
98
+ }
99
+ }
100
+
101
+ function mountGameplayDocument(): HTMLElement {
102
+ const body = requireDocumentBody();
103
+ const app = document.createElement('main');
104
+ const game = document.createElement('div');
105
+ const loading = document.createElement('p');
106
+
107
+ app.id = 'app';
108
+ game.id = 'game';
109
+ loading.className = 'devvit-inline-gameplay-loading';
110
+ loading.setAttribute('aria-live', 'polite');
111
+ loading.setAttribute('role', 'status');
112
+ loading.textContent = 'Loading gameplay…';
113
+ game.append(loading);
114
+ app.append(game);
115
+
116
+ body.classList.remove('devvit-inline-mode-host');
117
+ body.classList.add('devvit-inline-mode-gameplay');
118
+ body.dataset.mpgdPreserveBrowserTouchGestures = 'true';
119
+ body.replaceChildren(app);
120
+
121
+ return loading;
122
+ }
47
123
 
48
- preview.append(eyebrow, title, description, button);
124
+ function requireDocumentBody(): HTMLElement {
49
125
  const body = document.body;
50
126
 
51
127
  if (body === null) {
52
- throw new Error('Devvit inline preview requires a document body.');
128
+ throw new Error('Devvit inline mode requires a document body.');
53
129
  }
54
130
 
55
- body.classList.add('devvit-preview-host');
56
- body.replaceChildren(preview);
131
+ return body;
57
132
  }
@@ -0,0 +1,133 @@
1
+ body.devvit-inline-mode-host {
2
+ min-width: 320px;
3
+ min-height: 100vh;
4
+ margin: 0;
5
+ background: #0f172a;
6
+ }
7
+
8
+ body.devvit-inline-mode-gameplay {
9
+ overflow-y: auto;
10
+ overscroll-behavior-y: auto;
11
+ }
12
+
13
+ body.devvit-inline-mode-gameplay #game {
14
+ position: relative;
15
+ }
16
+
17
+ body.devvit-inline-mode-gameplay #game,
18
+ body.devvit-inline-mode-gameplay canvas {
19
+ touch-action: pan-y !important;
20
+ }
21
+
22
+ .devvit-inline-gameplay-loading {
23
+ position: fixed;
24
+ z-index: 1;
25
+ inset: 0;
26
+ display: grid;
27
+ place-items: center;
28
+ margin: 0;
29
+ background: #0f172a;
30
+ color: #cbd5e1;
31
+ font: 700 0.875rem/1.5 Inter, ui-sans-serif, system-ui, sans-serif;
32
+ pointer-events: none;
33
+ }
34
+
35
+ .devvit-launch-screen,
36
+ .devvit-launch-screen * {
37
+ box-sizing: border-box;
38
+ }
39
+
40
+ .devvit-launch-screen {
41
+ min-height: 100vh;
42
+ display: grid;
43
+ place-content: center;
44
+ justify-items: start;
45
+ gap: 0.75rem;
46
+ padding: clamp(1.25rem, 5vw, 3rem);
47
+ background:
48
+ radial-gradient(circle at 85% 15%, rgb(56 189 248 / 0.2), transparent 38%),
49
+ linear-gradient(145deg, #0f172a, #172554);
50
+ color: #f8fafc;
51
+ font-family: Inter, ui-sans-serif, system-ui, sans-serif;
52
+ }
53
+
54
+ .devvit-launch-screen__eyebrow,
55
+ .devvit-launch-screen__description,
56
+ .devvit-launch-screen__status,
57
+ .devvit-launch-screen h1 {
58
+ margin: 0;
59
+ }
60
+
61
+ .devvit-launch-screen__eyebrow {
62
+ color: #7dd3fc;
63
+ font-size: 0.75rem;
64
+ font-weight: 700;
65
+ letter-spacing: 0.12em;
66
+ text-transform: uppercase;
67
+ }
68
+
69
+ .devvit-launch-screen h1 {
70
+ max-width: 18ch;
71
+ font-size: clamp(2rem, 9vw, 4.5rem);
72
+ line-height: 0.95;
73
+ }
74
+
75
+ .devvit-launch-screen__description {
76
+ max-width: 36rem;
77
+ color: #cbd5e1;
78
+ line-height: 1.5;
79
+ }
80
+
81
+ .devvit-launch-screen__actions {
82
+ display: flex;
83
+ flex-wrap: wrap;
84
+ gap: 0.75rem;
85
+ margin-top: 0.5rem;
86
+ }
87
+
88
+ .devvit-launch-screen__button {
89
+ min-height: 2.75rem;
90
+ padding: 0.65rem 1.25rem;
91
+ border: 1px solid transparent;
92
+ border-radius: 999px;
93
+ color: #082f49;
94
+ background: #7dd3fc;
95
+ font: inherit;
96
+ font-weight: 800;
97
+ cursor: pointer;
98
+ }
99
+
100
+ .devvit-launch-screen__button--secondary {
101
+ border-color: #7dd3fc;
102
+ color: #e0f2fe;
103
+ background: rgb(15 23 42 / 0.64);
104
+ }
105
+
106
+ .devvit-launch-screen__button:disabled {
107
+ cursor: wait;
108
+ opacity: 0.65;
109
+ }
110
+
111
+ .devvit-launch-screen__button:focus-visible {
112
+ outline: 3px solid #f8fafc;
113
+ outline-offset: 3px;
114
+ }
115
+
116
+ .devvit-launch-screen__status {
117
+ min-height: 1.5em;
118
+ color: #fda4af;
119
+ font-size: 0.875rem;
120
+ }
121
+
122
+ @media (hover: hover) {
123
+ .devvit-launch-screen__button:not(:disabled):hover {
124
+ filter: brightness(1.1);
125
+ }
126
+ }
127
+
128
+ @media (max-width: 420px) {
129
+ .devvit-launch-screen__actions,
130
+ .devvit-launch-screen__button {
131
+ width: 100%;
132
+ }
133
+ }
@@ -7,6 +7,7 @@ import { starterContextKey, type StarterContext } from './gameContext';
7
7
 
8
8
  export function createStarterGame(input: {
9
9
  readonly mountId: string;
10
+ readonly preserveBrowserTouchGestures?: boolean;
10
11
  readonly context: StarterContext;
11
12
  }): Phaser.Game {
12
13
  const game = new Phaser.Game({
@@ -15,6 +16,11 @@ export function createStarterGame(input: {
15
16
  width: 960,
16
17
  height: 540,
17
18
  backgroundColor: '#07111f',
19
+ input: {
20
+ touch: {
21
+ capture: input.preserveBrowserTouchGestures !== true,
22
+ },
23
+ },
18
24
  scale: {
19
25
  mode: Phaser.Scale.FIT,
20
26
  autoCenter: Phaser.Scale.CENTER_BOTH,
@@ -1,75 +0,0 @@
1
- body.devvit-preview-host {
2
- min-width: 320px;
3
- min-height: 100vh;
4
- margin: 0;
5
- background: #0f172a;
6
- }
7
-
8
- .devvit-preview,
9
- .devvit-preview * {
10
- box-sizing: border-box;
11
- }
12
-
13
- .devvit-preview {
14
- min-height: 100vh;
15
- display: grid;
16
- place-content: center;
17
- justify-items: start;
18
- gap: 0.75rem;
19
- padding: clamp(1.25rem, 5vw, 3rem);
20
- background:
21
- radial-gradient(circle at 85% 15%, rgb(56 189 248 / 0.2), transparent 38%),
22
- linear-gradient(145deg, #0f172a, #172554);
23
- color: #f8fafc;
24
- font-family: Inter, ui-sans-serif, system-ui, sans-serif;
25
- }
26
-
27
- .devvit-preview__eyebrow,
28
- .devvit-preview__description,
29
- .devvit-preview h1 {
30
- margin: 0;
31
- }
32
-
33
- .devvit-preview__eyebrow {
34
- color: #7dd3fc;
35
- font-size: 0.75rem;
36
- font-weight: 700;
37
- letter-spacing: 0.12em;
38
- text-transform: uppercase;
39
- }
40
-
41
- .devvit-preview h1 {
42
- max-width: 18ch;
43
- font-size: clamp(2rem, 9vw, 4.5rem);
44
- line-height: 0.95;
45
- }
46
-
47
- .devvit-preview__description {
48
- max-width: 36rem;
49
- color: #cbd5e1;
50
- line-height: 1.5;
51
- }
52
-
53
- .devvit-preview__button {
54
- min-height: 2.75rem;
55
- margin-top: 0.5rem;
56
- padding: 0.65rem 1.25rem;
57
- border: 0;
58
- border-radius: 999px;
59
- color: #082f49;
60
- background: #7dd3fc;
61
- font: inherit;
62
- font-weight: 800;
63
- cursor: pointer;
64
- }
65
-
66
- .devvit-preview__button:focus-visible {
67
- outline: 3px solid #f8fafc;
68
- outline-offset: 3px;
69
- }
70
-
71
- @media (hover: hover) {
72
- .devvit-preview__button:hover {
73
- filter: brightness(1.1);
74
- }
75
- }