Skip to content

Commit

Permalink
DX-2411 Fix webcam example endpoints (#189)
Browse files Browse the repository at this point in the history
  • Loading branch information
kirillsud authored Sep 7, 2023
1 parent d2670e1 commit 8d0b61c
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 128 deletions.
4 changes: 3 additions & 1 deletion examples/nextjs-full-stack/pages/api/redirect.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ export default async function handler(req, res) {
}

await miro.exchangeCodeForAccessToken(userId, req.query.code);
res.redirect("/");

const boardId = req.query.state ?? "";
res.redirect(`/panel?boardId=${boardId}`);
}
117 changes: 12 additions & 105 deletions examples/nextjs-full-stack/pages/index.jsx
Original file line number Diff line number Diff line change
@@ -1,117 +1,24 @@
import { useRef, useEffect } from "react";
import { useEffect } from "react";
import Head from "next/head";

import initMiro from "../initMiro.js";

export async function getServerSideProps({ req, query }) {
const { userId, miro } = initMiro(req);

// redirect to auth url if user has not authorized the app
if (!(await miro.isAuthorized(userId))) {
return {
redirect: {
destination: miro.getAuthUrl(),
permanent: false,
},
};
}

if (!query.boardId) {
return {
notFound: true,
};
}

return {
props: {
boardId: query.boardId,
},
};
}

// get video stream and play it in the video element
async function initCamera(video) {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: false,
});
video.srcObject = stream;
video.play();
}

// upload image to our api
async function uploadBlob(blob, boardId) {
const fileName = "camera.jpeg";
const file = new File([blob], fileName, {
type: "image/jpeg",
});

const data = new FormData();
data.append("file", file, fileName);

await fetch(`/api/upload?boardId=${boardId}`, {
method: "POST",
body: data,
});

window.close();
}

export default function Main({ boardId }) {
const videoRef = useRef(null);
const canvasRef = useRef(null);
const width = 640;

export default function () {
useEffect(() => {
initCamera(videoRef.current);
}, [videoRef]);

const clickHandler = (e) => {
e.target.disabled = true;

// draw the image on canvas
const canvas = canvasRef.current;
const video = videoRef.current;

const height = video.videoHeight / (video.videoWidth / width);
canvas.width = width;
canvas.height = height;

canvas.getContext("2d").drawImage(video, 0, 0, width, height);
canvas.toBlob(async (blob) => {
try {
await uploadBlob(blob, boardId);
} finally {
e.target.disabled = false;
}
window.miro.board.ui.on("icon:click", async () => {
// open panel with the video stream
window.miro.board.ui.openModal({
url: `/panel?boardId=${(await window.miro.board.getInfo()).id}`,
width: 520,
height: 720,
});
});
};
}, []);

return (
<div className="container">
<div>
<Head>
<title>Miro camera upload</title>
<script src="https://miro.com/app/static/sdk/v2/miro.js"></script>
</Head>

<div>
<div>
<video ref={videoRef} style={{ width: "100%", maxWidth: "400px" }}>
Video stream not available.
</video>
</div>

{boardId ? (
<div>
<button className="button button-primary" onClick={clickHandler}>
<span className="icon-eye"></span> Take photo
</button>
</div>
) : (
<strong>Missing boardId in query params</strong>
)}

<canvas style={{ display: "none" }} ref={canvasRef}></canvas>
</div>
</div>
);
}
118 changes: 118 additions & 0 deletions examples/nextjs-full-stack/pages/panel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { useRef, useEffect } from "react";
import Head from "next/head";

import initMiro from "../initMiro.js";

export async function getServerSideProps({ req, query }) {
const { userId, miro } = initMiro(req);

const boardId = query.boardId;
if (!boardId) {
return {
notFound: true,
};
}

// redirect to auth url if user has not authorized the app
if (!(await miro.isAuthorized(userId))) {
return {
redirect: {
destination: miro.getAuthUrl(boardId),
permanent: false,
},
};
}

return {
props: {
boardId: boardId,
},
};
}

// get video stream and play it in the video element
async function initCamera(video) {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: false,
});
video.srcObject = stream;
video.play();
}

// upload image to our api
async function uploadBlob(blob, boardId) {
const fileName = "camera.jpeg";
const file = new File([blob], fileName, {
type: "image/jpeg",
});

const data = new FormData();
data.append("file", file, fileName);

await fetch(`/api/upload?boardId=${boardId}`, {
method: "POST",
body: data,
});

window.close();
}

export default function Main({ boardId }) {
const videoRef = useRef(null);
const canvasRef = useRef(null);
const width = 640;

useEffect(() => {
initCamera(videoRef.current);
}, [videoRef]);

const clickHandler = (e) => {
e.target.disabled = true;

// draw the image on canvas
const canvas = canvasRef.current;
const video = videoRef.current;

const height = video.videoHeight / (video.videoWidth / width);
canvas.width = width;
canvas.height = height;

canvas.getContext("2d").drawImage(video, 0, 0, width, height);
canvas.toBlob(async (blob) => {
try {
await uploadBlob(blob, boardId);
} finally {
e.target.disabled = false;
}
});
};

return (
<div className="container">
<Head>
<title>Miro camera upload</title>
</Head>

<div>
<div>
<video ref={videoRef} style={{ width: "100%", maxWidth: "400px" }}>
Video stream not available.
</video>
</div>

{boardId ? (
<div>
<button className="button button-primary" onClick={clickHandler}>
<span className="icon-eye"></span> Take photo
</button>
</div>
) : (
<strong>Missing boardId in query params</strong>
)}

<canvas style={{ display: "none" }} ref={canvasRef}></canvas>
</div>
</div>
);
}
22 changes: 0 additions & 22 deletions examples/nextjs-full-stack/pages/trigger.jsx

This file was deleted.

2 comments on commit 8d0b61c

@vercel
Copy link

@vercel vercel bot commented on 8d0b61c Sep 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

app-examples-wordle – ./examples/wordle

app-examples-wordle.vercel.app
app-examples-wordle-git-main-anthonyroux.vercel.app
app-examples-wordle-anthonyroux.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 8d0b61c Sep 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

webhooks-manager – ./examples/webhooks-manager

webhooks-manager-git-main-miro-web.vercel.app
webhooks-manager-sepia.vercel.app
webhooks-manager-miro-web.vercel.app

Please sign in to comment.