lightning-pose-app 1.8.1a1__py3-none-any.whl → 1.8.1a3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7892,6 +7892,10 @@ var OutputEmitterRef = class {
7892
7892
  }
7893
7893
  }
7894
7894
  };
7895
+ function output(opts) {
7896
+ ngDevMode && assertInInjectionContext(output);
7897
+ return new OutputEmitterRef();
7898
+ }
7895
7899
  function inputFunction(initialValue, opts) {
7896
7900
  ngDevMode && assertInInjectionContext(input);
7897
7901
  return createInputSignal(initialValue, opts);
@@ -20793,13 +20797,13 @@ function listenToOutput(tNode, lView, directiveIndex, lookupName, eventName, lis
20793
20797
  const tView = lView[TVIEW];
20794
20798
  const def = tView.data[directiveIndex];
20795
20799
  const propertyName = def.outputs[lookupName];
20796
- const output = instance[propertyName];
20797
- if (ngDevMode && !isOutputSubscribable(output)) {
20800
+ const output2 = instance[propertyName];
20801
+ if (ngDevMode && !isOutputSubscribable(output2)) {
20798
20802
  throw new Error(`@Output ${propertyName} not initialized in '${instance.constructor.name}'.`);
20799
20803
  }
20800
20804
  const tCleanup = tView.firstCreatePass ? getOrCreateTViewCleanup(tView) : null;
20801
20805
  const lCleanup = getOrCreateLViewCleanup(lView);
20802
- const subscription = output.subscribe(listenerFn);
20806
+ const subscription = output2.subscribe(listenerFn);
20803
20807
  const idx = lCleanup.length;
20804
20808
  lCleanup.push(listenerFn, subscription);
20805
20809
  tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
@@ -39894,6 +39898,423 @@ function provideRouterInitializer() {
39894
39898
  // node_modules/@angular/router/fesm2022/router.mjs
39895
39899
  var VERSION4 = new Version("19.2.11");
39896
39900
 
39901
+ // src/app/rpc.service.ts
39902
+ var RpcService = class _RpcService {
39903
+ http = inject(HttpClient);
39904
+ call(method, params) {
39905
+ const observable2 = this.http.post(`/app/v0/rpc/${method}`, params ?? null, {
39906
+ headers: { "Content-type": "application/json" }
39907
+ });
39908
+ return firstValueFrom(observable2);
39909
+ }
39910
+ static \u0275fac = function RpcService_Factory(__ngFactoryType__) {
39911
+ return new (__ngFactoryType__ || _RpcService)();
39912
+ };
39913
+ static \u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _RpcService, factory: _RpcService.\u0275fac, providedIn: "root" });
39914
+ };
39915
+ (() => {
39916
+ (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(RpcService, [{
39917
+ type: Injectable,
39918
+ args: [{
39919
+ providedIn: "root"
39920
+ }]
39921
+ }], null, null);
39922
+ })();
39923
+
39924
+ // src/app/project-info.ts
39925
+ var ProjectInfo = class {
39926
+ // absolute path on server filesystem
39927
+ data_dir;
39928
+ // absolute path on server filesystem
39929
+ model_dir;
39930
+ views;
39931
+ constructor(projectInfo) {
39932
+ this.data_dir = String(projectInfo.data_dir);
39933
+ this.model_dir = String(projectInfo.model_dir);
39934
+ this.views = projectInfo.views ?? [];
39935
+ }
39936
+ };
39937
+
39938
+ // node_modules/@angular/core/fesm2022/rxjs-interop.mjs
39939
+ function takeUntilDestroyed(destroyRef) {
39940
+ if (!destroyRef) {
39941
+ assertInInjectionContext(takeUntilDestroyed);
39942
+ destroyRef = inject(DestroyRef);
39943
+ }
39944
+ const destroyed$ = new Observable((observer) => {
39945
+ const unregisterFn = destroyRef.onDestroy(observer.next.bind(observer));
39946
+ return unregisterFn;
39947
+ });
39948
+ return (source) => {
39949
+ return source.pipe(takeUntil(destroyed$));
39950
+ };
39951
+ }
39952
+ function toSignal(source, options) {
39953
+ typeof ngDevMode !== "undefined" && ngDevMode && assertNotInReactiveContext(toSignal, "Invoking `toSignal` causes new subscriptions every time. Consider moving `toSignal` outside of the reactive context and read the signal value where needed.");
39954
+ const requiresCleanup = !options?.manualCleanup;
39955
+ requiresCleanup && !options?.injector && assertInInjectionContext(toSignal);
39956
+ const cleanupRef = requiresCleanup ? options?.injector?.get(DestroyRef) ?? inject(DestroyRef) : null;
39957
+ const equal = makeToSignalEqual(options?.equal);
39958
+ let state;
39959
+ if (options?.requireSync) {
39960
+ state = signal({
39961
+ kind: 0
39962
+ /* StateKind.NoValue */
39963
+ }, {
39964
+ equal
39965
+ });
39966
+ } else {
39967
+ state = signal({
39968
+ kind: 1,
39969
+ value: options?.initialValue
39970
+ }, {
39971
+ equal
39972
+ });
39973
+ }
39974
+ const sub = source.subscribe({
39975
+ next: (value) => state.set({
39976
+ kind: 1,
39977
+ value
39978
+ }),
39979
+ error: (error) => {
39980
+ if (options?.rejectErrors) {
39981
+ throw error;
39982
+ }
39983
+ state.set({
39984
+ kind: 2,
39985
+ error
39986
+ });
39987
+ }
39988
+ // Completion of the Observable is meaningless to the signal. Signals don't have a concept of
39989
+ // "complete".
39990
+ });
39991
+ if (options?.requireSync && state().kind === 0) {
39992
+ throw new RuntimeError(601, (typeof ngDevMode === "undefined" || ngDevMode) && "`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.");
39993
+ }
39994
+ cleanupRef?.onDestroy(sub.unsubscribe.bind(sub));
39995
+ return computed(() => {
39996
+ const current = state();
39997
+ switch (current.kind) {
39998
+ case 1:
39999
+ return current.value;
40000
+ case 2:
40001
+ throw current.error;
40002
+ case 0:
40003
+ throw new RuntimeError(601, (typeof ngDevMode === "undefined" || ngDevMode) && "`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.");
40004
+ }
40005
+ }, {
40006
+ equal: options?.equal
40007
+ });
40008
+ }
40009
+ function makeToSignalEqual(userEquality = Object.is) {
40010
+ return (a, b) => a.kind === 1 && b.kind === 1 && userEquality(a.value, b.value);
40011
+ }
40012
+
40013
+ // src/app/project-info.service.ts
40014
+ var ProjectInfoService = class _ProjectInfoService {
40015
+ rpc = inject(RpcService);
40016
+ // undefined means not yet requested
40017
+ // null means requested and not present
40018
+ _projectInfo = void 0;
40019
+ async loadProjectInfo() {
40020
+ const response = await this.rpc.call("getProjectInfo");
40021
+ this._projectInfo = response.projectInfo ? new ProjectInfo(response.projectInfo) : null;
40022
+ this.setAllViews(this._projectInfo?.views ?? []);
40023
+ }
40024
+ get projectInfo() {
40025
+ return this._projectInfo;
40026
+ }
40027
+ async setProjectInfo(projectInfo) {
40028
+ await this.rpc.call("setProjectInfo", { projectInfo });
40029
+ window.location.reload();
40030
+ }
40031
+ // Newer style of models.
40032
+ _allViews = new BehaviorSubject([]);
40033
+ allViews$ = this._allViews.asObservable().pipe(distinctUntilChanged());
40034
+ allViews = toSignal(this.allViews$, { requireSync: true });
40035
+ setAllViews(views) {
40036
+ this._allViews.next(views.concat(["unknown"]));
40037
+ }
40038
+ _allKeypoints = new BehaviorSubject([]);
40039
+ allKeypoints$ = this._allKeypoints.asObservable().pipe(distinctUntilChanged());
40040
+ allKeypoints = toSignal(this.allKeypoints$, { requireSync: true });
40041
+ setAllKeypoints(keypoints) {
40042
+ this._allKeypoints.next(keypoints);
40043
+ }
40044
+ _allModels = new BehaviorSubject([]);
40045
+ allModels$ = this._allModels.asObservable().pipe(distinctUntilChanged());
40046
+ allModels = toSignal(this.allModels$, { requireSync: true });
40047
+ setAllModels(models) {
40048
+ this._allModels.next(models);
40049
+ }
40050
+ static \u0275fac = function ProjectInfoService_Factory(__ngFactoryType__) {
40051
+ return new (__ngFactoryType__ || _ProjectInfoService)();
40052
+ };
40053
+ static \u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _ProjectInfoService, factory: _ProjectInfoService.\u0275fac, providedIn: "root" });
40054
+ };
40055
+ (() => {
40056
+ (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ProjectInfoService, [{
40057
+ type: Injectable,
40058
+ args: [{
40059
+ providedIn: "root"
40060
+ }]
40061
+ }], null, null);
40062
+ })();
40063
+
40064
+ // src/app/csv-parser.service.ts
40065
+ var import_ndarray = __toESM(require_ndarray());
40066
+ var import_papaparse = __toESM(require_papaparse_min());
40067
+ var CsvParserService = class _CsvParserService {
40068
+ /**
40069
+ * Parses a CSV string from pose estimation into a 3D array (ndarray-like structure)
40070
+ * using the PapaParse library.
40071
+ * The output shape is (number of frames, number of bodyparts, 2 for x/y coordinates).
40072
+ *
40073
+ * @param csvString The CSV data as a string.
40074
+ * @returns A 3D array of numbers: number[][][].
40075
+ * Returns an empty array if the CSV is malformed, has no data, or PapaParse fails.
40076
+ */
40077
+ getBodyParts(csvString) {
40078
+ const parseOutput = import_papaparse.default.parse(csvString.trim(), {
40079
+ dynamicTyping: false,
40080
+ skipEmptyLines: true
40081
+ });
40082
+ if (parseOutput.errors.length > 0) {
40083
+ console.error("PapaParse errors:", parseOutput.errors);
40084
+ return [];
40085
+ }
40086
+ const allRows = parseOutput.data;
40087
+ if (allRows.length < 4) {
40088
+ console.error("CSV must have at least 3 header lines and 1 data line.");
40089
+ return [];
40090
+ }
40091
+ const bodypartsHeader = allRows[1];
40092
+ if (!bodypartsHeader || bodypartsHeader.length <= 1 || (bodypartsHeader.length - 1) % 3 !== 0) {
40093
+ console.error("Malformed bodyparts header line (line 2 of CSV).", bodypartsHeader);
40094
+ return [];
40095
+ }
40096
+ return bodypartsHeader.filter((_element, index) => {
40097
+ return (index - 1) % 3 === 0 && index >= 1;
40098
+ });
40099
+ }
40100
+ parsePredictionFile(csvString) {
40101
+ const parseOutput = import_papaparse.default.parse(csvString.trim(), {
40102
+ dynamicTyping: false,
40103
+ skipEmptyLines: true
40104
+ });
40105
+ if (parseOutput.errors.length > 0) {
40106
+ console.error("PapaParse errors:", parseOutput.errors);
40107
+ return (0, import_ndarray.default)(new Float64Array(0), [0, 0, 2]);
40108
+ }
40109
+ const allRows = parseOutput.data;
40110
+ if (allRows.length < 4) {
40111
+ console.error("CSV must have at least 3 header lines and 1 data line.");
40112
+ return (0, import_ndarray.default)(new Float64Array(0), [0, 0, 2]);
40113
+ }
40114
+ const bodypartsHeader = allRows[1];
40115
+ if (!bodypartsHeader || bodypartsHeader.length <= 1 || (bodypartsHeader.length - 1) % 3 !== 0) {
40116
+ console.error("Malformed bodyparts header line (line 2 of CSV).", bodypartsHeader);
40117
+ return (0, import_ndarray.default)(new Float64Array(0), [0, 0, 2]);
40118
+ }
40119
+ const numBodyParts = (bodypartsHeader.length - 1) / 3;
40120
+ const coordsHeader = allRows[2];
40121
+ if (!coordsHeader || coordsHeader.length !== bodypartsHeader.length) {
40122
+ console.error("Coordinate header (line 3 of CSV) length mismatch with bodyparts header.");
40123
+ return (0, import_ndarray.default)(new Float64Array(0), [
40124
+ 0,
40125
+ numBodyParts > 0 ? numBodyParts : 0,
40126
+ 2
40127
+ ]);
40128
+ }
40129
+ const dataRowsOnly = allRows.slice(3);
40130
+ const numFrames = dataRowsOnly.length;
40131
+ if (numFrames === 0 || numBodyParts === 0) {
40132
+ return (0, import_ndarray.default)(new Float64Array(0), [numFrames, numBodyParts, 2]);
40133
+ }
40134
+ const flatData = new Float64Array(numFrames * numBodyParts * 2);
40135
+ let flatIndex = 0;
40136
+ for (let rowIndex = 0; rowIndex < numFrames; rowIndex++) {
40137
+ const values = dataRowsOnly[rowIndex];
40138
+ if (values.length < 1 + numBodyParts * 3) {
40139
+ console.warn(`Skipping malformed data row ${rowIndex + 4} (not enough columns): "${values.slice(0, 5).join(",")}..."`);
40140
+ for (let i = 0; i < numBodyParts; i++) {
40141
+ flatData[flatIndex++] = NaN;
40142
+ flatData[flatIndex++] = NaN;
40143
+ }
40144
+ continue;
40145
+ }
40146
+ for (let bodyPartIdx = 0; bodyPartIdx < numBodyParts; bodyPartIdx++) {
40147
+ const xDataIndex = 1 + bodyPartIdx * 3;
40148
+ const yDataIndex = 1 + bodyPartIdx * 3 + 1;
40149
+ if (xDataIndex >= values.length || yDataIndex >= values.length) {
40150
+ console.warn(`Skipping body part ${bodyPartIdx} in data row ${rowIndex + 4} due to insufficient data.`);
40151
+ flatData[flatIndex++] = NaN;
40152
+ flatData[flatIndex++] = NaN;
40153
+ continue;
40154
+ }
40155
+ const xString = values[xDataIndex];
40156
+ const yString = values[yDataIndex];
40157
+ const x = parseFloat(xString);
40158
+ const y = parseFloat(yString);
40159
+ if (isNaN(x) || isNaN(y)) {
40160
+ console.warn(`Could not parse x or y as number for body part ${bodyPartIdx} in data row ${rowIndex + 4}. Values: x='${xString}', y='${yString}'.`);
40161
+ flatData[flatIndex++] = NaN;
40162
+ flatData[flatIndex++] = NaN;
40163
+ } else {
40164
+ flatData[flatIndex++] = x;
40165
+ flatData[flatIndex++] = y;
40166
+ }
40167
+ }
40168
+ }
40169
+ return (0, import_ndarray.default)(flatData, [numFrames, numBodyParts, 2]);
40170
+ }
40171
+ static \u0275fac = function CsvParserService_Factory(__ngFactoryType__) {
40172
+ return new (__ngFactoryType__ || _CsvParserService)();
40173
+ };
40174
+ static \u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _CsvParserService, factory: _CsvParserService.\u0275fac, providedIn: "root" });
40175
+ };
40176
+ (() => {
40177
+ (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CsvParserService, [{
40178
+ type: Injectable,
40179
+ args: [{
40180
+ providedIn: "root"
40181
+ }]
40182
+ }], null, null);
40183
+ })();
40184
+
40185
+ // src/app/session.service.ts
40186
+ var SessionService = class _SessionService {
40187
+ rpc = inject(RpcService);
40188
+ httpClient = inject(HttpClient);
40189
+ predictionFiles = [];
40190
+ projectInfoService = inject(ProjectInfoService);
40191
+ csvParser = inject(CsvParserService);
40192
+ _allSessions = new BehaviorSubject([]);
40193
+ allSessions$ = this._allSessions.asObservable();
40194
+ allSessions = toSignal(this.allSessions$, { requireSync: true });
40195
+ async loadSessions() {
40196
+ const projectInfo = this.projectInfoService.projectInfo;
40197
+ const response = await this.rpc.call("rglob", {
40198
+ baseDir: projectInfo.data_dir,
40199
+ pattern: "**/*.fine.mp4",
40200
+ //temporary
40201
+ noDirs: true
40202
+ });
40203
+ const mp4Files = response.entries.filter((entry) => entry.type === "file").map((entry) => entry.path);
40204
+ const sessions = this.groupVideoFilesIntoSessions(mp4Files, this.projectInfoService.projectInfo.views);
40205
+ return this._allSessions.next(sessions);
40206
+ }
40207
+ async loadPredictionIndex() {
40208
+ const projectInfo = this.projectInfoService.projectInfo;
40209
+ const response = await this.rpc.call("rglob", {
40210
+ baseDir: projectInfo.model_dir,
40211
+ pattern: "**/video_preds/**/*.csv",
40212
+ noDirs: true
40213
+ });
40214
+ this.predictionFiles = response.entries.filter((entry) => {
40215
+ if (entry.type !== "file")
40216
+ return false;
40217
+ if (entry.path.endsWith("_bbox.csv"))
40218
+ return false;
40219
+ if (entry.path.endsWith("_error.csv"))
40220
+ return false;
40221
+ if (entry.path.endsWith("_loss.csv"))
40222
+ return false;
40223
+ if (entry.path.endsWith("_norm.csv"))
40224
+ return false;
40225
+ return true;
40226
+ }).map((entry) => {
40227
+ let match2 = entry.path.match(/(.+)\/video_preds\/([^/]+)\.mp4\/predictions\.csv/);
40228
+ if (!match2) {
40229
+ match2 = entry.path.match(/(.+)\/video_preds\/([^/]+)\.csv/);
40230
+ }
40231
+ if (!match2)
40232
+ return null;
40233
+ const modelKey = match2[1];
40234
+ const sessionView = match2[2];
40235
+ const viewName = this.projectInfoService.allViews().find((v) => sessionView.includes(v));
40236
+ if (!viewName)
40237
+ return null;
40238
+ const sessionKey = sessionView.replace(viewName, "*");
40239
+ return {
40240
+ path: entry.path,
40241
+ modelKey,
40242
+ sessionKey,
40243
+ viewName
40244
+ };
40245
+ }).filter((entry) => entry != null);
40246
+ this.initModels();
40247
+ await this.initKeypoints();
40248
+ }
40249
+ initModels() {
40250
+ const uniqueModels = new Set(this.predictionFiles.map((pfile) => pfile.modelKey).filter((x) => x));
40251
+ this.projectInfoService.setAllModels(Array.from(uniqueModels).sort());
40252
+ }
40253
+ async initKeypoints() {
40254
+ if (this.predictionFiles.length === 0)
40255
+ return;
40256
+ const csvFile = await this.getPredictionFile(this.predictionFiles[0]);
40257
+ if (!csvFile)
40258
+ return;
40259
+ const allKeypoints = this.csvParser.getBodyParts(csvFile);
40260
+ this.projectInfoService.setAllKeypoints(allKeypoints);
40261
+ }
40262
+ getPredictionFilesForSession(sessionKey) {
40263
+ const predictionFiles = this.predictionFiles.filter((p) => p.sessionKey === sessionKey || p.sessionKey === sessionKey.replace(/\.fine$/, ""));
40264
+ return predictionFiles;
40265
+ }
40266
+ async getPredictionFile(pfile) {
40267
+ const modelDir = this.projectInfoService.projectInfo?.model_dir;
40268
+ const src = "/app/v0/files/" + modelDir + "/" + pfile.path;
40269
+ return await firstValueFrom(this.httpClient.get(src, { responseType: "text" }).pipe(catchError((error) => {
40270
+ if (error.status === 404) {
40271
+ return [null];
40272
+ }
40273
+ throw error;
40274
+ })));
40275
+ }
40276
+ async ffprobe(file) {
40277
+ const response = await this.rpc.call("ffprobe", {
40278
+ path: file
40279
+ });
40280
+ return response;
40281
+ }
40282
+ groupVideoFilesIntoSessions(filenames, views) {
40283
+ const sessionKeyToItsViewFiles = /* @__PURE__ */ new Map();
40284
+ for (const filename of filenames) {
40285
+ let viewName = views.find((v) => filename.includes(v));
40286
+ if (!viewName) {
40287
+ viewName = "unknown";
40288
+ }
40289
+ const sessionKey = viewName == "unknown" ? filename : filename.replace(viewName, "*");
40290
+ if (!sessionKeyToItsViewFiles.has(sessionKey)) {
40291
+ sessionKeyToItsViewFiles.set(sessionKey, []);
40292
+ }
40293
+ const projectInfo = this.projectInfoService.projectInfo;
40294
+ sessionKeyToItsViewFiles.get(sessionKey).push({
40295
+ viewName,
40296
+ videoPath: projectInfo.data_dir + "/" + filename
40297
+ });
40298
+ }
40299
+ return Array.from(sessionKeyToItsViewFiles.entries()).map(([key, sessionViews]) => ({
40300
+ key: key.replace(/\.mp4$/, ""),
40301
+ views: sessionViews
40302
+ }));
40303
+ }
40304
+ static \u0275fac = function SessionService_Factory(__ngFactoryType__) {
40305
+ return new (__ngFactoryType__ || _SessionService)();
40306
+ };
40307
+ static \u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _SessionService, factory: _SessionService.\u0275fac, providedIn: "root" });
40308
+ };
40309
+ (() => {
40310
+ (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(SessionService, [{
40311
+ type: Injectable,
40312
+ args: [{
40313
+ providedIn: "root"
40314
+ }]
40315
+ }], null, null);
40316
+ })();
40317
+
39897
40318
  // node_modules/@angular/cdk/fesm2022/boolean-property-DaaVhX5A.mjs
39898
40319
  function coerceBooleanProperty(value) {
39899
40320
  return value != null && `${value}` !== "false";
@@ -40880,12 +41301,12 @@ var BreakpointObserver = class _BreakpointObserver {
40880
41301
  query,
40881
41302
  matches
40882
41303
  })), takeUntil(this._destroySubject));
40883
- const output = {
41304
+ const output2 = {
40884
41305
  observable: queryObservable,
40885
41306
  mql
40886
41307
  };
40887
- this._queries.set(query, output);
40888
- return output;
41308
+ this._queries.set(query, output2);
41309
+ return output2;
40889
41310
  }
40890
41311
  static \u0275fac = function BreakpointObserver_Factory(__ngFactoryType__) {
40891
41312
  return new (__ngFactoryType__ || _BreakpointObserver)();
@@ -52946,14 +53367,14 @@ var ViewportRuler = class _ViewportRuler {
52946
53367
  if (!this._viewportSize) {
52947
53368
  this._updateViewportSize();
52948
53369
  }
52949
- const output = {
53370
+ const output2 = {
52950
53371
  width: this._viewportSize.width,
52951
53372
  height: this._viewportSize.height
52952
53373
  };
52953
53374
  if (!this._platform.isBrowser) {
52954
53375
  this._viewportSize = null;
52955
53376
  }
52956
- return output;
53377
+ return output2;
52957
53378
  }
52958
53379
  /** Gets a DOMRect for the viewport's bounds. */
52959
53380
  getViewportRect() {
@@ -53892,399 +54313,6 @@ var ScrollingModule = class _ScrollingModule {
53892
54313
  }], null, null);
53893
54314
  })();
53894
54315
 
53895
- // node_modules/@angular/core/fesm2022/rxjs-interop.mjs
53896
- function takeUntilDestroyed(destroyRef) {
53897
- if (!destroyRef) {
53898
- assertInInjectionContext(takeUntilDestroyed);
53899
- destroyRef = inject(DestroyRef);
53900
- }
53901
- const destroyed$ = new Observable((observer) => {
53902
- const unregisterFn = destroyRef.onDestroy(observer.next.bind(observer));
53903
- return unregisterFn;
53904
- });
53905
- return (source) => {
53906
- return source.pipe(takeUntil(destroyed$));
53907
- };
53908
- }
53909
- function toSignal(source, options) {
53910
- typeof ngDevMode !== "undefined" && ngDevMode && assertNotInReactiveContext(toSignal, "Invoking `toSignal` causes new subscriptions every time. Consider moving `toSignal` outside of the reactive context and read the signal value where needed.");
53911
- const requiresCleanup = !options?.manualCleanup;
53912
- requiresCleanup && !options?.injector && assertInInjectionContext(toSignal);
53913
- const cleanupRef = requiresCleanup ? options?.injector?.get(DestroyRef) ?? inject(DestroyRef) : null;
53914
- const equal = makeToSignalEqual(options?.equal);
53915
- let state;
53916
- if (options?.requireSync) {
53917
- state = signal({
53918
- kind: 0
53919
- /* StateKind.NoValue */
53920
- }, {
53921
- equal
53922
- });
53923
- } else {
53924
- state = signal({
53925
- kind: 1,
53926
- value: options?.initialValue
53927
- }, {
53928
- equal
53929
- });
53930
- }
53931
- const sub = source.subscribe({
53932
- next: (value) => state.set({
53933
- kind: 1,
53934
- value
53935
- }),
53936
- error: (error) => {
53937
- if (options?.rejectErrors) {
53938
- throw error;
53939
- }
53940
- state.set({
53941
- kind: 2,
53942
- error
53943
- });
53944
- }
53945
- // Completion of the Observable is meaningless to the signal. Signals don't have a concept of
53946
- // "complete".
53947
- });
53948
- if (options?.requireSync && state().kind === 0) {
53949
- throw new RuntimeError(601, (typeof ngDevMode === "undefined" || ngDevMode) && "`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.");
53950
- }
53951
- cleanupRef?.onDestroy(sub.unsubscribe.bind(sub));
53952
- return computed(() => {
53953
- const current = state();
53954
- switch (current.kind) {
53955
- case 1:
53956
- return current.value;
53957
- case 2:
53958
- throw current.error;
53959
- case 0:
53960
- throw new RuntimeError(601, (typeof ngDevMode === "undefined" || ngDevMode) && "`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.");
53961
- }
53962
- }, {
53963
- equal: options?.equal
53964
- });
53965
- }
53966
- function makeToSignalEqual(userEquality = Object.is) {
53967
- return (a, b) => a.kind === 1 && b.kind === 1 && userEquality(a.value, b.value);
53968
- }
53969
-
53970
- // src/app/rpc.service.ts
53971
- var RpcService = class _RpcService {
53972
- http = inject(HttpClient);
53973
- call(method, params) {
53974
- const observable2 = this.http.post(`/app/v0/rpc/${method}`, params ?? null, {
53975
- headers: { "Content-type": "application/json" }
53976
- });
53977
- return firstValueFrom(observable2);
53978
- }
53979
- static \u0275fac = function RpcService_Factory(__ngFactoryType__) {
53980
- return new (__ngFactoryType__ || _RpcService)();
53981
- };
53982
- static \u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _RpcService, factory: _RpcService.\u0275fac, providedIn: "root" });
53983
- };
53984
- (() => {
53985
- (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(RpcService, [{
53986
- type: Injectable,
53987
- args: [{
53988
- providedIn: "root"
53989
- }]
53990
- }], null, null);
53991
- })();
53992
-
53993
- // src/app/project-info.service.ts
53994
- var ProjectInfoService = class _ProjectInfoService {
53995
- rpc = inject(RpcService);
53996
- // inits to undefined, set to null if no project exists.
53997
- _projectInfo = void 0;
53998
- async loadProjectInfo() {
53999
- const response = await this.rpc.call("getProjectInfo");
54000
- this._projectInfo = response.projectInfo;
54001
- this.setAllViews(response.projectInfo.views);
54002
- }
54003
- get projectInfo() {
54004
- return this._projectInfo;
54005
- }
54006
- async setProjectInfo(projectInfo) {
54007
- await this.rpc.call("setProjectInfo", { projectInfo });
54008
- }
54009
- // Newer style of models.
54010
- _allViews = new BehaviorSubject([]);
54011
- allViews$ = this._allViews.asObservable().pipe(distinctUntilChanged());
54012
- allViews = toSignal(this.allViews$, { requireSync: true });
54013
- setAllViews(views) {
54014
- this._allViews.next(views);
54015
- }
54016
- _allKeypoints = new BehaviorSubject([]);
54017
- allKeypoints$ = this._allKeypoints.asObservable().pipe(distinctUntilChanged());
54018
- allKeypoints = toSignal(this.allKeypoints$, { requireSync: true });
54019
- setAllKeypoints(keypoints) {
54020
- this._allKeypoints.next(keypoints);
54021
- }
54022
- _allModels = new BehaviorSubject([]);
54023
- allModels$ = this._allModels.asObservable().pipe(distinctUntilChanged());
54024
- allModels = toSignal(this.allModels$, { requireSync: true });
54025
- setAllModels(models) {
54026
- this._allModels.next(models);
54027
- }
54028
- static \u0275fac = function ProjectInfoService_Factory(__ngFactoryType__) {
54029
- return new (__ngFactoryType__ || _ProjectInfoService)();
54030
- };
54031
- static \u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _ProjectInfoService, factory: _ProjectInfoService.\u0275fac, providedIn: "root" });
54032
- };
54033
- (() => {
54034
- (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ProjectInfoService, [{
54035
- type: Injectable,
54036
- args: [{
54037
- providedIn: "root"
54038
- }]
54039
- }], null, null);
54040
- })();
54041
-
54042
- // src/app/csv-parser.service.ts
54043
- var import_ndarray = __toESM(require_ndarray());
54044
- var import_papaparse = __toESM(require_papaparse_min());
54045
- var CsvParserService = class _CsvParserService {
54046
- /**
54047
- * Parses a CSV string from pose estimation into a 3D array (ndarray-like structure)
54048
- * using the PapaParse library.
54049
- * The output shape is (number of frames, number of bodyparts, 2 for x/y coordinates).
54050
- *
54051
- * @param csvString The CSV data as a string.
54052
- * @returns A 3D array of numbers: number[][][].
54053
- * Returns an empty array if the CSV is malformed, has no data, or PapaParse fails.
54054
- */
54055
- getBodyParts(csvString) {
54056
- const parseOutput = import_papaparse.default.parse(csvString.trim(), {
54057
- dynamicTyping: false,
54058
- skipEmptyLines: true
54059
- });
54060
- if (parseOutput.errors.length > 0) {
54061
- console.error("PapaParse errors:", parseOutput.errors);
54062
- return [];
54063
- }
54064
- const allRows = parseOutput.data;
54065
- if (allRows.length < 4) {
54066
- console.error("CSV must have at least 3 header lines and 1 data line.");
54067
- return [];
54068
- }
54069
- const bodypartsHeader = allRows[1];
54070
- if (!bodypartsHeader || bodypartsHeader.length <= 1 || (bodypartsHeader.length - 1) % 3 !== 0) {
54071
- console.error("Malformed bodyparts header line (line 2 of CSV).", bodypartsHeader);
54072
- return [];
54073
- }
54074
- return bodypartsHeader.filter((_element, index) => {
54075
- return (index - 1) % 3 === 0 && index >= 1;
54076
- });
54077
- }
54078
- parsePredictionFile(csvString) {
54079
- const parseOutput = import_papaparse.default.parse(csvString.trim(), {
54080
- dynamicTyping: false,
54081
- skipEmptyLines: true
54082
- });
54083
- if (parseOutput.errors.length > 0) {
54084
- console.error("PapaParse errors:", parseOutput.errors);
54085
- return (0, import_ndarray.default)(new Float64Array(0), [0, 0, 2]);
54086
- }
54087
- const allRows = parseOutput.data;
54088
- if (allRows.length < 4) {
54089
- console.error("CSV must have at least 3 header lines and 1 data line.");
54090
- return (0, import_ndarray.default)(new Float64Array(0), [0, 0, 2]);
54091
- }
54092
- const bodypartsHeader = allRows[1];
54093
- if (!bodypartsHeader || bodypartsHeader.length <= 1 || (bodypartsHeader.length - 1) % 3 !== 0) {
54094
- console.error("Malformed bodyparts header line (line 2 of CSV).", bodypartsHeader);
54095
- return (0, import_ndarray.default)(new Float64Array(0), [0, 0, 2]);
54096
- }
54097
- const numBodyParts = (bodypartsHeader.length - 1) / 3;
54098
- const coordsHeader = allRows[2];
54099
- if (!coordsHeader || coordsHeader.length !== bodypartsHeader.length) {
54100
- console.error("Coordinate header (line 3 of CSV) length mismatch with bodyparts header.");
54101
- return (0, import_ndarray.default)(new Float64Array(0), [
54102
- 0,
54103
- numBodyParts > 0 ? numBodyParts : 0,
54104
- 2
54105
- ]);
54106
- }
54107
- const dataRowsOnly = allRows.slice(3);
54108
- const numFrames = dataRowsOnly.length;
54109
- if (numFrames === 0 || numBodyParts === 0) {
54110
- return (0, import_ndarray.default)(new Float64Array(0), [numFrames, numBodyParts, 2]);
54111
- }
54112
- const flatData = new Float64Array(numFrames * numBodyParts * 2);
54113
- let flatIndex = 0;
54114
- for (let rowIndex = 0; rowIndex < numFrames; rowIndex++) {
54115
- const values = dataRowsOnly[rowIndex];
54116
- if (values.length < 1 + numBodyParts * 3) {
54117
- console.warn(`Skipping malformed data row ${rowIndex + 4} (not enough columns): "${values.slice(0, 5).join(",")}..."`);
54118
- for (let i = 0; i < numBodyParts; i++) {
54119
- flatData[flatIndex++] = NaN;
54120
- flatData[flatIndex++] = NaN;
54121
- }
54122
- continue;
54123
- }
54124
- for (let bodyPartIdx = 0; bodyPartIdx < numBodyParts; bodyPartIdx++) {
54125
- const xDataIndex = 1 + bodyPartIdx * 3;
54126
- const yDataIndex = 1 + bodyPartIdx * 3 + 1;
54127
- if (xDataIndex >= values.length || yDataIndex >= values.length) {
54128
- console.warn(`Skipping body part ${bodyPartIdx} in data row ${rowIndex + 4} due to insufficient data.`);
54129
- flatData[flatIndex++] = NaN;
54130
- flatData[flatIndex++] = NaN;
54131
- continue;
54132
- }
54133
- const xString = values[xDataIndex];
54134
- const yString = values[yDataIndex];
54135
- const x = parseFloat(xString);
54136
- const y = parseFloat(yString);
54137
- if (isNaN(x) || isNaN(y)) {
54138
- console.warn(`Could not parse x or y as number for body part ${bodyPartIdx} in data row ${rowIndex + 4}. Values: x='${xString}', y='${yString}'.`);
54139
- flatData[flatIndex++] = NaN;
54140
- flatData[flatIndex++] = NaN;
54141
- } else {
54142
- flatData[flatIndex++] = x;
54143
- flatData[flatIndex++] = y;
54144
- }
54145
- }
54146
- }
54147
- return (0, import_ndarray.default)(flatData, [numFrames, numBodyParts, 2]);
54148
- }
54149
- static \u0275fac = function CsvParserService_Factory(__ngFactoryType__) {
54150
- return new (__ngFactoryType__ || _CsvParserService)();
54151
- };
54152
- static \u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _CsvParserService, factory: _CsvParserService.\u0275fac, providedIn: "root" });
54153
- };
54154
- (() => {
54155
- (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CsvParserService, [{
54156
- type: Injectable,
54157
- args: [{
54158
- providedIn: "root"
54159
- }]
54160
- }], null, null);
54161
- })();
54162
-
54163
- // src/app/session.service.ts
54164
- var SessionService = class _SessionService {
54165
- rpc = inject(RpcService);
54166
- httpClient = inject(HttpClient);
54167
- allSessions = new BehaviorSubject([]);
54168
- predictionFiles = [];
54169
- projectInfoService = inject(ProjectInfoService);
54170
- csvParser = inject(CsvParserService);
54171
- getAllSessions() {
54172
- return this.allSessions.asObservable();
54173
- }
54174
- async loadSessions() {
54175
- const projectInfo = this.projectInfoService.projectInfo;
54176
- const response = await this.rpc.call("rglob", {
54177
- baseDir: projectInfo.data_dir,
54178
- pattern: "**/*.fine.mp4",
54179
- //temporary
54180
- noDirs: true
54181
- });
54182
- const mp4Files = response.entries.filter((entry) => entry.type === "file").map((entry) => entry.path);
54183
- const sessions = getUniqueSessionTemplates(mp4Files, this.projectInfoService.projectInfo.views).map((templateName) => {
54184
- return { key: templateName.replace(/\.mp4$/, "") };
54185
- });
54186
- return this.allSessions.next(sessions);
54187
- }
54188
- async loadPredictionIndex() {
54189
- const projectInfo = this.projectInfoService.projectInfo;
54190
- const response = await this.rpc.call("rglob", {
54191
- baseDir: projectInfo.model_dir,
54192
- pattern: "**/video_preds/**/*.csv",
54193
- noDirs: true
54194
- });
54195
- this.predictionFiles = response.entries.filter((entry) => {
54196
- if (entry.type !== "file")
54197
- return false;
54198
- if (entry.path.endsWith("_bbox.csv"))
54199
- return false;
54200
- if (entry.path.endsWith("_error.csv"))
54201
- return false;
54202
- if (entry.path.endsWith("_loss.csv"))
54203
- return false;
54204
- if (entry.path.endsWith("_norm.csv"))
54205
- return false;
54206
- return true;
54207
- }).map((entry) => {
54208
- let match2 = entry.path.match(/(.+)\/video_preds\/([^/]+)\.mp4\/predictions\.csv/);
54209
- if (!match2) {
54210
- match2 = entry.path.match(/(.+)\/video_preds\/([^/]+)\.csv/);
54211
- }
54212
- if (!match2)
54213
- return null;
54214
- const modelKey = match2[1];
54215
- const sessionView = match2[2];
54216
- const viewName = this.projectInfoService.allViews().find((v) => sessionView.includes(v));
54217
- if (!viewName)
54218
- return null;
54219
- const sessionKey = sessionView.replace(viewName, "*");
54220
- return {
54221
- path: entry.path,
54222
- modelKey,
54223
- sessionKey,
54224
- viewName
54225
- };
54226
- }).filter((entry) => entry != null);
54227
- this.initModels();
54228
- await this.initKeypoints();
54229
- }
54230
- initModels() {
54231
- const uniqueModels = new Set(this.predictionFiles.map((pfile) => pfile.modelKey).filter((x) => x));
54232
- this.projectInfoService.setAllModels(Array.from(uniqueModels).sort());
54233
- }
54234
- async initKeypoints() {
54235
- if (this.predictionFiles.length === 0)
54236
- return;
54237
- const csvFile = await this.getPredictionFile(this.predictionFiles[0]);
54238
- if (!csvFile)
54239
- return;
54240
- const allKeypoints = this.csvParser.getBodyParts(csvFile);
54241
- this.projectInfoService.setAllKeypoints(allKeypoints);
54242
- }
54243
- getPredictionFilesForSession(sessionKey) {
54244
- const predictionFiles = this.predictionFiles.filter((p) => p.sessionKey === sessionKey || p.sessionKey === sessionKey.replace(/\.fine$/, ""));
54245
- return predictionFiles;
54246
- }
54247
- async getPredictionFile(pfile) {
54248
- const modelDir = this.projectInfoService.projectInfo?.model_dir;
54249
- const src = "/app/v0/files/" + modelDir + "/" + pfile.path;
54250
- return await firstValueFrom(this.httpClient.get(src, { responseType: "text" }).pipe(catchError((error) => {
54251
- if (error.status === 404) {
54252
- return [null];
54253
- }
54254
- throw error;
54255
- })));
54256
- }
54257
- async ffprobe(file) {
54258
- const response = await this.rpc.call("ffprobe", {
54259
- path: file
54260
- });
54261
- return response;
54262
- }
54263
- static \u0275fac = function SessionService_Factory(__ngFactoryType__) {
54264
- return new (__ngFactoryType__ || _SessionService)();
54265
- };
54266
- static \u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _SessionService, factory: _SessionService.\u0275fac, providedIn: "root" });
54267
- };
54268
- (() => {
54269
- (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(SessionService, [{
54270
- type: Injectable,
54271
- args: [{
54272
- providedIn: "root"
54273
- }]
54274
- }], null, null);
54275
- })();
54276
- function getUniqueSessionTemplates(filenames, views) {
54277
- const sessionTemplates = /* @__PURE__ */ new Set();
54278
- for (const filename of filenames) {
54279
- const viewName = views.find((v) => filename.includes(v));
54280
- const sessionTemplate = viewName ? filename.replace(viewName, "*") : filename;
54281
- if (!sessionTemplates.has(sessionTemplate)) {
54282
- sessionTemplates.add(sessionTemplate);
54283
- }
54284
- }
54285
- return Array.from(sessionTemplates);
54286
- }
54287
-
54288
54316
  // src/app/viewer/viewer-left-panel/viewer-sessions-panel.component.ts
54289
54317
  var _c03 = (a0) => ["/viewer", a0];
54290
54318
  var _forTrack0 = ($index, $item) => $item.key;
@@ -54303,19 +54331,11 @@ function ViewerSessionsPanelComponent_For_16_Template(rf, ctx) {
54303
54331
  }
54304
54332
  }
54305
54333
  var ViewerSessionsPanelComponent = class _ViewerSessionsPanelComponent {
54306
- sessionService;
54307
- sessions;
54308
- constructor(sessionService) {
54309
- this.sessionService = sessionService;
54310
- this.sessions = toSignal(this.sessionService.getAllSessions(), {
54311
- requireSync: true
54312
- });
54313
- }
54314
- selectedSession = input(null);
54334
+ sessionService = inject(SessionService);
54315
54335
  static \u0275fac = function ViewerSessionsPanelComponent_Factory(__ngFactoryType__) {
54316
- return new (__ngFactoryType__ || _ViewerSessionsPanelComponent)(\u0275\u0275directiveInject(SessionService));
54336
+ return new (__ngFactoryType__ || _ViewerSessionsPanelComponent)();
54317
54337
  };
54318
- static \u0275cmp = /* @__PURE__ */ \u0275\u0275defineComponent({ type: _ViewerSessionsPanelComponent, selectors: [["app-sessions-panel"]], inputs: { selectedSession: [1, "selectedSession"] }, decls: 17, vars: 0, consts: [[1, "flex", "justify-between"], [1, "px-2"], [1, "dropdown", "dropdown-end"], [1, "btn", "btn-xs", "btn-ghost"], [1, "material-icons", "text-base!"], ["tabindex", "0", 1, "dropdown-content", "menu", "menu-sm", "bg-base-100", "z-40", "w-52", "p-2", "shadow-sm"], [1, "panel-content", "inset-shadow-xs", "inset-shadow-black/30"], [1, "overflow-x-auto"], [1, "w-fit", "relative"], ["routerLinkActive", "bg-sky-700", 1, "text-nowrap"], [1, "p-1", "block", "hover:bg-base-content/10", "rounded-sm", 3, "routerLink"]], template: function ViewerSessionsPanelComponent_Template(rf, ctx) {
54338
+ static \u0275cmp = /* @__PURE__ */ \u0275\u0275defineComponent({ type: _ViewerSessionsPanelComponent, selectors: [["app-sessions-panel"]], decls: 17, vars: 0, consts: [[1, "flex", "justify-between"], [1, "px-2"], [1, "dropdown", "dropdown-end"], [1, "btn", "btn-xs", "btn-ghost"], [1, "material-icons", "text-base!"], ["tabindex", "0", 1, "dropdown-content", "menu", "menu-sm", "bg-base-100", "z-40", "w-52", "p-2", "shadow-sm"], [1, "panel-content", "inset-shadow-xs", "inset-shadow-black/30"], [1, "overflow-x-auto"], ["tabindex", "0", "role", "listbox", 1, "w-fit", "relative"], ["routerLinkActive", "bg-sky-700", 1, "text-nowrap"], ["role", "option", 1, "p-1", "block", "hover:bg-base-content/10", "rounded-sm", 3, "routerLink"]], template: function ViewerSessionsPanelComponent_Template(rf, ctx) {
54319
54339
  if (rf & 1) {
54320
54340
  \u0275\u0275elementStart(0, "div", 0)(1, "h1", 1);
54321
54341
  \u0275\u0275text(2, "Sessions");
@@ -54332,7 +54352,7 @@ var ViewerSessionsPanelComponent = class _ViewerSessionsPanelComponent {
54332
54352
  }
54333
54353
  if (rf & 2) {
54334
54354
  \u0275\u0275advance(15);
54335
- \u0275\u0275repeater(ctx.sessions());
54355
+ \u0275\u0275repeater(ctx.sessionService.allSessions());
54336
54356
  }
54337
54357
  }, dependencies: [RouterLink, RouterLinkActive, MatListModule, ScrollingModule], encapsulation: 2, changeDetection: 0 });
54338
54358
  };
@@ -54361,10 +54381,11 @@ var ViewerSessionsPanelComponent = class _ViewerSessionsPanelComponent {
54361
54381
  <div class="panel-content inset-shadow-xs inset-shadow-black/30">
54362
54382
  <!--panel content-->
54363
54383
  <div class="overflow-x-auto">
54364
- <ul class="w-fit relative">
54365
- @for (session of sessions(); track session.key) {
54384
+ <ul class="w-fit relative" tabindex="0" role="listbox">
54385
+ @for (session of sessionService.allSessions(); track session.key) {
54366
54386
  <li class="text-nowrap" routerLinkActive="bg-sky-700">
54367
54387
  <a
54388
+ role="option"
54368
54389
  class="p-1 block hover:bg-base-content/10 rounded-sm"
54369
54390
  [routerLink]="['/viewer', session.key]"
54370
54391
  >
@@ -54376,28 +54397,41 @@ var ViewerSessionsPanelComponent = class _ViewerSessionsPanelComponent {
54376
54397
  </div>
54377
54398
  </div>
54378
54399
  ` }]
54379
- }], () => [{ type: SessionService }], null);
54400
+ }], null, null);
54380
54401
  })();
54381
54402
  (() => {
54382
- (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(ViewerSessionsPanelComponent, { className: "ViewerSessionsPanelComponent", filePath: "src/app/viewer/viewer-left-panel/viewer-sessions-panel.component.ts", lineNumber: 23 });
54403
+ (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(ViewerSessionsPanelComponent, { className: "ViewerSessionsPanelComponent", filePath: "src/app/viewer/viewer-left-panel/viewer-sessions-panel.component.ts", lineNumber: 24 });
54383
54404
  })();
54384
54405
 
54406
+ // src/app/utils/comparators.ts
54407
+ var compareStringArraysOrdered = (prev, curr) => {
54408
+ if (prev.length !== curr.length) {
54409
+ return false;
54410
+ }
54411
+ for (let i = 0; i < prev.length; i++) {
54412
+ if (prev[i] !== curr[i]) {
54413
+ return false;
54414
+ }
54415
+ }
54416
+ return true;
54417
+ };
54418
+
54385
54419
  // src/app/view-settings.model.ts
54386
54420
  var ViewSettings = class _ViewSettings {
54387
54421
  _viewsShown = new BehaviorSubject([]);
54388
- viewsShown$ = this._viewsShown.asObservable().pipe(distinctUntilChanged());
54422
+ viewsShown$ = this._viewsShown.asObservable().pipe(distinctUntilChanged(compareStringArraysOrdered));
54389
54423
  viewsShown = toSignal(this.viewsShown$, { requireSync: true });
54390
54424
  setViewsShown(selected) {
54391
54425
  this._viewsShown.next(selected);
54392
54426
  }
54393
54427
  _keypointsShown = new BehaviorSubject([]);
54394
- keypointsShown$ = this._keypointsShown.asObservable().pipe(distinctUntilChanged());
54428
+ keypointsShown$ = this._keypointsShown.asObservable().pipe(distinctUntilChanged(compareStringArraysOrdered));
54395
54429
  keypointsShown = toSignal(this.keypointsShown$, { requireSync: true });
54396
54430
  setKeypointsShown(selected) {
54397
54431
  this._keypointsShown.next(selected);
54398
54432
  }
54399
54433
  _modelsShown = new BehaviorSubject([]);
54400
- modelsShown$ = this._modelsShown.asObservable().pipe(distinctUntilChanged());
54434
+ modelsShown$ = this._modelsShown.asObservable().pipe(distinctUntilChanged(compareStringArraysOrdered));
54401
54435
  modelsShown = toSignal(this.modelsShown$, { requireSync: true });
54402
54436
  setModelsShown(selected) {
54403
54437
  this._modelsShown.next(selected);
@@ -54630,7 +54664,7 @@ var VideoPlayerControlsComponent = class _VideoPlayerControlsComponent {
54630
54664
  var _c04 = ["videoEl"];
54631
54665
  var _c13 = ["*"];
54632
54666
  var VideoTileComponent = class _VideoTileComponent {
54633
- elementRef;
54667
+ host;
54634
54668
  videoElement = null;
54635
54669
  src = input("");
54636
54670
  // Current time of the video element, for displaying for debug info.
@@ -54638,8 +54672,8 @@ var VideoTileComponent = class _VideoTileComponent {
54638
54672
  // the main state, injected from parent so it can be easily shared across video players
54639
54673
  videoPlayerState = inject(VideoPlayerState);
54640
54674
  contentEnd = new EventEmitter();
54641
- constructor(elementRef) {
54642
- this.elementRef = elementRef;
54675
+ constructor(host) {
54676
+ this.host = host;
54643
54677
  this.videoPlayerState.registerVideoPlayer(this);
54644
54678
  combineLatest([
54645
54679
  this.videoPlayerState.currentTime,
@@ -54648,6 +54682,12 @@ var VideoTileComponent = class _VideoTileComponent {
54648
54682
  this.updateLocalCurrentTime();
54649
54683
  });
54650
54684
  }
54685
+ ngOnInit() {
54686
+ const observer = new ResizeObserver(() => {
54687
+ this.updateScaleFactorSignal();
54688
+ });
54689
+ observer.observe(this.host.nativeElement);
54690
+ }
54651
54691
  ngOnDestroy() {
54652
54692
  this.videoPlayerState.unregisterVideoPlayer(this);
54653
54693
  }
@@ -54665,9 +54705,12 @@ var VideoTileComponent = class _VideoTileComponent {
54665
54705
  width: this.videoElement?.nativeElement.videoWidth ?? 1,
54666
54706
  duration: this.videoElement?.nativeElement.duration ?? 0
54667
54707
  });
54668
- this.scaleFactor.set(this.elementRef.nativeElement.clientWidth / this.videoMetadata.value.width);
54708
+ this.updateScaleFactorSignal();
54669
54709
  this.showProjectedContent.set(true);
54670
54710
  }
54711
+ updateScaleFactorSignal() {
54712
+ this.scaleFactor.set(this.host.nativeElement.clientWidth / this.videoMetadata.value.width);
54713
+ }
54671
54714
  updateLocalCurrentTime() {
54672
54715
  this.localCurrentTime.set(this.videoElement?.nativeElement.currentTime ?? 0);
54673
54716
  }
@@ -54686,7 +54729,7 @@ var VideoTileComponent = class _VideoTileComponent {
54686
54729
  let _t;
54687
54730
  \u0275\u0275queryRefresh(_t = \u0275\u0275loadQuery()) && (ctx.videoElement = _t.first);
54688
54731
  }
54689
- }, inputs: { src: [1, "src"] }, outputs: { contentEnd: "contentEnd" }, ngContentSelectors: _c13, decls: 6, vars: 3, consts: [["videoEl", ""], [1, "relative", "shadow-lg", "shadow-black/20", "bg-base-200"], ["preload", "auto", "muted", "", 1, "w-full", 3, "loadedmetadata", "timeupdate", "ended"], ["type", "video/mp4", 3, "src"]], template: function VideoTileComponent_Template(rf, ctx) {
54732
+ }, inputs: { src: [1, "src"] }, outputs: { contentEnd: "contentEnd" }, ngContentSelectors: _c13, decls: 6, vars: 3, consts: [["videoEl", ""], [1, "relative", "shadow-lg", "shadow-black/20", "bg-base-200"], ["preload", "auto", "muted", "", 3, "loadedmetadata", "timeupdate", "ended"], ["type", "video/mp4", 3, "src"]], template: function VideoTileComponent_Template(rf, ctx) {
54690
54733
  if (rf & 1) {
54691
54734
  const _r1 = \u0275\u0275getCurrentView();
54692
54735
  \u0275\u0275projectionDef();
@@ -54713,12 +54756,12 @@ var VideoTileComponent = class _VideoTileComponent {
54713
54756
  \u0275\u0275advance();
54714
54757
  \u0275\u0275classProp("invisible", !ctx.showProjectedContent());
54715
54758
  }
54716
- }, encapsulation: 2, changeDetection: 0 });
54759
+ }, styles: ["\n\n[_nghost-%COMP%] {\n display: block;\n}\n/*# sourceMappingURL=/static/video-tile.component-XSYKMARQ.css.map */"], changeDetection: 0 });
54717
54760
  };
54718
54761
  (() => {
54719
54762
  (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(VideoTileComponent, [{
54720
54763
  type: Component,
54721
- args: [{ selector: "app-video-tile", imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: '<div class="relative shadow-lg shadow-black/20 bg-base-200">\n <!-- bg is visible during loading state -->\n <!-- wrapper to contain video + keypoints -->\n <video\n #videoEl\n preload="auto"\n (loadedmetadata)="onLoadedMetadata()"\n muted\n class="w-full"\n (timeupdate)="updateLocalCurrentTime()"\n (ended)="onEnd()"\n >\n <source [src]="src()" type="video/mp4" />\n </video>\n <div [class.invisible]="!showProjectedContent()">\n <!-- wrapper to hide content until video inited -->\n <ng-content></ng-content>\n </div>\n</div>\n<!--<p>T: {{ localCurrentTime() }}</p>-->\n' }]
54764
+ args: [{ selector: "app-video-tile", imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: '<style>\r\n :host {\r\n /* Display for angular elements is inline by default.\r\n This is a problem when querying the host height, because it ends up being just 0.\r\n */\r\n display: block;\r\n }\r\n</style>\r\n\r\n<div class="relative shadow-lg shadow-black/20 bg-base-200">\r\n <!-- bg is visible during loading state -->\r\n <!-- wrapper to contain video + keypoints -->\r\n <video\r\n #videoEl\r\n preload="auto"\r\n (loadedmetadata)="onLoadedMetadata()"\r\n muted\r\n (timeupdate)="updateLocalCurrentTime()"\r\n (ended)="onEnd()"\r\n >\r\n <source [src]="src()" type="video/mp4" />\r\n </video>\r\n <div [class.invisible]="!showProjectedContent()">\r\n <!-- wrapper to hide content until video inited -->\r\n <ng-content></ng-content>\r\n </div>\r\n</div>\r\n<!--<p>T: {{ localCurrentTime() }}</p>-->\r\n', styles: ["/* angular:styles/component:css;cca0ce7e24de60b661844ed36e56ee3734a21f5d38966246b6b69d25889a81d9;/home/ksikka/lightning-pose-app/web_ui/src/app/components/video-player/video-tile/video-tile.component.html */\n:host {\n display: block;\n}\n/*# sourceMappingURL=/static/video-tile.component-XSYKMARQ.css.map */\n"] }]
54722
54765
  }], () => [{ type: ElementRef }], { videoElement: [{
54723
54766
  type: ViewChild,
54724
54767
  args: ["videoEl", { static: true }]
@@ -58960,22 +59003,30 @@ var Pair = class _Pair {
58960
59003
  var _forTrack03 = ($index, $item) => $item.videoSrc;
58961
59004
  function ViewerCenterPanelComponent_For_3_Template(rf, ctx) {
58962
59005
  if (rf & 1) {
58963
- \u0275\u0275elementStart(0, "app-video-tile", 2);
58964
- \u0275\u0275element(1, "app-keypoint-container", 3);
59006
+ const _r1 = \u0275\u0275getCurrentView();
59007
+ \u0275\u0275elementStart(0, "div", 2)(1, "div", 3)(2, "h3");
59008
+ \u0275\u0275text(3);
58965
59009
  \u0275\u0275elementEnd();
59010
+ \u0275\u0275elementStart(4, "span", 4);
59011
+ \u0275\u0275listener("click", function ViewerCenterPanelComponent_For_3_Template_span_click_4_listener() {
59012
+ const w_r2 = \u0275\u0275restoreView(_r1).$implicit;
59013
+ const ctx_r2 = \u0275\u0275nextContext();
59014
+ return \u0275\u0275resetView(ctx_r2.onWidgetCloseClick(w_r2));
59015
+ });
59016
+ \u0275\u0275text(5, "close");
59017
+ \u0275\u0275elementEnd()();
59018
+ \u0275\u0275elementStart(6, "app-video-tile", 5);
59019
+ \u0275\u0275element(7, "app-keypoint-container", 6);
59020
+ \u0275\u0275elementEnd()();
58966
59021
  }
58967
59022
  if (rf & 2) {
58968
- const w_r1 = ctx.$implicit;
58969
- \u0275\u0275property("src", w_r1.videoSrc);
59023
+ const w_r2 = ctx.$implicit;
59024
+ \u0275\u0275advance(3);
59025
+ \u0275\u0275textInterpolate(w_r2.id);
59026
+ \u0275\u0275advance(3);
59027
+ \u0275\u0275property("src", w_r2.videoSrc);
58970
59028
  \u0275\u0275advance();
58971
- \u0275\u0275property("labelerMode", false)("keypointModels", w_r1.keypoints());
58972
- }
58973
- }
58974
- function ViewerCenterPanelComponent_ForEmpty_4_Template(rf, ctx) {
58975
- if (rf & 1) {
58976
- \u0275\u0275elementStart(0, "p");
58977
- \u0275\u0275text(1, "Loading...");
58978
- \u0275\u0275elementEnd();
59029
+ \u0275\u0275property("labelerMode", false)("keypointModels", w_r2.keypoints());
58979
59030
  }
58980
59031
  }
58981
59032
  var ViewerCenterPanelComponent = class _ViewerCenterPanelComponent {
@@ -58999,26 +59050,32 @@ var ViewerCenterPanelComponent = class _ViewerCenterPanelComponent {
58999
59050
  const sessionKey = this.sessionKey();
59000
59051
  if (!sessionKey)
59001
59052
  return;
59002
- this.widgetModels.set(this.projectInfoService.allViews().flatMap((view) => {
59003
- const keypointModels = this.projectInfoService.allModels().flatMap((modelKey) => {
59004
- const predictions = this.predictions.get(new Pair(view, modelKey).toMapKey());
59005
- if (predictions) {
59006
- return this.projectInfoService.allKeypoints().map((k) => this.buildKeypoint(k, predictions, modelKey));
59007
- } else {
59008
- return [];
59009
- }
59010
- });
59011
- const widget = this.buildWidget(sessionKey, view, keypointModels);
59012
- return widget;
59013
- }));
59053
+ const session = this.sessionService.allSessions().find((session2) => session2.key === sessionKey);
59054
+ if (!session)
59055
+ return;
59056
+ this.widgetModels.set(
59057
+ //this.projectInfoService.allViews().flatMap((view) => {
59058
+ session.views.flatMap((sessionView) => {
59059
+ const keypointModels = this.projectInfoService.allModels().flatMap((modelKey) => {
59060
+ const predictions = this.predictions.get(new Pair(sessionView.viewName, modelKey).toMapKey());
59061
+ if (predictions) {
59062
+ return this.projectInfoService.allKeypoints().map((k) => this.buildKeypoint(k, predictions, modelKey));
59063
+ } else {
59064
+ return [];
59065
+ }
59066
+ });
59067
+ const widget = this.buildWidget(sessionView, keypointModels);
59068
+ return widget;
59069
+ })
59070
+ );
59014
59071
  }
59015
- buildWidget(sessionKey, view, allKeypoints) {
59072
+ buildWidget(sessionView, allKeypoints) {
59016
59073
  const filteredKeypoints = computed(() => {
59017
59074
  return allKeypoints.filter((k) => this.viewSettings.modelsShown().includes(k.modelKey) && this.viewSettings.keypointsShown().includes(k.name));
59018
59075
  });
59019
59076
  return {
59020
- id: view,
59021
- videoSrc: this.getVideoSrc(sessionKey, view),
59077
+ id: sessionView.viewName,
59078
+ videoSrc: this.getVideoSrc(sessionView),
59022
59079
  keypoints: filteredKeypoints
59023
59080
  };
59024
59081
  }
@@ -59055,8 +59112,8 @@ var ViewerCenterPanelComponent = class _ViewerCenterPanelComponent {
59055
59112
  const dataDir = this.projectInfoService.projectInfo?.data_dir;
59056
59113
  return dataDir + "/" + sessionKey.replace(/\*/g, view) + ".mp4";
59057
59114
  }
59058
- getVideoSrc(sessionKey, view) {
59059
- return "/app/v0/files/" + this.getVideoPath(sessionKey, view);
59115
+ getVideoSrc(sessionView) {
59116
+ return "/app/v0/files/" + sessionView.videoPath;
59060
59117
  }
59061
59118
  async loadSession(sessionKey) {
59062
59119
  this.sessionKey.set(sessionKey);
@@ -59071,7 +59128,7 @@ var ViewerCenterPanelComponent = class _ViewerCenterPanelComponent {
59071
59128
  this.loadPredictionFiles()
59072
59129
  ]);
59073
59130
  this.loadingService.isLoading.set(false);
59074
- this.buildWidgetModels();
59131
+ await this.buildWidgetModels();
59075
59132
  }
59076
59133
  async loadPredictionFiles() {
59077
59134
  const sessionKey = this.sessionKey();
@@ -59105,15 +59162,19 @@ var ViewerCenterPanelComponent = class _ViewerCenterPanelComponent {
59105
59162
  this.videoPlayerState.duration.set(data.duration);
59106
59163
  this.videoPlayerState.fps.set(data.fps);
59107
59164
  }
59165
+ onWidgetCloseClick(w) {
59166
+ const nextViewsShown = this.viewSettings.viewsShown().filter((v) => v != w.id);
59167
+ this.viewSettings.setViewsShown(nextViewsShown);
59168
+ }
59108
59169
  static \u0275fac = function ViewerCenterPanelComponent_Factory(__ngFactoryType__) {
59109
59170
  return new (__ngFactoryType__ || _ViewerCenterPanelComponent)();
59110
59171
  };
59111
- static \u0275cmp = /* @__PURE__ */ \u0275\u0275defineComponent({ type: _ViewerCenterPanelComponent, selectors: [["app-viewer-center-panel"]], inputs: { viewSettings: "viewSettings" }, decls: 6, vars: 1, consts: [[1, "flex", "flex-col", "h-full"], [1, "video-tile", "w-full", "grow", "flex", "flex-wrap", "overflow-y-auto", "gap-4", "*:grow", "*:shrink-0", "*:max-w-xs", "justify-start", "content-start", "p-4"], [3, "src"], [3, "labelerMode", "keypointModels"]], template: function ViewerCenterPanelComponent_Template(rf, ctx) {
59172
+ static \u0275cmp = /* @__PURE__ */ \u0275\u0275defineComponent({ type: _ViewerCenterPanelComponent, selectors: [["app-viewer-center-panel"]], inputs: { viewSettings: "viewSettings" }, decls: 5, vars: 0, consts: [[1, "flex", "flex-col", "h-full"], [1, "w-full", "grow", "flex", "flex-wrap", "overflow-y-auto", "gap-4", "*:max-w-xs", "justify-start", "items-start", "content-start", "p-4"], [1, "panel", "!border-none"], [1, "px-2", "py-1", "text-sm", "flex", "justify-between"], [1, "btn", "btn-circle", "btn-xs", "material-icons", "!text-sm", 3, "click"], [1, "w-auto", 3, "src"], [3, "labelerMode", "keypointModels"]], template: function ViewerCenterPanelComponent_Template(rf, ctx) {
59112
59173
  if (rf & 1) {
59113
59174
  \u0275\u0275elementStart(0, "div", 0)(1, "div", 1);
59114
- \u0275\u0275repeaterCreate(2, ViewerCenterPanelComponent_For_3_Template, 2, 3, "app-video-tile", 2, _forTrack03, false, ViewerCenterPanelComponent_ForEmpty_4_Template, 2, 0, "p");
59175
+ \u0275\u0275repeaterCreate(2, ViewerCenterPanelComponent_For_3_Template, 8, 4, "div", 2, _forTrack03);
59115
59176
  \u0275\u0275elementEnd();
59116
- \u0275\u0275element(5, "app-video-player-controls");
59177
+ \u0275\u0275element(4, "app-video-player-controls");
59117
59178
  \u0275\u0275elementEnd();
59118
59179
  }
59119
59180
  if (rf & 2) {
@@ -59133,13 +59194,13 @@ var ViewerCenterPanelComponent = class _ViewerCenterPanelComponent {
59133
59194
  VideoPlayerControlsComponent,
59134
59195
  VideoTileComponent,
59135
59196
  KeypointContainerComponent
59136
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: '<div class="flex flex-col h-full">\n <!-- Video content area -->\n <div\n class="video-tile w-full grow flex flex-wrap overflow-y-auto gap-4 *:grow *:shrink-0 *:max-w-xs justify-start content-start p-4"\n >\n <!-- `track videosrc` makes the widget component get\n destroyed and recreated if videoSrc changes. Reactively\n changing videoSrc of an existing video does not work unfortunately. -->\n @for (w of filteredWidgetModels(); track w.videoSrc) {\n <!--@if (viewSettings.viewsShown().indexOf(w.id) >= 0) {-->\n <app-video-tile [src]="w.videoSrc">\n <app-keypoint-container\n [labelerMode]="false"\n [keypointModels]="w.keypoints()"\n ></app-keypoint-container>\n </app-video-tile>\n } @empty {\n <p>Loading...</p>\n }\n </div>\n\n <app-video-player-controls></app-video-player-controls>\n</div>\n' }]
59197
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: '<div class="flex flex-col h-full">\r\n <!-- Video content area -->\r\n <div\r\n class="w-full grow flex flex-wrap overflow-y-auto gap-4 *:max-w-xs justify-start items-start content-start p-4"\r\n >\r\n <!-- `track videosrc` makes the widget component get\r\n destroyed and recreated if videoSrc changes. Reactively\r\n changing videoSrc of an existing video does not work unfortunately. -->\r\n @for (w of filteredWidgetModels(); track w.videoSrc) {\r\n <div class="panel !border-none">\r\n <div class="px-2 py-1 text-sm flex justify-between">\r\n <h3>{{ w.id }}</h3>\r\n <span\r\n class="btn btn-circle btn-xs material-icons !text-sm"\r\n (click)="onWidgetCloseClick(w)"\r\n >close</span\r\n >\r\n </div>\r\n <app-video-tile [src]="w.videoSrc" class="w-auto">\r\n <app-keypoint-container\r\n [labelerMode]="false"\r\n [keypointModels]="w.keypoints()"\r\n ></app-keypoint-container>\r\n </app-video-tile>\r\n </div>\r\n }\r\n </div>\r\n\r\n <app-video-player-controls></app-video-player-controls>\r\n</div>\r\n' }]
59137
59198
  }], null, { viewSettings: [{
59138
59199
  type: Input
59139
59200
  }] });
59140
59201
  })();
59141
59202
  (() => {
59142
- (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(ViewerCenterPanelComponent, { className: "ViewerCenterPanelComponent", filePath: "src/app/viewer/viewer-center-panel/viewer-center-panel.component.ts", lineNumber: 36 });
59203
+ (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(ViewerCenterPanelComponent, { className: "ViewerCenterPanelComponent", filePath: "src/app/viewer/viewer-center-panel/viewer-center-panel.component.ts", lineNumber: 38 });
59143
59204
  })();
59144
59205
 
59145
59206
  // src/app/loading-bar/loading-bar.component.ts
@@ -59182,19 +59243,14 @@ var LoadingBarComponent = class _LoadingBarComponent {
59182
59243
 
59183
59244
  // src/app/viewer/viewer-page/viewer-page.component.ts
59184
59245
  var _c05 = (a0) => [a0];
59185
- function ViewerPageComponent_Conditional_0_Template(rf, ctx) {
59186
- if (rf & 1) {
59187
- \u0275\u0275text(0, " Loading...\n");
59188
- }
59189
- }
59190
- function ViewerPageComponent_Conditional_1_For_14_Template(rf, ctx) {
59246
+ function ViewerPageComponent_Conditional_0_For_14_Template(rf, ctx) {
59191
59247
  if (rf & 1) {
59192
59248
  const _r1 = \u0275\u0275getCurrentView();
59193
- \u0275\u0275elementStart(0, "li", 11)(1, "span");
59249
+ \u0275\u0275elementStart(0, "li", 9)(1, "span");
59194
59250
  \u0275\u0275text(2);
59195
59251
  \u0275\u0275elementEnd();
59196
- \u0275\u0275elementStart(3, "input", 14);
59197
- \u0275\u0275listener("change", function ViewerPageComponent_Conditional_1_For_14_Template_input_change_3_listener($event) {
59252
+ \u0275\u0275elementStart(3, "input", 12);
59253
+ \u0275\u0275listener("change", function ViewerPageComponent_Conditional_0_For_14_Template_input_change_3_listener($event) {
59198
59254
  const cam_r2 = \u0275\u0275restoreView(_r1).$implicit;
59199
59255
  const ctx_r2 = \u0275\u0275nextContext(2);
59200
59256
  return \u0275\u0275resetView(ctx_r2.onViewCheckboxChange($event, cam_r2));
@@ -59210,14 +59266,14 @@ function ViewerPageComponent_Conditional_1_For_14_Template(rf, ctx) {
59210
59266
  \u0275\u0275property("checked", ctx_r2.viewSelectionModel.isSelected(cam_r2));
59211
59267
  }
59212
59268
  }
59213
- function ViewerPageComponent_Conditional_1_For_22_Template(rf, ctx) {
59269
+ function ViewerPageComponent_Conditional_0_For_22_Template(rf, ctx) {
59214
59270
  if (rf & 1) {
59215
59271
  const _r4 = \u0275\u0275getCurrentView();
59216
- \u0275\u0275elementStart(0, "li", 11)(1, "span");
59272
+ \u0275\u0275elementStart(0, "li", 9)(1, "span");
59217
59273
  \u0275\u0275text(2);
59218
59274
  \u0275\u0275elementEnd();
59219
- \u0275\u0275elementStart(3, "input", 14);
59220
- \u0275\u0275listener("change", function ViewerPageComponent_Conditional_1_For_22_Template_input_change_3_listener($event) {
59275
+ \u0275\u0275elementStart(3, "input", 12);
59276
+ \u0275\u0275listener("change", function ViewerPageComponent_Conditional_0_For_22_Template_input_change_3_listener($event) {
59221
59277
  const kp_r5 = \u0275\u0275restoreView(_r4).$implicit;
59222
59278
  const ctx_r2 = \u0275\u0275nextContext(2);
59223
59279
  return \u0275\u0275resetView(ctx_r2.onKeypointCheckboxChange($event, kp_r5));
@@ -59233,9 +59289,9 @@ function ViewerPageComponent_Conditional_1_For_22_Template(rf, ctx) {
59233
59289
  \u0275\u0275property("checked", ctx_r2.keypointSelectionModel.isSelected(kp_r5));
59234
59290
  }
59235
59291
  }
59236
- function ViewerPageComponent_Conditional_1_For_31_For_5_Template(rf, ctx) {
59292
+ function ViewerPageComponent_Conditional_0_For_31_For_5_Template(rf, ctx) {
59237
59293
  if (rf & 1) {
59238
- \u0275\u0275elementStart(0, "option", 16);
59294
+ \u0275\u0275elementStart(0, "option", 14);
59239
59295
  \u0275\u0275text(1);
59240
59296
  \u0275\u0275elementEnd();
59241
59297
  }
@@ -59247,19 +59303,19 @@ function ViewerPageComponent_Conditional_1_For_31_For_5_Template(rf, ctx) {
59247
59303
  \u0275\u0275textInterpolate1(" ", dropdownItem_r8, " ");
59248
59304
  }
59249
59305
  }
59250
- function ViewerPageComponent_Conditional_1_For_31_Template(rf, ctx) {
59306
+ function ViewerPageComponent_Conditional_0_For_31_Template(rf, ctx) {
59251
59307
  if (rf & 1) {
59252
59308
  const _r6 = \u0275\u0275getCurrentView();
59253
59309
  \u0275\u0275declareLet(0);
59254
- \u0275\u0275elementStart(1, "li", 13)(2, "select", 15);
59255
- \u0275\u0275listener("change", function ViewerPageComponent_Conditional_1_For_31_Template_select_change_2_listener($event) {
59310
+ \u0275\u0275elementStart(1, "li", 11)(2, "select", 13);
59311
+ \u0275\u0275listener("change", function ViewerPageComponent_Conditional_0_For_31_Template_select_change_2_listener($event) {
59256
59312
  \u0275\u0275restoreView(_r6);
59257
59313
  const modelIndex_r7 = \u0275\u0275readContextLet(0);
59258
59314
  const ctx_r2 = \u0275\u0275nextContext(2);
59259
59315
  return \u0275\u0275resetView(ctx_r2.onModelDropdownItemClick(modelIndex_r7, $event));
59260
59316
  });
59261
59317
  \u0275\u0275declareLet(3);
59262
- \u0275\u0275repeaterCreate(4, ViewerPageComponent_Conditional_1_For_31_For_5_Template, 2, 2, "option", 16, \u0275\u0275repeaterTrackByIdentity);
59318
+ \u0275\u0275repeaterCreate(4, ViewerPageComponent_Conditional_0_For_31_For_5_Template, 2, 2, "option", 14, \u0275\u0275repeaterTrackByIdentity);
59263
59319
  \u0275\u0275elementEnd()();
59264
59320
  }
59265
59321
  if (rf & 2) {
@@ -59273,45 +59329,43 @@ function ViewerPageComponent_Conditional_1_For_31_Template(rf, ctx) {
59273
59329
  \u0275\u0275repeater(options_r12);
59274
59330
  }
59275
59331
  }
59276
- function ViewerPageComponent_Conditional_1_Template(rf, ctx) {
59332
+ function ViewerPageComponent_Conditional_0_Template(rf, ctx) {
59277
59333
  if (rf & 1) {
59278
59334
  \u0275\u0275elementStart(0, "div", 0)(1, "div", 1)(2, "div", 2);
59279
- \u0275\u0275element(3, "app-sessions-panel", 3);
59335
+ \u0275\u0275element(3, "app-sessions-panel");
59280
59336
  \u0275\u0275elementEnd();
59281
- \u0275\u0275element(4, "app-loading-bar", 4);
59337
+ \u0275\u0275element(4, "app-loading-bar", 3);
59282
59338
  \u0275\u0275elementEnd();
59283
- \u0275\u0275element(5, "app-viewer-center-panel", 5);
59284
- \u0275\u0275elementStart(6, "div", 6)(7, "div", 7)(8, "div", 8)(9, "h1", 9);
59339
+ \u0275\u0275element(5, "app-viewer-center-panel", 4);
59340
+ \u0275\u0275elementStart(6, "div", 5)(7, "div", 6)(8, "div")(9, "h1", 7);
59285
59341
  \u0275\u0275text(10, "Views");
59286
59342
  \u0275\u0275elementEnd()();
59287
- \u0275\u0275elementStart(11, "div", 10)(12, "ul");
59288
- \u0275\u0275repeaterCreate(13, ViewerPageComponent_Conditional_1_For_14_Template, 4, 2, "li", 11, \u0275\u0275repeaterTrackByIdentity);
59343
+ \u0275\u0275elementStart(11, "div", 8)(12, "ul");
59344
+ \u0275\u0275repeaterCreate(13, ViewerPageComponent_Conditional_0_For_14_Template, 4, 2, "li", 9, \u0275\u0275repeaterTrackByIdentity);
59289
59345
  \u0275\u0275elementEnd()()();
59290
- \u0275\u0275elementStart(15, "div", 7)(16, "div", 8)(17, "h1", 9);
59346
+ \u0275\u0275elementStart(15, "div", 6)(16, "div")(17, "h1", 7);
59291
59347
  \u0275\u0275text(18, "Keypoints");
59292
59348
  \u0275\u0275elementEnd()();
59293
- \u0275\u0275elementStart(19, "div", 10)(20, "ul");
59294
- \u0275\u0275repeaterCreate(21, ViewerPageComponent_Conditional_1_For_22_Template, 4, 2, "li", 11, \u0275\u0275repeaterTrackByIdentity);
59349
+ \u0275\u0275elementStart(19, "div", 8)(20, "ul");
59350
+ \u0275\u0275repeaterCreate(21, ViewerPageComponent_Conditional_0_For_22_Template, 4, 2, "li", 9, \u0275\u0275repeaterTrackByIdentity);
59295
59351
  \u0275\u0275elementEnd()()();
59296
- \u0275\u0275elementStart(23, "div", 7)(24, "div", 8)(25, "h1", 9);
59352
+ \u0275\u0275elementStart(23, "div", 6)(24, "div")(25, "h1", 7);
59297
59353
  \u0275\u0275text(26, "Models");
59298
59354
  \u0275\u0275elementEnd()();
59299
- \u0275\u0275elementStart(27, "div", 12)(28, "ul");
59355
+ \u0275\u0275elementStart(27, "div", 10)(28, "ul");
59300
59356
  \u0275\u0275declareLet(29);
59301
- \u0275\u0275repeaterCreate(30, ViewerPageComponent_Conditional_1_For_31_Template, 6, 7, "li", 13, \u0275\u0275repeaterTrackByIndex);
59357
+ \u0275\u0275repeaterCreate(30, ViewerPageComponent_Conditional_0_For_31_Template, 6, 7, "li", 11, \u0275\u0275repeaterTrackByIndex);
59302
59358
  \u0275\u0275elementEnd()()()()();
59303
59359
  }
59304
59360
  if (rf & 2) {
59305
59361
  const ctx_r2 = \u0275\u0275nextContext();
59306
- \u0275\u0275advance(3);
59307
- \u0275\u0275property("selectedSession", ctx_r2.selectedSession());
59308
- \u0275\u0275advance(2);
59362
+ \u0275\u0275advance(5);
59309
59363
  \u0275\u0275property("viewSettings", ctx_r2.viewSettings);
59310
59364
  \u0275\u0275advance(8);
59311
59365
  \u0275\u0275repeater(ctx_r2.allViews);
59312
59366
  \u0275\u0275advance(8);
59313
59367
  \u0275\u0275repeater(ctx_r2.projectInfoService.allKeypoints());
59314
- const modelSelectors_r13 = ctx_r2.viewSettings.modelsShown().length < 2 ? ctx_r2.viewSettings.modelsShown().concat(\u0275\u0275pureFunction1(2, _c05, ctx_r2.noneOption)) : ctx_r2.viewSettings.modelsShown();
59368
+ const modelSelectors_r13 = ctx_r2.viewSettings.modelsShown().length < 2 ? ctx_r2.viewSettings.modelsShown().concat(\u0275\u0275pureFunction1(1, _c05, ctx_r2.noneOption)) : ctx_r2.viewSettings.modelsShown();
59315
59369
  \u0275\u0275advance(9);
59316
59370
  \u0275\u0275repeater(modelSelectors_r13);
59317
59371
  }
@@ -59344,10 +59398,8 @@ var ViewerPageComponent = class _ViewerPageComponent {
59344
59398
  isIniting = signal(true);
59345
59399
  async ngOnInit() {
59346
59400
  this.loadingService.isLoading.set(true);
59347
- this.loadingService.maxProgress.set(3);
59401
+ this.loadingService.maxProgress.set(2);
59348
59402
  this.loadingService.progress.set(0);
59349
- await this.projectInfoService.loadProjectInfo();
59350
- this.loadingService.progress.update((x) => x + 1);
59351
59403
  const p = this.sessionService.loadPredictionIndex();
59352
59404
  const s = this.sessionService.loadSessions().then(() => {
59353
59405
  this.loadingService.progress.update((x) => x + 1);
@@ -59380,20 +59432,16 @@ var ViewerPageComponent = class _ViewerPageComponent {
59380
59432
  this.viewSelectionModel.changed.asObservable().pipe(takeUntilDestroyed()).subscribe(() => {
59381
59433
  this.viewSettings.setViewsShown(this.viewSelectionModel.selected);
59382
59434
  });
59435
+ this.viewSettings.viewsShown$.pipe(takeUntilDestroyed()).subscribe((viewsShown) => {
59436
+ this.viewSelectionModel.setSelection(...viewsShown);
59437
+ });
59383
59438
  this.keypointSelectionModel.changed.asObservable().pipe(takeUntilDestroyed()).subscribe(() => {
59384
59439
  this.viewSettings.setKeypointsShown(this.keypointSelectionModel.selected);
59385
59440
  });
59441
+ this.viewSettings.keypointsShown$.pipe(takeUntilDestroyed()).subscribe((keypointsShown) => {
59442
+ this.keypointSelectionModel.setSelection(...keypointsShown);
59443
+ });
59386
59444
  }
59387
- selectedSession = computed(() => {
59388
- const sessionKey = this._sessionKey();
59389
- if (!sessionKey) {
59390
- return null;
59391
- } else {
59392
- return {
59393
- key: sessionKey
59394
- };
59395
- }
59396
- });
59397
59445
  onKeypointCheckboxChange(event, keypointName) {
59398
59446
  const target = event.target;
59399
59447
  if (target.checked) {
@@ -59432,18 +59480,18 @@ var ViewerPageComponent = class _ViewerPageComponent {
59432
59480
  if (rf & 2) {
59433
59481
  \u0275\u0275queryAdvance();
59434
59482
  }
59435
- }, inputs: { sessionKey: "sessionKey" }, features: [\u0275\u0275ProvidersFeature([VideoPlayerState, ViewSettings])], decls: 2, vars: 1, consts: [[1, "fixed", "top-0", "left-0", "h-screen", "w-screen", "flex", "flex-row", "items-stretch", "shadow-lg"], [1, "bg-base-300", "w-60", "xl:w-80", "flex", "flex-col", "justify-between"], [1, "p-4"], [3, "selectedSession"], [1, "h-20", "p-4"], [1, "grow", "z-30", 3, "viewSettings"], [1, "shrink-0", "w-40", "xl:w-60", "h-full"], [1, "panel", "m-4"], [1, "panel-header"], [1, "px-2", "mb-1"], [1, "panel-content", "max-h-60", "overflow-y-auto"], [1, "p-1", "flex", "justify-between", "items-center"], [1, "panel-content", "max-h-60", "overflow-y-visible"], [1, "p-1", "w-full"], ["type", "checkbox", 1, "checkbox", "checkbox-xs", 3, "change", "checked"], [1, "select", "select-sm", "w-full", "flex-grow", 3, "change"], [3, "selected"]], template: function ViewerPageComponent_Template(rf, ctx) {
59483
+ }, inputs: { sessionKey: "sessionKey" }, features: [\u0275\u0275ProvidersFeature([VideoPlayerState, ViewSettings])], decls: 1, vars: 1, consts: [[1, "grow", "flex", "flex-row", "items-stretch"], [1, "bg-base-200", "w-60", "xl:w-80", "flex", "flex-col", "justify-between", "shadow-lg"], [1, "p-4"], [1, "h-20", "p-4"], [1, "grow", "z-30", 3, "viewSettings"], [1, "shrink-0", "w-40", "xl:w-60", "h-full"], [1, "panel", "m-4"], [1, "px-2", "mb-1"], [1, "panel-content", "max-h-60", "overflow-y-auto"], [1, "p-1", "flex", "justify-between", "items-center"], [1, "panel-content", "max-h-60", "overflow-y-visible"], [1, "p-1", "w-full"], ["type", "checkbox", 1, "checkbox", "checkbox-xs", 3, "change", "checked"], [1, "select", "select-sm", "w-full", "flex-grow", 3, "change"], [3, "selected"]], template: function ViewerPageComponent_Template(rf, ctx) {
59436
59484
  if (rf & 1) {
59437
- \u0275\u0275template(0, ViewerPageComponent_Conditional_0_Template, 1, 0)(1, ViewerPageComponent_Conditional_1_Template, 32, 4, "div", 0);
59485
+ \u0275\u0275template(0, ViewerPageComponent_Conditional_0_Template, 32, 3, "div", 0);
59438
59486
  }
59439
59487
  if (rf & 2) {
59440
- \u0275\u0275conditional(ctx.isIniting() ? 0 : 1);
59488
+ \u0275\u0275conditional(!ctx.isIniting() ? 0 : -1);
59441
59489
  }
59442
59490
  }, dependencies: [
59443
59491
  ViewerSessionsPanelComponent,
59444
59492
  ViewerCenterPanelComponent,
59445
59493
  LoadingBarComponent
59446
- ], encapsulation: 2, changeDetection: 0 });
59494
+ ], styles: ["\n\n[_nghost-%COMP%] {\n flex-grow: 1;\n display: flex;\n}\n/*# sourceMappingURL=/static/viewer-page.component-MRTIUFL2.css.map */"], changeDetection: 0 });
59447
59495
  };
59448
59496
  (() => {
59449
59497
  (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ViewerPageComponent, [{
@@ -59452,116 +59500,122 @@ var ViewerPageComponent = class _ViewerPageComponent {
59452
59500
  ViewerSessionsPanelComponent,
59453
59501
  ViewerCenterPanelComponent,
59454
59502
  LoadingBarComponent
59455
- ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [VideoPlayerState, ViewSettings], template: `@if (isIniting()) {
59456
- Loading...
59457
- } @else {
59458
- <div
59459
- class="fixed top-0 left-0 h-screen w-screen flex flex-row items-stretch shadow-lg"
59460
- >
59461
- <!-- Left pane -->
59462
- <div class="bg-base-300 w-60 xl:w-80 flex flex-col justify-between">
59463
- <div class="p-4">
59464
- <app-sessions-panel
59465
- [selectedSession]="selectedSession()"
59466
- ></app-sessions-panel>
59467
- </div>
59468
- <app-loading-bar class="h-20 p-4"></app-loading-bar>
59469
- </div>
59470
-
59471
- <!-- Center content -->
59472
- <app-viewer-center-panel
59473
- class="grow z-30"
59474
- [viewSettings]="viewSettings"
59475
- ></app-viewer-center-panel>
59476
-
59477
- <!-- Right pane -->
59478
- <div class="shrink-0 w-40 xl:w-60 h-full">
59479
- <div class="panel m-4">
59480
- <div class="panel-header">
59481
- <h1 class="px-2 mb-1">Views</h1>
59482
- </div>
59483
- <div class="panel-content max-h-60 overflow-y-auto">
59484
- <ul>
59485
- @for (cam of allViews; track cam) {
59486
- <li class="p-1 flex justify-between items-center">
59487
- <span>{{ cam }}</span>
59488
- <input
59489
- type="checkbox"
59490
- [checked]="viewSelectionModel.isSelected(cam)"
59491
- class="checkbox checkbox-xs"
59492
- (change)="onViewCheckboxChange($event, cam)"
59493
- />
59494
- </li>
59495
- }
59496
- </ul>
59497
- </div>
59498
- </div>
59499
-
59500
- <div class="panel m-4">
59501
- <div class="panel-header">
59502
- <h1 class="px-2 mb-1">Keypoints</h1>
59503
- </div>
59504
- <div class="panel-content max-h-60 overflow-y-auto">
59505
- <ul>
59506
- @for (kp of projectInfoService.allKeypoints(); track kp) {
59507
- <li class="p-1 flex justify-between items-center">
59508
- <span>{{ kp }}</span>
59509
- <input
59510
- type="checkbox"
59511
- [checked]="keypointSelectionModel.isSelected(kp)"
59512
- class="checkbox checkbox-xs"
59513
- (change)="onKeypointCheckboxChange($event, kp)"
59514
- />
59515
- </li>
59516
- }
59517
- </ul>
59518
- </div>
59519
- </div>
59520
-
59521
- <div class="panel m-4">
59522
- <div class="panel-header">
59523
- <h1 class="px-2 mb-1">Models</h1>
59524
- </div>
59525
- <div class="panel-content max-h-60 overflow-y-visible">
59526
- <ul>
59527
- @let modelSelectors =
59528
- viewSettings.modelsShown().length < 2
59529
- ? viewSettings.modelsShown().concat([noneOption])
59530
- : viewSettings.modelsShown();
59531
- <!-- it's important to track index, not model.
59532
- if we put model here, something weird happens when you clear all models and then set a model. -->
59533
- @for (model of modelSelectors; track $index) {
59534
- @let modelIndex = $index;
59535
- <li class="p-1 w-full">
59536
- <select
59537
- class="select select-sm w-full flex-grow"
59538
- [class.border-red-300]="modelIndex === 0"
59539
- [class.border-green-300]="modelIndex === 1"
59540
- (change)="onModelDropdownItemClick(modelIndex, $event)"
59541
- >
59542
- @let options =
59543
- [noneOption].concat(projectInfoService.allModels());
59544
- @for (dropdownItem of options; track dropdownItem) {
59545
- <option [selected]="dropdownItem === model">
59546
- {{ dropdownItem }}
59547
- </option>
59548
- }
59549
- </select>
59550
- </li>
59551
- }
59552
- </ul>
59553
- </div>
59554
- </div>
59555
- </div>
59556
- </div>
59557
- }
59558
- ` }]
59503
+ ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [VideoPlayerState, ViewSettings], template: `<style>\r
59504
+ :host {\r
59505
+ flex-grow: 1;\r
59506
+ display: flex;\r
59507
+ }\r
59508
+ </style>\r
59509
+ \r
59510
+ @if (!isIniting()) {\r
59511
+ <div class="grow flex flex-row items-stretch">\r
59512
+ <!-- Left pane -->\r
59513
+ <div\r
59514
+ class="bg-base-200 w-60 xl:w-80 flex flex-col justify-between shadow-lg"\r
59515
+ >\r
59516
+ <div class="p-4">\r
59517
+ <app-sessions-panel></app-sessions-panel>\r
59518
+ </div>\r
59519
+ <app-loading-bar class="h-20 p-4"></app-loading-bar>\r
59520
+ </div>\r
59521
+ \r
59522
+ <!-- Center content -->\r
59523
+ <app-viewer-center-panel\r
59524
+ class="grow z-30"\r
59525
+ [viewSettings]="viewSettings"\r
59526
+ ></app-viewer-center-panel>\r
59527
+ \r
59528
+ <!-- Right pane -->\r
59529
+ <div class="shrink-0 w-40 xl:w-60 h-full">\r
59530
+ <div class="panel m-4">\r
59531
+ <div>\r
59532
+ <!-- panel header -->\r
59533
+ <h1 class="px-2 mb-1">Views</h1>\r
59534
+ </div>\r
59535
+ <div class="panel-content max-h-60 overflow-y-auto">\r
59536
+ <ul>\r
59537
+ @for (cam of allViews; track cam) {\r
59538
+ <li class="p-1 flex justify-between items-center">\r
59539
+ <span>{{ cam }}</span>\r
59540
+ <input\r
59541
+ type="checkbox"\r
59542
+ [checked]="viewSelectionModel.isSelected(cam)"\r
59543
+ class="checkbox checkbox-xs"\r
59544
+ (change)="onViewCheckboxChange($event, cam)"\r
59545
+ />\r
59546
+ </li>\r
59547
+ }\r
59548
+ </ul>\r
59549
+ </div>\r
59550
+ </div>\r
59551
+ \r
59552
+ <div class="panel m-4">\r
59553
+ <div>\r
59554
+ <!-- panel header -->\r
59555
+ <h1 class="px-2 mb-1">Keypoints</h1>\r
59556
+ </div>\r
59557
+ <div class="panel-content max-h-60 overflow-y-auto">\r
59558
+ <ul>\r
59559
+ @for (kp of projectInfoService.allKeypoints(); track kp) {\r
59560
+ <li class="p-1 flex justify-between items-center">\r
59561
+ <span>{{ kp }}</span>\r
59562
+ <input\r
59563
+ type="checkbox"\r
59564
+ [checked]="keypointSelectionModel.isSelected(kp)"\r
59565
+ class="checkbox checkbox-xs"\r
59566
+ (change)="onKeypointCheckboxChange($event, kp)"\r
59567
+ />\r
59568
+ </li>\r
59569
+ }\r
59570
+ </ul>\r
59571
+ </div>\r
59572
+ </div>\r
59573
+ \r
59574
+ <div class="panel m-4">\r
59575
+ <div>\r
59576
+ <!-- panel header -->\r
59577
+ <h1 class="px-2 mb-1">Models</h1>\r
59578
+ </div>\r
59579
+ <div class="panel-content max-h-60 overflow-y-visible">\r
59580
+ <ul>\r
59581
+ @let modelSelectors =\r
59582
+ viewSettings.modelsShown().length < 2\r
59583
+ ? viewSettings.modelsShown().concat([noneOption])\r
59584
+ : viewSettings.modelsShown();\r
59585
+ <!-- it's important to track index, not model.\r
59586
+ if we put model here, something weird happens when you clear all models and then set a model. -->\r
59587
+ @for (model of modelSelectors; track $index) {\r
59588
+ @let modelIndex = $index;\r
59589
+ <li class="p-1 w-full">\r
59590
+ <select\r
59591
+ class="select select-sm w-full flex-grow"\r
59592
+ [class.border-red-300]="modelIndex === 0"\r
59593
+ [class.border-green-300]="modelIndex === 1"\r
59594
+ (change)="onModelDropdownItemClick(modelIndex, $event)"\r
59595
+ >\r
59596
+ @let options =\r
59597
+ [noneOption].concat(projectInfoService.allModels());\r
59598
+ @for (dropdownItem of options; track dropdownItem) {\r
59599
+ <option [selected]="dropdownItem === model">\r
59600
+ {{ dropdownItem }}\r
59601
+ </option>\r
59602
+ }\r
59603
+ </select>\r
59604
+ </li>\r
59605
+ }\r
59606
+ </ul>\r
59607
+ </div>\r
59608
+ </div>\r
59609
+ </div>\r
59610
+ </div>\r
59611
+ }\r
59612
+ `, styles: ["/* angular:styles/component:css;1fa15f7a0ae1b0259233282b2d49fb361834bf7fd726c58584c186240f9d8f04;/home/ksikka/lightning-pose-app/web_ui/src/app/viewer/viewer-page/viewer-page.component.html */\n:host {\n flex-grow: 1;\n display: flex;\n}\n/*# sourceMappingURL=/static/viewer-page.component-MRTIUFL2.css.map */\n"] }]
59559
59613
  }], () => [], { sessionKey: [{
59560
59614
  type: Input
59561
59615
  }] });
59562
59616
  })();
59563
59617
  (() => {
59564
- (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(ViewerPageComponent, { className: "ViewerPageComponent", filePath: "src/app/viewer/viewer-page/viewer-page.component.ts", lineNumber: 37 });
59618
+ (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(ViewerPageComponent, { className: "ViewerPageComponent", filePath: "src/app/viewer/viewer-page/viewer-page.component.ts", lineNumber: 35 });
59565
59619
  })();
59566
59620
 
59567
59621
  // src/app/labeler/labeler-page.component.ts
@@ -65999,36 +66053,356 @@ var appConfig = {
65999
66053
  ]
66000
66054
  };
66001
66055
 
66056
+ // src/app/project-settings/project-settings.component.ts
66057
+ function ProjectSettingsComponent_Conditional_1_Template(rf, ctx) {
66058
+ if (rf & 1) {
66059
+ \u0275\u0275text(0, " Setup ");
66060
+ }
66061
+ }
66062
+ function ProjectSettingsComponent_Conditional_2_Template(rf, ctx) {
66063
+ if (rf & 1) {
66064
+ \u0275\u0275text(0, " Settings ");
66065
+ }
66066
+ }
66067
+ function ProjectSettingsComponent_Conditional_31_Template(rf, ctx) {
66068
+ if (rf & 1) {
66069
+ const _r1 = \u0275\u0275getCurrentView();
66070
+ \u0275\u0275elementStart(0, "button", 14);
66071
+ \u0275\u0275listener("click", function ProjectSettingsComponent_Conditional_31_Template_button_click_0_listener() {
66072
+ \u0275\u0275restoreView(_r1);
66073
+ const ctx_r1 = \u0275\u0275nextContext();
66074
+ return \u0275\u0275resetView(ctx_r1.done.emit(null));
66075
+ });
66076
+ \u0275\u0275text(1, " Cancel ");
66077
+ \u0275\u0275elementEnd();
66078
+ }
66079
+ }
66080
+ var ProjectSettingsComponent = class _ProjectSettingsComponent {
66081
+ done = output();
66082
+ setupMode = input(false);
66083
+ projectInfoForm;
66084
+ viewsInitialRows = signal(4);
66085
+ projectInfoService = inject(ProjectInfoService);
66086
+ fb = inject(FormBuilder);
66087
+ constructor() {
66088
+ this.projectInfoForm = this.fb.group({
66089
+ dataDir: [""],
66090
+ modelDir: [""],
66091
+ views: [""]
66092
+ });
66093
+ }
66094
+ ngOnInit() {
66095
+ const projectInfo = this.projectInfoService.projectInfo;
66096
+ if (projectInfo) {
66097
+ this.projectInfoForm.patchValue({
66098
+ dataDir: projectInfo.data_dir,
66099
+ modelDir: projectInfo.model_dir,
66100
+ views: projectInfo.views.join("\n")
66101
+ });
66102
+ this.viewsInitialRows.update((x) => Math.max(x, projectInfo.views.length));
66103
+ }
66104
+ }
66105
+ async onSaveClick() {
66106
+ const projectInfo = {};
66107
+ projectInfo.data_dir = this.projectInfoForm.get("dataDir")?.value ?? "";
66108
+ projectInfo.model_dir = this.projectInfoForm.get("modelDir")?.value ?? "";
66109
+ projectInfo.views = this.parseMultilineText(this.projectInfoForm.get("views")?.value ?? "");
66110
+ await this.projectInfoService.setProjectInfo(projectInfo);
66111
+ }
66112
+ parseMultilineText(text) {
66113
+ return text.split("\n").map((x) => x.trim()).filter((x) => Boolean(x));
66114
+ }
66115
+ get cameraViewPlaceholder() {
66116
+ return `view1
66117
+ view2
66118
+ ...`;
66119
+ }
66120
+ static \u0275fac = function ProjectSettingsComponent_Factory(__ngFactoryType__) {
66121
+ return new (__ngFactoryType__ || _ProjectSettingsComponent)();
66122
+ };
66123
+ static \u0275cmp = /* @__PURE__ */ \u0275\u0275defineComponent({ type: _ProjectSettingsComponent, selectors: [["app-project-settings"]], inputs: { setupMode: [1, "setupMode"] }, outputs: { done: "done" }, decls: 34, vars: 6, consts: [[1, "text-xl", "font-bold"], [3, "formGroup"], [1, "fieldset", "my-6"], [1, "fieldset-legend", "text-md"], [1, "input", "w-full"], [1, "material-icons", "!text-sm", "opacity-50"], ["formControlName", "dataDir", "type", "text", "placeholder", "/", 1, "grow"], [1, "label"], [1, "fieldset", "my-4"], ["formControlName", "modelDir", "type", "text", "placeholder", "/", 1, "grow"], ["formControlName", "views", 1, "textarea", "w-full", 3, "rows", "placeholder"], [1, "modal-action"], [1, "btn", "btn-soft", "btn-secondary"], [1, "btn", "btn-soft", "btn-primary", 3, "click", "disabled"], [1, "btn", "btn-soft", "btn-secondary", 3, "click"]], template: function ProjectSettingsComponent_Template(rf, ctx) {
66124
+ if (rf & 1) {
66125
+ \u0275\u0275elementStart(0, "h3", 0);
66126
+ \u0275\u0275template(1, ProjectSettingsComponent_Conditional_1_Template, 1, 0)(2, ProjectSettingsComponent_Conditional_2_Template, 1, 0);
66127
+ \u0275\u0275elementEnd();
66128
+ \u0275\u0275elementStart(3, "form", 1)(4, "fieldset", 2)(5, "label", 3);
66129
+ \u0275\u0275text(6, "Data directory");
66130
+ \u0275\u0275elementEnd();
66131
+ \u0275\u0275elementStart(7, "div", 4)(8, "span", 5);
66132
+ \u0275\u0275text(9, "folder");
66133
+ \u0275\u0275elementEnd();
66134
+ \u0275\u0275element(10, "input", 6);
66135
+ \u0275\u0275elementEnd();
66136
+ \u0275\u0275elementStart(11, "p", 7);
66137
+ \u0275\u0275text(12, "Root of the data directory for the project.");
66138
+ \u0275\u0275elementEnd()();
66139
+ \u0275\u0275elementStart(13, "fieldset", 8)(14, "label", 3);
66140
+ \u0275\u0275text(15, "Model directory");
66141
+ \u0275\u0275elementEnd();
66142
+ \u0275\u0275elementStart(16, "div", 4)(17, "span", 5);
66143
+ \u0275\u0275text(18, "folder");
66144
+ \u0275\u0275elementEnd();
66145
+ \u0275\u0275element(19, "input", 9);
66146
+ \u0275\u0275elementEnd();
66147
+ \u0275\u0275elementStart(20, "p", 7);
66148
+ \u0275\u0275text(21, "Root of the model directory for the project.");
66149
+ \u0275\u0275elementEnd()();
66150
+ \u0275\u0275elementStart(22, "fieldset", 8)(23, "label", 3);
66151
+ \u0275\u0275text(24, "Camera Views");
66152
+ \u0275\u0275elementEnd();
66153
+ \u0275\u0275element(25, "textarea", 10);
66154
+ \u0275\u0275elementStart(26, "p", 7);
66155
+ \u0275\u0275text(27, " Names of views embedded in video filenames.");
66156
+ \u0275\u0275element(28, "br");
66157
+ \u0275\u0275text(29, " Used to group files into sessions. ");
66158
+ \u0275\u0275elementEnd()();
66159
+ \u0275\u0275elementStart(30, "div", 11);
66160
+ \u0275\u0275template(31, ProjectSettingsComponent_Conditional_31_Template, 2, 0, "button", 12);
66161
+ \u0275\u0275elementStart(32, "button", 13);
66162
+ \u0275\u0275listener("click", function ProjectSettingsComponent_Template_button_click_32_listener() {
66163
+ return ctx.onSaveClick();
66164
+ });
66165
+ \u0275\u0275text(33, " Save ");
66166
+ \u0275\u0275elementEnd()()();
66167
+ }
66168
+ if (rf & 2) {
66169
+ \u0275\u0275advance();
66170
+ \u0275\u0275conditional(ctx.setupMode() ? 1 : 2);
66171
+ \u0275\u0275advance(2);
66172
+ \u0275\u0275property("formGroup", ctx.projectInfoForm);
66173
+ \u0275\u0275advance(22);
66174
+ \u0275\u0275property("rows", ctx.viewsInitialRows())("placeholder", ctx.cameraViewPlaceholder);
66175
+ \u0275\u0275advance(6);
66176
+ \u0275\u0275conditional(!ctx.setupMode() ? 31 : -1);
66177
+ \u0275\u0275advance();
66178
+ \u0275\u0275property("disabled", !ctx.projectInfoForm.dirty);
66179
+ }
66180
+ }, dependencies: [ReactiveFormsModule, \u0275NgNoValidate, DefaultValueAccessor, NgControlStatus, NgControlStatusGroup, FormGroupDirective, FormControlName], styles: ["\n\n[_ngcontent-%COMP%]::placeholder {\n color: color-mix(in oklch, currentColor 50%, #0000) !important;\n}\n/*# sourceMappingURL=/static/project-settings.component-5IRK7U7U.css.map */"], changeDetection: 0 });
66181
+ };
66182
+ (() => {
66183
+ (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ProjectSettingsComponent, [{
66184
+ type: Component,
66185
+ args: [{ selector: "app-project-settings", imports: [ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `<style>
66186
+ ::placeholder {
66187
+ /** bring back the default style from daisy. dont know why it's getting overridden. */
66188
+ color: color-mix(in oklch, currentColor 50%, #0000) !important;
66189
+ }
66190
+ </style>
66191
+
66192
+ <h3 class="text-xl font-bold">
66193
+ @if (setupMode()) {
66194
+ Setup
66195
+ } @else {
66196
+ Settings
66197
+ }
66198
+ </h3>
66199
+
66200
+ <form [formGroup]="projectInfoForm">
66201
+ <fieldset class="fieldset my-6">
66202
+ <!-- We like the fieldset / fieldset-legend styles from daisy, but
66203
+ we're certainly not using it as intended. Daisy inputs
66204
+ have no other good way of putting the label on top.
66205
+ TODO fix properly. -->
66206
+ <label class="fieldset-legend text-md">Data directory</label>
66207
+ <div class="input w-full">
66208
+ <span class="material-icons !text-sm opacity-50">folder</span>
66209
+ <input
66210
+ formControlName="dataDir"
66211
+ type="text"
66212
+ class="grow"
66213
+ placeholder="/"
66214
+ />
66215
+ </div>
66216
+ <p class="label">Root of the data directory for the project.</p>
66217
+ </fieldset>
66218
+
66219
+ <fieldset class="fieldset my-4">
66220
+ <label class="fieldset-legend text-md">Model directory</label>
66221
+
66222
+ <div class="input w-full">
66223
+ <span class="material-icons !text-sm opacity-50">folder</span>
66224
+ <input
66225
+ formControlName="modelDir"
66226
+ type="text"
66227
+ class="grow"
66228
+ placeholder="/"
66229
+ />
66230
+ </div>
66231
+ <p class="label">Root of the model directory for the project.</p>
66232
+ </fieldset>
66233
+
66234
+ <fieldset class="fieldset my-4">
66235
+ <label class="fieldset-legend text-md">Camera Views</label>
66236
+
66237
+ <textarea
66238
+ class="textarea w-full"
66239
+ [rows]="viewsInitialRows()"
66240
+ [placeholder]="cameraViewPlaceholder"
66241
+ formControlName="views"
66242
+ ></textarea>
66243
+ <p class="label">
66244
+ Names of views embedded in video filenames.<br />
66245
+ Used to group files into sessions.
66246
+ </p>
66247
+ </fieldset>
66248
+
66249
+ <div class="modal-action">
66250
+ @if (!setupMode()) {
66251
+ <button class="btn btn-soft btn-secondary" (click)="done.emit(null)">
66252
+ Cancel
66253
+ </button>
66254
+ }
66255
+ <button
66256
+ class="btn btn-soft btn-primary"
66257
+ (click)="onSaveClick()"
66258
+ [disabled]="!projectInfoForm.dirty"
66259
+ >
66260
+ Save
66261
+ </button>
66262
+ </div>
66263
+ </form>
66264
+ `, styles: ["/* angular:styles/component:css;5c3484fa86afc1ad339d983c727d8801928706c064dd49a4001f42c8eac3d364;/home/ksikka/lightning-pose-app/web_ui/src/app/project-settings/project-settings.component.html */\n::placeholder {\n color: color-mix(in oklch, currentColor 50%, #0000) !important;\n}\n/*# sourceMappingURL=/static/project-settings.component-5IRK7U7U.css.map */\n"] }]
66265
+ }], () => [], null);
66266
+ })();
66267
+ (() => {
66268
+ (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(ProjectSettingsComponent, { className: "ProjectSettingsComponent", filePath: "src/app/project-settings/project-settings.component.ts", lineNumber: 21 });
66269
+ })();
66270
+
66002
66271
  // src/app/app.component.ts
66272
+ var _c08 = ["settingsDialog"];
66273
+ function AppComponent_Conditional_18_Template(rf, ctx) {
66274
+ if (rf & 1) {
66275
+ \u0275\u0275element(0, "router-outlet");
66276
+ }
66277
+ }
66278
+ function AppComponent_Conditional_19_Template(rf, ctx) {
66279
+ if (rf & 1) {
66280
+ \u0275\u0275element(0, "app-project-settings", 12);
66281
+ }
66282
+ if (rf & 2) {
66283
+ \u0275\u0275property("setupMode", true);
66284
+ }
66285
+ }
66286
+ function AppComponent_Conditional_22_Template(rf, ctx) {
66287
+ if (rf & 1) {
66288
+ const _r2 = \u0275\u0275getCurrentView();
66289
+ \u0275\u0275elementStart(0, "app-project-settings", 14);
66290
+ \u0275\u0275listener("done", function AppComponent_Conditional_22_Template_app_project_settings_done_0_listener() {
66291
+ \u0275\u0275restoreView(_r2);
66292
+ const ctx_r2 = \u0275\u0275nextContext();
66293
+ return \u0275\u0275resetView(ctx_r2.settingsDialogOpen.set(false));
66294
+ });
66295
+ \u0275\u0275elementEnd();
66296
+ \u0275\u0275elementStart(1, "div", 15)(2, "button", 16);
66297
+ \u0275\u0275listener("click", function AppComponent_Conditional_22_Template_button_click_2_listener() {
66298
+ \u0275\u0275restoreView(_r2);
66299
+ const ctx_r2 = \u0275\u0275nextContext();
66300
+ return \u0275\u0275resetView(ctx_r2.settingsDialogOpen.set(false));
66301
+ });
66302
+ \u0275\u0275text(3, "close");
66303
+ \u0275\u0275elementEnd()();
66304
+ }
66305
+ }
66003
66306
  var AppComponent = class _AppComponent {
66307
+ projectInfoService = inject(ProjectInfoService);
66308
+ // Whether the required initial setup has been done.
66309
+ // (Setting data directory, model directory, views).
66310
+ projectInfoRequestCompleted = signal(false);
66311
+ hasBeenSetup = signal(false);
66312
+ settingsDialog = viewChild.required("settingsDialog");
66313
+ settingsDialogOpen = signal(false);
66314
+ async ngOnInit() {
66315
+ await this.projectInfoService.loadProjectInfo();
66316
+ this.hasBeenSetup.set(Boolean(this.projectInfoService.projectInfo));
66317
+ this.projectInfoRequestCompleted.set(true);
66318
+ }
66319
+ constructor() {
66320
+ effect(() => {
66321
+ if (this.settingsDialogOpen()) {
66322
+ this.openSettingsDialog();
66323
+ } else {
66324
+ this.closeSettingsDialog();
66325
+ }
66326
+ });
66327
+ }
66328
+ openSettingsDialog() {
66329
+ const elementRef = this.settingsDialog();
66330
+ elementRef.nativeElement.showModal();
66331
+ }
66332
+ closeSettingsDialog() {
66333
+ const elementRef = this.settingsDialog();
66334
+ elementRef.nativeElement.close();
66335
+ }
66004
66336
  static \u0275fac = function AppComponent_Factory(__ngFactoryType__) {
66005
66337
  return new (__ngFactoryType__ || _AppComponent)();
66006
66338
  };
66007
- static \u0275cmp = /* @__PURE__ */ \u0275\u0275defineComponent({ type: _AppComponent, selectors: [["app-root"]], decls: 11, vars: 0, consts: [[1, "flex"], ["routerLink", "/viewer", "routerLinkActive", "active", "ariaCurrentWhenActive", "page"], ["routerLink", "/labeler", "routerLinkActive", "active", "ariaCurrentWhenActive", "page"]], template: function AppComponent_Template(rf, ctx) {
66339
+ static \u0275cmp = /* @__PURE__ */ \u0275\u0275defineComponent({ type: _AppComponent, selectors: [["app-root"]], viewQuery: function AppComponent_Query(rf, ctx) {
66008
66340
  if (rf & 1) {
66009
- \u0275\u0275elementStart(0, "nav", 0)(1, "span");
66010
- \u0275\u0275text(2, "Lightning Pose");
66011
- \u0275\u0275elementEnd();
66012
- \u0275\u0275elementStart(3, "ul", 0)(4, "li")(5, "a", 1);
66013
- \u0275\u0275text(6, "Viewer");
66341
+ \u0275\u0275viewQuerySignal(ctx.settingsDialog, _c08, 5);
66342
+ }
66343
+ if (rf & 2) {
66344
+ \u0275\u0275queryAdvance();
66345
+ }
66346
+ }, decls: 23, vars: 4, consts: [["settingsDialog", ""], [1, "navbar", "bg-base-200", "shadow-lg"], [1, "navbar-start"], [1, "text-lg", "font-semibold"], [1, "navbar-center"], [1, "menu", "menu-horizontal", "p-0"], ["routerLink", "/viewer", "routerLinkActive", "menu-active", "ariaCurrentWhenActive", "page"], ["routerLink", "/labeler", "routerLinkActive", "menu-active", "ariaCurrentWhenActive", "page"], [1, "navbar-end"], ["tabindex", "0", 1, "btn", "btn-ghost", 3, "click", "keydown.enter"], [1, "material-icons"], [1, "flex-grow", "flex", "min-h-0"], [1, "w-lg", "mt-8", 3, "setupMode"], [1, "modal"], [1, "modal-box", 3, "done"], [1, "modal-backdrop"], [3, "click"]], template: function AppComponent_Template(rf, ctx) {
66347
+ if (rf & 1) {
66348
+ const _r1 = \u0275\u0275getCurrentView();
66349
+ \u0275\u0275elementStart(0, "header")(1, "nav", 1)(2, "div", 2)(3, "span", 3);
66350
+ \u0275\u0275text(4, "Lightning Pose");
66014
66351
  \u0275\u0275elementEnd()();
66015
- \u0275\u0275elementStart(7, "li")(8, "a", 2);
66016
- \u0275\u0275text(9, "Labeler");
66352
+ \u0275\u0275elementStart(5, "div", 4)(6, "ul", 5)(7, "li")(8, "a", 6);
66353
+ \u0275\u0275text(9, "Viewer");
66354
+ \u0275\u0275elementEnd()();
66355
+ \u0275\u0275elementStart(10, "li")(11, "a", 7);
66356
+ \u0275\u0275text(12, "Labeler");
66017
66357
  \u0275\u0275elementEnd()()()();
66018
- \u0275\u0275element(10, "router-outlet");
66358
+ \u0275\u0275elementStart(13, "div", 8)(14, "button", 9);
66359
+ \u0275\u0275listener("click", function AppComponent_Template_button_click_14_listener() {
66360
+ \u0275\u0275restoreView(_r1);
66361
+ return \u0275\u0275resetView(ctx.settingsDialogOpen.set(true));
66362
+ })("keydown.enter", function AppComponent_Template_button_keydown_enter_14_listener() {
66363
+ \u0275\u0275restoreView(_r1);
66364
+ return \u0275\u0275resetView(ctx.settingsDialogOpen.set(true));
66365
+ });
66366
+ \u0275\u0275elementStart(15, "span", 10);
66367
+ \u0275\u0275text(16, "settings");
66368
+ \u0275\u0275elementEnd()()()()();
66369
+ \u0275\u0275elementStart(17, "main", 11);
66370
+ \u0275\u0275template(18, AppComponent_Conditional_18_Template, 1, 0, "router-outlet")(19, AppComponent_Conditional_19_Template, 1, 1, "app-project-settings", 12);
66371
+ \u0275\u0275elementEnd();
66372
+ \u0275\u0275elementStart(20, "dialog", 13, 0);
66373
+ \u0275\u0275template(22, AppComponent_Conditional_22_Template, 4, 0);
66374
+ \u0275\u0275elementEnd();
66375
+ }
66376
+ if (rf & 2) {
66377
+ \u0275\u0275advance(17);
66378
+ \u0275\u0275classProp("justify-center", !ctx.hasBeenSetup());
66379
+ \u0275\u0275advance();
66380
+ \u0275\u0275conditional(ctx.hasBeenSetup() ? 18 : 19);
66381
+ \u0275\u0275advance(4);
66382
+ \u0275\u0275conditional(ctx.settingsDialogOpen() ? 22 : -1);
66019
66383
  }
66020
- }, dependencies: [RouterOutlet, RouterLink, RouterLinkActive], encapsulation: 2, changeDetection: 0 });
66384
+ }, dependencies: [
66385
+ RouterOutlet,
66386
+ RouterLink,
66387
+ RouterLinkActive,
66388
+ ProjectSettingsComponent
66389
+ ], styles: ["\n\n[_nghost-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100vh;\n}\n.navbar[_ngcontent-%COMP%] {\n min-height: 3rem;\n}\n/*# sourceMappingURL=/static/app.component-UHVEDPZR.css.map */"], changeDetection: 0 });
66021
66390
  };
66022
66391
  (() => {
66023
66392
  (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(AppComponent, [{
66024
66393
  type: Component,
66025
- args: [{ selector: "app-root", imports: [RouterOutlet, RouterLink, RouterLinkActive], changeDetection: ChangeDetectionStrategy.OnPush, template: '<nav class="flex">\n <span>Lightning Pose</span>\n <ul class="flex">\n <li>\n <a\n routerLink="/viewer"\n routerLinkActive="active"\n ariaCurrentWhenActive="page"\n >Viewer</a\n >\n </li>\n <li>\n <a\n routerLink="/labeler"\n routerLinkActive="active"\n ariaCurrentWhenActive="page"\n >Labeler</a\n >\n </li>\n </ul>\n</nav>\n<router-outlet />\n' }]
66026
- }], null, null);
66394
+ args: [{ selector: "app-root", imports: [
66395
+ RouterOutlet,
66396
+ RouterLink,
66397
+ RouterLinkActive,
66398
+ ProjectSettingsComponent
66399
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: '<style>\n :host {\n display: flex;\n flex-direction: column;\n height: 100vh;\n }\n .navbar {\n min-height: 3rem; /* reduce from default of 4rem */\n }\n</style>\n<header>\n <nav class="navbar bg-base-200 shadow-lg">\n <div class="navbar-start">\n <span class="text-lg font-semibold">Lightning Pose</span>\n </div>\n\n <div class="navbar-center">\n <ul class="menu menu-horizontal p-0">\n <li>\n <a\n routerLink="/viewer"\n routerLinkActive="menu-active"\n ariaCurrentWhenActive="page"\n >Viewer</a\n >\n </li>\n <li>\n <a\n routerLink="/labeler"\n routerLinkActive="menu-active"\n ariaCurrentWhenActive="page"\n >Labeler</a\n >\n </li>\n </ul>\n </div>\n\n <div class="navbar-end">\n <button\n class="btn btn-ghost"\n tabindex="0"\n (click)="settingsDialogOpen.set(true)"\n (keydown.enter)="settingsDialogOpen.set(true)"\n >\n <span class="material-icons">settings</span>\n </button>\n </div>\n </nav>\n</header>\n\n<!-- Set min-height: 0 to allow it to shrink if its content is too large.\n Default min-height for flex items is auto. -->\n<main class="flex-grow flex min-h-0" [class.justify-center]="!hasBeenSetup()">\n @if (hasBeenSetup()) {\n <router-outlet />\n } @else {\n <app-project-settings\n [setupMode]="true"\n class="w-lg mt-8"\n ></app-project-settings>\n }\n</main>\n\n<!-- Settings dialog -->\n<dialog #settingsDialog class="modal">\n @if (settingsDialogOpen()) {\n <app-project-settings\n class="modal-box"\n (done)="settingsDialogOpen.set(false)"\n ></app-project-settings>\n\n <!-- Makes the dialog close when clicked from outside. -->\n <div class="modal-backdrop">\n <button (click)="settingsDialogOpen.set(false)">close</button>\n </div>\n }\n</dialog>\n', styles: ["/* angular:styles/component:css;22d8514f1dd5b50f33b3fb93fdb69668f78eeb349bd672e238e3ac9acfbbda19;/home/ksikka/lightning-pose-app/web_ui/src/app/app.component.html */\n:host {\n display: flex;\n flex-direction: column;\n height: 100vh;\n}\n.navbar {\n min-height: 3rem;\n}\n/*# sourceMappingURL=/static/app.component-UHVEDPZR.css.map */\n"] }]
66400
+ }], () => [], null);
66027
66401
  })();
66028
66402
  (() => {
66029
- (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(AppComponent, { className: "AppComponent", filePath: "src/app/app.component.ts", lineNumber: 11 });
66403
+ (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(AppComponent, { className: "AppComponent", filePath: "src/app/app.component.ts", lineNumber: 27 });
66030
66404
  })();
66031
66405
 
66032
66406
  // src/main.ts
66033
66407
  bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
66034
- //# sourceMappingURL=main-QMBNNDJG.js.map
66408
+ //# sourceMappingURL=main-LJHMLKBL.js.map