afterpresets 0.0.0-stage → 0.2.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/LICENSE +25 -0
- package/README.md +20 -2
- package/bin/ap.js +2 -0
- package/package.json +39 -4
- package/runtime/docs/cep/host-after-effects.md +57 -0
- package/runtime/docs/cep/host-premiere.md +84 -0
- package/runtime/docs/cep/host.md +128 -0
- package/runtime/docs/common/README.md +65 -0
- package/runtime/docs/common/assets.md +88 -0
- package/runtime/docs/common/boot.md +103 -0
- package/runtime/docs/common/licensing.md +261 -0
- package/runtime/docs/common/panels.md +114 -0
- package/runtime/docs/common/storage.md +82 -0
- package/runtime/docs/common/updates.md +251 -0
- package/runtime/package.json +5 -0
- package/runtime/runtime-files.json +14 -0
- package/runtime/src/boot.js +1 -0
- package/runtime/src/computer-id.js +1 -0
- package/runtime/src/core.js +1 -0
- package/runtime/src/events.js +1 -0
- package/runtime/src/host.js +1 -0
- package/runtime/src/license.js +1 -0
- package/runtime/src/panels.js +1 -0
- package/runtime/src/storage.js +1 -0
- package/runtime/src/updates.js +1 -0
- package/runtime/src/vendor/CSInterface.js +1 -0
- package/runtime/src/vendor/json2.jsx +506 -0
- package/runtime/src/zip.js +1 -0
- package/src/api/client.js +1 -0
- package/src/api/error-messages.js +1 -0
- package/src/api/session.js +1 -0
- package/src/args.js +1 -0
- package/src/auth/credentials.js +2 -0
- package/src/auth/logout.js +1 -0
- package/src/build/assets.js +1 -0
- package/src/build/build.js +9 -0
- package/src/build/bundle.js +5 -0
- package/src/build/html.js +4 -0
- package/src/build/jsx.js +3 -0
- package/src/build/jsxbin.js +1 -0
- package/src/build/runtime.js +4 -0
- package/src/check/check.js +3 -0
- package/src/check/extendscript-globals.d.ts +6 -0
- package/src/check/pricing.js +1 -0
- package/src/check/types.js +3 -0
- package/src/cli.js +5 -0
- package/src/commands/add.js +11 -0
- package/src/commands/build.js +6 -0
- package/src/commands/check.js +13 -0
- package/src/commands/config.js +7 -0
- package/src/commands/dev.js +6 -0
- package/src/commands/docs.js +1 -0
- package/src/commands/init.js +12 -0
- package/src/commands/login.js +3 -0
- package/src/commands/logout.js +1 -0
- package/src/commands/mcp.js +7 -0
- package/src/commands/push.js +7 -0
- package/src/commands/releases.js +5 -0
- package/src/commands/testers.js +1 -0
- package/src/commands/upgrade.js +2 -0
- package/src/commands/whoami.js +1 -0
- package/src/config.js +1 -0
- package/src/context.js +1 -0
- package/src/dev/cep-link.js +1 -0
- package/src/dev/debug-file.js +10 -0
- package/src/dev/debug-mode.js +1 -0
- package/src/dev/session.js +4 -0
- package/src/errors.js +1 -0
- package/src/mcp/capture.js +1 -0
- package/src/mcp/extendscript.js +9 -0
- package/src/mcp/panels.js +1 -0
- package/src/mcp/protocol.js +4 -0
- package/src/mcp/redact.js +1 -0
- package/src/mcp/server.js +2 -0
- package/src/mcp/snapshot.js +2 -0
- package/src/mcp/tools/account.js +3 -0
- package/src/mcp/tools/host.js +1 -0
- package/src/mcp/tools/panel.js +7 -0
- package/src/mcp/tools/project.js +40 -0
- package/src/mcp/tools/releases.js +29 -0
- package/src/output.js +6 -0
- package/src/project/add-panel.js +11 -0
- package/src/project/docs.js +3 -0
- package/src/project/manifest.js +6 -0
- package/src/project/panels.js +1 -0
- package/src/project/project.js +2 -0
- package/src/project/semver.js +1 -0
- package/src/project/template.js +4 -0
- package/src/prompt.js +1 -0
- package/src/release/installers.js +4 -0
- package/src/release/package.js +1 -0
- package/src/release/publish.js +1 -0
- package/src/system/run-command.js +1 -0
- package/src/system/update-check.js +2 -0
- package/src/targets.js +1 -0
- package/src/user-config.js +2 -0
- package/template/cep/.mcp.json +8 -0
- package/template/cep/AGENTS.md +169 -0
- package/template/cep/README.md +78 -0
- package/template/cep/docs/initial_setup.md +69 -0
- package/template/cep/gitignore +12 -0
- package/template/cep/package.json +14 -0
- package/template/cep/src/features/home/home.css +67 -0
- package/template/cep/src/features/home/home.html +23 -0
- package/template/cep/src/features/home/home.js +40 -0
- package/template/cep/src/features/license-screen/license-screen.css +80 -0
- package/template/cep/src/features/license-screen/license-screen.html +72 -0
- package/template/cep/src/features/license-screen/license-screen.js +60 -0
- package/template/cep/src/features/update-screen/update-screen.css +173 -0
- package/template/cep/src/features/update-screen/update-screen.html +57 -0
- package/template/cep/src/features/update-screen/update-screen.js +196 -0
- package/template/cep/src/panels/main/index.html +21 -0
- package/template/cep/src/panels/main/main.js +39 -0
- package/template/cep/src/shared/lib/dom.js +29 -0
- package/template/cep/src/shared/lib/host-theme.js +17 -0
- package/template/cep/src/shared/lib/markdown.js +109 -0
- package/template/cep/src/shared/lib/view.js +82 -0
- package/template/cep/src/shared/ui/base.css +60 -0
- package/template/cep/src/shared/ui/button.css +61 -0
- package/template/cep/src/shared/ui/card.css +22 -0
- package/template/cep/src/shared/ui/field.css +25 -0
- package/template/cep/src/shared/ui/index.css +7 -0
- package/template/cep/src/shared/ui/tokens.css +21 -0
- package/template/hosts/aeft/AGENTS.host.md +10 -0
- package/template/hosts/aeft/jsx/aeft/home.jsx +16 -0
- package/template/hosts/ppro/AGENTS.host.md +15 -0
- package/template/hosts/ppro/jsx/ppro/home.jsx +20 -0
- package/vendor/afterpresets-additions/AfterEffects.d.ts +291 -0
- package/vendor/afterpresets-additions/Premiere.d.ts +179 -0
- package/vendor/types-for-adobe/AfterEffects/26.0/index.d.ts +3403 -0
- package/vendor/types-for-adobe/LICENSE.md +21 -0
- package/vendor/types-for-adobe/Premiere/24.0/index.d.ts +2567 -0
- package/vendor/types-for-adobe/README.md +13 -0
- package/vendor/types-for-adobe/shared/JavaScript.d.ts +2761 -0
- package/vendor/types-for-adobe/shared/PlugPlugExternalObject.d.ts +91 -0
- package/vendor/types-for-adobe/shared/ScriptUI.d.ts +3012 -0
- package/vendor/types-for-adobe/shared/XMPScript.d.ts +72 -0
- package/vendor/types-for-adobe/shared/global.d.ts +152 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Licensing
|
|
3
|
+
description: Turn licensing on, handle every license status in renderLicense, choose offline grace, test with AP.__simulate, and follow the license screen standards.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Licensing
|
|
7
|
+
|
|
8
|
+
The runtime owns license logic: browser sign-in, the saved license, online
|
|
9
|
+
checks, device limits, and when your app may start. Your code only draws the
|
|
10
|
+
license screen.
|
|
11
|
+
|
|
12
|
+
## When to turn it on
|
|
13
|
+
|
|
14
|
+
- **Paid extension: required.** `ap check` and `ap push` fail when a paid
|
|
15
|
+
product has no panel that boots with `license` on.
|
|
16
|
+
- **Free extension: optional.** With licensing on, buyers must claim the free
|
|
17
|
+
product on AfterPresets and sign in. Leave it off unless the user
|
|
18
|
+
wants that.
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
license: false // default: no license screen, start runs at once
|
|
22
|
+
license: true // on, no offline grace
|
|
23
|
+
license: { offlineGraceDays: 7 } // on, works offline for up to 7 days
|
|
24
|
+
license: { checkInBackground: false } // on, always wait for the online check (see below)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Buyers sign in with their AfterPresets account: the extension opens the
|
|
28
|
+
browser, the buyer clicks **Sign in** on afterpresets.com, and the extension
|
|
29
|
+
unlocks. The extension receives a license token that only works for
|
|
30
|
+
this extension on this computer; it never sees the buyer's account
|
|
31
|
+
credentials. Each panel of an extension shares one license: the buyer signs in
|
|
32
|
+
once.
|
|
33
|
+
|
|
34
|
+
## When the license is checked
|
|
35
|
+
|
|
36
|
+
After a successful online check the API returns a signed record of it, valid
|
|
37
|
+
for 12 hours, and the runtime saves it with the token. Until it runs out,
|
|
38
|
+
launching the app, reloading a panel (including `ap dev` reloads) or
|
|
39
|
+
opening another panel unlocks straight away, with no network call and no
|
|
40
|
+
`checking` screen. After 12 hours the license is checked online again, without blocking the
|
|
41
|
+
buyer:
|
|
42
|
+
|
|
43
|
+
- **A panel that is already open** re-checks in the background. It stays
|
|
44
|
+
unlocked while offline and locks only if the server rejects the license (for
|
|
45
|
+
example a refund).
|
|
46
|
+
- **The next launch**, with `checkInBackground` on (the default), opens the
|
|
47
|
+
app straight away and checks online behind it. If the server rejects the
|
|
48
|
+
license, the license screen appears over the app with the matching status
|
|
49
|
+
(`signed_out`, `refunded`, `not_owned`). If the buyer is offline, the normal
|
|
50
|
+
offline grace rules apply: `offline_grace` within grace, otherwise
|
|
51
|
+
`offline_expired`. Blocking the network therefore never keeps an extension
|
|
52
|
+
unlocked.
|
|
53
|
+
- With `checkInBackground: false` the launch shows `checking` until the API
|
|
54
|
+
answers. Use it only if the app must not run at all before the check.
|
|
55
|
+
|
|
56
|
+
Only a record signed for this extension, computer and saved sign-in counts.
|
|
57
|
+
With no saved license, or one that is not signed (for example copied from
|
|
58
|
+
another computer), the launch always shows `checking`. **Try again**
|
|
59
|
+
(`api.retry()`) also always shows `checking`. The record is signed by
|
|
60
|
+
AfterPresets, so editing the saved file does not extend it.
|
|
61
|
+
|
|
62
|
+
## Offline grace
|
|
63
|
+
|
|
64
|
+
`offlineGraceDays` is a whole number from 0 to 30. The default is **0**.
|
|
65
|
+
Values outside the range are clamped, with a console warning.
|
|
66
|
+
|
|
67
|
+
| Setting | Buyer launches the extension while offline |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `0` (default) | Status `offline_expired`. The app does not start until the license is checked online. |
|
|
70
|
+
| `1`-`30` | If the last signed online check was less than that many days ago, status `offline_grace`: the app starts and the screen should show a small warning. Otherwise `offline_expired`. |
|
|
71
|
+
|
|
72
|
+
Tradeoff: grace lets buyers work on planes and in studios with no internet, but
|
|
73
|
+
a refunded license keeps working offline for up to that many days.
|
|
74
|
+
Ask the user; 7 days is a common choice. Never set it without telling them.
|
|
75
|
+
|
|
76
|
+
## The contract
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
AP.boot({
|
|
80
|
+
start: function (ctx) { /* your app */ },
|
|
81
|
+
license: true,
|
|
82
|
+
renderLicense: function (api, root) {
|
|
83
|
+
// Clear root, then draw api.state.status into it.
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- `renderLicense(api, root)` is called at boot and again after **every** state
|
|
89
|
+
change. Treat it as a pure render: empty `root`, then draw `api.state`.
|
|
90
|
+
- `root` is `<div id="ap-license-root">`, appended to `<body>` by the runtime.
|
|
91
|
+
The runtime only sets `root.style.display` (`"none"` while `active`, `""`
|
|
92
|
+
otherwise) and the attribute `data-ap-status="<status>"`. It has no classes and
|
|
93
|
+
no styles. **Your CSS positions it**: usually a full-panel overlay, and a thin
|
|
94
|
+
bar for `offline_grace`:
|
|
95
|
+
|
|
96
|
+
```css
|
|
97
|
+
#ap-license-root { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1000; background: #1d1d1d; }
|
|
98
|
+
#ap-license-root[data-ap-status="offline_grace"] { bottom: auto; }
|
|
99
|
+
```
|
|
100
|
+
- Do not move, remove, or set `display` on `root`. Draw only inside it.
|
|
101
|
+
- If `renderLicense` throws, the runtime logs the error and keeps going.
|
|
102
|
+
|
|
103
|
+
### api
|
|
104
|
+
|
|
105
|
+
`api` is the same object as `AP.license` and `ctx.license`.
|
|
106
|
+
|
|
107
|
+
| Member | Use |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `api.state` | `{ status, message, graceDaysLeft, productUrl, manageDevicesUrl, signIn, account }` |
|
|
110
|
+
| `api.signIn(cb?)` | Start a browser sign-in. The runtime opens the sign-in page and waits for approval. `cb(state)` once it started. |
|
|
111
|
+
| `api.openSignInPage()` | Open the sign-in page again (while `signing_in`). |
|
|
112
|
+
| `api.cancelSignIn()` | Stop waiting. Status becomes `signed_out`. |
|
|
113
|
+
| `api.retry(cb?)` | Check the saved license online again. |
|
|
114
|
+
| `api.signOut(cb?)` | Sign this computer out (all panels) and free its device slot. Status becomes `signed_out`. |
|
|
115
|
+
| `api.openUrl(url)` | Open an `http(s)` URL in the default browser. |
|
|
116
|
+
| `api.onChange(fn)` | `fn(state)` after each change. Returns an unsubscribe function. |
|
|
117
|
+
|
|
118
|
+
`state.message` is a ready-to-show sentence (sometimes from the server).
|
|
119
|
+
`state.productUrl` is the product page where buyers get the extension.
|
|
120
|
+
`state.manageDevicesUrl` is where buyers remove old computers.
|
|
121
|
+
`state.signIn` is `{ url }` (the sign-in page) while `signing_in`, otherwise
|
|
122
|
+
`null`. There is no code to show: the buyer only approves in the browser.
|
|
123
|
+
`state.account` is `{ username }` once signed in, otherwise `null`.
|
|
124
|
+
|
|
125
|
+
## Every status
|
|
126
|
+
|
|
127
|
+
`start` runs only for `active` and `offline_grace`. Every other status blocks the
|
|
128
|
+
app, so the screen must explain what to do.
|
|
129
|
+
|
|
130
|
+
| Status | What the screen shows | Controls and the api call |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `disabled` | Nothing. Licensing is off; `renderLicense` is never called. | None |
|
|
133
|
+
| `signed_out` | Ask the buyer to sign in (`state.message` says why after a sign-out) | **Sign in with AfterPresets** → `api.signIn()`; **Get it** → `api.openUrl(state.productUrl)` |
|
|
134
|
+
| `signing_in` | "Finish signing in in your browser" (`state.message`) | **Open browser again** → `api.openSignInPage()`; **Cancel** → `api.cancelSignIn()` |
|
|
135
|
+
| `checking` | Progress text (`state.message`) | Disable buttons |
|
|
136
|
+
| `active` | Nothing (root is hidden). Clear `root`. | None |
|
|
137
|
+
| `not_owned` | `state.message`: this account does not own the extension | **Get it** → `api.openUrl(state.productUrl)`; **Try again** → `api.retry()`; **Use a different account** → `api.signOut()` |
|
|
138
|
+
| `refunded` | `state.message`: the purchase was refunded | **Get it**; **Use a different account** → `api.signOut()` |
|
|
139
|
+
| `offline_grace` | One short non-blocking line with `state.graceDaysLeft` (the app is running) | **Retry** → `api.retry()` |
|
|
140
|
+
| `offline_expired` | Ask the buyer to connect to the internet | **Try again** → `api.retry()`; **Use a different account** → `api.signOut()` |
|
|
141
|
+
| `error` | `state.message` | **Try again** → `api.retry()`; **Sign in** → `api.signIn()` |
|
|
142
|
+
|
|
143
|
+
Too many computers is handled on the website during sign-in: the buyer removes
|
|
144
|
+
an old computer under **Manage devices** there, then confirms again. Show
|
|
145
|
+
**Manage devices** → `api.openUrl(state.manageDevicesUrl)` on the signed-in
|
|
146
|
+
screens of your app (for example in settings) so buyers can find it.
|
|
147
|
+
|
|
148
|
+
The labels **Sign in with AfterPresets** and **Get it** are required wording.
|
|
149
|
+
`ap check` warns when your license screen code does not mention a status.
|
|
150
|
+
|
|
151
|
+
## Example renderer
|
|
152
|
+
|
|
153
|
+
Keep the markup in HTML, one block per status, and let the JS pick the block
|
|
154
|
+
and fill in text. This is how the starter's `src/features/license-screen/`
|
|
155
|
+
works (it uses the small helpers in `src/shared/lib/view.js`).
|
|
156
|
+
|
|
157
|
+
```html
|
|
158
|
+
<!-- src/features/license-screen/license-screen.html (shortened) -->
|
|
159
|
+
<div data-state="signed_out">
|
|
160
|
+
<h1 id="license-title-signed_out">Sign in to unlock</h1>
|
|
161
|
+
<p data-field="message"></p>
|
|
162
|
+
<button type="button" data-action="signIn">Sign in with AfterPresets</button>
|
|
163
|
+
<button type="button" data-action="getIt">Get it</button>
|
|
164
|
+
</div>
|
|
165
|
+
<div data-state="signing_in">
|
|
166
|
+
<h1 id="license-title-signing_in">Continue in your browser</h1>
|
|
167
|
+
<p data-field="message"></p>
|
|
168
|
+
<button type="button" data-action="openSignInPage">Open browser again</button>
|
|
169
|
+
<button type="button" data-action="cancelSignIn">Cancel</button>
|
|
170
|
+
</div>
|
|
171
|
+
<!-- ...one block for checking, not_owned, refunded, offline_expired, error,
|
|
172
|
+
and a small banner for offline_grace -->
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
// src/features/license-screen/license-screen.js (shortened)
|
|
177
|
+
import markup from "./license-screen.html";
|
|
178
|
+
import { mount, isMounted, showState, fill, onAction } from "../../shared/lib/view.js";
|
|
179
|
+
|
|
180
|
+
export function renderLicense(api, root) {
|
|
181
|
+
if (!isMounted(root)) {
|
|
182
|
+
mount(root, markup); // the project's own HTML file, never outside data
|
|
183
|
+
onAction(root, {
|
|
184
|
+
signIn: function () { api.signIn(); },
|
|
185
|
+
openSignInPage: function () { api.openSignInPage(); },
|
|
186
|
+
cancelSignIn: function () { api.cancelSignIn(); },
|
|
187
|
+
retry: function () { api.retry(); },
|
|
188
|
+
signOut: function () { api.signOut(); },
|
|
189
|
+
getIt: function () { api.openUrl(api.state.productUrl); }
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
var s = api.state;
|
|
193
|
+
showState(root, s.status); // "active" matches no block: nothing shows
|
|
194
|
+
fill(root, { message: s.message }); // textContent
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Hard rules
|
|
199
|
+
|
|
200
|
+
1. Never call `start` yourself and never show app UI from `renderLicense`.
|
|
201
|
+
2. Never read, log, or send the saved license file or token. The runtime keeps
|
|
202
|
+
the only copy and sends it only to AfterPresets.
|
|
203
|
+
3. Never ask the buyer for a password or key. Sign-in always happens in the
|
|
204
|
+
browser through `api.signIn()`.
|
|
205
|
+
4. Never call the AfterPresets API directly for licensing. Use `api.*`.
|
|
206
|
+
5. Open links only with `api.openUrl(url)`, using the URLs in `api.state`. Do
|
|
207
|
+
not hard-code them.
|
|
208
|
+
6. Handle every status in the table.
|
|
209
|
+
7. Do not style or hide `#ap-license-root` so the app is usable while blocked.
|
|
210
|
+
8. Insert text with `textContent` (`fill`), never `innerHTML`: `state.message`
|
|
211
|
+
can come from the server. `innerHTML` is only for your own `.html` files.
|
|
212
|
+
|
|
213
|
+
## UI standards
|
|
214
|
+
|
|
215
|
+
**Accessibility**
|
|
216
|
+
|
|
217
|
+
- Use real `<button>` elements.
|
|
218
|
+
- Put `state.message` in an element with `role="status"` (`role="alert"` for
|
|
219
|
+
error statuses) so screen readers announce changes.
|
|
220
|
+
- Give the blocking screen a heading. Move focus to the main button.
|
|
221
|
+
- While `signing_in`, say plainly that the next step happens in the browser,
|
|
222
|
+
and keep **Open browser again** visible in case the tab was closed.
|
|
223
|
+
- Keep a visible focus outline and text contrast of at least 4.5:1.
|
|
224
|
+
- Fit a 300 px wide panel with no horizontal scroll.
|
|
225
|
+
|
|
226
|
+
**CEP browser limits** (Chromium 74 in CEP 10)
|
|
227
|
+
|
|
228
|
+
- No CSS `gap` on flex layouts (it is ignored). Use margins:
|
|
229
|
+
`.row > * + * { margin-left: 8px; }`. Grid `gap` is fine.
|
|
230
|
+
- No `:has()`, `:is()`, `:where()`, `aspect-ratio`, `inset`, container queries,
|
|
231
|
+
or `?.` / `??` in JavaScript.
|
|
232
|
+
- Links: `<a href>` does not open the browser in CEP. Use buttons that call
|
|
233
|
+
`api.openUrl(url)`.
|
|
234
|
+
|
|
235
|
+
**Style**
|
|
236
|
+
|
|
237
|
+
- Match the rest of the extension. Prefix classes with your own namespace
|
|
238
|
+
(for example `license__`); the runtime uses none.
|
|
239
|
+
|
|
240
|
+
## Testing with AP.__simulate
|
|
241
|
+
|
|
242
|
+
`AP.__simulate` works only in dev builds (`ap dev`) and only when licensing is
|
|
243
|
+
on. It drives `renderLicense` through a status without touching the saved
|
|
244
|
+
license and never calls `start`. While it is on, real license results do not
|
|
245
|
+
change the screen. Run it in the live panel's console or through the
|
|
246
|
+
debugging connection.
|
|
247
|
+
|
|
248
|
+
```js
|
|
249
|
+
AP.__simulate(); // lists every status
|
|
250
|
+
AP.__simulate("signing_in"); // shows code ABCD-EFGH
|
|
251
|
+
AP.__simulate("offline_grace", { graceDaysLeft: 1 });
|
|
252
|
+
AP.__simulate("error", { message: "Custom message" });
|
|
253
|
+
AP.__simulate(false); // end; validate for real
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Before you finish, simulate every status, screenshot each one, and check that:
|
|
257
|
+
|
|
258
|
+
- the required controls are present and the links open the right pages;
|
|
259
|
+
- `offline_grace` leaves the app usable;
|
|
260
|
+
- every other status except `active` covers the app;
|
|
261
|
+
- `ap check` reports no missing statuses.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Panels and events
|
|
3
|
+
description: Build extensions with several panels, modals, or invisible panels; open and close them with AP.panels; send data between them with AP.events.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Panels and events
|
|
7
|
+
|
|
8
|
+
One extension can have several windows. Each is a **panel** with its own id,
|
|
9
|
+
HTML, and JavaScript. They share `js/`, `css/`, `jsx/`, and the license.
|
|
10
|
+
|
|
11
|
+
## Panel types
|
|
12
|
+
|
|
13
|
+
| `type` | What it is | Use it for |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `panel` | A dockable panel, listed in **Window → Extensions** when `menu` is true | The main UI |
|
|
16
|
+
| `modal` | A dialog that blocks the app until closed | Settings, confirmations, one-off forms |
|
|
17
|
+
| `invisible` | No window; starts with the app and runs in the background | Listening for events, background work |
|
|
18
|
+
|
|
19
|
+
Panels are declared in `afterpresets.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
"panels": [
|
|
23
|
+
{ "id": "main", "title": "Tool", "type": "panel", "width": 400, "height": 580, "menu": true },
|
|
24
|
+
{ "id": "settings", "title": "Tool Settings", "type": "modal", "width": 360, "height": 300 }
|
|
25
|
+
]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Adding a panel
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
ap add panel settings --type modal
|
|
32
|
+
ap add panel worker --type invisible
|
|
33
|
+
ap add panel inspector --title "Inspector" --width 320 --height 480
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This creates `src/panels/<id>/index.html` and `src/panels/<id>/main.js`, and
|
|
37
|
+
adds the entry to `afterpresets.json`. Each panel calls `AP.boot` in its own
|
|
38
|
+
`main.js` and imports the screens and helpers it uses from `src/features/`
|
|
39
|
+
and `src/shared/`. After adding a panel, restart
|
|
40
|
+
`ap dev` and then the app: CEP reads the manifest only at launch.
|
|
41
|
+
|
|
42
|
+
## AP.panels
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
AP.panels.current(); // { id: "main" } - also ctx.panel
|
|
46
|
+
AP.panels.open("settings"); // open another panel of this extension
|
|
47
|
+
AP.panels.close(); // close this panel
|
|
48
|
+
AP.panels.close("settings"); // ask another open panel to close itself
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- Ids are the ones in `afterpresets.json`. An invalid id throws.
|
|
52
|
+
- `open` asks the app to open the extension `<bundleId>.<id>`. It returns
|
|
53
|
+
`true` when the request was sent; it does not wait for the panel to load.
|
|
54
|
+
- Data cannot be passed to `open`. Send it with `AP.events` once the other
|
|
55
|
+
panel is ready (see "Handshake" below).
|
|
56
|
+
|
|
57
|
+
## AP.events
|
|
58
|
+
|
|
59
|
+
`AP.events` sends JSON messages between the open panels of this extension.
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
var off = AP.events.on("preset:selected", function (data, meta) {
|
|
63
|
+
// data: the JSON value that was sent. meta.from: the sender's panel id.
|
|
64
|
+
showPreset(data.name);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
AP.events.emit("preset:selected", { name: "Bounce" }); // returns true if sent
|
|
68
|
+
|
|
69
|
+
off(); // stop listening
|
|
70
|
+
AP.events.off("preset:selected", handler); // same, by function
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- Delivered to every **other** open panel that listens to that name. The
|
|
74
|
+
sending panel does not receive its own event.
|
|
75
|
+
- `data` must be JSON-serialisable (objects, arrays, strings, numbers,
|
|
76
|
+
booleans, `null`). Delivery is asynchronous.
|
|
77
|
+
- Names use letters, digits, and `_ . : -` (up to 100 characters). Names
|
|
78
|
+
starting with `__ap.` are reserved and throw.
|
|
79
|
+
- A panel that is not open receives nothing; events are not queued.
|
|
80
|
+
|
|
81
|
+
### Handshake: send data to a panel you just opened
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
// src/panels/main/main.js
|
|
85
|
+
AP.events.on("settings:ready", function () {
|
|
86
|
+
AP.events.emit("settings:load", { fps: 24 });
|
|
87
|
+
});
|
|
88
|
+
AP.panels.open("settings");
|
|
89
|
+
|
|
90
|
+
// src/panels/settings/main.js (inside start)
|
|
91
|
+
AP.events.on("settings:load", function (data) { renderForm(data); });
|
|
92
|
+
AP.events.emit("settings:ready", null);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Shared license
|
|
96
|
+
|
|
97
|
+
All panels of an extension share one saved license. The buyer activates once.
|
|
98
|
+
|
|
99
|
+
- When a panel opens, it validates the saved key; it does not ask again.
|
|
100
|
+
- When the buyer activates or signs out in one panel, every other open panel
|
|
101
|
+
updates itself automatically.
|
|
102
|
+
- Every panel that boots with `license` on needs its own `renderLicense`. A
|
|
103
|
+
small modal can reuse the main panel's license screen code from `js/`.
|
|
104
|
+
- An `invisible` panel cannot show a license screen. Boot it with licensing
|
|
105
|
+
on and a `renderLicense` that draws nothing; its `start` runs once the buyer
|
|
106
|
+
has activated in a visible panel.
|
|
107
|
+
|
|
108
|
+
## Updates with several panels
|
|
109
|
+
|
|
110
|
+
Every panel with `updates.check: "auto"` checks for updates. Only one install
|
|
111
|
+
runs at a time, and `onInstalled` fires in every open panel. `AP.updates.reload()`
|
|
112
|
+
reloads every open panel of the extension, so one **Reload now** click starts
|
|
113
|
+
the new version everywhere. To keep it simple, show the update screen in the
|
|
114
|
+
main panel and use `updates: { check: "manual" }` in the others.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Saving data
|
|
3
|
+
description: Where an extension keeps settings and other local state with AP.storage and AP.paths.data(), what goes inside the app's project file instead, and what never to store.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Saving data
|
|
7
|
+
|
|
8
|
+
## Where each kind of data goes
|
|
9
|
+
|
|
10
|
+
| Data | Where |
|
|
11
|
+
|---|---|
|
|
12
|
+
| Settings, preferences, recent items, small caches | `AP.storage` (below) |
|
|
13
|
+
| Bigger files you create or download (images, media, generated presets) | A file in `AP.paths.data()` |
|
|
14
|
+
| Data that belongs to one After Effects or Premiere Pro project | Inside the `.aep` / `.prproj`, from `jsx/` (it travels with the project file) |
|
|
15
|
+
| Secrets (passwords, API keys, tokens) | Nowhere. Everything above is a plain file the user can open. |
|
|
16
|
+
|
|
17
|
+
Do **not** use `localStorage`, `sessionStorage` or IndexedDB: in CEP each panel
|
|
18
|
+
gets its own copy (so panels do not share it), and it lives in a cache folder
|
|
19
|
+
that can be cleared. Never write into the extension folder
|
|
20
|
+
(`AP.paths.extension()`): every update replaces it.
|
|
21
|
+
|
|
22
|
+
## AP.storage
|
|
23
|
+
|
|
24
|
+
Small JSON values, kept on this computer across launches and updates, shared
|
|
25
|
+
by every panel of this extension.
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
var settings = AP.storage.get("settings", { fps: 24, autoApply: false }); // fallback until saved
|
|
29
|
+
settings.fps = 30;
|
|
30
|
+
AP.storage.set("settings", settings); // true when saved
|
|
31
|
+
|
|
32
|
+
AP.storage.remove("settings"); // or AP.storage.set("settings", undefined)
|
|
33
|
+
AP.storage.keys(); // ["recent", "settings"]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Call | Does |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `get(key, fallback)` | The saved value, or `fallback` when nothing is saved (or the file is unreadable). |
|
|
39
|
+
| `set(key, value)` | Saves any JSON value (objects, arrays, strings, numbers, booleans, `null`). Returns `true`, or `false` if the disk write failed. |
|
|
40
|
+
| `remove(key)` | Deletes the value. |
|
|
41
|
+
| `keys()` | Saved keys, sorted. |
|
|
42
|
+
| `onChange(key, fn)` | `fn(value)` runs when **another** open panel of this extension sets or removes `key` (`value` is `undefined` after a remove). Returns a function that stops listening. |
|
|
43
|
+
|
|
44
|
+
- Keys use letters, digits, `_`, `.` and `-` (up to 100 characters, not
|
|
45
|
+
starting with `.` or `-`). An invalid key throws.
|
|
46
|
+
- A value is limited to 1 MB; functions, cyclic objects and other values JSON
|
|
47
|
+
cannot hold throw. Store bigger data as a file in `AP.paths.data()`.
|
|
48
|
+
- Each key is its own file, written atomically, so a crash never leaves a
|
|
49
|
+
half-saved value and two panels saving different keys never overwrite each
|
|
50
|
+
other. If two panels save the same key, the last one wins.
|
|
51
|
+
- `get` returns a fresh copy from disk: change it, then `set` it again.
|
|
52
|
+
|
|
53
|
+
Keep a panel in sync when the settings modal saves:
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
// src/panels/main/main.js
|
|
57
|
+
AP.storage.onChange("settings", function (settings) {
|
|
58
|
+
applySettings(settings || DEFAULTS);
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Files: AP.paths.data()
|
|
63
|
+
|
|
64
|
+
`AP.paths.data()` returns a folder that exists, belongs to this extension, and
|
|
65
|
+
survives updates (`.../AfterPresets/extensions/<bundleId>/app/`). Use Node's
|
|
66
|
+
`fs` and `path` there for anything that is not a small JSON value:
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
var fs = require("fs");
|
|
70
|
+
var path = require("path");
|
|
71
|
+
var file = path.join(AP.paths.data(), "thumbnails", "intro.png");
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Pass such paths to ExtendScript as arguments when the app needs them
|
|
75
|
+
(see [assets.md](assets.md)).
|
|
76
|
+
|
|
77
|
+
## Inside the project file
|
|
78
|
+
|
|
79
|
+
Data about one project (which presets it uses, a layout you applied) belongs in
|
|
80
|
+
the project, not on the computer: it should follow the `.aep` or `.prproj` to another
|
|
81
|
+
machine. Read and write it from a `jsx/` function, for example in a project
|
|
82
|
+
XMP field or on a layer or comp comment, and call it with `ctx.host.eval`.
|