first commit

This commit is contained in:
René Preuß
2023-03-01 19:24:20 +01:00
commit 4ab5e76d85
45 changed files with 1963 additions and 0 deletions

261
src/App.vue Normal file
View File

@@ -0,0 +1,261 @@
<script setup lang="ts">
import {computed, onMounted, ref} from "vue";
import ProgressBar from "./components/ProgressBar.vue";
import AppTitleBar from "./components/AppTitleBar.vue";
import VButton from "./components/VButton.vue";
import {Settings, Video} from "../shared/schema";
console.log("[App.vue]", `Hello world from Electron ${process.versions.electron}!`)
const isEncoding = ref(false)
const isCompleted = ref(false)
const encodingProgress = ref(0)
const encodingProgressStage = ref<string>('unknown')
const encodingError = ref<string | null>(null)
const version = ref<string>('unknown')
const reset = () => {
encodingProgress.value = 0
encodingProgressStage.value = 'unknown'
encodingError.value = null
isEncoding.value = false
isCompleted.value = false
}
const requestEncode = (event: any) => {
// get file from @change event
const file = event.target.files[0] as File
// @ts-ignore
window.api.on('encode-start', (event: any, id: string) => {
encodingProgressStage.value = 'starting'
encodingProgress.value = 0
console.log('encode-start', id)
})
// @ts-ignore
window.api.on('encode-progress', (event: any, id: string, progress: number) => {
encodingProgress.value = progress
encodingProgressStage.value = 'transcoding'
console.log('encode-progress', id, progress)
})
// @ts-ignore
window.api.on('encode-upload-progress', (event: any, id: string, progress: number) => {
encodingProgress.value = progress
encodingProgressStage.value = 'uploading'
console.log('encode-upload-progress', id, progress)
})
// @ts-ignore
window.api.on('encode-upload-complete', (event: any, id: string, video: Video) => {
encodingProgressStage.value = 'complete'
encodingProgress.value = 100
isEncoding.value = false
isCompleted.value = true
console.log('encode-upload-complete', id, video)
})
// @ts-ignore
window.api.on('encode-error', (event: any, id: string, error: any) => {
encodingProgressStage.value = 'error'
encodingProgress.value = 0
isEncoding.value = false
isCompleted.value = false
encodingError.value = error
console.log('encode-error', id, error)
})
try {
isEncoding.value = true
encodingError.value = null
isCompleted.value = false
// @ts-ignore
window.api.encode('file-upload-123', file.path, {
title: file.name,
publishOnComplete: true,
})
} catch (err) {
isEncoding.value = false
isCompleted.value = false
encodingError.value = 'Failed to encode video'
console.error(err)
}
}
const settings = ref<Settings | null>(null);
const fileInput = ref<HTMLInputElement | null>(null)
// Authorization Code Grant with PKCE
const oauthHref = computed(() => {
const params = new URLSearchParams({
client_id: '97be2e0d-fc64-4a0c-af7b-959f754f516e',
redirect_uri: 'http://localhost:8361/oauth',
response_type: 'token',
scope: 'user:read video:read video:manage',
})
return `https://rerunmanager.com/oauth/authorize?${params.toString()}`
})
const settingsChanged = (settings: any) => {
console.log('settingsChanged', settings)
}
const logout = () => {
// @ts-ignore
window.api.logout()
}
const storageUpgradeRequired = computed(() => {
if (!settings.value) return false
// @ts-ignore
return !settings.value.credentials.user.config.premium
})
onMounted(() => {
// @ts-ignore
window.api.onSettingsChanged((x: any) => settings.value = x)
// @ts-ignore
window.api.getSettings()
.then((x: any) => settings.value = x)
.catch((err: any) => console.error(err))
// @ts-ignore
window.api.getVersion().then((x: any) => version.value = `v${x}`)
})
</script>
<template>
<div class="app h-screen w-screen bg-base-800 text-white flex flex-col justify-between">
<div>
<app-title-bar/>
<div class="px-6 flex">
<div>
<img src="./assets/ffmpeg.svg" class="h-16" alt="ffmpeg">
</div>
<div>
<div class="font-bold text-xl mt-2">Rerun Encoder</div>
<div class="text-sm text-zinc-400">Powered by FFmpeg</div>
<div class="flex gap-4 mt-4">
<a
class="text-rose-400"
href="https://www.bitinflow.com/legal/privacy/"
target="_blank"
>
<i class="far fa-arrow-up-right-from-square"></i>
Privacy & terms
</a>
<a
class="text-rose-400"
href="https://github.com/bitinflow/rerun-encoder#readme"
target="_blank"
>
<i class="far fa-arrow-up-right-from-square"></i>
More details
</a>
</div>
</div>
</div>
</div>
<div>
<div>
<div v-if="settings && settings.credentials" class="p-4 flex justify-between bg-base-900">
<div class="test flex gap-2">
<div>
<img
class="h-16 rounded-full"
:src="settings.credentials.user.avatar_url"
alt="channel avatar"
>
</div>
<div class="self-center">
<div class="text-lg">{{ settings.credentials.user.name }}</div>
<div class="text-sm text-zinc-400">
{{ settings.credentials.user.config.storage_used }}
of {{ settings.credentials.user.config.storage_limit }}
GB used
</div>
<div class="flex text-sm text-zinc-600 gap-1">
<div>
{{ version }}
</div>
<div class="text-zinc-700">|</div>
<a href="#" @click.prevent="logout">Logout</a>
</div>
</div>
</div>
<div class="self-center">
<div v-if="encodingError" class="text-right">
<div class="text-rose-400 text-sm w-64">
{{ encodingError }}
</div>
<a href="#"
class="text-zinc-400 text-sm"
@click="reset">
Retry
</a>
</div>
<div v-else-if="isCompleted" class="text-right">
<div class="text-emerald-400 w-64">
<i class="fa-solid fa-check"></i>
Video uploaded successfully!
</div>
<a href="#"
class="text-zinc-400 text-sm"
@click="reset">
Upload another
</a>
</div>
<template v-else-if="isEncoding">
<progress-bar :progress="encodingProgress" :stage="encodingProgressStage"/>
</template>
<template v-else>
<input
ref="fileInput"
class="hidden"
type="file"
accept="video/mp4"
@change="requestEncode"
>
<v-button
@click="fileInput && fileInput.click()"
:disabled="isEncoding"
>
<i class="fal fa-upload"></i>
Transcode & Upload
</v-button>
</template>
</div>
</div>
<div v-else class="p-4 flex justify-between bg-base-900">
<div class="self-center opacity-70">
Login required
</div>
<div class="py-5">
<a :href="oauthHref" class="bg-primary-500 py-1.5 px-4 rounded text-white" target="_blank">
<i class="fal fa-lock"></i>
Login with Rerun Manager
</a>
</div>
</div>
</div>
<div
v-if="storageUpgradeRequired"
class="bg-primary-500 text-white px-4 py-1 text-sm flex justify-between">
<div>
<i class="fa-solid fa-stars"></i>
Need more storage? Get Rerun Manager Premium!
</div>
<div class="text-sm">
<a href="https://www.rerunmanager.com/plans" target="_blank" class="text-white">
<i class="far fa-arrow-up-right-from-square"></i>
Learn more
</a>
</div>
</div>
</div>
</div>
</template>

