Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: added screenshoting and setting the channel thumbnail #601

Merged
merged 16 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@playwright/test": "1.36.0",
"@playwright/test": "1.36.1",
"@sveltejs/adapter-cloudflare": "^2.2.0",
"@sveltejs/kit": "^1.10.0",
"@sveltejs/svelte-virtual-list": "^3.0.1",
Expand Down
12 changes: 12 additions & 0 deletions src/lib/assets/icons/chat/IconChatScreenshot.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-8 h-8">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" />
</svg>
64 changes: 52 additions & 12 deletions src/lib/components/Channel/Chat/DrawerEditChannel.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<script lang="ts">
// import IconPhoto from '$lib/assets/icons/IconPhoto.svelte'
import { tags } from '$lib/stores/channelStore'
import { is_sharing_screen, is_sharing_webcam } from '$lib/stores/streamStore'
import { onDestroy, onMount } from 'svelte'
import Tags from 'svelte-tags-input'
import DrawerAddCategory from '$lib/components/Browse/DrawerAddCategory.svelte'
import { get } from '$lib/api'
import { enhance } from '$app/forms'
import { category_list } from '$lib/stores/channelStore'
import { emitChannelUpdate } from '$lib/websocket'
import IconChatScreenshot from '$lib/assets/icons/chat/IconChatScreenshot.svelte'

export let channel: any, showDrawer: boolean

Expand All @@ -16,7 +17,8 @@
showThumbnail = false,
showAddCategory = false,
maxTag = 3,
maxCategory = 4
maxCategory = 4,
imageSrc = ''

$: maxTagLabel = channel?.tags.length == maxTag ? 'max reached' : 'max ' + maxTag
$: maxCategoryLabel =
Expand Down Expand Up @@ -49,6 +51,37 @@
showThumbnail = false
}

const checkVideo = (e: any) => {
e.preventDefault()
showThumbnail = true
if (channel.videoItems.length > 0) {
let screenElement = document.getElementById(
`screen-${channel.videoItems[0]._id}`
) as HTMLVideoElement
let webcamElement = document.getElementById(
`webcam-${channel.videoItems[0]._id}`
) as HTMLVideoElement
let canvas = document.createElement('canvas')
canvas.width = 1920
canvas.height = 1080

let ctx = canvas.getContext('2d')

console.log(screenElement)
console.log(webcamElement)

if (ctx !== null && screenElement !== null && webcamElement !== null) {
ctx.drawImage(screenElement, 0, 0, canvas.width, canvas.height)
ctx.globalAlpha = 1
ctx.drawImage(webcamElement, 1400, 750, canvas.width - 1400, canvas.height - 750)
}

let screenshot = canvas.toDataURL('image/jpeg')
imageSrc = screenshot
//thumbnailRef.setAttribute('src', screenshot);
}
}

