mirror of
https://github.com/audacity/audacity.github.io.git
synced 2026-04-12 21:24:53 -05:00
Fix capitalization of "Audio.com" in various components and documentation; add UI semaphore utility for managing concurrent access.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import audioComPromoImage from "../img/promo/audacity-audiocom-promo.png";
|
||||
|
||||
export type PromoType = "banner" | "video" | "exit-popup";
|
||||
|
||||
export type ExitPopupPolicy = {
|
||||
@@ -6,10 +8,17 @@ export type ExitPopupPolicy = {
|
||||
minDwellMs?: number;
|
||||
};
|
||||
|
||||
export type ExitPopupCopy = {
|
||||
export type ExitPopupOptions = {
|
||||
routeAllowlist: string[];
|
||||
displayMode?: "toast" | "modal";
|
||||
promoImageSrc?: string;
|
||||
promoImageAlt?: string;
|
||||
title: string;
|
||||
body: string;
|
||||
body?: string;
|
||||
dismissText: string;
|
||||
policy?: ExitPopupPolicy;
|
||||
impressionTracking?: TrackingConfig;
|
||||
dismissTracking?: TrackingConfig;
|
||||
};
|
||||
|
||||
export type TrackingConfig = {
|
||||
@@ -18,14 +27,6 @@ export type TrackingConfig = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ExitPopupConfig = {
|
||||
routeAllowlist: string[];
|
||||
copy: ExitPopupCopy;
|
||||
displayMode?: "toast" | "modal";
|
||||
policy?: ExitPopupPolicy;
|
||||
impressionTracking?: TrackingConfig;
|
||||
};
|
||||
|
||||
export type PromoData = {
|
||||
type: PromoType;
|
||||
isActive?: boolean;
|
||||
@@ -44,7 +45,7 @@ export type PromoData = {
|
||||
text: string;
|
||||
link: string;
|
||||
};
|
||||
exitPopup?: ExitPopupConfig;
|
||||
popupOptions?: ExitPopupOptions;
|
||||
// Video-specific properties
|
||||
video?: {
|
||||
placeholderImage: string;
|
||||
@@ -77,7 +78,7 @@ export const getFilteredPromos = (
|
||||
if (type && promo.type !== type) return false;
|
||||
|
||||
if (path && promo.type === "exit-popup") {
|
||||
const allowlist = promo.exitPopup?.routeAllowlist ?? [];
|
||||
const allowlist = promo.popupOptions?.routeAllowlist ?? [];
|
||||
if (!routeMatchesAllowlist(path, allowlist)) return false;
|
||||
}
|
||||
|
||||
@@ -93,6 +94,11 @@ export const getFilteredPromos = (
|
||||
});
|
||||
};
|
||||
|
||||
const AUDIO_COM_EXIT_POPUP_IMAGE_SRC =
|
||||
typeof audioComPromoImage === "string"
|
||||
? audioComPromoImage
|
||||
: audioComPromoImage.src;
|
||||
|
||||
const promoData: Record<string, PromoData> = {
|
||||
// === BANNER PROMOS ===
|
||||
audacity4Alpha: {
|
||||
@@ -316,29 +322,36 @@ const promoData: Record<string, PromoData> = {
|
||||
isActive: true,
|
||||
priority: 50,
|
||||
message:
|
||||
"Start with Audio.com to back up and access your projects across devices.",
|
||||
"Use Audio.com to back up your projects, and share them from anywhere!",
|
||||
cta: {
|
||||
text: "Start with Audio.com",
|
||||
text: "Join Audio.com",
|
||||
link: "https://audio.com/",
|
||||
},
|
||||
tracking: {
|
||||
category: "Exit Intent",
|
||||
action: "exit_intent_cta_click",
|
||||
name: "Audio.com Exit Intent Popup",
|
||||
},
|
||||
exitPopup: {
|
||||
popupOptions: {
|
||||
title: "Keep your audio safe in the cloud",
|
||||
routeAllowlist: ["/download", "/post-download", "/cloud-saving"],
|
||||
displayMode: "modal",
|
||||
copy: {
|
||||
title: "Keep your audio safe in the cloud",
|
||||
body: "Start with Audio.com to back up and access your projects across devices.",
|
||||
dismissText: "Not now",
|
||||
promoImageSrc: AUDIO_COM_EXIT_POPUP_IMAGE_SRC,
|
||||
promoImageAlt: "Audio.com promotion",
|
||||
dismissText: "Not now",
|
||||
policy: {
|
||||
minDwellMs: 3000,
|
||||
},
|
||||
impressionTracking: {
|
||||
category: "Exit Intent",
|
||||
action: "exit_intent_impression",
|
||||
name: "Audio.com Exit Intent Popup",
|
||||
name: "audio.com Exit Intent Popup",
|
||||
},
|
||||
dismissTracking: {
|
||||
category: "Exit Intent",
|
||||
action: "exit_intent_dismiss",
|
||||
name: "audio.com Exit Intent Popup",
|
||||
},
|
||||
},
|
||||
tracking: {
|
||||
category: "Exit Intent",
|
||||
action: "exit_intent_cta_click",
|
||||
name: "audio.com Exit Intent Popup",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
52
src/assets/img/promo/audio-com-exit-intent-logo.svg
Normal file
52
src/assets/img/promo/audio-com-exit-intent-logo.svg
Normal file
@@ -0,0 +1,52 @@
|
||||
<svg
|
||||
width="153"
|
||||
height="32"
|
||||
viewBox="0 0 153 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M27.4853 30.2857H27.5714L19.3 0.571621L8.27143 0.571411L27.4853 30.2857Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M8.96071 30.2857C13.9096 30.2857 17.9214 26.2637 17.9214 21.3023C17.9214 16.3409 13.9096 12.3189 8.96071 12.3189C4.01185 12.3189 0 16.3409 0 21.3023C0 26.2637 4.01185 30.2857 8.96071 30.2857Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M46.378 25.2521C45.1313 25.2521 44.1317 24.8518 43.3789 24.0514C42.6498 23.2274 42.2852 22.015 42.2852 20.4142V17.7304C42.2852 16.2002 42.6968 15.0466 43.5201 14.2697C44.3434 13.4928 45.3901 13.1044 46.6603 13.1044H46.8014C47.4836 13.1044 48.0951 13.2339 48.6362 13.4928C49.2007 13.7518 49.6123 14.1049 49.8711 14.5522V10.809C49.8711 9.39652 49.3301 8.69026 48.248 8.69026H48.1069C47.5659 8.69026 47.1543 8.8786 46.872 9.25527C46.6132 9.63194 46.4839 10.1499 46.4839 10.809V11.6566H42.4969V10.2087C42.4969 8.74912 42.9673 7.57202 43.9082 6.67742C44.8726 5.75928 46.2369 5.30021 48.0011 5.30021H48.3539C50.2592 5.30021 51.6588 5.73574 52.5526 6.60679C53.4464 7.45431 53.8934 8.73734 53.8934 10.4559V25.0755H50.0828V23.3098C49.2124 24.6047 48.0481 25.2521 46.5897 25.2521H46.378ZM46.1663 19.9551C46.1663 20.5907 46.3192 21.1087 46.625 21.5089C46.9308 21.8856 47.366 22.0739 47.9305 22.0739H48.1069C48.6714 22.0739 49.1066 21.8856 49.4124 21.5089C49.7182 21.1087 49.8711 20.5907 49.8711 19.9551V18.1895C49.8711 17.5538 49.7182 17.0477 49.4124 16.671C49.1066 16.2708 48.6714 16.0707 48.1069 16.0707H47.9305C47.3424 16.0707 46.8955 16.259 46.5897 16.6357C46.3075 17.0124 46.1663 17.5303 46.1663 18.1895V19.9551Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M61.0978 25.4286C59.1219 25.4286 57.6635 24.9342 56.7227 23.9455C55.7818 22.9567 55.3113 21.4618 55.3113 19.4607V5.65334H59.3336V19.602C59.3336 20.3082 59.4865 20.8732 59.7923 21.297C60.0981 21.6972 60.545 21.8973 61.1331 21.8973H61.3448C61.9328 21.8973 62.3797 21.6972 62.6855 21.297C62.9913 20.8732 63.1442 20.3082 63.1442 19.602V5.65334H67.1665V19.4607C67.1665 21.4618 66.6961 22.9567 65.7552 23.9455C64.8143 24.9342 63.3559 25.4286 61.3801 25.4286H61.0978Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M72.9522 25.4286C71.6114 25.4286 70.5294 24.9931 69.7061 24.122C68.9063 23.2274 68.5065 21.8973 68.5065 20.1317V10.5972C68.5065 8.83151 68.9063 7.51316 69.7061 6.64211C70.5294 5.74751 71.6114 5.30021 72.9522 5.30021H73.1286C73.8578 5.30021 74.5046 5.48855 75.0692 5.86522C75.6337 6.21835 76.0571 6.68919 76.3394 7.27774V1.06265H80.3617V25.0755H76.4099V23.5924C76.0336 24.1574 75.5631 24.6047 74.9986 24.9342C74.4576 25.2638 73.8342 25.4286 73.1286 25.4286H72.9522ZM72.5288 19.602C72.5288 20.3082 72.6816 20.8732 72.9874 21.297C73.2932 21.6972 73.7401 21.8973 74.3282 21.8973H74.5046C75.0927 21.8973 75.5396 21.6972 75.8454 21.297C76.1512 20.8732 76.3041 20.3082 76.3041 19.602V11.1269C76.3041 10.4206 76.1512 9.86736 75.8454 9.46715C75.5396 9.04339 75.0927 8.83151 74.5046 8.83151H74.3282C73.7401 8.83151 73.2932 9.04339 72.9874 9.46715C72.6816 9.86736 72.5288 10.4206 72.5288 11.1269V19.602Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M81.9839 5.65334H86.0062V25.0755H81.9839V5.65334ZM81.6663 2.61642C81.6663 1.93371 81.878 1.38047 82.3014 0.956713C82.7483 0.509415 83.3129 0.285767 83.995 0.285767C84.6772 0.285767 85.2299 0.509415 85.6533 0.956713C86.1002 1.38047 86.3237 1.93371 86.3237 2.61642C86.3237 3.29914 86.1002 3.86415 85.6533 4.31145C85.2299 4.7352 84.6772 4.94708 83.995 4.94708C83.3129 4.94708 82.7483 4.7352 82.3014 4.31145C81.878 3.86415 81.6663 3.29914 81.6663 2.61642Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M93.3489 25.4286C89.4443 25.4286 87.4919 23.4746 87.4919 19.5667V11.1622C87.4919 7.2542 89.4325 5.30021 93.3137 5.30021H93.5959C97.4771 5.30021 99.4177 7.2542 99.4177 11.1622V19.5667C99.4177 23.4746 97.4653 25.4286 93.5606 25.4286H93.3489ZM91.5142 19.602C91.5142 20.3082 91.6789 20.8732 92.0082 21.297C92.3375 21.6972 92.7962 21.8973 93.3842 21.8973H93.5254C94.1134 21.8973 94.5721 21.6972 94.9014 21.297C95.2307 20.8732 95.3954 20.3082 95.3954 19.602V11.1269C95.3954 10.4206 95.2307 9.86736 94.9014 9.46715C94.5721 9.04339 94.1134 8.83151 93.5254 8.83151H93.3842C92.7962 8.83151 92.3375 9.04339 92.0082 9.46715C91.6789 9.86736 91.5142 10.4206 91.5142 11.1269V19.602Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M102.949 25.1814C102.22 25.1814 101.597 24.9225 101.079 24.4045C100.562 23.8866 100.303 23.2628 100.303 22.533C100.303 21.7796 100.562 21.1558 101.079 20.6614C101.597 20.1434 102.22 19.8845 102.949 19.8845C103.702 19.8845 104.325 20.1434 104.819 20.6614C105.337 21.1558 105.596 21.7796 105.596 22.533C105.596 23.2628 105.337 23.8866 104.819 24.4045C104.325 24.9225 103.702 25.1814 102.949 25.1814Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M112.265 25.4286C110.337 25.4286 108.878 24.9225 107.89 23.9102C106.902 22.8979 106.408 21.45 106.408 19.5667V11.1622C106.408 9.27881 106.902 7.83098 107.89 6.81867C108.878 5.80637 110.337 5.30021 112.265 5.30021H112.512C116.205 5.30021 118.052 7.2542 118.052 11.1622V11.9744H114.136V11.1269C114.136 10.3971 113.983 9.83205 113.677 9.43183C113.395 9.03162 112.959 8.83151 112.371 8.83151H112.23C111.642 8.83151 111.195 9.04339 110.889 9.46715C110.584 9.86736 110.431 10.4206 110.431 11.1269V19.602C110.431 20.3082 110.584 20.8732 110.889 21.297C111.195 21.6972 111.642 21.8973 112.23 21.8973H112.371C112.959 21.8973 113.395 21.6972 113.677 21.297C113.983 20.8732 114.136 20.3082 114.136 19.602V18.7898H118.052V19.5667C118.052 23.4746 116.205 25.4286 112.512 25.4286H112.265Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M125.187 25.4286C121.282 25.4286 119.33 23.4746 119.33 19.5667V11.1622C119.33 7.2542 121.27 5.30021 125.151 5.30021H125.434C129.315 5.30021 131.255 7.2542 131.255 11.1622V19.5667C131.255 23.4746 129.303 25.4286 125.398 25.4286H125.187ZM123.352 19.602C123.352 20.3082 123.517 20.8732 123.846 21.297C124.175 21.6972 124.634 21.8973 125.222 21.8973H125.363C125.951 21.8973 126.41 21.6972 126.739 21.297C127.068 20.8732 127.233 20.3082 127.233 19.602V11.1269C127.233 10.4206 127.068 9.86736 126.739 9.46715C126.41 9.04339 125.951 8.83151 125.363 8.83151H125.222C124.634 8.83151 124.175 9.04339 123.846 9.46715C123.517 9.86736 123.352 10.4206 123.352 11.1269V19.602Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M132.736 5.65334H136.618V7.31305C136.994 6.7245 137.488 6.24189 138.1 5.86522C138.735 5.48855 139.417 5.30021 140.146 5.30021H140.322C142.039 5.30021 143.239 6.06533 143.921 7.59556C144.274 6.95992 144.792 6.41846 145.474 5.97116C146.179 5.52386 146.967 5.30021 147.838 5.30021H148.014C149.331 5.30021 150.343 5.74751 151.048 6.64211C151.778 7.5367 152.142 8.86683 152.142 10.6325V25.0755H148.12V11.1269C148.12 10.4206 147.967 9.86736 147.661 9.46715C147.379 9.04339 146.944 8.83151 146.356 8.83151H146.215C145.627 8.83151 145.18 9.04339 144.874 9.46715C144.592 9.86736 144.451 10.4206 144.451 11.1269V25.0755H140.428V11.1269C140.428 10.4206 140.275 9.86736 139.97 9.46715C139.687 9.04339 139.252 8.83151 138.664 8.83151H138.523C137.935 8.83151 137.488 9.04339 137.182 9.46715C136.9 9.86736 136.759 10.4206 136.759 11.1269V25.0755H132.736V5.65334Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
@@ -8,13 +8,91 @@ const cookieStorage = {
|
||||
},
|
||||
setItem: (key, value) => {
|
||||
document.cookie = `${key}=${value}; expires=${new Date(
|
||||
new Date().getTime() + 1000 * 60 * 60 * 24 * 365
|
||||
new Date().getTime() + 1000 * 60 * 60 * 24 * 365,
|
||||
).toGMTString()}; path=/ `;
|
||||
},
|
||||
};
|
||||
|
||||
const storageType = cookieStorage;
|
||||
const consentPropertyName = "audacity_consent";
|
||||
const ATTENTION_OVERLAY_CHANNEL = "attention-overlay";
|
||||
const COOKIE_CONSENT_OWNER = "cookie-consent";
|
||||
const COOKIE_CONSENT_PRIORITY = 10;
|
||||
|
||||
const getUiSemaphore = () => {
|
||||
if (typeof window === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (window.__audacityUiSemaphore) {
|
||||
return window.__audacityUiSemaphore;
|
||||
}
|
||||
|
||||
const state = window.__audacityUiSemaphoreState || {
|
||||
locks: new Map(),
|
||||
listeners: new Set(),
|
||||
};
|
||||
|
||||
window.__audacityUiSemaphoreState = state;
|
||||
|
||||
const notify = (channel) => {
|
||||
const lock = state.locks.get(channel) || null;
|
||||
state.listeners.forEach((listener) => listener(channel, lock));
|
||||
};
|
||||
|
||||
const semaphore = {
|
||||
acquire(channel, owner, options) {
|
||||
const requestedPriority = options?.priority || 0;
|
||||
const shouldPreempt = options?.preempt || false;
|
||||
const currentLock = state.locks.get(channel);
|
||||
|
||||
if (currentLock && currentLock.owner !== owner) {
|
||||
if (!(shouldPreempt && requestedPriority > currentLock.priority)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
currentLock &&
|
||||
currentLock.owner === owner &&
|
||||
currentLock.priority === requestedPriority
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
state.locks.set(channel, {
|
||||
owner,
|
||||
priority: requestedPriority,
|
||||
});
|
||||
notify(channel);
|
||||
return true;
|
||||
},
|
||||
release(channel, owner) {
|
||||
const currentLock = state.locks.get(channel);
|
||||
if (!currentLock || currentLock.owner !== owner) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state.locks.delete(channel);
|
||||
notify(channel);
|
||||
},
|
||||
isLocked(channel) {
|
||||
return state.locks.has(channel);
|
||||
},
|
||||
getLock(channel) {
|
||||
return state.locks.get(channel) || null;
|
||||
},
|
||||
subscribe(listener) {
|
||||
state.listeners.add(listener);
|
||||
return () => {
|
||||
state.listeners.delete(listener);
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
window.__audacityUiSemaphore = semaphore;
|
||||
return semaphore;
|
||||
};
|
||||
|
||||
const showShowPopup = () => !storageType.getItem(consentPropertyName);
|
||||
const saveAcceptToStorage = () =>
|
||||
@@ -26,12 +104,46 @@ window.addEventListener("load", function () {
|
||||
const consentPopup = document.getElementById("consent-popup");
|
||||
const acceptBtn = document.getElementById("accept");
|
||||
const rejectBtn = document.getElementById("reject");
|
||||
const semaphore = getUiSemaphore();
|
||||
let popupRetryTimeoutId = null;
|
||||
let shouldAttemptPopup = true;
|
||||
let tryShowPopup = () => {};
|
||||
|
||||
const releaseOverlayLock = () => {
|
||||
if (!semaphore) {
|
||||
return;
|
||||
}
|
||||
|
||||
semaphore.release(ATTENTION_OVERLAY_CHANNEL, COOKIE_CONSENT_OWNER);
|
||||
};
|
||||
|
||||
const stopPopupAttempts = () => {
|
||||
shouldAttemptPopup = false;
|
||||
if (popupRetryTimeoutId !== null) {
|
||||
window.clearTimeout(popupRetryTimeoutId);
|
||||
popupRetryTimeoutId = null;
|
||||
}
|
||||
};
|
||||
|
||||
const queuePopupRetry = () => {
|
||||
if (!shouldAttemptPopup || !showShowPopup(storageType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (popupRetryTimeoutId !== null) {
|
||||
window.clearTimeout(popupRetryTimeoutId);
|
||||
}
|
||||
|
||||
popupRetryTimeoutId = window.setTimeout(tryShowPopup, 300);
|
||||
};
|
||||
|
||||
const acceptCookie = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
saveAcceptToStorage(storageType);
|
||||
stopPopupAttempts();
|
||||
consentPopup.classList.add("hide");
|
||||
releaseOverlayLock();
|
||||
if (typeof _paq !== "undefined") {
|
||||
_paq.push(["setCookieConsentGiven"]);
|
||||
}
|
||||
@@ -41,15 +153,59 @@ window.addEventListener("load", function () {
|
||||
event.preventDefault();
|
||||
|
||||
saveRejectToStorage(storageType);
|
||||
stopPopupAttempts();
|
||||
consentPopup.classList.add("hide");
|
||||
releaseOverlayLock();
|
||||
};
|
||||
|
||||
acceptBtn.addEventListener("click", acceptCookie);
|
||||
rejectBtn.addEventListener("click", rejectCookie);
|
||||
|
||||
if (semaphore) {
|
||||
semaphore.subscribe((channel, lock) => {
|
||||
if (channel !== ATTENTION_OVERLAY_CHANNEL || !consentPopup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ownedByCookie = lock?.owner === COOKIE_CONSENT_OWNER;
|
||||
if (!ownedByCookie && !consentPopup.classList.contains("hide")) {
|
||||
consentPopup.classList.add("hide");
|
||||
}
|
||||
|
||||
if (!lock && shouldAttemptPopup && showShowPopup(storageType)) {
|
||||
queuePopupRetry();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (showShowPopup(storageType)) {
|
||||
setTimeout(() => {
|
||||
tryShowPopup = () => {
|
||||
if (!shouldAttemptPopup || !showShowPopup(storageType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!semaphore) {
|
||||
consentPopup.classList.remove("hide");
|
||||
return;
|
||||
}
|
||||
|
||||
const acquired = semaphore.acquire(
|
||||
ATTENTION_OVERLAY_CHANNEL,
|
||||
COOKIE_CONSENT_OWNER,
|
||||
{
|
||||
priority: COOKIE_CONSENT_PRIORITY,
|
||||
preempt: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (!acquired) {
|
||||
queuePopupRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
consentPopup.classList.remove("hide");
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
setTimeout(tryShowPopup, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import promoData, {
|
||||
} from "../../assets/data/promotions";
|
||||
import { trackEventIfConsented } from "../../utils/matomo";
|
||||
import { selectWeightedItem } from "../../utils/selectWeightedItem";
|
||||
import { getUiSemaphore } from "../../utils/uiSemaphore";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
type ExitIntentPopupProps = {
|
||||
@@ -14,6 +15,9 @@ type ExitIntentPopupProps = {
|
||||
|
||||
const SESSION_IMPRESSION_KEY = "exit_intent_session_impressions";
|
||||
const SUPPRESS_UNTIL_KEY = "exit_intent_suppress_until";
|
||||
const ATTENTION_OVERLAY_CHANNEL = "attention-overlay";
|
||||
const EXIT_INTENT_OVERLAY_OWNER = "exit-intent-popup";
|
||||
const EXIT_INTENT_OVERLAY_PRIORITY = 100;
|
||||
|
||||
const DEFAULT_POLICY: Required<ExitPopupPolicy> = {
|
||||
sessionCap: 1,
|
||||
@@ -28,19 +32,22 @@ type ExitPopupPromo = PromoData & {
|
||||
link: string;
|
||||
};
|
||||
tracking: TrackingConfig;
|
||||
exitPopup: {
|
||||
popupOptions: {
|
||||
routeAllowlist: string[];
|
||||
displayMode?: "toast" | "modal";
|
||||
copy: {
|
||||
title: string;
|
||||
body: string;
|
||||
dismissText: string;
|
||||
};
|
||||
promoImageSrc?: string;
|
||||
promoImageAlt?: string;
|
||||
title: string;
|
||||
body?: string;
|
||||
dismissText: string;
|
||||
policy?: ExitPopupPolicy;
|
||||
impressionTracking?: TrackingConfig;
|
||||
dismissTracking?: TrackingConfig;
|
||||
};
|
||||
};
|
||||
|
||||
type DismissReason = "button" | "backdrop" | "escape";
|
||||
|
||||
const resolvePolicy = (policy: ExitPopupPolicy | undefined) => ({
|
||||
sessionCap: policy?.sessionCap ?? DEFAULT_POLICY.sessionCap,
|
||||
dismissCooldownDays:
|
||||
@@ -53,7 +60,8 @@ const isExitPopupPromo = (promo: PromoData): promo is ExitPopupPromo => {
|
||||
promo.type === "exit-popup" &&
|
||||
Boolean(promo.cta) &&
|
||||
Boolean(promo.tracking) &&
|
||||
Boolean(promo.exitPopup?.copy)
|
||||
Boolean(promo.popupOptions?.title) &&
|
||||
Boolean(promo.popupOptions?.dismissText)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -62,11 +70,10 @@ const isDesktopCapableContext = () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasMinWidth = window.matchMedia("(min-width: 1024px)").matches;
|
||||
const hasPointer = window.matchMedia("(pointer: fine)").matches;
|
||||
const hasHover = window.matchMedia("(hover: hover)").matches;
|
||||
|
||||
return hasMinWidth && hasPointer && hasHover;
|
||||
return hasPointer && hasHover;
|
||||
};
|
||||
|
||||
const getNumberFromStorage = (storage: Storage, key: string): number => {
|
||||
@@ -118,10 +125,13 @@ const ExitIntentPopup: React.FC<ExitIntentPopupProps> = ({ requestPath }) => {
|
||||
const [isDwellReady, setIsDwellReady] = useState(false);
|
||||
const [hasEngagement, setHasEngagement] = useState(false);
|
||||
const [hasExitIntent, setHasExitIntent] = useState(false);
|
||||
const [isAttentionOverlayLocked, setIsAttentionOverlayLocked] =
|
||||
useState(false);
|
||||
const [selectedPromo, setSelectedPromo] = useState<ExitPopupPromo | null>(
|
||||
null,
|
||||
);
|
||||
const hasShownRef = useRef(false);
|
||||
const hasOverlayLockRef = useRef(false);
|
||||
const isDebugMode = isExitIntentDebugEnabled();
|
||||
|
||||
const resolvedPath = useMemo(() => {
|
||||
@@ -132,13 +142,57 @@ const ExitIntentPopup: React.FC<ExitIntentPopupProps> = ({ requestPath }) => {
|
||||
}, [requestPath]);
|
||||
|
||||
const resolvedPolicy = useMemo(
|
||||
() => resolvePolicy(selectedPromo?.exitPopup?.policy),
|
||||
() => resolvePolicy(selectedPromo?.popupOptions?.policy),
|
||||
[selectedPromo],
|
||||
);
|
||||
|
||||
const impressionTracking = selectedPromo?.exitPopup?.impressionTracking;
|
||||
const impressionTracking = selectedPromo?.popupOptions?.impressionTracking;
|
||||
const trackingForImpression = impressionTracking ?? selectedPromo?.tracking;
|
||||
|
||||
const releaseOverlayLock = () => {
|
||||
const semaphore = getUiSemaphore();
|
||||
if (!semaphore || !hasOverlayLockRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
semaphore.release(ATTENTION_OVERLAY_CHANNEL, EXIT_INTENT_OVERLAY_OWNER);
|
||||
hasOverlayLockRef.current = false;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const semaphore = getUiSemaphore();
|
||||
if (!semaphore) {
|
||||
return;
|
||||
}
|
||||
|
||||
const syncOverlayLock = () => {
|
||||
const lock = semaphore.getLock(ATTENTION_OVERLAY_CHANNEL);
|
||||
setIsAttentionOverlayLocked(
|
||||
Boolean(lock) && lock?.owner !== EXIT_INTENT_OVERLAY_OWNER,
|
||||
);
|
||||
};
|
||||
|
||||
syncOverlayLock();
|
||||
const unsubscribe = semaphore.subscribe((channel) => {
|
||||
if (channel !== ATTENTION_OVERLAY_CHANNEL) {
|
||||
return;
|
||||
}
|
||||
syncOverlayLock();
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
releaseOverlayLock();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAttentionOverlayLocked && isVisible) {
|
||||
releaseOverlayLock();
|
||||
setIsVisible(false);
|
||||
}
|
||||
}, [isAttentionOverlayLocked, isVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
@@ -149,6 +203,7 @@ const ExitIntentPopup: React.FC<ExitIntentPopupProps> = ({ requestPath }) => {
|
||||
path: resolvedPath,
|
||||
}).filter(isExitPopupPromo);
|
||||
|
||||
releaseOverlayLock();
|
||||
setSelectedPromo(selectExitPopupPromo(eligiblePopups));
|
||||
setIsVisible(false);
|
||||
setIsDwellReady(false);
|
||||
@@ -171,6 +226,10 @@ const ExitIntentPopup: React.FC<ExitIntentPopupProps> = ({ requestPath }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAttentionOverlayLocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isRouteEligible || !isDesktopCapableContext()) {
|
||||
return;
|
||||
}
|
||||
@@ -214,7 +273,17 @@ const ExitIntentPopup: React.FC<ExitIntentPopupProps> = ({ requestPath }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const dismissTracking = selectedPromo?.popupOptions?.dismissTracking;
|
||||
if (dismissTracking) {
|
||||
trackEventIfConsented(
|
||||
dismissTracking.category,
|
||||
dismissTracking.action,
|
||||
`${dismissTracking.name} (escape)`,
|
||||
);
|
||||
}
|
||||
|
||||
setSuppressCooldown(resolvedPolicy.dismissCooldownDays);
|
||||
releaseOverlayLock();
|
||||
setIsVisible(false);
|
||||
};
|
||||
|
||||
@@ -232,7 +301,14 @@ const ExitIntentPopup: React.FC<ExitIntentPopupProps> = ({ requestPath }) => {
|
||||
document.removeEventListener("mouseout", handleMouseOut);
|
||||
window.removeEventListener("keydown", handleEscapeDismiss);
|
||||
};
|
||||
}, [isDebugMode, isRouteEligible, isVisible, resolvedPolicy]);
|
||||
}, [
|
||||
isAttentionOverlayLocked,
|
||||
isDebugMode,
|
||||
isRouteEligible,
|
||||
isVisible,
|
||||
resolvedPolicy,
|
||||
selectedPromo,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -241,12 +317,25 @@ const ExitIntentPopup: React.FC<ExitIntentPopupProps> = ({ requestPath }) => {
|
||||
!isDwellReady ||
|
||||
!hasEngagement ||
|
||||
!hasExitIntent ||
|
||||
isAttentionOverlayLocked ||
|
||||
hasShownRef.current
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const semaphore = getUiSemaphore();
|
||||
if (
|
||||
semaphore &&
|
||||
!semaphore.acquire(ATTENTION_OVERLAY_CHANNEL, EXIT_INTENT_OVERLAY_OWNER, {
|
||||
priority: EXIT_INTENT_OVERLAY_PRIORITY,
|
||||
preempt: true,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasShownRef.current = true;
|
||||
hasOverlayLockRef.current = true;
|
||||
incrementSessionImpressions();
|
||||
setIsVisible(true);
|
||||
|
||||
@@ -261,13 +350,24 @@ const ExitIntentPopup: React.FC<ExitIntentPopupProps> = ({ requestPath }) => {
|
||||
hasEngagement,
|
||||
hasExitIntent,
|
||||
isDwellReady,
|
||||
isAttentionOverlayLocked,
|
||||
isRouteEligible,
|
||||
selectedPromo,
|
||||
trackingForImpression,
|
||||
]);
|
||||
|
||||
const handleDismiss = () => {
|
||||
const handleDismiss = (reason: DismissReason) => {
|
||||
const dismissTracking = selectedPromo?.popupOptions?.dismissTracking;
|
||||
if (dismissTracking) {
|
||||
trackEventIfConsented(
|
||||
dismissTracking.category,
|
||||
dismissTracking.action,
|
||||
`${dismissTracking.name} (${reason})`,
|
||||
);
|
||||
}
|
||||
|
||||
setSuppressCooldown(resolvedPolicy.dismissCooldownDays);
|
||||
releaseOverlayLock();
|
||||
setIsVisible(false);
|
||||
};
|
||||
|
||||
@@ -277,6 +377,7 @@ const ExitIntentPopup: React.FC<ExitIntentPopupProps> = ({ requestPath }) => {
|
||||
}
|
||||
|
||||
setSuppressCooldown(resolvedPolicy.dismissCooldownDays);
|
||||
releaseOverlayLock();
|
||||
trackEventIfConsented(
|
||||
selectedPromo.tracking.category,
|
||||
selectedPromo.tracking.action,
|
||||
@@ -288,65 +389,39 @@ const ExitIntentPopup: React.FC<ExitIntentPopupProps> = ({ requestPath }) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { copy, displayMode = "toast" } = selectedPromo.exitPopup;
|
||||
const {
|
||||
title,
|
||||
body: popupBody,
|
||||
dismissText,
|
||||
displayMode = "toast",
|
||||
promoImageSrc,
|
||||
promoImageAlt,
|
||||
} = selectedPromo.popupOptions;
|
||||
const body = popupBody ?? selectedPromo.message;
|
||||
const promoImageClassName =
|
||||
displayMode === "toast"
|
||||
? "mb-3 h-[5rem] w-full rounded-md object-cover object-center"
|
||||
: "mb-3 h-auto w-full rounded-md";
|
||||
|
||||
const content = (
|
||||
<div className="w-[calc(100%-2rem)] max-w-md rounded-lg border border-gray-300 bg-white p-4 shadow-lg">
|
||||
<svg
|
||||
width="153"
|
||||
height="32"
|
||||
viewBox="0 0 153 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M27.4853 30.2857H27.5714L19.3 0.571621L8.27143 0.571411L27.4853 30.2857Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M8.96071 30.2857C13.9096 30.2857 17.9214 26.2637 17.9214 21.3023C17.9214 16.3409 13.9096 12.3189 8.96071 12.3189C4.01185 12.3189 0 16.3409 0 21.3023C0 26.2637 4.01185 30.2857 8.96071 30.2857Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M46.378 25.2521C45.1313 25.2521 44.1317 24.8518 43.3789 24.0514C42.6498 23.2274 42.2852 22.015 42.2852 20.4142V17.7304C42.2852 16.2002 42.6968 15.0466 43.5201 14.2697C44.3434 13.4928 45.3901 13.1044 46.6603 13.1044H46.8014C47.4836 13.1044 48.0951 13.2339 48.6362 13.4928C49.2007 13.7518 49.6123 14.1049 49.8711 14.5522V10.809C49.8711 9.39652 49.3301 8.69026 48.248 8.69026H48.1069C47.5659 8.69026 47.1543 8.8786 46.872 9.25527C46.6132 9.63194 46.4839 10.1499 46.4839 10.809V11.6566H42.4969V10.2087C42.4969 8.74912 42.9673 7.57202 43.9082 6.67742C44.8726 5.75928 46.2369 5.30021 48.0011 5.30021H48.3539C50.2592 5.30021 51.6588 5.73574 52.5526 6.60679C53.4464 7.45431 53.8934 8.73734 53.8934 10.4559V25.0755H50.0828V23.3098C49.2124 24.6047 48.0481 25.2521 46.5897 25.2521H46.378ZM46.1663 19.9551C46.1663 20.5907 46.3192 21.1087 46.625 21.5089C46.9308 21.8856 47.366 22.0739 47.9305 22.0739H48.1069C48.6714 22.0739 49.1066 21.8856 49.4124 21.5089C49.7182 21.1087 49.8711 20.5907 49.8711 19.9551V18.1895C49.8711 17.5538 49.7182 17.0477 49.4124 16.671C49.1066 16.2708 48.6714 16.0707 48.1069 16.0707H47.9305C47.3424 16.0707 46.8955 16.259 46.5897 16.6357C46.3075 17.0124 46.1663 17.5303 46.1663 18.1895V19.9551Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M61.0978 25.4286C59.1219 25.4286 57.6635 24.9342 56.7227 23.9455C55.7818 22.9567 55.3113 21.4618 55.3113 19.4607V5.65334H59.3336V19.602C59.3336 20.3082 59.4865 20.8732 59.7923 21.297C60.0981 21.6972 60.545 21.8973 61.1331 21.8973H61.3448C61.9328 21.8973 62.3797 21.6972 62.6855 21.297C62.9913 20.8732 63.1442 20.3082 63.1442 19.602V5.65334H67.1665V19.4607C67.1665 21.4618 66.6961 22.9567 65.7552 23.9455C64.8143 24.9342 63.3559 25.4286 61.3801 25.4286H61.0978Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M72.9522 25.4286C71.6114 25.4286 70.5294 24.9931 69.7061 24.122C68.9063 23.2274 68.5065 21.8973 68.5065 20.1317V10.5972C68.5065 8.83151 68.9063 7.51316 69.7061 6.64211C70.5294 5.74751 71.6114 5.30021 72.9522 5.30021H73.1286C73.8578 5.30021 74.5046 5.48855 75.0692 5.86522C75.6337 6.21835 76.0571 6.68919 76.3394 7.27774V1.06265H80.3617V25.0755H76.4099V23.5924C76.0336 24.1574 75.5631 24.6047 74.9986 24.9342C74.4576 25.2638 73.8342 25.4286 73.1286 25.4286H72.9522ZM72.5288 19.602C72.5288 20.3082 72.6816 20.8732 72.9874 21.297C73.2932 21.6972 73.7401 21.8973 74.3282 21.8973H74.5046C75.0927 21.8973 75.5396 21.6972 75.8454 21.297C76.1512 20.8732 76.3041 20.3082 76.3041 19.602V11.1269C76.3041 10.4206 76.1512 9.86736 75.8454 9.46715C75.5396 9.04339 75.0927 8.83151 74.5046 8.83151H74.3282C73.7401 8.83151 73.2932 9.04339 72.9874 9.46715C72.6816 9.86736 72.5288 10.4206 72.5288 11.1269V19.602Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M81.9839 5.65334H86.0062V25.0755H81.9839V5.65334ZM81.6663 2.61642C81.6663 1.93371 81.878 1.38047 82.3014 0.956713C82.7483 0.509415 83.3129 0.285767 83.995 0.285767C84.6772 0.285767 85.2299 0.509415 85.6533 0.956713C86.1002 1.38047 86.3237 1.93371 86.3237 2.61642C86.3237 3.29914 86.1002 3.86415 85.6533 4.31145C85.2299 4.7352 84.6772 4.94708 83.995 4.94708C83.3129 4.94708 82.7483 4.7352 82.3014 4.31145C81.878 3.86415 81.6663 3.29914 81.6663 2.61642Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M93.3489 25.4286C89.4443 25.4286 87.4919 23.4746 87.4919 19.5667V11.1622C87.4919 7.2542 89.4325 5.30021 93.3137 5.30021H93.5959C97.4771 5.30021 99.4177 7.2542 99.4177 11.1622V19.5667C99.4177 23.4746 97.4653 25.4286 93.5606 25.4286H93.3489ZM91.5142 19.602C91.5142 20.3082 91.6789 20.8732 92.0082 21.297C92.3375 21.6972 92.7962 21.8973 93.3842 21.8973H93.5254C94.1134 21.8973 94.5721 21.6972 94.9014 21.297C95.2307 20.8732 95.3954 20.3082 95.3954 19.602V11.1269C95.3954 10.4206 95.2307 9.86736 94.9014 9.46715C94.5721 9.04339 94.1134 8.83151 93.5254 8.83151H93.3842C92.7962 8.83151 92.3375 9.04339 92.0082 9.46715C91.6789 9.86736 91.5142 10.4206 91.5142 11.1269V19.602Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M102.949 25.1814C102.22 25.1814 101.597 24.9225 101.079 24.4045C100.562 23.8866 100.303 23.2628 100.303 22.533C100.303 21.7796 100.562 21.1558 101.079 20.6614C101.597 20.1434 102.22 19.8845 102.949 19.8845C103.702 19.8845 104.325 20.1434 104.819 20.6614C105.337 21.1558 105.596 21.7796 105.596 22.533C105.596 23.2628 105.337 23.8866 104.819 24.4045C104.325 24.9225 103.702 25.1814 102.949 25.1814Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M112.265 25.4286C110.337 25.4286 108.878 24.9225 107.89 23.9102C106.902 22.8979 106.408 21.45 106.408 19.5667V11.1622C106.408 9.27881 106.902 7.83098 107.89 6.81867C108.878 5.80637 110.337 5.30021 112.265 5.30021H112.512C116.205 5.30021 118.052 7.2542 118.052 11.1622V11.9744H114.136V11.1269C114.136 10.3971 113.983 9.83205 113.677 9.43183C113.395 9.03162 112.959 8.83151 112.371 8.83151H112.23C111.642 8.83151 111.195 9.04339 110.889 9.46715C110.584 9.86736 110.431 10.4206 110.431 11.1269V19.602C110.431 20.3082 110.584 20.8732 110.889 21.297C111.195 21.6972 111.642 21.8973 112.23 21.8973H112.371C112.959 21.8973 113.395 21.6972 113.677 21.297C113.983 20.8732 114.136 20.3082 114.136 19.602V18.7898H118.052V19.5667C118.052 23.4746 116.205 25.4286 112.512 25.4286H112.265Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M125.187 25.4286C121.282 25.4286 119.33 23.4746 119.33 19.5667V11.1622C119.33 7.2542 121.27 5.30021 125.151 5.30021H125.434C129.315 5.30021 131.255 7.2542 131.255 11.1622V19.5667C131.255 23.4746 129.303 25.4286 125.398 25.4286H125.187ZM123.352 19.602C123.352 20.3082 123.517 20.8732 123.846 21.297C124.175 21.6972 124.634 21.8973 125.222 21.8973H125.363C125.951 21.8973 126.41 21.6972 126.739 21.297C127.068 20.8732 127.233 20.3082 127.233 19.602V11.1269C127.233 10.4206 127.068 9.86736 126.739 9.46715C126.41 9.04339 125.951 8.83151 125.363 8.83151H125.222C124.634 8.83151 124.175 9.04339 123.846 9.46715C123.517 9.86736 123.352 10.4206 123.352 11.1269V19.602Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
<path
|
||||
d="M132.736 5.65334H136.618V7.31305C136.994 6.7245 137.488 6.24189 138.1 5.86522C138.735 5.48855 139.417 5.30021 140.146 5.30021H140.322C142.039 5.30021 143.239 6.06533 143.921 7.59556C144.274 6.95992 144.792 6.41846 145.474 5.97116C146.179 5.52386 146.967 5.30021 147.838 5.30021H148.014C149.331 5.30021 150.343 5.74751 151.048 6.64211C151.778 7.5367 152.142 8.86683 152.142 10.6325V25.0755H148.12V11.1269C148.12 10.4206 147.967 9.86736 147.661 9.46715C147.379 9.04339 146.944 8.83151 146.356 8.83151H146.215C145.627 8.83151 145.18 9.04339 144.874 9.46715C144.592 9.86736 144.451 10.4206 144.451 11.1269V25.0755H140.428V11.1269C140.428 10.4206 140.275 9.86736 139.97 9.46715C139.687 9.04339 139.252 8.83151 138.664 8.83151H138.523C137.935 8.83151 137.488 9.04339 137.182 9.46715C136.9 9.86736 136.759 10.4206 136.759 11.1269V25.0755H132.736V5.65334Z"
|
||||
fill="currentcolor"
|
||||
></path>
|
||||
</svg>
|
||||
<p className="text-lg font-semibold text-gray-900">{copy.title}</p>
|
||||
<p className="mt-2 text-gray-700">{copy.body}</p>
|
||||
<div className="mt-4 flex gap-2">
|
||||
{promoImageSrc && (
|
||||
<img
|
||||
src={promoImageSrc}
|
||||
alt={promoImageAlt ?? ""}
|
||||
className={promoImageClassName}
|
||||
/>
|
||||
)}
|
||||
<p className="text-lg font-semibold text-gray-900">{title}</p>
|
||||
<p className="mt-2 text-gray-700">{body}</p>
|
||||
<div className="mt-4 flex gap-2 justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDismiss("button")}
|
||||
className="flex h-10 items-center justify-center rounded-md border border-gray-300 px-4 font-semibold text-gray-700 hover:bg-gray-100"
|
||||
>
|
||||
{dismissText}
|
||||
</button>
|
||||
<a
|
||||
href={selectedPromo.cta.link}
|
||||
onClick={handleCtaClick}
|
||||
@@ -354,13 +429,6 @@ const ExitIntentPopup: React.FC<ExitIntentPopupProps> = ({ requestPath }) => {
|
||||
>
|
||||
{selectedPromo.cta.text}
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDismiss}
|
||||
className="flex h-10 items-center justify-center rounded-md border border-gray-300 px-4 font-semibold text-gray-700 hover:bg-gray-100"
|
||||
>
|
||||
{copy.dismissText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -369,7 +437,7 @@ const ExitIntentPopup: React.FC<ExitIntentPopupProps> = ({ requestPath }) => {
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-40 flex items-center justify-center bg-black/60 px-4"
|
||||
onClick={handleDismiss}
|
||||
onClick={() => handleDismiss("backdrop")}
|
||||
aria-live="polite"
|
||||
>
|
||||
<aside onClick={(event) => event.stopPropagation()}>{content}</aside>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Image } from "astro:assets";
|
||||
import FeaturedVideo from "../video/FeaturedVideo";
|
||||
import JoinAudioDotComButton from "../button/JoinAudioDotComButton";
|
||||
import AudioDotComLogo from "../../assets/img/audiocom_wordmark_offwhite_transparentbg.svg";
|
||||
import VideoPlaceholder from '../../assets/img/audiocom-placeholder.webp'
|
||||
import VideoPlaceholder from "../../assets/img/audiocom-placeholder.webp";
|
||||
|
||||
// Optimize the background image
|
||||
const optimizedBg = await getImage({
|
||||
@@ -26,13 +26,17 @@ const releaseVideo = {
|
||||
title: "Introducing Audio.com",
|
||||
placeholderImage: releaseVideoPlaceholderImage.src,
|
||||
videoURL: "https://www.youtube-nocookie.com/embed/ZDnQgaCoppo?autoplay=1",
|
||||
imageAltText: "Video thumbnail: 15 reasons why you should use Audio.com"
|
||||
imageAltText: "Video thumbnail: 15 reasons why you should use Audio.com",
|
||||
};
|
||||
---
|
||||
|
||||
<!-- Apply the background using inline style -->
|
||||
<section style={`background-image: url('${optimizedBg.src}'); background-size: cover;`}>
|
||||
<div class="mx-auto px-6 xs:px-12 md:px-8 py-8 md:py-8 lg:py-12 max-w-screen-lg">
|
||||
<section
|
||||
style={`background-image: url('${optimizedBg.src}'); background-size: cover;`}
|
||||
>
|
||||
<div
|
||||
class="mx-auto px-6 xs:px-12 md:px-8 py-8 md:py-8 lg:py-12 max-w-screen-lg"
|
||||
>
|
||||
<div class="flex flex-col md:flex-row gap-12 md:gap-16">
|
||||
<!-- Video Column - Order changes on mobile vs desktop -->
|
||||
<div class="w-full md:w-1/2 order-2 md:order-1">
|
||||
@@ -45,16 +49,18 @@ const releaseVideo = {
|
||||
matomoEventName={releaseVideo.title}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Text Content Column -->
|
||||
<div class="w-full md:w-1/2 order-1 md:order-2 flex flex-col gap-4 md:gap-8">
|
||||
<Image src={AudioDotComLogo} class="w-40" alt="audio.com" />
|
||||
<div
|
||||
class="w-full md:w-1/2 order-1 md:order-2 flex flex-col gap-4 md:gap-8"
|
||||
>
|
||||
<Image src={AudioDotComLogo} class="w-40" alt="Audio.com" />
|
||||
<div>
|
||||
<h2 class="text-white">Level up your Audacity</h2>
|
||||
<p class="text-gray-300 mt-4">
|
||||
Audio.com, the online companion to Audacity, lets you collaborate on
|
||||
projects, create versioned backups, and easily share and publish your
|
||||
work.
|
||||
projects, create versioned backups, and easily share and publish
|
||||
your work.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -64,4 +70,4 @@ const releaseVideo = {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -24,7 +24,7 @@ We are excited to announce Audacity 3.5, which adds the following features:
|
||||
|
||||
## Cloud project saving
|
||||
|
||||
We've introduced a new cloud-saving feature that allows you to save your Audacity projects to audio.com. This allows you to work from any device, share & collaborate with others and restore previous versions if something went wrong.
|
||||
We've introduced a new cloud-saving feature that allows you to save your Audacity projects to Audio.com. This allows you to work from any device, share & collaborate with others and restore previous versions if something went wrong.
|
||||
|
||||
## Automatic tempo detection
|
||||
|
||||
|
||||
@@ -10,16 +10,16 @@ draft: false
|
||||
|
||||
In this update, we wanted to share our future goals for [Audio.com](http://audio.com/), including a focus on building a **creator-first platform**.
|
||||
|
||||
As [audio.com](http://audio.com/) has grown, we’ve realized — through in-depth research and discussions with our community — that many creators are interested in sophisticated cloud tools to help collaborate, sell, manage and distribute their work.
|
||||
As [Audio.com](http://audio.com/) has grown, we’ve realized — through in-depth research and discussions with our community — that many creators are interested in sophisticated cloud tools to help collaborate, sell, manage and distribute their work.
|
||||
|
||||
That’s why we’re planning to soon include a cloud-saving feature on [audio.com](http://audio.com/) that allows creators to work on Audacity projects that are saved directly to the cloud. This means that by signing in to Audio.com on any device, you can immediately continue working on your Audacity project. This feature will also provide a convenient way to share your projects with others, and get feedback instantly. It’s a first, exciting step in providing a space that enables audiophiles, podcasters and musicians to collaborate together.
|
||||
That’s why we’re planning to soon include a cloud-saving feature on [Audio.com](http://audio.com/) that allows creators to work on Audacity projects that are saved directly to the cloud. This means that by signing in to Audio.com on any device, you can immediately continue working on your Audacity project. This feature will also provide a convenient way to share your projects with others, and get feedback instantly. It’s a first, exciting step in providing a space that enables audiophiles, podcasters and musicians to collaborate together.
|
||||
|
||||
Building this kind of capability is complicated, so we'll soon launch a beta release to let you try it out and provide feedback. We expect to be releasing this Beta in mid-February.
|
||||
|
||||
We want to provide the best creator tools and services we can while still growing revenue to invest into our products. And while many audio.com features will remain free, the new cloud storage feature will incur additional costs for our team. So we will offer 5 projects completely free, but charge a small fee for usage beyond this amount.
|
||||
We want to provide the best creator tools and services we can while still growing revenue to invest into our products. And while many Audio.com features will remain free, the new cloud storage feature will incur additional costs for our team. So we will offer 5 projects completely free, but charge a small fee for usage beyond this amount.
|
||||
|
||||
Audacity will, of course, **always** remain 100% free and open source.
|
||||
|
||||
We’d love to hear your opinions about this, including the kinds of additional tools you think we should build. Please let us know what you think [in our forum](https://forum.audacityteam.org/t/the-next-steps-for-audio-com-audacity/97997). Thanks for your continued support,
|
||||
|
||||
*Audacity &* [*audio.com*](http://audio.com/) *teams*
|
||||
_Audacity &_ [_Audio.com_](http://audio.com/) _teams_
|
||||
|
||||
@@ -6,70 +6,78 @@ title: Audacity ® | Frequently Asked Questions
|
||||
## About Audacity
|
||||
|
||||
### What is Audacity?
|
||||
|
||||
Audacity is the world’s most popular free software for recording and editing audio. So if you're producing music, a podcast, or just playing around with audio, Audacity is for you. It is [available to download](/download) as a desktop app for Windows, macOS and Linux.
|
||||
|
||||
### Is Audacity free?
|
||||
|
||||
Yes! Audacity has always — and will always — remain free for everyone. It is available to download from the [Audacity website](/).
|
||||
|
||||
### Who is Audacity for?
|
||||
|
||||
Audacity is for anyone who wants to get creative with sound. It’s also the perfect tool for anyone who needs to quickly edit or export audio, for any reason.
|
||||
|
||||
Here are a few of the most common ways Audacity is used every day:
|
||||
Here are a few of the most common ways Audacity is used every day:
|
||||
|
||||
- Podcasters recording and editing spoken content. Audacity is the world’s most popular app for podcasters.
|
||||
- Musicians and bedroom producers, who can edit multiple-parts, mix and add simple effects in an app that’s faster and more intuitive than most DAWs.
|
||||
- Field recorders and educators, who can capture, edit or analyze the sounds of environments, ambience, animals and more.
|
||||
|
||||
### Is Audacity open source?
|
||||
Audacity is proudly open source. This means its source code remains open to anyone to view or modify. Audacity is licensed under the GNU General Public License. Details can be found [here](https://github.com/audacity/audacity/blob/master/LICENSE.txt).
|
||||
|
||||
Audacity is proudly open source. This means its source code remains open to anyone to view or modify. Audacity is licensed under the GNU General Public License. Details can be found [here](https://github.com/audacity/audacity/blob/master/LICENSE.txt).
|
||||
|
||||
A dedicated worldwide community of passionate audio lovers have collaborated to make Audacity the well-loved software it is today. Many third-party plugins have also been developed for Audacity thanks to its open source nature.
|
||||
|
||||
### Does Audacity cost money?
|
||||
No, Audacity is free for everyone. If you found yourself paying for Audacity, one of the following things happened:
|
||||
|
||||
* You signed up for additional Audacity Cloud storage, so you can [save more projects in the cloud](/cloud-saving). Contact support@audio.com to ask for a refund.
|
||||
* You tried to download an Audacity app for iOS or Android. At the time of writing, there doesn't exist an Audacity version for these platforms, so any app you see there was not made by the Audacity team. Contact Apple or Google Play support and ask for a refund.
|
||||
No, Audacity is free for everyone. If you found yourself paying for Audacity, one of the following things happened:
|
||||
|
||||
- You signed up for additional Audacity Cloud storage, so you can [save more projects in the cloud](/cloud-saving). Contact support@audio.com to ask for a refund.
|
||||
- You tried to download an Audacity app for iOS or Android. At the time of writing, there doesn't exist an Audacity version for these platforms, so any app you see there was not made by the Audacity team. Contact Apple or Google Play support and ask for a refund.
|
||||
|
||||
---
|
||||
|
||||
## Download & install
|
||||
|
||||
### What devices are compatible with Audacity?
|
||||
|
||||
Audacity is tested compatible with the following operating systems:
|
||||
|
||||
- Windows 10 & 11, with Vista, 7 and 8.1 believed to work
|
||||
- MacOS 12 & 13, with OS X versions since 10.9 believed to still work
|
||||
- Linux: Ubuntu 22.04, with most major distributions believed to work.
|
||||
|
||||
|
||||
### Is Audacity only available for desktop?
|
||||
|
||||
Yes, Audacity is only available for desktop computers or laptops.
|
||||
|
||||
There is no mobile version of Audacity available.
|
||||
|
||||
### Where can I download Audacity?
|
||||
|
||||
Audacity is available to download from the [Audacity website](/).
|
||||
|
||||
You can either download the app directly, or download Audacity through the free [Muse Hub](https://www.musehub.com/).
|
||||
You can either download the app directly, or download Audacity through the free [Muse Hub](https://www.musehub.com/).
|
||||
|
||||
If you download Audacity via the Muse Hub, you'll also get access to a free selection of sounds, loops and effects available to use in your Audacity projects.
|
||||
|
||||
### Why should I download Audacity through the Muse Hub?
|
||||
|
||||
The [Muse Hub](https://www.musehub.com/) is a gateway to creativity for any audio producer. It's packed with the best free apps, sounds and effects for composing, producing or performing. In the Muse Hub you'll find:
|
||||
|
||||
- **Muse Sounds:** premium collections of sampled instruments, including keys, strings, brass, woodwind, percussion, choirs and electronics. Hear your music played back in astonishing detail with these moving — and completely free — preset packs.
|
||||
- **Elements:** free looping sound clips. Drop them easily into your Audacity performances or podcasts.
|
||||
- **Muse FX:** effects plugins including reverb, delay, compress and more, for fine-tuning your Audacity projects.
|
||||
You can download Audacity via the Muse Hub. If you already have Audacity, you can download the Muse Hub and access the free extras [here](https://www.musehub.com/).
|
||||
|
||||
You can download Audacity via the Muse Hub. If you already have Audacity, you can download the Muse Hub and access the free extras [here](https://www.musehub.com/).
|
||||
|
||||
### How do I install Audacity or update to the latest version?
|
||||
|
||||
You can learn how to install Audacity on Windows, macOS and Linux on our [support page](https://support.audacityteam.org/basics/downloading-and-installing-audacity).
|
||||
|
||||
|
||||
### Is Audacity safe to download?
|
||||
Yes. Audacity is entirely safe to download and install on your desktop computer if it has been downloaded directly from the [Audacity website](/).
|
||||
|
||||
Yes. Audacity is entirely safe to download and install on your desktop computer if it has been downloaded directly from the [Audacity website](/).
|
||||
|
||||
We also publish official versions through the Microsoft Store, via `winget`, via Github and Fosshub. There also are third party Audacity builds around, though we cannot guarantee for the integrity of versions downloaded from places other than mentioned here.
|
||||
|
||||
@@ -78,7 +86,8 @@ We also publish official versions through the Microsoft Store, via `winget`, via
|
||||
## Features
|
||||
|
||||
### What features come with Audacity?
|
||||
In the app, every audio creator can:
|
||||
|
||||
In the app, every audio creator can:
|
||||
|
||||
- Record live with a microphone or mixer. Or digitize imported recordings.
|
||||
- Edit your tracks fast with intuitive tools, including cutting, pasting and smooth volume mixing.
|
||||
@@ -91,43 +100,48 @@ In the app, every audio creator can:
|
||||
- Take your editing to the next level with an extensive selection of third-party effects plugins, designed by the passionate Audacity, open-source community.
|
||||
- Collaborate and backup projects with Cloud saving.
|
||||
- Visualize and analyze your audio clips in Spectrogram view.
|
||||
- Upload and share your files online to [audio.com](https://audio.com/), instantly.
|
||||
- Upload and share your files online to [Audio.com](https://audio.com/), instantly.
|
||||
|
||||
To learn more about specific features, check out the [Audacity manual](https://manual.audacityteam.org/index.html).
|
||||
|
||||
### What audio file formats are compatible with Audacity?
|
||||
|
||||
Audacity supports import, export and conversion of files in every popular audio format, including mp3, m4a, AIFF, FLAC, WAV and more. In fact, you can just use Audacity to convert files into different formats if you like.
|
||||
|
||||
You can even combine clips from multiple formats into the same audio project.
|
||||
You can even combine clips from multiple formats into the same audio project.
|
||||
|
||||
### What audio quality is compatible with Audacity?
|
||||
|
||||
Audacity supports 16-bit, 24-bit and 32-bit audio. Sample rates and formats are converted using high-quality resampling and dithering.
|
||||
|
||||
### Can I use Audacity to produce music?
|
||||
|
||||
Yes! Audacity can be used to record and produce your music tracks. Because it's free and easy-to-use, Audacity is a great DAW for music production beginners or those who want to make quick edits to recordings of live performances.
|
||||
|
||||
For more information on how to use specific Audacity features, check out the [Audacity manual](https://manual.audacityteam.org/).
|
||||
|
||||
### Can I use Audacity to produce podcasts?
|
||||
|
||||
Yes! In fact, Audacity is the world's most popular software for recording and producing podcasts — because it's easy-to-use and completely free.
|
||||
|
||||
For more information on how to use specific Audacity features, check out the [Audacity manual](https://manual.audacityteam.org/).
|
||||
|
||||
### Is Audacity a DAW?
|
||||
|
||||
Audacity is blurring the line between audio editor and DAW. It's capable of much more than a mere audio editor (for example, non-destructive editing and realtime effects), but it also is lacking some key features which DAWs traditionally have (MIDI editing and virtual instruments).
|
||||
|
||||
The gap towards being a full DAW is being rapidly closed, with each release bringing new features that make Audacity increasingly capable, while remaining simple to use.
|
||||
|
||||
### I'm just getting started with Audacity. Are there any free tutorials available?
|
||||
We have a beginner tutorial series available on https://support.audacityteam.org/, beginning with [installing Audacity](https://support.audacityteam.org/basics/downloading-and-installing-audacity) and [FFmpeg](https://support.audacityteam.org/basics/installing-ffmpeg), [recording your voice](https://support.audacityteam.org/basics/recording-your-voice-and-microphone), [recording desktop audio](https://support.audacityteam.org/basics/recording-desktop-audio), [editing audio](https://support.audacityteam.org/basics/audacity-editing), [saving and exporting projects](https://support.audacityteam.org/basics/saving-and-exporting-projects) and more!
|
||||
|
||||
We have a beginner tutorial series available on https://support.audacityteam.org/, beginning with [installing Audacity](https://support.audacityteam.org/basics/downloading-and-installing-audacity) and [FFmpeg](https://support.audacityteam.org/basics/installing-ffmpeg), [recording your voice](https://support.audacityteam.org/basics/recording-your-voice-and-microphone), [recording desktop audio](https://support.audacityteam.org/basics/recording-desktop-audio), [editing audio](https://support.audacityteam.org/basics/audacity-editing), [saving and exporting projects](https://support.audacityteam.org/basics/saving-and-exporting-projects) and more!
|
||||
|
||||
### Is Audacity compatible with third party plugins?
|
||||
|
||||
Yes! As an open-source application many third party plugins have been developed for Audacity. Check out a list of some of our team's favorite Audacity plugins at https://plugins.audacityteam.org/
|
||||
|
||||
Audacity supports VST3, LADSPA, LV2, Nyquist, VST and Audio Unit plugins.
|
||||
|
||||
|
||||
|
||||
### Where can I find more information about specific Audacity features?
|
||||
|
||||
Try the free, online [Audacity manual](https://manual.audacityteam.org/) for in-depth, technical information on specific features.
|
||||
@@ -137,6 +151,7 @@ Try the free, online [Audacity manual](https://manual.audacityteam.org/) for in-
|
||||
## Legal & privacy
|
||||
|
||||
### What is Audacity's privacy policy?
|
||||
|
||||
The Audacity app only collects data relevant to error reporting (such as device information) and software updates.
|
||||
|
||||
The Audacity team will take all the necessary steps to keep your data protected. It is fully GDPR compliant.
|
||||
@@ -144,45 +159,52 @@ The Audacity team will take all the necessary steps to keep your data protected.
|
||||
Read Audacity's privacy policy in full [here](/desktop-privacy-notice/). The cookie policy for this website can be found [here](/cookie-policy/).
|
||||
|
||||
### Is Audacity spyware?
|
||||
|
||||
No. Audacity is entirely safe to use and doesn't store personal information. Audacity only collects data relevant to error reporting (such as device information) and software updates.
|
||||
|
||||
The Audacity team is fully GDPR compliant. Read Audacity's privacy policy in full [here](/desktop-privacy-notice/).
|
||||
|
||||
### I've shared my Audacity project on Audio.com. What are my legal rights regarding my project?
|
||||
If you are the sole creator of the audio project, the intellectual property rights and mechanical copyright is retained by you. Similar to other UGC (User Generated Content) platforms, by using [audio.com](https://audio.com/) you grant a license to host and show your work. You are also free to distribute your work on other platforms, if you wish. For more details read the full [Terms & Conditions](https://audio.com/legal/terms-of-use.pdf).
|
||||
|
||||
If you are the sole creator of the audio project, the intellectual property rights and mechanical copyright is retained by you. Similar to other UGC (User Generated Content) platforms, by using [Audio.com](https://audio.com/) you grant a license to host and show your work. You are also free to distribute your work on other platforms, if you wish. For more details read the full [Terms & Conditions](https://audio.com/legal/terms-of-use.pdf).
|
||||
|
||||
---
|
||||
|
||||
## Team & news
|
||||
|
||||
### Who is the Audacity Team?
|
||||
Audacity is developed by a small team working remotely and supported by the community.
|
||||
|
||||
Audacity is developed by a small team working remotely and supported by the community.
|
||||
|
||||
Current team members are:
|
||||
|
||||
* Product owner: Martin Keary
|
||||
* Project manager: Yana Larina
|
||||
* Developers: Matthieu Hodgkinson, Grzegorz Wojciechowski, Dmitry Makarenko, Gabriel Sartori, Paul Martin
|
||||
* Designers: Dilson's Pickles, Leo Wattenberg
|
||||
* Testers: Sergey Lapysh, Antons Činakovs
|
||||
* People from [MuseScore Studio](https://musescore.org) helping with Audacity 4 development: Jessica Williamson, Igor Korsukov, Elnur Ismailzada
|
||||
- Product owner: Martin Keary
|
||||
- Project manager: Yana Larina
|
||||
- Developers: Matthieu Hodgkinson, Grzegorz Wojciechowski, Dmitry Makarenko, Gabriel Sartori, Paul Martin
|
||||
- Designers: Dilson's Pickles, Leo Wattenberg
|
||||
- Testers: Sergey Lapysh, Antons Činakovs
|
||||
- People from [MuseScore Studio](https://musescore.org) helping with Audacity 4 development: Jessica Williamson, Igor Korsukov, Elnur Ismailzada
|
||||
|
||||
Code contributors can be found on Github in the [commit history](https://github.com/audacity/audacity/commits/master) and [graphs](https://github.com/audacity/audacity/graphs/contributors), [contributors to the manual](https://manual.audacityteam.org/man/credits.html), [contributors to the forum](https://forum.audacityteam.org/u?order=post_count&period=all) and [contributors to the support site](https://support.audacityteam.org/community/contributing/credits-and-license) can be found on the respective pages.
|
||||
|
||||
### Where can I stay up to date with the latest Audacity news and updates?
|
||||
|
||||
Check out our [blog](/blog), subscribe to Audacity [YouTube Channel](https://www.youtube.com/@audacity), or follow us on [Facebook](https://www.facebook.com/Audacity/) or [Twitter](https://twitter.com/getaudacity).
|
||||
|
||||
### I have a question or issue with Audacity. Is there a support team I can contact?
|
||||
|
||||
Many frequently asked questions and issues are answered on our support page [here](https://support.audacityteam.org/). For detailed information on Audacity features, please check out the [Audacity manual](https://manual.audacityteam.org/index.html).
|
||||
|
||||
If you have a question you can't find the answer to, please leave a comment on the [Audacity forum](https://forum.audacityteam.org/), where one of our team (or someone in the Audacity community), will be happy to help!
|
||||
|
||||
### I'm an open-source developer working with Audacity. How can I get in touch with the team?
|
||||
Audacity is currently undergoing major code restructuring, so we unfortunately have very limited capacity to support third parties. That said, feel free to drop by our [dev discord](/devserver).
|
||||
|
||||
Audacity is currently undergoing major code restructuring, so we unfortunately have very limited capacity to support third parties. That said, feel free to drop by our [dev discord](/devserver).
|
||||
|
||||
### What is the history of Audacity?
|
||||
|
||||
Audacity has transformed the lives of musicians and audio content creators for over two decades. Since launching in 2000, the desktop app has been downloaded over 200 million times.
|
||||
|
||||
In 2020, we joined with the makers of [Ultimate Guitar](https://www.ultimate-guitar.com/), [MuseScore](https://musescore.org/) and others to form [Muse Group](https://mu.se/), with a mission to empower millions of creatives all over the globe. Since then, our sister service [audio.com](https://audio.com) launched.
|
||||
In 2020, we joined with the makers of [Ultimate Guitar](https://www.ultimate-guitar.com/), [MuseScore](https://musescore.org/) and others to form [Muse Group](https://mu.se/), with a mission to empower millions of creatives all over the globe. Since then, our sister service [Audio.com](https://audio.com) launched.
|
||||
|
||||
We remain committed to our free, open-source roots — and passionate about helping the next generation create the very best sounds, music and podcasts with Audacity.
|
||||
We remain committed to our free, open-source roots — and passionate about helping the next generation create the very best sounds, music and podcasts with Audacity.
|
||||
|
||||
@@ -6,42 +6,58 @@ import logo from "../assets/img/promo/audacity-audiocom-promo.png";
|
||||
import { Image } from "astro:assets";
|
||||
---
|
||||
|
||||
<BaseLayout title="Audacity Cloud Saving Platform" description="Sync & save projects in Audacity Cloud. Secure, automatic, and always accessible." index="all">
|
||||
|
||||
<BaseLayout
|
||||
title="Audacity Cloud Saving Platform"
|
||||
description="Sync & save projects in Audacity Cloud. Secure, automatic, and always accessible."
|
||||
index="all"
|
||||
>
|
||||
<section class="mx-8 max-w-screen-md xl:max-w-screen-lg md:mx-auto">
|
||||
<div class="flex flex-col items-center justify-center" style="height: 90vh;">
|
||||
<Image src={logo} alt="Audacity logo and audio.com logo overlaying in a playful manner" width=300 quality=80/>
|
||||
<div
|
||||
class="flex flex-col items-center justify-center"
|
||||
style="height: 90vh;"
|
||||
>
|
||||
<Image
|
||||
src={logo}
|
||||
alt="Audacity logo and Audio.com logo overlaying in a playful manner"
|
||||
width="300"
|
||||
quality="80/"
|
||||
/>
|
||||
<div class="text-center">
|
||||
<h1 class="font-bold">
|
||||
Thank you for downloading Audacity!
|
||||
</h1>
|
||||
<h1 class="font-bold">Thank you for downloading Audacity!</h1>
|
||||
<h1 class="font-bold">You can now complete Cloud setup</h1>
|
||||
</div>
|
||||
<div class="w-full mt-8">
|
||||
<div class="grid grid-cols-12 items-center">
|
||||
|
||||
<div class="col-span-3 bg-green-500 h-2"></div>
|
||||
<div class="bg-green-500 h-8 w-8 rounded-full place-self-center">
|
||||
<div class="icon icon-check icon-medium-small text-center text-white mt-1"></div>
|
||||
<div class="col-span-3 bg-green-500 h-2"></div>
|
||||
<div class="bg-green-500 h-8 w-8 rounded-full place-self-center">
|
||||
<div
|
||||
class="icon icon-check icon-medium-small text-center text-white mt-1"
|
||||
>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-span-3 bg-gray-300 h-2"></div>
|
||||
<div class="bg-gray-300 h-8 w-8 rounded-full place-self-center">
|
||||
<div class="icon icon-check icon-medium-small text-center text-white mt-1"></div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-3 bg-gray-300 h-2"></div>
|
||||
<div class="bg-gray-300 h-8 w-8 rounded-full place-self-center">
|
||||
<div
|
||||
class="icon icon-check icon-medium-small text-center text-white mt-1"
|
||||
>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-span-3 bg-gray-300 h-2"></div>
|
||||
<div class="bg-gray-300 h-8 w-8 rounded-full place-self-center">
|
||||
<div class="icon icon-check icon-medium-small text-center text-white mt-1"></div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-3 bg-gray-300 h-2"></div>
|
||||
<div class="bg-gray-300 h-8 w-8 rounded-full place-self-center">
|
||||
<div
|
||||
class="icon icon-check icon-medium-small text-center text-white mt-1"
|
||||
>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-12 mt-2 text-center text-lg font-bold">
|
||||
<div class="col-start-1 col-end-4">Download Audacity application</div>
|
||||
<div class="col-start-5 col-end-8">Connect to Cloud storage</div>
|
||||
<div class="col-start-9 col-end-12">Never lose your work in Audacity</div>
|
||||
<div class="col-start-9 col-end-12">
|
||||
Never lose your work in Audacity
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-12">
|
||||
@@ -52,7 +68,6 @@ import { Image } from "astro:assets";
|
||||
large
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
113
src/utils/uiSemaphore.ts
Normal file
113
src/utils/uiSemaphore.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
type UiSemaphoreLock = {
|
||||
owner: string;
|
||||
priority: number;
|
||||
};
|
||||
|
||||
type UiSemaphoreAcquireOptions = {
|
||||
priority?: number;
|
||||
preempt?: boolean;
|
||||
};
|
||||
|
||||
type UiSemaphoreListener = (
|
||||
channel: string,
|
||||
lock: UiSemaphoreLock | null,
|
||||
) => void;
|
||||
|
||||
type UiSemaphore = {
|
||||
acquire: (
|
||||
channel: string,
|
||||
owner: string,
|
||||
options?: UiSemaphoreAcquireOptions,
|
||||
) => boolean;
|
||||
release: (channel: string, owner: string) => void;
|
||||
isLocked: (channel: string) => boolean;
|
||||
getLock: (channel: string) => UiSemaphoreLock | null;
|
||||
subscribe: (listener: UiSemaphoreListener) => () => void;
|
||||
};
|
||||
|
||||
type UiSemaphoreState = {
|
||||
locks: Map<string, UiSemaphoreLock>;
|
||||
listeners: Set<UiSemaphoreListener>;
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__audacityUiSemaphoreState?: UiSemaphoreState;
|
||||
__audacityUiSemaphore?: UiSemaphore;
|
||||
}
|
||||
}
|
||||
|
||||
const notify = (state: UiSemaphoreState, channel: string) => {
|
||||
const lock = state.locks.get(channel) ?? null;
|
||||
state.listeners.forEach((listener) => listener(channel, lock));
|
||||
};
|
||||
|
||||
export const getUiSemaphore = (): UiSemaphore | null => {
|
||||
if (typeof window === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (window.__audacityUiSemaphore) {
|
||||
return window.__audacityUiSemaphore;
|
||||
}
|
||||
|
||||
const state: UiSemaphoreState = window.__audacityUiSemaphoreState ?? {
|
||||
locks: new Map(),
|
||||
listeners: new Set(),
|
||||
};
|
||||
|
||||
window.__audacityUiSemaphoreState = state;
|
||||
|
||||
const semaphore: UiSemaphore = {
|
||||
acquire(channel, owner, options) {
|
||||
const requestedPriority = options?.priority ?? 0;
|
||||
const shouldPreempt = options?.preempt ?? false;
|
||||
const currentLock = state.locks.get(channel);
|
||||
|
||||
if (currentLock && currentLock.owner !== owner) {
|
||||
if (!(shouldPreempt && requestedPriority > currentLock.priority)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
currentLock &&
|
||||
currentLock.owner === owner &&
|
||||
currentLock.priority === requestedPriority
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
state.locks.set(channel, {
|
||||
owner,
|
||||
priority: requestedPriority,
|
||||
});
|
||||
notify(state, channel);
|
||||
return true;
|
||||
},
|
||||
release(channel, owner) {
|
||||
const currentLock = state.locks.get(channel);
|
||||
if (currentLock?.owner !== owner) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.locks.delete(channel);
|
||||
notify(state, channel);
|
||||
},
|
||||
isLocked(channel) {
|
||||
return state.locks.has(channel);
|
||||
},
|
||||
getLock(channel) {
|
||||
return state.locks.get(channel) ?? null;
|
||||
},
|
||||
subscribe(listener) {
|
||||
state.listeners.add(listener);
|
||||
return () => {
|
||||
state.listeners.delete(listener);
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
window.__audacityUiSemaphore = semaphore;
|
||||
return semaphore;
|
||||
};
|
||||
4
test-results/.last-run.json
Normal file
4
test-results/.last-run.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "passed",
|
||||
"failedTests": []
|
||||
}
|
||||
Reference in New Issue
Block a user