1
src/assets/electron.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256"><g fill="none" fill-rule="evenodd"><circle fill="#2B2E3A" cx="128" cy="128" r="128"/><g fill="#9FEAF9" fill-rule="nonzero"><path d="M100.502 71.69c-26.005-4.736-46.567.221-54.762 14.415-6.115 10.592-4.367 24.635 4.24 39.646a2.667 2.667 0 1 0 4.626-2.653c-7.752-13.522-9.261-25.641-4.247-34.326 6.808-11.791 25.148-16.213 49.187-11.835a2.667 2.667 0 0 0 .956-5.247zm-36.999 72.307c10.515 11.555 24.176 22.394 39.756 31.388 37.723 21.78 77.883 27.601 97.675 14.106a2.667 2.667 0 1 0-3.005-4.406c-17.714 12.078-55.862 6.548-92.003-14.318-15.114-8.726-28.343-19.222-38.478-30.36a2.667 2.667 0 1 0-3.945 3.59z"/><path d="M194.62 140.753c17.028-20.116 22.973-40.348 14.795-54.512-6.017-10.423-18.738-15.926-35.645-16.146a2.667 2.667 0 0 0-.069 5.333c15.205.198 26.165 4.939 31.096 13.48 6.792 11.765 1.49 29.807-14.248 48.399a2.667 2.667 0 1 0 4.071 3.446zm-43.761-68.175c-15.396 3.299-31.784 9.749-47.522 18.835-38.942 22.483-64.345 55.636-60.817 79.675a2.667 2.667 0 1 0 5.277-.775c-3.133-21.344 20.947-52.769 58.207-74.281 15.267-8.815 31.135-15.06 45.972-18.239a2.667 2.667 0 1 0-1.117-5.215z"/><path d="M87.77 187.753c8.904 24.86 23.469 40.167 39.847 40.167 11.945 0 22.996-8.143 31.614-22.478a2.667 2.667 0 1 0-4.571-2.748c-7.745 12.883-17.258 19.892-27.043 19.892-13.605 0-26.596-13.652-34.825-36.63a2.667 2.667 0 1 0-5.021 1.797zm81.322-4.863c4.61-14.728 7.085-31.718 7.085-49.423 0-44.179-15.463-82.263-37.487-92.042a2.667 2.667 0 0 0-2.164 4.874c19.643 8.723 34.317 44.866 34.317 87.168 0 17.177-2.397 33.63-6.84 47.83a2.667 2.667 0 1 0 5.09 1.593zm50.224-2.612c0-7.049-5.714-12.763-12.763-12.763-7.049 0-12.763 5.714-12.763 12.763 0 7.049 5.714 12.763 12.763 12.763 7.049 0 12.763-5.714 12.763-12.763zm-5.333 0a7.43 7.43 0 1 1-14.86 0 7.43 7.43 0 0 1 14.86 0zM48.497 193.041c7.05 0 12.764-5.714 12.764-12.763 0-7.049-5.715-12.763-12.764-12.763-7.048 0-12.763 5.714-12.763 12.763 0 7.049 5.715 12.763 12.763 12.763zm0-5.333a7.43 7.43 0 1 1 0-14.86 7.43 7.43 0 0 1 0 14.86z"/><path d="M127.617 54.444c7.049 0 12.763-5.714 12.763-12.763 0-7.049-5.714-12.763-12.763-12.763-7.049 0-12.763 5.714-12.763 12.763 0 7.049 5.714 12.763 12.763 12.763zm0-5.333a7.43 7.43 0 1 1 0-14.86 7.43 7.43 0 0 1 0 14.86zm1.949 93.382c-4.985 1.077-9.896-2.091-10.975-7.076a9.236 9.236 0 0 1 7.076-10.976c4.985-1.077 9.896 2.091 10.976 7.076 1.077 4.985-2.091 9.897-7.077 10.976z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

