(chore): initial MVP release v0.1.0

This commit is contained in:
2025-05-01 21:39:12 +05:30
commit bd9483374f
36 changed files with 10146 additions and 0 deletions

112
entrypoints/background.ts Normal file
View File

@@ -0,0 +1,112 @@
import { WebsocketMessage } from "@/types/websocket";
export default defineBackground(() => {
const sendMessageToNativeHost = (message: WebsocketMessage) => {
return new Promise((resolve, reject) => {
let port = browser.runtime.connectNative('com.neosubhamoy.neodlp');
port.onMessage.addListener((response) => {
console.log('Received from native host:', response);
if (response.status === 'success') {
resolve(response.response);
} else if (response.status === 'error') {
reject(new Error(response.message));
}
});
port.onDisconnect.addListener(() => {
if (browser.runtime.lastError) {
reject(new Error(browser.runtime.lastError.message));
}
});
port.postMessage(message);
});
}
// Listen for messages from the popup or content scripts
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'download') {
const url = request.url;
sendMessageToNativeHost({url: url, command: 'download', argument: ''}).then(response => sendResponse(response))
return true; // Keep the message channel open for sendResponse to be called asynchronously
}
});
// Listen for the keyboard commands
browser.commands.onCommand.addListener(async (command) => {
if (command === "neodlp:quick-download") {
try {
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
const activeTab = tabs[0];
if (activeTab && activeTab.url) {
console.log("Quick download triggered for URL:", activeTab.url);
const response = await sendMessageToNativeHost({
url: activeTab.url,
command: 'download',
argument: ''
});
console.log("Quick download response:", response);
} else {
console.error("No active tab or URL found for quick download");
}
} catch (error) {
console.error("Error in quick download:", error);
}
}
});
// Clear existing context menus before creating new ones
browser.contextMenus.removeAll().then(() => {
// Context menu for quick download
browser.contextMenus.create({
id: "quick-download:page",
title: "Download with Neo Downloader Plus",
contexts: ["page"]
});
browser.contextMenus.create({
id: "quick-download:link",
title: "Download Link with Neo Downloader Plus",
contexts: ["link"]
});
browser.contextMenus.create({
id: "quick-download:media",
title: "Download Media with Neo Downloader Plus",
contexts: ["video", "audio"]
});
browser.contextMenus.create({
id: "quick-download:selection",
title: "Download Selection with Neo Downloader Plus",
contexts: ["selection"]
});
});
browser.contextMenus.onClicked.addListener((info, tab) => {
let url = '';
if (info.menuItemId === "quick-download:page") {
if(!info.pageUrl) return;
url = info.pageUrl;
} else if (info.menuItemId === "quick-download:link") {
if(!info.linkUrl) return;
url = info.linkUrl;
} else if (info.menuItemId === "quick-download:media") {
if(!info.srcUrl) return;
url = info.srcUrl;
} else if (info.menuItemId === "quick-download:selection") {
if(!info.selectionText) return;
url = info.selectionText;
}
if (!url) return;
sendMessageToNativeHost({url: url, command: 'download', argument: ''}).then(response => {
console.log("Context menu download response:", response);
}).catch(error => {
console.error("Error in context menu download:", error);
});
});
});

38
entrypoints/popup/App.tsx Normal file
View File

@@ -0,0 +1,38 @@
import { useEffect, useState } from "react";
import { ThemeProvider } from "@/components/theme-provider";
import { Settings } from "@/types/settings";
function App({ children }: { children: React.ReactNode }) {
const [settings, setSettings] = useState<Settings>({
theme: "system",
autofill_url: true,
});
// loading the settings from storage if available, overwriting the default values when the component mounts
useEffect(() => {
const loadSettings = async () => {
try {
const result = await browser.storage.local.get('settings');
if (result.settings) {
// Merge saved settings with default settings
// Only override keys that exist in saved settings, keeping defaults otherwise
setSettings(prevSettings => ({
...prevSettings,
...result.settings
}));
}
} catch (error) {
console.error("Failed to load settings:", error);
}
};
loadSettings();
}, []);
return (
<ThemeProvider defaultTheme={settings.theme || "system"} storageKey="vite-ui-theme">
{children}
</ThemeProvider>
);
}
export default App;

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Neo Downloader Plus</title>
<meta name="manifest.type" content="browser_action" />
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,21 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from '@/entrypoints/popup/App.tsx';
import '@/entrypoints/popup/style.css';
import { HashRouter, Routes, Route } from "react-router-dom";
import RootLayout from '@/pages/layout/root';
import HomePage from '@/pages/home';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<HashRouter>
<App>
<Routes>
<Route path="/" element={<RootLayout />}>
<Route index element={<HomePage />} />
</Route>
</Routes>
</App>
</HashRouter>
</React.StrictMode>,
);

123
entrypoints/popup/style.css Normal file
View File

@@ -0,0 +1,123 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}