mirror of
https://github.com/jely2002/youtube-dl-gui.git
synced 2021-11-01 22:46:21 +03:00
feat: automatically fill in copied URL's into the url box (#94)
This commit is contained in:
5
main.js
5
main.js
@@ -7,10 +7,12 @@ const BinaryUpdater = require("./modules/BinaryUpdater");
|
||||
const AppUpdater = require("./modules/AppUpdater");
|
||||
const TaskList = require("./modules/persistence/TaskList");
|
||||
const DoneAction = require("./modules/DoneAction");
|
||||
const ClipboardWatcher = require("./modules/ClipboardWatcher");
|
||||
|
||||
let win
|
||||
let env
|
||||
let queryManager
|
||||
let clipboardWatcher
|
||||
let taskList
|
||||
let appStarting = true;
|
||||
|
||||
@@ -34,6 +36,8 @@ function startCriticalHandlers(env) {
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
clipboardWatcher = new ClipboardWatcher(win, env);
|
||||
|
||||
queryManager = new QueryManager(win, env);
|
||||
|
||||
taskList = new TaskList(env.paths, queryManager)
|
||||
@@ -44,6 +48,7 @@ function startCriticalHandlers(env) {
|
||||
binaryUpdater.checkUpdate().finally(() => {
|
||||
win.webContents.send("binaryLock", {lock: false});
|
||||
taskList.load();
|
||||
clipboardWatcher.startPolling();
|
||||
});
|
||||
} else if(env.settings.taskList) {
|
||||
taskList.load();
|
||||
|
||||
39
modules/ClipboardWatcher.js
Normal file
39
modules/ClipboardWatcher.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const { clipboard } = require('electron');
|
||||
|
||||
class ClipboardWatcher {
|
||||
constructor(win, env) {
|
||||
this.win = win;
|
||||
this.env = env;
|
||||
this.urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)?/gi;
|
||||
}
|
||||
|
||||
startPolling() {
|
||||
this.poll();
|
||||
this.pollId = setInterval(() => this.poll(), 1000);
|
||||
}
|
||||
|
||||
resetPlaceholder() {
|
||||
const standard = "Enter a video/playlist URL to add to the queue";
|
||||
this.win.webContents.send("updateLinkPlaceholder", {text: standard, copied: false});
|
||||
}
|
||||
|
||||
poll() {
|
||||
if(this.env.settings.autoFillClipboard) {
|
||||
const text = clipboard.readText();
|
||||
if (text != null) {
|
||||
if (this.previous != null && this.previous === text) return;
|
||||
this.previous = text;
|
||||
const isURL = text.match(this.urlRegex);
|
||||
if (isURL) {
|
||||
this.win.webContents.send("updateLinkPlaceholder", {text: text, copied: true});
|
||||
} else {
|
||||
this.resetPlaceholder();
|
||||
}
|
||||
} else {
|
||||
this.resetPlaceholder();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClipboardWatcher;
|
||||
@@ -2,11 +2,12 @@ const os = require("os");
|
||||
const fs = require("fs").promises;
|
||||
|
||||
class Settings {
|
||||
constructor(paths, env, outputFormat, proxy, spoofUserAgent, validateCertificate, taskList, nameFormat, nameFormatMode, sizeMode, splitMode, maxConcurrent, updateBinary, updateApplication, cookiePath, statSend, downloadMetadata, downloadThumbnail, keepUnmerged, calculateTotalSize, theme) {
|
||||
constructor(paths, env, outputFormat, proxy, autoFillClipboard, spoofUserAgent, validateCertificate, taskList, nameFormat, nameFormatMode, sizeMode, splitMode, maxConcurrent, updateBinary, updateApplication, cookiePath, statSend, downloadMetadata, downloadThumbnail, keepUnmerged, calculateTotalSize, theme) {
|
||||
this.paths = paths;
|
||||
this.env = env
|
||||
this.outputFormat = outputFormat == null ? "none" : outputFormat;
|
||||
this.proxy = proxy == null ? "" : proxy;
|
||||
this.autoFillClipboard = autoFillClipboard == null ? true : autoFillClipboard;
|
||||
this.spoofUserAgent = spoofUserAgent == null ? true : spoofUserAgent;
|
||||
this.validateCertificate = validateCertificate == null ? false : validateCertificate;
|
||||
this.taskList = taskList == null ? true : taskList;
|
||||
@@ -30,7 +31,7 @@ class Settings {
|
||||
try {
|
||||
let result = await fs.readFile(paths.settings, "utf8");
|
||||
let data = JSON.parse(result);
|
||||
return new Settings(paths, env, data.outputFormat, data.proxy, data.spoofUserAgent, data.validateCertificate, data.taskList, data.nameFormat, data.nameFormatMode, data.sizeMode, data.splitMode, data.maxConcurrent, data.updateBinary, data.updateApplication, data.cookiePath, data.statSend, data.downloadMetadata, data.downloadThumbnail, data.keepUnmerged, data.calculateTotalSize, data.theme);
|
||||
return new Settings(paths, env, data.outputFormat, data.proxy, data.autoFillClipboard, data.spoofUserAgent, data.validateCertificate, data.taskList, data.nameFormat, data.nameFormatMode, data.sizeMode, data.splitMode, data.maxConcurrent, data.updateBinary, data.updateApplication, data.cookiePath, data.statSend, data.downloadMetadata, data.downloadThumbnail, data.keepUnmerged, data.calculateTotalSize, data.theme);
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
let settings = new Settings(paths, env);
|
||||
@@ -43,6 +44,7 @@ class Settings {
|
||||
update(settings) {
|
||||
this.outputFormat = settings.outputFormat;
|
||||
this.proxy = settings.proxy;
|
||||
this.autoFillClipboard = settings.autoFillClipboard;
|
||||
this.spoofUserAgent = settings.spoofUserAgent;
|
||||
this.validateCertificate = settings.validateCertificate;
|
||||
this.taskList = settings.taskList;
|
||||
@@ -57,8 +59,7 @@ class Settings {
|
||||
if(this.maxConcurrent !== settings.maxConcurrent) {
|
||||
this.maxConcurrent = settings.maxConcurrent;
|
||||
this.env.changeMaxConcurrent(settings.maxConcurrent);
|
||||
}
|
||||
this.updateBinary = settings.updateBinary;
|
||||
}this.updateBinary = settings.updateBinary;
|
||||
this.updateApplication = settings.updateApplication;
|
||||
this.theme = settings.theme;
|
||||
this.save();
|
||||
@@ -71,6 +72,7 @@ class Settings {
|
||||
return {
|
||||
outputFormat: this.outputFormat,
|
||||
proxy: this.proxy,
|
||||
autoFillClipboard: this.autoFillClipboard,
|
||||
spoofUserAgent: this.spoofUserAgent,
|
||||
validateCertificate: this.validateCertificate,
|
||||
taskList: this.taskList,
|
||||
|
||||
@@ -36,6 +36,7 @@ contextBridge.exposeInMainWorld(
|
||||
"maximized",
|
||||
"videoAction",
|
||||
"updateGlobalButtons",
|
||||
"updateLinkPlaceholder",
|
||||
"totalSize",
|
||||
"binaryLock"
|
||||
];
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="container url-input">
|
||||
<form id="url-form" class="row mx-auto">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="add-url" placeholder="Enter a video/playlist URL to add to the queue" required>
|
||||
<input type="text" class="form-control" id="add-url" placeholder="Enter a video/playlist URL to add to the queue">
|
||||
<div class="input-group-append">
|
||||
<button type="button" id="add-url-btn" class="btn btn-dark" title="Add video to queue"><i class="bi bi-plus"></i></button>
|
||||
</div>
|
||||
@@ -256,6 +256,10 @@
|
||||
<input type="range" class="form-range w-50 d-block align-middle" min="1" max="32" id="maxConcurrent">
|
||||
<button type="button" class="btn btn-dark" id="defaultConcurrent">Reset to default</button>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<input class="check-input" type="checkbox" value="" id="autoFillClipboard">
|
||||
<label class="check-label" for="autoFillClipboard">Automatically fill in copied links</label>
|
||||
</div>
|
||||
<hr/>
|
||||
<h3>Appearance</h3>
|
||||
<div class="mb-4">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
let platform;
|
||||
let linkCopied = false;
|
||||
let progressCooldown = [];
|
||||
let sizeCooldown = [];
|
||||
let sizeCache = [];
|
||||
@@ -31,6 +32,11 @@ async function init() {
|
||||
window.main.invoke('titlebarClick', "maximize")
|
||||
})
|
||||
|
||||
//Updates the placeholder to a copied link
|
||||
window.main.receive("updateLinkPlaceholder", (args) => {
|
||||
$('#add-url').prop("placeholder", args.text);
|
||||
linkCopied = args.copied;
|
||||
});
|
||||
|
||||
//Init the when done dropdown
|
||||
$('.dropdown-toggle').dropdown();
|
||||
@@ -82,10 +88,7 @@ async function init() {
|
||||
//Add url when user presses enter, but prevent default behavior
|
||||
$(document).on("keydown", "form", function(event) {
|
||||
if(event.key == "Enter") {
|
||||
if ($('#url-form')[0].checkValidity()) {
|
||||
parseURL($('#add-url').val());
|
||||
$('#url-form').trigger('reset');
|
||||
}
|
||||
verifyURL();
|
||||
return false;
|
||||
}
|
||||
return true
|
||||
@@ -93,10 +96,7 @@ async function init() {
|
||||
|
||||
//Add url when user press on the + button
|
||||
$('#add-url-btn').on('click', () => {
|
||||
if($('#url-form')[0].checkValidity()) {
|
||||
parseURL($('#add-url').val());
|
||||
$('#url-form').trigger('reset');
|
||||
}
|
||||
verifyURL();
|
||||
});
|
||||
|
||||
$('body').on('click', '#install-btn', () => {
|
||||
@@ -150,6 +150,7 @@ async function init() {
|
||||
let settings = {
|
||||
updateBinary: $('#updateBinary').prop('checked'),
|
||||
updateApplication: $('#updateApplication').prop('checked'),
|
||||
autoFillClipboard: $('#autoFillClipboard').prop('checked'),
|
||||
outputFormat: $('#outputFormat').val(),
|
||||
proxy: $('#proxySetting').val(),
|
||||
spoofUserAgent: $('#spoofUserAgent').prop('checked'),
|
||||
@@ -191,6 +192,7 @@ async function init() {
|
||||
$('#spoofUserAgent').prop('checked', settings.spoofUserAgent);
|
||||
$('#validateCertificate').prop('checked', settings.validateCertificate);
|
||||
$('#taskList').prop('checked', settings.taskList);
|
||||
$('#autoFillClipboard').prop('checked', settings.autoFillClipboard);
|
||||
$('#proxySetting').val(settings.proxy);
|
||||
$('#nameFormatCustom').val(settings.nameFormat);
|
||||
$('#nameFormat').val(settings.nameFormatMode);
|
||||
@@ -397,6 +399,19 @@ async function init() {
|
||||
});
|
||||
}
|
||||
|
||||
function verifyURL() {
|
||||
if(linkCopied) {
|
||||
parseURL($('#add-url').prop('placeholder'));
|
||||
$('#url-form').trigger('reset');
|
||||
} else if($('#url-form')[0].checkValidity()) {
|
||||
const value = $('#add-url').val()
|
||||
if(value != null && value.length > 0) {
|
||||
parseURL(value);
|
||||
$('#url-form').trigger('reset');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleWhiteMode(setting) {
|
||||
const value = setting === "light";
|
||||
$('body').toggleClass("white-mode", value);
|
||||
|
||||
99
tests/ClipboardWatcher.test.js
Normal file
99
tests/ClipboardWatcher.test.js
Normal file
@@ -0,0 +1,99 @@
|
||||
const ClipboardWatcher = require("../modules/ClipboardWatcher");
|
||||
const { clipboard } = require('electron');
|
||||
|
||||
|
||||
jest.mock('electron', () => ({
|
||||
clipboard: {
|
||||
readText: jest.fn()
|
||||
}
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
describe('poll', () => {
|
||||
it('reads the text from the clipboard', () => {
|
||||
clipboard.readText.mockReturnValue("https://i.am.a.url.com");
|
||||
const instance = instanceBuilder(true);
|
||||
const resetMock = jest.spyOn(instance, "resetPlaceholder").mockImplementation(() => {});
|
||||
instance.poll();
|
||||
expect(clipboard.readText).toBeCalledTimes(1);
|
||||
expect(resetMock).toBeCalledTimes(0);
|
||||
});
|
||||
it('resets when it is not a URL', () => {
|
||||
clipboard.readText.mockReturnValue("im not a url");
|
||||
const instance = instanceBuilder(true);
|
||||
const resetMock = jest.spyOn(instance, "resetPlaceholder").mockImplementation(() => {});
|
||||
instance.poll();
|
||||
expect(resetMock).toBeCalledTimes(1);
|
||||
});
|
||||
it('resets when the copied text is null', () => {
|
||||
clipboard.readText.mockReturnValue(null);
|
||||
const instance = instanceBuilder(true);
|
||||
const resetMock = jest.spyOn(instance, "resetPlaceholder").mockImplementation(() => {});
|
||||
instance.poll();
|
||||
expect(resetMock).toBeCalledTimes(1);
|
||||
});
|
||||
it('sends the URL to renderer if it is one', () => {
|
||||
clipboard.readText.mockReturnValue("https://i.am.a.url.com");
|
||||
const instance = instanceBuilder(true);
|
||||
const resetMock = jest.spyOn(instance, "resetPlaceholder").mockImplementation(() => {});
|
||||
instance.poll();
|
||||
expect(instance.win.webContents.send).toBeCalledWith("updateLinkPlaceholder", {text: "https://i.am.a.url.com", copied: true})
|
||||
expect(instance.win.webContents.send).toBeCalledTimes(1);
|
||||
expect(resetMock).toBeCalledTimes(0);
|
||||
});
|
||||
it('doesnt poll when it is disabled in settings', () => {
|
||||
clipboard.readText.mockReturnValue("https://i.am.a.url.com");
|
||||
const instance = instanceBuilder(false);
|
||||
const resetMock = jest.spyOn(instance, "resetPlaceholder").mockImplementation(() => {});
|
||||
instance.poll();
|
||||
expect(clipboard.readText).toBeCalledTimes(0);
|
||||
expect(resetMock).toBeCalledTimes(0);
|
||||
expect(instance.win.webContents.send).toBeCalledTimes(0);
|
||||
});
|
||||
it('does nothing when the previous copied text matches the new one', () => {
|
||||
clipboard.readText.mockReturnValue("https://i.am.a.url.com");
|
||||
const instance = instanceBuilder(true);
|
||||
const resetMock = jest.spyOn(instance, "resetPlaceholder").mockImplementation(() => {});
|
||||
instance.poll();
|
||||
instance.poll();
|
||||
expect(clipboard.readText).toBeCalledTimes(2);
|
||||
expect(resetMock).toBeCalledTimes(0);
|
||||
expect(instance.win.webContents.send).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetPlaceholder', () => {
|
||||
it('sends the standard placeholder to renderer', () => {
|
||||
const instance = instanceBuilder(true);
|
||||
instance.resetPlaceholder();
|
||||
expect(instance.win.webContents.send).toBeCalledTimes(1);
|
||||
expect(instance.win.webContents.send.mock.calls[0][1].copied).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('startPolling', () => {
|
||||
it('Polls one time', () => {
|
||||
const instance = instanceBuilder(true);
|
||||
const pollMock = jest.spyOn(instance, "poll").mockImplementation(() => {});
|
||||
instance.startPolling()
|
||||
expect(pollMock).toBeCalledTimes(1);
|
||||
});
|
||||
it('Starts a polling loop', () => {
|
||||
const loops = 5;
|
||||
const instance = instanceBuilder(true);
|
||||
const pollMock = jest.spyOn(instance, "poll").mockImplementation(() => {});
|
||||
instance.startPolling()
|
||||
jest.advanceTimersByTime(loops * 1000);
|
||||
expect(pollMock).toBeCalledTimes(loops + 1);
|
||||
});
|
||||
});
|
||||
|
||||
function instanceBuilder(enabled) {
|
||||
const env = {settings: {autoFillClipboard: enabled}};
|
||||
const win = {webContents: {send: jest.fn()}};
|
||||
return new ClipboardWatcher(win, env);
|
||||
}
|
||||
@@ -2,8 +2,8 @@ const fs = require('fs').promises;
|
||||
const os = require("os");
|
||||
const Settings = require('../modules/persistence/Settings');
|
||||
const env = {version: "2.0.0-test1"};
|
||||
const defaultSettingsInstance = new Settings({settings: "tests/test-settings.json"}, env, "none", "", true, false, true, "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "click", "49", 8, true, true, "C:\\Users\\user\\cookies.txt", false, true, false, false, true, "dark");
|
||||
const defaultSettings = "{\"outputFormat\":\"none\",\"proxy\":\"\",\"spoofUserAgent\":true,\"validateCertificate\":false,\"taskList\":true,\"nameFormat\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"nameFormatMode\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"sizeMode\":\"click\",\"splitMode\":\"49\",\"maxConcurrent\":8,\"defaultConcurrent\":8,\"updateBinary\":true,\"updateApplication\":true,\"statSend\":false,\"downloadMetadata\":true,\"downloadThumbnail\":false,\"keepUnmerged\":false,\"calculateTotalSize\":true,\"theme\":\"dark\",\"version\":\"2.0.0-test1\"}"
|
||||
const defaultSettingsInstance = new Settings({settings: "tests/test-settings.json"}, env, "none", "", true, true, false, true, "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "click", "49", 8, true, true, "C:\\Users\\user\\cookies.txt", false, true, false, false, true, "dark");
|
||||
const defaultSettings = "{\"outputFormat\":\"none\",\"proxy\":\"\",\"autoFillClipboard\":true,\"spoofUserAgent\":true,\"validateCertificate\":false,\"taskList\":true,\"nameFormat\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"nameFormatMode\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"sizeMode\":\"click\",\"splitMode\":\"49\",\"maxConcurrent\":8,\"defaultConcurrent\":8,\"updateBinary\":true,\"updateApplication\":true,\"statSend\":false,\"downloadMetadata\":true,\"downloadThumbnail\":false,\"keepUnmerged\":false,\"calculateTotalSize\":true,\"theme\":\"dark\",\"version\":\"2.0.0-test1\"}"
|
||||
|
||||
describe('Load settings from file', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"outputFormat":"none","proxy": "","spoofUserAgent":true,"validateCertificate": false,"taskList":true,"nameFormat":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","nameFormatMode":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","sizeMode":"click","splitMode":"49","maxConcurrent":8,"defaultConcurrent":8,"updateBinary":true,"updateApplication":true,"cookiePath":"C:\\Users\\user\\cookies.txt","statSend":false,"downloadMetadata":true,"downloadThumbnail":false,"keepUnmerged":false,"calculateTotalSize":true,"theme": "dark","version":"2.0.0-test1"}
|
||||
{"outputFormat":"none","proxy": "","autoFillClipboard":true,"spoofUserAgent":true,"validateCertificate": false,"taskList":true,"nameFormat":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","nameFormatMode":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","sizeMode":"click","splitMode":"49","maxConcurrent":8,"defaultConcurrent":8,"updateBinary":true,"updateApplication":true,"cookiePath":"C:\\Users\\user\\cookies.txt","statSend":false,"downloadMetadata":true,"downloadThumbnail":false,"keepUnmerged":false,"calculateTotalSize":true,"theme": "dark","version":"2.0.0-test1"}
|
||||
|
||||
Reference in New Issue
Block a user