Compare commits

...
8 Commits
Author SHA1 Message Date
death-claw c6ef54e115 v2.7.1 2019-12-21 11:24:03 +01:00
death-claw 441b2831fc Add version to title 2019-12-21 11:16:53 +01:00
death-claw 3cf4d60eca v2.7.0 2019-12-21 09:59:17 +01:00
death-claw d228c89a1c Add support for pixxxels
Add missing icon
2019-12-21 09:56:57 +01:00
death-claw f3b592e81b v2.6.0 2019-12-21 08:39:04 +01:00
death-claw a5ff659fb4 Better quality app icon
Implement context menu on download tab
Add stop remove and start button to context menu
Link will not be removed automatically from link collector tab
Some UI fixes
2019-12-21 08:34:56 +01:00
death-claw ab80e2f3b3 v2.5.1 2019-12-09 20:26:52 +01:00
death-claw 2ef082f87e Add hosts column in Link collector grab menu
Disable download button when nothing selected in Link collector grab menu
2019-12-09 19:56:26 +01:00
53 changed files with 689 additions and 224 deletions
+25
View File
@@ -1,5 +1,30 @@
# Changelog
## [2.7.1] - 2019-12-21
### Added
- Add version to title bar
## [2.7.0] - 2019-12-21
### Changed
- Add missing icon
### Added
- Add support for pixxxels
## [2.6.0] - 2019-12-21
### Changed
- Disable download button when nothing selected in Link collector grab menu
- Better quality app icon
- Implement context menu on download tab
- Add stop remove and start button to context menu
- Link will not be removed automatically from link collector tab
- Some UI fixes
## [2.5.1] - 2019-12-09
### Changed
- Disable download button when nothing selected in Link collector grab menu
### Added
- Add hosts column in Link collector grab menu
## [2.5.0] - 2019-12-08
### Changed
- Change loading screen
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>tn.mnlr</groupId>
<artifactId>vripper</artifactId>
<version>2.5.0</version>
<version>2.7.1</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