4
src/assets/ffmpeg.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px">
<path fill="#ffffff"
d="M39.5,42h-9c-1,0-1.9-0.6-2.3-1.5c-0.4-0.9-0.2-2,0.5-2.7l8.3-8.3v-3.9L21.3,41.3 c-0.5,0.5-1.1,0.7-1.8,0.7h-11c-1,0-1.9-0.6-2.3-1.5c-0.4-0.9-0.2-2,0.5-2.7L33.5,11h-3.9L10.3,30.3c-0.7,0.7-1.8,0.9-2.7,0.5 C6.6,30.4,6,29.5,6,28.5v-11c0-0.7,0.3-1.3,0.7-1.8l4.7-4.7h-3C7.1,11,6,9.9,6,8.5S7.1,6,8.5,6h9c1,0,1.9,0.6,2.3,1.5 c0.4,0.9,0.2,2-0.5,2.7L11,18.5v3.9L26.7,6.7C27.2,6.3,27.8,6,28.5,6h11c1,0,1.9,0.6,2.3,1.5c0.4,0.9,0.2,2-0.5,2.7L14.5,37h3.9 l19.3-19.3c0.7-0.7,1.8-0.9,2.7-0.5c0.9,0.4,1.5,1.3,1.5,2.3v11c0,0.7-0.3,1.3-0.7,1.8L36.5,37h3c1.4,0,2.5,1.1,2.5,2.5 S40.9,42,39.5,42z"/>
</svg>

