Compare commits

...
4 Commits
Author SHA1 Message Date
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
44 changed files with 620 additions and 177 deletions
+15
View File
@@ -1,5 +1,20 @@
# Changelog
## [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
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>tn.mnlr</groupId>
<artifactId>vripper</artifactId>
<version>2.5.1</version>
<version>2.7.0</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.1",
"version": "2.7.0",
"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.1",
"version": "2.7.0",
"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": "V Ripper",
"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.1</version>
<version>2.7.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.5.1</version>
<version>2.7.0</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();
}
@@ -158,7 +158,7 @@ class VRThreadHandler extends DefaultHandler {
imageCount,
String.format("https://vipergirls.to/threads/%s/?p=%s&viewfull=1#post%s", threadId, postId, postId),
previews,
hostMap.entrySet().stream().map(e -> e.getKey().getHost() + " (" + e.getValue().get() + ")").collect(Collectors.joining(", "))
hostMap.entrySet().stream().filter(v -> v.getValue().get() > 0).map(e -> e.getKey().getHost() + " (" + e.getValue().get() + ")").collect(Collectors.joining(", "))
));
}
previewCounter = 0;
@@ -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.1",
"version": "2.7.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vripper-ui",
"version": "2.5.1",
"version": "2.7.0",
"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.1</version>
<version>2.7.0</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]
})
@@ -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() {
+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]
})
@@ -14,7 +14,7 @@ import { MatSnackBar, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { GridOptions, RowNode } from 'ag-grid-community';
import { UrlRendererComponent } from './url-renderer.component';
import { WsHandler } from '../ws-handler';
import { Subscription, concat, merge, BehaviorSubject, Subject } 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';
@@ -54,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,
@@ -62,17 +63,20 @@ 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'
}
],
@@ -118,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
});
}
@@ -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