+1 -1
View File
@@ -86,7 +86,6 @@ getPort().then(port => {
}
vripperServer = spawn(javaBinPath, [
"-Xms256m",
"-Xmx1024m",
"-Dvripper.server.port=" + port,
"-jar",
jarPath
@@ -136,6 +135,7 @@ if (!gotTheLock) {
})
.catch((error) => {
// Terminate immediately
console.log(error);
vripperServer.kill('SIGKILL');
terminated = true;
app.quit();
+60 -35
View File
@@ -1,6 +1,6 @@
{
"name": "vripper-electron",
"version": "2.5.0",
"version": "2.7.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -35,6 +35,11 @@
"defer-to-connect": "^1.0.1"
}
},
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
},
"@types/debug": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz",
@@ -117,6 +122,7 @@
"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"
}
@@ -216,9 +222,9 @@
"dev": true
},
"astral-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg=="
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="
},
"async-exit-hook": {
"version": "2.0.1",
@@ -519,18 +525,18 @@
"dev": true
},
"cli-truncate": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.0.0.tgz",
"integrity": "sha512-C4hp+8GCIFVsUUiXcw+ce+7wexVWImw8rQrgMBFsqerx9LvvcGlwm6sMjQYAEmV/Xb87xc1b5Ttx505MSpZVqg==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz",
"integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
"requires": {
"slice-ansi": "^2.1.0",
"string-width": "^4.1.0"
"slice-ansi": "^3.0.0",
"string-width": "^4.2.0"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"emoji-regex": {
"version": "8.0.0",
@@ -543,21 +549,21 @@
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.1.0.tgz",
"integrity": "sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^5.2.0"
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^4.1.0"
"ansi-regex": "^5.0.0"
}
}
}
@@ -626,6 +632,7 @@
"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"
}
@@ -633,7 +640,8 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"combined-stream": {
"version": "1.0.8",
@@ -760,11 +768,6 @@
"array-find-index": "^1.0.1"
}
},
"custom-electron-titlebar": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/custom-electron-titlebar/-/custom-electron-titlebar-3.0.10.tgz",
"integrity": "sha512-FVKy7P0NpMTkm5rEpETXkJWQ7w9ufdU8TA5xhfiR1Jgnhy6fBaCNUMmXWLrNFrhG69MVvDXM/lLN21Rxaz78Ig=="
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -2426,19 +2429,41 @@
}
},
"slice-ansi": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
"integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
"integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
"requires": {
"ansi-styles": "^3.2.0",
"astral-regex": "^1.0.0",
"is-fullwidth-code-point": "^2.0.0"
"ansi-styles": "^4.0.0",
"astral-regex": "^2.0.0",
"is-fullwidth-code-point": "^3.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.0.tgz",
"integrity": "sha512-7kFQgnEaMdRtwf6uSfUnVr9gSGC7faurn+J/Mv90/W+iTtN0405/nLdopfMWwchyxhbGYl6TC4Sccn9TUkGAgg==",
"requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
}
}
},
+4 -5
View File
@@ -1,6 +1,6 @@
{
"name": "vripper-electron",
"version": "2.5.0",
"version": "2.7.1",
"description": "A ripper for vipergirls.to built using web technolgies",
"main": "main.js",
"author": "death-claw <53543762+death-claw@users.noreply.github.com>",
@@ -8,7 +8,7 @@
"license": "ISC",
"build": {
"appId": "tn.mnlr.vripper",
"productName": "vripper",
"productName": "VRipper",
"files": [
"**/*",
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}",
@@ -38,7 +38,7 @@
}
],
"win": {
"icon": "icon.png",
"icon": "icon.ico",
"target": [
"nsis"
]
@@ -62,7 +62,7 @@
"target": [
"dmg"
],
"icon": "icon.png"
"icon": "icon.icns"
}
},
"scripts": {
@@ -77,7 +77,6 @@
"axios": "^0.19.0",
"cheerio": "^1.0.0-rc.3",
"copy-dir": "^1.1.0",
"custom-electron-titlebar": "^3.0.10",
"electron-context-menu": "^0.13.0",
"get-port": "^5.0.0",
"rimraf": "^2.6.3"
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>tn.mnlr</groupId>
<artifactId>vripper</artifactId>
<version>2.5.0</version>
<version>2.7.1</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.5.0</version>
<version>2.7.1</version>
</parent>
<artifactId>vripper-server</artifactId>
<name>vripper-server</name>
@@ -0,0 +1,67 @@
package tn.mnlr.vripper.host;
import org.apache.http.client.protocol.HttpClientContext;
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 org.w3c.dom.Node;
import tn.mnlr.vripper.exception.HostException;
import tn.mnlr.vripper.exception.XpathException;
import tn.mnlr.vripper.q.ImageFileData;
import tn.mnlr.vripper.services.ConnectionManager;
@Service
public class PixxxelsHost extends Host {
private static final Logger logger = LoggerFactory.getLogger(PixxxelsHost.class);
private static final String host = "pixxxels.cc";
private static final String IMG_XPATH = "//*[@id='download']";
private static final String TITLE_XPATH = "//*[contains(@class,'imagename')]";
@Autowired
private ConnectionManager cm;
@Override
public String getHost() {
return host;
}
@Override
public String getLookup() {
return host;
}
@Override
protected void setNameAndUrl(final String _url, final ImageFileData imageFileData, final HttpClientContext context) throws HostException {
String url = _url.replace("http://", "https://");
Response resp = getResponse(url, context);
Document doc = resp.getDocument();
Node imgNode, titleNode;
try {
logger.debug(String.format("Looking for xpath expression %s in %s", IMG_XPATH, url));
imgNode = xpathService.getAsNode(doc, IMG_XPATH);
logger.debug(String.format("Looking for xpath expression %s in %s", TITLE_XPATH, url));
titleNode = xpathService.getAsNode(doc, TITLE_XPATH);
} catch (XpathException e) {
throw new HostException(e);
}
try {
logger.debug(String.format("Resolving name and image url for %s", url));
String imgTitle = titleNode.getTextContent().trim();
String imgUrl = imgNode.getAttributes().getNamedItem("href").getTextContent().trim();
imageFileData.setImageUrl(imgUrl);
imageFileData.setImageName(imgTitle.isEmpty() ? imgUrl.substring(imgUrl.lastIndexOf('/') + 1) : imgTitle);
} catch (Exception e) {
throw new HostException("Unexpected error occurred", e);
}
}
}
@@ -54,9 +54,7 @@ public class PersistenceService {
om.addMixIn(Post.class, PostPersistanceMixin.class);
subscription = processor
.onBackpressureLatest()
.buffer(10, TimeUnit.SECONDS)
.filter(e -> !e.isEmpty())
.map(e -> e.get(0))
.throttleLatest(1, TimeUnit.SECONDS)
.doOnNext(this::persist)
.subscribe();
}
@@ -77,6 +77,6 @@ public class PostParser {
}
public VRThreadParser createVRThreadParser(String threadId) {
return new VRThreadParser(threadId, cm, vipergirlsAuthService);
return new VRThreadParser(threadId, cm, vipergirlsAuthService, supportedHosts);
}
}
@@ -104,21 +104,17 @@ class VRPostHandler extends DefaultHandler {
private static final Logger logger = LoggerFactory.getLogger(VRPostHandler.class);
private final List<Host> supportedHosts;
private final String threadId;
private final String postId;
private List<Image> images = new ArrayList<>();
private List<String> previews = new ArrayList<>();
private String threadTitle;
private String postTitle;
private String forum;
private int previewCounter = 0;
private int index = 0;
private String postTitle;
private int imageCount;
private String forum;
private List<String> previews = new ArrayList<>();
private List<Image> images = new ArrayList<>();
@Getter
private Post parsedPost;
@@ -149,7 +145,7 @@ class VRPostHandler extends DefaultHandler {
case "image":
index++;
if (previewCounter++ < 4) {
Optional.ofNullable(attributes.getValue("thumb_url")).map(String::trim).ifPresent(thumbUrl -> previews.add(thumbUrl));
Optional.ofNullable(attributes.getValue("thumb_url")).map(String::trim).ifPresent(previews::add);
}
String mainUrl = Optional.ofNullable(attributes.getValue("main_url")).map(String::trim).orElse(null);
@@ -14,8 +14,9 @@ public class VRPostState {
private int imageCount;
private String url;
private List<String> previews;
private String hosts;
VRPostState(String threadId, String postId, int number, String title, int imageCount, String url, List<String> previews) {
VRPostState(String threadId, String postId, int number, String title, int imageCount, String url, List<String> previews, String hosts) {
this.threadId = threadId;
this.postId = postId;
this.number = number;
@@ -23,5 +24,6 @@ public class VRPostState {
this.imageCount = imageCount;
this.previews = previews;
this.url = url;
this.hosts = hosts;
}
}
@@ -15,16 +15,17 @@ import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import tn.mnlr.vripper.exception.DownloadException;
import tn.mnlr.vripper.exception.PostParseException;
import tn.mnlr.vripper.host.Host;
import javax.xml.parsers.SAXParserFactory;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import static tn.mnlr.vripper.services.PostParser.VR_API;
@@ -44,12 +45,14 @@ public class VRThreadParser {
private final String threadId;
private final ConnectionManager cm;
private final VipergirlsAuthService vipergirlsAuthService;
private final List<Host> hosts;
VRThreadParser(String threadId, ConnectionManager cm, VipergirlsAuthService vipergirlsAuthService) {
VRThreadParser(String threadId, ConnectionManager cm, VipergirlsAuthService vipergirlsAuthService, List<Host> hosts) {
this.threadId = threadId;
this.cm = cm;
this.vipergirlsAuthService = vipergirlsAuthService;
this.hosts = hosts;
}
public Void parse() throws PostParseException {
@@ -64,7 +67,7 @@ public class VRThreadParser {
throw new PostParseException(e);
}
VRThreadHandler handler = new VRThreadHandler(threadId, postPublishProcessor);
VRThreadHandler handler = new VRThreadHandler(threadId, postPublishProcessor, hosts);
AtomicReference<Throwable> thr = new AtomicReference<>();
logger.debug(String.format("Requesting %s", httpGet));
Failsafe.with(retryPolicy)
@@ -98,17 +101,20 @@ class VRThreadHandler extends DefaultHandler {
private final String threadId;
private final ReplayProcessor<VRPostState> vrPostPublishProcessor;
private final List<Host> supportedHosts;
private final Map<Host, AtomicInteger> hostMap = new HashMap<>();
private List<String> previews = new ArrayList<>();
private String threadTitle;
private String postId;
private String postTitle;
private int imageCount;
private int postCounter;
private int previewCounter = 0;
private List<String> previews = new ArrayList<>();
VRThreadHandler(String threadId, ReplayProcessor<VRPostState> vrPostPublishProcessor) {
VRThreadHandler(String threadId, ReplayProcessor<VRPostState> vrPostPublishProcessor, List<Host> supportedHosts) {
this.vrPostPublishProcessor = vrPostPublishProcessor;
this.threadId = threadId;
this.supportedHosts = supportedHosts;
}
@Override
@@ -129,8 +135,12 @@ class VRThreadHandler extends DefaultHandler {
postTitle = Optional.ofNullable(attributes.getValue("title")).map(e -> e.trim().isEmpty() ? null : e.trim()).orElse(threadTitle);
break;
case "image":
Optional.ofNullable(attributes.getValue("main_url"))
.map(String::trim)
.flatMap(mainUrl -> supportedHosts.stream().filter(host -> host.isSupported(mainUrl)).findFirst())
.ifPresent(host -> Optional.ofNullable(hostMap.get(host)).ifPresentOrElse(AtomicInteger::incrementAndGet, () -> hostMap.put(host, new AtomicInteger(0))));
if (previewCounter++ < 4) {
Optional.ofNullable(attributes.getValue("thumb_url")).map(String::trim).ifPresent(thumbUrl -> previews.add(thumbUrl));
Optional.ofNullable(attributes.getValue("thumb_url")).map(String::trim).ifPresent(previews::add);
}
break;
}
@@ -147,11 +157,13 @@ class VRThreadHandler extends DefaultHandler {
postTitle,
imageCount,
String.format("https://vipergirls.to/threads/%s/?p=%s&viewfull=1#post%s", threadId, postId, postId),
previews
previews,
hostMap.entrySet().stream().filter(v -> v.getValue().get() > 0).map(e -> e.getKey().getHost() + " (" + e.getValue().get() + ")").collect(Collectors.joining(", "))
));
}
previewCounter = 0;
previews = new ArrayList<>();
hostMap.clear();
}
}
@@ -56,7 +56,7 @@ public class SettingsRestEndpoint {
} catch (ValidationException e) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new Response(e.getMessage()));
.body(e.getMessage());
}
this.settings.setDownloadPath(settings.getDownloadPath());
this.settings.setMaxThreads(settings.getMaxThreads());
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vripper-ui",
"version": "2.5.0",
"version": "2.7.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vripper-ui",
"version": "2.4.1",
"version": "2.7.1",
"scripts": {
"ng": "ng",
"start": "ng serve",
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>tn.mnlr</groupId>
<artifactId>vripper</artifactId>
<version>2.5.0</version>
<version>2.7.1</version>
</parent>
<artifactId>vripper-ui</artifactId>
<name>vripper-ui</name>
+8 -3
View File
@@ -1,3 +1,5 @@
import { ContextMenuService } from './ctxt-menu.service';
import { PostContextMenuComponent } from './posts/post-context-menu.component';
import { PostsDataService } from './posts-data.service';
import { AppPreviewDirective } from './common/preview-tooltip.directive';
import { WsConnectionService } from './ws-connection.service';
@@ -57,7 +59,8 @@ import { UrlGrabRendererComponent } from './grab-queue/url-renderer.component';
ScanComponent,
StatusBarComponent,
ToolbarComponent,
GrabQueueComponent
GrabQueueComponent,
PostContextMenuComponent
],
entryComponents: [
PostDetailComponent,
@@ -65,7 +68,8 @@ import { UrlGrabRendererComponent } from './grab-queue/url-renderer.component';
ConfirmDialogComponent,
AppPreviewComponent,
ScanComponent,
MultiPostComponent
MultiPostComponent,
PostContextMenuComponent
],
imports: [
BrowserAnimationsModule,
@@ -91,7 +95,8 @@ import { UrlGrabRendererComponent } from './grab-queue/url-renderer.component';
ServerService,
SharedService,
SelectionService,
PostsDataService
PostsDataService,
ContextMenuService
],
bootstrap: [AppComponent]
})
+6 -1
View File
@@ -1,3 +1,4 @@
import { environment } from 'src/environments/environment';
import { MatDialog } from '@angular/material';
import { HttpClient } from '@angular/common/http';
import { Injectable, Renderer2 } from '@angular/core';
@@ -6,6 +7,7 @@ import { tap } from 'rxjs/operators';
import { ScanComponent } from './scan/scan.component';
import { Observable } from 'rxjs';
import { BreakpointState, Breakpoints, BreakpointObserver } from '@angular/cdk/layout';
import { Title } from '@angular/platform-browser';
@Injectable()
export class AppService {
@@ -14,7 +16,10 @@ export class AppService {
private serverService: ServerService,
public dialog: MatDialog,
private breakpointObserver: BreakpointObserver,
) {}
private titleService: Title
) {
this.titleService.setTitle('VRipper ' + environment.version);
}
darkTheme = false;
_renderer: Renderer2;
@@ -10,28 +10,33 @@ export class AppPreviewDirective implements OnInit, OnDestroy {
@Input() appPreview: string[];
constructor(private overlayPositionBuilder: OverlayPositionBuilder,
constructor(
private overlayPositionBuilder: OverlayPositionBuilder,
private elementRef: ElementRef,
private overlay: Overlay,
private ngZone: NgZone) {}
private ngZone: NgZone
) {}
ngOnInit() {
const positionStrategy = this.overlayPositionBuilder
.flexibleConnectedTo(this.elementRef)
.withPush(true)
.withGrowAfterOpen(true)
.withPositions([{
originX: 'center',
originY: 'bottom',
overlayX: 'center',
overlayY: 'top',
}, {
originX: 'center',
originY: 'top',
overlayX: 'center',
overlayY: 'bottom',
}]);
this.overlayRef = this.overlay.create({ positionStrategy });
.flexibleConnectedTo(this.elementRef)
.withPush(true)
.withGrowAfterOpen(true)
.withPositions([
{
originX: 'center',
originY: 'bottom',
overlayX: 'center',
overlayY: 'top'
},
{
originX: 'center',
originY: 'top',
overlayX: 'center',
overlayY: 'bottom'
}
]);
this.overlayRef = this.overlay.create({ positionStrategy, scrollStrategy: this.overlay.scrollStrategies.close() });
}
ngOnDestroy() {
@@ -6,6 +6,7 @@ export class VRPostParse {
public title: string,
public imageCount: number,
public url: string,
public previews: string[]
public previews: string[],
public hosts: string
) {}
}
+68
View File
@@ -0,0 +1,68 @@
import { PostState } from './posts/post-state.model';
import { Injectable, ComponentRef } from '@angular/core';
import { filter, take } from 'rxjs/operators';
import { fromEvent, Subscription } from 'rxjs';
import { OverlayPositionBuilder, Overlay, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { PostContextMenuComponent } from './posts/post-context-menu.component';
@Injectable()
export class ContextMenuService {
constructor(private overlayPositionBuilder: OverlayPositionBuilder, private overlay: Overlay) {}
postCtxtMenuPortal = new ComponentPortal(PostContextMenuComponent);
private postCtxtMenuOverlayRef: OverlayRef;
sub: Subscription;
openPostCtxtMenu(mouseEvent: MouseEvent, postState: PostState) {
mouseEvent.preventDefault();
this.closePostCtxtMenu();
const positionStrategy = this.overlayPositionBuilder
.flexibleConnectedTo({ x: mouseEvent.x, y: mouseEvent.y })
.withPush(true)
.withGrowAfterOpen(true)
.withPositions([
{
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top'
},
{
originX: 'start',
originY: 'top',
overlayX: 'start',
overlayY: 'bottom'
}
]);
this.postCtxtMenuOverlayRef = this.overlay.create({
positionStrategy
});
const ref: ComponentRef<PostContextMenuComponent> = this.postCtxtMenuOverlayRef.attach(this.postCtxtMenuPortal);
ref.instance.postState = postState;
ref.instance.contextMenuService = this;
this.sub = fromEvent<MouseEvent>(document, 'click')
.pipe(
filter(event => {
const clickTarget = event.target as HTMLElement;
return !!this.postCtxtMenuOverlayRef && !this.postCtxtMenuOverlayRef.overlayElement.contains(clickTarget);
}),
take(1)
)
.subscribe(() => this.closePostCtxtMenu());
}
closePostCtxtMenu() {
if (this.sub != null) {
this.sub.unsubscribe();
}
if (this.postCtxtMenuOverlayRef) {
this.postCtxtMenuOverlayRef.dispose();
this.postCtxtMenuOverlayRef = null;
}
}
}
export interface Theme {
darkTheme: boolean;
}
@@ -18,6 +18,7 @@ export class GrabQueueComponent implements OnInit {
field: 'link',
sortable: true,
sort: 'asc',
suppressMovable: true,
cellRenderer: 'urlCellRenderer'
}
],
@@ -14,9 +14,13 @@ import { HttpClient } from '@angular/common/http';
selector: 'app-url-cell-grab',
template: `
<div fxLayout="row" fxLayoutAlign="space-between center">
<span><a (click)="goTo()" href="javascript:void(0)">{{ grabQueue.link }}</a></span>
<span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"
><a (click)="goTo()" href="javascript:void(0)">{{ grabQueue.link }}</a></span
>
<span>
<button style="margin-right: 5px" (click)="grab()" color="primary" mat-mini-fab><mat-icon>get_app</mat-icon></button>
<button style="margin-right: 5px" (click)="grab()" color="primary" mat-mini-fab>
<mat-icon>get_app</mat-icon>
</button>
<button (click)="remove()" color="primary" mat-mini-fab><mat-icon>delete</mat-icon></button>
</span>
</div>
+4 -2
View File
@@ -15,7 +15,8 @@ import {
MatSnackBar,
MatTabsModule,
MatDividerModule,
MatChipsModule
MatChipsModule,
MatListModule
} from '@angular/material';
@NgModule({
@@ -34,7 +35,8 @@ import {
MatSnackBarModule,
MatTabsModule,
MatDividerModule,
MatChipsModule
MatChipsModule,
MatListModule
],
providers: [MatSnackBar]
})
@@ -20,6 +20,8 @@
</mat-dialog-content>
<mat-dialog-actions align="end" fxFlex="nogrow">
<button (click)="onNoClick()" mat-raised-button>Close</button>
<button (click)="submit()" color="primary" mat-raised-button>Download</button>
<button (click)="submit()" [disabled]="(selectedRowsCount | async) === 0" color="primary" mat-raised-button>
Download
</button>
</mat-dialog-actions>
</div>
@@ -11,10 +11,10 @@ import {
Inject
} from '@angular/core';
import { MatSnackBar, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { GridOptions } from 'ag-grid-community';
import { GridOptions, RowNode } from 'ag-grid-community';
import { UrlRendererComponent } from './url-renderer.component';
import { WsHandler } from '../ws-handler';
import { Subscription, concat, merge } from 'rxjs';
import { Subscription, BehaviorSubject, Subject } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { ServerService } from '../server-service';
import { GrabQueueState } from '../grab-queue/grab-queue.model';
@@ -40,6 +40,8 @@ export class MultiPostComponent implements OnInit, OnDestroy, AfterViewInit {
@Inject(MAT_DIALOG_DATA) public dialogData: GrabQueueState
) {}
selectedRowsCount: Subject<number> = new BehaviorSubject(0);
ngAfterViewInit(): void {
this.loading.emit(true);
}
@@ -52,6 +54,7 @@ export class MultiPostComponent implements OnInit, OnDestroy, AfterViewInit {
field: 'number',
cellClass: 'no-padding',
maxWidth: 100,
suppressMovable: true,
checkboxSelection: true,
headerCheckboxSelection: true,
headerCheckboxSelectionFilteredOnly: false,
@@ -60,13 +63,21 @@ export class MultiPostComponent implements OnInit, OnDestroy, AfterViewInit {
{
headerName: 'Title',
field: 'title',
suppressMovable: true,
cellClass: 'no-padding'
},
{
headerName: 'URL',
field: 'url',
cellClass: 'no-padding',
suppressMovable: true,
cellRenderer: 'urlCellRenderer'
},
{
headerName: 'Hosts',
field: 'hosts',
suppressMovable: true,
cellClass: 'no-padding'
}
],
defaultColDef: {
@@ -89,10 +100,15 @@ export class MultiPostComponent implements OnInit, OnDestroy, AfterViewInit {
this.grab();
},
onGridSizeChanged: () => this.gridOptions.api.sizeColumnsToFit(),
onRowDataUpdated: () => this.gridOptions.api.sizeColumnsToFit()
onRowDataUpdated: () => this.gridOptions.api.sizeColumnsToFit(),
onSelectionChanged: () => this.onSelectionChange(this.gridOptions.api.getSelectedNodes())
};
}
onSelectionChange(data: RowNode[]) {
this.selectedRowsCount.next(data.length);
}
ngOnDestroy() {}
search(event) {
@@ -106,18 +122,18 @@ export class MultiPostComponent implements OnInit, OnDestroy, AfterViewInit {
threadId: this.dialogData.threadId
}));
merge(
this.httpClient.post(this.serverService.baseUrl + '/post/add', data),
this.httpClient.post(this.serverService.baseUrl + '/grab/remove', { url: this.dialogData.link })
)
.pipe(finalize(() => {
this.dialogRef.close();
this.loading.emit(false);
}))
this.httpClient
.post(this.serverService.baseUrl + '/post/add', data)
.pipe(
finalize(() => {
this.dialogRef.close();
this.loading.emit(false);
})
)
.subscribe(
() => {},
error => {
this._snackBar.open(error.error, null, {
this._snackBar.open(error.error || 'Unexpected error, check log file', null, {
duration: 5000
});
}
@@ -140,26 +156,6 @@ export class MultiPostComponent implements OnInit, OnDestroy, AfterViewInit {
);
}
// removeFromQueue() {
// this.httpClient.post(this.serverService.baseUrl + '/grab/remove', { url: this.dialogData.link }).subscribe(
// () => {},
// error => {
// this._snackBar.open(error.error || 'Unexpected error, check log file', null, {
// duration: 5000
// });
// }
// );
// }
// addPosts(data: { postId: string; threadId: string }[]) {
// this.httpClient.post(this.serverService.baseUrl + '/post/add', data).subscribe(() => {
// this._snackBar.open('Adding to download queue', null, {
// duration: 5000
// });
// this.dialogRef.close();
// });
// }
onNoClick() {
this.dialogRef.close();
}
@@ -27,6 +27,7 @@ export class PostDetailComponent implements OnInit, OnDestroy {
field: 'index',
sortable: true,
sort: 'asc',
suppressMovable: true,
cellClass: 'col-white',
width: 10
},
@@ -34,6 +35,7 @@ export class PostDetailComponent implements OnInit, OnDestroy {
headerName: 'URL',
field: 'url',
sortable: true,
suppressMovable: true,
cellRenderer: 'progressCellRenderer',
cellClass: 'no-padding'
}
@@ -0,0 +1,213 @@
import { ServerService } from './../server-service';
import { HttpClient } from '@angular/common/http';
import { ElectronService } from 'ngx-electron';
import { Component, ChangeDetectionStrategy, NgZone } from '@angular/core';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { MatDialog, MatSnackBar } from '@angular/material';
import { PostDetailComponent } from '../post-detail/post-detail.component';
import { Observable } from 'rxjs';
import { BreakpointState, BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { PostState } from './post-state.model';
import { DownloadPath } from '../common/download-path.model';
import { ContextMenuService } from '../ctxt-menu.service';
import { ConfirmDialogComponent } from '../common/confirmation-component/confirmation-dialog';
import { filter, flatMap } from 'rxjs/operators';
import { RemoveResponse } from '../common/remove-response.model';
@Component({
selector: 'app-post-ctx-menu',
template: `
<mat-card>
<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>Start</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)="open()" *ngIf="electronService.isElectronApp" mat-list-item>
<mat-icon>open_in_new</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;
}
`
],
animations: [
trigger('simpleFadeAnimation', [
state('in', style({ opacity: 1 })),
transition(':enter', [style({ opacity: 0 }), animate(300)])
])
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PostContextMenuComponent {
postState: PostState;
isExtraSmall: Observable<BreakpointState> = this.breakpointObserver.observe(Breakpoints.XSmall);
fs;
contextMenuService: ContextMenuService;
constructor(
public electronService: ElectronService,
private dialog: MatDialog,
private breakpointObserver: BreakpointObserver,
private httpClient: HttpClient,
private serverService: ServerService,
private _snackBar: MatSnackBar,
private ngZone: NgZone
) {
if (this.electronService.isElectronApp) {
this.fs = this.electronService.remote.require('fs');
}
}
restart() {
this.contextMenuService.closePostCtxtMenu();
this.httpClient.post(this.serverService.baseUrl + '/post/restart', [ this.postState.postId ]).subscribe(
() => {
this._snackBar.open('Download started', null, {
duration: 5000
});
},
error => {
this._snackBar.open(error.error || 'Unexpected error, check log file', null, {
duration: 5000
});
}
);
}
remove() {
this.contextMenuService.closePostCtxtMenu();
this.ngZone.run(() => {
this.dialog
.open(ConfirmDialogComponent, {
maxHeight: '100vh',
maxWidth: '100vw',
height: '200px',
width: '60%',
data: { header: 'Confirmation', content: 'Are you sure you want to remove this item ?' }
})
.afterClosed()
.pipe(
filter(e => e === 'yes'),
flatMap(e =>
this.httpClient.post<RemoveResponse>(this.serverService.baseUrl + '/post/remove', [ this.postState.postId ])
)
)
.subscribe(
() => {},
error => {
this._snackBar.open(error.error || 'Unexpected error, check log file', null, {
duration: 5000
});
}
);
});
}
stop() {
this.contextMenuService.closePostCtxtMenu();
this.httpClient.post(this.serverService.baseUrl + '/post/stop', [ this.postState.postId ]).subscribe(
() => {
this._snackBar.open('Download stopped', null, {
duration: 5000
});
},
error => {
this._snackBar.open(error.error || 'Unexpected error, check log file', null, {
duration: 5000
});
}
);
}
seeDetails() {
this.contextMenuService.closePostCtxtMenu();
this.ngZone.run(() => {
const dialogRef = this.dialog.open(PostDetailComponent, {
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) {
console.error('Cannot open downloader folder, not electron app');
return;
}
// Request the server to give the correct file location
this.httpClient.get<DownloadPath>(this.serverService.baseUrl + '/post/path/' + this.postState.postId).subscribe(
path => {
if (this.fs.existsSync(path.path)) {
this.electronService.shell.openItem(path.path);
} else {
if (this.postState.done <= 0) {
this._snackBar.open('Download has not been started yet for this post', null, {
duration: 5000
});
} else {
this._snackBar.open(path.path + ' does not exist, you probably removed it', null, {
duration: 5000
});
}
}
},
error => {
this._snackBar.open(error.error || 'Unexpected error, check log file', null, {
duration: 5000
});
}
);
}
}
@@ -1,4 +1,4 @@
<div class="container" style="height: 100%; background-color: white">
<div (contextmenu)="context($event)" class="container" style="height: 100%; background-color: white">
<ng-container *ngIf="postState$ | async as postState">
<div
[ngClass]="{
@@ -28,19 +28,6 @@
>{{ postState.done + '/' + postState.total }} from {{ postState.hosts }}</span
>
</span>
<button [matMenuTriggerFor]="menu" fxFlex="nogrow" mat-icon-button>
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button (click)="seeDetails()" mat-menu-item>
<mat-icon>list</mat-icon>
<span>Files</span>
</button>
<button (click)="open()" *ngIf="electronService.isElectronApp" mat-menu-item>
<mat-icon>open_in_new</mat-icon>
<span>Download Location</span>
</button>
</mat-menu>
</div>
<div class="progress-bar" color="accent" style="position: absolute; width: 100%; top: 37px; padding: 0 10px">
<mat-progress-bar *ngIf="(loaded | async)" [value]="postState.progress" color="primary"
@@ -5,6 +5,7 @@
.row-text {
white-space: nowrap;
overflow: hidden;
padding-right: 15px;
}
.row-text > span {
@@ -7,19 +7,15 @@ import {
NgZone,
ChangeDetectionStrategy,
AfterViewInit,
EventEmitter
EventEmitter,
} from '@angular/core';
import { AgRendererComponent } from 'ag-grid-angular';
import { Subscription, Observable, BehaviorSubject, Subject } from 'rxjs';
import { Subscription, BehaviorSubject, Subject } from 'rxjs';
import { WsHandler } from '../ws-handler';
import { ICellRendererParams } from 'ag-grid-community';
import { ICellRendererParams, RowNode, GridApi } from 'ag-grid-community';
import { ElectronService } from 'ngx-electron';
import { BreakpointState, Breakpoints, BreakpointObserver } from '@angular/cdk/layout';
import { MatDialog, MatSnackBar } from '@angular/material';
import { PostDetailComponent } from '../post-detail/post-detail.component';
import { HttpClient } from '@angular/common/http';
import { ServerService } from '../server-service';
import { DownloadPath } from '../common/download-path.model';
import { MatDialog } from '@angular/material';
import { ContextMenuService } from '../ctxt-menu.service';
@Component({
selector: 'app-progress-cell',
@@ -32,16 +28,9 @@ export class PostProgressRendererComponent implements AgRendererComponent, OnIni
private wsConnectionService: WsConnectionService,
private zone: NgZone,
public electronService: ElectronService,
private breakpointObserver: BreakpointObserver,
public dialog: MatDialog,
private httpClient: HttpClient,
private serverService: ServerService,
private _snackBar: MatSnackBar
) {
private contextMenuService: ContextMenuService) {
this.websocketHandlerPromise = this.wsConnectionService.getConnection();
if (this.electronService.isElectronApp) {
this.fs = this.electronService.remote.require('fs');
}
}
websocketHandlerPromise: Promise<WsHandler>;
@@ -49,10 +38,10 @@ export class PostProgressRendererComponent implements AgRendererComponent, OnIni
private postState: PostState;
updatesSubscription: Subscription;
expanded = false;
isExtraSmall: Observable<BreakpointState> = this.breakpointObserver.observe(Breakpoints.XSmall);
fs;
loaded: Subject<boolean> = new BehaviorSubject(false);
loading;
node: RowNode;
gridApi: GridApi;
trunc(value: number): number {
return Math.trunc(value);
@@ -86,6 +75,8 @@ export class PostProgressRendererComponent implements AgRendererComponent, OnIni
}
agInit(params: ICellRendererParams): void {
this.node = params.node;
this.gridApi = params.api;
this.postState = params.data;
}
@@ -103,55 +94,9 @@ export class PostProgressRendererComponent implements AgRendererComponent, OnIni
}
}
seeDetails() {
const dialogRef = this.dialog.open(PostDetailComponent, {
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() {
if (!this.electronService.isElectronApp) {
console.error('Cannot open downloader folder, not electron app');
return;
}
// Request the server to give the correct file location
this.httpClient.get<DownloadPath>(this.serverService.baseUrl + '/post/path/' + this.postState.postId).subscribe(
path => {
if (this.fs.existsSync(path.path)) {
this.electronService.shell.openItem(path.path);
} else {
if (this.postState.done <= 0) {
this._snackBar.open('Download has not been started yet for this post', null, {
duration: 5000
});
} else {
this._snackBar.open(path.path + ' does not exist, you probably removed it', null, {
duration: 5000
});
}
}
},
error => {
console.error(error);
}
);
context(event: MouseEvent) {
this.gridApi.getSelectedNodes().forEach(e => e.setSelected(false));
this.node.setSelected(true);
this.contextMenuService.openPostCtxtMenu(event, this.postState);
}
}
+7 -2
View File
@@ -6,6 +6,7 @@ import { WsConnectionService } from '../ws-connection.service';
import { GridOptions } from 'ag-grid-community';
import { PostProgressRendererComponent } from './post-progress.renderer.component';
import { Subject } from 'rxjs';
import { ContextMenuService } from '../ctxt-menu.service';
@Component({
selector: 'app-posts',
@@ -17,7 +18,8 @@ export class PostsComponent implements OnInit, OnDestroy, AfterViewInit {
private wsConnection: WsConnectionService,
private zone: NgZone,
private selectionService: SelectionService,
private postsDataService: PostsDataService
private postsDataService: PostsDataService,
private contextMenuService: ContextMenuService
) {
this.gridOptions = <GridOptions>{
columnDefs: [
@@ -28,10 +30,12 @@ export class PostsComponent implements OnInit, OnDestroy, AfterViewInit {
cellRenderer: 'progressCellRenderer',
cellClass: 'no-padding',
sort: 'asc',
suppressMovable: true,
headerCheckboxSelection: true,
headerCheckboxSelectionFilteredOnly: true
}
],
rowBuffer: 200,
rowHeight: 48,
animateRows: true,
rowSelection: 'multiple',
@@ -50,7 +54,8 @@ export class PostsComponent implements OnInit, OnDestroy, AfterViewInit {
},
onGridSizeChanged: () => this.gridOptions.api.sizeColumnsToFit(),
onRowDataUpdated: () => this.gridOptions.api.sizeColumnsToFit(),
onSelectionChanged: () => this.selectionService.onSelectionChanged(this.gridOptions.api.getSelectedNodes())
onSelectionChanged: () => this.selectionService.onSelectionChanged(this.gridOptions.api.getSelectedNodes()),
onBodyScroll: () => this.contextMenuService.closePostCtxtMenu()
};
}
@@ -57,7 +57,9 @@ export class SettingsComponent implements OnInit {
this.desktopSettingsForm.reset(data);
},
error => {
console.error(error);
this._snackBar.open(error.error || 'Unexpected error, check log file', null, {
duration: 5000
});
}
);
}
@@ -28,7 +28,7 @@
<div fxFlex="grow" id="global-search">
<form autocomplete="off">
<mat-form-field style="width: 100%">
<input (ngModelChange)="search($event)" matInput name="search" ngModel/>
<input (ngModelChange)="search($event)" matInput name="search" ngModel placeholder="Search"/>
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
</form>
@@ -101,7 +101,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.postsDataService.remove(data);
},
error => {
console.error(error);
this._snackBar.open(error.error || 'Unexpected error, check log file', null, {
duration: 5000
});
}
);
}
@@ -116,7 +118,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
});
},
error => {
console.error(error);
this._snackBar.open(error.error || 'Unexpected error, check log file', null, {
duration: 5000
});
}
);
}
@@ -131,7 +135,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
});
},
error => {
console.error(error);
this._snackBar.open(error.error || 'Unexpected error, check log file', null, {
duration: 5000
});
}
);
}
@@ -15,4 +15,9 @@
border-radius:12px;
box-shadow:0 0 3px black;
}
}
#global-search {
font-size: 16px;
line-height: 18px;
}
+4 -1
View File
@@ -32,10 +32,13 @@ export class WsConnectionService {
return this.wsHandler;
}
if (this.electronService.isElectronApp) {
this.electronService.ipcRenderer.send('get-port');
const portRequest = setInterval(() => {
this.electronService.ipcRenderer.send('get-port');
}, 1000);
// wait for MainIPC
this.electronService.ipcRenderer.once('port', (event, port) => {
clearInterval(portRequest);
console.log('server running on port', port);
this.serverService.baseUrl = 'http://localhost:' + port;
this.tryConnect(resolve, reject);
+81
View File
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns="http://www.w3.org/2000/svg"
width="1024"
height="1024"
viewBox="0 0 270.93333 270.93334"
version="1.1"
id="svg8"
inkscape:export-filename="C:\Users\Marwen\IdeaProjects\vripper-project\vripper-electron\build\icons\1024x1024.png"
inkscape:export-xdpi="96.00956"
inkscape:export-ydpi="96.00956"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="v.svg">
<defs
id="defs2"/>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="-200.49762"
inkscape:cy="299.73785"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"/>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-26.06665)">
<circle
id="path3715"
cx="135.46666"
cy="161.53331"
style="fill:#aa0000;fill-opacity:1;stroke-width:0.35248032"
r="135.45317"/>
<flowRoot
xml:space="preserve"
id="flowRoot4522"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40.00005341px;line-height:1.25;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none"
transform="matrix(5.7825687,0,0,5.7825687,-859.25089,-2020.8769)"><flowRegion
id="flowRegion4524"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40.00005341px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1"><rect
id="rect4526"
width="514.28571"
height="380"
x="160"
y="356.80539"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40.00005341px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1" /></flowRegion>
<flowPara
id="flowPara4534">V</flowPara></flowRoot>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

@@ -1,4 +1,5 @@
export const environment = {
production: true,
localhost: ''
localhost: '',
version: '2.7.1'
};
+2 -1
View File
@@ -4,7 +4,8 @@
export const environment = {
production: false,
localhost: 'http://localhost:8080'
localhost: 'http://localhost:8080',
version: '2.7.1'
};
/*
+1 -1
View File
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>V Ripper</title>
<title>VRipper</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">