Refactor download animation with improved code, animations and styles
@@ -80,13 +80,7 @@
|
|||||||
content/browser/zen-images/grain-bg.png (../../zen/images/grain-bg.png)
|
content/browser/zen-images/grain-bg.png (../../zen/images/grain-bg.png)
|
||||||
content/browser/zen-images/note-indicator.svg (../../zen/images/note-indicator.svg)
|
content/browser/zen-images/note-indicator.svg (../../zen/images/note-indicator.svg)
|
||||||
|
|
||||||
content/browser/zen-images/downloads-icons/application.svg (../../zen/images/downloads-icons/application.svg)
|
content/browser/zen-images/downloads/download.svg (../../zen/images/downloads/download.svg)
|
||||||
content/browser/zen-images/downloads-icons/archive.svg (../../zen/images/downloads-icons/archive.svg)
|
|
||||||
content/browser/zen-images/downloads-icons/audio.svg (../../zen/images/downloads-icons/audio.svg)
|
|
||||||
content/browser/zen-images/downloads-icons/document.svg (../../zen/images/downloads-icons/document.svg)
|
|
||||||
content/browser/zen-images/downloads-icons/download.svg (../../zen/images/downloads-icons/download.svg)
|
|
||||||
content/browser/zen-images/downloads-icons/image.svg (../../zen/images/downloads-icons/image.svg)
|
|
||||||
content/browser/zen-images/downloads-icons/video.svg (../../zen/images/downloads-icons/video.svg)
|
|
||||||
|
|
||||||
# Fonts
|
# Fonts
|
||||||
content/browser/zen-fonts/JunicodeVF-Italic.woff2 (../../zen/fonts/JunicodeVF-Italic.woff2)
|
content/browser/zen-fonts/JunicodeVF-Italic.woff2 (../../zen/fonts/JunicodeVF-Italic.woff2)
|
||||||
|
@@ -3,128 +3,87 @@ var { Downloads } = ChromeUtils.importESModule('resource://gre/modules/Downloads
|
|||||||
{
|
{
|
||||||
const CONFIG = Object.freeze({
|
const CONFIG = Object.freeze({
|
||||||
ANIMATION: {
|
ANIMATION: {
|
||||||
APPEAR_DURATION: 400,
|
|
||||||
FADE_DURATION: 300,
|
FADE_DURATION: 300,
|
||||||
ARC_STEPS: 30,
|
ARC_STEPS: 60,
|
||||||
DISTANCE_MULTIPLIER: 2, // Animation duration = distance * multiplier
|
DURATION: 1500,
|
||||||
MAX_ARC_HEIGHT: 200,
|
MAX_ARC_HEIGHT: 500,
|
||||||
ARC_HEIGHT_RATIO: 0.8, // Arc height = distance * ratio (capped at MAX_ARC_HEIGHT)
|
ARC_HEIGHT_RATIO: 0.8, // Arc height = distance * ratio (capped at MAX_ARC_HEIGHT)
|
||||||
SCALE_END: 0.5, // Final scale at destination
|
SCALE_END: 0.5, // Final scale at destination
|
||||||
},
|
},
|
||||||
FILE_TYPES: {
|
|
||||||
document: ['pdf', 'doc', 'docx', 'txt', 'rtf', 'odt', 'xls', 'xlsx', 'ppt', 'pptx'],
|
|
||||||
image: ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp', 'tiff', 'ico'],
|
|
||||||
audio: ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a', 'wma'],
|
|
||||||
video: ['mp4', 'webm', 'avi', 'mkv', 'mov', 'wmv', 'flv', 'm4v'],
|
|
||||||
archive: ['zip', 'rar', 'tar', 'gz', '7z', 'bz2', 'xz'],
|
|
||||||
application: ['exe', 'dmg', 'pkg', 'apk', 'msi', 'deb', 'rpm'],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
class ZenAnimationController {
|
class ZenDownloadAnimation extends ZenDOMOperatedFeature {
|
||||||
constructor() {
|
async init() {
|
||||||
this._lastClickPosition = null;
|
this._lastClickPosition = null;
|
||||||
this._lastClickTime = 0;
|
this._lastClickTime = 0;
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
this._ensureAnimationComponent();
|
|
||||||
this._setupClickListener();
|
this._setupClickListener();
|
||||||
|
await this._setupDownloadListeners();
|
||||||
console.log('Animation controller initialized');
|
|
||||||
}
|
|
||||||
|
|
||||||
_ensureAnimationComponent() {
|
|
||||||
if (!document.body) {
|
|
||||||
// Document not ready, try again later
|
|
||||||
setTimeout(() => this._ensureAnimationComponent(), 100);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!document.querySelector('zen-download-animation')) {
|
|
||||||
const downloadAnimation = document.createElement('zen-download-animation');
|
|
||||||
document.body.appendChild(downloadAnimation);
|
|
||||||
console.log('Download animation component added to document body');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setupClickListener() {
|
_setupClickListener() {
|
||||||
const handleClick = (event) => {
|
document.addEventListener(
|
||||||
|
'mousedown',
|
||||||
|
(event) => {
|
||||||
this._lastClickPosition = {
|
this._lastClickPosition = {
|
||||||
clientX: event.clientX,
|
clientX: event.clientX,
|
||||||
clientY: event.clientY,
|
clientY: event.clientY,
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// Add regular click listener
|
|
||||||
document.addEventListener('click', handleClick, true);
|
|
||||||
|
|
||||||
// Add right-click (contextmenu) listener
|
|
||||||
document.addEventListener('contextmenu', handleClick, true);
|
|
||||||
|
|
||||||
// Track mousedown events for more reliable position capture
|
|
||||||
document.addEventListener(
|
|
||||||
'mousedown',
|
|
||||||
(event) => {
|
|
||||||
// Only track right mouse button (button 2)
|
|
||||||
if (event.button === 2) {
|
|
||||||
handleClick(event);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('Global click and contextmenu listeners registered');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getLastClickPosition() {
|
_getLastClickPosition() {
|
||||||
if (this._lastClickPosition) {
|
if (this._lastClickPosition) {
|
||||||
return this._lastClickPosition;
|
return this._lastClickPosition;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileTypeFromPath(pathname) {
|
async _setupDownloadListeners() {
|
||||||
if (!pathname) return 'generic';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const extension = pathname.split('.').pop().toLowerCase();
|
const list = await Downloads.getList(Downloads.ALL);
|
||||||
|
list.addView({
|
||||||
// Check each file type category
|
onDownloadAdded: () => {
|
||||||
for (const [type, extensions] of Object.entries(CONFIG.FILE_TYPES)) {
|
this._handleNewDownload();
|
||||||
if (extensions.includes(extension)) {
|
},
|
||||||
return type;
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Error parsing URL for file type:', error);
|
console.error('Failed to set up download listeners:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'generic';
|
_animateDownload(startPosition) {
|
||||||
}
|
|
||||||
|
|
||||||
animateDownload(startPosition, fileType) {
|
|
||||||
this._triggerAnimation(startPosition, fileType);
|
|
||||||
}
|
|
||||||
|
|
||||||
_triggerAnimation(startPosition, fileType) {
|
|
||||||
const animationElement = document.querySelector('zen-download-animation');
|
const animationElement = document.querySelector('zen-download-animation');
|
||||||
if (animationElement) {
|
if (animationElement) {
|
||||||
animationElement.initializeAnimation(startPosition, fileType);
|
animationElement.initializeAnimation(startPosition);
|
||||||
} else {
|
} else {
|
||||||
console.error('Animation component not found in the DOM');
|
if (!document.querySelector('zen-download-animation')) {
|
||||||
this._ensureAnimationComponent();
|
const downloadAnimation = document.createElement('zen-download-animation');
|
||||||
|
document.body.appendChild(downloadAnimation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleNewDownload() {
|
||||||
|
const clickPosition = this._getLastClickPosition();
|
||||||
|
|
||||||
|
if (!clickPosition) {
|
||||||
|
console.warn('No recent click position available for animation');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._animateDownload(clickPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ZenDownloadAnimationElement extends HTMLElement {
|
class ZenDownloadAnimationElement extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.attachShadow({ mode: 'open' });
|
this.attachShadow({ mode: 'open' });
|
||||||
this._createStyles();
|
this._createStyles();
|
||||||
}
|
}
|
||||||
|
|
||||||
_createStyles() {
|
_createStyles() {
|
||||||
const style = document.createElement('style');
|
const style = document.createElement('style');
|
||||||
style.textContent = `
|
style.textContent = `
|
||||||
@@ -141,76 +100,81 @@ var { Downloads } = ChromeUtils.importESModule('resource://gre/modules/Downloads
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
background-size: contain;
|
border-radius: 50%;
|
||||||
background-repeat: no-repeat;
|
display: flex;
|
||||||
background-position: center;
|
align-items: center;
|
||||||
opacity: 0;
|
justify-content: center;
|
||||||
transform: translate(-50%, -50%);
|
padding: 4px;
|
||||||
will-change: transform, opacity;
|
background-color: var(--zen-primary-color);
|
||||||
}
|
}
|
||||||
.document {
|
.download-animation-inner-circle {
|
||||||
background-image: url("chrome://browser/content/zen-images/downloads-icons/document.svg");
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: black;
|
||||||
}
|
}
|
||||||
.image {
|
.download-animation-icon {
|
||||||
background-image: url("chrome://browser/content/zen-images/downloads-icons/image.svg");
|
position: absolute;
|
||||||
}
|
top: 0;
|
||||||
.audio {
|
left: 0;
|
||||||
background-image: url("chrome://browser/content/zen-images/downloads-icons/audio.svg");
|
width: 100%;
|
||||||
}
|
height: 100%;
|
||||||
.video {
|
background-color: var(--zen-primary-color);
|
||||||
background-image: url("chrome://browser/content/zen-images/downloads-icons/video.svg");
|
-webkit-mask: url("chrome://browser/content/zen-images/downloads/download.svg") no-repeat center;
|
||||||
}
|
-webkit-mask-size: 70%;
|
||||||
.archive {
|
mask: url("chrome://browser/content/zen-images/downloads/download.svg") no-repeat center;
|
||||||
background-image: url("chrome://browser/content/zen-images/downloads-icons/archive.svg");
|
mask-size: 70%;
|
||||||
}
|
|
||||||
.application {
|
|
||||||
background-image: url("chrome://browser/content/zen-images/downloads-icons/application.svg");
|
|
||||||
}
|
|
||||||
.generic {
|
|
||||||
background-image: url("chrome://browser/content/zen-images/downloads-icons/download.svg");
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
this.shadowRoot.appendChild(style);
|
this.shadowRoot.appendChild(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeAnimation(startPosition, fileType) {
|
_createAnimationElement(startPosition) {
|
||||||
|
const animationElement = document.createElement('div');
|
||||||
|
animationElement.className = 'download-animation';
|
||||||
|
animationElement.style.position = 'absolute';
|
||||||
|
animationElement.style.left = `${startPosition.clientX}px`;
|
||||||
|
animationElement.style.top = `${startPosition.clientY}px`;
|
||||||
|
animationElement.style.transform = 'translate(-50%, -50%)';
|
||||||
|
|
||||||
|
const innerCircle = document.createElement('div');
|
||||||
|
innerCircle.className = 'download-animation-inner-circle';
|
||||||
|
|
||||||
|
const icon = document.createElement('div');
|
||||||
|
icon.className = 'download-animation-icon';
|
||||||
|
|
||||||
|
innerCircle.appendChild(icon);
|
||||||
|
animationElement.appendChild(innerCircle);
|
||||||
|
this.shadowRoot.appendChild(animationElement);
|
||||||
|
return animationElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeAnimation(startPosition) {
|
||||||
if (!startPosition) {
|
if (!startPosition) {
|
||||||
console.log('No start position provided, skipping animation');
|
console.warn('No start position provided, skipping animation');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the download button
|
|
||||||
const downloadsButton = document.getElementById('downloads-button');
|
const downloadsButton = document.getElementById('downloads-button');
|
||||||
|
|
||||||
if (!downloadsButton) {
|
if (!downloadsButton) {
|
||||||
console.warn('Downloads button not found, skipping animation');
|
console.warn('Downloads button not found, skipping animation');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate end position (center of downloads button)
|
|
||||||
const buttonRect = downloadsButton.getBoundingClientRect();
|
const buttonRect = downloadsButton.getBoundingClientRect();
|
||||||
const endPosition = {
|
const endPosition = {
|
||||||
clientX: buttonRect.left + buttonRect.width / 2,
|
clientX: buttonRect.left + buttonRect.width / 2,
|
||||||
clientY: buttonRect.top + buttonRect.height / 2,
|
clientY: buttonRect.top + buttonRect.height / 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const animationElement = this._createAnimationElement(startPosition, fileType);
|
const animationElement = this._createAnimationElement(startPosition);
|
||||||
|
|
||||||
const distance = this._calculateDistance(startPosition, endPosition);
|
const distance = this._calculateDistance(startPosition, endPosition);
|
||||||
const arcHeight = Math.min(distance * CONFIG.ANIMATION.ARC_HEIGHT_RATIO, CONFIG.ANIMATION.MAX_ARC_HEIGHT);
|
const arcHeight = Math.min(distance * CONFIG.ANIMATION.ARC_HEIGHT_RATIO, CONFIG.ANIMATION.MAX_ARC_HEIGHT);
|
||||||
|
|
||||||
this._runAnimationSequence(animationElement, startPosition, endPosition, distance, arcHeight, downloadsButton);
|
this._runAnimationSequence(animationElement, startPosition, endPosition, arcHeight);
|
||||||
}
|
|
||||||
|
|
||||||
_createAnimationElement(startPosition, fileType) {
|
|
||||||
const animationElement = document.createElement('div');
|
|
||||||
animationElement.className = `download-animation ${fileType || 'generic'}`;
|
|
||||||
animationElement.style.position = 'absolute';
|
|
||||||
animationElement.style.left = `${startPosition.clientX}px`;
|
|
||||||
animationElement.style.top = `${startPosition.clientY}px`;
|
|
||||||
animationElement.style.opacity = '0';
|
|
||||||
animationElement.style.transform = 'translate(-50%, -50%)';
|
|
||||||
this.shadowRoot.appendChild(animationElement);
|
|
||||||
return animationElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_calculateDistance(start, end) {
|
_calculateDistance(start, end) {
|
||||||
@@ -219,88 +183,92 @@ var { Downloads } = ChromeUtils.importESModule('resource://gre/modules/Downloads
|
|||||||
return Math.sqrt(distanceX * distanceX + distanceY * distanceY);
|
return Math.sqrt(distanceX * distanceX + distanceY * distanceY);
|
||||||
}
|
}
|
||||||
|
|
||||||
_runAnimationSequence(element, start, end, distance, arcHeight, downloadsButton) {
|
_runAnimationSequence(element, start, end, arcHeight) {
|
||||||
try {
|
try {
|
||||||
const distanceX = end.clientX - start.clientX;
|
const distanceX = end.clientX - start.clientX;
|
||||||
const distanceY = end.clientY - start.clientY;
|
const distanceY = end.clientY - start.clientY;
|
||||||
|
|
||||||
this._runAnimation(element, distanceX, distanceY, distance, arcHeight, downloadsButton);
|
this._runAnimation(element, distanceX, distanceY, arcHeight);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in animation sequence:', error);
|
console.error('Error in animation sequence:', error);
|
||||||
this._cleanupAnimation(element);
|
this._cleanupAnimation(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_runAnimation(element, distanceX, distanceY, distance, arcHeight, downloadsButton) {
|
_runAnimation(element, distanceX, distanceY, arcHeight) {
|
||||||
// Appear with a pop effect
|
this._createArcAnimation(element, distanceX, distanceY, arcHeight).onfinish = () => {
|
||||||
gZenUIManager.motion.animate(
|
|
||||||
element,
|
|
||||||
{
|
|
||||||
opacity: [0, 1],
|
|
||||||
scale: [0.5, 1.2, 1],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
duration: CONFIG.ANIMATION.APPEAR_DURATION / 1000, // Convert to seconds for motion module
|
|
||||||
ease: [0.34, 1.56, 0.64, 1], // Spring-like overshoot
|
|
||||||
onComplete: () => {
|
|
||||||
// Create the arc trajectory animation
|
|
||||||
this._createArcAnimation(element, distanceX, distanceY, distance, arcHeight).onfinish = () => {
|
|
||||||
// Add feedback to the downloads button
|
|
||||||
this._animateButtonFeedback(downloadsButton);
|
|
||||||
|
|
||||||
// Fade out the animation element
|
|
||||||
this._fadeOutAnimation(element);
|
this._fadeOutAnimation(element);
|
||||||
};
|
};
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_createArcAnimation(element, distanceX, distanceY, distance, arcHeight) {
|
_createArcAnimation(element, distanceX, distanceY, arcHeight) {
|
||||||
const keyframes = [];
|
const keyframes = [];
|
||||||
const steps = CONFIG.ANIMATION.ARC_STEPS;
|
const steps = CONFIG.ANIMATION.ARC_STEPS;
|
||||||
|
const endScale = CONFIG.ANIMATION.SCALE_END;
|
||||||
|
|
||||||
|
const opacityValues = [];
|
||||||
|
const scaleValues = [];
|
||||||
|
|
||||||
for (let i = 0; i <= steps; i++) {
|
for (let i = 0; i <= steps; i++) {
|
||||||
const progress = i / steps;
|
const progress = i / steps;
|
||||||
|
|
||||||
// Calculate horizontal position (linear)
|
let opacity;
|
||||||
|
if (progress < 0.3) {
|
||||||
|
opacity = 0.3 + (progress / 0.3) * 0.6;
|
||||||
|
} else if (progress < 0.5) {
|
||||||
|
opacity = 0.9 + ((progress - 0.3) / 0.2) * 0.1;
|
||||||
|
} else {
|
||||||
|
opacity = 1;
|
||||||
|
}
|
||||||
|
opacityValues.push(opacity);
|
||||||
|
|
||||||
|
let scale;
|
||||||
|
if (progress < 0.3) {
|
||||||
|
scale = 0.5 + (progress / 0.3) * 0.5;
|
||||||
|
} else if (progress < 0.5) {
|
||||||
|
scale = 1 + ((progress - 0.3) / 0.2) * 0.05;
|
||||||
|
} else {
|
||||||
|
scale = 1.05 - ((progress - 0.5) / 0.5) * (1.05 - endScale);
|
||||||
|
}
|
||||||
|
scaleValues.push(scale);
|
||||||
|
|
||||||
const x = distanceX * progress;
|
const x = distanceX * progress;
|
||||||
|
|
||||||
// Calculate vertical position (parabolic arc)
|
|
||||||
const adjustedProgress = progress * 2 - 1; // -1 to 1
|
const adjustedProgress = progress * 2 - 1; // -1 to 1
|
||||||
const verticalOffset = -arcHeight * (1 - adjustedProgress * adjustedProgress);
|
const verticalOffset = -arcHeight * (1 - adjustedProgress * adjustedProgress);
|
||||||
const y = distanceY * progress + verticalOffset;
|
const y = distanceY * progress + verticalOffset;
|
||||||
|
|
||||||
// Scale down as it reaches the destination
|
let rotation = 0;
|
||||||
let scale = 1 - (1 - CONFIG.ANIMATION.SCALE_END) * progress;
|
let previousRotation = 0;
|
||||||
|
|
||||||
|
if (i > 0 && i < steps) {
|
||||||
|
const prevProgress = (i - 1) / steps;
|
||||||
|
const prevX = distanceX * prevProgress;
|
||||||
|
const prevAdjustedProgress = prevProgress * 2 - 1;
|
||||||
|
const prevVerticalOffset = -arcHeight * (1 - prevAdjustedProgress * prevAdjustedProgress);
|
||||||
|
const prevY = distanceY * prevProgress + prevVerticalOffset;
|
||||||
|
|
||||||
|
const targetRotation = Math.atan2(y - prevY, x - prevX) * (180 / Math.PI);
|
||||||
|
|
||||||
|
rotation = previousRotation + (targetRotation - previousRotation) * 0.1;
|
||||||
|
|
||||||
|
previousRotation = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
keyframes.push({
|
keyframes.push({
|
||||||
offset: progress,
|
offset: progress,
|
||||||
transform: `translate(calc(${x}px - 50%), calc(${y}px - 50%)) rotate(0deg) scale(${scale})`,
|
opacity: opacityValues[i],
|
||||||
|
transform: `translate(calc(${x}px - 50%), calc(${y}px - 50%)) rotate(${rotation}deg) scale(${scaleValues[i]})`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return element.animate(keyframes, {
|
return element.animate(keyframes, {
|
||||||
duration: distance * CONFIG.ANIMATION.DISTANCE_MULTIPLIER,
|
duration: CONFIG.ANIMATION.DURATION,
|
||||||
easing: 'cubic-bezier(0.37, 0, 0.63, 1)',
|
easing: 'cubic-bezier(0.37, 0, 0.63, 1)',
|
||||||
fill: 'forwards',
|
fill: 'forwards',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_animateButtonFeedback(button) {
|
|
||||||
button.animate(
|
|
||||||
[
|
|
||||||
{ boxShadow: '0 0 0 0 rgba(0, 128, 255, 0)', transform: 'scale(1)' },
|
|
||||||
{ boxShadow: '0 0 8px 2px rgba(0, 128, 255, 0.5)', transform: 'scale(1.08)' },
|
|
||||||
{ boxShadow: '0 0 0 0 rgba(0, 128, 255, 0)', transform: 'scale(1)' },
|
|
||||||
],
|
|
||||||
{
|
|
||||||
duration: 500,
|
|
||||||
easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_fadeOutAnimation(element) {
|
_fadeOutAnimation(element) {
|
||||||
element.animate([{ opacity: 1 }, { opacity: 0 }], {
|
element.animate([{ opacity: 1 }, { opacity: 0 }], {
|
||||||
duration: CONFIG.ANIMATION.FADE_DURATION,
|
duration: CONFIG.ANIMATION.FADE_DURATION,
|
||||||
@@ -315,64 +283,6 @@ var { Downloads } = ChromeUtils.importESModule('resource://gre/modules/Downloads
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ZenDownloadAnimation {
|
|
||||||
constructor() {
|
|
||||||
this.animationController = new ZenAnimationController();
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
console.log('Initializing download manager...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Initialize the animation controller
|
|
||||||
await this.animationController.init();
|
|
||||||
|
|
||||||
// Set up download listeners
|
|
||||||
await this._setupDownloadListeners();
|
|
||||||
|
|
||||||
console.log('Download animation initialized successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to initialize download animation:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _setupDownloadListeners() {
|
|
||||||
try {
|
|
||||||
const list = await Downloads.getList(Downloads.ALL);
|
|
||||||
|
|
||||||
list.addView({
|
|
||||||
onDownloadAdded: (download) => {
|
|
||||||
console.log('New download detected:', download);
|
|
||||||
this._handleNewDownload(download);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Download listeners set up successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to set up download listeners:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleNewDownload(download) {
|
|
||||||
// Get the last click position
|
|
||||||
const clickPosition = this.animationController.getLastClickPosition();
|
|
||||||
|
|
||||||
console.log('Download initiated:', download.source.url);
|
|
||||||
|
|
||||||
if (!clickPosition) {
|
|
||||||
console.log('No recent click position available for animation');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the file type from the URL
|
|
||||||
const fileType = this.animationController.getFileTypeFromPath(download.target.path);
|
|
||||||
console.log(`Animating download for ${fileType} file from ${download.source.url}`);
|
|
||||||
|
|
||||||
this.animationController.animateDownload(clickPosition, fileType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define('zen-download-animation', ZenDownloadAnimationElement);
|
customElements.define('zen-download-animation', ZenDownloadAnimationElement);
|
||||||
|
|
||||||
const zenDownloadAnimation = new ZenDownloadAnimation();
|
const zenDownloadAnimation = new ZenDownloadAnimation();
|
||||||
|
@@ -1,7 +0,0 @@
|
|||||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect x="6" y="3" width="20" height="26" rx="2" fill="#FFFFFF" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
<rect x="10" y="9" width="5" height="5" rx="1" fill="#2C7BE5"/>
|
|
||||||
<rect x="17" y="9" width="5" height="5" rx="1" fill="#2C7BE5"/>
|
|
||||||
<rect x="10" y="18" width="5" height="5" rx="1" fill="#2C7BE5"/>
|
|
||||||
<rect x="17" y="18" width="5" height="5" rx="1" fill="#2C7BE5"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 470 B |
@@ -1,9 +0,0 @@
|
|||||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect x="6" y="3" width="20" height="26" rx="2" fill="#FFFFFF" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
<rect x="12" y="3" width="8" height="26" fill="#FFFFFF"/>
|
|
||||||
<path d="M12 7H20" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
<path d="M12 11H20" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
<path d="M12 15H20" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
<path d="M12 19H20" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
<path d="M12 23H20" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 553 B |
@@ -1,7 +0,0 @@
|
|||||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect x="6" y="3" width="20" height="26" rx="2" fill="#FFFFFF" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
<path d="M13 10V22" stroke="#2C7BE5" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
<path d="M19 10V22" stroke="#2C7BE5" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
<path d="M13 12C11.8954 12 11 12.8954 11 14V18C11 19.1046 11.8954 20 13 20" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
<path d="M19 12C20.1046 12 21 12.8954 21 14V18C21 19.1046 20.1046 20 19 20" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 594 B |
@@ -1,6 +0,0 @@
|
|||||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect x="6" y="3" width="20" height="26" rx="2" fill="#FFFFFF" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
<line x1="10" y1="10" x2="22" y2="10" stroke="#2C7BE5" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
<line x1="10" y1="16" x2="22" y2="16" stroke="#2C7BE5" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
<line x1="10" y1="22" x2="18" y2="22" stroke="#2C7BE5" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 501 B |
@@ -1,6 +0,0 @@
|
|||||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect x="6" y="3" width="20" height="26" rx="2" fill="#FFFFFF" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
<path d="M16 10V18" stroke="#2C7BE5" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
<path d="M13 15L16 18L19 15" stroke="#2C7BE5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M11 22H21" stroke="#2C7BE5" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 480 B |
@@ -1,5 +0,0 @@
|
|||||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect x="6" y="3" width="20" height="26" rx="2" fill="#FFFFFF" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
<circle cx="12" cy="12" r="2" fill="#2C7BE5"/>
|
|
||||||
<path d="M6 22L13 16L18 21L23 16V27H9C7.34315 27 6 25.6569 6 24V22Z" fill="#2C7BE5"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 341 B |
@@ -1,5 +0,0 @@
|
|||||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect x="6" y="3" width="20" height="26" rx="2" fill="#FFFFFF" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
<rect x="9" y="9" width="14" height="14" rx="2" stroke="#2C7BE5" stroke-width="2"/>
|
|
||||||
<path d="M17 13.5L14 16L17 18.5V13.5Z" fill="#2C7BE5" stroke="#2C7BE5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 412 B |
6
src/zen/images/downloads/download.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
class="lucide lucide-arrow-down-icon lucide-arrow-down">
|
||||||
|
<path d="M12 5v14" />
|
||||||
|
<path d="m19 12-7 7-7-7" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 305 B |