studio-engine 0.97.0.rc1 → 0.98.0.rc1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +89 -0
- data/app/javascript/studio/alpine_scopes.js +66 -0
- data/app/javascript/studio/alpine_shims.js +4 -0
- data/app/javascript/studio/application.js +4 -1
- data/app/javascript/studio/birthday.js +351 -0
- data/app/javascript/studio/confirm_interstitial.js +44 -0
- data/app/javascript/studio/controllers/identity_mini_controller.js +32 -0
- data/app/javascript/studio/controllers/style_modals_controller.js +41 -0
- data/app/javascript/studio/cropper.js +281 -0
- data/app/javascript/studio/identity_mini.js +74 -0
- data/app/javascript/studio/image_upload.js +189 -0
- data/app/javascript/studio/profile_form.js +88 -0
- data/app/javascript/studio/stimulus.js +4 -0
- data/app/javascript/studio/style_guide.js +13 -0
- data/app/javascript/studio/style_modals.js +214 -0
- data/app/views/components/_avatar_cropper.html.erb +3 -38
- data/app/views/layouts/studio/_head.html.erb +12 -2
- data/app/views/studio/_alpine_scopes_guard.html.erb +178 -0
- data/app/views/studio/_birthday_assets.html.erb +6 -240
- data/app/views/studio/_confirm_interstitial.html.erb +31 -18
- data/app/views/studio/_cropper_assets.html.erb +9 -8
- data/app/views/studio/modals/_crop_photo.html.erb +1 -1
- data/app/views/studio/modals/_image_upload.html.erb +8 -321
- data/app/views/studio/modals/blocks/_birthday.html.erb +2 -3
- data/app/views/studio/profiles/_birthday_fields.html.erb +0 -1
- data/app/views/studio/profiles/_identity_mini.html.erb +5 -62
- data/app/views/studio/profiles/_plain_save.html.erb +11 -0
- data/app/views/studio/profiles/edit.html.erb +1 -6
- data/app/views/style/_modals.html.erb +36 -420
- data/lib/studio/version.rb +1 -1
- metadata +14 -3
- data/app/views/studio/profiles/_birthday_picker_script.html.erb +0 -146
- data/app/views/studio/profiles/_form_script.html.erb +0 -65
|
@@ -151,6 +151,7 @@
|
|
|
151
151
|
to the app-supplied submit_url. Kept on the section (always mounted) so it
|
|
152
152
|
fires even as the age-gate card closes. %>
|
|
153
153
|
<section id="modals" class="space-y-10" style="scroll-margin-top: calc(var(--nav-h, 0px) + 5rem)"
|
|
154
|
+
data-studio-controller="style-modals"
|
|
154
155
|
x-data="{}" @ds-modal-close.window="$store.dsModals.close()"
|
|
155
156
|
@age-verified.window="$store.dsModals.open('entry-confirmed', { txSignature: null, lobbyUrl: null, seedsEarned: 0 })">
|
|
156
157
|
<div class="space-y-1">
|
|
@@ -166,199 +167,21 @@
|
|
|
166
167
|
</p>
|
|
167
168
|
</div>
|
|
168
169
|
|
|
169
|
-
<%# ----
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// Connect stub for the wallet picker (solana-studio
|
|
185
|
-
// solana_studio/modals/_wallet_connect, configured by
|
|
186
|
-
// style/modals/_wallet_connect). Performs NO connect: it
|
|
187
|
-
// resolves success after a beat so the picker's connecting state is
|
|
188
|
-
// visible and its onConnected hook can continue the demo walk.
|
|
189
|
-
window.dsWalletConnectDemo = window.dsWalletConnectDemo || function () {
|
|
190
|
-
return new Promise(function (resolve) {
|
|
191
|
-
setTimeout(function () { resolve({ success: true }); }, 1100);
|
|
192
|
-
});
|
|
193
|
-
};
|
|
194
|
-
window.walletProvider = window.walletProvider || {
|
|
195
|
-
available: function () { return [{ name: 'Phantom' }, { name: 'Solflare' }]; },
|
|
196
|
-
isMobile: function () { return false; },
|
|
197
|
-
isAvailable: function () { return false; }
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
// Register the page-scoped dsModals store. Named (not an
|
|
201
|
-
// inline alpine:init callback) so the dual-guard at the end can invoke it
|
|
202
|
-
// directly on a Turbo visit, when alpine:init will not fire again.
|
|
203
|
-
function registerDsModals() {
|
|
204
|
-
if (Alpine.store('dsModals')) return;
|
|
205
|
-
|
|
206
|
-
// Enter/exit animation registry — keys map to a CSS class (engine-
|
|
207
|
-
// motion.css) plus its duration in ms. Open a modal with
|
|
208
|
-
// { enterAnim: 'shake' } / { exitAnim: 'slide' } to pick one; omitted
|
|
209
|
-
// uses 'pop' (the spring bounce). The directional swap()/advance()
|
|
210
|
-
// slide uses the modal-card-swap-* classes regardless of these keys.
|
|
211
|
-
var CLOSE_ANIM_MS = 220;
|
|
212
|
-
var SWAP_IN_MS = 220;
|
|
213
|
-
|
|
214
|
-
// THE LIVE REGISTRY IS window.ModalAnimations, NOT THIS TABLE. This is a
|
|
215
|
-
// fallback only, for the case where studio/modal_host, which publishes
|
|
216
|
-
// the registry on every page, failed to load.
|
|
217
|
-
//
|
|
218
|
-
// It used to be a hard-coded local copy, and that made the Enter/Leave
|
|
219
|
-
// simulator below a LIAR: the simulator builds its controls from the live
|
|
220
|
-
// registry, so a newly registered animation grew a button here while the
|
|
221
|
-
// store silently resolved the unknown key back to 'pop'. The control said
|
|
222
|
-
// "shake", the card popped, and nothing reported the gap. Reading the
|
|
223
|
-
// registry at CALL time is what makes a registered animation actually
|
|
224
|
-
// playable on this page.
|
|
225
|
-
var ANIM_FALLBACK = {
|
|
226
|
-
enter: {
|
|
227
|
-
pop: { cls: 'modal-card-mount', ms: 320 },
|
|
228
|
-
shake: { cls: 'modal-card-shake-in', ms: 600 },
|
|
229
|
-
slide: { cls: 'modal-card-swap-in', ms: 220 }
|
|
230
|
-
},
|
|
231
|
-
exit: {
|
|
232
|
-
pop: { cls: 'modal-card-unmount', ms: 220 },
|
|
233
|
-
slide: { cls: 'modal-card-swap-out', ms: 220 }
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
// Same late-binding guard, and for the same reason, as the host's own
|
|
237
|
-
// modalAnim: never return undefined. A miss would throw on .ms in close()
|
|
238
|
-
// and strand the modal open, so an unknown key falls back to 'pop'.
|
|
239
|
-
function modalAnim(channel, key) {
|
|
240
|
-
var table = (window.ModalAnimations && window.ModalAnimations[channel]) ||
|
|
241
|
-
ANIM_FALLBACK[channel];
|
|
242
|
-
return table[key] || table.pop || ANIM_FALLBACK[channel].pop;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
Alpine.store('dsModals', {
|
|
246
|
-
stack: [],
|
|
247
|
-
current: function () { return this.stack.length ? this.stack[this.stack.length - 1] : null; },
|
|
248
|
-
isOpen: function (id) {
|
|
249
|
-
for (var i = 0; i < this.stack.length; i++) { if (this.stack[i].id === id) return true; }
|
|
250
|
-
return false;
|
|
251
|
-
},
|
|
252
|
-
// open(id, props, opts) — opts.replace swaps the top entry with a
|
|
253
|
-
// directional slide (forward = exit right / enter left); otherwise
|
|
254
|
-
// push (stacking — closing reveals the entry underneath).
|
|
255
|
-
open: function (id, props, opts) {
|
|
256
|
-
props = props || {}; opts = opts || {};
|
|
257
|
-
var self = this;
|
|
258
|
-
if (opts.replace && this.stack.length > 0) {
|
|
259
|
-
var current = this.current();
|
|
260
|
-
if (current && !current._closing) {
|
|
261
|
-
var dir = (opts.direction === 'back') ? 'back' : 'forward';
|
|
262
|
-
current._swapDir = dir;
|
|
263
|
-
current._swappingOut = true;
|
|
264
|
-
current._closing = true;
|
|
265
|
-
setTimeout(function () {
|
|
266
|
-
var idx = self.stack.indexOf(current);
|
|
267
|
-
var newEntry = { id: id, props: props, _swappingIn: true, _swapDir: dir };
|
|
268
|
-
if (idx >= 0) { self.stack[idx] = newEntry; } else { self.stack.push(newEntry); }
|
|
269
|
-
self._sync();
|
|
270
|
-
setTimeout(function () {
|
|
271
|
-
newEntry._swappingIn = false; newEntry._swapDir = null; newEntry._settled = true;
|
|
272
|
-
}, SWAP_IN_MS);
|
|
273
|
-
}, CLOSE_ANIM_MS);
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
this.stack[this.stack.length - 1] = { id: id, props: props };
|
|
277
|
-
} else {
|
|
278
|
-
this.stack.push({ id: id, props: props });
|
|
279
|
-
}
|
|
280
|
-
this._sync();
|
|
281
|
-
},
|
|
282
|
-
// swap(id, props, opts) ≡ open with { replace: true }.
|
|
283
|
-
swap: function (id, props, opts) {
|
|
284
|
-
return this.open(id, props, Object.assign({ replace: true }, opts || {}));
|
|
285
|
-
},
|
|
286
|
-
// advance(patch, opts) — in-flow step change: same directional slide as
|
|
287
|
-
// swap() but WITHOUT replacing the stack entry, so the modal's outer
|
|
288
|
-
// x-data scope survives (the auth step machine relies on this).
|
|
289
|
-
advance: function (propsPatch, opts) {
|
|
290
|
-
opts = opts || {};
|
|
291
|
-
var cur = this.current();
|
|
292
|
-
if (!cur || cur._closing || cur._swappingOut) return;
|
|
293
|
-
var self = this;
|
|
294
|
-
cur._swapDir = (opts.direction === 'back') ? 'back' : 'forward';
|
|
295
|
-
cur._swappingOut = true;
|
|
296
|
-
setTimeout(function () {
|
|
297
|
-
if (self.stack.indexOf(cur) < 0) return;
|
|
298
|
-
if (propsPatch) Object.assign(cur.props, propsPatch);
|
|
299
|
-
cur._swappingOut = false;
|
|
300
|
-
cur._swappingIn = true;
|
|
301
|
-
setTimeout(function () {
|
|
302
|
-
cur._swappingIn = false; cur._swapDir = null; cur._settled = true;
|
|
303
|
-
}, SWAP_IN_MS);
|
|
304
|
-
}, CLOSE_ANIM_MS);
|
|
305
|
-
},
|
|
306
|
-
close: function () {
|
|
307
|
-
var self = this;
|
|
308
|
-
var entry = this.current();
|
|
309
|
-
if (!entry || entry._closing) return;
|
|
310
|
-
entry._closing = true;
|
|
311
|
-
setTimeout(function () {
|
|
312
|
-
var idx = self.stack.indexOf(entry);
|
|
313
|
-
if (idx >= 0) { self.stack.splice(idx, 1); self._sync(); }
|
|
314
|
-
}, modalAnim('exit', entry.props && entry.props.exitAnim).ms);
|
|
315
|
-
},
|
|
316
|
-
closeAll: function () { this.stack = []; this._sync(); },
|
|
317
|
-
cardClasses: function () {
|
|
318
|
-
var c = this.current();
|
|
319
|
-
if (!c) return {};
|
|
320
|
-
var o = {};
|
|
321
|
-
if (!c._settled && !c._swappingIn && !c._swappingOut && !c._closing) {
|
|
322
|
-
o[modalAnim('enter', c.props && c.props.enterAnim).cls] = true;
|
|
323
|
-
}
|
|
324
|
-
if (c._closing && !c._swappingOut) {
|
|
325
|
-
o[modalAnim('exit', c.props && c.props.exitAnim).cls] = true;
|
|
326
|
-
}
|
|
327
|
-
o['modal-card-swap-in'] = !!(c._swappingIn && c._swapDir !== 'back');
|
|
328
|
-
o['modal-card-swap-out'] = !!(c._swappingOut && c._swapDir !== 'back');
|
|
329
|
-
o['modal-card-swap-in-back'] = !!(c._swappingIn && c._swapDir === 'back');
|
|
330
|
-
o['modal-card-swap-out-back'] = !!(c._swappingOut && c._swapDir === 'back');
|
|
331
|
-
return o;
|
|
332
|
-
},
|
|
333
|
-
_sync: function () {
|
|
334
|
-
if (this.stack.length) { document.body.classList.add('modal-open'); }
|
|
335
|
-
else { document.body.classList.remove('modal-open'); }
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
// A SECOND STORE USED TO BE REGISTERED HERE: dsSolanaModal, a stateless
|
|
340
|
-
// proxy that re-read the current 'onchain-tx' entry's props so
|
|
341
|
-
// style/modals/_onchain_tx could keep its
|
|
342
|
-
// $store.dsSolanaModal.{state,title,txSignature,…} reads. That partial
|
|
343
|
-
// was a mirror of turf-monster's card and was retired on 2026-09-09,
|
|
344
|
-
// leaving the proxy with no reader, so it went with it. A store with no
|
|
345
|
-
// reader still registers, still looks load-bearing, and still has to be
|
|
346
|
-
// reasoned about by everyone who reads this file.
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Alpine's deferred CDN <script> fires alpine:init exactly ONCE, on the
|
|
350
|
-
// first full-document load. Turbo Drive advance visits swap the document
|
|
351
|
-
// body without reloading that head script, so alpine:init never fires
|
|
352
|
-
// again — and
|
|
353
|
-
// because this dsModals registration lives ONLY in the /admin/style body,
|
|
354
|
-
// it was absent during that one alpine:init. Registering here on the
|
|
355
|
-
// Turbo-visit path (Alpine already started) keeps $store.dsModals defined,
|
|
356
|
-
// so the specimen cards' @click / :style / <template x-if> don't throw.
|
|
357
|
-
// Mirrors studio/modals/_image_upload's cropPhotoModal guard.
|
|
358
|
-
if (window.Alpine) { registerDsModals(); }
|
|
359
|
-
else { document.addEventListener('alpine:init', registerDsModals); }
|
|
360
|
-
})();
|
|
361
|
-
</script>
|
|
170
|
+
<%# ---- The section's own modal host (dsModals) --------------------------
|
|
171
|
+
$store.dsModals is the engine's modal stack under the guide's own name:
|
|
172
|
+
the overlay below declares itself a modal host, and studio/modal_host
|
|
173
|
+
registers the store from that declaration before Alpine reads it, on a
|
|
174
|
+
full load and on a Turbo visit alike. So the store the specimens open on
|
|
175
|
+
is the store an app runs: open / swap / advance / close / closeAll,
|
|
176
|
+
current(), cardClasses() reading the live animation registry, and the
|
|
177
|
+
body.modal-open scroll lock.
|
|
178
|
+
|
|
179
|
+
The demo drivers, the enter/leave simulator and the walletProvider stub
|
|
180
|
+
solana-studio's picker needs are studio/style_modals, bound to the
|
|
181
|
+
section by the style-modals controller. studio/style_guide registers that
|
|
182
|
+
controller; the tag below is the only page that loads it. %>
|
|
183
|
+
<%= tag.script('import "studio/style_guide"'.html_safe, type: "module",
|
|
184
|
+
nonce: (content_security_policy_nonce if respond_to?(:content_security_policy_nonce))) %>
|
|
362
185
|
|
|
363
186
|
<%# The minimum-visible-duration convention (window.StudioModals.holdAtLeast +
|
|
364
187
|
MIN_LOAD_MS) is studio/modal_host's, published on every page, so the demo
|
|
@@ -366,34 +189,27 @@
|
|
|
366
189
|
resolveAt = max(min_duration, actual) and auto-resolve after min_duration
|
|
367
190
|
in demo mode. %>
|
|
368
191
|
|
|
369
|
-
<%# Cropper.js
|
|
370
|
-
|
|
371
|
-
|
|
192
|
+
<%# Cropper.js, so the Profile crop and upload modals open live on this page.
|
|
193
|
+
The cropPhotoModal, birthdayModal and levelingActionModal factories the
|
|
194
|
+
specimens mount are on every page (studio/alpine_scopes, studio/alpine_shims). %>
|
|
372
195
|
<%= render "studio/cropper_assets" %>
|
|
373
196
|
|
|
374
|
-
<%# birthdayModal factory — rendered here at page level (never inside the
|
|
375
|
-
overlay template, where a cloned script would not run) so the age-gate
|
|
376
|
-
specimen opens live. A consumer app renders this in its layout the same way. %>
|
|
377
|
-
<%= render "studio/birthday_assets" %>
|
|
378
|
-
|
|
379
|
-
<%# levelingActionModal factory — page level (never inside the overlay template,
|
|
380
|
-
where a cloned script would not run) so the change-username + join-newsletter
|
|
381
|
-
specimens open live. A consumer app renders this in its layout the same way. %>
|
|
382
|
-
<%= render "studio/leveling_activity_assets" %>
|
|
383
|
-
|
|
384
197
|
<%# ---- The overlay: one backdrop + card; every specimen's real content is a
|
|
385
198
|
single-root <template x-if> gated on the current modal id. Faithful to the
|
|
386
199
|
shared host — backdrop fade, scroll lock, escape + click-self dismissal
|
|
387
200
|
gated on props.dismissible (never @click.outside, so a hold modal can't
|
|
388
201
|
close itself on the button release). %>
|
|
389
|
-
<template x-if="$store.dsModals.current()"
|
|
202
|
+
<template x-if="$store.dsModals.current()"
|
|
203
|
+
data-studio-controller="modal-host"
|
|
204
|
+
data-modal-host-store-value="dsModals">
|
|
390
205
|
<div class="fixed inset-0 z-[var(--z-modal)] flex items-center justify-center p-4 modal-backdrop-mount"
|
|
391
206
|
:class="$store.dsModals.current()?._closing && 'modal-backdrop-unmount'"
|
|
392
207
|
style="background: rgba(0,0,0,0.6)"
|
|
393
208
|
role="dialog" aria-modal="true"
|
|
394
209
|
@keydown.escape.window="$store.dsModals.current() && $store.dsModals.current().props.dismissible !== false && $store.dsModals.close()"
|
|
395
210
|
@click.self="$store.dsModals.current() && $store.dsModals.current().props.dismissible !== false && $store.dsModals.close()">
|
|
396
|
-
|
|
211
|
+
<%# No max-w-* here: cardClasses() names exactly one, by modal id. %>
|
|
212
|
+
<div class="bg-surface rounded-xl border border-subtle shadow-2xl p-6 w-full"
|
|
397
213
|
:class="$store.dsModals.cardClasses()">
|
|
398
214
|
|
|
399
215
|
<%# --- Auth fragments --- %>
|
|
@@ -1565,9 +1381,9 @@
|
|
|
1565
1381
|
class="block mt-1 text-sm bg-inset border border-subtle rounded px-2 py-1 text-heading"></select>
|
|
1566
1382
|
</label>
|
|
1567
1383
|
<button type="button" class="btn btn-primary btn-sm"
|
|
1568
|
-
|
|
1384
|
+
data-studio-action="click->style-modals#demo" data-style-modals-name-param="animOpenSelected">Open</button>
|
|
1569
1385
|
<button type="button" class="btn btn-outline btn-sm"
|
|
1570
|
-
|
|
1386
|
+
data-studio-action="click->style-modals#demo" data-style-modals-name-param="animRoundTripSelected">Open, then auto-close</button>
|
|
1571
1387
|
<button type="button" class="btn btn-outline btn-sm"
|
|
1572
1388
|
@click="$store.dsModals.close()">Close</button>
|
|
1573
1389
|
</div>
|
|
@@ -1612,15 +1428,15 @@
|
|
|
1612
1428
|
</p>
|
|
1613
1429
|
<div class="flex flex-wrap gap-2">
|
|
1614
1430
|
<button type="button" class="btn btn-outline btn-sm"
|
|
1615
|
-
|
|
1431
|
+
data-studio-action="click->style-modals#demo" data-style-modals-name-param="processing">Processing</button>
|
|
1616
1432
|
<button type="button" class="btn btn-outline btn-sm"
|
|
1617
|
-
|
|
1433
|
+
data-studio-action="click->style-modals#demo" data-style-modals-name-param="success">Success</button>
|
|
1618
1434
|
<button type="button" class="btn btn-outline btn-sm"
|
|
1619
|
-
|
|
1435
|
+
data-studio-action="click->style-modals#demo" data-style-modals-name-param="error">Error</button>
|
|
1620
1436
|
<button type="button" class="btn btn-primary btn-sm"
|
|
1621
|
-
|
|
1437
|
+
data-studio-action="click->style-modals#demo" data-style-modals-name-param="processThenSuccess">Processing to success (3s)</button>
|
|
1622
1438
|
<button type="button" class="btn btn-primary btn-sm"
|
|
1623
|
-
|
|
1439
|
+
data-studio-action="click->style-modals#demo" data-style-modals-name-param="processThenError">Processing to error (3s)</button>
|
|
1624
1440
|
</div>
|
|
1625
1441
|
</div>
|
|
1626
1442
|
|
|
@@ -1634,9 +1450,9 @@
|
|
|
1634
1450
|
</p>
|
|
1635
1451
|
<div class="flex flex-wrap gap-2">
|
|
1636
1452
|
<button type="button" class="btn btn-outline btn-sm"
|
|
1637
|
-
|
|
1453
|
+
data-studio-action="click->style-modals#demo" data-style-modals-name-param="processing">Try Escape (locked)</button>
|
|
1638
1454
|
<button type="button" class="btn btn-outline btn-sm"
|
|
1639
|
-
|
|
1455
|
+
data-studio-action="click->style-modals#demo" data-style-modals-name-param="dismissible">Dismissible processing</button>
|
|
1640
1456
|
</div>
|
|
1641
1457
|
</div>
|
|
1642
1458
|
|
|
@@ -1649,9 +1465,9 @@
|
|
|
1649
1465
|
</p>
|
|
1650
1466
|
<div class="flex flex-wrap gap-2">
|
|
1651
1467
|
<button type="button" class="btn btn-primary btn-sm"
|
|
1652
|
-
|
|
1468
|
+
data-studio-action="click->style-modals#demo" data-style-modals-name-param="fastWithHold">Fast op, hold 1500ms</button>
|
|
1653
1469
|
<button type="button" class="btn btn-outline btn-sm"
|
|
1654
|
-
|
|
1470
|
+
data-studio-action="click->style-modals#demo" data-style-modals-name-param="fastNoHold">Fast op, no hold (flashes)</button>
|
|
1655
1471
|
</div>
|
|
1656
1472
|
</div>
|
|
1657
1473
|
|
|
@@ -1664,7 +1480,7 @@
|
|
|
1664
1480
|
</p>
|
|
1665
1481
|
<div class="flex flex-wrap gap-2">
|
|
1666
1482
|
<button type="button" class="btn btn-primary btn-sm"
|
|
1667
|
-
|
|
1483
|
+
data-studio-action="click->style-modals#demo" data-style-modals-name-param="stackTwo">Open two</button>
|
|
1668
1484
|
<button type="button" class="btn btn-outline btn-sm"
|
|
1669
1485
|
@click="$store.dsModals.closeAll()">closeAll()</button>
|
|
1670
1486
|
</div>
|
|
@@ -1672,204 +1488,4 @@
|
|
|
1672
1488
|
</div>
|
|
1673
1489
|
</section>
|
|
1674
1490
|
|
|
1675
|
-
<%# ---- Demo drivers for the two sections above -------------------------
|
|
1676
|
-
Deliberately NOT an Alpine component: these are multi-step sequences with
|
|
1677
|
-
timers, and an x-data scope would be cloned per specimen card. One global
|
|
1678
|
-
namespace, defined after the markup it drives so the control containers
|
|
1679
|
-
exist when the registry build runs.
|
|
1680
|
-
|
|
1681
|
-
THE STORE IS dsModals. turf-monster's version of this page drove roughly
|
|
1682
|
-
half of these through its legacy solana-modal compatibility proxy, a store
|
|
1683
|
-
this engine does not ship; every demo here goes through the page-scoped
|
|
1684
|
-
store and the ds-stack-demo vehicle directly instead. The exact spelling of
|
|
1685
|
-
that legacy store name is deliberately absent from this file, because
|
|
1686
|
-
style_guide_modal_simulator_test scans the source for it. %>
|
|
1687
|
-
<script>
|
|
1688
|
-
window.dsModalDemos = (function () {
|
|
1689
|
-
var VEHICLE = 'ds-stack-demo';
|
|
1690
|
-
|
|
1691
|
-
function store() { return Alpine.store('dsModals'); }
|
|
1692
|
-
|
|
1693
|
-
// Open the stack-mechanics vehicle. The defaults model a real pending
|
|
1694
|
-
// operation, INCLUDING dismissible: false — the demos that want an
|
|
1695
|
-
// escapable card override it explicitly.
|
|
1696
|
-
//
|
|
1697
|
-
// THE VEHICLE IS DELIBERATELY GENERIC. These demos ran on the onchain-tx
|
|
1698
|
-
// specimen until 2026-09-09, which made every one of them depend on a copy
|
|
1699
|
-
// of turf-monster's card; retiring that copy would have taken the whole
|
|
1700
|
-
// section with it. style/modals/_ds_stack_demo belongs to this page and to
|
|
1701
|
-
// nothing that ships.
|
|
1702
|
-
function openDemo(props) {
|
|
1703
|
-
store().open(VEHICLE, Object.assign({
|
|
1704
|
-
state: 'processing',
|
|
1705
|
-
title: 'Submitting entry',
|
|
1706
|
-
message: 'Awaiting confirmation',
|
|
1707
|
-
dismissible: false
|
|
1708
|
-
}, props || {}));
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
// In-flow state change on the card that is already open. advance() patches
|
|
1712
|
-
// props WITHOUT replacing the stack entry, so the vehicle's x-data scope
|
|
1713
|
-
// survives the transition. Guarded to a still-open vehicle card: a timer
|
|
1714
|
-
// that fires after the user closed it must do nothing rather than patch
|
|
1715
|
-
// whatever card happens to be on top now.
|
|
1716
|
-
function advanceDemo(patch) {
|
|
1717
|
-
var cur = store().current();
|
|
1718
|
-
if (!cur || cur.id !== VEHICLE || cur._closing) return;
|
|
1719
|
-
store().advance(patch);
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
var SUCCESS = {
|
|
1723
|
-
state: 'success',
|
|
1724
|
-
title: 'Entry confirmed',
|
|
1725
|
-
message: 'Resolved in place — this is the same stack entry you opened.',
|
|
1726
|
-
dismissible: true
|
|
1727
|
-
};
|
|
1728
|
-
var FAILURE = {
|
|
1729
|
-
state: 'error',
|
|
1730
|
-
title: 'Submission failed',
|
|
1731
|
-
message: 'The error face of that same entry, patched in by advance().',
|
|
1732
|
-
dismissible: true
|
|
1733
|
-
};
|
|
1734
|
-
|
|
1735
|
-
// === Enter / leave simulator =====================================
|
|
1736
|
-
// The demo vehicle is the always-registered email-change-pending card, so
|
|
1737
|
-
// only the motion varies between runs.
|
|
1738
|
-
var ANIM_DEMO_ID = 'email-change-pending';
|
|
1739
|
-
var ANIM_DEMO_PROPS = { currentEmail: 'you@example.com', newEmail: 'new@example.com' };
|
|
1740
|
-
|
|
1741
|
-
function animOpen(enterAnim, exitAnim) {
|
|
1742
|
-
store().open(ANIM_DEMO_ID, Object.assign({}, ANIM_DEMO_PROPS, {
|
|
1743
|
-
enterAnim: enterAnim, exitAnim: exitAnim
|
|
1744
|
-
}));
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
// Open, let the entrance settle, then close so the chosen EXIT keyframe is
|
|
1748
|
-
// what you watch. The hold is the entrance's own registry duration plus a
|
|
1749
|
-
// beat, read from the live registry rather than assumed.
|
|
1750
|
-
function animRoundTrip(enterAnim, exitAnim) {
|
|
1751
|
-
animOpen(enterAnim, exitAnim);
|
|
1752
|
-
var reg = window.ModalAnimations || { enter: {}, exit: {} };
|
|
1753
|
-
var enterMs = ((reg.enter[enterAnim] || {}).ms) || 320;
|
|
1754
|
-
setTimeout(function () { store().close(); }, enterMs + 700);
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
function el(id) { return document.getElementById(id); }
|
|
1758
|
-
|
|
1759
|
-
// Build the quick buttons and the dropdown options FROM THE LIVE REGISTRY.
|
|
1760
|
-
// This is the property the section exists to demonstrate: a newly
|
|
1761
|
-
// registered animation key grows a control here with no edit to this file.
|
|
1762
|
-
// Idempotent — it clears each container first, so re-running on a Turbo
|
|
1763
|
-
// visit cannot double the controls.
|
|
1764
|
-
function buildAnimControls() {
|
|
1765
|
-
var reg = window.ModalAnimations || { enter: {}, exit: {} };
|
|
1766
|
-
|
|
1767
|
-
var label = function (key) {
|
|
1768
|
-
return key.charAt(0).toUpperCase() + key.slice(1) + (key === 'pop' ? ' (default)' : '');
|
|
1769
|
-
};
|
|
1770
|
-
var addOption = function (sel, key) {
|
|
1771
|
-
if (!sel) return;
|
|
1772
|
-
var o = document.createElement('option');
|
|
1773
|
-
o.value = key;
|
|
1774
|
-
o.textContent = label(key);
|
|
1775
|
-
if (key === 'pop') o.selected = true;
|
|
1776
|
-
sel.appendChild(o);
|
|
1777
|
-
};
|
|
1778
|
-
var addButton = function (container, key, onClick, suffix) {
|
|
1779
|
-
if (!container) return;
|
|
1780
|
-
var b = document.createElement('button');
|
|
1781
|
-
b.type = 'button';
|
|
1782
|
-
b.className = 'btn btn-outline btn-sm';
|
|
1783
|
-
b.textContent = label(key) + (suffix || '');
|
|
1784
|
-
b.onclick = onClick;
|
|
1785
|
-
container.appendChild(b);
|
|
1786
|
-
};
|
|
1787
|
-
|
|
1788
|
-
var enterBtns = el('modal-anim-enter-buttons');
|
|
1789
|
-
var exitBtns = el('modal-anim-exit-buttons');
|
|
1790
|
-
var enterSel = el('modal-anim-enter-select');
|
|
1791
|
-
var exitSel = el('modal-anim-exit-select');
|
|
1792
|
-
|
|
1793
|
-
[enterBtns, exitBtns, enterSel, exitSel].forEach(function (node) {
|
|
1794
|
-
if (node) node.innerHTML = '';
|
|
1795
|
-
});
|
|
1796
|
-
|
|
1797
|
-
Object.keys(reg.enter || {}).forEach(function (key) {
|
|
1798
|
-
addButton(enterBtns, key, function () { animOpen(key, 'pop'); }, ' ↗');
|
|
1799
|
-
addOption(enterSel, key);
|
|
1800
|
-
});
|
|
1801
|
-
Object.keys(reg.exit || {}).forEach(function (key) {
|
|
1802
|
-
addButton(exitBtns, key, function () { animRoundTrip('pop', key); });
|
|
1803
|
-
addOption(exitSel, key);
|
|
1804
|
-
});
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
// WHEN TO BUILD. window.ModalAnimations is published by studio/modal_host,
|
|
1808
|
-
// a module, which runs after the document has parsed and before
|
|
1809
|
-
// DOMContentLoaded, so the build waits for the rest of the load either way:
|
|
1810
|
-
// full load — DOMContentLoaded, the module has run by then;
|
|
1811
|
-
// Turbo visit — DOMContentLoaded already fired and never fires again, so
|
|
1812
|
-
// fall through to a macrotask; the registry is already up.
|
|
1813
|
-
// turbo:load covers the remaining visits; the build is idempotent, so the
|
|
1814
|
-
// overlap between these paths is redundant work and nothing worse.
|
|
1815
|
-
if (document.readyState === 'loading') {
|
|
1816
|
-
document.addEventListener('DOMContentLoaded', buildAnimControls);
|
|
1817
|
-
} else {
|
|
1818
|
-
setTimeout(buildAnimControls, 0);
|
|
1819
|
-
}
|
|
1820
|
-
if (!window.__dsAnimControlsBound) {
|
|
1821
|
-
window.__dsAnimControlsBound = true;
|
|
1822
|
-
document.addEventListener('turbo:load', buildAnimControls);
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
return {
|
|
1826
|
-
// Stack behaviour
|
|
1827
|
-
processing: function () { openDemo({}); },
|
|
1828
|
-
success: function () { openDemo(SUCCESS); },
|
|
1829
|
-
error: function () { openDemo(FAILURE); },
|
|
1830
|
-
processThenSuccess: function () {
|
|
1831
|
-
openDemo({ message: 'Awaiting RPC confirmation' });
|
|
1832
|
-
setTimeout(function () { advanceDemo(SUCCESS); }, 3000);
|
|
1833
|
-
},
|
|
1834
|
-
processThenError: function () {
|
|
1835
|
-
openDemo({ message: 'Awaiting RPC confirmation' });
|
|
1836
|
-
setTimeout(function () { advanceDemo(FAILURE); }, 3000);
|
|
1837
|
-
},
|
|
1838
|
-
dismissible: function () {
|
|
1839
|
-
openDemo({
|
|
1840
|
-
dismissible: true,
|
|
1841
|
-
title: 'Dismissible processing',
|
|
1842
|
-
message: 'Escape and click-outside both close this one.'
|
|
1843
|
-
});
|
|
1844
|
-
},
|
|
1845
|
-
fastWithHold: function () {
|
|
1846
|
-
openDemo({ title: 'Fast operation', message: 'Holding the spinner for at least 1500ms' });
|
|
1847
|
-
window.StudioModals.holdAtLeast(1500).then(function () { advanceDemo(SUCCESS); });
|
|
1848
|
-
},
|
|
1849
|
-
fastNoHold: function () {
|
|
1850
|
-
openDemo({ title: 'Fast operation', message: 'No hold, so this flashes past' });
|
|
1851
|
-
setTimeout(function () { advanceDemo(SUCCESS); }, 50);
|
|
1852
|
-
},
|
|
1853
|
-
stackTwo: function () {
|
|
1854
|
-
openDemo({
|
|
1855
|
-
dismissible: true,
|
|
1856
|
-
title: 'Bottom of the stack',
|
|
1857
|
-
message: 'A second card is about to push in front of this one.'
|
|
1858
|
-
});
|
|
1859
|
-
setTimeout(function () {
|
|
1860
|
-
store().open(ANIM_DEMO_ID, ANIM_DEMO_PROPS);
|
|
1861
|
-
}, 700);
|
|
1862
|
-
},
|
|
1863
|
-
|
|
1864
|
-
// Enter / leave simulator
|
|
1865
|
-
animOpenSelected: function () {
|
|
1866
|
-
animOpen(el('modal-anim-enter-select').value, el('modal-anim-exit-select').value);
|
|
1867
|
-
},
|
|
1868
|
-
animRoundTripSelected: function () {
|
|
1869
|
-
animRoundTrip(el('modal-anim-enter-select').value, el('modal-anim-exit-select').value);
|
|
1870
|
-
}
|
|
1871
|
-
};
|
|
1872
|
-
})();
|
|
1873
|
-
</script>
|
|
1874
|
-
|
|
1875
1491
|
</section>
|
data/lib/studio/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: studio-engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.98.0.rc1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex McRitchie
|
|
@@ -409,23 +409,29 @@ files:
|
|
|
409
409
|
- app/helpers/studio_navbar_helper.rb
|
|
410
410
|
- app/helpers/studio_sidebar_helper.rb
|
|
411
411
|
- app/helpers/studio_theme_helper.rb
|
|
412
|
+
- app/javascript/studio/alpine_scopes.js
|
|
412
413
|
- app/javascript/studio/alpine_shims.js
|
|
413
414
|
- app/javascript/studio/alpine_stores.js
|
|
414
415
|
- app/javascript/studio/application.js
|
|
416
|
+
- app/javascript/studio/birthday.js
|
|
415
417
|
- app/javascript/studio/board.js
|
|
416
418
|
- app/javascript/studio/booking.js
|
|
419
|
+
- app/javascript/studio/confirm_interstitial.js
|
|
417
420
|
- app/javascript/studio/controllers/board_controller.js
|
|
418
421
|
- app/javascript/studio/controllers/booking_controller.js
|
|
419
422
|
- app/javascript/studio/controllers/email_banner_scale_controller.js
|
|
420
423
|
- app/javascript/studio/controllers/footer_map_controller.js
|
|
421
424
|
- app/javascript/studio/controllers/geo_settings_controller.js
|
|
422
425
|
- app/javascript/studio/controllers/hold_button_controller.js
|
|
426
|
+
- app/javascript/studio/controllers/identity_mini_controller.js
|
|
423
427
|
- app/javascript/studio/controllers/link_preview_card_controller.js
|
|
424
428
|
- app/javascript/studio/controllers/link_sidebar_controller.js
|
|
425
429
|
- app/javascript/studio/controllers/modal_host_controller.js
|
|
426
430
|
- app/javascript/studio/controllers/nav_collapse_controller.js
|
|
431
|
+
- app/javascript/studio/controllers/style_modals_controller.js
|
|
427
432
|
- app/javascript/studio/controllers/survey_controller.js
|
|
428
433
|
- app/javascript/studio/controllers/toast_controller.js
|
|
434
|
+
- app/javascript/studio/cropper.js
|
|
429
435
|
- app/javascript/studio/email_banner.js
|
|
430
436
|
- app/javascript/studio/email_banner_scale.js
|
|
431
437
|
- app/javascript/studio/footer_map.js
|
|
@@ -433,6 +439,8 @@ files:
|
|
|
433
439
|
- app/javascript/studio/head_chrome.js
|
|
434
440
|
- app/javascript/studio/hold_button.js
|
|
435
441
|
- app/javascript/studio/hold_button_hooks.js
|
|
442
|
+
- app/javascript/studio/identity_mini.js
|
|
443
|
+
- app/javascript/studio/image_upload.js
|
|
436
444
|
- app/javascript/studio/lazy_controllers.js
|
|
437
445
|
- app/javascript/studio/leveling_activity.js
|
|
438
446
|
- app/javascript/studio/link_preview_card.js
|
|
@@ -441,7 +449,10 @@ files:
|
|
|
441
449
|
- app/javascript/studio/modal_host.js
|
|
442
450
|
- app/javascript/studio/nav_collapse.js
|
|
443
451
|
- app/javascript/studio/pinned_stack.js
|
|
452
|
+
- app/javascript/studio/profile_form.js
|
|
444
453
|
- app/javascript/studio/stimulus.js
|
|
454
|
+
- app/javascript/studio/style_guide.js
|
|
455
|
+
- app/javascript/studio/style_modals.js
|
|
445
456
|
- app/javascript/studio/survey.js
|
|
446
457
|
- app/javascript/studio/theme_editor.js
|
|
447
458
|
- app/javascript/studio/toast.js
|
|
@@ -507,6 +518,7 @@ files:
|
|
|
507
518
|
- app/views/sessions/_sso_continue.html.erb
|
|
508
519
|
- app/views/sessions/new.html.erb
|
|
509
520
|
- app/views/solana_sessions/phantom_callback.html.erb
|
|
521
|
+
- app/views/studio/_alpine_scopes_guard.html.erb
|
|
510
522
|
- app/views/studio/_at_time_script.html.erb
|
|
511
523
|
- app/views/studio/_birthday_assets.html.erb
|
|
512
524
|
- app/views/studio/_board_assets.html.erb
|
|
@@ -601,10 +613,8 @@ files:
|
|
|
601
613
|
- app/views/studio/profile_mailer/email_change_notification.html.erb
|
|
602
614
|
- app/views/studio/profile_mailer/email_change_notification.text.erb
|
|
603
615
|
- app/views/studio/profiles/_birthday_fields.html.erb
|
|
604
|
-
- app/views/studio/profiles/_birthday_picker_script.html.erb
|
|
605
616
|
- app/views/studio/profiles/_editable_identity.html.erb
|
|
606
617
|
- app/views/studio/profiles/_email_fields.html.erb
|
|
607
|
-
- app/views/studio/profiles/_form_script.html.erb
|
|
608
618
|
- app/views/studio/profiles/_google_section.html.erb
|
|
609
619
|
- app/views/studio/profiles/_identity.html.erb
|
|
610
620
|
- app/views/studio/profiles/_identity_body.html.erb
|
|
@@ -614,6 +624,7 @@ files:
|
|
|
614
624
|
- app/views/studio/profiles/_newsletter_modals.html.erb
|
|
615
625
|
- app/views/studio/profiles/_newsletter_section.html.erb
|
|
616
626
|
- app/views/studio/profiles/_pencil_icon.html.erb
|
|
627
|
+
- app/views/studio/profiles/_plain_save.html.erb
|
|
617
628
|
- app/views/studio/profiles/_save_controls.html.erb
|
|
618
629
|
- app/views/studio/profiles/edit.html.erb
|
|
619
630
|
- app/views/studio/profiles/show.html.erb
|