After

Width:  |  Height:  |  Size: 708 B

1
src/assets/vue.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@@ -0,0 +1,34 @@
<template>
<div class="flex justify-between p-4 text-sm title-bar">
<div class="flex gap-2">
<i class="fas fa-solid fa-arrow-rotate-right self-center"></i>
<div>
Rerun Manager - Encoder
</div>
</div>
<div class="flex gap-3">
<a href="#" @click="minimizeCompanion">
<i class="fal fa-dash"></i>
</a>
<a href="#" @click="closeCompanion">
<i class="fal fa-xmark"></i>
</a>
</div>
</div>
</template>
<script lang="ts">
export default {
name: "AppTitleBar",
methods: {
minimizeCompanion() {
// @ts-ignore
window.api.minimize()
},
closeCompanion() {
// @ts-ignore
window.api.quit();
},
},
}
</script>

View File

@@ -0,0 +1,37 @@
<template>
<div class="flex flex-col items-center justify-center gap-1.5 w-64">
<div class="text-xs text-zinc-400 flex justify-between w-full">
<div class="font-bold capitalize">{{ stage }}</div>
<div>{{ format(progress) }}%</div>
</div>
<div class="w-full h-2 bg-base-500 rounded-full">
<div
class="h-2 bg-primary-500 rounded-full"
:style="{ width: `${progress}%` }"
></div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: "ProgressBar",
props: {
progress: {
type: Number,
default: 0
},
stage: {
type: String,
default: ""
}
},
methods: {
format(progress: number) {
return Math.round(progress)
}
}
}
</script>

View File

@@ -0,0 +1,16 @@
<template>
<button
type="button"
@click="$emit('click')"
class="bg-primary-500 py-1.5 px-4 rounded text-white"
>
<slot/>
</button>
</template>
<script lang="ts">
export default {
name: "VButton",
emits: ['click']
}
</script>

10
src/main.ts Normal file
View File

@@ -0,0 +1,10 @@
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import './samples/node-api'
createApp(App)
.mount('#app')
.$nextTick(() => {
postMessage({ payload: 'removeLoading' }, '*')
})

13
src/samples/node-api.ts Normal file
View File

@@ -0,0 +1,13 @@
import { lstat } from 'node:fs/promises'
import { cwd } from 'node:process'
import { ipcRenderer } from 'electron'
ipcRenderer.on('main-process-message', (_event, ...args) => {
console.log('[Receive Main-process message]:', ...args)
})
lstat(cwd()).then(stats => {
console.log('[fs.lstat]', stats)
}).catch(err => {
console.error(err)
})

12
src/style.css Normal file
View File

@@ -0,0 +1,12 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
-webkit-user-select: none;
-webkit-app-region: drag;
}
button, a, input, label {
-webkit-app-region: no-drag;
}

7
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}