Compare commits

..
15 Commits
Author SHA1 Message Date
death-claw bf532dd1d0 v2.12.0 2020-05-21 16:43:03 +01:00
death-claw eb1a125e59 Use new user hash from the api 2020-05-21 16:41:16 +01:00
death-claw 256659a72d v2.11.7 2020-05-08 12:57:41 +01:00
death-claw dbf3aa7e7d Fix bug with paths creation
Move Open Link button
Rename rename all title
2020-05-08 12:56:11 +01:00
death-claw a75ce181ff v2.11.6 2020-05-04 21:07:16 +01:00
death-claw 37ae6b4ebf Merge branch 'master' of https://github.com/death-claw/vripper-project 2020-05-04 21:06:48 +01:00
death-claw 6631a6a95b PNG filenames are now correctly named
Fix issue with special characters in post titles
2020-05-04 21:04:51 +01:00
death-clawandGitHub 918f8578ad Update CHANGELOG.md 2020-05-03 16:40:26 +01:00
death-claw 10fa9f68ae v2.11.5 2020-05-03 15:18:28 +01:00
death-claw 6b5c9ccb0f Reduce rows buffer size 2020-05-03 15:16:31 +01:00
death-claw 652e8f1a9d v2.11.4 2020-05-03 14:24:27 +01:00
death-claw fe53ae733d Code cleanup
Dependencies update
2020-05-03 14:22:40 +01:00
death-claw 690dab2ae8 v2.11.3 2020-04-30 18:26:42 +01:00
death-claw 530542163e Add missing files 2020-04-30 18:26:23 +01:00
death-claw cf7f4c75c0 Source code restructure
UI fixes and enhancements
Add menu entry to rename a gallery to first alternative title
Add menu on toolbar for massive rename to first alternative title
2020-04-30 18:23:11 +01:00
89 changed files with 2435 additions and 1948 deletions
+33 -1
View File
@@ -1,5 +1,37 @@
# Changelog
## [2.12.0] - 2020-05-21
### Changed
- Use new user hash from the api
## [2.11.7] - 2020-05-08
### Changed
- Fix bug with paths creation
- Move Open Link button
- Rename rename all title
## [2.11.6] - 2020-05-04
### Changed
- PNG filenames are now correctly named
- Fix issue with special characters in post titles
## [2.11.5] - 2020-05-03
### Changed
- Reduce rows buffer size
## [2.11.4] - 2020-05-03
### Changed
- Code cleanup
- Dependencies update
## [2.11.3] - 2020-04-30
### Changed
- Source code restructure
- UI fixes and enhancements
### Added
- Add menu entry to rename a gallery to first alternative title
- Add menu on toolbar for massive rename to first alternative title
## [2.11.2] - 2020-04-26
### Changed
- Bug fixes
@@ -337,4 +369,4 @@
- Add real time communication for progress updates via websocket (SockJS)
- Add settings for download path, max concurrent download, enable/disable integration with vipergirls
- Add integration with vipergitls.to (possibility to auto leave thanks after grab post)
- Add support for acidimg.cc, imagezilla.net, imgbox.com, imx.to, pixhost.to, turboimagehost.com
- Add support for acidimg.cc, imagezilla.net, imgbox.com, imx.to, pixhost.to, turboimagehost.com
+2
View File
@@ -20,3 +20,5 @@ To build the desktop app:
mvn clean install -Pelectron -DskipTests
Maven will automatically handle front end compilation. However, for development, you will need to install a recent version of nodejs on your system.
[![Donate with PayPal](https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/master/paypal-donate-button.png)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SC7M5LNWZ528Q)
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>tn.mnlr</groupId>
<artifactId>vripper</artifactId>
<version>2.11.2</version>
<version>2.12.0</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
+119 -118
View File
@@ -1,3 +1,4 @@
require('v8-compile-cache');
const { app, BrowserWindow } = require("electron");
const path = require("path");
const url = require("url");
@@ -18,139 +19,139 @@ let terminationInteval;
let terminated = false;
process.on("uncaughtException", err => {
dialog.showErrorBox(err.message, err.stack);
process.exit(1);
dialog.showErrorBox(err.message, err.stack);
process.exit(1);
});
function createWindow() {
if (process.platform === 'win32') {
app.setAppUserModelId("tn.mnlr.vripper");
}
if (process.platform === 'win32') {
app.setAppUserModelId("tn.mnlr.vripper");
}
let icon;
if(process.platform === "win32") {
icon = __dirname + '/icon.ico';
} else {
icon = __dirname + '/icon.png';
}
win = new BrowserWindow({
width: 1024,
height: 800,
minWidth: 800,
minHeight: 600,
webPreferences: {
nodeIntegration: true
},
icon: icon
});
let icon;
if(process.platform === "win32") {
icon = __dirname + '/icon.ico';
} else {
icon = __dirname + '/icon.png';
}
win = new BrowserWindow({
width: 1024,
height: 800,
minWidth: 800,
minHeight: 600,
webPreferences: {
nodeIntegration: true
},
icon: icon
});
win.removeMenu();
win.setMenu(null);
win.setMenuBarVisibility(false);
win.removeMenu();
win.setMenu(null);
win.setMenuBarVisibility(false);
win.loadURL(
url.format({
pathname: path.join(__dirname, `vripper-ui/index.html`),
protocol: "file:",
slashes: true
})
);
win.loadURL(
url.format({
pathname: path.join(__dirname, `vripper-ui/index.html`),
protocol: "file:",
slashes: true
})
);
// win.webContents.openDevTools();
// win.webContents.openDevTools();
win.on("closed", () => {
win = null;
});
win.on("closed", () => {
win = null;
});
}
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
app.quit();
} else {
getPort().then(port => {
serverPort = port;
ipcMain.on("get-port", event => {
event.reply("port", port);
});
let javaBinPath;
if(appDir !== undefined) {
javaBinPath = path.join(appDir, "java-runtime/bin/java");
} else {
if(process.platform === 'darwin') {
javaBinPath = path.join(app.getPath('exe'), "../../java-runtime/bin/java");
} else {
javaBinPath = path.join(app.getPath('exe'), "../java-runtime/bin/java");
}
}
let jarPath;
if(appDir !== undefined) {
jarPath = path.join(appDir, "bin/vripper-server.jar");
} else {
if(process.platform === 'darwin') {
jarPath = path.join(app.getPath('exe'), "../../bin/vripper-server.jar");
} else {
jarPath = path.join(app.getPath('exe'), "../bin/vripper-server.jar");
}
}
vripperServer = spawn(javaBinPath, [
"-Xms256m",
"-Dvripper.server.port=" + port,
"-jar",
jarPath
], {
stdio: 'ignore'
});
vripperServer.on('exit', (code, signal) => {
console.log(`vripper server terminated, code = ${code}, signal = ${signal}`);
terminated = true;
app.quit();
});
});
app.on("second-instance", (event, commandLine, workingDirectory) => {
if (win) {
if (win.isMinimized()) win.restore();
win.focus();
}
});
app.on("ready", createWindow);
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
axios.post('http://localhost:' + serverPort + '/actuator/shutdown', {}, {
headers: { 'content-type': 'application/json' },
}).then((response) => {
terminationInteval= setInterval(() => {
terminationAttemps++;
if(terminated) {
console.log('viper server terminated');
clearInterval(terminationInteval);
getPort().then(port => {
serverPort = port;
ipcMain.on("get-port", event => {
event.reply("port", port);
});
let javaBinPath;
if(appDir !== undefined) {
javaBinPath = path.join(appDir, "java-runtime/bin/java");
} else {
if(process.platform === 'darwin') {
javaBinPath = path.join(app.getPath('exe'), "../../java-runtime/bin/java");
} else {
javaBinPath = path.join(app.getPath('exe'), "../java-runtime/bin/java");
}
}
let jarPath;
if(appDir !== undefined) {
jarPath = path.join(appDir, "bin/vripper-server.jar");
} else {
if(process.platform === 'darwin') {
jarPath = path.join(app.getPath('exe'), "../../bin/vripper-server.jar");
} else {
jarPath = path.join(app.getPath('exe'), "../bin/vripper-server.jar");
}
}
vripperServer = spawn(javaBinPath, [
"-Xms256m",
"-Dvripper.server.port=" + port,
"-jar",
jarPath
], {
stdio: 'ignore'
});
vripperServer.on('exit', (code, signal) => {
console.log(`vripper server terminated, code = ${code}, signal = ${signal}`);
terminated = true;
app.quit();
} else if(terminationAttemps > maxTerminationAttemps) {
console.log('viper server is not terminated');
console.log('Proceed to kill');
vripperServer.kill('SIGKILL');
clearInterval(terminationInteval);
app.quit();
}
}, 1000);
})
.catch((error) => {
// Terminate immediately
console.log(error);
vripperServer.kill('SIGKILL');
terminated = true;
app.quit();
});
}
});
});
});
app.on("activate", () => {
if (win === null) {
createWindow();
}
});
app.on("second-instance", (event, commandLine, workingDirectory) => {
if (win) {
if (win.isMinimized()) win.restore();
win.focus();
}
});
app.on("ready", createWindow);
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
axios.post('http://localhost:' + serverPort + '/actuator/shutdown', {}, {
headers: { 'content-type': 'application/json' },
}).then((response) => {
terminationInteval= setInterval(() => {
terminationAttemps++;
if(terminated) {
console.log('viper server terminated');
clearInterval(terminationInteval);
app.quit();
} else if(terminationAttemps > maxTerminationAttemps) {
console.log('viper server is not terminated');
console.log('Proceed to kill');
vripperServer.kill('SIGKILL');
clearInterval(terminationInteval);
app.quit();
}
}, 1000);
})
.catch((error) => {
// Terminate immediately
console.log(error);
vripperServer.kill('SIGKILL');
terminated = true;
app.quit();
});
}
});
app.on("activate", () => {
if (win === null) {
createWindow();
}
});
}
+390 -123
View File
@@ -1,6 +1,6 @@
{
"name": "vripper-electron",
"version": "2.11.2",
"version": "2.12.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -11,19 +11,19 @@
"dev": true
},
"@develar/schema-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.1.0.tgz",
"integrity": "sha512-qjCqB4ctMig9Gz5bd6lkdFr3bO6arOdQqptdBSpF1ZpCnjofieCciEzkoS9ujY9cMGyllYSCSmBJ3x9OKHXzoA==",
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
"integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==",
"dev": true,
"requires": {
"ajv": "^6.1.0",
"ajv-keywords": "^3.1.0"
"ajv": "^6.12.0",
"ajv-keywords": "^3.4.1"
}
},
"@electron/get": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@electron/get/-/get-1.9.0.tgz",
"integrity": "sha512-OBIKtF6ttIJotDXe4KJMUyTBO4xMii+mFjlA8R4CORuD4HvCUaCK3lPjhdTRCvuEv6gzWNbAvd9DNBv0v780lw==",
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@electron/get/-/get-1.10.0.tgz",
"integrity": "sha512-hlueNXU51c3CwQjBw/i5fwt+VfQgSQVUTdicpCHkhEjNZaa4CXJ5W1GaxSwtLE2dvRmAHjpIjUMHTqJ53uojfg==",
"dev": true,
"requires": {
"debug": "^4.1.1",
@@ -93,9 +93,9 @@
"dev": true
},
"ajv": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
"integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
@@ -149,39 +149,69 @@
}
},
"app-builder-bin": {
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-3.5.5.tgz",
"integrity": "sha512-ZcHzJ9Xl+azPqdKzXZKdRZmkNmbxHHZyl4cbobNf8qMQpoPChpcov8riVrZSbu/0cT/JqJ8LOwJjy1OAwbChaQ==",
"version": "3.5.8",
"resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-3.5.8.tgz",
"integrity": "sha512-ni3q7QTfQNWHNWuyn5x3FZu6GnQZv+TFnfgk5++svqleKEhHGqS1mIaKsh7x5pBX6NFXU3/+ktk98wA/AW4EXw==",
"dev": true
},
"app-builder-lib": {
"version": "22.4.1",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.4.1.tgz",
"integrity": "sha512-epwUzIM+2pcdy/If9koTP74CKx4v7xGPj75a2Z5cM4rrGN9yVZ3eDUBbfF0e0qE4Qmcv5pd0BAZJ26bGm8NWsQ==",
"version": "22.6.0",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.6.0.tgz",
"integrity": "sha512-ky2aLYy92U+Gh6dKq/e8/bNmCotp6/GMhnX8tDZPv9detLg9WuBnWWi1ktBPlpbl1DREusy+TIh+9rgvfduQoA==",
"dev": true,
"requires": {
"7zip-bin": "~5.0.3",
"@develar/schema-utils": "~2.1.0",
"@develar/schema-utils": "~2.6.5",
"async-exit-hook": "^2.0.1",
"bluebird-lst": "^1.0.9",
"builder-util": "22.4.1",
"builder-util-runtime": "8.6.2",
"builder-util": "22.6.0",
"builder-util-runtime": "8.7.0",
"chromium-pickle-js": "^0.2.0",
"debug": "^4.1.1",
"ejs": "^3.0.1",
"electron-publish": "22.4.1",
"fs-extra": "^8.1.0",
"ejs": "^3.1.2",
"electron-publish": "22.6.0",
"fs-extra": "^9.0.0",
"hosted-git-info": "^3.0.4",
"is-ci": "^2.0.0",
"isbinaryfile": "^4.0.4",
"isbinaryfile": "^4.0.6",
"js-yaml": "^3.13.1",
"lazy-val": "^1.0.4",
"minimatch": "^3.0.4",
"normalize-package-data": "^2.5.0",
"read-config-file": "5.0.2",
"read-config-file": "6.0.0",
"sanitize-filename": "^1.6.3",
"semver": "^7.1.3",
"semver": "^7.3.2",
"temp-file": "^3.3.7"
},
"dependencies": {
"fs-extra": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz",
"integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==",
"dev": true,
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^1.0.0"
}
},
"jsonfile": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz",
"integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^1.0.0"
}
},
"universalify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
"dev": true
}
}
},
"argparse": {
@@ -206,12 +236,24 @@
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="
},
"async": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
"integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=",
"dev": true
},
"async-exit-hook": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz",
"integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==",
"dev": true
},
"at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
"dev": true
},
"axios": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
@@ -269,6 +311,16 @@
"widest-line": "^3.1.0"
},
"dependencies": {
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"type-fest": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
@@ -299,31 +351,61 @@
"dev": true
},
"builder-util": {
"version": "22.4.1",
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-22.4.1.tgz",
"integrity": "sha512-+ysLc7cC4w6P7rBxmZ5X2aU3QvcwFoWCl1us+mcUKdsGmJAtFUMPJqueeptdxjyPrPShIUOKHzA8uk5A3d1fHg==",
"version": "22.6.0",
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-22.6.0.tgz",
"integrity": "sha512-jgdES2ExJYkuXC3DEaGAjFctKNA81C4QDy8zdoc+rqdSqheTizuDNtZg02uMFklmUES4V4fggmqds+Y7wraqng==",
"dev": true,
"requires": {
"7zip-bin": "~5.0.3",
"@types/debug": "^4.1.5",
"@types/fs-extra": "^8.1.0",
"app-builder-bin": "3.5.5",
"app-builder-bin": "3.5.8",
"bluebird-lst": "^1.0.9",
"builder-util-runtime": "8.6.2",
"chalk": "^3.0.0",
"builder-util-runtime": "8.7.0",
"chalk": "^4.0.0",
"debug": "^4.1.1",
"fs-extra": "^8.1.0",
"fs-extra": "^9.0.0",
"is-ci": "^2.0.0",
"js-yaml": "^3.13.1",
"source-map-support": "^0.5.16",
"source-map-support": "^0.5.19",
"stat-mode": "^1.0.0",
"temp-file": "^3.3.7"
},
"dependencies": {
"fs-extra": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz",
"integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==",
"dev": true,
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^1.0.0"
}
},
"jsonfile": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz",
"integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^1.0.0"
}
},
"universalify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
"dev": true
}
}
},
"builder-util-runtime": {
"version": "8.6.2",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.6.2.tgz",
"integrity": "sha512-9QnIBISfhgQ2BxtRLidVqf/v5HD73vSKZDllpUmGd2L6VORGQk7cZAPmPtw4HQM3gPBelyVJ5yIjMNZ8xjmd1A==",
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.7.0.tgz",
"integrity": "sha512-G1AqqVM2vYTrSFR982c1NNzwXKrGLQjVjaZaWQdn4O6Z3YKjdMDofw88aD9jpyK9ZXkrCxR0tI3Qe9wNbyTlXg==",
"dev": true,
"requires": {
"debug": "^4.1.1",
@@ -369,9 +451,9 @@
"dev": true
},
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
"integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
@@ -553,9 +635,9 @@
"integrity": "sha512-eqHj1AMmS53QiLr8Xqcz754m6ljmG9eFnrzMhPGA1eKrsNYBGIKLktXz6JnzqNMSH/6Qksxgq8OMKsr0MPjKlw=="
},
"core-js": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
"integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==",
"version": "3.6.5",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==",
"dev": true,
"optional": true
},
@@ -641,18 +723,47 @@
"optional": true
},
"dmg-builder": {
"version": "22.4.1",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-22.4.1.tgz",
"integrity": "sha512-hEemh7n0zoVt7zPPwvn7iOttP03oENjJ4ApttPmt8oDnX8T4q42MjGWyDlLkPMplMJfoTxkkNqmm296f0OYM8Q==",
"version": "22.6.0",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-22.6.0.tgz",
"integrity": "sha512-rJxuGhHIpcuDGBtWZMM8aLxkbZNgYO2MO5dUerDIBXebhX1K8DA23iz/uZ8ahcRNgWEv57b8GDqJbXKEfr5T0A==",
"dev": true,
"requires": {
"app-builder-lib": "~22.4.1",
"bluebird-lst": "^1.0.9",
"builder-util": "~22.4.1",
"fs-extra": "^8.1.0",
"app-builder-lib": "22.6.0",
"builder-util": "22.6.0",
"fs-extra": "^9.0.0",
"iconv-lite": "^0.5.1",
"js-yaml": "^3.13.1",
"sanitize-filename": "^1.6.3"
},
"dependencies": {
"fs-extra": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz",
"integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==",
"dev": true,
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^1.0.0"
}
},
"jsonfile": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz",
"integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^1.0.0"
}
},
"universalify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
"dev": true
}
}
},
"dom-serializer": {
@@ -714,15 +825,18 @@
"dev": true
},
"ejs": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.0.1.tgz",
"integrity": "sha512-cuIMtJwxvzumSAkqaaoGY/L6Fc/t6YvoP9/VIaK0V/CyqKLEQ8sqODmYfy/cjXEdZ9+OOL8TecbJu+1RsofGDw==",
"dev": true
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.2.tgz",
"integrity": "sha512-zFuywxrAWtX5Mk2KAuoJNkXXbfezpNA0v7i+YC971QORguPekpjpAgeOv99YWSdKXwj7JxI2QAWDeDkE8fWtXw==",
"dev": true,
"requires": {
"jake": "^10.6.1"
}
},
"electron": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-8.2.0.tgz",
"integrity": "sha512-mnV43gKCrCUMHLmGws/DU/l8LhaxrFD53A4ofwtthdCqOZWGIdk1+eMphiVumXR5a3lC64XVvmXQ2k28i7F/zw==",
"version": "8.2.4",
"resolved": "https://registry.npmjs.org/electron/-/electron-8.2.4.tgz",
"integrity": "sha512-Lle0InIgSAHZxD5KDY0wZ1A2Zlc6GHwMhAxoHMzn05mndyP1YBkCYHc0TDDofzUTrsLFofduPjlknO5Oj9fTPA==",
"dev": true,
"requires": {
"@electron/get": "^1.0.1",
@@ -731,33 +845,63 @@
},
"dependencies": {
"@types/node": {
"version": "12.12.32",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.32.tgz",
"integrity": "sha512-44/reuCrwiQEsXud3I5X3sqI5jIXAmHB5xoiyKUw965olNHF3IWKjBLKK3F9LOSUZmK+oDt8jmyO637iX+hMgA==",
"version": "12.12.37",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.37.tgz",
"integrity": "sha512-4mXKoDptrXAwZErQHrLzpe0FN/0Wmf5JRniSVIdwUrtDf9wnmEV1teCNLBo/TwuXhkK/bVegoEn/wmb+x0AuPg==",
"dev": true
}
}
},
"electron-builder": {
"version": "22.4.1",
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-22.4.1.tgz",
"integrity": "sha512-13CjZcGeJS+c3EKRwFT/Oty5Niif5g1FwDioBLEbjkPCPQgxdtDsr+rJtCu9qxkiKDYpAoPS+t/clNk0efONvQ==",
"version": "22.6.0",
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-22.6.0.tgz",
"integrity": "sha512-aLHlB6DTfjJ3MI4AUIFeWnwIozNgNlbOk2c2sTHxB10cAKp0dBVSPZ7xF5NK0uwDhElvRzJQubnHtJD6zKg42Q==",
"dev": true,
"requires": {
"@types/yargs": "^15.0.4",
"app-builder-lib": "22.4.1",
"app-builder-lib": "22.6.0",
"bluebird-lst": "^1.0.9",
"builder-util": "22.4.1",
"builder-util-runtime": "8.6.2",
"chalk": "^3.0.0",
"dmg-builder": "22.4.1",
"fs-extra": "^8.1.0",
"builder-util": "22.6.0",
"builder-util-runtime": "8.7.0",
"chalk": "^4.0.0",
"dmg-builder": "22.6.0",
"fs-extra": "^9.0.0",
"is-ci": "^2.0.0",
"lazy-val": "^1.0.4",
"read-config-file": "5.0.2",
"read-config-file": "6.0.0",
"sanitize-filename": "^1.6.3",
"update-notifier": "^4.1.0",
"yargs": "^15.1.0"
"yargs": "^15.3.1"
},
"dependencies": {
"fs-extra": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz",
"integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==",
"dev": true,
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^1.0.0"
}
},
"jsonfile": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz",
"integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^1.0.0"
}
},
"universalify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
"dev": true
}
}
},
"electron-context-menu": {
@@ -786,19 +930,49 @@
"integrity": "sha512-Z1qA/1oHNowGtSBIcWk0pcLEqYT/j+13xUw/MYOrBUOL4X7VN0i0KCTf5SqyvMPmW5pSPKbo28wkxMxzZ20YnQ=="
},
"electron-publish": {
"version": "22.4.1",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.4.1.tgz",
"integrity": "sha512-nwKNum3KXm+01rtWX2pc1jhazdzDy2zYnQx+zmXphZchjd6UOMX3ZN0xyZUCKugw5ZliflT6LkgbrcBXBtYD3A==",
"version": "22.6.0",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.6.0.tgz",
"integrity": "sha512-+v05SBf9qR7Os5au+fifloNHy5QxHQkUGudBj68YaTb43Pn37UkwRxSc49Lf13s4wW32ohM45g8BOVInPJEdnA==",
"dev": true,
"requires": {
"@types/fs-extra": "^8.1.0",
"bluebird-lst": "^1.0.9",
"builder-util": "~22.4.1",
"builder-util-runtime": "8.6.2",
"chalk": "^3.0.0",
"fs-extra": "^8.1.0",
"builder-util": "22.6.0",
"builder-util-runtime": "8.7.0",
"chalk": "^4.0.0",
"fs-extra": "^9.0.0",
"lazy-val": "^1.0.4",
"mime": "^2.4.4"
},
"dependencies": {
"fs-extra": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz",
"integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==",
"dev": true,
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^1.0.0"
}
},
"jsonfile": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz",
"integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^1.0.0"
}
},
"universalify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
"dev": true
}
}
},
"emoji-regex": {
@@ -927,6 +1101,15 @@
"pend": "~1.2.0"
}
},
"filelist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz",
"integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==",
"dev": true,
"requires": {
"minimatch": "^3.0.4"
}
},
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@@ -1088,9 +1271,9 @@
}
},
"graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true
},
"has-flag": {
@@ -1215,9 +1398,9 @@
"dev": true
},
"is-installed-globally": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.1.tgz",
"integrity": "sha512-oiEcGoQbGc+3/iijAijrK2qFpkNoNjsHOm/5V5iaeydyrS/hnwaRCEgH5cpW0P3T1lSjV5piB7S5b5lEugNLhg==",
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz",
"integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==",
"dev": true,
"requires": {
"global-dirs": "^2.0.1",
@@ -1266,11 +1449,81 @@
"dev": true
},
"isbinaryfile": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.5.tgz",
"integrity": "sha512-Jvz0gpTh1AILHMCBUyqq7xv1ZOQrxTDwyp1/QUq1xFpOBvp4AH5uEobPePJht8KnBGqQIH7We6OR73mXsjG0cA==",
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz",
"integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==",
"dev": true
},
"jake": {
"version": "10.6.1",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.6.1.tgz",
"integrity": "sha512-pHUK3+V0BjOb1XSi95rbBksrMdIqLVC9bJqDnshVyleYsET3H0XAq+3VB2E3notcYvv4wRdRHn13p7vobG+wfQ==",
"dev": true,
"requires": {
"async": "0.9.x",
"chalk": "^2.4.2",
"filelist": "^1.0.1",
"minimatch": "^3.0.4"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"js-yaml": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
@@ -1301,9 +1554,9 @@
"optional": true
},
"json5": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz",
"integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
@@ -1372,9 +1625,9 @@
}
},
"make-dir": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz",
"integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dev": true,
"requires": {
"semver": "^6.0.0"
@@ -1430,9 +1683,9 @@
"dev": true
},
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
@@ -1522,9 +1775,9 @@
"dev": true
},
"p-limit": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
"integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
@@ -1661,16 +1914,15 @@
}
},
"read-config-file": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-5.0.2.tgz",
"integrity": "sha512-tVt1lsiSjs+FtL/vtfCivqtKR1UNk3BB3uPJQvJqkgtAYDvZjo0xyXFYSVmzaTcO+Jdi5G7O2K2vDV+p1M/oug==",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.0.0.tgz",
"integrity": "sha512-PHjROSdpceKUmqS06wqwP92VrM46PZSTubmNIMJ5DrMwg1OgenSTSEHIkCa6TiOJ+y/J0xnG1fFwG3M+Oi1aNA==",
"dev": true,
"requires": {
"dotenv": "^8.2.0",
"dotenv-expand": "^5.1.0",
"fs-extra": "^8.1.0",
"js-yaml": "^3.13.1",
"json5": "^2.1.1",
"json5": "^2.1.2",
"lazy-val": "^1.0.4"
}
},
@@ -1720,9 +1972,9 @@
"dev": true
},
"resolve": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz",
"integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==",
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
"integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
"dev": true,
"requires": {
"path-parse": "^1.0.6"
@@ -1746,9 +1998,9 @@
}
},
"roarr": {
"version": "2.15.2",
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.2.tgz",
"integrity": "sha512-jmaDhK9CO4YbQAV8zzCnq9vjAqeO489MS5ehZ+rXmFiPFFE6B+S9KYO6prjmLJ5A0zY3QxVlQdrIya7E/azz/Q==",
"version": "2.15.3",
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.3.tgz",
"integrity": "sha512-AEjYvmAhlyxOeB9OqPUzQCo3kuAkNfuDk/HqWbZdFsqDFpapkTjiw+p4svNEoRLvuqNTxqfL+s+gtD4eDgZ+CA==",
"dev": true,
"optional": true,
"requires": {
@@ -1787,9 +2039,9 @@
"dev": true
},
"semver": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz",
"integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==",
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
"dev": true
},
"semver-compare": {
@@ -1909,9 +2161,9 @@
"dev": true
},
"source-map-support": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
"integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
@@ -1929,9 +2181,9 @@
}
},
"spdx-exceptions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
"integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
"dev": true
},
"spdx-expression-parse": {
@@ -2148,6 +2400,16 @@
"xdg-basedir": "^4.0.0"
},
"dependencies": {
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"pupa": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz",
@@ -2188,6 +2450,11 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"v8-compile-cache": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
"integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g=="
},
"validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
@@ -2296,9 +2563,9 @@
}
},
"yargs-parser": {
"version": "18.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz",
"integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==",
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "vripper-electron",
"version": "2.11.2",
"version": "2.12.0",
"description": "A ripper for vipergirls.to built using web technolgies",
"main": "main.js",
"author": "death-claw <53543762+death-claw@users.noreply.github.com>",
@@ -70,8 +70,8 @@
"dist": "node pre-build.js && electron-builder"
},
"devDependencies": {
"electron": "^8.2.0",
"electron-builder": "^22.4.1"
"electron": "8.2.4",
"electron-builder": "22.6.0"
},
"dependencies": {
"axios": "^0.19.0",
@@ -79,6 +79,7 @@
"copy-dir": "^1.1.0",
"electron-context-menu": "^0.13.0",
"get-port": "^5.0.0",
"rimraf": "^2.6.3"
"rimraf": "^2.6.3",
"v8-compile-cache": "^2.1.0"
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>tn.mnlr</groupId>
<artifactId>vripper</artifactId>
<version>2.11.2</version>
<version>2.12.0</version>
</parent>
<artifactId>vripper-electron</artifactId>
<name>vripper-electron</name>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>tn.mnlr</groupId>
<artifactId>vripper</artifactId>
<version>2.11.2</version>
<version>2.12.0</version>
</parent>
<artifactId>vripper-server</artifactId>
<name>vripper-server</name>
@@ -43,7 +43,7 @@ public class Post {
private Map<String, Object> metadata;
private AtomicInteger done = new AtomicInteger(0);
private final AtomicInteger done = new AtomicInteger(0);
private int total;
@@ -53,6 +53,8 @@ public class Post {
private String forum;
private String securityToken;
@Setter
private String postFolderName;
@@ -60,7 +62,7 @@ public class Post {
this.appStateService = SpringContext.getBean(AppStateService.class);
}
public Post(String title, String url, List<Image> images, Map<String, Object> metadata, String postId, String threadId, String threadTitle, String forum) throws PostParseException {
public Post(String title, String url, List<Image> images, Map<String, Object> metadata, String postId, String threadId, String threadTitle, String forum, String securityToken) throws PostParseException {
this();
this.title = title;
this.url = url;
@@ -70,6 +72,7 @@ public class Post {
this.forum = forum;
this.threadId = threadId;
this.threadTitle = threadTitle;
this.securityToken = securityToken;
this.hosts = this.images.stream().map(Image::getHost).map(Host::getHost).collect(Collectors.toSet());
total = images.size();
status = Status.STOPPED;
@@ -178,6 +178,19 @@ abstract public class Host {
imageName = imageName.substring(0, imageName.length() - toReplace.length()) + ".jpg";
}
}
} else if (reader.getFormatName().toUpperCase().equals("PNG")) {
String imageNameLC = imageName.toLowerCase();
if (!imageNameLC.endsWith("_png")) {
imageName += ".png";
} else {
String toReplace = null;
if (imageNameLC.endsWith("_png")) {
toReplace = "_png";
}
if (toReplace != null) {
imageName = imageName.substring(0, imageName.length() - toReplace.length()) + ".png";
}
}
}
} catch (Exception e) {
throw new HostException("Failed to guess image format", e);
@@ -46,7 +46,7 @@ public class PathService {
}
public synchronized final void createDefaultPostFolder(Post post) {
File sourceFolder = _getDownloadDestinationFolder(post.getForum(), post.getThreadTitle(), post.getTitle());
File sourceFolder = _getDownloadDestinationFolder(post.getForum(), post.getThreadTitle(), sanitize(post.getTitle()));
File destFolder = makeDirs(sourceFolder);
post.setPostFolderName(destFolder.getName());
}
@@ -56,7 +56,7 @@ public class PathService {
if (post.getPostFolderName() == null) {
return;
}
File newDestFolder = makeDirs(_getDownloadDestinationFolder(post.getForum(), post.getThreadTitle(), altName));
File newDestFolder = makeDirs(_getDownloadDestinationFolder(post.getForum(), post.getThreadTitle(), sanitize(altName)));
File currentDesFolder = getDownloadDestinationFolder(post);
post.setPostFolderName(newDestFolder.getName());
@@ -170,6 +170,7 @@ class VRApiPostHandler extends DefaultHandler {
private String threadTitle;
private String postTitle;
private String forum;
private String userHash;
private int previewCounter = 0;
private int index = 0;
private int imageCount;
@@ -200,6 +201,9 @@ class VRApiPostHandler extends DefaultHandler {
case "forum":
forum = attributes.getValue("title").trim();
break;
case "user":
userHash = attributes.getValue("hash").trim();
break;
case "thread":
threadTitle = attributes.getValue("title").trim();
break;
@@ -249,7 +253,8 @@ class VRApiPostHandler extends DefaultHandler {
postId,
threadId,
threadTitle,
forum);
forum,
userHash);
} catch (PostParseException e) {
logger.error(String.format("Error occurred while parsing postId %s", postId));
}
@@ -5,7 +5,6 @@ import lombok.Getter;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.cookie.Cookie;
@@ -17,13 +16,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import tn.mnlr.vripper.entities.Post;
import tn.mnlr.vripper.exception.VripperException;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
@@ -32,13 +29,11 @@ public class VipergirlsAuthService {
private static final Logger logger = LoggerFactory.getLogger(VipergirlsAuthService.class);
private final ConnectionManager cm;
private final HtmlProcessorService htmlProcessorService;
private final XpathService xpathService;
private final AppSettingsService appSettingsService;
private final CommonExecutor commonExecutor;
@Getter
private HttpClientContext context = HttpClientContext.create();
private final HttpClientContext context = HttpClientContext.create();
@Getter
private boolean authenticated = false;
@@ -47,13 +42,11 @@ public class VipergirlsAuthService {
private String loggedUser;
@Getter
private PublishProcessor<String> loggedInUser = PublishProcessor.create();
private final PublishProcessor<String> loggedInUser = PublishProcessor.create();
@Autowired
public VipergirlsAuthService(ConnectionManager cm, HtmlProcessorService htmlProcessorService, XpathService xpathService, AppSettingsService appSettingsService, CommonExecutor commonExecutor) {
public VipergirlsAuthService(ConnectionManager cm, AppSettingsService appSettingsService, CommonExecutor commonExecutor) {
this.cm = cm;
this.htmlProcessorService = htmlProcessorService;
this.xpathService = xpathService;
this.appSettingsService = appSettingsService;
this.commonExecutor = commonExecutor;
}
@@ -95,14 +88,10 @@ public class VipergirlsAuthService {
HttpPost postAuth = cm.buildHttpPost("https://vipergirls.to/login.php?do=login");
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("vb_login_username", username));
params.add(new BasicNameValuePair("vb_login_password", ""));
params.add(new BasicNameValuePair("vb_login_password_hint", "Password"));
params.add(new BasicNameValuePair("cookieuser", "1"));
params.add(new BasicNameValuePair("securitytoken", "guest"));
params.add(new BasicNameValuePair("do", "login"));
params.add(new BasicNameValuePair("vb_login_md5password", password));
params.add(new BasicNameValuePair("vb_login_md5password_utf", password));
try {
postAuth.setEntity(new UrlEncodedFormEntity(params));
} catch (Exception e) {
@@ -163,56 +152,21 @@ public class VipergirlsAuthService {
}
commonExecutor.getGeneralExecutor().submit(() -> {
try {
postThanks(post, getSecurityToken(post.getUrl()));
postThanks(post);
} catch (Exception e) {
logger.error(String.format("Failed to leave a thanks for url %s, post id %s", post.getUrl(), post.getPostId()), e);
}
});
}
private String getSecurityToken(String url) throws VripperException {
String securityToken;
HttpGet httpGet = cm.buildHttpGet(url);
httpGet.addHeader("Referer", "https://vipergirls.to/");
httpGet.addHeader("Host", "vipergirls.to");
CloseableHttpClient client = cm.getClient().build();
try (CloseableHttpResponse response = client.execute(httpGet, context)) {
String postPage = EntityUtils.toString(response.getEntity());
Document document = htmlProcessorService.clean(postPage);
String thanksUrl = xpathService
.getAsNode(document, "//li[contains(@id,'post_')][not(contains(@id,'post_thank'))]//a[@class='post_thanks_button']")
.getAttributes()
.getNamedItem("href")
.getTextContent()
.trim();
securityToken = Arrays.stream(thanksUrl.split("&amp;"))
.filter(v -> v.startsWith("securitytoken"))
.findAny()
.orElse("")
.replace("securitytoken=", "");
EntityUtils.consumeQuietly(response.getEntity());
} catch (Exception e) {
throw new VripperException(e);
}
return securityToken;
}
private void postThanks(Post post, String securityKey) throws VripperException {
private void postThanks(Post post) throws VripperException {
HttpPost postThanks = cm.buildHttpPost("https://vipergirls.to/post_thanks.php");
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("do", "post_thanks_add"));
params.add(new BasicNameValuePair("using_ajax", "1"));
params.add(new BasicNameValuePair("p", post.getPostId()));
params.add(new BasicNameValuePair("securitytoken", securityKey));
params.add(new BasicNameValuePair("securitytoken", post.getSecurityToken()));
try {
postThanks.setEntity(new UrlEncodedFormEntity(params));
} catch (Exception e) {
@@ -225,10 +179,16 @@ public class VipergirlsAuthService {
CloseableHttpClient client = cm.getClient().build();
try (CloseableHttpResponse response = client.execute(postThanks, context)) {
post.getMetadata().put(Post.METADATA.THANKED.name(), true);
if (response.getStatusLine().getStatusCode() / 100 == 2) {
post.getMetadata().put(Post.METADATA.THANKED.name(), true);
}
EntityUtils.consumeQuietly(response.getEntity());
} catch (Exception e) {
throw new VripperException(e);
}
if (!((Boolean) post.getMetadata().get(Post.METADATA.THANKED.name()))) {
throw new VripperException("Failed to leave");
}
}
}
@@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import tn.mnlr.vripper.entities.Post;
import tn.mnlr.vripper.entities.Post.METADATA;
import tn.mnlr.vripper.exception.PostParseException;
import tn.mnlr.vripper.q.ExecutionService;
import tn.mnlr.vripper.services.*;
@@ -154,6 +155,25 @@ public class PostRestEndpoint {
return postToRename;
}
@PostMapping("/post/rename/first")
@ResponseStatus(value = HttpStatus.OK)
public List<PostId> renameFirst(@RequestBody @NonNull List<PostId> postToRename) {
for (PostId postId : postToRename) {
Post post = appStateExchange.getPost(postId.getPostId());
if (post.getMetadata().get(METADATA.RESOLVED_NAME.name()) != null) {
List<String> resolvedNames = ((List<String>) post.getMetadata().get(METADATA.RESOLVED_NAME.name()));
if (!resolvedNames.isEmpty()) {
try {
pathService.rename(post, resolvedNames.get(0));
} catch (Exception e) {
throw new ServerErrorException(e.getMessage());
}
}
}
}
return postToRename;
}
@PostMapping("/post/clear/all")
@ResponseStatus(value = HttpStatus.OK)
public RemoveAllResult clearAll() {
+3 -15
View File
@@ -28,13 +28,7 @@
"src/assets"
],
"styles": [
"src/styles.scss",
"./node_modules/photoswipe/dist/photoswipe.css",
"./node_modules/photoswipe/dist/default-skin/default-skin.css"
],
"scripts": [
"./node_modules/photoswipe/dist/photoswipe.min.js",
"./node_modules/photoswipe/dist/photoswipe-ui-default.min.js"
"src/styles.scss"
]
},
"configurations": {
@@ -93,13 +87,7 @@
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"src/styles.scss",
"./node_modules/photoswipe/dist/photoswipe.css",
"./node_modules/photoswipe/dist/default-skin/default-skin.css"
],
"scripts": [
"./node_modules/photoswipe/dist/photoswipe.min.js",
"./node_modules/photoswipe/dist/photoswipe-ui-default.min.js"
"src/styles.scss"
],
"assets": [
"src/favicon.ico",
@@ -151,4 +139,4 @@
}
},
"defaultProject": "vripper-ui"
}
}
+1511 -1028
View File
File diff suppressed because it is too large Load Diff
+40 -41
View File
@@ -1,6 +1,6 @@
{
"name": "vripper-ui",
"version": "2.11.2",
"version": "2.12.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
@@ -13,47 +13,46 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^9.1.0",
"@angular/cdk": "^9.2.0",
"@angular/common": "~9.1.0",
"@angular/compiler": "~9.1.0",
"@angular/core": "~9.1.0",
"@angular/flex-layout": "^9.0.0-beta.29",
"@angular/forms": "~9.1.0",
"@angular/material": "^9.2.0",
"@angular/platform-browser": "~9.1.0",
"@angular/platform-browser-dynamic": "~9.1.0",
"@angular/router": "~9.1.0",
"ag-grid-angular": "^23.0.2",
"ag-grid-community": "^23.0.2",
"core-js": "^3.6.4",
"ngx-clipboard": "^13.0.0",
"ngx-electron": "^2.2.0",
"photoswipe": "^4.1.3",
"rxjs": "~6.5.4",
"tslib": "^1.11.1",
"zone.js": "~0.10.3"
"@angular/animations": "9.1.4",
"@angular/cdk": "9.2.1",
"@angular/common": "9.1.4",
"@angular/compiler": "9.1.4",
"@angular/core": "9.1.4",
"@angular/flex-layout": "9.0.0-beta.29",
"@angular/forms": "9.1.4",
"@angular/material": "9.2.1",
"@angular/platform-browser": "9.1.4",
"@angular/platform-browser-dynamic": "9.1.4",
"@angular/router": "9.1.4",
"ag-grid-angular": "23.0.3",
"ag-grid-community": "23.0.2",
"core-js": "3.6.5",
"ngx-clipboard": "13.0.0",
"ngx-electron": "2.2.0",
"rxjs": "6.5.5",
"tslib": "1.11.1",
"zone.js": "0.10.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.901.0",
"@angular/cli": "~9.1.0",
"@angular/compiler-cli": "^9.1.0",
"@angular/language-service": "~9.1.0",
"@types/jasmine": "~3.5.10",
"@types/jasminewd2": "~2.0.8",
"@types/node": "^12.12.32",
"codelyzer": "^5.2.2",
"electron": "^8.2.0",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~5.0.1",
"karma": "^4.4.1",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~2.1.1",
"karma-jasmine": "~3.1.1",
"karma-jasmine-html-reporter": "^1.5.3",
"protractor": "~5.4.3",
"ts-node": "~8.8.1",
"tslint": "~6.1.0",
"typescript": "~3.8.3"
"@angular-devkit/build-angular": "0.901.4",
"@angular/cli": "9.1.4",
"@angular/compiler-cli": "9.1.4",
"@angular/language-service": "9.1.4",
"@types/jasmine": "3.5.10",
"@types/jasminewd2": "2.0.8",
"@types/node": "12.12.37",
"codelyzer": "5.2.2",
"electron": "8.2.4",
"jasmine-core": "3.5.0",
"jasmine-spec-reporter": "5.0.2",
"karma": "5.0.4",
"karma-chrome-launcher": "3.1.0",
"karma-coverage-istanbul-reporter": "2.1.1",
"karma-jasmine": "3.1.1",
"karma-jasmine-html-reporter": "1.5.3",
"protractor": "5.4.4",
"ts-node": "8.9.1",
"tslint": "6.1.2",
"typescript": "3.8.3"
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>tn.mnlr</groupId>
<artifactId>vripper</artifactId>
<version>2.11.2</version>
<version>2.12.0</version>
</parent>
<artifactId>vripper-ui</artifactId>
<name>vripper-ui</name>
+21 -29
View File
@@ -1,39 +1,36 @@
import {PostContextMenuComponent} from './posts/post-context-menu.component';
import {PostDetailsContextMenuComponent} from './post-detail/post-details-context-menu.component';
import {AppPreviewDirective} from './common/preview-tooltip.directive';
import {PostContextMenuComponent} from './posts/context-menu/post-context-menu.component';
import {PostDetailsContextMenuComponent} from './post-progress/context-menu/post-details-context-menu.component';
import {AppPreviewDirective} from './domain/preview-tooltip.directive';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {NgModule} from '@angular/core';
import {AppComponent} from './app.component';
import {MaterialModule} from './material.module';
import {PostsComponent} from './posts/posts.component';
import {PostDetailComponent} from './post-detail/post-detail.component';
import {PostDetailComponent} from './post-progress/post-detail.component';
import {FlexLayoutModule} from '@angular/flex-layout';
import {AppRoutingModule} from './app-routing.module';
import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import {HttpClientModule} from '@angular/common/http';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {AgGridModule} from 'ag-grid-angular';
import {PostProgressRendererComponent} from './posts/post-progress.renderer.component';
import {PostDetailsProgressRendererComponent} from './post-detail/post-details-progress.component';
import {PostProgressRendererComponent} from './posts/renderer/post-progress.renderer.component';
import {PostDetailsProgressRendererComponent} from './post-progress/renderer/post-details-progress.component';
import {LoginComponent} from './login/login.component';
import {XhrInterceptorService} from './xhr-interceptor.service';
import {HomeComponent} from './home/home.component';
import {SettingsComponent} from './settings/settings.component';
import {NgxElectronModule} from 'ngx-electron';
import {ConfirmDialogComponent} from './common/confirmation-component/confirmation-dialog';
import {ConfirmDialogComponent} from './confirmation-component/confirmation-dialog';
import {MultiPostComponent} from './multi-post/multi-post.component';
import {OverlayModule} from '@angular/cdk/overlay';
import {AppPreviewComponent} from './common/preview-tooltip.component';
import {UrlRendererComponent} from './multi-post/url-renderer.component';
import {AppPreviewComponent} from './domain/preview-tooltip.component';
import {UrlRendererComponent} from './multi-post/renderer/url-renderer.component';
import {FilterComponent} from './filter/filter.component';
import {ScanComponent} from './scan/scan.component';
import {StatusBarComponent} from './status-bar/status-bar.component';
import {ToolbarComponent} from './toolbar/toolbar.component';
import {GrabQueueComponent} from './grab-queue/grab-queue.component';
import {UrlGrabRendererComponent} from './grab-queue/url-renderer.component';
import {GalleryComponent} from './gallery/gallery.component';
import {PhotoSwipeComponent} from './photo-swipe/photo-swipe.component';
import {UrlGrabRendererComponent} from './grab-queue/renderer/url-renderer.component';
import {ClipboardModule} from 'ngx-clipboard';
import {AlternativeTitleComponent} from './posts/alternative-title/alternative-title.component';
@@ -60,20 +57,17 @@ import {AlternativeTitleComponent} from './posts/alternative-title/alternative-t
GrabQueueComponent,
PostContextMenuComponent,
PostDetailsContextMenuComponent,
GalleryComponent,
PhotoSwipeComponent,
AlternativeTitleComponent
],
entryComponents: [
PostDetailComponent,
SettingsComponent,
ConfirmDialogComponent,
AppPreviewComponent,
ScanComponent,
MultiPostComponent,
PostContextMenuComponent,
PostDetailsContextMenuComponent,
GalleryComponent
// PostDetailComponent,
// SettingsComponent,
// ConfirmDialogComponent,
// AppPreviewComponent,
// ScanComponent,
// MultiPostComponent,
// PostContextMenuComponent,
// PostDetailsContextMenuComponent
],
imports: [
BrowserAnimationsModule,
@@ -93,9 +87,7 @@ import {AlternativeTitleComponent} from './posts/alternative-title/alternative-t
OverlayModule,
ClipboardModule
],
providers: [
{provide: HTTP_INTERCEPTORS, useClass: XhrInterceptorService, multi: true}
],
bootstrap: [AppComponent]
})
export class AppModule {}
export class AppModule {
}
+1 -1
View File
@@ -1,4 +1,4 @@
import {Settings} from './common/settings.model';
import {Settings} from './domain/settings.model';
import {environment} from 'src/environments/environment';
import {MatDialog} from '@angular/material/dialog';
import {HttpClient} from '@angular/common/http';
+8 -7
View File
@@ -1,10 +1,10 @@
import { Subject, Observable } from 'rxjs';
import { ElectronService } from 'ngx-electron';
import { Injectable } from '@angular/core';
import { Clipboard } from 'electron';
import { HttpClient } from '@angular/common/http';
import { Settings } from './common/settings.model';
import { ServerService } from './server-service';
import {Observable, Subject} from 'rxjs';
import {ElectronService} from 'ngx-electron';
import {Injectable} from '@angular/core';
import {Clipboard} from 'electron';
import {HttpClient} from '@angular/common/http';
import {Settings} from './domain/settings.model';
import {ServerService} from './server-service';
@Injectable({
providedIn: 'root'
@@ -13,6 +13,7 @@ export class ClipboardService {
private links$: Subject<string> = new Subject();
private interval: NodeJS.Timer;
private lastText = '';
constructor(
private electronService: ElectronService,
private serverService: ServerService,
@@ -0,0 +1,3 @@
export interface PostId {
postId: string;
}
@@ -1,9 +1,9 @@
import { AppPreviewComponent } from './preview-tooltip.component';
import { Directive, Input, HostListener, ComponentRef, OnInit, ElementRef, NgZone, OnDestroy } from '@angular/core';
import { OverlayRef, Overlay, OverlayPositionBuilder } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import {AppPreviewComponent} from './preview-tooltip.component';
import {ComponentRef, Directive, ElementRef, HostListener, Input, NgZone, OnDestroy, OnInit} from '@angular/core';
import {Overlay, OverlayPositionBuilder, OverlayRef} from '@angular/cdk/overlay';
import {ComponentPortal} from '@angular/cdk/portal';
@Directive({ selector: '[appPreview]' })
@Directive({selector: '[appPreview]'})
export class AppPreviewDirective implements OnInit, OnDestroy {
private overlayRef: OverlayRef;
tooltipPortal = new ComponentPortal(AppPreviewComponent);
@@ -1,40 +0,0 @@
<div class="dialog-container" fxLayout="column" style="height: 100%;">
<div fxFlex="nogrow">
<h2 class="no-wrap" mat-dialog-title>{{ dialogData.title }}</h2>
</div>
<mat-dialog-content fxFlex="grow">
<div class="container">
<div class="masonry">
<ng-container *ngFor="let img of images | async; let i = index">
<figure class="masonry-brick">
<img
[style.opacity]="img._initialized ? 1 : 0"
[style.visibility]="img._initialized ? 'visible' : 'hidden'"
(click)="openSlideshow(i)"
(load)="loaded(img)"
[src]="img.msrc"
/>
</figure>
</ng-container>
<app-photo-swipe #photoSwipe></app-photo-swipe>
</div>
</div>
</mat-dialog-content>
<div style="height: 5px; width: 100%; margin-top: 8px">
<div *ngIf="loadProgress | async as progress">
<mat-progress-bar
*ngIf="!progress.done"
[mode]="progress.loading ? 'determinate' : 'indeterminate'"
[value]="progress.progress"
></mat-progress-bar>
</div>
</div>
<mat-dialog-actions align="end" fxFlex="nogrow">
<button (click)="refresh()" [disabled]="disableRefresh | async" color="primary" mat-raised-button>
<mat-icon>refresh</mat-icon>
<span>Refresh</span>
</button>
<button mat-dialog-close mat-raised-button>Close</button>
</mat-dialog-actions>
</div>
@@ -1,24 +0,0 @@
.container {
height: 100%;
}
.masonry {
width: 100%;
}
.masonry-brick {
height: 250px;
width: 250px;
display: inline-block;
cursor: pointer;
margin: 0 3px;
}
img {
opacity: 0;
visibility: hidden;
transition: opacity 0.5s ease-in, visibility 0.5s;
object-fit: cover;
height: 250px;
width: 250px;
}
@@ -1,118 +0,0 @@
import {IImage, PhotoSwipeComponent} from './../photo-swipe/photo-swipe.component';
import {ChangeDetectionStrategy, Component, Inject, NgZone, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {MatSnackBar} from '@angular/material/snack-bar';
import {ServerService} from '../server-service';
import {PostState} from '../posts/post-state.model';
import {BehaviorSubject, Subject} from 'rxjs';
class Image extends IImage {
constructor(
public title: string,
public src: string,
public msrc: string,
public w: number,
public h: number,
public _initialized: boolean
) {
super(src, msrc, w, h);
}
}
interface Progress {
loading: boolean;
progress: number;
done: boolean;
}
const initialProgressState: Progress = { loading: false, progress: 0, done: false };
@Component({
selector: 'app-gallery',
templateUrl: './gallery.component.html',
styleUrls: ['./gallery.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class GalleryComponent implements OnInit, OnDestroy {
constructor(
public dialogRef: MatDialogRef<GalleryComponent>,
@Inject(MAT_DIALOG_DATA) public dialogData: PostState,
private httpClient: HttpClient,
private serverService: ServerService,
private _snackBar: MatSnackBar,
private ngZone: NgZone
) {}
images: Subject<Image[]> = new BehaviorSubject([]);
_images: Image[] = [];
disableRefresh: Subject<boolean> = new BehaviorSubject(true);
loadProgress: Subject<Progress> = new BehaviorSubject(initialProgressState);
loadedImages = 0;
@ViewChild('photoSwipe') photoSwipe: PhotoSwipeComponent;
loaded(img: Image) {
img._initialized = true;
this.loadedImages++;
this.loadProgress.next({
loading: true,
progress: Math.floor((this.loadedImages / this._images.length) * 100),
done: this.loadedImages === this._images.length
});
if (this.loadedImages === this._images.length) {
this.disableRefresh.next(false);
}
}
ngOnInit() {
this.refresh();
}
ngOnDestroy() {}
openSlideshow(index: number) {
this.photoSwipe.openGallery(this._images, {
index: index,
getThumbBoundsFn: this.getThumbBoundsFn,
showHideOpacity: true,
preload: [1, 8]
});
}
getThumbBoundsFn(index: number) {
// See Options -> getThumbBoundsFn section of documentation for more info
const thumbnail = document.querySelectorAll('.masonry-brick img')[index],
pageYScroll = window.pageYOffset || document.documentElement.scrollTop,
rect = thumbnail.getBoundingClientRect();
return { x: rect.left, y: rect.top + pageYScroll, w: rect.width };
}
refresh() {
this.loadProgress.next(initialProgressState);
this.loadedImages = 0;
this.disableRefresh.next(true);
this.httpClient.get<Image[]>(this.serverService.baseUrl + '/gallery/' + this.dialogData.postId).subscribe(
response => {
this.ngZone.run(() => {
response.forEach(i => {
i.src = this.serverService.baseUrl + '/image/' + this.dialogData.postId + '/' + i.src;
i.msrc = this.serverService.baseUrl + '/image/thumb/' + this.dialogData.postId + '/' + i.msrc;
i._initialized = false;
});
this._images = response;
this.images.next(this._images);
});
},
error => {
this.ngZone.run(() => {
this.dialogRef.close();
this._snackBar.open(error?.error?.message.error || 'Unexpected error, check log file', null, {
duration: 5000
});
});
}
);
}
}
@@ -1,5 +1,5 @@
import {LinkCollectorService} from './../link-collector.service';
import {UrlGrabRendererComponent} from './url-renderer.component';
import {UrlGrabRendererComponent} from './renderer/url-renderer.component';
import {GrabQueueDataSource} from './grab-queue.datasource';
import {ChangeDetectionStrategy, Component, NgZone, OnInit} from '@angular/core';
import {GridOptions} from 'ag-grid-community';
@@ -1,6 +1,6 @@
import {GrabQueueState} from './grab-queue.model';
import {CMD} from './../common/cmd.enum';
import {WSMessage} from './../common/ws-message.model';
import {GrabQueueState} from '../domain/grab-queue.model';
import {CMD} from '../domain/cmd.enum';
import {WSMessage} from '../domain/ws-message.model';
import {Subscription} from 'rxjs';
import {WsConnectionService} from '../ws-connection.service';
import {NotificationService} from '../notification.service';
@@ -1,16 +1,16 @@
import {ElectronService} from 'ngx-electron';
import {MultiPostComponent} from '../multi-post/multi-post.component';
import {MultiPostComponent} from '../../multi-post/multi-post.component';
import {ChangeDetectionStrategy, Component, NgZone, OnDestroy, OnInit} from '@angular/core';
import {AgRendererComponent} from 'ag-grid-angular';
import {GridApi, ICellRendererParams} from 'ag-grid-community';
import {GrabQueueState} from './grab-queue.model';
import {GrabQueueState} from '../../domain/grab-queue.model';
import {MatDialog} from '@angular/material/dialog';
import {MatSnackBar} from '@angular/material/snack-bar';
import {BreakpointObserver, Breakpoints, BreakpointState} from '@angular/cdk/layout';
import {BehaviorSubject, Observable, Subject, Subscription} from 'rxjs';
import {ServerService} from '../server-service';
import {ServerService} from '../../server-service';
import {HttpClient} from '@angular/common/http';
import {WsConnectionService} from "../ws-connection.service";
import {WsConnectionService} from "../../ws-connection.service";
@Component({
selector: 'app-url-cell-grab',
+3 -3
View File
@@ -1,6 +1,6 @@
import { Component, OnInit, HostBinding } from '@angular/core';
import { SafeStyle, DomSanitizer } from '@angular/platform-browser';
import { Credentials } from '../common/credential.model';
import {Component, HostBinding, OnInit} from '@angular/core';
import {DomSanitizer, SafeStyle} from '@angular/platform-browser';
import {Credentials} from '../domain/credential.model';
@Component({
selector: 'app-login',
@@ -1,5 +1,5 @@
import {finalize} from 'rxjs/operators';
import {VRPostParse} from './../common/vr-post-parse.model';
import {VRPostParse} from '../domain/vr-post-parse.model';
import {
AfterViewInit,
ChangeDetectionStrategy,
@@ -13,11 +13,11 @@ import {
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {MatSnackBar} from '@angular/material/snack-bar';
import {GridOptions, RowNode} from 'ag-grid-community';
import {UrlRendererComponent} from './url-renderer.component';
import {UrlRendererComponent} from './renderer/url-renderer.component';
import {BehaviorSubject, Subject, Subscription} from 'rxjs';
import {HttpClient} from '@angular/common/http';
import {ServerService} from '../server-service';
import {GrabQueueState} from '../grab-queue/grab-queue.model';
import {GrabQueueState} from '../domain/grab-queue.model';
@Component({
selector: 'app-multi-post',
@@ -0,0 +1,3 @@
<a href="javascript:void(0)" [appPreview]="postResult.previews" (click)="goTo()"
>https://vipergirls/threads/?p={{ postResult.postId }}</a
>
@@ -1,20 +1,17 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
import { AgRendererComponent } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
import { VRPostParse } from '../common/vr-post-parse.model';
import { ElectronService } from 'ngx-electron';
import {ChangeDetectionStrategy, Component, OnDestroy, OnInit} from '@angular/core';
import {AgRendererComponent} from 'ag-grid-angular';
import {ICellRendererParams} from 'ag-grid-community';
import {VRPostParse} from '../../domain/vr-post-parse.model';
import {ElectronService} from 'ngx-electron';
@Component({
selector: 'app-url-cell',
template: `
<a href="javascript:void(0)" [appPreview]="postResult.previews" (click)="goTo()"
>https://vipergirls/threads/?p={{ postResult.postId }}</a
>
`,
templateUrl: 'url-renderer.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UrlRendererComponent implements OnInit, OnDestroy, AgRendererComponent {
constructor(public electronService: ElectronService) {}
constructor(public electronService: ElectronService) {
}
postResult: VRPostParse;
params: ICellRendererParams;
@@ -1,51 +0,0 @@
<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div #photoSwipe aria-hidden="true" class="pswp" role="dialog" tabindex="-1">
<!-- Background of PhotoSwipe.
It's a separate element as animating opacity is faster than rgba(). -->
<div class="pswp__bg"></div>
<!-- Slides wrapper with overflow:hidden. -->
<div class="pswp__scroll-wrap">
<!-- Container that holds slides.
PhotoSwipe keeps only 3 of them in the DOM to save memory.
Don't modify these 3 pswp__item elements, data is added later on. -->
<div class="pswp__container">
<div class="pswp__item"></div>
<div class="pswp__item"></div>
<div class="pswp__item"></div>
</div>
<!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
<div class="pswp__ui pswp__ui--hidden">
<div class="pswp__top-bar">
<!-- Controls are self-explanatory. Order can be changed. -->
<div class="pswp__counter"></div>
<button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
<!-- Preloader demo https://codepen.io/dimsemenov/pen/yyBWoR -->
<!-- element will get class pswp__preloader--active when preloader is running -->
<div class="pswp__preloader">
<div class="pswp__preloader__icn">
<div class="pswp__preloader__cut">
<div class="pswp__preloader__donut"></div>
</div>
</div>
</div>
</div>
<button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"></button>
<button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"></button>
<div class="pswp__caption">
<div class="pswp__caption__center"></div>
</div>
</div>
</div>
</div>
@@ -1,23 +0,0 @@
import {Component, ElementRef, ViewChild} from '@angular/core';
import PhotoSwipe from 'photoswipe';
import PhotoSwipeUI_Default from 'photoswipe/dist/photoswipe-ui-default';
export class IImage {
constructor(public src: string, public msrc: string, public w: number, public h: number) {}
}
@Component({
selector: 'app-photo-swipe',
templateUrl: './photo-swipe.component.html',
styleUrls: ['./photo-swipe.component.css']
})
export class PhotoSwipeComponent {
@ViewChild('photoSwipe') photoSwipe: ElementRef;
openGallery(images: IImage[], options?: any) {
options = { ...options, history: false };
const gallery = new PhotoSwipe(this.photoSwipe.nativeElement, PhotoSwipeUI_Default, images, options);
gallery.init();
}
}
@@ -1,16 +0,0 @@
import {TestBed} from '@angular/core/testing';
import {CtxtMenuService} from './ctxt-menu.service';
describe('CtxtMenuService', () => {
let service: CtxtMenuService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CtxtMenuService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -1,5 +1,5 @@
import {ComponentRef, Injectable} from '@angular/core';
import {PostDetails} from "./post-details.model";
import {PostDetails} from "../../domain/post-details.model";
import {PostDetailsContextMenuComponent} from "./post-details-context-menu.component";
import {fromEvent, Subscription} from "rxjs";
import {filter, take} from "rxjs/operators";
@@ -1,5 +1,5 @@
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {PostDetails} from './post-details.model';
import {PostDetails} from '../../domain/post-details.model';
import {CtxtMenuService} from "./ctxt-menu.service";
import {ElectronService} from "ngx-electron";
@@ -1,11 +1,11 @@
import {PostDetailsProgressRendererComponent} from './post-details-progress.component';
import {PostDetailsProgressRendererComponent} from './renderer/post-details-progress.component';
import {WsConnectionService} from './../ws-connection.service';
import {ChangeDetectionStrategy, Component, Inject, NgZone, OnDestroy, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {PostDetailsDataSource} from './post-details.datasource';
import {PostState} from '../posts/post-state.model';
import {PostDetailDatasource} from './post-detail.datasource';
import {PostState} from '../domain/post-state.model';
import {GridOptions} from 'ag-grid-community';
import {CtxtMenuService} from "./ctxt-menu.service";
import {CtxtMenuService} from "./context-menu/ctxt-menu.service";
@Component({
selector: 'app-post-detail',
@@ -54,7 +54,7 @@ export class PostDetailComponent implements OnInit, OnDestroy {
getRowNodeId: (data) => data['url'],
onGridReady: () => {
this.gridOptions.api.sizeColumnsToFit();
this.dataSource = new PostDetailsDataSource(this.wsConnection, this.gridOptions, this.dialogData.postId, this.zone);
this.dataSource = new PostDetailDatasource(this.wsConnection, this.gridOptions, this.dialogData.postId, this.zone);
this.dataSource.connect();
},
onGridSizeChanged: () => this.gridOptions.api.sizeColumnsToFit(),
@@ -65,7 +65,7 @@ export class PostDetailComponent implements OnInit, OnDestroy {
}
gridOptions: GridOptions;
dataSource: PostDetailsDataSource;
dataSource: PostDetailDatasource;
ngOnInit() {
}
@@ -1,11 +1,11 @@
import {GridOptions} from 'ag-grid-community';
import {Subscription} from 'rxjs';
import {WsConnectionService} from '../ws-connection.service';
import {WSMessage} from '../common/ws-message.model';
import {CMD} from '../common/cmd.enum';
import {WSMessage} from '../domain/ws-message.model';
import {CMD} from '../domain/cmd.enum';
import {NgZone} from '@angular/core';
export class PostDetailsDataSource {
export class PostDetailDatasource {
constructor(
private ws: WsConnectionService,
private gridOptions: GridOptions,
@@ -1,4 +1,4 @@
@import '~@angular/material/theming';
@import 'node_modules/@angular/material/theming';
@mixin post-detail-renderer-component-theme($theme) {
$blue: mat-palette($mat-blue);
@@ -1,4 +1,4 @@
import {WsConnectionService} from '../ws-connection.service';
import {WsConnectionService} from '../../ws-connection.service';
import {
AfterViewInit,
ChangeDetectionStrategy,
@@ -10,10 +10,10 @@ import {
} from '@angular/core';
import {AgRendererComponent} from 'ag-grid-angular';
import {BehaviorSubject, Subject, Subscription} from 'rxjs';
import {PostDetails} from './post-details.model';
import {PostDetails} from '../../domain/post-details.model';
import {ICellRendererParams} from 'ag-grid-community';
import {ElectronService} from 'ngx-electron';
import {CtxtMenuService} from "./ctxt-menu.service";
import {CtxtMenuService} from "../context-menu/ctxt-menu.service";
@Component({
selector: 'app-details-cell',
@@ -1,11 +1,11 @@
import {Component, Inject, OnInit} from '@angular/core';
import {ChangeDetectionStrategy, Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {RenamePostModel} from "../../common/rename-post.model";
import {RenamePostModel} from "../../domain/rename-post.model";
import {HttpClient} from "@angular/common/http";
import {ServerService} from "../../server-service";
import {MatSnackBar} from "@angular/material/snack-bar";
import {FormControl, FormGroup, Validators} from "@angular/forms";
import {PostState} from "../post-state.model";
import {PostState} from "../../domain/post-state.model";
export interface AlternativeTitleDialog {
post: PostState
@@ -14,7 +14,8 @@ export interface AlternativeTitleDialog {
@Component({
selector: 'app-alternative-title',
templateUrl: './alternative-title.component.html',
styleUrls: ['./alternative-title.component.scss']
styleUrls: ['./alternative-title.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AlternativeTitleComponent implements OnInit {
@@ -28,6 +29,9 @@ export class AlternativeTitleComponent implements OnInit {
ngOnInit(): void {
this.form.get('altNameSelect').valueChanges.subscribe(v => this.form.get('altNameInput').setValue(v));
if (this.data.post.alternativeTitle == null || this.data.post.alternativeTitle.length == 0) {
this.form.get('altNameInput').setValue(this.data.post.title);
}
}
form = new FormGroup({
@@ -3,7 +3,7 @@ import {ComponentPortal} from "@angular/cdk/portal";
import {PostContextMenuComponent} from "./post-context-menu.component";
import {Overlay, OverlayPositionBuilder, OverlayRef} from "@angular/cdk/overlay";
import {fromEvent, Subscription} from "rxjs";
import {PostState} from "./post-state.model";
import {PostState} from "../../domain/post-state.model";
import {filter, take} from "rxjs/operators";
@Injectable({
@@ -0,0 +1,48 @@
<mat-card class="mat-elevation-z4">
<mat-action-list>
<button
(click)="restart()"
*ngIf="
(postState.status === 'COMPLETE' && postState.progress !== 100) ||
postState.status === 'ERROR' ||
postState.status === 'STOPPED'
"
mat-list-item
>
<mat-icon>play_arrow</mat-icon>
<span>{{postState.progress == 0 ? 'Start' : 'Resume'}}</span>
</button>
<button
(click)="stop()"
*ngIf="postState.status === 'DOWNLOADING' || postState.status === 'PARTIAL' || postState.status === 'PENDING'"
mat-list-item
>
<mat-icon>stop</mat-icon>
<span>Stop</span>
</button>
<button (click)="remove()" mat-list-item>
<mat-icon>delete</mat-icon>
<span>Remove</span>
</button>
<button (click)="seeDetails()" mat-list-item>
<mat-icon>list</mat-icon>
<span>Files</span>
</button>
<button (click)="useAltTitle()" mat-list-item>
<mat-icon>edit</mat-icon>
<span>Use alternative title</span>
</button>
<button (click)="openRenameDialog()" mat-list-item>
<mat-icon>edit</mat-icon>
<span>Rename</span>
</button>
<button (click)="goTo()" ngxClipboard [cbContent]="postState.url" mat-list-item>
<mat-icon>open_in_new</mat-icon>
<span>Open link</span>
</button>
<button (click)="open()" *ngIf="electronService.isElectronApp" mat-list-item>
<mat-icon>folder</mat-icon>
<span>Download Location</span>
</button>
</mat-action-list>
</mat-card>
@@ -0,0 +1,7 @@
mat-card {
padding: 0 0 8px 0;
}
mat-icon {
margin-right: 5px;
}
@@ -1,87 +1,28 @@
import {AppService} from '../app.service';
import {GalleryComponent} from '../gallery/gallery.component';
import {ServerService} from '../server-service';
import {AppService} from '../../app.service';
import {ServerService} from '../../server-service';
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
import {ElectronService} from 'ngx-electron';
import {ChangeDetectionStrategy, Component, NgZone} from '@angular/core';
import {animate, state, style, transition, trigger} from '@angular/animations';
import {MatDialog, MatDialogConfig} from '@angular/material/dialog';
import {MatSnackBar} from '@angular/material/snack-bar';
import {PostDetailComponent} from '../post-detail/post-detail.component';
import {PostDetailComponent} from '../../post-progress/post-detail.component';
import {BehaviorSubject, Observable, Subject} from 'rxjs';
import {BreakpointObserver, Breakpoints, BreakpointState} from '@angular/cdk/layout';
import {PostState} from './post-state.model';
import {DownloadPath} from '../common/download-path.model';
import {ConfirmDialogComponent} from '../common/confirmation-component/confirmation-dialog';
import {PostState} from '../../domain/post-state.model';
import {DownloadPath} from '../../domain/download-path.model';
import {ConfirmDialogComponent} from '../../confirmation-component/confirmation-dialog';
import {filter, flatMap} from 'rxjs/operators';
import {RemoveResponse} from '../common/remove-response.model';
import {RemoveResponse} from '../../domain/remove-response.model';
import {CtxtMenuService} from "./ctxt-menu.service";
import {PostsDataService} from "./posts-data.service";
import {AlternativeTitleComponent, AlternativeTitleDialog} from "./alternative-title/alternative-title.component";
import {PostsDataService} from "../posts-data.service";
import {AlternativeTitleComponent, AlternativeTitleDialog} from "../alternative-title/alternative-title.component";
import {PostId} from "../../domain/post-id.model";
@Component({
selector: 'app-post-ctx-menu',
template: `
<mat-card class="mat-elevation-z4">
<mat-action-list>
<button (click)="goTo()" ngxClipboard [cbContent]="postState.url" mat-list-item>
<mat-icon>open_in_new</mat-icon>
<span>Open link</span>
</button>
<button
(click)="restart()"
*ngIf="
(postState.status === 'COMPLETE' && postState.progress !== 100) ||
postState.status === 'ERROR' ||
postState.status === 'STOPPED'
"
mat-list-item
>
<mat-icon>play_arrow</mat-icon>
<span>{{postState.progress == 0 ? 'Start' : 'Resume'}}</span>
</button>
<button
(click)="stop()"
*ngIf="postState.status === 'DOWNLOADING' || postState.status === 'PARTIAL' || postState.status === 'PENDING'"
mat-list-item
>
<mat-icon>stop</mat-icon>
<span>Stop</span>
</button>
<button (click)="remove()" mat-list-item>
<mat-icon>delete</mat-icon>
<span>Remove</span>
</button>
<button (click)="seeDetails()" mat-list-item>
<mat-icon>list</mat-icon>
<span>Files</span>
</button>
<button *ngIf="isViewEnabled | async" (click)="openGallery()" mat-list-item>
<mat-icon>photo_library</mat-icon>
<span>View Photos</span>
</button>
<button (click)="openRenameDialog()" mat-list-item>
<mat-icon>edit</mat-icon>
<span>Gallery name</span>
</button>
<button (click)="open()" *ngIf="electronService.isElectronApp" mat-list-item>
<mat-icon>folder</mat-icon>
<span>Download Location</span>
</button>
</mat-action-list>
</mat-card>
`,
styles: [
`
mat-card {
padding: 0 0 8px 0;
}
mat-icon {
margin-right: 5px;
}
`
],
templateUrl: 'post-context-menu.component.html',
styleUrls: ['post-context-menu.component.scss'],
animations: [
trigger('simpleFadeAnimation', [
state('in', style({opacity: 1})),
@@ -172,6 +113,21 @@ export class PostContextMenuComponent {
});
}
useAltTitle() {
this.contextMenuService.closePostCtxtMenu();
this.httpClient.post<PostId[]>(this.serverService.baseUrl + '/post/rename/first', [{
postId: this.postState.postId
}]).subscribe(
() => {
},
error => {
this._snackBar.open(error?.error?.message || 'Unexpected error, check log file', null, {
duration: 5000
});
}
);
}
openRenameDialog() {
this.contextMenuService.closePostCtxtMenu();
const dialogConfig: MatDialogConfig<AlternativeTitleDialog> = {
@@ -230,33 +186,6 @@ export class PostContextMenuComponent {
});
}
openGallery() {
this.contextMenuService.closePostCtxtMenu();
this.ngZone.run(() => {
const dialogRef = this.dialog.open(GalleryComponent, {
width: '90%',
height: '90%',
maxWidth: '100vw',
maxHeight: '100vh',
data: this.postState
});
const smallDialogSubscription = this.isExtraSmall.subscribe(result => {
if (result.matches) {
dialogRef.updateSize('100%', '100%');
} else {
dialogRef.updateSize('90%', '90%');
}
});
dialogRef.afterClosed().subscribe(() => {
if (smallDialogSubscription != null) {
smallDialogSubscription.unsubscribe();
}
});
});
}
open() {
this.contextMenuService.closePostCtxtMenu();
if (!this.electronService.isElectronApp) {
+3 -3
View File
@@ -1,6 +1,6 @@
import {PostState} from './post-state.model';
import {CMD} from '../common/cmd.enum';
import {WSMessage} from '../common/ws-message.model';
import {PostState} from '../domain/post-state.model';
import {CMD} from '../domain/cmd.enum';
import {WSMessage} from '../domain/ws-message.model';
import {Subscription} from 'rxjs';
import {WsConnectionService} from '../ws-connection.service';
import {GridOptions} from 'ag-grid-community';
+2 -3
View File
@@ -4,9 +4,9 @@ import {AfterViewInit, ChangeDetectionStrategy, Component, NgZone, OnDestroy, On
import {PostsDataSource} from './post.datasource';
import {WsConnectionService} from '../ws-connection.service';
import {GridOptions} from 'ag-grid-community';
import {PostProgressRendererComponent} from './post-progress.renderer.component';
import {PostProgressRendererComponent} from './renderer/post-progress.renderer.component';
import {Subject} from 'rxjs';
import {CtxtMenuService} from "./ctxt-menu.service";
import {CtxtMenuService} from "./context-menu/ctxt-menu.service";
@Component({
selector: 'app-posts',
@@ -35,7 +35,6 @@ export class PostsComponent implements OnInit, OnDestroy, AfterViewInit {
headerCheckboxSelectionFilteredOnly: true
}
],
rowBuffer: 200,
rowHeight: 60,
animateRows: true,
rowSelection: 'multiple',
@@ -21,15 +21,16 @@
<div class="checkbox" fxFlex="nogrow" style="width: 5px; height: 100%;"></div>
<span class="row-text" fxFlex="grow" fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="20px">
<span fxLayout="row" fxLayoutAlign="center center">
<mat-icon class="icon" color="primary" [appPreview]="postState.previews">image</mat-icon>
<mat-icon class="icon" fxFlex="none" color="primary" [appPreview]="postState.previews">image</mat-icon>
<span class="title">
<p [title]="postState.title">{{
postState.title
}}</p>
<p><label>Alternative titles: </label>{{postState.alternativeTitle.join(', ')}}</p>
<p><label>Alternative titles: </label><span
[title]="postState?.alternativeTitle?.join(', ')">{{postState?.alternativeTitle?.join(', ') || 'none'}}</span></p>
</span>
</span>
<span [title]="postState.done + '/' + postState.total + ' from ' + postState.hosts"
<span fxFlex="none" [title]="postState.done + '/' + postState.total + ' from ' + postState.hosts"
>{{ postState.done + '/' + postState.total }} from {{ postState.hosts }}</span>
</span>
</div>
@@ -8,15 +8,15 @@
padding-right: 15px;
}
.row-text > span {
.row-text * {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
& .icon {
margin: 0 5px;
cursor: pointer;
}
.icon {
margin: 0 5px;
cursor: pointer;
}
.title > p {
@@ -1,4 +1,4 @@
@import '~@angular/material/theming';
@import 'node_modules/@angular/material/theming';
@mixin post-progress-renderer-component-theme($theme) {
$red: mat-palette($mat-red);
@@ -1,5 +1,5 @@
import {WsConnectionService} from '../ws-connection.service';
import {PostState} from './post-state.model';
import {WsConnectionService} from '../../ws-connection.service';
import {PostState} from '../../domain/post-state.model';
import {
AfterViewInit,
ChangeDetectionStrategy,
@@ -14,7 +14,7 @@ import {BehaviorSubject, Subject, Subscription} from 'rxjs';
import {GridApi, ICellRendererParams, RowNode} from 'ag-grid-community';
import {ElectronService} from 'ngx-electron';
import {MatDialog} from '@angular/material/dialog';
import {CtxtMenuService} from "./ctxt-menu.service";
import {CtxtMenuService} from "../context-menu/ctxt-menu.service";
@Component({
selector: 'app-progress-cell',
@@ -1,18 +0,0 @@
import {Injectable} from '@angular/core';
import {Observable, Subject} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SharedService {
private expandedPost$ = new Subject<string>();
get expandedPost(): Observable<string> {
return this.expandedPost$.asObservable();
}
publishExpanded(postId: string) {
this.expandedPost$.next(postId);
}
}
@@ -7,7 +7,7 @@ import {FormControl, FormGroup} from '@angular/forms';
import {MatSnackBar} from '@angular/material/snack-bar';
import {ServerService} from '../server-service';
import {ElectronService} from 'ngx-electron';
import {Settings} from '../common/settings.model';
import {Settings} from '../domain/settings.model';
import {OpenDialogReturnValue} from 'electron';
import {BehaviorSubject, Subject} from 'rxjs';
@@ -8,12 +8,12 @@ import {
OnDestroy,
OnInit
} from '@angular/core';
import {DownloadSpeed} from '../common/download-speed.model';
import {DownloadSpeed} from '../domain/download-speed.model';
import {WsConnectionService} from '../ws-connection.service';
import {Subscription} from 'rxjs';
import {GlobalState} from '../common/global-state.model';
import {WSMessage} from '../common/ws-message.model';
import {CMD} from '../common/cmd.enum';
import {GlobalState} from '../domain/global-state.model';
import {WSMessage} from '../domain/ws-message.model';
import {CMD} from '../domain/cmd.enum';
@Component({
selector: 'app-status-bar',
@@ -1,5 +1,5 @@
<mat-toolbar class="mat-elevation-z8" color="accent">
<mat-toolbar-row fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="50px">
<mat-toolbar-row fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="10px">
<div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="5px" id="left-controls">
<button (click)="scan()" class="add-button" color="primary" mat-icon-button>
<mat-icon>add</mat-icon>
@@ -16,6 +16,11 @@
title="Stop selected">
<mat-icon>pause</mat-icon>
</button>
<button (click)="rename()" [disabled]="disableSelection | async" aria-label="Rename selected to alternative title"
mat-icon-button
title="Rename selected to alternative title">
<mat-icon>edit</mat-icon>
</button>
<div class="or-spacer-vertical left">
<div class="mask"></div>
</div>
@@ -26,16 +31,16 @@
<mat-icon>stop</mat-icon>
</button>
</div>
<div fxFlex="grow" id="global-search">
<div fxFlex="grow" id="global-search" fxHide.lt-md>
<form autocomplete="off">
<mat-form-field style="width: 100%">
<mat-form-field class="search-field">
<input (ngModelChange)="search($event)" matInput name="search" ngModel placeholder="Search"/>
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
</form>
</div>
<div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="5px" id="right-controls">
<p *ngIf="loggedUser | async as user" style="font-size: 16px; display: inline;">
<p class="logged-user" fxHide.lt-sm *ngIf="loggedUser | async as user">
Logged in as: {{ user!.user == null || user!.user === '' ? 'guest' : user.user }}
</p>
<button (click)="openSettings()" aria-label="settings" mat-icon-button>
@@ -7,25 +7,26 @@ import {
OnDestroy,
OnInit
} from '@angular/core';
import {RemoveAllResponse} from '../common/remove-all-response.model';
import {RemoveAllResponse} from '../domain/remove-all-response.model';
import {ServerService} from '../server-service';
import {AppService} from '../app.service';
import {HttpClient} from '@angular/common/http';
import {MatDialog} from '@angular/material/dialog';
import {MatSnackBar} from '@angular/material/snack-bar';
import {ConfirmDialogComponent} from '../common/confirmation-component/confirmation-dialog';
import {ConfirmDialogComponent} from '../confirmation-component/confirmation-dialog';
import {filter, flatMap} from 'rxjs/operators';
import {LoggedUser} from '../common/logged-user.model';
import {LoggedUser} from '../domain/logged-user.model';
import {SettingsComponent} from '../settings/settings.component';
import {Observable, Subscription} from 'rxjs';
import {BreakpointObserver, Breakpoints, BreakpointState} from '@angular/cdk/layout';
import {WsConnectionService} from '../ws-connection.service';
import {WSMessage} from '../common/ws-message.model';
import {CMD} from '../common/cmd.enum';
import {WSMessage} from '../domain/ws-message.model';
import {CMD} from '../domain/cmd.enum';
import {SelectionService} from '../selection-service';
import {RowNode} from 'ag-grid-community';
import {RemoveResponse} from '../common/remove-response.model';
import {RemoveResponse} from '../domain/remove-response.model';
import {PostsDataService} from '../posts/posts-data.service';
import {PostId} from "../domain/post-id.model";
@Component({
selector: 'app-toolbar',
@@ -145,11 +146,28 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
);
}
rename() {
const toRename = [];
this.selected.forEach(e => toRename.push(e.data.postId));
this.httpClient.post<PostId[]>(this.serverService.baseUrl + '/post/rename/first', toRename).subscribe(
() => {
this._snackBar.open('Galleries renamed', null, {
duration: 5000
});
},
error => {
this._snackBar.open(error?.error?.message.error || 'Unexpected error, check log file', null, {
duration: 5000
});
}
);
}
clear() {
this.ngZone.run(() => {
this.httpClient.post<RemoveAllResponse>(this.serverService.baseUrl + '/post/clear/all', {}).subscribe(
data => {
this._snackBar.open(`${data.removed} items cleared`, null, { duration: 5000 });
this._snackBar.open(`${data.removed} items cleared`, null, {duration: 5000});
},
error => {
this._snackBar.open(error?.error?.message.error || 'Unexpected error, check log file', null, {
@@ -1,23 +1,35 @@
.or-spacer-vertical {
display:inline-block;
width:1px;
position:relative;
.mask {
overflow:hidden; width:10px; height:50px;
}
&.left .mask:after {
content:'';
display:block;
margin-left:-20px;
width:20px;
height:100%;
border-radius:12px;
box-shadow:0 0 3px black;
}
display: inline-block;
width: 1px;
position: relative;
.mask {
overflow: hidden;
width: 10px;
height: 50px;
}
#global-search {
font-size: 16px;
line-height: 18px;
}
&.left .mask:after {
content: '';
display: block;
margin-left: -20px;
width: 20px;
height: 100%;
border-radius: 12px;
box-shadow: 0 0 3px black;
}
}
#global-search {
font-size: 16px;
line-height: 18px;
}
.search-field {
width: 100%;
}
.logged-user {
font-size: 16px;
display: inline;
}
+7 -7
View File
@@ -4,13 +4,13 @@ import {WebSocketSubject} from 'rxjs/webSocket';
import {environment} from 'src/environments/environment';
import {ElectronService} from 'ngx-electron';
import {ServerService} from './server-service';
import {GrabQueueState} from './grab-queue/grab-queue.model';
import {WSMessage} from './common/ws-message.model';
import {PostDetails} from './post-detail/post-details.model';
import {PostState} from './posts/post-state.model';
import {LoggedUser} from './common/logged-user.model';
import {DownloadSpeed} from './common/download-speed.model';
import {GlobalState} from './common/global-state.model';
import {GrabQueueState} from './domain/grab-queue.model';
import {WSMessage} from './domain/ws-message.model';
import {PostDetails} from './domain/post-details.model';
import {PostState} from './domain/post-state.model';
import {LoggedUser} from './domain/logged-user.model';
import {DownloadSpeed} from './domain/download-speed.model';
import {GlobalState} from './domain/global-state.model';
import {filter, map} from 'rxjs/operators';
@Injectable({
@@ -1,16 +0,0 @@
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class XhrInterceptorService implements HttpInterceptor {
constructor() { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const xhr = req.clone({
headers: req.headers.set('X-Requested-With', 'XMLHttpRequest')
});
return next.handle(xhr);
}
}
@@ -2,5 +2,5 @@ export const environment = {
production: true,
localhost: `${window.location.protocol}//${window.location.host}`,
ws: `${window.location.protocol === 'http:' ? 'ws:' : 'wss:'}//${window.location.host}`,
version: '2.11.2'
version: '2.12.0'
};
+1 -1
View File
@@ -6,7 +6,7 @@ export const environment = {
production: false,
localhost: 'http://localhost:8080',
ws: 'ws://localhost:8080',
version: '2.11.2'
version: '2.12.0'
};
/*
+2 -2
View File
@@ -31,8 +31,8 @@
@import 'mat-theme.scss';
@import 'src/app/status-bar/status-bar.component.scss-theme';
@import 'app/app.component.scss-theme.scss';
@import 'app/posts/post-progress.renderer.component.scss-theme.scss';
@import 'app/post-detail/post-details-progress.component.scss-theme.scss';
@import 'src/app/posts/renderer/post-progress.renderer.component.scss-theme';
@import 'src/app/post-progress/renderer/post-details-progress.component.scss-theme';
@import 'grid-theme.scss';
@mixin custom-components-theme($theme) {