mirror of
https://github.com/dev-claw/vripper-project.git
synced 2026-08-19 08:35:41 +02:00
fixes #143
This commit is contained in:
@@ -3,7 +3,7 @@ package me.vripper.entities
|
||||
import me.vripper.entities.domain.Status
|
||||
|
||||
data class Image(
|
||||
var id: Long = -1,
|
||||
val id: Long = -1,
|
||||
val postId: Long,
|
||||
val url: String,
|
||||
val thumbUrl: String,
|
||||
|
||||
@@ -5,7 +5,7 @@ import me.vripper.entities.domain.Status
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class Post(
|
||||
var id: Long = -1,
|
||||
val id: Long = -1,
|
||||
val postTitle: String,
|
||||
val threadTitle: String,
|
||||
val forum: String,
|
||||
|
||||
@@ -9,12 +9,9 @@ import me.vripper.model.ErrorCount
|
||||
import me.vripper.model.QueueState
|
||||
import me.vripper.model.Settings
|
||||
|
||||
data class PostEvent(
|
||||
val add: List<Post> = emptyList(),
|
||||
val update: List<Post> = emptyList(),
|
||||
val delete: List<Long> = emptyList()
|
||||
)
|
||||
|
||||
data class PostCreateEvent(val posts: List<Post>)
|
||||
data class PostUpdateEvent(val posts: List<Post>)
|
||||
data class PostDeleteEvent(val postIds: List<Long>)
|
||||
data class ImageEvent(val images: List<Image>)
|
||||
data class ThreadCreateEvent(val thread: Thread)
|
||||
data class ThreadDeleteEvent(val threadId: Long)
|
||||
|
||||
@@ -8,10 +8,10 @@ import java.util.concurrent.Executors
|
||||
|
||||
object EventBus {
|
||||
|
||||
private val Scheduler: Scheduler = Schedulers.fromExecutor(Executors.newSingleThreadExecutor())
|
||||
private val scheduler: Scheduler = Schedulers.fromExecutor(Executors.newSingleThreadExecutor())
|
||||
private val _events = Sinks.many().multicast().onBackpressureBuffer<Any>()
|
||||
val events: Flux<Any> = _events.asFlux().publishOn(Scheduler)
|
||||
val events: Flux<Any> = _events.asFlux().publishOn(scheduler)
|
||||
fun publishEvent(event: Any) {
|
||||
_events.tryEmitNext(event)
|
||||
_events.emitNext(event) { _, _ -> true }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,12 @@ class DataTransaction(
|
||||
|
||||
fun updatePosts(posts: List<Post>) {
|
||||
transaction { postDownloadStateRepository.update(posts) }
|
||||
eventBus.publishEvent(PostEvent(update = posts))
|
||||
eventBus.publishEvent(PostUpdateEvent(posts))
|
||||
}
|
||||
|
||||
fun updatePost(posts: Post) {
|
||||
transaction { postDownloadStateRepository.update(posts) }
|
||||
eventBus.publishEvent(PostEvent(update = listOf(posts)))
|
||||
fun updatePost(post: Post) {
|
||||
transaction { postDownloadStateRepository.update(post) }
|
||||
eventBus.publishEvent(PostUpdateEvent(listOf(post)))
|
||||
}
|
||||
|
||||
fun save(thread: Thread) {
|
||||
@@ -102,7 +102,7 @@ class DataTransaction(
|
||||
}
|
||||
savedPosts
|
||||
}
|
||||
eventBus.publishEvent(PostEvent(add = savedPosts))
|
||||
eventBus.publishEvent(PostCreateEvent(savedPosts))
|
||||
return savedPosts
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ class DataTransaction(
|
||||
}
|
||||
|
||||
|
||||
eventBus.publishEvent(PostEvent(delete = postIds))
|
||||
eventBus.publishEvent(PostDeleteEvent(postIds = postIds))
|
||||
eventBus.publishEvent(ErrorCountEvent(ErrorCount(countImagesInError())))
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class ImagesTableView : Fragment("Photos") {
|
||||
it.copy(images = it.images.filter { image: Image -> image.postId == postId })
|
||||
}.filter {
|
||||
it.images.isNotEmpty()
|
||||
}.buffer(Duration.ofMillis(125))
|
||||
}.buffer(Duration.ofMillis(175))
|
||||
.subscribe { imageEvent ->
|
||||
runLater {
|
||||
for (image in imageEvent.map { it.images }.flatten().reversed().distinct()) {
|
||||
|
||||
@@ -14,13 +14,14 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import me.vripper.event.EventBus
|
||||
import me.vripper.event.PostEvent
|
||||
import me.vripper.event.PostCreateEvent
|
||||
import me.vripper.event.PostDeleteEvent
|
||||
import me.vripper.event.PostUpdateEvent
|
||||
import me.vripper.gui.clipboard.ClipboardService
|
||||
import me.vripper.gui.controller.PostController
|
||||
import me.vripper.gui.model.PostModel
|
||||
import me.vripper.gui.view.*
|
||||
import tornadofx.*
|
||||
import java.time.Duration
|
||||
|
||||
class PostsTableView : View() {
|
||||
|
||||
@@ -54,15 +55,17 @@ class PostsTableView : View() {
|
||||
clipboardService.init()
|
||||
}
|
||||
}
|
||||
eventBus.events.ofType(PostEvent::class.java).buffer(Duration.ofMillis(125)).subscribe { postEvents ->
|
||||
runLater {
|
||||
postEvents.map { it.delete }.flatten().forEach {
|
||||
items.removeIf { p -> p.postId == it }
|
||||
}
|
||||
val add = postEvents.map { it.add }.flatten().map { postController.mapper(it) }
|
||||
tableView.items.addAll(add)
|
||||
|
||||
for (post in postEvents.map { it.update }.flatten().reversed().distinct()) {
|
||||
eventBus.events.ofType(PostCreateEvent::class.java).subscribe { postEvents ->
|
||||
val add = postEvents.posts.map { postController.mapper(it) }
|
||||
runLater {
|
||||
tableView.items.addAll(add)
|
||||
}
|
||||
}
|
||||
|
||||
eventBus.events.ofType(PostUpdateEvent::class.java).subscribe { postEvents ->
|
||||
runLater {
|
||||
for (post in postEvents.posts) {
|
||||
val postModel = items.find { it.postId == post.postId } ?: continue
|
||||
|
||||
postModel.status = post.status.name
|
||||
@@ -77,6 +80,14 @@ class PostsTableView : View() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eventBus.events.ofType(PostDeleteEvent::class.java).subscribe { postEvents ->
|
||||
runLater {
|
||||
postEvents.postIds.forEach {
|
||||
items.removeIf { p -> p.postId == it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val root = vbox {
|
||||
|
||||
@@ -61,10 +61,10 @@
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "vripper-web-ui:build:production"
|
||||
"buildTarget": "vripper-web-ui:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "vripper-web-ui:build:development"
|
||||
"buildTarget": "vripper-web-ui:build:development"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
@@ -75,7 +75,7 @@
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "vripper-web-ui:build"
|
||||
"buildTarget": "vripper-web-ui:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
|
||||
Generated
+4620
-4115
File diff suppressed because it is too large
Load Diff
+23
-23
@@ -11,37 +11,37 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^16.0.0",
|
||||
"@angular/cdk": "^16.0.0",
|
||||
"@angular/common": "^16.0.0",
|
||||
"@angular/compiler": "^16.0.0",
|
||||
"@angular/core": "^16.0.0",
|
||||
"@angular/forms": "^16.0.0",
|
||||
"@angular/material": "^16.0.0",
|
||||
"@angular/platform-browser": "^16.0.0",
|
||||
"@angular/platform-browser-dynamic": "^16.0.0",
|
||||
"@angular/router": "^16.0.0",
|
||||
"@angular/animations": "^17.0.5",
|
||||
"@angular/cdk": "^17.0.1",
|
||||
"@angular/common": "^17.0.5",
|
||||
"@angular/compiler": "^17.0.5",
|
||||
"@angular/core": "^17.0.5",
|
||||
"@angular/forms": "^17.0.5",
|
||||
"@angular/material": "^17.0.1",
|
||||
"@angular/platform-browser": "^17.0.5",
|
||||
"@angular/platform-browser-dynamic": "^17.0.5",
|
||||
"@angular/router": "^17.0.5",
|
||||
"@stomp/rx-stomp": "^2.0.0",
|
||||
"@stomp/stompjs": "^7.0.0",
|
||||
"ag-grid-angular": "^29.3.4",
|
||||
"ag-grid-community": "^29.3.4",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.13.0"
|
||||
"zone.js": "~0.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^16.0.0",
|
||||
"@angular-eslint/builder": "16.0.1",
|
||||
"@angular-eslint/eslint-plugin": "16.0.1",
|
||||
"@angular-eslint/eslint-plugin-template": "16.0.1",
|
||||
"@angular-eslint/schematics": "16.0.1",
|
||||
"@angular-eslint/template-parser": "16.0.1",
|
||||
"@angular/cli": "~16.0.0",
|
||||
"@angular/compiler-cli": "^16.0.0",
|
||||
"@angular-devkit/build-angular": "^17.0.5",
|
||||
"@angular-eslint/builder": "17.1.1",
|
||||
"@angular-eslint/eslint-plugin": "17.1.1",
|
||||
"@angular-eslint/eslint-plugin-template": "17.1.1",
|
||||
"@angular-eslint/schematics": "17.1.1",
|
||||
"@angular-eslint/template-parser": "17.1.1",
|
||||
"@angular/cli": "~17.0.5",
|
||||
"@angular/compiler-cli": "^17.0.5",
|
||||
"@types/jasmine": "~4.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.2",
|
||||
"@typescript-eslint/parser": "5.59.2",
|
||||
"eslint": "^8.39.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||
"@typescript-eslint/parser": "^6.10.0",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"jasmine-core": "~4.6.0",
|
||||
@@ -52,6 +52,6 @@
|
||||
"karma-jasmine-html-reporter": "~2.0.0",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-eslint": "^15.0.1",
|
||||
"typescript": "~5.0.2"
|
||||
"typescript": "~5.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@ import {
|
||||
} from '@angular/material/dialog';
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
import { Settings } from './domain/settings.model';
|
||||
import { ConfirmComponent, ConfirmDialogData } from './confirm/confirm.component';
|
||||
import {
|
||||
ConfirmComponent,
|
||||
ConfirmDialogData,
|
||||
} from './confirm/confirm.component';
|
||||
import { Subject } from 'rxjs';
|
||||
import { RxStompState } from '@stomp/rx-stomp';
|
||||
import { StatusBarComponent } from './status-bar/status-bar.component';
|
||||
@@ -96,6 +99,10 @@ export class AppComponent {
|
||||
this.applicationEndpoint.settings().subscribe(s =>
|
||||
this.dialog.open<SettingsComponent, Settings>(SettingsComponent, {
|
||||
data: s,
|
||||
maxWidth: '100vw',
|
||||
maxHeight: '100vh',
|
||||
width: '80vw',
|
||||
height: '80vh',
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<h2 mat-dialog-title>Confirmation</h2>
|
||||
<mat-dialog-content
|
||||
class="mat-typography"
|
||||
style="min-width: 30vw">
|
||||
<mat-dialog-content class="mat-typography" style="min-width: 30vw">
|
||||
{{ data.message }}
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button (click)="confirm()" cdkFocusInitial>Yes</button>
|
||||
<button mat-button mat-dialog-close cdkFocusInitial>No</button>
|
||||
<button mat-flat-button mat-dialog-close cdkFocusInitial>No</button>
|
||||
<button mat-flat-button color="primary" (click)="confirm()">Yes</button>
|
||||
</mat-dialog-actions>
|
||||
</mat-dialog-content>
|
||||
|
||||
@@ -41,6 +41,8 @@ import { ProgressCellComponent } from '../progress-cell/progress-cell.component'
|
||||
import { Image } from '../domain/image.model';
|
||||
import { ImageDialogData, ImagesComponent } from '../images/images.component';
|
||||
import { formatBytes } from '../utils/utils';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||
|
||||
@Component({
|
||||
selector: 'app-download-table',
|
||||
@@ -52,6 +54,7 @@ import { formatBytes } from '../utils/utils';
|
||||
PortalModule,
|
||||
MatDialogModule,
|
||||
ProgressCellComponent,
|
||||
MatListModule,
|
||||
],
|
||||
templateUrl: './download-table.component.html',
|
||||
styleUrls: ['./download-table.component.scss'],
|
||||
@@ -72,19 +75,23 @@ export class DownloadTableComponent implements OnDestroy {
|
||||
private applicationEndpoint: ApplicationEndpointService,
|
||||
private overlayPositionBuilder: OverlayPositionBuilder,
|
||||
private overlay: Overlay,
|
||||
private dialog: MatDialog
|
||||
private dialog: MatDialog,
|
||||
breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
this.gridOptions = <GridOptions>{
|
||||
columnDefs: [
|
||||
{
|
||||
colId: 'title',
|
||||
headerName: 'Title',
|
||||
field: 'postTitle',
|
||||
tooltipField: 'postTitle',
|
||||
headerCheckboxSelection: true,
|
||||
headerCheckboxSelectionFilteredOnly: true,
|
||||
flex: 2,
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
colId: 'progress',
|
||||
headerName: 'Progress',
|
||||
tooltipValueGetter: params => {
|
||||
if (!params.data) {
|
||||
@@ -103,8 +110,11 @@ export class DownloadTableComponent implements OnDestroy {
|
||||
: (params.data.done / params.data.total) * 100;
|
||||
},
|
||||
cellRenderer: ProgressCellComponent,
|
||||
flex: 1,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
colId: 'status',
|
||||
headerName: 'Status',
|
||||
field: 'status',
|
||||
tooltipValueGetter: params => {
|
||||
@@ -121,14 +131,17 @@ export class DownloadTableComponent implements OnDestroy {
|
||||
value.substring(1, value.length).toLowerCase()
|
||||
);
|
||||
},
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
colId: 'path',
|
||||
headerName: 'Path',
|
||||
field: 'downloadDirectory',
|
||||
tooltipField: 'downloadDirectory',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
colId: 'total',
|
||||
headerName: 'Total',
|
||||
tooltipValueGetter: params =>
|
||||
`${params.data?.done}/${params.data?.total} (${formatBytes(
|
||||
@@ -138,25 +151,32 @@ export class DownloadTableComponent implements OnDestroy {
|
||||
`${params.data?.done}/${params.data?.total} (${formatBytes(
|
||||
params.data?.downloaded
|
||||
)})`,
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
colId: 'hosts',
|
||||
headerName: 'Hosts',
|
||||
field: 'hosts',
|
||||
tooltipField: 'hosts',
|
||||
valueGetter: (params: ValueGetterParams<Post>) => {
|
||||
return params.data?.hosts.join(', ');
|
||||
},
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
colId: 'addedOn',
|
||||
headerName: 'Added On',
|
||||
field: 'addedOn',
|
||||
tooltipField: 'addedOn',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
colId: 'order',
|
||||
headerName: 'Order',
|
||||
field: 'rank',
|
||||
tooltipField: 'rank',
|
||||
sort: 'asc',
|
||||
flex: 0.5,
|
||||
},
|
||||
],
|
||||
defaultColDef: {
|
||||
@@ -165,7 +185,17 @@ export class DownloadTableComponent implements OnDestroy {
|
||||
},
|
||||
rowSelection: 'multiple',
|
||||
getRowId: row => row.data['postId'],
|
||||
onGridReady: () => this.connect(),
|
||||
onGridReady: () => {
|
||||
this.connect();
|
||||
breakpointObserver
|
||||
.observe(Breakpoints.HandsetPortrait)
|
||||
.subscribe(result => {
|
||||
this.agGrid.columnApi.setColumnsVisible(
|
||||
['status', 'total', 'hosts', 'path', 'addedOn', 'order'],
|
||||
!result.matches
|
||||
);
|
||||
});
|
||||
},
|
||||
onRowDataUpdated: (event: RowDataUpdatedEvent<Post>) =>
|
||||
this.rowCountChange.emit(event.api.getDisplayedRowCount()),
|
||||
onCellContextMenu: (event: CellContextMenuEvent<Post>) => {
|
||||
@@ -252,7 +282,13 @@ export class DownloadTableComponent implements OnDestroy {
|
||||
return;
|
||||
}
|
||||
const data: ImageDialogData = { postId: event.data.postId };
|
||||
this.dialog.open(ImagesComponent, { data });
|
||||
this.dialog.open(ImagesComponent, {
|
||||
data,
|
||||
maxWidth: '100vw',
|
||||
maxHeight: '100vh',
|
||||
width: '80vw',
|
||||
height: '80vh',
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
<h2 mat-dialog-title>Images</h2>
|
||||
<mat-dialog-content
|
||||
class="mat-typography"
|
||||
style="min-width: 50vw; min-height: 50vh">
|
||||
<mat-dialog-content class="mat-typography">
|
||||
<ag-grid-angular
|
||||
style="width: 50vw; height: 50vh"
|
||||
style="width: 100%; height: 100%"
|
||||
class="ag-theme-alpine"
|
||||
#agGrid
|
||||
[gridOptions]="gridOptions">
|
||||
</ag-grid-angular>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button [mat-dialog-close]="true" cdkFocusInitial>Close</button>
|
||||
<button mat-flat-button [mat-dialog-close]="true" cdkFocusInitial>Close</button>
|
||||
</mat-dialog-actions>
|
||||
|
||||
@@ -10,6 +10,8 @@ import { MatButtonModule } from '@angular/material/button';
|
||||
import { ValueGetterParams } from 'ag-grid-community/dist/lib/entities/colDef';
|
||||
import { ProgressCellComponent } from '../progress-cell/progress-cell.component';
|
||||
import { ITooltipParams } from 'ag-grid-community/dist/lib/rendering/tooltipComponent';
|
||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||
import { DialogRef } from '@angular/cdk/dialog';
|
||||
|
||||
export interface ImageDialogData {
|
||||
postId: number;
|
||||
@@ -30,23 +32,29 @@ export class ImagesComponent implements OnDestroy {
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: ImageDialogData,
|
||||
private applicationEndpoint: ApplicationEndpointService
|
||||
public dialogRef: DialogRef<ImageDialogData>,
|
||||
private applicationEndpoint: ApplicationEndpointService,
|
||||
breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
this.gridOptions = <GridOptions>{
|
||||
columnDefs: [
|
||||
{
|
||||
colId: 'index',
|
||||
headerName: '#',
|
||||
field: 'index',
|
||||
tooltipField: 'index',
|
||||
sort: 'asc',
|
||||
},
|
||||
{
|
||||
headerName: 'URL',
|
||||
field: 'url',
|
||||
tooltipField: 'url',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
colId: 'url',
|
||||
headerName: 'URL',
|
||||
field: 'url',
|
||||
tooltipField: 'url',
|
||||
flex: 2,
|
||||
},
|
||||
{
|
||||
colId: 'progress',
|
||||
headerName: 'Progress',
|
||||
field: 'progress',
|
||||
tooltipValueGetter: (params: ITooltipParams<Image>) => {
|
||||
@@ -66,8 +74,10 @@ export class ImagesComponent implements OnDestroy {
|
||||
: (params.data.downloaded / params.data.size) * 100;
|
||||
},
|
||||
cellRenderer: ProgressCellComponent,
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
colId: 'status',
|
||||
headerName: 'Status',
|
||||
field: 'status',
|
||||
valueFormatter: (params: ValueFormatterParams<Image>) => {
|
||||
@@ -77,6 +87,7 @@ export class ImagesComponent implements OnDestroy {
|
||||
value.substring(1, value.length).toLowerCase()
|
||||
);
|
||||
},
|
||||
flex: 1,
|
||||
},
|
||||
],
|
||||
defaultColDef: {
|
||||
@@ -84,7 +95,23 @@ export class ImagesComponent implements OnDestroy {
|
||||
resizable: true,
|
||||
},
|
||||
getRowId: row => row.data['url'].toString(),
|
||||
onGridReady: () => this.connect(),
|
||||
onGridReady: () => {
|
||||
this.connect();
|
||||
breakpointObserver
|
||||
.observe(Breakpoints.HandsetPortrait)
|
||||
.subscribe(result => {
|
||||
this.agGrid.columnApi.setColumnsVisible(
|
||||
['index', 'status'],
|
||||
!result.matches
|
||||
);
|
||||
if (result.matches) {
|
||||
this.dialogRef.updateSize('100vw', '80vh');
|
||||
} else {
|
||||
this.dialogRef.updateSize('80vw', '80vh');
|
||||
}
|
||||
this.dialogRef.updatePosition();
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ export class LogTableComponent implements OnInit, OnDestroy {
|
||||
field: 'time',
|
||||
tooltipField: 'time',
|
||||
sort: 'desc',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
headerName: 'Type',
|
||||
@@ -70,11 +71,13 @@ export class LogTableComponent implements OnInit, OnDestroy {
|
||||
return params.data.type;
|
||||
}
|
||||
},
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
headerName: 'Status',
|
||||
field: 'status',
|
||||
tooltipField: 'status',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
headerName: 'Message',
|
||||
|
||||
@@ -41,7 +41,13 @@ export class PostContextmenuComponent {
|
||||
|
||||
openImages = () => {
|
||||
const data: ImageDialogData = { postId: this.post.postId };
|
||||
this.dialog.open(ImagesComponent, { data });
|
||||
this.dialog.open(ImagesComponent, {
|
||||
data,
|
||||
maxWidth: '100vw',
|
||||
maxHeight: '100vh',
|
||||
width: '80vw',
|
||||
height: '80vh',
|
||||
});
|
||||
};
|
||||
|
||||
onPostStart!: () => void;
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
<mat-dialog-content
|
||||
class="mat-typography"
|
||||
style="min-width: 50vw; min-height: 50vh">
|
||||
<mat-form-field appearance="outline" style="width: 100%; height: 100%; padding-top: 20px">
|
||||
<mat-form-field
|
||||
appearance="outline"
|
||||
style="width: 100%; height: calc(100% - 20px); padding-top: 20px">
|
||||
<mat-label>Add new links</mat-label>
|
||||
<textarea
|
||||
[formControl]="formControl"
|
||||
cdkTextareaAutosize
|
||||
cdkAutosizeMinRows="5"
|
||||
matInput
|
||||
name="url"
|
||||
placeholder="Put each link in a new line"
|
||||
@@ -15,8 +16,11 @@
|
||||
</mat-form-field>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button mat-dialog-close>Cancel</button>
|
||||
<button mat-button [mat-dialog-close]="formControl.value" cdkFocusInitial>
|
||||
<button mat-flat-button mat-dialog-close cdkFocusInitial>Cancel</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
[mat-dialog-close]="formControl.value">
|
||||
Scan
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
|
||||
@@ -5,6 +5,8 @@ import { MatButtonModule } from '@angular/material/button';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { TextFieldModule } from '@angular/cdk/text-field';
|
||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||
import { DialogRef } from '@angular/cdk/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-scan',
|
||||
@@ -22,4 +24,19 @@ import { TextFieldModule } from '@angular/cdk/text-field';
|
||||
})
|
||||
export class ScanComponent {
|
||||
formControl = new FormControl<string>('');
|
||||
constructor(
|
||||
public dialogRef: DialogRef<never>,
|
||||
breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
breakpointObserver
|
||||
.observe(Breakpoints.HandsetPortrait)
|
||||
.subscribe(result => {
|
||||
if (result.matches) {
|
||||
this.dialogRef.updateSize('100vw', '80vh');
|
||||
} else {
|
||||
this.dialogRef.updateSize('80vw', '80vh');
|
||||
}
|
||||
this.dialogRef.updatePosition();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,22 +128,22 @@
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Global concurrent downloads</mat-label>
|
||||
<input
|
||||
formControlName='maxConcurrentPerHost'
|
||||
formControlName="maxConcurrentPerHost"
|
||||
matInput
|
||||
max="12"
|
||||
min="0"
|
||||
name='maxConcurrentPerHost'
|
||||
name="maxConcurrentPerHost"
|
||||
required
|
||||
type="number" />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Concurrent downloads per host</mat-label>
|
||||
<input
|
||||
formControlName='maxGlobalConcurrent'
|
||||
formControlName="maxGlobalConcurrent"
|
||||
matInput
|
||||
max="4"
|
||||
min="1"
|
||||
name='maxGlobalConcurrent'
|
||||
name="maxGlobalConcurrent"
|
||||
required
|
||||
type="number" />
|
||||
</mat-form-field>
|
||||
@@ -229,9 +229,9 @@
|
||||
</div>
|
||||
</form>
|
||||
</mat-tab>
|
||||
<mat-tab label='System'>
|
||||
<mat-tab label="System">
|
||||
<form
|
||||
[formGroup]='systemSettingsForm'
|
||||
[formGroup]="systemSettingsForm"
|
||||
autocomplete="off"
|
||||
style="
|
||||
display: flex;
|
||||
@@ -242,17 +242,17 @@
|
||||
">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Temporary Path</mat-label>
|
||||
<input formControlName='tempPath' matInput name='tempPath' required />
|
||||
<input formControlName="tempPath" matInput name="tempPath" required />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance='outline'>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Cache Path</mat-label>
|
||||
<input
|
||||
formControlName='cachePath'
|
||||
formControlName="cachePath"
|
||||
matInput
|
||||
name='cachePath'
|
||||
name="cachePath"
|
||||
required />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance='outline'>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Maximum log entries</mat-label>
|
||||
<input
|
||||
formControlName="maxEventLog"
|
||||
@@ -268,6 +268,6 @@
|
||||
</mat-tab-group>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button mat-dialog-close>Cancel</button>
|
||||
<button (click)="save()" mat-button cdkFocusInitial>Save</button>
|
||||
<button mat-flat-button mat-dialog-close cdkFocusInitial>Cancel</button>
|
||||
<button (click)="save()" mat-flat-button color="primary">Save</button>
|
||||
</mat-dialog-actions>
|
||||
|
||||
@@ -25,6 +25,8 @@ import {
|
||||
ViperSettings,
|
||||
} from '../domain/settings.model';
|
||||
import { ApplicationEndpointService } from '../services/application-endpoint.service';
|
||||
import { DialogRef } from '@angular/cdk/dialog';
|
||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings',
|
||||
@@ -87,13 +89,23 @@ export class SettingsComponent {
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: Settings,
|
||||
public dialogRef: MatDialogRef<SettingsComponent>,
|
||||
private applicationEndpoint: ApplicationEndpointService
|
||||
public dialogRef: DialogRef<Settings>,
|
||||
private applicationEndpoint: ApplicationEndpointService,
|
||||
breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
this.viperGirlsSettingsForm.reset(data.viperSettings);
|
||||
this.downloadSettingsForm.reset(data.downloadSettings);
|
||||
this.connectionSettingsForm.reset(data.connectionSettings);
|
||||
this.systemSettingsForm.reset(data.systemSettings);
|
||||
breakpointObserver
|
||||
.observe(Breakpoints.HandsetPortrait)
|
||||
.subscribe(result => {
|
||||
if (result.matches) {
|
||||
this.dialogRef.updateSize('100vw', '80vh');
|
||||
} else {
|
||||
this.dialogRef.updateSize('80vw', '80vh');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
save = () => {
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
<div class='status-bar' style='display: flex'>
|
||||
<span style='padding-left: 10px; flex-grow: 1'>
|
||||
<ng-container *ngIf='vgUsername$ | async as vgUsername'>
|
||||
<span>{{
|
||||
vgUsername.length > 0 ? 'Logged in as: ' + vgUsername : ''
|
||||
<div class="status-bar" style="display: flex; justify-content: space-between">
|
||||
<div>
|
||||
<span
|
||||
*ngIf="!(handsetPortrait$ | async)?.matches"
|
||||
style="padding-left: 10px; flex-grow: 1">
|
||||
<ng-container *ngIf="vgUsername$ | async as vgUsername">
|
||||
<span>{{
|
||||
vgUsername.length > 0 ? 'Logged in as: ' + vgUsername : ''
|
||||
}}</span>
|
||||
</ng-container>
|
||||
</span>
|
||||
<span *ngIf='downloadSpeed$ | async as downloadSpeed'>{{
|
||||
(downloadSpeed.speed | downloadSpeed) + '/s'
|
||||
</ng-container>
|
||||
</span>
|
||||
</div>
|
||||
<div style="display: flex">
|
||||
<span *ngIf="downloadSpeed$ | async as downloadSpeed">{{
|
||||
(downloadSpeed.speed | downloadSpeed) + '/s'
|
||||
}}</span>
|
||||
<mat-divider [vertical]='true' style='height: 20px'></mat-divider>
|
||||
<ng-container *ngIf='queueState$ | async as queueState'>
|
||||
<span>Downloading: {{ queueState.running }}</span>
|
||||
<mat-divider [vertical]="true" style="height: 20px"></mat-divider>
|
||||
<span>Pending: {{ queueState.remaining }}</span>
|
||||
<mat-divider [vertical]="true" style="height: 20px"></mat-divider>
|
||||
</ng-container>
|
||||
<span *ngIf='errorCount$ | async as errorCount' style='padding-right: 10px'
|
||||
>Error: {{ errorCount.count }}</span
|
||||
>
|
||||
<ng-container *ngIf="queueState$ | async as queueState">
|
||||
<ng-container *ngIf="!(handsetPortrait$ | async)?.matches">
|
||||
<mat-divider [vertical]="true" style="height: 20px"></mat-divider>
|
||||
<span>Downloading: {{ queueState.running }}</span>
|
||||
<mat-divider [vertical]="true" style="height: 20px"></mat-divider>
|
||||
<span>Pending: {{ queueState.remaining }}</span>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="errorCount$ | async as errorCount">
|
||||
<ng-container *ngIf="!(handsetPortrait$ | async)?.matches">
|
||||
<mat-divider [vertical]="true" style="height: 20px"></mat-divider>
|
||||
<span style="padding-right: 10px">Error: {{ errorCount.count }}</span>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CommonModule, NgIf } from '@angular/common';
|
||||
import { ApplicationEndpointService } from '../services/application-endpoint.service';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { DownloadSpeedPipe } from '../pipes/download-speed.pipe';
|
||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||
|
||||
@Component({
|
||||
selector: 'app-status-bar',
|
||||
@@ -12,7 +13,13 @@ import { DownloadSpeedPipe } from '../pipes/download-speed.pipe';
|
||||
styleUrls: ['./status-bar.component.scss'],
|
||||
})
|
||||
export class StatusBarComponent {
|
||||
constructor(private applicationEndpoint: ApplicationEndpointService) {}
|
||||
handsetPortrait$ = this.breakpointObserver.observe(
|
||||
Breakpoints.HandsetPortrait
|
||||
);
|
||||
constructor(
|
||||
private applicationEndpoint: ApplicationEndpointService,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {}
|
||||
|
||||
queueState$ = this.applicationEndpoint.queueState$;
|
||||
downloadSpeed$ = this.applicationEndpoint.downloadSpeed$;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<h2 mat-dialog-title>Thread</h2>
|
||||
<mat-dialog-content
|
||||
class="mat-typography"
|
||||
style="min-width: 50vw; min-height: 50vh">
|
||||
<mat-dialog-content class="mat-typography">
|
||||
<ag-grid-angular
|
||||
style="width: 50vw; height: 50vh"
|
||||
style="width: 100%; height: 100%"
|
||||
class="ag-theme-alpine"
|
||||
#agGrid
|
||||
[gridOptions]="gridOptions">
|
||||
</ag-grid-angular>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button (click)="downloadSelected()" cdkFocusInitial>Download</button>
|
||||
<button mat-flat-button [mat-dialog-close]="true" cdkFocusInitial>
|
||||
Cancel
|
||||
</button>
|
||||
<button mat-flat-button color="primary" (click)="downloadSelected()">Download</button>
|
||||
</mat-dialog-actions>
|
||||
|
||||
@@ -2,11 +2,7 @@ import { Component, Inject, ViewChild } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { AgGridAngular, AgGridModule } from 'ag-grid-angular';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogModule,
|
||||
MatDialogRef,
|
||||
} from '@angular/material/dialog';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import {
|
||||
GridOptions,
|
||||
ITooltipParams,
|
||||
@@ -16,6 +12,8 @@ import { ApplicationEndpointService } from '../services/application-endpoint.ser
|
||||
import { GridReadyEvent } from 'ag-grid-community/dist/lib/events';
|
||||
import { PostItem } from '../domain/post-item.model';
|
||||
import { ValueGetterParams } from 'ag-grid-community/dist/lib/entities/colDef';
|
||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||
import { DialogRef } from '@angular/cdk/dialog';
|
||||
|
||||
export interface ThreadDialogData {
|
||||
threadId: number;
|
||||
@@ -35,31 +33,37 @@ export class ThreadSelectionComponent {
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: ThreadDialogData,
|
||||
private dialogRef: MatDialogRef<ThreadSelectionComponent>,
|
||||
private applicationEndpoint: ApplicationEndpointService
|
||||
private dialogRef: DialogRef<ThreadDialogData>,
|
||||
private applicationEndpoint: ApplicationEndpointService,
|
||||
breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
this.gridOptions = <GridOptions>{
|
||||
columnDefs: [
|
||||
{
|
||||
colId: 'number',
|
||||
headerName: 'Number',
|
||||
field: 'number',
|
||||
tooltipField: 'number',
|
||||
checkboxSelection: true,
|
||||
headerCheckboxSelection: true,
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
colId: 'title',
|
||||
headerName: 'Title',
|
||||
field: 'title',
|
||||
tooltipField: 'title',
|
||||
flex: 2,
|
||||
},
|
||||
{
|
||||
colId: 'url',
|
||||
headerName: 'URL',
|
||||
field: 'url',
|
||||
tooltipField: 'url',
|
||||
flex: 1,
|
||||
flex: 2,
|
||||
},
|
||||
{
|
||||
colId: 'hosts',
|
||||
headerName: 'Hosts',
|
||||
field: 'hosts',
|
||||
tooltipValueGetter: (params: ITooltipParams<PostItem>) => {
|
||||
@@ -72,6 +76,7 @@ export class ThreadSelectionComponent {
|
||||
.map(v => `${v.first} (${v.second})`)
|
||||
.join(', ');
|
||||
},
|
||||
flex: 1,
|
||||
},
|
||||
],
|
||||
defaultColDef: {
|
||||
@@ -81,6 +86,17 @@ export class ThreadSelectionComponent {
|
||||
rowSelection: 'multiple',
|
||||
getRowId: row => row.data['postId'].toString(),
|
||||
onGridReady: (event: GridReadyEvent<PostItem>) => {
|
||||
breakpointObserver
|
||||
.observe(Breakpoints.HandsetPortrait)
|
||||
.subscribe(result => {
|
||||
this.agGrid.columnApi.setColumnsVisible(['url'], !result.matches);
|
||||
if (result.matches) {
|
||||
this.dialogRef.updateSize('100vw', '80vh');
|
||||
} else {
|
||||
this.dialogRef.updateSize('80vw', '80vh');
|
||||
}
|
||||
this.dialogRef.updatePosition();
|
||||
});
|
||||
this.applicationEndpoint
|
||||
.getThreadPosts(this.data.threadId)
|
||||
.subscribe(result => {
|
||||
|
||||
@@ -75,18 +75,19 @@ export class ThreadTableComponent implements OnDestroy {
|
||||
headerName: 'Title',
|
||||
field: 'title',
|
||||
tooltipField: 'title',
|
||||
flex: 1,
|
||||
flex: 2,
|
||||
},
|
||||
{
|
||||
headerName: 'Url',
|
||||
field: 'link',
|
||||
tooltipField: 'link',
|
||||
flex: 1,
|
||||
flex: 2,
|
||||
},
|
||||
{
|
||||
headerName: 'Count',
|
||||
field: 'total',
|
||||
tooltipField: 'total',
|
||||
flex: 1,
|
||||
},
|
||||
],
|
||||
defaultColDef: {
|
||||
@@ -138,7 +139,13 @@ export class ThreadTableComponent implements OnDestroy {
|
||||
return;
|
||||
}
|
||||
const data: ThreadDialogData = { threadId: event.data.threadId };
|
||||
this.dialog.open(ThreadSelectionComponent, { data });
|
||||
this.dialog.open(ThreadSelectionComponent, {
|
||||
data,
|
||||
maxWidth: '100vw',
|
||||
maxHeight: '100vh',
|
||||
width: '80vw',
|
||||
height: '80vh',
|
||||
});
|
||||
};
|
||||
|
||||
ref.instance.onThreadDelete = () => {
|
||||
@@ -177,7 +184,13 @@ export class ThreadTableComponent implements OnDestroy {
|
||||
return;
|
||||
}
|
||||
const data: ThreadDialogData = { threadId: event.data.threadId };
|
||||
this.dialog.open(ThreadSelectionComponent, { data });
|
||||
this.dialog.open(ThreadSelectionComponent, {
|
||||
data,
|
||||
maxWidth: '100vw',
|
||||
maxHeight: '100vh',
|
||||
width: '80vw',
|
||||
height: '80vh',
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,27 +26,31 @@
|
||||
<mat-divider
|
||||
[vertical]="true"
|
||||
style="height: 20px; flex-grow: 0"></mat-divider>
|
||||
<button
|
||||
[disabled]="disableSelected()"
|
||||
(click)="startSelectedClick()"
|
||||
mat-icon-button
|
||||
matTooltip="Start selected">
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
[disabled]="disableSelected()"
|
||||
(click)="stopSelectedClick()"
|
||||
mat-icon-button
|
||||
matTooltip="Stop selected">
|
||||
<mat-icon>pause</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
[disabled]="disableSelected()"
|
||||
(click)="removeSelectedClick()"
|
||||
mat-icon-button
|
||||
matTooltip="Remove selected">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
<ng-container *ngIf="handsetPortrait$ | async as handsetPortrait">
|
||||
@if (!handsetPortrait.matches) {
|
||||
<button
|
||||
[disabled]="disableSelected()"
|
||||
(click)="startSelectedClick()"
|
||||
mat-icon-button
|
||||
matTooltip="Start selected">
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
[disabled]="disableSelected()"
|
||||
(click)="stopSelectedClick()"
|
||||
mat-icon-button
|
||||
matTooltip="Stop selected">
|
||||
<mat-icon>pause</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
[disabled]="disableSelected()"
|
||||
(click)="removeSelectedClick()"
|
||||
mat-icon-button
|
||||
matTooltip="Remove selected">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</ng-container>
|
||||
<mat-divider
|
||||
[vertical]="true"
|
||||
style="height: 20px; flex-grow: 0"></mat-divider>
|
||||
@@ -78,9 +82,9 @@
|
||||
<button mat-icon-button matTooltip="Settings" (click)="settingsClick()">
|
||||
<mat-icon>settings</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button matTooltip="About">
|
||||
<mat-icon>info</mat-icon>
|
||||
</button>
|
||||
<!-- <button mat-icon-button matTooltip="About">-->
|
||||
<!-- <mat-icon>info</mat-icon>-->
|
||||
<!-- </button>-->
|
||||
</div>
|
||||
</div>
|
||||
</mat-toolbar-row>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { ScanComponent } from '../scan/scan.component';
|
||||
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||
|
||||
@Component({
|
||||
selector: 'app-toolbar',
|
||||
@@ -69,11 +70,21 @@ export class ToolbarComponent {
|
||||
@Input({ required: true })
|
||||
disableSelected!: Signal<boolean>;
|
||||
|
||||
constructor(public dialog: MatDialog) {}
|
||||
handsetPortrait$ = this.breakpointObserver.observe(
|
||||
Breakpoints.HandsetPortrait
|
||||
);
|
||||
|
||||
constructor(
|
||||
public dialog: MatDialog,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {}
|
||||
|
||||
openScanDialog() {
|
||||
this.dialog
|
||||
.open<ScanComponent, never, string>(ScanComponent)
|
||||
.open<ScanComponent, never, string>(ScanComponent, {
|
||||
maxWidth: '100vw',
|
||||
maxHeight: '100vh',
|
||||
})
|
||||
.afterClosed()
|
||||
.subscribe(v => {
|
||||
if (v) {
|
||||
|
||||
@@ -17,7 +17,7 @@ body {
|
||||
}
|
||||
|
||||
#tab-group {
|
||||
height: calc(100% - 64px);
|
||||
height: calc(100% - 84px);
|
||||
}
|
||||
|
||||
.ag-theme-alpine {
|
||||
@@ -135,12 +135,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
white-space: nowrap;
|
||||
padding: 2px 0;
|
||||
z-index: 9999;
|
||||
.mat-mdc-dialog-content {
|
||||
max-height: 100vh !important;
|
||||
}
|
||||
|
||||
@@ -17,23 +17,14 @@ class DataBroadcast(
|
||||
|
||||
@PostConstruct
|
||||
private fun run() {
|
||||
eventBus.events.ofType(PostEvent::class.java).buffer(Duration.ofMillis(125)).subscribe { events ->
|
||||
|
||||
events.map { it.delete }.flatten().also {
|
||||
if (it.isNotEmpty()) {
|
||||
template.convertAndSend("/topic/posts/deleted", it)
|
||||
}
|
||||
}
|
||||
|
||||
events.map { it.add }.flatten().also {
|
||||
if (it.isNotEmpty()) {
|
||||
template.convertAndSend("/topic/posts/new", it)
|
||||
}
|
||||
}
|
||||
|
||||
events.map { it.update }.flatten().reversed().distinct().also {
|
||||
template.convertAndSend("/topic/posts/updated", it)
|
||||
}
|
||||
eventBus.events.ofType(PostCreateEvent::class.java).subscribe { events ->
|
||||
template.convertAndSend("/topic/posts/new", events.posts)
|
||||
}
|
||||
eventBus.events.ofType(PostUpdateEvent::class.java).subscribe { events ->
|
||||
template.convertAndSend("/topic/posts/updated", events.posts)
|
||||
}
|
||||
eventBus.events.ofType(PostDeleteEvent::class.java).subscribe { events ->
|
||||
template.convertAndSend("/topic/posts/deleted", events.postIds)
|
||||
}
|
||||
|
||||
eventBus.events.ofType(QueueStateEvent::class.java).subscribe {
|
||||
@@ -56,41 +47,35 @@ class DataBroadcast(
|
||||
template.convertAndSend("/topic/loading", it.loading)
|
||||
}
|
||||
|
||||
eventBus.events.ofType(ImageEvent::class.java).buffer(Duration.ofMillis(125)).subscribe { events ->
|
||||
events.map { it.images }.flatten().reversed().distinct().groupBy { it.postId }
|
||||
.forEach { template.convertAndSend("/topic/images/${it.key}", it.value) }
|
||||
eventBus.events.ofType(ImageEvent::class.java).buffer(Duration.ofMillis(500)).subscribe { events ->
|
||||
events.reversed().map { it.images }.flatten().distinct().groupBy { it.postId }
|
||||
.forEach {
|
||||
template.convertAndSend("/topic/images/${it.key}", it.value)
|
||||
}
|
||||
}
|
||||
|
||||
eventBus.events.ofType(ThreadCreateEvent::class.java).map { it.thread }.distinct()
|
||||
.buffer(Duration.ofMillis(125)).subscribe {
|
||||
template.convertAndSend("/topic/threads", it)
|
||||
eventBus.events.ofType(ThreadCreateEvent::class.java).map { listOf(it.thread) }
|
||||
.subscribe { events ->
|
||||
template.convertAndSend("/topic/threads", events)
|
||||
}
|
||||
|
||||
eventBus.events.ofType(ThreadDeleteEvent::class.java).map { it.threadId }.distinct()
|
||||
.buffer(Duration.ofMillis(125)).subscribe {
|
||||
eventBus.events.ofType(ThreadDeleteEvent::class.java).map { listOf(it.threadId) }
|
||||
.subscribe {
|
||||
template.convertAndSend("/topic/threads/deleted", it)
|
||||
}
|
||||
|
||||
eventBus.events.ofType(ThreadClearEvent::class.java).subscribe {
|
||||
template.convertAndSend("/topic/threads/deletedAll", listOf(true))
|
||||
eventBus.events.ofType(ThreadClearEvent::class.java).map { listOf(true) }.subscribe {
|
||||
template.convertAndSend("/topic/threads/deletedAll", it)
|
||||
}
|
||||
|
||||
eventBus.events.ofType(LogCreateEvent::class.java).map { it.logEntry }.buffer(Duration.ofMillis(125))
|
||||
eventBus.events.ofType(LogCreateEvent::class.java).map { it.logEntry }
|
||||
.subscribe { logCreateEvent ->
|
||||
logCreateEvent.distinct().also {
|
||||
if (it.isNotEmpty()) {
|
||||
template.convertAndSend("/topic/logs/new", it)
|
||||
}
|
||||
}
|
||||
template.convertAndSend("/topic/logs/new", logCreateEvent)
|
||||
}
|
||||
|
||||
eventBus.events.ofType(LogUpdateEvent::class.java).map { it.logEntry }.buffer(Duration.ofMillis(125))
|
||||
eventBus.events.ofType(LogUpdateEvent::class.java).map { it.logEntry }
|
||||
.subscribe { logUpdateEvent ->
|
||||
logUpdateEvent.distinct().also {
|
||||
if (it.isNotEmpty()) {
|
||||
template.convertAndSend("/topic/logs/updated", it)
|
||||
}
|
||||
}
|
||||
template.convertAndSend("/topic/logs/updated", logUpdateEvent)
|
||||
}
|
||||
|
||||
eventBus.events.ofType(LogDeleteEvent::class.java).subscribe {
|
||||
|
||||
Reference in New Issue
Block a user