This commit is contained in:
nnduc
2026-10-24 14:03:52 +07:00
commit 4fe046de58
4790 changed files with 311146 additions and 0 deletions
@@ -0,0 +1,9 @@
[story]
new = New story
new_edit = Edit your story
new_publish = Publish your story
to_roster = Publish to my contacts
uploading = Uploading the story
publishing = Publishing the story
publishing_text = Almost there…
published = Story published
@@ -0,0 +1,180 @@
#publishstories {
top: 0;
left: 0;
background-color: rgb(var(--movim-background-main));
width: 100%;
height: 100%;
z-index: 2;
position: absolute;
transform: translateX(-100%);
}
#publishstories #publishstoriesswitch,
#publishstories #publishstoriesgallery {
width: 5.5rem;
height: 5.5rem;
line-height: 5.5rem;
margin-top: 0.75rem;
font-size: 2.5rem;
}
#publishstories.show,
#publishstories.shoot,
#publishstories.edit {
transform: translateX(0);
transition: transform 0.4s var(--decelerate-easing);
}
#publishstories #publishstoriesbackedit,
#publishstories #publishstoriesback {
margin-left: 0;
padding: 1rem 0;
}
#publishstories #publishstoriesshoot,
#publishstories #publishstoriesgallery,
#publishstories #publishstoriesswitch,
#publishstories #publishstoriessource,
#publishstories #publishstoriesnext,
#publishstories #publishactions,
#publishstories #publishstoriesbackedit,
#publishstories #publishstoriespreview,
#publishstories.edit.publish #publishstoriesback,
#publishstories.edit.publish #publishstoriesnext,
#publishstories.shoot #publishstoriesheadedit,
#publishstories.edit #publishstorieshead,
#publishstories.edit.publish #publishstorieshead,
#publishstories.edit.publish #publishstoriesheadedit,
#publishstories.shoot.error video,
#publishstories form {
display: none;
}
#publishstories.shoot.error:before {
font-family: 'Material Symbols';
content: '\e04c';
color: var(--movim-font);
font-size: 7rem;
opacity: 0.5;
display: block;
position: absolute;
left: calc(50% - 3.5rem);
top: calc(50% - 3.5rem);
}
#publishstories.shoot:not(.error) #publishstoriesshoot,
#publishstories.shoot #publishstoriesgallery,
#publishstories.shoot:not(.error) #publishstoriesswitch.enabled,
#publishstories.edit form,
#publishstories.edit #publishstoriesnext,
#publishstories.edit.publish #publishactions,
#publishstories.edit.publish #publishstoriespreview,
#publishstories.edit.publish #publishstoriesbackedit {
display: initial;
}
#publishstories input[type=file] {
display: none;
}
#publishstories #publishactions,
#publishstories form {
position: absolute;
width: 100%;
bottom: 3rem;
left: 50%;
translate: -50%;
max-width: calc(56.25vh - 8.25rem);
/* 16:9 */
}
#publishstories form {
background-image: linear-gradient(to bottom, transparent, rgba(var(--movim-background-main), 1) 100%);
padding: 1.5rem 2rem;
box-sizing: border-box;
bottom: 7.25rem;
border-radius: 2rem;
animation: fadeIn 0.5s;
transition: bottom 0.3s, max-width 0.3s;
}
#publishstories canvas,
#publishstories video {
aspect-ratio: 9 / 16;
height: 100%;
position: absolute;
left: 50%;
top: 50%;
translate: -50% -50%;
animation: zoom 0.3s;
transition: top 0.3s, height 0.3s;
}
#publishstories .bottom_center {
width: 100%;
position: absolute;
bottom: 2rem;
display: flex;
justify-content: center;
}
#publishstories .bottom_center>button {
position: relative;
margin-left: 1rem;
margin-right: 1rem;
right: 0;
}
#publishstories video {
background-image: url(../theme/img/cloud_spinner.svg);
background-repeat: no-repeat;
background-position: center center;
background-size: 9rem;
object-fit: cover;
}
#publishstories.shoot canvas,
#publishstories.edit video {
display: none;
}
#publishstories.edit canvas {
animation: zoomOut 0.3s;
border-radius: 2rem;
height: calc(100% - 15rem);
box-shadow: var(--elevation-5);
}
#publishstories #publishactions {
animation: fadeIn 0.5s;
max-height: 7rem;
overflow: hidden;
}
#publishstories #publishactions ul {
transition: transform 0.3s;
}
#publishstories #publishactions.uploading ul {
transform: translateY(-7rem);
}
#publishstories #publishactions.publishing ul {
transform: translateY(-14rem);
}
#publishstories.edit.publish canvas {
top: calc(50% - 2rem);
height: calc(100% - 21rem);
pointer-events: none;
}
#publishstories.edit.publish form {
bottom: 12.5rem;
max-width: calc(56.25vh - 11.5rem);
}
#publishstories.edit.publish form textarea {
pointer-events: none;
box-shadow: none;
}
@@ -0,0 +1,401 @@
MAX_ZOOM = 5;
MIN_ZOOM = 0.1;
SCROLL_SENSITIVITY = 0.0005;
var PublishStories = {
main: undefined,
back: undefined,
video: undefined,
videoSelect: undefined,
canvas: undefined,
imageCanvas: undefined,
canvasMinWidth: 1080,
canvasMinHeight: 1920,
cameraOffset: { x: 0, y: 0 },
cameraZoom: 1,
isDragging: false,
dragStart: { x: 0, y: 0 },
initialPinchDistance: null,
lastZoom: 1,
init: function () {
PublishStories_ajaxGetContactsCount();
MovimTpl.loadingPage();
MovimTpl.pushAnchorState('story', function () {
document.querySelector('#publishstories').classList = '';
PublishStories.close();
});
PublishStories.main = document.querySelector('#publishstories');
PublishStories.back = document.querySelector("#publishstories #publishstoriesback");
PublishStories.video = document.querySelector("#publishstories video");
PublishStories.videoSelect = document.querySelector("#publishstories select#publishstoriessource");
PublishStories.canvas = document.querySelector('#publishstories canvas');
PublishStories.main.addEventListener('mousemove', PublishStories.onMainPointerMove);
PublishStories.canvas.addEventListener('mousedown', PublishStories.onPointerDown);
PublishStories.canvas.addEventListener('touchstart', (e) => PublishStories.handleTouch(e, PublishStories.onPointerDown));
PublishStories.canvas.addEventListener('mouseup', PublishStories.onPointerUp);
PublishStories.canvas.addEventListener('touchend', (e) => PublishStories.handleTouch(e, PublishStories.onPointerUp));
PublishStories.canvas.addEventListener('mousemove', PublishStories.onPointerMove);
PublishStories.canvas.addEventListener('touchmove', (e) => PublishStories.handleTouch(e, PublishStories.onPointerMove));
PublishStories.canvas.addEventListener('wheel', (e) => PublishStories.adjustZoom(e.deltaY * SCROLL_SENSITIVITY));
PublishStories.main.querySelector('#publishstoriesswitch').onclick = () => {
PublishStories.videoSelect.selectedIndex++;
if (PublishStories.videoSelect.selectedIndex == -1) {
PublishStories.videoSelect.selectedIndex = 0;
}
Toast.send(PublishStories.videoSelect.options[PublishStories.videoSelect.selectedIndex].label);
PublishStories.getStream();
};
PublishStories.main.classList = 'show';
PublishStories.back.onclick = () => { history.back(); };
PublishStories.close();
PublishStories.getStream(true);
},
draw: function () {
if (PublishStories.imageCanvas == undefined) return;
PublishStories.canvas.width = PublishStories.canvasMinWidth;
PublishStories.canvas.height = PublishStories.canvasMinHeight;
let context = PublishStories.canvas.getContext('2d');
ctx.clearRect(0, 0, PublishStories.canvas.width, PublishStories.canvas.height)
context.fillStyle = MovimUtils.imageToHex(PublishStories.imageCanvas);
context.fillRect(0, 0, PublishStories.canvas.width, PublishStories.canvas.height);
// Let's move!
context.translate(PublishStories.canvas.width / 2, PublishStories.canvas.height / 2);
context.scale(PublishStories.cameraZoom, PublishStories.cameraZoom);
context.translate(
-PublishStories.canvas.width / 2 + PublishStories.cameraOffset.x,
-PublishStories.canvas.height / 2 + PublishStories.cameraOffset.y
);
let adjustedHeight = PublishStories.canvas.height > PublishStories.imageCanvas.height
? PublishStories.imageCanvas.height
: PublishStories.canvas.height;
let ratio = adjustedHeight / PublishStories.imageCanvas.height;
context.drawImage(
PublishStories.imageCanvas,
PublishStories.canvas.width / 2 - PublishStories.imageCanvas.width * ratio / 2,
PublishStories.canvas.height / 2 - adjustedHeight / 2,
PublishStories.imageCanvas.width * ratio,
adjustedHeight
);
requestAnimationFrame(PublishStories.draw);
},
goToPublish: function () {
let title = PublishStories.main.querySelector('form textarea[name=title]');
if (title.value == '') {
PublishStories_ajaxNoTitle();
return;
}
PublishStories.main.classList.add('publish');
},
goToEdit: function () {
PublishStories.main.classList.remove('publish');
},
publish: function () {
PublishStories.main.querySelector('#publishactions').classList.add('uploading');
PublishStories.canvas.toBlob(
function (blob) {
Upload.prepare(blob);
Upload.name = 'story.jpg';
Upload.init(true);
},
'image/jpeg',
0.85
);
},
close: function () {
if (!PublishStories.video) return;
let stream = PublishStories.video.srcObject;
if (stream) {
stream.getTracks().forEach(track => track.stop());
}
PublishStories.video.srcObject = null;
PublishStories.imageCanvas = undefined;
PublishStories.main.querySelector('form').reset();
PublishStories.main.querySelector('#publishactions').classList = '';
PublishStories.reset();
PublishStories.main.classList = '';
PublishStories.main == undefined;
},
openImage: function () {
PublishStories.main.querySelector('input[type=file]').click();
},
applyImage: function () {
file = PublishStories.main.querySelector('input[type=file]').files[0];
if (!file.type.match(/image.*/)) {
console.log("Not a picture !");
PublishStories.reset();
} else {
var reader = new FileReader();
reader.readAsDataURL(file);
reader.addEventListener('load', function (ev) {
var image = new Image();
image.src = reader.result;
image.onload = function () {
PublishStories.drawMediaToImageCanvas(image, image.width, image.height, 1);
PublishStories.draw();
PublishStories.main.classList = 'edit';
}
});
};
},
shoot: function () {
if (PublishStories.imageCapture) {
PublishStories.imageCapture.grabFrame()
.then(image => {
PublishStories.drawMediaToImageCanvas(
image,
image.width,
image.height,
PublishStories.getRatioFromHeight(image.height)
);
PublishStories.draw();
})
.catch(error => console.log(error));
} else {
PublishStories.drawMediaToImageCanvas(
PublishStories.video,
PublishStories.video.videoWidth,
PublishStories.video.videoHeight,
PublishStories.getRatioFromHeight(PublishStories.video.videoHeight)
);
PublishStories.video.pause();
PublishStories.draw();
}
PublishStories.main.classList = 'edit';
},
getRatioFromHeight: function (height) {
let ratio = PublishStories.canvasMinHeight / height;
if (ratio < 1) ratio = 1;
return ratio;
},
drawMediaToImageCanvas: function (media, width, height, ratio) {
PublishStories.imageCanvas = document.createElement('canvas');
PublishStories.imageCanvas.width = width * ratio;
PublishStories.imageCanvas.height = height * ratio;
var context = PublishStories.imageCanvas.getContext('2d');
context.drawImage(media, 0, 0, PublishStories.imageCanvas.width, PublishStories.imageCanvas.height);
},
reset: function () {
if (PublishStories.videoSelect.options.length > 0) {
PublishStories.main.classList = 'shoot';
PublishStories.video.play();
} else {
PublishStories.main.classList = 'shoot error';
}
PublishStories.cameraOffset = { x: 0, y: 0 };
PublishStories.cameraZoom = PublishStories.lastZoom = 1;
},
getStream: function (init) {
PublishStories.main.classList = 'wait';
if (PublishStories.video.srcObject) {
PublishStories.video.srcObject.getTracks().forEach(track => track.stop());
}
const videoSource = PublishStories.videoSelect.value;
const constraints = {
video: {
deviceId: videoSource ? { exact: videoSource } : undefined,
width: { ideal: 1920 },
height: { ideal: 1920 }
}
};
navigator.mediaDevices.getUserMedia(constraints)
.then(stream => {
MovimTpl.finishedPage();
PublishStories.gotStream(stream);
if (init) {
navigator.mediaDevices.enumerateDevices().then(PublishStories.gotDevices);
}
})
.catch(e => {
MovimTpl.finishedPage();
PublishStories.noStream()
});
},
noStream: function (stream) {
PublishStories.main.classList = 'shoot error';
},
gotStream: function (stream) {
PublishStories.main.classList = 'shoot';
PublishStories.videoSelect.selectedIndex = [...PublishStories.videoSelect.options].
findIndex(option => option.text === stream.getVideoTracks()[0].label);
PublishStories.video.srcObject = stream;
PublishStories.video.play();
// We try to use ImageCapture
if (typeof (ImageCapture) != 'undefined') {
const track = stream.getVideoTracks()[0];
PublishStories.imageCapture = new ImageCapture(track);
}
// If we cancel after the authorization was given
if (PublishStories.main.classList == '') {
PublishStories.close();
};
},
gotDevices: function (devicesInfo) {
PublishStories.videoSelect.innerHTML = '';
for (const deviceInfo of devicesInfo) {
if (deviceInfo.kind === 'videoinput') {
const option = document.createElement('option');
option.value = deviceInfo.deviceId;
option.text = deviceInfo.label || `Camera ${PublishStories.videoSelect.length + 1}`;
if (!PublishStories.videoSelect.querySelector('option[value="' + deviceInfo.deviceId + '"]')) {
PublishStories.videoSelect.appendChild(option);
}
}
}
if (PublishStories.videoSelect.options.length >= 2) {
document.querySelector("#publishstories #publishstoriesswitch").classList.add('enabled');
}
},
onPointerDown: function (e) {
PublishStories.isDragging = true
PublishStories.dragStart.x = MovimUtils.getEventLocation(e).x / PublishStories.cameraZoom - PublishStories.cameraOffset.x
PublishStories.dragStart.y = MovimUtils.getEventLocation(e).y / PublishStories.cameraZoom - PublishStories.cameraOffset.y
},
onPointerUp: function (e) {
PublishStories.isDragging = false
PublishStories.initialPinchDistance = null
PublishStories.lastZoom = PublishStories.cameraZoom
},
onMainPointerMove: function (e) {
if (!PublishStories.main.classList.contains('edit') || !MovimUtils.getEventLocation(e)) return;
let borders = PublishStories.canvas.getBoundingClientRect();
if (MovimUtils.getEventLocation(e).x < borders.left
|| MovimUtils.getEventLocation(e).x > borders.right
|| MovimUtils.getEventLocation(e).y < borders.top
|| MovimUtils.getEventLocation(e).y > borders.bottom) {
PublishStories.onPointerUp(e);
}
},
onPointerMove: function (e) {
if (PublishStories.isDragging) {
PublishStories.cameraOffset.x = MovimUtils.getEventLocation(e).x / PublishStories.cameraZoom - PublishStories.dragStart.x
PublishStories.cameraOffset.y = MovimUtils.getEventLocation(e).y / PublishStories.cameraZoom - PublishStories.dragStart.y
}
},
handleTouch: function (e, singleTouchHandler) {
if (e.touches.length == 1) {
singleTouchHandler(e)
}
else if (e.type == "touchmove" && e.touches.length == 2) {
PublishStories.isDragging = false
PublishStories.handlePinch(e)
}
},
handlePinch: function (e) {
e.preventDefault()
let touch1 = { x: e.touches[0].clientX, y: e.touches[0].clientY }
let touch2 = { x: e.touches[1].clientX, y: e.touches[1].clientY }
// This is distance squared, but no need for an expensive sqrt as it's only used in ratio
let currentDistance = (touch1.x - touch2.x) ** 2 + (touch1.y - touch2.y) ** 2
if (PublishStories.initialPinchDistance == null) {
PublishStories.initialPinchDistance = currentDistance
}
else {
PublishStories.adjustZoom(null, currentDistance / PublishStories.initialPinchDistance)
}
},
adjustZoom: function (zoomAmount, zoomFactor) {
if (!PublishStories.isDragging) {
if (zoomAmount) {
PublishStories.cameraZoom += zoomAmount
}
else if (zoomFactor) {
PublishStories.cameraZoom = zoomFactor * PublishStories.lastZoom
}
PublishStories.cameraZoom = Math.min(PublishStories.cameraZoom, MAX_ZOOM)
PublishStories.cameraZoom = Math.max(PublishStories.cameraZoom, MIN_ZOOM)
}
}
}
MovimEvents.registerWindow('loaded', 'publishstories', () => {
Upload.attach((file) => {
if (PublishStories.main && PublishStories.main.classList.contains('publish')) {
PublishStories.main.querySelector('#publishactions').classList.remove('uploading');
PublishStories.main.querySelector('#publishactions').classList.add('publishing');
PublishStories_ajaxPublish(MovimUtils.formToJson('metadata'), file.id);
}
});
Upload.fail(() => {
PublishStories.main.querySelector('#publishactions').classList.remove('uploading');
});
Upload.progress((percent) => {
if (PublishStories.main != undefined && PublishStories.main.classList.contains('publish')) {
PublishStories.main.querySelector('#publishactionsprogress').innerHTML =
percent + '%';
}
});
});