feat(import): fire confetti when the complete banner appears
Vendors canvas-confetti 1.9.3 and wires an ImportConfetti hook on the alert-success banner that /imports/:id already renders when run.status == "complete". The hook uses the "realistic look" preset from confetti.js.org and runs on mount, so it fires exactly once — when the banner first appears.
This commit is contained in:
parent
cec74c727c
commit
b4a36890da
4 changed files with 54 additions and 1 deletions
|
|
@ -9,6 +9,7 @@ import {Socket} from "phoenix"
|
||||||
import {LiveSocket} from "phoenix_live_view"
|
import {LiveSocket} from "phoenix_live_view"
|
||||||
import {hooks as colocatedHooks} from "phoenix-colocated/microwaveprop"
|
import {hooks as colocatedHooks} from "phoenix-colocated/microwaveprop"
|
||||||
import topbar from "../vendor/topbar"
|
import topbar from "../vendor/topbar"
|
||||||
|
import "../vendor/canvas-confetti"
|
||||||
|
|
||||||
import initLiveStash from "../../deps/live_stash/assets/js/live-stash.js"
|
import initLiveStash from "../../deps/live_stash/assets/js/live-stash.js"
|
||||||
import {PropagationMap} from "./propagation_map_hook"
|
import {PropagationMap} from "./propagation_map_hook"
|
||||||
|
|
@ -46,6 +47,25 @@ interface CommaNumberHook extends ViewHook {
|
||||||
format(): void
|
format(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Realistic-look recipe from https://confetti.js.org (the "realistic" preset).
|
||||||
|
// Fires exactly once when the hook element mounts — we attach the hook to
|
||||||
|
// the import-complete banner, which only renders when run.status == "complete".
|
||||||
|
const ImportConfetti = {
|
||||||
|
mounted(this: ViewHook) {
|
||||||
|
const fire = window.confetti
|
||||||
|
if (typeof fire !== "function") return
|
||||||
|
const count = 200
|
||||||
|
const defaults = { origin: { y: 0.7 } }
|
||||||
|
const burst = (particleRatio: number, opts: ConfettiOptions) =>
|
||||||
|
fire({ ...defaults, ...opts, particleCount: Math.floor(count * particleRatio) })
|
||||||
|
burst(0.25, { spread: 26, startVelocity: 55 })
|
||||||
|
burst(0.2, { spread: 60 })
|
||||||
|
burst(0.35, { spread: 100, decay: 0.91, scalar: 0.8 })
|
||||||
|
burst(0.1, { spread: 120, startVelocity: 25, decay: 0.92, scalar: 1.2 })
|
||||||
|
burst(0.1, { spread: 120, startVelocity: 45 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const CommaNumber = {
|
const CommaNumber = {
|
||||||
mounted(this: CommaNumberHook) {
|
mounted(this: CommaNumberHook) {
|
||||||
this.el.addEventListener("input", () => this.format())
|
this.el.addEventListener("input", () => this.format())
|
||||||
|
|
@ -74,7 +94,7 @@ const csrfToken = document.querySelector("meta[name='csrf-token']")!.getAttribut
|
||||||
const liveSocket = new LiveSocket("/live", Socket, {
|
const liveSocket = new LiveSocket("/live", Socket, {
|
||||||
longPollFallbackMs: 2500,
|
longPollFallbackMs: 2500,
|
||||||
params: initLiveStash({_csrf_token: csrfToken}),
|
params: initLiveStash({_csrf_token: csrfToken}),
|
||||||
hooks: {...colocatedHooks, PropagationMap, UtcClock, ContactMap, ContactsMap, ElevationProfile, WeatherMap, LocateMe, CopyLink, RoverMap, BeaconMap, BeaconsListMap, CommaNumber},
|
hooks: {...colocatedHooks, PropagationMap, UtcClock, ContactMap, ContactsMap, ElevationProfile, WeatherMap, LocateMe, CopyLink, RoverMap, BeaconMap, BeaconsListMap, CommaNumber, ImportConfetti},
|
||||||
})
|
})
|
||||||
|
|
||||||
// live_table rows are dead on their own — wire up whole-row clicks to
|
// live_table rows are dead on their own — wire up whole-row clicks to
|
||||||
|
|
|
||||||
24
assets/js/types/global.d.ts
vendored
24
assets/js/types/global.d.ts
vendored
|
|
@ -48,6 +48,29 @@ declare module "../vendor/chart.js" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// canvas-confetti UMD attaches to window.confetti as a side effect.
|
||||||
|
declare module "../vendor/canvas-confetti" {
|
||||||
|
const _default: void
|
||||||
|
export default _default
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfettiOptions = {
|
||||||
|
particleCount?: number
|
||||||
|
angle?: number
|
||||||
|
spread?: number
|
||||||
|
startVelocity?: number
|
||||||
|
decay?: number
|
||||||
|
gravity?: number
|
||||||
|
drift?: number
|
||||||
|
ticks?: number
|
||||||
|
origin?: { x?: number; y?: number }
|
||||||
|
colors?: string[]
|
||||||
|
shapes?: string[]
|
||||||
|
scalar?: number
|
||||||
|
zIndex?: number
|
||||||
|
disableForReducedMotion?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
declare module "../vendor/leaflet/leaflet.js" {
|
declare module "../vendor/leaflet/leaflet.js" {
|
||||||
export = L
|
export = L
|
||||||
}
|
}
|
||||||
|
|
@ -89,4 +112,5 @@ interface Window {
|
||||||
L: typeof import("leaflet")
|
L: typeof import("leaflet")
|
||||||
liveSocket: any
|
liveSocket: any
|
||||||
liveReloader: any
|
liveReloader: any
|
||||||
|
confetti: (opts?: ConfettiOptions) => Promise<void> | null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
assets/vendor/canvas-confetti.js
vendored
Normal file
8
assets/vendor/canvas-confetti.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -114,6 +114,7 @@ defmodule MicrowavepropWeb.ImportLive do
|
||||||
:if={@run.status == "complete"}
|
:if={@run.status == "complete"}
|
||||||
id="import-complete-panel"
|
id="import-complete-panel"
|
||||||
class="alert alert-success"
|
class="alert alert-success"
|
||||||
|
phx-hook="ImportConfetti"
|
||||||
>
|
>
|
||||||
<.icon name="hero-check-circle" class="w-6 h-6" />
|
<.icon name="hero-check-circle" class="w-6 h-6" />
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue