+
+
-
+
@@ -82,12 +75,12 @@
Azimuth
{{ tppaAzimuthError }}
- {{ tppaAzimuthErrorDirection }}
+ {{ tppaAzimuthErrorDirection }}
Altitude
{{ tppaAltitudeError }}
- {{ tppaAltitudeErrorDirection }}
+ {{ tppaAltitudeErrorDirection }}
Total
@@ -96,14 +89,15 @@
+ @if (pausingOrPaused) {
+
+ } @else if(!running) {
-
-
+ }
+
@@ -145,6 +139,17 @@
styleClass="ml-4" pTooltip="View image" tooltipPosition="bottom" size="small" />
+
+
+ 1. Locate a star in the Meridian and close to declination 0.
+ 2. Start DARV and wait for routine to complete.
+ 3. If you see V shaped track, adjust the Azimuth and repeat the step 2 till you get a line.
+ 4. Locate a star in the Eastern or Western horizon and close to declination 0.
+ 5. Start DARV and wait for routine to complete.
+ 6. If you see V shaped track, adjust the Altitude and repeat the step 5 till you get a line.
+ 7. Increase the drift time and repeat the step 1 to get a more accurate alignment.
+
+
diff --git a/desktop/src/app/alignment/alignment.component.ts b/desktop/src/app/alignment/alignment.component.ts
index d11200c64..922073481 100644
--- a/desktop/src/app/alignment/alignment.component.ts
+++ b/desktop/src/app/alignment/alignment.component.ts
@@ -1,14 +1,15 @@
-import { AfterViewInit, Component, HostListener, NgZone, OnDestroy } from '@angular/core'
+import { AfterViewInit, Component, HostListener, NgZone, OnDestroy, ViewChild } from '@angular/core'
+import { CameraExposureComponent } from '../../shared/components/camera-exposure/camera-exposure.component'
import { ApiService } from '../../shared/services/api.service'
import { BrowserWindowService } from '../../shared/services/browser-window.service'
import { ElectronService } from '../../shared/services/electron.service'
import { PreferenceService } from '../../shared/services/preference.service'
import { AlignmentMethod, AlignmentPreference, DARVStart, DARVState, Hemisphere, TPPAStart, TPPAState } from '../../shared/types/alignment.types'
import { Angle } from '../../shared/types/atlas.types'
-import { Camera, EMPTY_CAMERA, EMPTY_CAMERA_START_CAPTURE, ExposureTimeUnit } from '../../shared/types/camera.types'
+import { Camera, EMPTY_CAMERA, EMPTY_CAMERA_START_CAPTURE, ExposureTimeUnit, updateCameraStartCaptureFromCamera } from '../../shared/types/camera.types'
import { EMPTY_GUIDE_OUTPUT, GuideDirection, GuideOutput } from '../../shared/types/guider.types'
import { EMPTY_MOUNT, Mount } from '../../shared/types/mount.types'
-import { DEFAULT_SOLVER_TYPES, EMPTY_PLATE_SOLVER_OPTIONS } from '../../shared/types/settings.types'
+import { DEFAULT_SOLVER_TYPES, EMPTY_PLATE_SOLVER_PREFERENCE } from '../../shared/types/settings.types'
import { deviceComparator } from '../../shared/utils/comparators'
import { AppComponent } from '../app.component'
import { CameraComponent } from '../camera/camera.component'
@@ -32,31 +33,29 @@ export class AlignmentComponent implements AfterViewInit, OnDestroy {
tab = 0
running = false
+ pausingOrPaused = false
alignmentMethod?: AlignmentMethod
status: DARVState | TPPAState = 'IDLE'
- elapsedTime = 0
- remainingTime = 0
- progress = 0
- private id = ''
readonly tppaRequest: TPPAStart = {
capture: structuredClone(EMPTY_CAMERA_START_CAPTURE),
- plateSolver: structuredClone(EMPTY_PLATE_SOLVER_OPTIONS),
+ plateSolver: structuredClone(EMPTY_PLATE_SOLVER_PREFERENCE),
startFromCurrentPosition: true,
- eastDirection: true,
+ stepDirection: 'EAST',
compensateRefraction: true,
stopTrackingWhenDone: true,
- stepDistance: 10,
+ stepDuration: 5,
}
readonly plateSolverTypes = Array.from(DEFAULT_SOLVER_TYPES)
+ tppaFailed = false
+ tppaRightAscension: Angle = `00h00m00s`
+ tppaDeclination: Angle = `00°00'00"`
tppaAzimuthError: Angle = `00°00'00"`
tppaAzimuthErrorDirection = ''
tppaAltitudeError: Angle = `00°00'00"`
tppaAltitudeErrorDirection = ''
tppaTotalError: Angle = `00°00'00"`
- tppaRightAscension: Angle = '00h00m00s'
- tppaDeclination: Angle = `00°00'00"`
readonly darvRequest: DARVStart = {
capture: structuredClone(EMPTY_CAMERA_START_CAPTURE),
@@ -67,10 +66,14 @@ export class AlignmentComponent implements AfterViewInit, OnDestroy {
}
readonly driftExposureUnit = ExposureTimeUnit.SECOND
- readonly darvHemispheres: Hemisphere[] = ['NORTHERN', 'SOUTHERN']
darvHemisphere: Hemisphere = 'NORTHERN'
darvDirection?: GuideDirection
+ @ViewChild('cameraExposure')
+ private readonly cameraExposure!: CameraExposureComponent
+
+ private autoResizeTimeout?: any
+
constructor(
app: AppComponent,
private api: ApiService,
@@ -172,50 +175,52 @@ export class AlignmentComponent implements AfterViewInit, OnDestroy {
})
electron.on('TPPA.ELAPSED', event => {
- if (event.id === this.id) {
+ if (event.camera.id === this.camera?.id) {
ngZone.run(() => {
- if (this.status !== 'PAUSING' || event.state === 'PAUSED') {
- this.status = event.state
- }
-
+ this.status = event.state
this.running = event.state !== 'FINISHED'
- this.elapsedTime = event.elapsedTime
+ this.pausingOrPaused = event.state === 'PAUSING' || event.state === 'PAUSED'
if (event.state === 'COMPUTED') {
+ this.tppaFailed = false
+ this.tppaRightAscension = event.rightAscension
+ this.tppaDeclination = event.declination
this.tppaAzimuthError = event.azimuthError
this.tppaAltitudeError = event.altitudeError
this.tppaAzimuthErrorDirection = event.azimuthErrorDirection
this.tppaAltitudeErrorDirection = event.altitudeErrorDirection
this.tppaTotalError = event.totalError
- } else if (event.state === 'SOLVED' || event.state === 'SLEWING') {
+ clearTimeout(this.autoResizeTimeout)
+ this.autoResizeTimeout = electron.autoResizeWindow()
+ } else if (event.state === 'FINISHED') {
+ this.cameraExposure.reset()
+ electron.autoResizeWindow()
+ } else if (event.state === 'SOLVED' || event.state === 'SLEWED') {
+ this.tppaFailed = false
this.tppaRightAscension = event.rightAscension
this.tppaDeclination = event.declination
+ } else if (event.state === 'FAILED') {
+ this.tppaFailed = true
}
- if (!this.running) {
- this.alignmentMethod = undefined
+ if (event.capture && event.capture.state !== 'CAPTURE_FINISHED') {
+ this.cameraExposure.handleCameraCaptureEvent(event.capture, true)
}
})
}
})
electron.on('DARV.ELAPSED', event => {
- if (event.id === this.id) {
+ if (event.camera.id === this.camera?.id) {
ngZone.run(() => {
this.status = event.state
- this.remainingTime = event.remainingTime
- this.progress = event.progress
- this.running = event.remainingTime > 0
+ this.running = this.cameraExposure.handleCameraCaptureEvent(event.capture)
if (event.state === 'FORWARD' || event.state === 'BACKWARD') {
this.darvDirection = event.direction
} else {
this.darvDirection = undefined
}
-
- if (!this.running) {
- this.alignmentMethod = undefined
- }
})
}
})
@@ -236,29 +241,30 @@ export class AlignmentComponent implements AfterViewInit, OnDestroy {
}
async cameraChanged() {
- if (this.camera.name) {
- const camera = await this.api.camera(this.camera.name)
+ if (this.camera.id) {
+ const camera = await this.api.camera(this.camera.id)
Object.assign(this.camera, camera)
this.loadPreference()
}
}
async mountChanged() {
- if (this.mount.name) {
- const mount = await this.api.mount(this.mount.name)
+ if (this.mount.id) {
+ const mount = await this.api.mount(this.mount.id)
Object.assign(this.mount, mount)
+ this.tppaRequest.stepSpeed = mount.slewRate?.name
}
}
async guideOutputChanged() {
- if (this.guideOutput.name) {
- const guideOutput = await this.api.guideOutput(this.guideOutput.name)
+ if (this.guideOutput.id) {
+ const guideOutput = await this.api.guideOutput(this.guideOutput.id)
Object.assign(this.guideOutput, guideOutput)
}
}
mountConnect() {
- if (this.mount.name) {
+ if (this.mount.id) {
if (this.mount.connected) {
this.api.mountDisconnect(this.mount)
} else {
@@ -268,7 +274,7 @@ export class AlignmentComponent implements AfterViewInit, OnDestroy {
}
guideOutputConnect() {
- if (this.guideOutput.name) {
+ if (this.guideOutput.id) {
if (this.guideOutput.connected) {
this.api.guideOutputDisconnect(this.guideOutput)
} else {
@@ -278,7 +284,7 @@ export class AlignmentComponent implements AfterViewInit, OnDestroy {
}
async showCameraDialog() {
- if (this.camera.name) {
+ if (this.camera.id) {
if (this.tab === 0) {
if (await CameraComponent.showAsDialog(this.browserWindow, 'TPPA', this.camera, this.tppaRequest.capture)) {
this.savePreference()
@@ -295,7 +301,7 @@ export class AlignmentComponent implements AfterViewInit, OnDestroy {
}
plateSolverChanged() {
- this.tppaRequest.plateSolver = this.preference.plateSolverOptions(this.tppaRequest.plateSolver.type).get()
+ this.tppaRequest.plateSolver = this.preference.plateSolverPreference(this.tppaRequest.plateSolver.type).get()
this.savePreference()
}
@@ -316,30 +322,29 @@ export class AlignmentComponent implements AfterViewInit, OnDestroy {
this.darvRequest.capture.exposureTime = this.darvRequest.exposureTime * 1000000
this.darvRequest.capture.exposureDelay = this.darvRequest.initialPause
await this.openCameraImage()
- this.id = await this.api.darvStart(this.camera, this.guideOutput, this.darvRequest)
+ await this.api.darvStart(this.camera, this.guideOutput, this.darvRequest)
}
darvStop() {
- this.api.darvStop(this.id)
+ this.api.darvStop(this.camera)
}
async tppaStart() {
this.alignmentMethod = 'TPPA'
await this.openCameraImage()
- this.id = await this.api.tppaStart(this.camera, this.mount, this.tppaRequest)
+ await this.api.tppaStart(this.camera, this.mount, this.tppaRequest)
}
tppaPause() {
- this.status = 'PAUSING'
- this.api.tppaPause(this.id)
+ return this.api.tppaPause(this.camera)
}
tppaUnpause() {
- this.api.tppaUnpause(this.id)
+ return this.api.tppaUnpause(this.camera)
}
tppaStop() {
- this.api.tppaStop(this.id)
+ return this.api.tppaStop(this.camera)
}
openCameraImage() {
@@ -350,18 +355,24 @@ export class AlignmentComponent implements AfterViewInit, OnDestroy {
const preference = this.preference.alignmentPreference.get()
this.tppaRequest.startFromCurrentPosition = preference.tppaStartFromCurrentPosition
- this.tppaRequest.eastDirection = preference.tppaEastDirection
+ this.tppaRequest.stepDirection = preference.tppaStepDirection
this.tppaRequest.compensateRefraction = preference.tppaCompensateRefraction
this.tppaRequest.stopTrackingWhenDone = preference.tppaStopTrackingWhenDone
- this.tppaRequest.stepDistance = preference.tppaStepDistance
+ this.tppaRequest.stepDuration = preference.tppaStepDuration
this.tppaRequest.plateSolver.type = preference.tppaPlateSolverType
this.darvRequest.initialPause = preference.darvInitialPause
this.darvRequest.exposureTime = preference.darvExposureTime
this.darvHemisphere = preference.darvHemisphere
- if (this.camera.name) {
- Object.assign(this.tppaRequest.capture, this.preference.cameraStartCaptureForTPPA(this.camera).get(this.tppaRequest.capture))
- Object.assign(this.darvRequest.capture, this.preference.cameraStartCaptureForDARV(this.camera).get(this.darvRequest.capture))
+ if (this.camera.id) {
+ const cameraPreference = this.preference.cameraPreference(this.camera).get()
+ Object.assign(this.tppaRequest.capture, this.preference.cameraStartCaptureForTPPA(this.camera).get(cameraPreference))
+ Object.assign(this.darvRequest.capture, this.preference.cameraStartCaptureForDARV(this.camera).get(cameraPreference))
+
+ if (this.camera.connected) {
+ updateCameraStartCaptureFromCamera(this.tppaRequest.capture, this.camera)
+ updateCameraStartCaptureFromCamera(this.darvRequest.capture, this.camera)
+ }
}
this.plateSolverChanged()
@@ -376,10 +387,10 @@ export class AlignmentComponent implements AfterViewInit, OnDestroy {
const preference: AlignmentPreference = {
tppaStartFromCurrentPosition: this.tppaRequest.startFromCurrentPosition,
- tppaEastDirection: this.tppaRequest.eastDirection,
+ tppaStepDirection: this.tppaRequest.stepDirection,
tppaCompensateRefraction: this.tppaRequest.compensateRefraction,
tppaStopTrackingWhenDone: this.tppaRequest.stopTrackingWhenDone,
- tppaStepDistance: this.tppaRequest.stepDistance,
+ tppaStepDuration: this.tppaRequest.stepDuration,
tppaPlateSolverType: this.tppaRequest.plateSolver.type,
darvInitialPause: this.darvRequest.initialPause,
darvExposureTime: this.darvRequest.exposureTime,
diff --git a/desktop/src/app/app-routing.module.ts b/desktop/src/app/app-routing.module.ts
index 989fc38bb..bdd1b6a47 100644
--- a/desktop/src/app/app-routing.module.ts
+++ b/desktop/src/app/app-routing.module.ts
@@ -16,6 +16,7 @@ import { HomeComponent } from './home/home.component'
import { ImageComponent } from './image/image.component'
import { INDIComponent } from './indi/indi.component'
import { MountComponent } from './mount/mount.component'
+import { RotatorComponent } from './rotator/rotator.component'
import { SequencerComponent } from './sequencer/sequencer.component'
import { SettingsComponent } from './settings/settings.component'
@@ -45,6 +46,10 @@ const routes: Routes = [
path: 'mount',
component: MountComponent,
},
+ {
+ path: 'rotator',
+ component: RotatorComponent,
+ },
{
path: 'guider',
component: GuiderComponent,
diff --git a/desktop/src/app/app.component.html b/desktop/src/app/app.component.html
index fef76d594..d6c47560e 100644
--- a/desktop/src/app/app.component.html
+++ b/desktop/src/app/app.component.html
@@ -1,4 +1,4 @@
-
{{ title }}
diff --git a/desktop/src/app/app.component.scss b/desktop/src/app/app.component.scss
index 011a140d7..67a9efabd 100644
--- a/desktop/src/app/app.component.scss
+++ b/desktop/src/app/app.component.scss
@@ -24,4 +24,9 @@
width: 16px;
}
}
+
+ #main {
+ margin-left: 2px;
+ margin-right: 2px;
+ }
}
\ No newline at end of file
diff --git a/desktop/src/app/app.component.ts b/desktop/src/app/app.component.ts
index 0ece200c6..a9058300f 100644
--- a/desktop/src/app/app.component.ts
+++ b/desktop/src/app/app.component.ts
@@ -22,6 +22,7 @@ export class AppComponent implements AfterViewInit {
subTitle? = ''
backgroundColor = '#212121'
topMenu: ExtendedMenuItem[] = []
+ showTopBar = true
get title() {
return this.windowTitle.getTitle()
diff --git a/desktop/src/app/app.module.ts b/desktop/src/app/app.module.ts
index 423d76b89..74dad403c 100644
--- a/desktop/src/app/app.module.ts
+++ b/desktop/src/app/app.module.ts
@@ -5,6 +5,7 @@ import { LOCALE_ID, NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { BrowserModule } from '@angular/platform-browser'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
+import { AccordionModule } from 'primeng/accordion'
import { ConfirmationService, MessageService } from 'primeng/api'
import { BadgeModule } from 'primeng/badge'
import { ButtonModule } from 'primeng/button'
@@ -39,6 +40,7 @@ import { TagModule } from 'primeng/tag'
import { TieredMenuModule } from 'primeng/tieredmenu'
import { ToastModule } from 'primeng/toast'
import { TooltipModule } from 'primeng/tooltip'
+import { TreeModule } from 'primeng/tree'
import { CameraExposureComponent } from '../shared/components/camera-exposure/camera-exposure.component'
import { DeviceListButtonComponent } from '../shared/components/device-list-button/device-list-button.component'
import { DeviceListMenuComponent } from '../shared/components/device-list-menu/device-list-menu.component'
@@ -47,6 +49,7 @@ import { HistogramComponent } from '../shared/components/histogram/histogram.com
import { MapComponent } from '../shared/components/map/map.component'
import { MenuItemComponent } from '../shared/components/menu-item/menu-item.component'
import { MoonComponent } from '../shared/components/moon/moon.component'
+import { SlideMenuComponent } from '../shared/components/slide-menu/slide-menu.component'
import { LocationDialog } from '../shared/dialogs/location/location.dialog'
import { NoDropdownDirective } from '../shared/directives/no-dropdown.directive'
import { StopPropagationDirective } from '../shared/directives/stop-propagation.directive'
@@ -76,6 +79,7 @@ import { ImageComponent } from './image/image.component'
import { INDIComponent } from './indi/indi.component'
import { INDIPropertyComponent } from './indi/property/indi-property.component'
import { MountComponent } from './mount/mount.component'
+import { RotatorComponent } from './rotator/rotator.component'
import { SequencerComponent } from './sequencer/sequencer.component'
import { SettingsComponent } from './settings/settings.component'
@@ -108,18 +112,21 @@ import { SettingsComponent } from './settings/settings.component'
INDIComponent,
INDIPropertyComponent,
LocationDialog,
+ MapComponent,
MenuItemComponent,
MoonComponent,
MountComponent,
NoDropdownDirective,
- MapComponent,
+ RotatorComponent,
SequencerComponent,
SettingsComponent,
SkyObjectPipe,
+ SlideMenuComponent,
StopPropagationDirective,
WinPipe,
],
imports: [
+ AccordionModule,
AppRoutingModule,
BadgeModule,
BrowserAnimationsModule,
@@ -160,6 +167,7 @@ import { SettingsComponent } from './settings/settings.component'
TieredMenuModule,
ToastModule,
TooltipModule,
+ TreeModule,
],
providers: [
AnglePipe,
diff --git a/desktop/src/app/atlas/atlas.component.html b/desktop/src/app/atlas/atlas.component.html
index 7b16f9fe8..b8ab9cc80 100644
--- a/desktop/src/app/atlas/atlas.component.html
+++ b/desktop/src/app/atlas/atlas.component.html
@@ -441,5 +441,5 @@
-
+
\ No newline at end of file
diff --git a/desktop/src/app/atlas/atlas.component.scss b/desktop/src/app/atlas/atlas.component.scss
index 5f6c0f61e..99334642c 100644
--- a/desktop/src/app/atlas/atlas.component.scss
+++ b/desktop/src/app/atlas/atlas.component.scss
@@ -5,9 +5,6 @@
::ng-deep {
.p-tabview {
- padding-left: 0.21rem;
- padding-right: 0.21rem;
-
p-table.planet .p-datatable-wrapper {
height: 229px;
}
diff --git a/desktop/src/app/atlas/atlas.component.ts b/desktop/src/app/atlas/atlas.component.ts
index b6b430f46..4e95d9c85 100644
--- a/desktop/src/app/atlas/atlas.component.ts
+++ b/desktop/src/app/atlas/atlas.component.ts
@@ -3,12 +3,12 @@ import { ActivatedRoute } from '@angular/router'
import { Chart, ChartData, ChartOptions } from 'chart.js'
import zoomPlugin from 'chartjs-plugin-zoom'
import moment from 'moment'
-import { MenuItem } from 'primeng/api'
import { UIChart } from 'primeng/chart'
import { ListboxChangeEvent } from 'primeng/listbox'
import { OverlayPanel } from 'primeng/overlaypanel'
import { Subscription, timer } from 'rxjs'
import { DeviceListMenuComponent } from '../../shared/components/device-list-menu/device-list-menu.component'
+import { ExtendedMenuItem } from '../../shared/components/menu-item/menu-item.component'
import { ONE_DECIMAL_PLACE_FORMATTER, TWO_DIGITS_FORMATTER } from '../../shared/constants'
import { SkyObjectPipe } from '../../shared/pipes/skyObject.pipe'
import { ApiService } from '../../shared/services/api.service'
@@ -152,7 +152,7 @@ export class AtlasComponent implements OnInit, AfterContentInit, AfterViewInit,
readonly satelliteSearchGroup = new Map()
name? = 'Sun'
- tags: { title: string, severity: string }[] = []
+ tags: { title: string, severity: 'success' | 'info' | 'warning' | 'danger' }[] = []
@ViewChild('imageOfSun')
private readonly imageOfSun!: ElementRef
@@ -406,7 +406,7 @@ export class AtlasComponent implements OnInit, AfterContentInit, AfterViewInit,
'ONEWEB', 'SCIENCE', 'STARLINK', 'STATIONS', 'VISUAL'
]
- readonly ephemerisModel: MenuItem[] = [
+ readonly ephemerisModel: ExtendedMenuItem[] = [
{
icon: 'mdi mdi-magnify',
label: 'Find sky objects around this object',
@@ -519,7 +519,7 @@ export class AtlasComponent implements OnInit, AfterContentInit, AfterViewInit,
if (this.tab === SkyAtlasTab.SKY_OBJECT) {
this.skyObjectFilter.rightAscension = data.filter?.rightAscension || this.skyObjectFilter.rightAscension
this.skyObjectFilter.declination = data.filter?.declination || this.skyObjectFilter.declination
- if (data.filter?.radius) this.skyObjectFilter.radius = data.filter?.radius || this.skyObjectFilter.radius
+ this.skyObjectFilter.radius = data.filter?.radius || this.skyObjectFilter.radius || 4.0
this.skyObjectFilter.constellation = data.filter?.constellation || this.skyObjectFilter.constellation
this.skyObjectFilter.magnitude = data.filter?.magnitude || this.skyObjectFilter.magnitude
this.skyObjectFilter.type = data.filter?.type || this.skyObjectFilter.type
@@ -895,7 +895,7 @@ export class AtlasComponent implements OnInit, AfterContentInit, AfterViewInit,
} else {
const mount = await this.deviceMenu.show(mounts)
- if (mount && mount.connected) {
+ if (mount && mount !== 'NONE' && mount.connected) {
action(mount)
return true
}
diff --git a/desktop/src/app/calibration/calibration.component.html b/desktop/src/app/calibration/calibration.component.html
index 7b4ebf8ee..acfded5e3 100644
--- a/desktop/src/app/calibration/calibration.component.html
+++ b/desktop/src/app/calibration/calibration.component.html
@@ -1,98 +1,64 @@
-
-
Groups
+
-
-
-
- Type |
- # |
- Filter |
- Duration |
- Size |
- Bin |
- T. (°C) |
- Gain |
-
-
-
-
- {{ item.key.type }} |
- {{ item.frames.length }} |
- {{ item.key.filter ?? '' }} |
- {{ item.key.exposureTime | exposureTime }} |
- {{ item.key.width }}x{{ item.key.height }} |
- {{ item.key.binX }}x{{ item.key.binY }} |
- {{ item.key.temperature }} |
- {{ item.key.gain }} |
-
+
+
+
+ @if (node.data.type === 'NAME') {
+
{{ node.label }}
+ } @else if (node.data.type === 'GROUP') {
+
+ } @else if (node.data.type === 'FRAME') {
+
+
+
{{ node.data.data.path }}
+
+ }
+
+ @if (node.data.type === 'NAME') {
+
+
+
+ }
+
+
+
-
-
-
-
-
-
-
-
-
- |
- Type |
- Filter |
- Duration |
- Size |
- Bin |
- T. (°C) |
- Gain |
-
-
-
-
-
-
- |
- {{ item.type }} |
- {{ item.filter ?? '' }} |
- {{ item.exposureTime | exposureTime }} |
- {{ item.width }}x{{ item.height }} |
- {{ item.binX }}x{{ item.binY }} |
- {{ item.temperature }} |
- {{ item.gain }} |
-
-
-
+
+
+
+
+
+
-
\ No newline at end of file
+
+
+
+
\ No newline at end of file
diff --git a/desktop/src/app/calibration/calibration.component.scss b/desktop/src/app/calibration/calibration.component.scss
index e69de29bb..83a651dd7 100644
--- a/desktop/src/app/calibration/calibration.component.scss
+++ b/desktop/src/app/calibration/calibration.component.scss
@@ -0,0 +1,33 @@
+:host {
+ ::ng-deep {
+ .p-treenode-label {
+ width: 100%;
+ }
+
+ .p-tree-wrapper {
+ max-height: 288px;
+ }
+
+ .p-tree {
+ .p-tree-container {
+ padding-right: 4px;
+
+ .p-treenode {
+ padding: 0;
+
+ .p-treenode-content {
+ padding: 0 0.5rem;
+ }
+ }
+ }
+ }
+
+ .p-treenode-leaf>.p-treenode-content .p-tree-toggler {
+ display: none;
+ }
+
+ .p-tree-empty-message {
+ padding: 1rem 0.5rem;
+ }
+ }
+}
\ No newline at end of file
diff --git a/desktop/src/app/calibration/calibration.component.ts b/desktop/src/app/calibration/calibration.component.ts
index 18ed705b2..eb50c15e9 100644
--- a/desktop/src/app/calibration/calibration.component.ts
+++ b/desktop/src/app/calibration/calibration.component.ts
@@ -1,170 +1,271 @@
-import { AfterViewInit, Component, HostListener, NgZone, OnDestroy } from '@angular/core'
-import { ActivatedRoute } from '@angular/router'
-import path from 'path'
-import { CheckboxChangeEvent } from 'primeng/checkbox'
+import { AfterViewInit, Component, HostListener, OnDestroy } from '@angular/core'
+import { dirname } from 'path'
+import { TreeDragDropService, TreeNode } from 'primeng/api'
+import { TreeNodeDropEvent } from 'primeng/tree'
import { ApiService } from '../../shared/services/api.service'
import { BrowserWindowService } from '../../shared/services/browser-window.service'
import { ElectronService } from '../../shared/services/electron.service'
-import { LocalStorageService } from '../../shared/services/local-storage.service'
+import { PreferenceService } from '../../shared/services/preference.service'
import { CalibrationFrame, CalibrationFrameGroup } from '../../shared/types/calibration.types'
-import { Camera } from '../../shared/types/camera.types'
import { AppComponent } from '../app.component'
-export const CALIBRATION_DIR_KEY = 'calibration.directory'
+export interface CalibrationNode extends TreeNode {
+ key: string
+ label: string
+ data: TreeNodeData
+ children: CalibrationNode[]
+ parent?: CalibrationNode
+}
+
+export type TreeNodeData =
+ { type: 'NAME', data: string } |
+ { type: 'GROUP', data: CalibrationFrameGroup } |
+ { type: 'FRAME', data: CalibrationFrame }
@Component({
selector: 'app-calibration',
templateUrl: './calibration.component.html',
styleUrls: ['./calibration.component.scss'],
+ providers: [TreeDragDropService],
})
export class CalibrationComponent implements AfterViewInit, OnDestroy {
- camera!: Camera
-
- groups: CalibrationFrameGroup[] = []
- group?: CalibrationFrameGroup
- frame?: CalibrationFrame
+ readonly frames: CalibrationNode[] = []
- get groupIsEnabled() {
- return !!this.group && !this.group.frames.find(e => !e.enabled)
- }
+ showNewGroupDialog = false
+ newGroupName = ''
+ newGroupDialogSave: () => void = () => { }
constructor(
- private app: AppComponent,
+ app: AppComponent,
private api: ApiService,
- electron: ElectronService,
+ private electron: ElectronService,
private browserWindow: BrowserWindowService,
- private route: ActivatedRoute,
- private storage: LocalStorageService,
- ngZone: NgZone,
+ private preference: PreferenceService,
) {
app.title = 'Calibration'
-
- app.topMenu.push({
- icon: 'mdi mdi-image-plus',
- tooltip: 'Add file',
- command: async () => {
- const defaultPath = this.storage.get(CALIBRATION_DIR_KEY, '')
- const filePath = await electron.openFits({ defaultPath })
-
- if (filePath) {
- this.storage.set(CALIBRATION_DIR_KEY, path.dirname(filePath))
- this.upload(filePath)
- }
- },
- })
-
- app.topMenu.push({
- icon: 'mdi mdi-folder-plus',
- tooltip: 'Add folder',
- command: async () => {
- const defaultPath = this.storage.get(CALIBRATION_DIR_KEY, '')
- const dirPath = await electron.openDirectory({ defaultPath })
-
- if (dirPath) {
- this.storage.set(CALIBRATION_DIR_KEY, dirPath)
- this.upload(dirPath)
- }
- },
- })
-
- electron.on('DATA.CHANGED', (data: Camera) => {
- ngZone.run(() => {
- if (data.name !== this.camera.name) {
- this.loadForCamera(data, true)
- }
- })
- })
}
- async ngAfterViewInit() {
- this.route.queryParams.subscribe(async e => {
- const camera = JSON.parse(decodeURIComponent(e.data)) as Camera
- this.loadForCamera(camera)
- })
+ ngAfterViewInit() {
+ this.load()
}
@HostListener('window:unload')
ngOnDestroy() { }
- private loadForCamera(camera: Camera, reload: boolean = false) {
- this.camera = camera
- this.app.subTitle = this.camera.name
- return reload ? this.reload() : this.load()
+ private makeTreeNode(key: string, label: string, data: TreeNodeData, parent?: CalibrationNode): CalibrationNode {
+ const draggable = data.type === 'FRAME'
+ const droppable = data.type === 'NAME'
+ return { key, label, data, children: [], parent, draggable, droppable }
}
- private async upload(path: string) {
- const frames = await this.api.uploadCalibrationFrame(this.camera!, path)
+ addGroup(name: string) {
+ const node = this.frames.find(e => e.label === name)
+ ?? this.makeTreeNode(`group-${name}`, name, { type: 'NAME', data: name })
- if (frames.length > 0) {
- this.load()
+ if (this.frames.indexOf(node) < 0) {
+ this.frames.push(node)
}
+
+ return node
}
- private async load() {
- this.groups = await this.api.calibrationFrames(this.camera)
+ addFrameGroup(name: string | CalibrationNode, group: CalibrationFrameGroup) {
+ const parent = typeof name === 'string'
+ ? this.frames.find(e => e.label === name)
+ : name
+
+ if (parent) {
+ const node = this.makeTreeNode(`frame-group-${group.id}`, `Frame`, { type: 'GROUP', data: group }, parent)
+ parent.children.push(node)
+ return node
+ }
+
+ return undefined
}
- private async reload() {
- this.group = undefined
- this.groupSelected()
- this.load()
+ addFrame(group: string | CalibrationNode, frame: CalibrationFrame) {
+ const parent = typeof group === 'string'
+ ? this.frames.find(e => e.label === group)
+ : group
+
+ if (parent) {
+ const node = this.makeTreeNode(`frame-${frame.id}`, `Frame`, { type: 'FRAME', data: frame }, parent)
+ parent.children.push(node)
+ return node
+ }
+
+ return undefined
}
- groupSelected() {
- this.frame = undefined
+ async openFileToUpload(node: CalibrationNode) {
+ if (node.data.type === 'NAME') {
+ const preference = this.preference.calibrationPreference.get()
+ const path = await this.electron.openImage({ defaultPath: preference.openPath })
+
+ if (path) {
+ preference.openPath = dirname(path)
+ this.preference.calibrationPreference.set(preference)
+ this.upload(node, path)
+ }
+ }
}
- groupChecked(event: CheckboxChangeEvent) {
- this.group?.frames?.forEach(e => e.enabled = event.checked)
+ async openDirectoryToUpload(node: CalibrationNode) {
+ if (node.data.type === 'NAME') {
+ const preference = this.preference.calibrationPreference.get()
+ const path = await this.electron.openDirectory({ defaultPath: preference.openPath })
+
+ if (path) {
+ preference.openPath = path
+ this.preference.calibrationPreference.set(preference)
+ this.upload(node, path)
+ }
+ }
}
- async frameChecked(frame: CalibrationFrame, event: CheckboxChangeEvent) {
- await this.api.editCalibrationFrame(frame)
+ private async upload(node: CalibrationNode, path: string) {
+ if (node.data.type === 'NAME') {
+ const frames = await this.api.uploadCalibrationFrame(node.data.data, path)
+
+ if (frames.length > 0) {
+ this.electron.calibrationChanged()
+ this.load()
+ }
+ }
+ }
+
+ private async load() {
+ this.frames.length = 0
+
+ const names = await this.api.calibrationGroups()
+
+ for (const name of names) {
+ const nameNode = this.addGroup(name)
+
+ const groups = await this.api.calibrationFrames(name)
+
+ for (const group of groups) {
+ const frameGroupNode = this.addFrameGroup(nameNode, group)!
+
+ for (const frame of group.frames) {
+ this.addFrame(frameGroupNode, frame)
+ }
+ }
+ }
}
openImage(frame: CalibrationFrame) {
this.browserWindow.openImage({ path: frame.path })
}
- replaceFrame(frame: CalibrationFrame) {
- console.info(frame)
+ toggleCalibrationFrame(node: CalibrationNode, enabled: boolean) {
+ if (node.data.type === 'FRAME') {
+ this.api.editCalibrationFrame(node.data.data)
+ }
}
- async deleteFrame(frame: CalibrationFrame) {
- await this.api.deleteCalibrationFrame(frame)
+ async deleteFrame(node: CalibrationNode) {
+ const deleteFromParent = async () => {
+ if (node.parent) {
+ const idx = node.parent.children.indexOf(node)
- if (this.frame === frame) {
- this.frame = undefined
+ if (idx >= 0) {
+ node.parent.children.splice(idx, 1)
+ console.info('frame deleted', node)
+ }
+
+ if (!node.parent.children.length) {
+ await this.deleteFrame(node.parent)
+ }
+ } else {
+ const idx = this.frames.indexOf(node)
+
+ if (idx >= 0) {
+ this.frames.splice(idx, 1)
+ console.info('frame deleted', node)
+ this.electron.calibrationChanged()
+ }
+ }
}
- let index = this.group?.frames?.findIndex(e => e.id === frame.id) ?? -1
+ if (node.data.type === 'FRAME') {
+ await this.api.deleteCalibrationFrame(node.data.data)
+ await deleteFromParent()
+ } else {
+ for (const frame of Array.from(node.children)) {
+ await this.deleteFrame(frame)
+ }
- if (index >= 0) {
- this.group!.frames.splice(index, 1)
+ if (!node.children.length) {
+ await deleteFromParent()
+ }
+ }
+ }
- if (!this.group!.frames.length) {
- index = this.groups.indexOf(this.group!)
+ private calibrationFrameFromNode(node: CalibrationNode) {
+ const frames: CalibrationFrame[] = []
- if (index >= 0) {
- this.groups.splice(index, 1)
- this.group = undefined
+ function recursive(node: TreeNode) {
+ if (node.data!.type === 'NAME' || node.data!.type === 'GROUP') {
+ for (const child of node.children!) {
+ recursive(child)
}
+ } else {
+ frames.push(node.data!.data)
}
}
+
+ recursive(node)
+
+ return frames
+ }
+
+ showNewGroupDialogForAdd() {
+ this.newGroupDialogSave = () => {
+ this.addGroup(this.newGroupName)
+ this.showNewGroupDialog = false
+ }
+
+ this.newGroupName = ''
+ this.showNewGroupDialog = true
}
- async deleteGroupFrames(group: CalibrationFrameGroup) {
- for (const frame of group.frames) {
- await this.api.deleteCalibrationFrame(frame)
+ showNewGroupDialogForEdit(node: CalibrationNode) {
+ if (node.data.type === 'NAME') {
+ this.newGroupDialogSave = async () => {
+ const frames = this.calibrationFrameFromNode(node)
- if (frame === this.frame) {
- this.frame = undefined
+ for (const frame of frames) {
+ frame.name = this.newGroupName
+ await this.api.editCalibrationFrame(frame)
+ this.electron.calibrationChanged()
+ }
+
+ this.showNewGroupDialog = false
+ this.load()
}
+
+ this.newGroupName = node.data.data
+ this.showNewGroupDialog = true
}
+ }
- if (group === this.group) {
- this.group === undefined
+ editGroupName() {
+ this.showNewGroupDialog = false
+ }
+
+ async frameDropped(event: TreeNodeDropEvent) {
+ const dragNode = event.dragNode as CalibrationNode
+ const dropNode = event.dropNode as CalibrationNode
+
+ if (dragNode.data.type === 'FRAME' && dropNode.data.type === 'NAME' &&
+ dragNode.data.data.name !== dropNode.data.data
+ ) {
+ dragNode.data.data.name = dropNode.data.data
+ await this.api.editCalibrationFrame(dragNode.data.data)
+ this.electron.calibrationChanged()
+ this.load()
}
}
}
\ No newline at end of file
diff --git a/desktop/src/app/camera/camera.component.html b/desktop/src/app/camera/camera.component.html
index 0c4d324ec..0bac743f4 100644
--- a/desktop/src/app/camera/camera.component.html
+++ b/desktop/src/app/camera/camera.component.html
@@ -13,8 +13,8 @@
@@ -46,12 +46,12 @@