const addTag = (tagName: string) => {
tagName && channel.tags.length < maxTag ? channel.tags.push(tagName) : ''
channel = channel
Expand Down Expand Up @@ -101,25 +134,32 @@
Edit channel
</p>
<div class="flex flex-col p-3">
<p class="text-lg font-semibold">Please hide all sensitive data before going live.</p>

<!-- <div class="flex flex-row justify-center w-full">
<div class="card w-40 shadow-xl">
<div class="card-body items-center max-h-40 {showThumbnail ? '!p-3' : ''}">
{#if showThumbnail}
<img bind:this={thumbnailRef} src="" alt="Preview" class="rounded-lg h-full" />
<div class="flex flex-row justify-center w-full">
<div class="card w-60 shadow-xl" on:click={checkVideo}>
<div class="items-center max-h-40 {showThumbnail ? '!p-3' : ''}">
{#if imageSrc || channel.thumbnail}
<img
bind:this={thumbnailRef}
src={imageSrc || channel.thumbnail}
alt="Preview"
class="rounded-lg h-full" />
{:else}
<IconPhoto />
<div class="h-40 flex flex-col justify-center items-center">
<IconChatScreenshot />
Thumbnail
</div>
{/if}
</div>
</div>
</div>

<input type="hidden" name="imageSrc" value={imageSrc} />
<input type="hidden" name="channelId" value={channel._id} />
<input
bind:this={fileuploader}
on:change={fileupload}
type="file"
class="file-input file-input-bordered file-input-primary w-full mt-5" /> -->
name="thumbnail"
class="file-input file-input-bordered file-input-primary w-full mt-5" />
<input
bind:value={channel.title}
type="text"
Expand Down
14 changes: 12 additions & 2 deletions src/lib/components/Channel/Stream/VideoItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -331,15 +331,25 @@
{formattedTime}
</span>
{/if}
<video bind:this={screenElement} autoplay muted class="rounded-md w-full h-full" />
<video
bind:this={screenElement}
id={`screen-${video._id}`}
autoplay
muted
class="rounded-md w-full h-full" />
<div
use:draggable={{ bounds: 'parent' }}
on:mousedown={onMouseDown}
on:mouseup={onMouseUp}
class={animate +
' absolute ' +
(!isScreenLive ? 'w-full bottom-0 left-0 h-full' : 'w-1/4 bottom-0 right-0')}>
<video bind:this={webcamElement} autoplay muted class="rounded-md h-full w-full" />
<video
bind:this={webcamElement}
id={`webcam-${video._id}`}
autoplay
muted
class="rounded-md h-full w-full" />
</div>
<video bind:this={audioElement} autoplay muted class="rounded-md w-0 h-0" />
<div class="absolute left-2 bottom-2 rounded-md dropdown">
Expand Down
33 changes: 18 additions & 15 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ export const setRole = ({
channel,
currentUserId
}: {
userId: any
channel: any
userId: string
channel: { user: string; mods: string[] }
currentUserId: string
}): any => {
}): string => {
if (userId === 'AI') return '🤖 AI'
else if (userId === channel?.user) return 'Host'
else if (channel?.mods?.includes(userId)) return 'Mod'
Expand Down Expand Up @@ -165,9 +165,9 @@ export const cardCounts: { [key: number]: number[] } = {

export const timeSince = (date: string) => {
if (!date) return 'Date created unknown'
const created: any = new Date(date)
const currentDate: any = new Date(Date.now())
const seconds = Math.floor((currentDate - created) / 1000)
const created: Date = new Date(date)
const currentDate: Date = new Date(Date.now())
const seconds = Math.floor((currentDate.getTime() - created.getTime()) / 1000)
let interval = seconds / 31536000
if (interval > 1) {
return Math.floor(interval) + ' years ago'
Expand All @@ -191,15 +191,15 @@ export const timeSince = (date: string) => {
return Math.floor(seconds) + ' seconds ago'
}

export const getVideoGrids = (list: any, limit: number) => {
export const getVideoGrids = (list: object[], limit: number) => {
if (!list.length) return []

const numList = divideNumber(list.length, limit)
const result: any[] = []
const result: unknown[][] = []
let pointer = 0

for (let i = 0; i < numList.length; i++) {
const row: any = cardCounts[numList[i]]
const row: number[] = cardCounts[numList[i]]
for (let j = 0; j < row.length; j++) {
result[j] = []
for (let k = 0; k < row[j]; k++) {
Expand Down Expand Up @@ -253,17 +253,20 @@ export const getAudioIndicator = (

/** Dispatch event on click outside of node */

export const clickOutside = (element: any, callbackFunction: any) => {
const onClick = (event: any) => {
if (!element.contains(event.target)) {
export const clickOutside = (
element: HTMLElement,
callbackFunction: (event: MouseEvent) => void
) => {
const onClick = (event: MouseEvent) => {
if (!element.contains(event.target as Node)) {
callbackFunction(event)
}
}

document.body.addEventListener('click', onClick)

return {
update(newCallbackFunction: any) {
update(newCallbackFunction: (event: MouseEvent) => void) {
callbackFunction = newCallbackFunction
},
destroy() {
Expand All @@ -272,9 +275,9 @@ export const clickOutside = (element: any, callbackFunction: any) => {
}
}

export const createEffect = (...initialDeps: any[]) => {
export const createEffect = <T extends unknown[]>(...initialDeps: T) => {
let diff = JSON.stringify(initialDeps)
return (callback: () => void, deps?: any[], allowServerSide = false) => {
return (callback: () => void, deps?: T, allowServerSide = false) => {
if (JSON.stringify(deps) !== diff && ((!allowServerSide && browser) || allowServerSide)) {
diff = JSON.stringify(deps)
callback()
Expand Down
58 changes: 57 additions & 1 deletion src/routes/channel/[channelId]/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,63 @@
import { patch, putImage } from '$lib/api'
import type { Actions } from './$types'

const dataURLtoFile = (dataurl: string, filename: string) => {
const arr = dataurl.split(',')
const mime = (arr[0] && arr[0].match(/:(.*?);/)?.[1]) || ''
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new File([u8arr], filename, { type: mime })
}

export const actions = {
'edit-channel': async () => {
'edit-channel': async ({ request, locals }) => {
const data: FormData = await request.formData()
const newChannel = {}
addPropertyIfDefined(data, 'description', newChannel)
addPropertyIfDefined(data, 'title', newChannel)
addPropertyIfDefined(data, 'category', newChannel)
const thumbnail = data.get('thumbnail') as File
const imageSrc = data.get('imageSrc') as string
const channelId = data.get('channelId') as string
const file =
thumbnail !== null && thumbnail.size > 0
? thumbnail
: dataURLtoFile(imageSrc, 'thumbnail-image')
console.log(file)
if (file !== null && file.size > 0) {
const urlLocation = await putImage(
`channels/thumbnail?channelId=${channelId}&bucketName=thumbnails&originalName=${channelId}-thumbnail`,
file,
{
userId: locals.user.userId,
token: locals.user.token
}
)
console.log(urlLocation)
}

const updatedChannel = await patch(`channels?channelId=${channelId}`, newChannel, {
userId: locals.user.userId,
token: locals.user.token
})

console.log(updatedChannel)

return { success: true }
}
} satisfies Actions

const addPropertyIfDefined = (
data: FormData,
property: string,
newChannel: { [key: string]: unknown }
) => {
const propertyValue = data.get(property)
if (propertyValue !== null && propertyValue !== undefined) {
newChannel[property] = propertyValue
}
}