mirror of
https://github.com/dev-claw/vripper-project.git
synced 2026-08-19 08:35:41 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8355b88b9 | ||
|
|
379660236d |
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## [3.2] - 2020-11-16
|
||||
### Changed
|
||||
- Fix build issue
|
||||
|
||||
## [3.1.0] - 2020-11-16
|
||||
### Changed
|
||||
- UI revamp
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>tn.mnlr</groupId>
|
||||
<artifactId>vripper</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.2.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vripper-electron",
|
||||
"version": "3.1.0",
|
||||
"version": "3.2.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vripper-electron",
|
||||
"version": "3.1.0",
|
||||
"version": "3.2.0",
|
||||
"description": "A ripper for vipergirls.to built using web technolgies",
|
||||
"main": "main.js",
|
||||
"author": "death-claw <53543762+death-claw@users.noreply.github.com>",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tn.mnlr</groupId>
|
||||
<artifactId>vripper</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.2.0</version>
|
||||
</parent>
|
||||
<artifactId>vripper-electron</artifactId>
|
||||
<name>vripper-electron</name>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tn.mnlr</groupId>
|
||||
<artifactId>vripper</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.2.0</version>
|
||||
</parent>
|
||||
<artifactId>vripper-server</artifactId>
|
||||
<name>vripper-server</name>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package tn.mnlr.vripper.services.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
import tn.mnlr.vripper.SpringContext;
|
||||
import tn.mnlr.vripper.host.Host;
|
||||
import tn.mnlr.vripper.jpa.domain.Queued;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ApiThreadHandler extends DefaultHandler {
|
||||
|
||||
private final Queued queued;
|
||||
private final Collection<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;
|
||||
|
||||
@Getter
|
||||
private final List<MultiPostItem> posts = new ArrayList<>();
|
||||
|
||||
public ApiThreadHandler(Queued queued) {
|
||||
this.queued = queued;
|
||||
this.supportedHosts = SpringContext.getBeansOfType(Host.class).values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDocument() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) {
|
||||
|
||||
switch (qName.toLowerCase()) {
|
||||
case "thread":
|
||||
threadTitle = attributes.getValue("title").trim();
|
||||
break;
|
||||
case "post":
|
||||
imageCount = Integer.parseInt(attributes.getValue("imagecount").trim());
|
||||
postId = attributes.getValue("id").trim();
|
||||
postCounter = Integer.parseInt(attributes.getValue("number").trim());
|
||||
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(1))));
|
||||
if (previewCounter++ < 4) {
|
||||
Optional.ofNullable(attributes.getValue("thumb_url")).map(String::trim).ifPresent(previews::add);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String qName) {
|
||||
if ("post".equals(qName.toLowerCase())) {
|
||||
if (imageCount != 0) {
|
||||
posts.add(new MultiPostItem(
|
||||
queued.getThreadId(),
|
||||
postId,
|
||||
postCounter,
|
||||
postTitle,
|
||||
imageCount,
|
||||
String.format("https://vipergirls.to/threads/?p=%s&viewfull=1#post%s", postId, postId),
|
||||
previews,
|
||||
hostMap.entrySet().stream().filter(v -> v.getValue().get() > 0).map(e -> e.getKey().getHost() + " (" + e.getValue().get() + ")").collect(Collectors.joining(", "))
|
||||
));
|
||||
queued.increment();
|
||||
}
|
||||
previewCounter = 0;
|
||||
previews = new ArrayList<>();
|
||||
hostMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endDocument() {
|
||||
queued.done();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package tn.mnlr.vripper.services.domain;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.jodah.failsafe.Failsafe;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import tn.mnlr.vripper.SpringContext;
|
||||
import tn.mnlr.vripper.VripperApplication;
|
||||
import tn.mnlr.vripper.exception.DownloadException;
|
||||
import tn.mnlr.vripper.exception.PostParseException;
|
||||
import tn.mnlr.vripper.jpa.domain.Queued;
|
||||
import tn.mnlr.vripper.services.ConnectionService;
|
||||
import tn.mnlr.vripper.services.VGAuthService;
|
||||
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@Slf4j
|
||||
public class ApiThreadParser {
|
||||
|
||||
private static final String VR_API = "https://vipergirls.to/vr.php";
|
||||
|
||||
private static final SAXParserFactory factory = SAXParserFactory.newInstance();
|
||||
private final Queued queued;
|
||||
private final ConnectionService cm;
|
||||
private final VGAuthService VGAuthService;
|
||||
|
||||
public ApiThreadParser(Queued queued) {
|
||||
this.queued = queued;
|
||||
this.cm = SpringContext.getBean(ConnectionService.class);
|
||||
this.VGAuthService = SpringContext.getBean(VGAuthService.class);
|
||||
}
|
||||
|
||||
public List<MultiPostItem> parse() throws PostParseException {
|
||||
|
||||
log.debug(String.format("Parsing thread %s", queued));
|
||||
HttpGet httpGet;
|
||||
try {
|
||||
URIBuilder uriBuilder = new URIBuilder(VR_API);
|
||||
uriBuilder.setParameter("t", queued.getThreadId());
|
||||
httpGet = cm.buildHttpGet(uriBuilder.build(), null);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new PostParseException(e);
|
||||
}
|
||||
|
||||
ApiThreadHandler apiThreadHandler = new ApiThreadHandler(queued);
|
||||
AtomicReference<Throwable> thr = new AtomicReference<>();
|
||||
log.debug(String.format("Requesting %s", httpGet));
|
||||
List<MultiPostItem> posts = Failsafe.with(VripperApplication.retryPolicy)
|
||||
.onFailure(e -> thr.set(e.getFailure()))
|
||||
.get(() -> {
|
||||
HttpClient connection = cm.getClient().build();
|
||||
try (CloseableHttpResponse response = (CloseableHttpResponse) connection.execute(httpGet, VGAuthService.getContext())) {
|
||||
if (response.getStatusLine().getStatusCode() / 100 != 2) {
|
||||
throw new DownloadException(String.format("Unexpected response code '%d' for %s", response.getStatusLine().getStatusCode(), httpGet));
|
||||
}
|
||||
|
||||
try {
|
||||
factory.newSAXParser().parse(new BufferedInputStream(response.getEntity().getContent()), apiThreadHandler);
|
||||
return apiThreadHandler.getPosts();
|
||||
} catch (Exception e) {
|
||||
throw new PostParseException(String.format("Failed to parse thread %s", queued), e);
|
||||
} finally {
|
||||
EntityUtils.consumeQuietly(response.getEntity());
|
||||
}
|
||||
}
|
||||
});
|
||||
if (thr.get() != null) {
|
||||
log.error(String.format("parsing failed for thread %s", queued), thr.get());
|
||||
throw new PostParseException(thr.get());
|
||||
}
|
||||
return posts;
|
||||
}
|
||||
}
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vripper-ui",
|
||||
"version": "3.1.0",
|
||||
"version": "3.2.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vripper-ui",
|
||||
"version": "3.1.0",
|
||||
"version": "3.2.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tn.mnlr</groupId>
|
||||
<artifactId>vripper</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.2.0</version>
|
||||
</parent>
|
||||
<artifactId>vripper-ui</artifactId>
|
||||
<name>vripper-ui</name>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import {GridApi, ICellRendererComp, ICellRendererParams} from 'ag-grid-community';
|
||||
import {MultiPostService} from '../services/multi-post.service';
|
||||
import {MultiPostModel} from '../domain/multi-post.model';
|
||||
|
||||
export class CollectorActionsRendererNative implements ICellRendererComp {
|
||||
private gui: HTMLElement;
|
||||
private selectSpan: HTMLSpanElement;
|
||||
private selectButton: HTMLButtonElement;
|
||||
private deleteSpan: HTMLSpanElement;
|
||||
private deleteButton: HTMLButtonElement;
|
||||
private multiPostService: MultiPostService;
|
||||
private multiPostModel: MultiPostModel;
|
||||
private gridApi: GridApi;
|
||||
|
||||
destroy(): void {
|
||||
this.selectButton.removeEventListener('click', () => this.multiPostService.selectItems(this.multiPostModel));
|
||||
this.deleteButton.removeEventListener('click', () => this.multiPostService.remove(this.gridApi, this.multiPostModel));
|
||||
}
|
||||
|
||||
getGui(): HTMLElement {
|
||||
return this.gui;
|
||||
}
|
||||
|
||||
init(params: ICellRendererParams): void {
|
||||
// @ts-ignore
|
||||
this.multiPostService = params.grabQueueService;
|
||||
this.multiPostModel = params.node.data;
|
||||
this.gridApi = params.api;
|
||||
this.gui = document.createElement('div');
|
||||
this.gui.setAttribute('style', 'display: flex');
|
||||
this.selectSpan = document.createElement('span');
|
||||
this.selectButton = document.createElement('button');
|
||||
this.selectButton.classList.add('mat-icon-button', 'mat-button-base', 'cell-icon');
|
||||
this.selectButton.setAttribute('style', 'width: auto; height: 24px; display: flex; align-items: center');
|
||||
this.selectButton.innerHTML = `<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M20,16H8V4H20V16M16,20V22H4A2,2 0 0,1 2,20V7H4V20H16M18.53,8.06L17.47,7L12.59,11.88L10.47,9.76L9.41,10.82L12.59,14L18.53,8.06Z" /></svg><span>Select</span>`;
|
||||
this.selectSpan.append(this.selectButton);
|
||||
|
||||
this.deleteSpan = document.createElement('span');
|
||||
this.deleteButton = document.createElement('button');
|
||||
this.deleteButton.classList.add('mat-icon-button', 'mat-button-base', 'cell-icon');
|
||||
this.deleteButton.setAttribute('style', 'width: auto; height: 24px; display: flex; align-items: center');
|
||||
this.deleteButton.innerHTML = `<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" /></svg><span>Remove</span>`;
|
||||
this.deleteSpan.append(this.deleteButton);
|
||||
|
||||
this.gui.append(this.selectSpan, this.deleteSpan);
|
||||
|
||||
this.selectButton.addEventListener('click', () => this.multiPostService.selectItems(this.multiPostModel));
|
||||
this.deleteButton.addEventListener('click', () => this.multiPostService.remove(this.gridApi, this.multiPostModel));
|
||||
}
|
||||
|
||||
refresh(params: ICellRendererParams): boolean {
|
||||
this.multiPostModel = params.node.data;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import {GridApi, ICellRendererComp, ICellRendererParams, RowNode} from 'ag-grid-community';
|
||||
import {PostContextMenuService} from '../services/post-context-menu.service';
|
||||
|
||||
export class PostAltRendererNative implements ICellRendererComp {
|
||||
private gui: HTMLElement;
|
||||
private names: string[];
|
||||
private text: HTMLSpanElement;
|
||||
private gridApi: GridApi;
|
||||
private node: RowNode;
|
||||
private contextMenuService: PostContextMenuService;
|
||||
|
||||
destroy(): void {
|
||||
if(this.gui) {
|
||||
this.gui.removeEventListener('contextmenu', this.context.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
getGui(): HTMLElement {
|
||||
return this.gui;
|
||||
}
|
||||
|
||||
init(params: ICellRendererParams): void {
|
||||
// @ts-ignore
|
||||
this.contextMenuService = params.contextMenuService;
|
||||
this.gridApi = params.api;
|
||||
this.node = params.node;
|
||||
this.names = params.node.data.metadata?.resolvedNames || [];
|
||||
this.gui = document.createElement('div');
|
||||
this.gui.classList.add('text-cell');
|
||||
this.text = document.createElement('span');
|
||||
this.text.innerText = this.names.join(', ') || 'none';
|
||||
this.text.setAttribute('title', this.names.join(', ') || 'none');
|
||||
this.gui.append(this.text);
|
||||
this.gui.addEventListener('contextmenu', this.context.bind(this));
|
||||
}
|
||||
|
||||
refresh(params: ICellRendererParams): boolean {
|
||||
this.names = params.node.data.metadata?.resolvedNames || [];
|
||||
this.text.innerText = this.names.join(', ') || 'none';
|
||||
this.text.setAttribute('title', this.names.join(', ') || 'none');
|
||||
return true;
|
||||
}
|
||||
|
||||
context(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
this.gridApi.getSelectedNodes().forEach(e => e.setSelected(false));
|
||||
this.node.setSelected(true);
|
||||
this.contextMenuService.openPostContextMenu(event, this.node.data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import {GridApi, ICellRendererComp, ICellRendererParams, RowNode} from 'ag-grid-community';
|
||||
import {PostContextMenuService} from '../services/post-context-menu.service';
|
||||
|
||||
export class PostFilesRendererNative implements ICellRendererComp {
|
||||
|
||||
private gui: HTMLElement;
|
||||
private done: number;
|
||||
private total: number;
|
||||
private hosts: string[];
|
||||
private text: HTMLElement;
|
||||
private gridApi: GridApi;
|
||||
private node: RowNode;
|
||||
private contextMenuService: PostContextMenuService;
|
||||
|
||||
destroy(): void {
|
||||
if(this.gui) {
|
||||
this.gui.removeEventListener('contextmenu', this.context.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
getGui(): HTMLElement {
|
||||
return this.gui;
|
||||
}
|
||||
|
||||
init(params: ICellRendererParams): void {
|
||||
// @ts-ignore
|
||||
this.contextMenuService = params.contextMenuService;
|
||||
this.gridApi = params.api;
|
||||
this.node = params.node;
|
||||
this.done = params.node.data.done;
|
||||
this.total = params.node.data.total;
|
||||
this.hosts = params.node.data.hosts;
|
||||
this.gui = document.createElement('div');
|
||||
this.gui.classList.add('text-cell');
|
||||
this.text = document.createElement('span');
|
||||
this.text.innerText = `${this.done}/${this.total} from ${this.hosts.join(', ')}`;
|
||||
this.text.setAttribute('title', `${this.done}/${this.total} from ${this.hosts.join(', ')}`);
|
||||
this.gui.append(this.text);
|
||||
this.gui.addEventListener('contextmenu', this.context.bind(this));
|
||||
}
|
||||
|
||||
refresh(params: ICellRendererParams): boolean {
|
||||
this.done = params.node.data.done;
|
||||
this.total = params.node.data.total;
|
||||
this.hosts = params.node.data.hosts;
|
||||
this.text.innerText = `${this.done}/${this.total} from ${this.hosts.join(', ')}`;
|
||||
this.text.setAttribute('title', `${this.done}/${this.total} from ${this.hosts.join(', ')}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
context(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
this.gridApi.getSelectedNodes().forEach(e => e.setSelected(false));
|
||||
this.node.setSelected(true);
|
||||
this.contextMenuService.openPostContextMenu(event, this.node.data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import {GridApi, ICellRendererParams, RowNode} from 'ag-grid-community';
|
||||
import {PostContextMenuService} from '../services/post-context-menu.service';
|
||||
import {ProgressRendererNative} from './progress-renderer.native';
|
||||
|
||||
export class PostProgressRendererNative extends ProgressRendererNative {
|
||||
|
||||
private gridApi: GridApi;
|
||||
private node: RowNode;
|
||||
private contextMenuService: PostContextMenuService;
|
||||
|
||||
destroy(): void {
|
||||
if(this.gui) {
|
||||
this.gui.removeEventListener('contextmenu', this.context.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
init(params: ICellRendererParams): void {
|
||||
super.init(params);
|
||||
// @ts-ignore
|
||||
this.contextMenuService = params.contextMenuService;
|
||||
this.gridApi = params.api;
|
||||
this.node = params.node;
|
||||
this.gui.addEventListener('contextmenu', this.context.bind(this));
|
||||
}
|
||||
|
||||
context(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
this.gridApi.getSelectedNodes().forEach(e => e.setSelected(false));
|
||||
this.node.setSelected(true);
|
||||
this.contextMenuService.openPostContextMenu(event, this.node.data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import {StatusRendererNative} from './status-renderer.native';
|
||||
import {GridApi, ICellRendererParams, RowNode} from 'ag-grid-community';
|
||||
import {PostContextMenuService} from '../services/post-context-menu.service';
|
||||
|
||||
export class PostStatusRendererNative extends StatusRendererNative {
|
||||
|
||||
private gridApi: GridApi;
|
||||
private node: RowNode;
|
||||
private contextMenuService: PostContextMenuService;
|
||||
|
||||
destroy(): void {
|
||||
if(this.gui) {
|
||||
this.gui.removeEventListener('contextmenu', this.context.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
init(params: ICellRendererParams): void {
|
||||
super.init(params);
|
||||
// @ts-ignore
|
||||
this.contextMenuService = params.contextMenuService;
|
||||
this.gridApi = params.api;
|
||||
this.node = params.node;
|
||||
this.gui.addEventListener('contextmenu', this.context.bind(this));
|
||||
}
|
||||
|
||||
context(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
this.gridApi.getSelectedNodes().forEach(e => e.setSelected(false));
|
||||
this.node.setSelected(true);
|
||||
this.contextMenuService.openPostContextMenu(event, this.node.data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import {ICellRendererComp, ICellRendererParams} from 'ag-grid-community';
|
||||
|
||||
export class ProgressRendererNative implements ICellRendererComp {
|
||||
|
||||
protected gui: HTMLElement;
|
||||
protected progressBackDiv: HTMLElement;
|
||||
protected progressFrontDiv: HTMLElement;
|
||||
private progress: number;
|
||||
|
||||
destroy(): void {
|
||||
}
|
||||
|
||||
getGui(): HTMLElement {
|
||||
return this.gui;
|
||||
}
|
||||
|
||||
init(params: ICellRendererParams): void {
|
||||
this.progress = params.value;
|
||||
this.gui = document.createElement('div');
|
||||
this.gui.setAttribute('class', 'native-progress-bar-container');
|
||||
this.progressBackDiv = document.createElement('div');
|
||||
this.progressBackDiv.setAttribute('class', 'native-progress-bar-back');
|
||||
this.progressFrontDiv = document.createElement('div');
|
||||
this.progressFrontDiv.setAttribute('class', 'native-progress-bar-front');
|
||||
this.progressFrontDiv.setAttribute('style', `width: ${this.progress}%`);
|
||||
this.progressBackDiv.append(this.progressFrontDiv);
|
||||
this.gui.append(this.progressBackDiv);
|
||||
}
|
||||
|
||||
refresh(params: ICellRendererParams): boolean {
|
||||
this.progress = params.value;
|
||||
this.progressFrontDiv.setAttribute('style', `width: ${this.progress}%`);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import {ICellRendererComp, ICellRendererParams} from 'ag-grid-community';
|
||||
|
||||
export class StatusRendererNative implements ICellRendererComp {
|
||||
|
||||
protected gui: HTMLElement;
|
||||
private container: HTMLElement;
|
||||
private icon: HTMLElement;
|
||||
private text: HTMLElement;
|
||||
private status: string;
|
||||
private currentStatusClass;
|
||||
|
||||
destroy(): void {
|
||||
}
|
||||
|
||||
getGui(): HTMLElement {
|
||||
return this.gui;
|
||||
}
|
||||
|
||||
init(params: ICellRendererParams): void {
|
||||
this.status = params.value;
|
||||
this.gui = document.createElement('div');
|
||||
this.gui.classList.add('text-cell');
|
||||
|
||||
this.container = document.createElement('span');
|
||||
this.container.classList.add('native-status-container');
|
||||
|
||||
this.icon = document.createElement('span');
|
||||
this.currentStatusClass = this.getClass(this.status);
|
||||
this.icon.classList.add('native-status-icon', this.currentStatusClass);
|
||||
this.icon.innerHTML = this.getIcon(this.status);
|
||||
|
||||
this.text = document.createElement('span');
|
||||
this.text.classList.add('native-status-text');
|
||||
this.text.innerText = this.titleCase(this.status);
|
||||
|
||||
this.container.append(this.icon, this.text);
|
||||
this.gui.append(this.container);
|
||||
}
|
||||
|
||||
refresh(params: any): boolean {
|
||||
const oldStatusClass = this.currentStatusClass;
|
||||
this.status = params.value;
|
||||
this.currentStatusClass = this.getClass(this.status);
|
||||
this.icon.classList.replace(oldStatusClass, this.currentStatusClass);
|
||||
this.icon.innerHTML = this.getIcon(this.status);
|
||||
|
||||
this.text.innerText = this.titleCase(this.status);
|
||||
return true;
|
||||
}
|
||||
|
||||
titleCase(string: string): string {
|
||||
return string[0].toUpperCase() + string.substr(1, string.length).toLowerCase();
|
||||
}
|
||||
|
||||
getIcon(status: string) {
|
||||
switch (status) {
|
||||
case 'PENDING':
|
||||
return `<svg style="width:24px;height:24px" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C6.47,22 2,17.5 2,12A10,10 0 0,1 12,2M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z" />
|
||||
</svg>`;
|
||||
case 'DOWNLOADING':
|
||||
return `<svg style="width:24px;height:24px" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" />
|
||||
</svg>`;
|
||||
case 'COMPLETE':
|
||||
return `<svg style="width:24px;height:24px" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z" />
|
||||
</svg>`;
|
||||
case 'ERROR':
|
||||
return `<svg style="width:24px;height:24px" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16" />
|
||||
</svg>`;
|
||||
case 'STOPPED':
|
||||
return `<svg style="width:24px;height:24px" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M14,19H18V5H14M6,19H10V5H6V19Z" />
|
||||
</svg></span>`;
|
||||
case 'PARTIAL':
|
||||
return `<svg style="width:24px;height:24px" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" />
|
||||
</svg>`;
|
||||
}
|
||||
}
|
||||
|
||||
getClass(status: string) {
|
||||
switch (status) {
|
||||
case 'PENDING':
|
||||
return 'pending';
|
||||
case 'DOWNLOADING':
|
||||
return 'downloading';
|
||||
case 'COMPLETE':
|
||||
return 'complete';
|
||||
case 'ERROR':
|
||||
return 'error';
|
||||
case 'STOPPED':
|
||||
return 'stopped';
|
||||
case 'PARTIAL':
|
||||
return 'downloading';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import {GridApi, ICellRendererComp, ICellRendererParams, RowNode} from 'ag-grid-community';
|
||||
import {PostContextMenuService} from '../services/post-context-menu.service';
|
||||
import {Overlay, OverlayPositionBuilder, OverlayRef} from '@angular/cdk/overlay';
|
||||
import {ComponentPortal} from '@angular/cdk/portal';
|
||||
import {AppPreviewComponent} from '../preview-tooltip/preview-tooltip.component';
|
||||
import {ComponentRef, NgZone} from '@angular/core';
|
||||
|
||||
export class TitleRendererNative implements ICellRendererComp {
|
||||
private gui: HTMLElement;
|
||||
private text: HTMLSpanElement;
|
||||
private gridApi: GridApi;
|
||||
private node: RowNode;
|
||||
private contextMenuService: PostContextMenuService;
|
||||
private icon: HTMLSpanElement;
|
||||
|
||||
private overlayPositionBuilder: OverlayPositionBuilder;
|
||||
private overlay: Overlay;
|
||||
private zone: NgZone;
|
||||
|
||||
private overlayRef: OverlayRef;
|
||||
tooltipPortal = new ComponentPortal(AppPreviewComponent);
|
||||
appPreview: string[];
|
||||
|
||||
destroy(): void {
|
||||
if(this.gui) {
|
||||
this.gui.removeEventListener('contextmenu', this.context.bind(this));
|
||||
}
|
||||
if(this.overlayRef) {
|
||||
this.overlayRef.dispose();
|
||||
}
|
||||
|
||||
if(this.icon) {
|
||||
this.icon.removeEventListener('mouseenter', this.mouseenter.bind(this));
|
||||
this.icon.removeEventListener('mouseleave', this.mouseout.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
getGui(): HTMLElement {
|
||||
return this.gui;
|
||||
}
|
||||
|
||||
init(params: ICellRendererParams): void {
|
||||
// @ts-ignore
|
||||
this.contextMenuService = params.contextMenuService;
|
||||
// @ts-ignore
|
||||
this.overlayPositionBuilder = params.overlayPositionBuilder;
|
||||
// @ts-ignore
|
||||
this.overlay = params.overlay;
|
||||
// @ts-ignore
|
||||
this.zone = params.zone;
|
||||
|
||||
this.gridApi = params.api;
|
||||
this.node = params.node;
|
||||
this.appPreview = this.node.data.previews;
|
||||
this.gui = document.createElement('div');
|
||||
this.gui.classList.add('text-cell');
|
||||
this.gui.setAttribute('style', 'display: flex; align-items: center');
|
||||
this.icon = document.createElement('span');
|
||||
this.icon.setAttribute('style', 'height: 24px; cursor: pointer');
|
||||
this.icon.classList.add('cell-icon');
|
||||
this.icon.innerHTML = `<svg style="width:24px;height:24px" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M8.5,13.5L11,16.5L14.5,12L19,18H5M21,19V5C21,3.89 20.1,3 19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19Z" />
|
||||
</svg>`;
|
||||
this.text = document.createElement('span');
|
||||
this.text.classList.add('text-cell');
|
||||
this.text.innerText = params.value;
|
||||
this.text.setAttribute('title', params.value);
|
||||
this.gui.append(this.icon, this.text);
|
||||
this.gui.addEventListener('contextmenu', this.context.bind(this));
|
||||
|
||||
const positionStrategy = this.overlayPositionBuilder
|
||||
.flexibleConnectedTo(this.icon)
|
||||
.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() });
|
||||
|
||||
this.icon.addEventListener('mouseenter', this.mouseenter.bind(this));
|
||||
this.icon.addEventListener('mouseleave', this.mouseout.bind(this));
|
||||
}
|
||||
|
||||
mouseenter() {
|
||||
this.zone.run(() => {
|
||||
const tooltipRef: ComponentRef<AppPreviewComponent> = this.overlayRef.attach(this.tooltipPortal);
|
||||
tooltipRef.instance.links = this.appPreview;
|
||||
});
|
||||
}
|
||||
|
||||
mouseout() {
|
||||
this.zone.run(() => this.overlayRef.detach());
|
||||
}
|
||||
|
||||
refresh(params: ICellRendererParams): boolean {
|
||||
this.text.innerText = params.value;
|
||||
this.text.setAttribute('title', params.value);
|
||||
return true;
|
||||
}
|
||||
|
||||
context(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
this.gridApi.getSelectedNodes().forEach(e => e.setSelected(false));
|
||||
this.node.setSelected(true);
|
||||
this.contextMenuService.openPostContextMenu(event, this.node.data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import {ICellRendererComp, ICellRendererParams} from 'ag-grid-community';
|
||||
import {ElectronService} from 'ngx-electron';
|
||||
|
||||
export class UrlRendererNative implements ICellRendererComp {
|
||||
|
||||
private electronService: ElectronService;
|
||||
private gui: HTMLElement;
|
||||
private text: HTMLSpanElement;
|
||||
private link: HTMLAnchorElement;
|
||||
private url: string;
|
||||
|
||||
destroy(): void {
|
||||
if(this.link) {
|
||||
this.link.removeEventListener('click', this.goTo.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
getGui(): HTMLElement {
|
||||
return this.gui;
|
||||
}
|
||||
|
||||
init(params: ICellRendererParams): void {
|
||||
// @ts-ignore
|
||||
this.electronService = params.electronService;
|
||||
|
||||
this.url = params.value;
|
||||
this.gui = document.createElement('div');
|
||||
this.gui.classList.add('text-cell');
|
||||
this.text = document.createElement('span');
|
||||
this.link = document.createElement('a');
|
||||
this.link.setAttribute('href', 'javascript:void(0)');
|
||||
this.link.innerText = this.url;
|
||||
|
||||
this.text.append(this.link);
|
||||
this.gui.append(this.text);
|
||||
this.link.addEventListener('click', this.goTo.bind(this));
|
||||
}
|
||||
|
||||
refresh(params: ICellRendererParams): boolean {
|
||||
this.url = params.value;
|
||||
this.link.innerText = this.url;
|
||||
return true;
|
||||
}
|
||||
|
||||
goTo() {
|
||||
if (this.electronService.isElectronApp) {
|
||||
this.electronService.shell.openExternal(this.url).then();
|
||||
} else {
|
||||
window.open(this.url, '_blank');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {MultiPostItemsComponent} from '../multi-post-items/multi-post-items.component';
|
||||
import {MultiPostModel} from '../domain/multi-post.model';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {ServerService} from './server-service';
|
||||
import { GridApi } from 'ag-grid-community';
|
||||
|
||||
interface ThreadId {
|
||||
threadId: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MultiPostService {
|
||||
|
||||
constructor(private dialog: MatDialog,
|
||||
private httpClient: HttpClient,
|
||||
private serverService: ServerService) {
|
||||
}
|
||||
|
||||
selectItems(multiPostModel: MultiPostModel) {
|
||||
this.dialog.open(MultiPostItemsComponent, {
|
||||
width: '90%',
|
||||
height: '90%',
|
||||
maxWidth: '100vw',
|
||||
maxHeight: '100vh',
|
||||
data: multiPostModel
|
||||
});
|
||||
}
|
||||
|
||||
remove(api: GridApi, multiPostModel: MultiPostModel) {
|
||||
this.httpClient.post<ThreadId>(this.serverService.baseUrl + '/grab/remove', {threadId: multiPostModel.threadId}).subscribe(
|
||||
data => {
|
||||
MultiPostService.removeFromGrid(api, data);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static removeFromGrid(api: GridApi, data: ThreadId) {
|
||||
const removeTx = [];
|
||||
const nodeToDelete = api.getRowNode(data.threadId);
|
||||
if (nodeToDelete != null) {
|
||||
removeTx.push(nodeToDelete.data);
|
||||
}
|
||||
api.applyTransaction({remove: removeTx});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
@mixin toolbar-theme($theme, $dark: false) {
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$warn: map-get($theme, warn);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
|
||||
app-toolbar .mat-toolbar {
|
||||
@if $dark {
|
||||
background: mat-color($background);
|
||||
} @else {
|
||||
background: mat-color($accent);
|
||||
}
|
||||
}
|
||||
|
||||
app-toolbar .mat-form-field-label {
|
||||
@if $dark {
|
||||
color: mat-color($foreground, text);
|
||||
} @else {
|
||||
color: mat-color($background, background);
|
||||
}
|
||||
}
|
||||
|
||||
app-toolbar .mat-icon {
|
||||
@if $dark {
|
||||
color: mat-color($foreground, text);
|
||||
} @else {
|
||||
color: mat-color($background, background);
|
||||
}
|
||||
}
|
||||
|
||||
app-toolbar .mat-form-field {
|
||||
@if $dark {
|
||||
color: mat-color($foreground, text);
|
||||
} @else {
|
||||
color: mat-color($background, background);
|
||||
}
|
||||
}
|
||||
|
||||
app-toolbar .logged-user {
|
||||
@if $dark {
|
||||
color: mat-color($foreground, text);
|
||||
} @else {
|
||||
color: mat-color($background, background);
|
||||
}
|
||||
}
|
||||
|
||||
app-toolbar .mat-icon-button.mat-button-disabled .mat-icon {
|
||||
@if $dark {
|
||||
color: mat-color($foreground, disabled-text);
|
||||
} @else {
|
||||
color: mat-color($foreground, disabled-text);
|
||||
}
|
||||
}
|
||||
|
||||
app-toolbar .add-button .mat-icon {
|
||||
@if $dark {
|
||||
color: mat-color($foreground);
|
||||
} @else {
|
||||
color: mat-color($accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -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: '3.1.0'
|
||||
version: '3.2'
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ export const environment = {
|
||||
production: false,
|
||||
localhost: 'http://localhost:8080',
|
||||
ws: 'ws://localhost:8080',
|
||||
version: '3.1.0'
|
||||
version: '3.2'
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user