Skip to content

Commit

Permalink
Windows builds dont show cmd, nicer toasts, loader added, more effeci…
Browse files Browse the repository at this point in the history
…ent theme switcher (css based)
  • Loading branch information
nxrmqlly committed Oct 20, 2024
1 parent cacbd7f commit 0963450
Show file tree
Hide file tree
Showing 11 changed files with 300 additions and 89 deletions.
2 changes: 1 addition & 1 deletion backend/controllers/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (a *App) DownloadVideo(videoInfo models.VideoInfo, opts models.DownloadOpti

cmdArgs = append(cmdArgs, "-o", pathToSave)
cmd := exec.Command(ytdlpPath, cmdArgs...)
output, err := cmd.CombinedOutput()
output, err := utils.RunCmd(cmd)

if err != nil {
fmt.Println(string(output))
Expand Down
9 changes: 8 additions & 1 deletion backend/controllers/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,26 @@ package controllers
import (
"encoding/json"
"fmt"
"net/url"
"os/exec"

"youtube-downloader-go/backend/models"
"youtube-downloader-go/backend/utils"
)

func (a *App) GetVideoInfo(videoURL string) (models.VideoInfo, error) {

_, err := url.ParseRequestURI(videoURL)
if err != nil {
return models.VideoInfo{}, fmt.Errorf("invalid URL: %w", err)
}

_, ytDlpPath := utils.YtDlpSetup()

cmd := exec.Command(ytDlpPath, "-J", videoURL)

// Capture the output
output, err := cmd.CombinedOutput()
output, err := utils.RunCmd(cmd)
if err != nil {
return models.VideoInfo{}, fmt.Errorf("%s; output: %s", err, output)
}
Expand Down
2 changes: 1 addition & 1 deletion backend/models/video.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type VideoInfo struct {
FPS float32 `json:"fps"`
Uploader string `json:"uploader"`
UploaderURL string `json:"uploader_url"`
Duration int `json:"duration"` // Duration in seconds
Duration float32 `json:"duration"` // Duration in seconds
ViewCount int `json:"view_count"`
Thumbnail string `json:"thumbnail"`
Height int `json:"height"`
Expand Down
14 changes: 14 additions & 0 deletions backend/utils/run_cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package utils

import (
"os/exec"
"runtime"
"syscall"
)

func RunCmd(cmd *exec.Cmd) ([]byte, error) {
if runtime.GOOS == "windows" {
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
}
return cmd.CombinedOutput()
}
114 changes: 67 additions & 47 deletions frontend/src/App.svelte
Original file line number Diff line number Diff line change
@@ -1,49 +1,41 @@
<script>
import { GetVideoInfo } from "../wailsjs/go/controllers/App.js";
import Toast from "./components/Toast.svelte";
import "@fortawesome/fontawesome-free/css/all.min.css";
import Loader from "./components/Loader.svelte";
import ExpandableToast from "./components/ExpandableToast.svelte";
import VideoModal from "./components/VideoModal.svelte";
import ThemeSwitcher from "./components/ThemeSwitcher.svelte";
import Link from "./components/Link.svelte";
import "@fortawesome/fontawesome-free/css/all.min.css";
let url = "";
let errorMessage = "";
let videoResult;
let loading = false;
let showToast = false;
let showModal = false;
function isValidURL(string) {
const regex = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/;
return regex.test(string);
}
let visibleToasts = [];
// Function to open the modal
function openModal() {
showModal = true;
}
// Function to close the modal
function closeModal() {
showModal = false;
}
function openToast() {
showToast = true;
}
function closeToast() {
showToast = false;
}
// Function to handle form submission
function handleSubmit(e) {
e.preventDefault();
handleButtonClick();
}
// Function to handle button click
function handleButtonClick() {
if (!url) return;
if (!isValidURL(url)) {
errorMessage = "Error: Invalid URL";
openToast();
return;
}
loading = true;
Expand All @@ -53,51 +45,54 @@ function handleButtonClick() {
openModal();
})
.catch((e) => {
openToast();
errorMessage = e.toString();
// add a new toast message
const message = e.toString();
const newToast = {
id: Date.now(),
message: "Error Occurred",
detail: message,
};
visibleToasts = [...visibleToasts, newToast];
})
.finally(() => {
loading = false;
});
}
const handleClose = (id) => {
visibleToasts = visibleToasts.filter((toast) => toast.id !== id);
};
</script>

<style>
main {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100dvh;
background-color: var(--bg-color);
}
.title {
color: var(--fg-color);
}
.title span {
display: inline-block;
}
.title span::after{
content: '';
height: 0.1em;
background: var(--accent);
border-radius: 0.1em;
display: block
display: block;
}
.input-box {
margin-top: 1em;
display: flex;
border: 0.1em solid var(--accent);
border-radius: 0.1em;
overflow: hidden;
align-items: center;
/* padding: 0.5rem; */
}
.input {
border: none;
Expand All @@ -115,9 +110,6 @@ function handleButtonClick() {
color: white;
border: none;
padding: 0.5rem 1.25rem;
/* width: 4rem; */
/* height: 0.5rem; */
transition: background-color 0.3s;
}
.btn:hover {
Expand Down Expand Up @@ -153,37 +145,65 @@ function handleButtonClick() {
width: 1em;
height: 1em;
}
.toast-container {
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
flex-direction: column;
align-items: flex-end; /* Align items to the right */
}
.main-box {
height: 10%;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
</style>

<main>
<ThemeSwitcher />

<ThemeSwitcher />

<h1 class="title"><span>Youtube Downloader</span></h1>

<form class="input-box" id="input" on:submit={handleSubmit}>
<i class="mag fa-solid fa-magnifying-glass"></i>
<input autocomplete="off" bind:value={url} class="input" id="name" type="text" placeholder="Enter your URL here..."/>
<button class="btn" type="submit" on:click={handleButtonClick} disabled={loading || !url}>

<div class="main-box">
<form class="input-box" id="input" on:submit={handleSubmit}>
<i class="mag fa-solid fa-magnifying-glass"></i>
<input autocomplete="off" bind:value={url} class="input" id="name" type="text" placeholder="Enter your URL here..."/>
<button class="btn" type="submit" disabled={loading}>
<i class="btn-icon fa-solid fa-chevron-right"></i>
</button>
</form>
{#if loading}
<Loader/>
{/if}
</div>

</button>
</form>

<footer>
<p>Works with <Link ref="accent-anchor" href="https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md">almost anything</Link>. Blazingly fast, I guess.</p>
<p>Made by <Link ref="accent-anchor" href="https://github.com/nxrmqlly">@nxrmqlly</Link> with <Link ref="accent-anchor" href="https://www.youtube.com/watch?v=dQw4w9WgXcQ&pp=ygUIcmlja3JvbGw%3D">❤</Link> using Go and Svelte</p>
</footer>

<div class="toast-container">
{#each visibleToasts as toast}
<ExpandableToast
id={toast.id}
message={toast.message}
detail={toast.detail}
onClose={handleClose}
/>
{/each}
</div>

{#if showModal}
<VideoModal
videoInfo={videoResult}
videoInfo={videoResult}
onClose={closeModal}
/>
{/if}

{#if showToast}
<Toast message={errorMessage} onClose={closeToast} />
{/if}

</main>
</main>
70 changes: 70 additions & 0 deletions frontend/src/components/ExpandableToast.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@

<script>
export let message = "";
export let detail = "";
export let onClose;
export let id; // Add id to track each toast
let isOpen = false;
function toggle() {
isOpen = !isOpen;
}
// Automatically close the toast after 3 seconds
setTimeout(() => {
if (!isOpen) {
onClose(id); // Pass the id when closing
}
}, 3000);
</script>

<style>
.toast {
margin: 0.1rem 0;
background: #333;
color: white;
padding: 16px;
border-radius: 8px;
position: relative;
transition: opacity 0.3s ease;
max-width: 300px; /* Optional: set a max-width */
}
.detail {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.detail.open {
max-height: 200px; /* Adjust as necessary to fit your content */
}
.collapse-button,
.close-button {
margin: 0;
background: transparent;
border: none;
color: #ccc;
cursor: pointer;
text-decoration: underline;
}
.close-button {
position: absolute;
top: 8px;
right: 8px;
font-size: 16px;
}
</style>


<div class="toast">
<button class="close-button" on:click={() => onClose(id)}>✖</button>
<div>{message}</div>
<div class={`detail ${isOpen ? 'open' : ''}`}>
{detail}
</div>
<button class="collapse-button" on:click={toggle}>{isOpen ? 'Collapse' : 'Expand'}</button>
</div>
35 changes: 35 additions & 0 deletions frontend/src/components/Loader.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<style>
.loader {
width: 100%;
height: 0.2rem;
display: inline-block;
position: relative;
background: rgba(255, 255, 255, 0.15);
overflow: hidden;
}
.loader::after {
content: '';
width: 50%;
height: 0.2rem;
background: var(--accent);
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
animation: animloader 2s ease-in-out infinite;
}
@keyframes animloader {
0% {
left: 0;
transform: translateX(-100%);
}
100% {
left: 100%;
transform: translateX(0%);
}
}
</style>

<span class="loader"></span>
Loading

0 comments on commit 0963450

Please sign in to comment.