@devvit/ui-renderer 0.8.7 → 0.8.8
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/client/devvit-animation-player.d.ts +12 -0
- package/client/devvit-animation-player.d.ts.map +1 -0
- package/client/devvit-animation-player.js +34 -0
- package/client/devvit-ui-interactive.d.ts +16 -9
- package/client/devvit-ui-interactive.d.ts.map +1 -1
- package/client/devvit-ui-interactive.js +122 -86
- package/package.json +15 -9
- package/render-core.d.ts +3 -3
- package/render-core.d.ts.map +1 -1
- package/render-core.js +98 -22
- package/styles.css +8 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { AnimationFormat } from '@devvit/protos';
|
|
3
|
+
import '@lottiefiles/lottie-player';
|
|
4
|
+
/**
|
|
5
|
+
* @description a simple wrapper for lottie-player
|
|
6
|
+
*/
|
|
7
|
+
export declare class DevvitAnimationPlayer extends LitElement {
|
|
8
|
+
src: string;
|
|
9
|
+
format?: AnimationFormat;
|
|
10
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=devvit-animation-player.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devvit-animation-player.d.ts","sourceRoot":"","sources":["../../src/client/devvit-animation-player.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAE,MAAM,KAAK,CAAC;AAEvC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,4BAA4B,CAAC;AAEpC;;GAEG;AACH,qBACa,qBAAsB,SAAQ,UAAU;IACvB,GAAG,EAAE,MAAM,CAAM;IACjB,MAAM,CAAC,EAAE,eAAe,CAA0B;IAErE,MAAM;CAQhB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { html, LitElement } from 'lit';
|
|
3
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
4
|
+
import { AnimationFormat } from '@devvit/protos';
|
|
5
|
+
import '@lottiefiles/lottie-player';
|
|
6
|
+
/**
|
|
7
|
+
* @description a simple wrapper for lottie-player
|
|
8
|
+
*/
|
|
9
|
+
let DevvitAnimationPlayer = class DevvitAnimationPlayer extends LitElement {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(...arguments);
|
|
12
|
+
this.src = '';
|
|
13
|
+
this.format = AnimationFormat.LOTTIE;
|
|
14
|
+
}
|
|
15
|
+
render() {
|
|
16
|
+
if (this.format === AnimationFormat.LOTTIE) {
|
|
17
|
+
return html `<lottie-player autoplay loop mode="normal" src="${this.src}"></lottie-player>`;
|
|
18
|
+
}
|
|
19
|
+
// eslint-disable-next-line @reddit/i18n-shreddit/no-unwrapped-strings
|
|
20
|
+
return html `<div>Unsupported animation format</div>`;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
__decorate([
|
|
24
|
+
property({ type: String }),
|
|
25
|
+
__metadata("design:type", String)
|
|
26
|
+
], DevvitAnimationPlayer.prototype, "src", void 0);
|
|
27
|
+
__decorate([
|
|
28
|
+
property({ type: Number }),
|
|
29
|
+
__metadata("design:type", Number)
|
|
30
|
+
], DevvitAnimationPlayer.prototype, "format", void 0);
|
|
31
|
+
DevvitAnimationPlayer = __decorate([
|
|
32
|
+
customElement('devvit-animation-player')
|
|
33
|
+
], DevvitAnimationPlayer);
|
|
34
|
+
export { DevvitAnimationPlayer };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference lib="dom" />
|
|
2
1
|
/**
|
|
3
2
|
* Component that renders wraps a rendering of devvit ui and makes it interactive.
|
|
4
3
|
*
|
|
@@ -6,21 +5,29 @@
|
|
|
6
5
|
* 1 - not getting slotted content and IT triggers the first render
|
|
7
6
|
* 2 - getting slotted content from SSR and then replacing it when interactivity happens
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
import type {
|
|
12
|
-
import
|
|
13
|
-
type
|
|
8
|
+
/// <reference lib="dom" />
|
|
9
|
+
import { LitElement, PropertyValues } from 'lit';
|
|
10
|
+
import type { Metadata, Realtime } from '@devvit/protos';
|
|
11
|
+
import { RenderPostResponse } from '@devvit/protos';
|
|
12
|
+
import type { ActorRef } from '@devvit/runtimes/common/runtime/ActorRef.js';
|
|
13
|
+
import './devvit-animation-player.js';
|
|
14
|
+
type RenderStateChange = (state: RenderPostResponse) => void;
|
|
15
|
+
declare global {
|
|
16
|
+
interface HTMLElementTagNameMap {
|
|
17
|
+
'devvit-ui-interactive': DevvitInteractiveUI;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
14
20
|
export declare class DevvitInteractiveUI extends LitElement {
|
|
15
21
|
#private;
|
|
16
|
-
|
|
22
|
+
actorRef?: ActorRef;
|
|
23
|
+
realtime?: Realtime;
|
|
17
24
|
metadata?: Metadata;
|
|
18
25
|
onRender?: RenderStateChange;
|
|
19
|
-
|
|
20
|
-
renderResponse?: RenderResponse;
|
|
26
|
+
renderResponse?: RenderPostResponse;
|
|
21
27
|
static get styles(): import("lit").CSSResult[];
|
|
22
28
|
connectedCallback(): void;
|
|
23
29
|
disconnectedCallback(): void;
|
|
30
|
+
protected willUpdate(changedProperties: PropertyValues): void;
|
|
24
31
|
render(): import("lit-html").TemplateResult<1>;
|
|
25
32
|
}
|
|
26
33
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devvit-ui-interactive.d.ts","sourceRoot":"","sources":["../../src/client/devvit-ui-interactive.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"devvit-ui-interactive.d.ts","sourceRoot":"","sources":["../../src/client/devvit-ui-interactive.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;;AAGH,OAAO,EAAa,UAAU,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAK5D,OAAO,KAAK,EAGV,QAAQ,EACR,QAAQ,EAMT,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAKL,kBAAkB,EAGnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6CAA6C,CAAC;AAO5E,OAAO,8BAA8B,CAAC;AAItC,KAAK,iBAAiB,GAAG,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAK7D,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,uBAAuB,EAAE,mBAAmB,CAAC;KAC9C;CACF;AAED,qBACa,mBAAoB,SAAQ,UAAU;;IACjB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAG7D,cAAc,CAAC,EAAE,kBAAkB,CAAC;IAQpC,WAAoB,MAAM,8BAMzB;IAEQ,iBAAiB;IAOjB,oBAAoB;cAOV,UAAU,CAAC,iBAAiB,EAAE,cAAc;IAwKtD,MAAM;CAiBhB"}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
var _DevvitInteractiveUI_instances, _DevvitInteractiveUI_context, _DevvitInteractiveUI_realtime, _DevvitInteractiveUI_realtimeHandle, _DevvitInteractiveUI_rerenderHandle, _DevvitInteractiveUI_onMessage, _DevvitInteractiveUI_triggerFirstRender, _DevvitInteractiveUI_requestRender, _DevvitInteractiveUI_updateSubscriptions, _DevvitInteractiveUI_handleRenderAgainFromResponse, _DevvitInteractiveUI_onClick;
|
|
2
|
-
import { __classPrivateFieldGet, __classPrivateFieldSet, __decorate, __metadata } from "tslib";
|
|
3
1
|
/**
|
|
4
2
|
* Component that renders wraps a rendering of devvit ui and makes it interactive.
|
|
5
3
|
*
|
|
@@ -8,30 +6,48 @@ import { __classPrivateFieldGet, __classPrivateFieldSet, __decorate, __metadata
|
|
|
8
6
|
* 2 - getting slotted content from SSR and then replacing it when interactivity happens
|
|
9
7
|
*/
|
|
10
8
|
/// <reference lib="dom" />
|
|
11
|
-
|
|
9
|
+
var _DevvitInteractiveUI_instances, _DevvitInteractiveUI_customPost, _DevvitInteractiveUI_uiEventHandler, _DevvitInteractiveUI_context, _DevvitInteractiveUI_rerenderTimeout, _DevvitInteractiveUI_realtimeSubscription, _DevvitInteractiveUI_onMessage, _DevvitInteractiveUI_triggerFirstRender, _DevvitInteractiveUI_requestRender, _DevvitInteractiveUI_onClick, _DevvitInteractiveUI_handleEffects, _DevvitInteractiveUI_handleEvents, _DevvitInteractiveUI_rerenderEffect, _DevvitInteractiveUI_realtimeSubscriptionsEffect, _DevvitInteractiveUI_showToastEffect;
|
|
10
|
+
import { __classPrivateFieldGet, __classPrivateFieldSet, __decorate, __metadata } from "tslib";
|
|
11
|
+
import { css, html, LitElement } from 'lit';
|
|
12
12
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
13
|
-
import { BlocksEvent, BlocksEventType,
|
|
14
|
-
import { renderRoot } from './renderer.js';
|
|
13
|
+
import { BlocksEvent, BlocksEventType, CustomPostDefinition, RenderPostResponse, ToastAppearance, UIEventHandlerDefinition, } from '@devvit/protos';
|
|
15
14
|
import tailwind from '@reddit/shreddit.styles';
|
|
15
|
+
import { customEvent } from '@reddit/faceplate/lib/custom-event.js';
|
|
16
|
+
import { Severity } from '@reddit/faceplate/types.js';
|
|
17
|
+
import { renderRoot } from './renderer.js';
|
|
18
|
+
import './devvit-animation-player.js';
|
|
16
19
|
let DevvitInteractiveUI = class DevvitInteractiveUI extends LitElement {
|
|
17
20
|
constructor() {
|
|
18
21
|
super(...arguments);
|
|
19
22
|
_DevvitInteractiveUI_instances.add(this);
|
|
23
|
+
_DevvitInteractiveUI_customPost.set(this, void 0);
|
|
24
|
+
_DevvitInteractiveUI_uiEventHandler.set(this, void 0);
|
|
20
25
|
_DevvitInteractiveUI_context.set(this, {});
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
_DevvitInteractiveUI_rerenderHandle.set(this, void 0);
|
|
26
|
+
_DevvitInteractiveUI_rerenderTimeout.set(this, void 0);
|
|
27
|
+
_DevvitInteractiveUI_realtimeSubscription.set(this, void 0);
|
|
24
28
|
_DevvitInteractiveUI_onMessage.set(this, (event) => {
|
|
25
29
|
console.log('got message', event);
|
|
26
30
|
const evt = BlocksEvent.fromJSON(event.data);
|
|
27
31
|
if (evt.type === BlocksEventType.USER_ACTION) {
|
|
28
32
|
const req = {
|
|
29
|
-
event: evt,
|
|
30
33
|
context: __classPrivateFieldGet(this, _DevvitInteractiveUI_context, "f"),
|
|
34
|
+
blocks: {
|
|
35
|
+
event: evt,
|
|
36
|
+
},
|
|
31
37
|
};
|
|
32
38
|
void __classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_requestRender).call(this, req);
|
|
33
39
|
}
|
|
34
40
|
});
|
|
41
|
+
_DevvitInteractiveUI_handleEvents.set(this, async (event) => {
|
|
42
|
+
const res = await __classPrivateFieldGet(this, _DevvitInteractiveUI_uiEventHandler, "f")?.HandleUIEvent({
|
|
43
|
+
context: __classPrivateFieldGet(this, _DevvitInteractiveUI_context, "f"),
|
|
44
|
+
event,
|
|
45
|
+
});
|
|
46
|
+
if (res) {
|
|
47
|
+
__classPrivateFieldSet(this, _DevvitInteractiveUI_context, res.context, "f");
|
|
48
|
+
await __classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_handleEffects).call(this, res.effects);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
35
51
|
}
|
|
36
52
|
static get styles() {
|
|
37
53
|
return [
|
|
@@ -42,50 +58,65 @@ let DevvitInteractiveUI = class DevvitInteractiveUI extends LitElement {
|
|
|
42
58
|
}
|
|
43
59
|
connectedCallback() {
|
|
44
60
|
super.connectedCallback();
|
|
45
|
-
void __classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_triggerFirstRender).call(this);
|
|
46
61
|
window.addEventListener('message', __classPrivateFieldGet(this, _DevvitInteractiveUI_onMessage, "f"));
|
|
62
|
+
__classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_triggerFirstRender).call(this);
|
|
47
63
|
}
|
|
48
64
|
disconnectedCallback() {
|
|
49
65
|
super.disconnectedCallback();
|
|
50
66
|
console.log('disconnected from custom post preview');
|
|
51
67
|
window.removeEventListener('message', __classPrivateFieldGet(this, _DevvitInteractiveUI_onMessage, "f"));
|
|
52
68
|
}
|
|
69
|
+
willUpdate(changedProperties) {
|
|
70
|
+
if (changedProperties.has('actorRef')) {
|
|
71
|
+
__classPrivateFieldSet(this, _DevvitInteractiveUI_customPost, this.actorRef?.As(CustomPostDefinition), "f");
|
|
72
|
+
__classPrivateFieldSet(this, _DevvitInteractiveUI_uiEventHandler, this.actorRef?.As(UIEventHandlerDefinition), "f");
|
|
73
|
+
__classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_triggerFirstRender).call(this);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
53
76
|
render() {
|
|
54
|
-
//
|
|
77
|
+
// At first, we don't have a render response. In the meantime allow
|
|
78
|
+
// slot'd content to be rendered. This lets SSR work.
|
|
55
79
|
if (!this.renderResponse) {
|
|
56
|
-
|
|
57
|
-
return html `<div>Loading...</div>`;
|
|
80
|
+
return html `<slot></slot>`;
|
|
58
81
|
}
|
|
59
|
-
const uiRoot = this.renderResponse.ui;
|
|
82
|
+
const uiRoot = this.renderResponse.blocks?.ui;
|
|
60
83
|
// TODO better handle case where there is no UI to render
|
|
61
84
|
if (!uiRoot) {
|
|
62
85
|
// eslint-disable-next-line @reddit/i18n-shreddit/no-unwrapped-strings
|
|
63
86
|
return html `<div>No UI to render</div>`;
|
|
64
87
|
}
|
|
65
|
-
return html `<div @click
|
|
88
|
+
return html `<div @click="${__classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_onClick)}">${renderRoot(uiRoot, this.renderResponse)}</div>`;
|
|
66
89
|
}
|
|
67
90
|
};
|
|
68
|
-
|
|
91
|
+
_DevvitInteractiveUI_customPost = new WeakMap(), _DevvitInteractiveUI_uiEventHandler = new WeakMap(), _DevvitInteractiveUI_context = new WeakMap(), _DevvitInteractiveUI_rerenderTimeout = new WeakMap(), _DevvitInteractiveUI_realtimeSubscription = new WeakMap(), _DevvitInteractiveUI_onMessage = new WeakMap(), _DevvitInteractiveUI_handleEvents = new WeakMap(), _DevvitInteractiveUI_instances = new WeakSet(), _DevvitInteractiveUI_triggerFirstRender = function _DevvitInteractiveUI_triggerFirstRender() {
|
|
92
|
+
if (!this.actorRef || !__classPrivateFieldGet(this, _DevvitInteractiveUI_customPost, "f")) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
69
95
|
const req = {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
96
|
+
blocks: {
|
|
97
|
+
event: {
|
|
98
|
+
type: BlocksEventType.INITIAL_RENDER,
|
|
99
|
+
key: 'initial-render',
|
|
100
|
+
data: {},
|
|
101
|
+
},
|
|
74
102
|
},
|
|
75
103
|
context: {},
|
|
76
104
|
};
|
|
77
|
-
|
|
105
|
+
void __classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_requestRender).call(this, req);
|
|
78
106
|
}, _DevvitInteractiveUI_requestRender =
|
|
79
107
|
// TODO block on only one render request going through at a time
|
|
80
108
|
async function _DevvitInteractiveUI_requestRender(req) {
|
|
81
109
|
try {
|
|
82
|
-
const response = await this
|
|
110
|
+
const response = await __classPrivateFieldGet(this, _DevvitInteractiveUI_customPost, "f")?.RenderPost(req, this.metadata);
|
|
83
111
|
if (response) {
|
|
84
112
|
__classPrivateFieldSet(this, _DevvitInteractiveUI_context, response.context, "f");
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
113
|
+
if (response.blocks) {
|
|
114
|
+
this.renderResponse = response;
|
|
115
|
+
this.onRender?.(response);
|
|
116
|
+
}
|
|
117
|
+
if (response.effects) {
|
|
118
|
+
await __classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_handleEffects).call(this, response.effects);
|
|
119
|
+
}
|
|
89
120
|
}
|
|
90
121
|
else {
|
|
91
122
|
console.warn('No response from custom post');
|
|
@@ -94,55 +125,6 @@ async function _DevvitInteractiveUI_requestRender(req) {
|
|
|
94
125
|
catch (err) {
|
|
95
126
|
console.error('Error while running custom post', err);
|
|
96
127
|
}
|
|
97
|
-
}, _DevvitInteractiveUI_updateSubscriptions = function _DevvitInteractiveUI_updateSubscriptions(subscriptions) {
|
|
98
|
-
if (!__classPrivateFieldGet(this, _DevvitInteractiveUI_realtime, "f")) {
|
|
99
|
-
if (this.runtime) {
|
|
100
|
-
__classPrivateFieldSet(this, _DevvitInteractiveUI_realtime, this.runtime.getPlugin(RealtimeDefinition), "f");
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
console.error('No runtime found');
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (__classPrivateFieldGet(this, _DevvitInteractiveUI_realtimeHandle, "f")) {
|
|
108
|
-
__classPrivateFieldGet(this, _DevvitInteractiveUI_realtimeHandle, "f").unsubscribe();
|
|
109
|
-
}
|
|
110
|
-
if (!__classPrivateFieldGet(this, _DevvitInteractiveUI_realtime, "f")) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
const obs = __classPrivateFieldGet(this, _DevvitInteractiveUI_realtime, "f").Subscribe({ channels: subscriptions });
|
|
114
|
-
__classPrivateFieldSet(this, _DevvitInteractiveUI_realtimeHandle, obs.subscribe((event) => {
|
|
115
|
-
console.log('got realtime event', event);
|
|
116
|
-
const req = {
|
|
117
|
-
event: {
|
|
118
|
-
type: BlocksEventType.SUBSCRIPTION_EVENT,
|
|
119
|
-
key: event.channel,
|
|
120
|
-
data: event.data,
|
|
121
|
-
}, context: __classPrivateFieldGet(this, _DevvitInteractiveUI_context, "f"),
|
|
122
|
-
};
|
|
123
|
-
void __classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_requestRender).call(this, req);
|
|
124
|
-
}), "f");
|
|
125
|
-
}, _DevvitInteractiveUI_handleRenderAgainFromResponse = function _DevvitInteractiveUI_handleRenderAgainFromResponse(response) {
|
|
126
|
-
// TODO: This could keep the app running indefinitely when you switch tabs and aren't
|
|
127
|
-
// focused on it (bad for battery / cpu).
|
|
128
|
-
if (__classPrivateFieldGet(this, _DevvitInteractiveUI_rerenderHandle, "f")) {
|
|
129
|
-
window.clearTimeout(__classPrivateFieldGet(this, _DevvitInteractiveUI_rerenderHandle, "f"));
|
|
130
|
-
__classPrivateFieldSet(this, _DevvitInteractiveUI_rerenderHandle, undefined, "f");
|
|
131
|
-
}
|
|
132
|
-
let tick = response.renderAgainInSeconds;
|
|
133
|
-
if (tick && tick > 0) {
|
|
134
|
-
tick = Math.round(tick * 1000);
|
|
135
|
-
const req = {
|
|
136
|
-
event: {
|
|
137
|
-
type: BlocksEventType.SCHEDULED_EVENT,
|
|
138
|
-
key: "scheduled-event",
|
|
139
|
-
data: {},
|
|
140
|
-
}, context: __classPrivateFieldGet(this, _DevvitInteractiveUI_context, "f"),
|
|
141
|
-
};
|
|
142
|
-
__classPrivateFieldSet(this, _DevvitInteractiveUI_rerenderHandle, window.setTimeout(() => {
|
|
143
|
-
void __classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_requestRender).call(this, req);
|
|
144
|
-
}, tick), "f");
|
|
145
|
-
}
|
|
146
128
|
}, _DevvitInteractiveUI_onClick = function _DevvitInteractiveUI_onClick(event) {
|
|
147
129
|
// walk the tree and find the nearest data-action-id
|
|
148
130
|
// and use that to send a new render request into the app
|
|
@@ -157,19 +139,78 @@ async function _DevvitInteractiveUI_requestRender(req) {
|
|
|
157
139
|
}
|
|
158
140
|
if (actionId) {
|
|
159
141
|
const req = {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
142
|
+
context: __classPrivateFieldGet(this, _DevvitInteractiveUI_context, "f"),
|
|
143
|
+
blocks: {
|
|
144
|
+
event: {
|
|
145
|
+
type: BlocksEventType.USER_ACTION,
|
|
146
|
+
key: actionId,
|
|
147
|
+
data: {},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
165
150
|
};
|
|
166
151
|
void __classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_requestRender).call(this, req);
|
|
167
152
|
}
|
|
153
|
+
}, _DevvitInteractiveUI_handleEffects = async function _DevvitInteractiveUI_handleEffects(effects) {
|
|
154
|
+
const running = [];
|
|
155
|
+
for (const effect of effects) {
|
|
156
|
+
if (effect.rerenderUi) {
|
|
157
|
+
running.push(__classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_rerenderEffect).call(this, effect.rerenderUi));
|
|
158
|
+
}
|
|
159
|
+
else if (effect.realtimeSubscriptions) {
|
|
160
|
+
__classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_realtimeSubscriptionsEffect).call(this, effect.realtimeSubscriptions);
|
|
161
|
+
}
|
|
162
|
+
else if (effect.showToast) {
|
|
163
|
+
__classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_showToastEffect).call(this, effect.showToast);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// Can't handle this directly. Bubble effect up.
|
|
167
|
+
this.dispatchEvent(customEvent('devvit-ui-effect', { effect, onEvent: __classPrivateFieldGet(this, _DevvitInteractiveUI_handleEvents, "f") }));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
await Promise.allSettled(running);
|
|
171
|
+
}, _DevvitInteractiveUI_rerenderEffect = async function _DevvitInteractiveUI_rerenderEffect(effect) {
|
|
172
|
+
const rerender = async () => {
|
|
173
|
+
__classPrivateFieldSet(this, _DevvitInteractiveUI_rerenderTimeout, undefined, "f");
|
|
174
|
+
await __classPrivateFieldGet(this, _DevvitInteractiveUI_instances, "m", _DevvitInteractiveUI_requestRender).call(this, {
|
|
175
|
+
context: __classPrivateFieldGet(this, _DevvitInteractiveUI_context, "f"),
|
|
176
|
+
blocks: {
|
|
177
|
+
event: {
|
|
178
|
+
type: BlocksEventType.EFFECT_ACTION,
|
|
179
|
+
key: 'rerender-effect',
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
// Rerender already queued. Resolve it early and queue another.
|
|
185
|
+
if (__classPrivateFieldGet(this, _DevvitInteractiveUI_rerenderTimeout, "f")) {
|
|
186
|
+
window.clearTimeout(__classPrivateFieldGet(this, _DevvitInteractiveUI_rerenderTimeout, "f"));
|
|
187
|
+
await rerender();
|
|
188
|
+
}
|
|
189
|
+
__classPrivateFieldSet(this, _DevvitInteractiveUI_rerenderTimeout, window.setTimeout(() => void rerender(), (effect.delaySeconds ?? 0) * 1000), "f");
|
|
190
|
+
}, _DevvitInteractiveUI_realtimeSubscriptionsEffect = function _DevvitInteractiveUI_realtimeSubscriptionsEffect(effect) {
|
|
191
|
+
__classPrivateFieldSet(this, _DevvitInteractiveUI_realtimeSubscription, this.realtime
|
|
192
|
+
?.Subscribe({ channels: effect.subscriptionIds }, this.metadata)
|
|
193
|
+
.subscribe((event) => {
|
|
194
|
+
void __classPrivateFieldGet(this, _DevvitInteractiveUI_handleEvents, "f").call(this, { realtimeEvent: { event } });
|
|
195
|
+
}), "f");
|
|
196
|
+
}, _DevvitInteractiveUI_showToastEffect = function _DevvitInteractiveUI_showToastEffect(effect) {
|
|
197
|
+
// TODO:
|
|
198
|
+
// - effect.toast.leadingElement
|
|
199
|
+
// - effect.toast.trailingElement
|
|
200
|
+
// - onAction
|
|
201
|
+
this.dispatchEvent(customEvent('faceplate-alert', {
|
|
202
|
+
level: effect.toast?.appearance === ToastAppearance.SUCCESS ? Severity.success : Severity.info,
|
|
203
|
+
message: effect.toast?.text,
|
|
204
|
+
}));
|
|
168
205
|
};
|
|
169
206
|
__decorate([
|
|
170
207
|
property({ attribute: false }),
|
|
171
208
|
__metadata("design:type", Object)
|
|
172
|
-
], DevvitInteractiveUI.prototype, "
|
|
209
|
+
], DevvitInteractiveUI.prototype, "actorRef", void 0);
|
|
210
|
+
__decorate([
|
|
211
|
+
property({ attribute: false }),
|
|
212
|
+
__metadata("design:type", Object)
|
|
213
|
+
], DevvitInteractiveUI.prototype, "realtime", void 0);
|
|
173
214
|
__decorate([
|
|
174
215
|
property({ attribute: false }),
|
|
175
216
|
__metadata("design:type", Object)
|
|
@@ -178,13 +219,8 @@ __decorate([
|
|
|
178
219
|
property({ attribute: false }),
|
|
179
220
|
__metadata("design:type", Function)
|
|
180
221
|
], DevvitInteractiveUI.prototype, "onRender", void 0);
|
|
181
|
-
__decorate([
|
|
182
|
-
property({ attribute: false }),
|
|
183
|
-
__metadata("design:type", Object)
|
|
184
|
-
], DevvitInteractiveUI.prototype, "runtime", void 0);
|
|
185
222
|
__decorate([
|
|
186
223
|
state(),
|
|
187
|
-
property({ attribute: false, reflect: true }),
|
|
188
224
|
__metadata("design:type", Object)
|
|
189
225
|
], DevvitInteractiveUI.prototype, "renderResponse", void 0);
|
|
190
226
|
DevvitInteractiveUI = __decorate([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devvit/ui-renderer",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
4
4
|
"license": "BSD-3-Clause",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,15 +26,20 @@
|
|
|
26
26
|
},
|
|
27
27
|
"types": "./index.d.ts",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@devvit/protos": "0.8.
|
|
30
|
-
"@devvit/runtimes": "0.8.
|
|
31
|
-
"
|
|
29
|
+
"@devvit/protos": "0.8.8",
|
|
30
|
+
"@devvit/runtimes": "0.8.8",
|
|
31
|
+
"@lottiefiles/lottie-player": "1.7.1",
|
|
32
|
+
"rxjs": "7.5.7"
|
|
32
33
|
},
|
|
33
34
|
"peerDependencies": {
|
|
35
|
+
"@reddit/faceplate": "2.0.0",
|
|
34
36
|
"@reddit/faceplate-ui": "1.0.5-6",
|
|
35
37
|
"@reddit/shreddit.styles": "^1.0.8"
|
|
36
38
|
},
|
|
37
39
|
"peerDependenciesMeta": {
|
|
40
|
+
"@reddit/faceplate": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
38
43
|
"@reddit/faceplate-ui": {
|
|
39
44
|
"optional": true
|
|
40
45
|
},
|
|
@@ -47,12 +52,13 @@
|
|
|
47
52
|
"lit": "^2.0.0"
|
|
48
53
|
},
|
|
49
54
|
"devDependencies": {
|
|
50
|
-
"@devvit/eslint-config": "0.8.
|
|
51
|
-
"@devvit/public-api": "0.8.
|
|
52
|
-
"@devvit/repo-tools": "0.8.
|
|
53
|
-
"@devvit/tsconfig": "0.8.
|
|
55
|
+
"@devvit/eslint-config": "0.8.8",
|
|
56
|
+
"@devvit/public-api": "0.8.8",
|
|
57
|
+
"@devvit/repo-tools": "0.8.8",
|
|
58
|
+
"@devvit/tsconfig": "0.8.8",
|
|
54
59
|
"@reddit/baseplate": "^0.13.0",
|
|
55
60
|
"@reddit/eslint-plugin-i18n-shreddit": "0.1.0",
|
|
61
|
+
"@reddit/faceplate": "2.0.0",
|
|
56
62
|
"@reddit/faceplate-ui": "1.0.5-6",
|
|
57
63
|
"eslint": "8.9.0",
|
|
58
64
|
"lit": "^2.0.0",
|
|
@@ -63,5 +69,5 @@
|
|
|
63
69
|
"directory": "dist"
|
|
64
70
|
},
|
|
65
71
|
"source": "./src/index.ts",
|
|
66
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "495020478f831cb7235b2dbdb494a9cd67b59e14"
|
|
67
73
|
}
|
package/render-core.d.ts
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Styling not feasible with tailwind classes will be done with inline styles or css classes
|
|
15
15
|
*/
|
|
16
|
-
import { Element,
|
|
16
|
+
import { Element, RenderPostResponse } from '@devvit/protos';
|
|
17
17
|
import type { TemplateLike } from '@reddit/baseplate/html.js';
|
|
18
|
-
export declare const renderRoot: (element: Element, rsp:
|
|
19
|
-
export declare const renderElement: (element: Element, rsp:
|
|
18
|
+
export declare const renderRoot: (element: Element, rsp: RenderPostResponse) => TemplateLike;
|
|
19
|
+
export declare const renderElement: (element: Element, rsp: RenderPostResponse) => TemplateLike;
|
|
20
20
|
//# sourceMappingURL=render-core.d.ts.map
|
package/render-core.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-core.d.ts","sourceRoot":"","sources":["../src/render-core.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"render-core.d.ts","sourceRoot":"","sources":["../src/render-core.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAEL,OAAO,EAOP,kBAAkB,EAKnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAc9D,eAAO,MAAM,UAAU,YAAa,OAAO,OAAO,kBAAkB,KAAG,YAKtE,CAAC;AAEF,eAAO,MAAM,aAAa,YAAa,OAAO,OAAO,kBAAkB,KAAG,YAsBzE,CAAC"}
|
package/render-core.js
CHANGED
|
@@ -14,8 +14,15 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Styling not feasible with tailwind classes will be done with inline styles or css classes
|
|
16
16
|
*/
|
|
17
|
-
import { ElementType, ObjectFit, Padding, StackAlign, StackDirection, StackRoundedCornerSize, TextAlign, } from '@devvit/protos';
|
|
17
|
+
import { AnimationFormat, ElementType, ButtonAppearance, ButtonShape, ButtonSize, ObjectFit, Padding, StackAlign, StackDirection, StackRoundedCornerSize, TextAlign, } from '@devvit/protos';
|
|
18
18
|
import { getTemplateRenderingStrategy } from '@reddit/faceplate-ui/faceplateUIConfig.js';
|
|
19
|
+
// Faceplate Client|Server agnostic templates.
|
|
20
|
+
// These cannot be called reliably until the faceplate rendering strategy is set.
|
|
21
|
+
// To make sure that works, callers should import the render functions from the client/server
|
|
22
|
+
// directories instead of this file.
|
|
23
|
+
import { button as faceplateButton } from '@reddit/faceplate-ui/templates/button.js';
|
|
24
|
+
import { ButtonSize as FPButtonSize } from '@reddit/faceplate-ui/templates/button.js';
|
|
25
|
+
// Rendering functions
|
|
19
26
|
export const renderRoot = (element, rsp) => {
|
|
20
27
|
const { html } = getTemplateRenderingStrategy();
|
|
21
28
|
return html `<div class="font-semibold p-sm text-12 font-sans tracking-tight leading-5">
|
|
@@ -28,6 +35,8 @@ export const renderElement = (element, rsp) => {
|
|
|
28
35
|
return renderStack(element, rsp);
|
|
29
36
|
case ElementType.IMAGE:
|
|
30
37
|
return renderImage(element);
|
|
38
|
+
case ElementType.ANIMATION:
|
|
39
|
+
return renderAnimation(element);
|
|
31
40
|
case ElementType.BUTTON:
|
|
32
41
|
return renderButton(element);
|
|
33
42
|
case ElementType.SPACER:
|
|
@@ -49,18 +58,19 @@ const renderWebView = (element, rsp) => {
|
|
|
49
58
|
postMessageToIframe(element.src, rsp);
|
|
50
59
|
return html `<iframe
|
|
51
60
|
class="border-box ${grow(element)}"
|
|
52
|
-
@load="${(evt) => {
|
|
53
|
-
|
|
61
|
+
@load="${(evt) => {
|
|
62
|
+
refBySrc[element.src] = evt.target;
|
|
63
|
+
postMessageToIframe(element.src, rsp);
|
|
64
|
+
}}"
|
|
65
|
+
ref="${(el) => (refBySrc[element.src] = el)}"
|
|
54
66
|
sandbox="allow-scripts"
|
|
55
|
-
src
|
|
56
|
-
style
|
|
67
|
+
src="${element.src}"
|
|
68
|
+
style="${styleMap({
|
|
57
69
|
backgroundColor: element.backgroundColor,
|
|
58
70
|
borderColor: element.borderColor,
|
|
59
|
-
borderWidth: element.borderColor ? '1px' :
|
|
60
|
-
borderRadius: element.roundedCornerSize
|
|
61
|
-
|
|
62
|
-
: undefined,
|
|
63
|
-
})}
|
|
71
|
+
borderWidth: element.borderColor ? '1px' : '0',
|
|
72
|
+
borderRadius: element.roundedCornerSize ? `${element.roundedCornerSize}px` : undefined,
|
|
73
|
+
})}"
|
|
64
74
|
></iframe>`;
|
|
65
75
|
};
|
|
66
76
|
const postMessageToIframe = (src, rsp) => {
|
|
@@ -90,8 +100,8 @@ const renderStack = (element, rsp) => {
|
|
|
90
100
|
const { html, repeat, styleMap } = getTemplateRenderingStrategy();
|
|
91
101
|
return html `<div
|
|
92
102
|
class="border-box ${layoutClass} ${grow(element)} ${flexDirection} ${alignContent} ${justifyContent} ${borderRadiusClass} ${paddingClass}"
|
|
93
|
-
style
|
|
94
|
-
data-action-id
|
|
103
|
+
style="${styleMap(style)}"
|
|
104
|
+
data-action-id="${element.actionId}"
|
|
95
105
|
>
|
|
96
106
|
${repeat(element.children, (e) => renderElement(e, rsp))}
|
|
97
107
|
</div>`;
|
|
@@ -186,19 +196,32 @@ const getBorderRadiusClass = (rounding) => {
|
|
|
186
196
|
default:
|
|
187
197
|
return 'rounded-none';
|
|
188
198
|
}
|
|
189
|
-
return '';
|
|
190
199
|
};
|
|
191
200
|
const renderImage = (element) => {
|
|
192
201
|
const { html } = getTemplateRenderingStrategy();
|
|
202
|
+
// note: empty ' ' at start of `class` to avoid false-positive in
|
|
203
|
+
// lit/quoted-expressions rule
|
|
193
204
|
return html `<img
|
|
205
|
+
class=" ${getObjectFitClass(element.objectFit)} ${grow(element)}"
|
|
194
206
|
referrerpolicy="no-referrer"
|
|
195
207
|
crossorigin="anonymous"
|
|
196
208
|
loading="lazy"
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
data-action-id=${element.actionId}
|
|
209
|
+
src="${element.src}"
|
|
210
|
+
data-action-id="${element.actionId}"
|
|
200
211
|
/>`;
|
|
201
212
|
};
|
|
213
|
+
const renderAnimation = (element) => {
|
|
214
|
+
const { html } = getTemplateRenderingStrategy();
|
|
215
|
+
const { src, animationFormat } = element;
|
|
216
|
+
return html `
|
|
217
|
+
<devvit-animation-player
|
|
218
|
+
class=" ${getObjectFitClass(element.objectFit)} ${grow(element)}"
|
|
219
|
+
src="${src}"
|
|
220
|
+
format="${animationFormat ?? AnimationFormat.LOTTIE}"
|
|
221
|
+
>
|
|
222
|
+
</devvit-animation-player>
|
|
223
|
+
`;
|
|
224
|
+
};
|
|
202
225
|
const getObjectFitClass = (fit) => {
|
|
203
226
|
switch (fit) {
|
|
204
227
|
case ObjectFit.CONTAIN:
|
|
@@ -218,9 +241,58 @@ const renderButton = (element) => {
|
|
|
218
241
|
// TODO improve button rendering
|
|
219
242
|
// TODO figure out how to dynamically pick the client vs server faceplate template
|
|
220
243
|
const { html } = getTemplateRenderingStrategy();
|
|
221
|
-
return
|
|
222
|
-
|
|
223
|
-
|
|
244
|
+
return faceplateButton({
|
|
245
|
+
attributes: {
|
|
246
|
+
className: grow(element),
|
|
247
|
+
'data-action-id': element.actionId,
|
|
248
|
+
},
|
|
249
|
+
children: html `${element.text}`,
|
|
250
|
+
shape: getFPButtonShape(element),
|
|
251
|
+
appearance: getFPButtonAppearance(element),
|
|
252
|
+
size: getFPButtonSize(element),
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
const getFPButtonShape = (element) => {
|
|
256
|
+
switch (element.buttonShape) {
|
|
257
|
+
case ButtonShape.SQUARE:
|
|
258
|
+
return 'square';
|
|
259
|
+
case ButtonShape.PILL:
|
|
260
|
+
case ButtonShape.UNRECOGNIZED:
|
|
261
|
+
default:
|
|
262
|
+
return 'pill';
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
const getFPButtonAppearance = (element) => {
|
|
266
|
+
switch (element.buttonAppearance) {
|
|
267
|
+
case ButtonAppearance.BUTTON_PRIMARY:
|
|
268
|
+
return 'primary';
|
|
269
|
+
case ButtonAppearance.BUTTON_PLAIN:
|
|
270
|
+
return 'plain';
|
|
271
|
+
case ButtonAppearance.BUTTON_OUTLINE:
|
|
272
|
+
return 'outline';
|
|
273
|
+
case ButtonAppearance.BUTTON_DESTRUCTIVE:
|
|
274
|
+
return 'destructive';
|
|
275
|
+
case ButtonAppearance.BUTTON_MEDIA:
|
|
276
|
+
return 'media';
|
|
277
|
+
case ButtonAppearance.BUTTON_BRAND:
|
|
278
|
+
return 'brand';
|
|
279
|
+
case ButtonAppearance.BUTTON_SUCCESS:
|
|
280
|
+
return 'success';
|
|
281
|
+
case ButtonAppearance.BUTTON_SECONDARY:
|
|
282
|
+
default:
|
|
283
|
+
return 'secondary';
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const getFPButtonSize = (element) => {
|
|
287
|
+
switch (element.buttonSize) {
|
|
288
|
+
case ButtonSize.BUTTON_SMALL:
|
|
289
|
+
return FPButtonSize.Small;
|
|
290
|
+
case ButtonSize.BUTTON_LARGE:
|
|
291
|
+
return FPButtonSize.Large;
|
|
292
|
+
case ButtonSize.BUTTON_MEDIUM:
|
|
293
|
+
default:
|
|
294
|
+
return FPButtonSize.Medium;
|
|
295
|
+
}
|
|
224
296
|
};
|
|
225
297
|
const getSpacerPaddingClass = (element) => {
|
|
226
298
|
if (element.padding === Padding.PADDING_LARGE) {
|
|
@@ -239,11 +311,13 @@ const getSpacerPaddingClass = (element) => {
|
|
|
239
311
|
const renderSpacer = (element) => {
|
|
240
312
|
const paddingClass = getSpacerPaddingClass(element);
|
|
241
313
|
const { html } = getTemplateRenderingStrategy();
|
|
242
|
-
|
|
314
|
+
// note: empty ' ' at start of `class` to avoid false-positive in
|
|
315
|
+
// lit/quoted-expressions rule
|
|
316
|
+
return html `<div class=" ${grow(element)} ${paddingClass}"></div>`;
|
|
243
317
|
};
|
|
244
318
|
const renderFragment = (element, rsp) => {
|
|
245
319
|
const { html, repeat } = getTemplateRenderingStrategy();
|
|
246
|
-
return html `${repeat(element.children, (e) => renderElement(e, rsp))}
|
|
320
|
+
return html `${repeat(element.children, (e) => renderElement(e, rsp))}`;
|
|
247
321
|
};
|
|
248
322
|
const renderText = (element) => {
|
|
249
323
|
const style = {
|
|
@@ -253,7 +327,9 @@ const renderText = (element) => {
|
|
|
253
327
|
};
|
|
254
328
|
const textAlignClass = getTextAlignClass(element.textAlign);
|
|
255
329
|
const { html, styleMap } = getTemplateRenderingStrategy();
|
|
256
|
-
|
|
330
|
+
// note: empty ' ' at start of `class` to avoid false-positive in
|
|
331
|
+
// lit/quoted-expressions rule
|
|
332
|
+
return html `<div class=" ${textAlignClass} ${grow(element)}" style="${styleMap(style)}">
|
|
257
333
|
${element.text}
|
|
258
334
|
</div>`;
|
|
259
335
|
};
|
package/styles.css
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* devvit-depth powers ZStack.
|
|
3
3
|
* Requires real CSS classes for ease of implementation
|
|
4
|
+
*
|
|
5
|
+
* TODO (schwers)
|
|
6
|
+
* We could refactor this tailwind by doing the following
|
|
7
|
+
* - `grid grid-cols-1` on the stack
|
|
8
|
+
* - and then `col-start-1 row-start-1` on all children
|
|
9
|
+
*
|
|
10
|
+
* This could make it easier to consume this everywhere.
|
|
11
|
+
* I want to setup visual testing before doing this though.
|
|
4
12
|
*/
|
|
5
13
|
.devvit-depth {
|
|
6
14
|
display: grid;
|