mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
range function done
Signed-off-by: vagabond-0 <220229@tkmce.ac.in>
This commit is contained in:
parent
36f6b4fbd1
commit
e78d32e458
1 changed files with 162 additions and 37 deletions
|
@ -1,7 +1,3 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
@ -77,6 +73,147 @@
|
||||||
.padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
|
.padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CustomRangeInput {
|
||||||
|
constructor(originalInput) {
|
||||||
|
this.originalInput = originalInput;
|
||||||
|
this.container = document.createElement('div');
|
||||||
|
this.container.className = 'custom-range-container';
|
||||||
|
this.container.style.position = 'relative';
|
||||||
|
this.container.style.height = '20px';
|
||||||
|
this.container.style.width = '150px';
|
||||||
|
|
||||||
|
this.track = document.createElement('div');
|
||||||
|
this.track.className = 'custom-range-track';
|
||||||
|
this.track.style.position = 'absolute';
|
||||||
|
this.track.style.top = '50%';
|
||||||
|
this.track.style.transform = 'translateY(-50%)';
|
||||||
|
this.track.style.width = '100%';
|
||||||
|
this.track.style.height = '4px';
|
||||||
|
this.track.style.backgroundColor = '#d3d3d3';
|
||||||
|
this.track.style.borderRadius = '2px';
|
||||||
|
|
||||||
|
this.progress = document.createElement('div');
|
||||||
|
this.progress.className = 'custom-range-progress';
|
||||||
|
this.progress.style.position = 'absolute';
|
||||||
|
this.progress.style.top = '50%';
|
||||||
|
this.progress.style.transform = 'translateY(-50%)';
|
||||||
|
this.progress.style.width = '0%';
|
||||||
|
this.progress.style.height = '4px';
|
||||||
|
this.progress.style.backgroundColor = '#4c8bf5';
|
||||||
|
this.progress.style.borderRadius = '2px';
|
||||||
|
|
||||||
|
this.thumb = document.createElement('div');
|
||||||
|
this.thumb.className = 'custom-range-thumb';
|
||||||
|
this.thumb.style.position = 'absolute';
|
||||||
|
this.thumb.style.top = '50%';
|
||||||
|
this.thumb.style.transform = 'translate(-50%, -50%)';
|
||||||
|
this.thumb.style.width = '16px';
|
||||||
|
this.thumb.style.height = '16px';
|
||||||
|
this.thumb.style.backgroundColor = '#4c8bf5';
|
||||||
|
this.thumb.style.borderRadius = '50%';
|
||||||
|
this.thumb.style.cursor = 'pointer';
|
||||||
|
this.thumb.style.zIndex = '1';
|
||||||
|
|
||||||
|
this.originalInput.style.display = 'none';
|
||||||
|
|
||||||
|
// Assemble component
|
||||||
|
this.container.appendChild(this.track);
|
||||||
|
this.container.appendChild(this.progress);
|
||||||
|
this.container.appendChild(this.thumb);
|
||||||
|
this.originalInput.parentNode.insertBefore(this.container, this.originalInput.nextSibling);
|
||||||
|
|
||||||
|
this.updateThumbPosition();
|
||||||
|
|
||||||
|
// Bind event handlers
|
||||||
|
this.bindEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateThumbPosition() {
|
||||||
|
const min = parseFloat(this.originalInput.min) || 0;
|
||||||
|
const max = parseFloat(this.originalInput.max) || 100;
|
||||||
|
const value = parseFloat(this.originalInput.value) || min;
|
||||||
|
|
||||||
|
// Calculate percentage
|
||||||
|
const percentage = ((value - min) / (max - min)) * 100;
|
||||||
|
|
||||||
|
// Update thumb and progress position
|
||||||
|
this.thumb.style.left = `${percentage}%`;
|
||||||
|
this.progress.style.width = `${percentage}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(clientX) {
|
||||||
|
const rect = this.track.getBoundingClientRect();
|
||||||
|
const min = parseFloat(this.originalInput.min) || 0;
|
||||||
|
const max = parseFloat(this.originalInput.max) || 100;
|
||||||
|
const step = parseFloat(this.originalInput.step) || 1;
|
||||||
|
|
||||||
|
// Calculate percentage of position within track
|
||||||
|
let percentage = (clientX - rect.left) / rect.width;
|
||||||
|
|
||||||
|
// Clamp percentage to 0-1 range
|
||||||
|
percentage = Math.max(0, Math.min(1, percentage));
|
||||||
|
|
||||||
|
// Calculate value based on percentage
|
||||||
|
let value = min + percentage * (max - min);
|
||||||
|
|
||||||
|
// Apply step if specified
|
||||||
|
if (step > 0) {
|
||||||
|
value = Math.round(value / step) * step;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure value is within min/max bounds
|
||||||
|
value = Math.max(min, Math.min(max, value));
|
||||||
|
|
||||||
|
// Update original input value
|
||||||
|
this.originalInput.value = value;
|
||||||
|
|
||||||
|
// Dispatch input and change events
|
||||||
|
const inputEvent = new Event('input', { bubbles: true });
|
||||||
|
const changeEvent = new Event('change', { bubbles: true });
|
||||||
|
this.originalInput.dispatchEvent(inputEvent);
|
||||||
|
this.originalInput.dispatchEvent(changeEvent);
|
||||||
|
|
||||||
|
// Update thumb position
|
||||||
|
this.updateThumbPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
// Mouse events
|
||||||
|
this.container.addEventListener('mousedown', (e) => {
|
||||||
|
this.isDragging = true;
|
||||||
|
this.setValue(e.clientX);
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', (e) => {
|
||||||
|
if (this.isDragging) {
|
||||||
|
this.setValue(e.clientX);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', () => {
|
||||||
|
this.isDragging = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Touch events
|
||||||
|
this.container.addEventListener('touchstart', (e) => {
|
||||||
|
this.isDragging = true;
|
||||||
|
this.setValue(e.touches[0].clientX);
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('touchmove', (e) => {
|
||||||
|
if (this.isDragging) {
|
||||||
|
this.setValue(e.touches[0].clientX);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('touchend', () => {
|
||||||
|
this.isDragging = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class MediaControls {
|
class MediaControls {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.nonce = Date.now();
|
this.nonce = Date.now();
|
||||||
|
@ -101,7 +238,6 @@
|
||||||
this.root.innerHTML = generateMarkup(this.isAudioOnly);
|
this.root.innerHTML = generateMarkup(this.isAudioOnly);
|
||||||
this.controls.appendChild(this.root);
|
this.controls.appendChild(this.root);
|
||||||
|
|
||||||
|
|
||||||
const elementNames = [
|
const elementNames = [
|
||||||
"duration",
|
"duration",
|
||||||
"play-pause-button",
|
"play-pause-button",
|
||||||
|
@ -122,6 +258,10 @@
|
||||||
this.elements[camelCase(id)] = this.controls.getElementById(id);
|
this.elements[camelCase(id)] = this.controls.getElementById(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Replace standard range inputs with custom ones
|
||||||
|
this.customProgress = new CustomRangeInput(this.elements.progress);
|
||||||
|
this.customVolume = new CustomRangeInput(this.elements.volumeLevel);
|
||||||
|
|
||||||
// Init position duration box.
|
// Init position duration box.
|
||||||
const positionTextNode = this.elements.positionText;
|
const positionTextNode = this.elements.positionText;
|
||||||
const durationSpan = this.elements.duration;
|
const durationSpan = this.elements.duration;
|
||||||
|
@ -205,10 +345,6 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create state transitions.
|
// Create state transitions.
|
||||||
//
|
|
||||||
// It exposes one method per transition. i.e. this.pause(), this.play(), etc.
|
|
||||||
// For each transition, we check that the transition is possible and call
|
|
||||||
// the `onStateChange` handler.
|
|
||||||
for (let name in TRANSITIONS) {
|
for (let name in TRANSITIONS) {
|
||||||
if (!TRANSITIONS.hasOwnProperty(name)) {
|
if (!TRANSITIONS.hasOwnProperty(name)) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -216,7 +352,6 @@
|
||||||
this[name] = () => {
|
this[name] = () => {
|
||||||
const from = this.state;
|
const from = this.state;
|
||||||
|
|
||||||
// Checks if the transition is valid in the current state.
|
|
||||||
if (!TRANSITIONS[name][from]) {
|
if (!TRANSITIONS[name][from]) {
|
||||||
const error = `Transition "${name}" invalid for the current state "${from}"`;
|
const error = `Transition "${name}" invalid for the current state "${from}"`;
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -229,7 +364,6 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transition to the next state.
|
|
||||||
this.state = to;
|
this.state = to;
|
||||||
this.onStateChange(from);
|
this.onStateChange(from);
|
||||||
};
|
};
|
||||||
|
@ -250,28 +384,21 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// State change handler
|
|
||||||
onStateChange(from) {
|
onStateChange(from) {
|
||||||
this.render(from);
|
this.render(from);
|
||||||
}
|
}
|
||||||
|
|
||||||
render(from = this.state) {
|
render(from = this.state) {
|
||||||
if (!this.isAudioOnly) {
|
if (!this.isAudioOnly) {
|
||||||
// XXX This should ideally use clientHeight/clientWidth,
|
|
||||||
// but for some reason I couldn't figure out yet,
|
|
||||||
// using it breaks layout.
|
|
||||||
this.root.style.height = this.media.videoHeight;
|
this.root.style.height = this.media.videoHeight;
|
||||||
this.root.style.width = this.media.videoWidth;
|
this.root.style.width = this.media.videoWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error
|
|
||||||
if (this.state == ERRORED) {
|
if (this.state == ERRORED) {
|
||||||
//XXX render errored state
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state != from) {
|
if (this.state != from) {
|
||||||
// Play/Pause button.
|
|
||||||
const playPauseButton = this.elements.playPauseButton;
|
const playPauseButton = this.elements.playPauseButton;
|
||||||
playPauseButton.classList.remove(from);
|
playPauseButton.classList.remove(from);
|
||||||
playPauseButton.classList.add(this.state);
|
playPauseButton.classList.add(this.state);
|
||||||
|
@ -282,8 +409,10 @@
|
||||||
(this.media.currentTime / this.media.duration) * 100;
|
(this.media.currentTime / this.media.duration) * 100;
|
||||||
if (Number.isFinite(positionPercent)) {
|
if (Number.isFinite(positionPercent)) {
|
||||||
this.elements.progress.value = positionPercent;
|
this.elements.progress.value = positionPercent;
|
||||||
|
this.customProgress.updateThumbPosition();
|
||||||
} else {
|
} else {
|
||||||
this.elements.progress.value = 0;
|
this.elements.progress.value = 0;
|
||||||
|
this.customProgress.updateThumbPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current time and duration.
|
// Current time and duration.
|
||||||
|
@ -303,6 +432,7 @@
|
||||||
: Math.round(this.media.volume * 100);
|
: Math.round(this.media.volume * 100);
|
||||||
if (this.elements.volumeLevel.value != volumeLevelValue) {
|
if (this.elements.volumeLevel.value != volumeLevelValue) {
|
||||||
this.elements.volumeLevel.value = volumeLevelValue;
|
this.elements.volumeLevel.value = volumeLevelValue;
|
||||||
|
this.customVolume.updateThumbPosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,8 +460,8 @@
|
||||||
this.toggleMuted();
|
this.toggleMuted();
|
||||||
break;
|
break;
|
||||||
case this.elements.fullscreenSwitch:
|
case this.elements.fullscreenSwitch:
|
||||||
this.toggleFullscreen();
|
this.toggleFullscreen();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "input":
|
case "input":
|
||||||
|
@ -346,7 +476,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTMLMediaElement event handler
|
|
||||||
onMediaEvent(event) {
|
onMediaEvent(event) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "ended":
|
case "ended":
|
||||||
|
@ -354,7 +483,6 @@
|
||||||
break;
|
break;
|
||||||
case "play":
|
case "play":
|
||||||
case "pause":
|
case "pause":
|
||||||
// Transition to PLAYING or PAUSED state.
|
|
||||||
this[event.type]();
|
this[event.type]();
|
||||||
break;
|
break;
|
||||||
case "volumechange":
|
case "volumechange":
|
||||||
|
@ -368,7 +496,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Media actions */
|
/* Media actions */
|
||||||
|
|
||||||
playOrPause() {
|
playOrPause() {
|
||||||
switch (this.state) {
|
switch (this.state) {
|
||||||
case PLAYING:
|
case PLAYING:
|
||||||
|
@ -389,19 +516,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFullscreen() {
|
toggleFullscreen() {
|
||||||
const { fullscreenEnabled, fullscreenElement } = document;
|
const { fullscreenEnabled, fullscreenElement } = document;
|
||||||
|
const isElementFullscreen = fullscreenElement && fullscreenElement === this.media;
|
||||||
|
|
||||||
const isElementFullscreen = fullscreenElement && fullscreenElement === this.media;
|
if (fullscreenEnabled && isElementFullscreen) {
|
||||||
|
document.exitFullscreen().then(() => {
|
||||||
if (fullscreenEnabled && isElementFullscreen) {
|
this.elements.fullscreenSwitch.classList.remove("fullscreen-active");
|
||||||
document.exitFullscreen().then(() => {
|
});
|
||||||
this.elements.fullscreenSwitch.classList.remove("fullscreen-active");
|
} else {
|
||||||
});
|
this.media.requestFullscreen().then(() => {
|
||||||
} else {
|
this.elements.fullscreenSwitch.classList.add("fullscreen-active");
|
||||||
this.media.requestFullscreen().then(() => {
|
});
|
||||||
this.elements.fullscreenSwitch.classList.add("fullscreen-active");
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changeVolume() {
|
changeVolume() {
|
||||||
|
@ -413,5 +539,4 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
new MediaControls();
|
new MediaControls();
|
||||||
})();
|
})();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue