lightning-pose-app 1.8.1a1__py3-none-any.whl → 1.8.1a2__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,10 +54397,10 @@ 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
 
54385
54406
  // src/app/view-settings.model.ts
@@ -58971,13 +58992,6 @@ function ViewerCenterPanelComponent_For_3_Template(rf, ctx) {
58971
58992
  \u0275\u0275property("labelerMode", false)("keypointModels", w_r1.keypoints());
58972
58993
  }
58973
58994
  }
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();
58979
- }
58980
- }
58981
58995
  var ViewerCenterPanelComponent = class _ViewerCenterPanelComponent {
58982
58996
  sessionKey = signal(null);
58983
58997
  csvParser = inject(CsvParserService);
@@ -58999,26 +59013,32 @@ var ViewerCenterPanelComponent = class _ViewerCenterPanelComponent {
58999
59013
  const sessionKey = this.sessionKey();
59000
59014
  if (!sessionKey)
59001
59015
  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
- }));
59016
+ const session = this.sessionService.allSessions().find((session2) => session2.key === sessionKey);
59017
+ if (!session)
59018
+ return;
59019
+ this.widgetModels.set(
59020
+ //this.projectInfoService.allViews().flatMap((view) => {
59021
+ session.views.flatMap((sessionView) => {
59022
+ const keypointModels = this.projectInfoService.allModels().flatMap((modelKey) => {
59023
+ const predictions = this.predictions.get(new Pair(sessionView.viewName, modelKey).toMapKey());
59024
+ if (predictions) {
59025
+ return this.projectInfoService.allKeypoints().map((k) => this.buildKeypoint(k, predictions, modelKey));
59026
+ } else {
59027
+ return [];
59028
+ }
59029
+ });
59030
+ const widget = this.buildWidget(sessionView, keypointModels);
59031
+ return widget;
59032
+ })
59033
+ );
59014
59034
  }
59015
- buildWidget(sessionKey, view, allKeypoints) {
59035
+ buildWidget(sessionView, allKeypoints) {
59016
59036
  const filteredKeypoints = computed(() => {
59017
59037
  return allKeypoints.filter((k) => this.viewSettings.modelsShown().includes(k.modelKey) && this.viewSettings.keypointsShown().includes(k.name));
59018
59038
  });
59019
59039
  return {
59020
- id: view,
59021
- videoSrc: this.getVideoSrc(sessionKey, view),
59040
+ id: sessionView.viewName,
59041
+ videoSrc: this.getVideoSrc(sessionView),
59022
59042
  keypoints: filteredKeypoints
59023
59043
  };
59024
59044
  }
@@ -59055,8 +59075,8 @@ var ViewerCenterPanelComponent = class _ViewerCenterPanelComponent {
59055
59075
  const dataDir = this.projectInfoService.projectInfo?.data_dir;
59056
59076
  return dataDir + "/" + sessionKey.replace(/\*/g, view) + ".mp4";
59057
59077
  }
59058
- getVideoSrc(sessionKey, view) {
59059
- return "/app/v0/files/" + this.getVideoPath(sessionKey, view);
59078
+ getVideoSrc(sessionView) {
59079
+ return "/app/v0/files/" + sessionView.videoPath;
59060
59080
  }
59061
59081
  async loadSession(sessionKey) {
59062
59082
  this.sessionKey.set(sessionKey);
@@ -59071,7 +59091,7 @@ var ViewerCenterPanelComponent = class _ViewerCenterPanelComponent {
59071
59091
  this.loadPredictionFiles()
59072
59092
  ]);
59073
59093
  this.loadingService.isLoading.set(false);
59074
- this.buildWidgetModels();
59094
+ await this.buildWidgetModels();
59075
59095
  }
59076
59096
  async loadPredictionFiles() {
59077
59097
  const sessionKey = this.sessionKey();
@@ -59108,12 +59128,12 @@ var ViewerCenterPanelComponent = class _ViewerCenterPanelComponent {
59108
59128
  static \u0275fac = function ViewerCenterPanelComponent_Factory(__ngFactoryType__) {
59109
59129
  return new (__ngFactoryType__ || _ViewerCenterPanelComponent)();
59110
59130
  };
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) {
59131
+ 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, "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) {
59112
59132
  if (rf & 1) {
59113
59133
  \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");
59134
+ \u0275\u0275repeaterCreate(2, ViewerCenterPanelComponent_For_3_Template, 2, 3, "app-video-tile", 2, _forTrack03);
59115
59135
  \u0275\u0275elementEnd();
59116
- \u0275\u0275element(5, "app-video-player-controls");
59136
+ \u0275\u0275element(4, "app-video-player-controls");
59117
59137
  \u0275\u0275elementEnd();
59118
59138
  }
59119
59139
  if (rf & 2) {
@@ -59133,13 +59153,13 @@ var ViewerCenterPanelComponent = class _ViewerCenterPanelComponent {
59133
59153
  VideoPlayerControlsComponent,
59134
59154
  VideoTileComponent,
59135
59155
  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' }]
59156
+ ], 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 }\n </div>\n\n <app-video-player-controls></app-video-player-controls>\n</div>\n' }]
59137
59157
  }], null, { viewSettings: [{
59138
59158
  type: Input
59139
59159
  }] });
59140
59160
  })();
59141
59161
  (() => {
59142
- (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(ViewerCenterPanelComponent, { className: "ViewerCenterPanelComponent", filePath: "src/app/viewer/viewer-center-panel/viewer-center-panel.component.ts", lineNumber: 36 });
59162
+ (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(ViewerCenterPanelComponent, { className: "ViewerCenterPanelComponent", filePath: "src/app/viewer/viewer-center-panel/viewer-center-panel.component.ts", lineNumber: 38 });
59143
59163
  })();
59144
59164
 
59145
59165
  // src/app/loading-bar/loading-bar.component.ts
@@ -59182,19 +59202,14 @@ var LoadingBarComponent = class _LoadingBarComponent {
59182
59202
 
59183
59203
  // src/app/viewer/viewer-page/viewer-page.component.ts
59184
59204
  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) {
59205
+ function ViewerPageComponent_Conditional_0_For_14_Template(rf, ctx) {
59191
59206
  if (rf & 1) {
59192
59207
  const _r1 = \u0275\u0275getCurrentView();
59193
- \u0275\u0275elementStart(0, "li", 11)(1, "span");
59208
+ \u0275\u0275elementStart(0, "li", 10)(1, "span");
59194
59209
  \u0275\u0275text(2);
59195
59210
  \u0275\u0275elementEnd();
59196
- \u0275\u0275elementStart(3, "input", 14);
59197
- \u0275\u0275listener("change", function ViewerPageComponent_Conditional_1_For_14_Template_input_change_3_listener($event) {
59211
+ \u0275\u0275elementStart(3, "input", 13);
59212
+ \u0275\u0275listener("change", function ViewerPageComponent_Conditional_0_For_14_Template_input_change_3_listener($event) {
59198
59213
  const cam_r2 = \u0275\u0275restoreView(_r1).$implicit;
59199
59214
  const ctx_r2 = \u0275\u0275nextContext(2);
59200
59215
  return \u0275\u0275resetView(ctx_r2.onViewCheckboxChange($event, cam_r2));
@@ -59210,14 +59225,14 @@ function ViewerPageComponent_Conditional_1_For_14_Template(rf, ctx) {
59210
59225
  \u0275\u0275property("checked", ctx_r2.viewSelectionModel.isSelected(cam_r2));
59211
59226
  }
59212
59227
  }
59213
- function ViewerPageComponent_Conditional_1_For_22_Template(rf, ctx) {
59228
+ function ViewerPageComponent_Conditional_0_For_22_Template(rf, ctx) {
59214
59229
  if (rf & 1) {
59215
59230
  const _r4 = \u0275\u0275getCurrentView();
59216
- \u0275\u0275elementStart(0, "li", 11)(1, "span");
59231
+ \u0275\u0275elementStart(0, "li", 10)(1, "span");
59217
59232
  \u0275\u0275text(2);
59218
59233
  \u0275\u0275elementEnd();
59219
- \u0275\u0275elementStart(3, "input", 14);
59220
- \u0275\u0275listener("change", function ViewerPageComponent_Conditional_1_For_22_Template_input_change_3_listener($event) {
59234
+ \u0275\u0275elementStart(3, "input", 13);
59235
+ \u0275\u0275listener("change", function ViewerPageComponent_Conditional_0_For_22_Template_input_change_3_listener($event) {
59221
59236
  const kp_r5 = \u0275\u0275restoreView(_r4).$implicit;
59222
59237
  const ctx_r2 = \u0275\u0275nextContext(2);
59223
59238
  return \u0275\u0275resetView(ctx_r2.onKeypointCheckboxChange($event, kp_r5));
@@ -59233,9 +59248,9 @@ function ViewerPageComponent_Conditional_1_For_22_Template(rf, ctx) {
59233
59248
  \u0275\u0275property("checked", ctx_r2.keypointSelectionModel.isSelected(kp_r5));
59234
59249
  }
59235
59250
  }
59236
- function ViewerPageComponent_Conditional_1_For_31_For_5_Template(rf, ctx) {
59251
+ function ViewerPageComponent_Conditional_0_For_31_For_5_Template(rf, ctx) {
59237
59252
  if (rf & 1) {
59238
- \u0275\u0275elementStart(0, "option", 16);
59253
+ \u0275\u0275elementStart(0, "option", 15);
59239
59254
  \u0275\u0275text(1);
59240
59255
  \u0275\u0275elementEnd();
59241
59256
  }
@@ -59247,19 +59262,19 @@ function ViewerPageComponent_Conditional_1_For_31_For_5_Template(rf, ctx) {
59247
59262
  \u0275\u0275textInterpolate1(" ", dropdownItem_r8, " ");
59248
59263
  }
59249
59264
  }
59250
- function ViewerPageComponent_Conditional_1_For_31_Template(rf, ctx) {
59265
+ function ViewerPageComponent_Conditional_0_For_31_Template(rf, ctx) {
59251
59266
  if (rf & 1) {
59252
59267
  const _r6 = \u0275\u0275getCurrentView();
59253
59268
  \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) {
59269
+ \u0275\u0275elementStart(1, "li", 12)(2, "select", 14);
59270
+ \u0275\u0275listener("change", function ViewerPageComponent_Conditional_0_For_31_Template_select_change_2_listener($event) {
59256
59271
  \u0275\u0275restoreView(_r6);
59257
59272
  const modelIndex_r7 = \u0275\u0275readContextLet(0);
59258
59273
  const ctx_r2 = \u0275\u0275nextContext(2);
59259
59274
  return \u0275\u0275resetView(ctx_r2.onModelDropdownItemClick(modelIndex_r7, $event));
59260
59275
  });
59261
59276
  \u0275\u0275declareLet(3);
59262
- \u0275\u0275repeaterCreate(4, ViewerPageComponent_Conditional_1_For_31_For_5_Template, 2, 2, "option", 16, \u0275\u0275repeaterTrackByIdentity);
59277
+ \u0275\u0275repeaterCreate(4, ViewerPageComponent_Conditional_0_For_31_For_5_Template, 2, 2, "option", 15, \u0275\u0275repeaterTrackByIdentity);
59263
59278
  \u0275\u0275elementEnd()();
59264
59279
  }
59265
59280
  if (rf & 2) {
@@ -59273,45 +59288,43 @@ function ViewerPageComponent_Conditional_1_For_31_Template(rf, ctx) {
59273
59288
  \u0275\u0275repeater(options_r12);
59274
59289
  }
59275
59290
  }
59276
- function ViewerPageComponent_Conditional_1_Template(rf, ctx) {
59291
+ function ViewerPageComponent_Conditional_0_Template(rf, ctx) {
59277
59292
  if (rf & 1) {
59278
59293
  \u0275\u0275elementStart(0, "div", 0)(1, "div", 1)(2, "div", 2);
59279
- \u0275\u0275element(3, "app-sessions-panel", 3);
59294
+ \u0275\u0275element(3, "app-sessions-panel");
59280
59295
  \u0275\u0275elementEnd();
59281
- \u0275\u0275element(4, "app-loading-bar", 4);
59296
+ \u0275\u0275element(4, "app-loading-bar", 3);
59282
59297
  \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);
59298
+ \u0275\u0275element(5, "app-viewer-center-panel", 4);
59299
+ \u0275\u0275elementStart(6, "div", 5)(7, "div", 6)(8, "div", 7)(9, "h1", 8);
59285
59300
  \u0275\u0275text(10, "Views");
59286
59301
  \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);
59302
+ \u0275\u0275elementStart(11, "div", 9)(12, "ul");
59303
+ \u0275\u0275repeaterCreate(13, ViewerPageComponent_Conditional_0_For_14_Template, 4, 2, "li", 10, \u0275\u0275repeaterTrackByIdentity);
59289
59304
  \u0275\u0275elementEnd()()();
59290
- \u0275\u0275elementStart(15, "div", 7)(16, "div", 8)(17, "h1", 9);
59305
+ \u0275\u0275elementStart(15, "div", 6)(16, "div", 7)(17, "h1", 8);
59291
59306
  \u0275\u0275text(18, "Keypoints");
59292
59307
  \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);
59308
+ \u0275\u0275elementStart(19, "div", 9)(20, "ul");
59309
+ \u0275\u0275repeaterCreate(21, ViewerPageComponent_Conditional_0_For_22_Template, 4, 2, "li", 10, \u0275\u0275repeaterTrackByIdentity);
59295
59310
  \u0275\u0275elementEnd()()();
59296
- \u0275\u0275elementStart(23, "div", 7)(24, "div", 8)(25, "h1", 9);
59311
+ \u0275\u0275elementStart(23, "div", 6)(24, "div", 7)(25, "h1", 8);
59297
59312
  \u0275\u0275text(26, "Models");
59298
59313
  \u0275\u0275elementEnd()();
59299
- \u0275\u0275elementStart(27, "div", 12)(28, "ul");
59314
+ \u0275\u0275elementStart(27, "div", 11)(28, "ul");
59300
59315
  \u0275\u0275declareLet(29);
59301
- \u0275\u0275repeaterCreate(30, ViewerPageComponent_Conditional_1_For_31_Template, 6, 7, "li", 13, \u0275\u0275repeaterTrackByIndex);
59316
+ \u0275\u0275repeaterCreate(30, ViewerPageComponent_Conditional_0_For_31_Template, 6, 7, "li", 12, \u0275\u0275repeaterTrackByIndex);
59302
59317
  \u0275\u0275elementEnd()()()()();
59303
59318
  }
59304
59319
  if (rf & 2) {
59305
59320
  const ctx_r2 = \u0275\u0275nextContext();
59306
- \u0275\u0275advance(3);
59307
- \u0275\u0275property("selectedSession", ctx_r2.selectedSession());
59308
- \u0275\u0275advance(2);
59321
+ \u0275\u0275advance(5);
59309
59322
  \u0275\u0275property("viewSettings", ctx_r2.viewSettings);
59310
59323
  \u0275\u0275advance(8);
59311
59324
  \u0275\u0275repeater(ctx_r2.allViews);
59312
59325
  \u0275\u0275advance(8);
59313
59326
  \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();
59327
+ 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
59328
  \u0275\u0275advance(9);
59316
59329
  \u0275\u0275repeater(modelSelectors_r13);
59317
59330
  }
@@ -59344,10 +59357,8 @@ var ViewerPageComponent = class _ViewerPageComponent {
59344
59357
  isIniting = signal(true);
59345
59358
  async ngOnInit() {
59346
59359
  this.loadingService.isLoading.set(true);
59347
- this.loadingService.maxProgress.set(3);
59360
+ this.loadingService.maxProgress.set(2);
59348
59361
  this.loadingService.progress.set(0);
59349
- await this.projectInfoService.loadProjectInfo();
59350
- this.loadingService.progress.update((x) => x + 1);
59351
59362
  const p = this.sessionService.loadPredictionIndex();
59352
59363
  const s = this.sessionService.loadSessions().then(() => {
59353
59364
  this.loadingService.progress.update((x) => x + 1);
@@ -59384,16 +59395,6 @@ var ViewerPageComponent = class _ViewerPageComponent {
59384
59395
  this.viewSettings.setKeypointsShown(this.keypointSelectionModel.selected);
59385
59396
  });
59386
59397
  }
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
59398
  onKeypointCheckboxChange(event, keypointName) {
59398
59399
  const target = event.target;
59399
59400
  if (target.checked) {
@@ -59432,18 +59433,18 @@ var ViewerPageComponent = class _ViewerPageComponent {
59432
59433
  if (rf & 2) {
59433
59434
  \u0275\u0275queryAdvance();
59434
59435
  }
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) {
59436
+ }, 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, "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) {
59436
59437
  if (rf & 1) {
59437
- \u0275\u0275template(0, ViewerPageComponent_Conditional_0_Template, 1, 0)(1, ViewerPageComponent_Conditional_1_Template, 32, 4, "div", 0);
59438
+ \u0275\u0275template(0, ViewerPageComponent_Conditional_0_Template, 32, 3, "div", 0);
59438
59439
  }
59439
59440
  if (rf & 2) {
59440
- \u0275\u0275conditional(ctx.isIniting() ? 0 : 1);
59441
+ \u0275\u0275conditional(!ctx.isIniting() ? 0 : -1);
59441
59442
  }
59442
59443
  }, dependencies: [
59443
59444
  ViewerSessionsPanelComponent,
59444
59445
  ViewerCenterPanelComponent,
59445
59446
  LoadingBarComponent
59446
- ], encapsulation: 2, changeDetection: 0 });
59447
+ ], styles: ["\n\n[_nghost-%COMP%] {\n flex-grow: 1;\n display: flex;\n}\n/*# sourceMappingURL=/static/viewer-page.component-KIYG73MW.css.map */"], changeDetection: 0 });
59447
59448
  };
59448
59449
  (() => {
59449
59450
  (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ViewerPageComponent, [{
@@ -59452,18 +59453,21 @@ var ViewerPageComponent = class _ViewerPageComponent {
59452
59453
  ViewerSessionsPanelComponent,
59453
59454
  ViewerCenterPanelComponent,
59454
59455
  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
- >
59456
+ ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [VideoPlayerState, ViewSettings], template: `<style>
59457
+ :host {
59458
+ flex-grow: 1;
59459
+ display: flex;
59460
+ }
59461
+ </style>
59462
+
59463
+ @if (!isIniting()) {
59464
+ <div class="grow flex flex-row items-stretch">
59461
59465
  <!-- Left pane -->
59462
- <div class="bg-base-300 w-60 xl:w-80 flex flex-col justify-between">
59466
+ <div
59467
+ class="bg-base-200 w-60 xl:w-80 flex flex-col justify-between shadow-lg"
59468
+ >
59463
59469
  <div class="p-4">
59464
- <app-sessions-panel
59465
- [selectedSession]="selectedSession()"
59466
- ></app-sessions-panel>
59470
+ <app-sessions-panel></app-sessions-panel>
59467
59471
  </div>
59468
59472
  <app-loading-bar class="h-20 p-4"></app-loading-bar>
59469
59473
  </div>
@@ -59555,7 +59559,7 @@ var ViewerPageComponent = class _ViewerPageComponent {
59555
59559
  </div>
59556
59560
  </div>
59557
59561
  }
59558
- ` }]
59562
+ `, 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-KIYG73MW.css.map */\n"] }]
59559
59563
  }], () => [], { sessionKey: [{
59560
59564
  type: Input
59561
59565
  }] });
@@ -65999,36 +66003,356 @@ var appConfig = {
65999
66003
  ]
66000
66004
  };
66001
66005
 
66006
+ // src/app/project-settings/project-settings.component.ts
66007
+ function ProjectSettingsComponent_Conditional_1_Template(rf, ctx) {
66008
+ if (rf & 1) {
66009
+ \u0275\u0275text(0, " Setup ");
66010
+ }
66011
+ }
66012
+ function ProjectSettingsComponent_Conditional_2_Template(rf, ctx) {
66013
+ if (rf & 1) {
66014
+ \u0275\u0275text(0, " Settings ");
66015
+ }
66016
+ }
66017
+ function ProjectSettingsComponent_Conditional_31_Template(rf, ctx) {
66018
+ if (rf & 1) {
66019
+ const _r1 = \u0275\u0275getCurrentView();
66020
+ \u0275\u0275elementStart(0, "button", 14);
66021
+ \u0275\u0275listener("click", function ProjectSettingsComponent_Conditional_31_Template_button_click_0_listener() {
66022
+ \u0275\u0275restoreView(_r1);
66023
+ const ctx_r1 = \u0275\u0275nextContext();
66024
+ return \u0275\u0275resetView(ctx_r1.done.emit(null));
66025
+ });
66026
+ \u0275\u0275text(1, " Cancel ");
66027
+ \u0275\u0275elementEnd();
66028
+ }
66029
+ }
66030
+ var ProjectSettingsComponent = class _ProjectSettingsComponent {
66031
+ done = output();
66032
+ setupMode = input(false);
66033
+ projectInfoForm;
66034
+ viewsInitialRows = signal(4);
66035
+ projectInfoService = inject(ProjectInfoService);
66036
+ fb = inject(FormBuilder);
66037
+ constructor() {
66038
+ this.projectInfoForm = this.fb.group({
66039
+ dataDir: [""],
66040
+ modelDir: [""],
66041
+ views: [""]
66042
+ });
66043
+ }
66044
+ ngOnInit() {
66045
+ const projectInfo = this.projectInfoService.projectInfo;
66046
+ if (projectInfo) {
66047
+ this.projectInfoForm.patchValue({
66048
+ dataDir: projectInfo.data_dir,
66049
+ modelDir: projectInfo.model_dir,
66050
+ views: projectInfo.views.join("\n")
66051
+ });
66052
+ this.viewsInitialRows.update((x) => Math.max(x, projectInfo.views.length));
66053
+ }
66054
+ }
66055
+ async onSaveClick() {
66056
+ const projectInfo = {};
66057
+ projectInfo.data_dir = this.projectInfoForm.get("dataDir")?.value ?? "";
66058
+ projectInfo.model_dir = this.projectInfoForm.get("modelDir")?.value ?? "";
66059
+ projectInfo.views = this.parseMultilineText(this.projectInfoForm.get("views")?.value ?? "");
66060
+ await this.projectInfoService.setProjectInfo(projectInfo);
66061
+ }
66062
+ parseMultilineText(text) {
66063
+ return text.split("\n").map((x) => x.trim()).filter((x) => Boolean(x));
66064
+ }
66065
+ get cameraViewPlaceholder() {
66066
+ return `view1
66067
+ view2
66068
+ ...`;
66069
+ }
66070
+ static \u0275fac = function ProjectSettingsComponent_Factory(__ngFactoryType__) {
66071
+ return new (__ngFactoryType__ || _ProjectSettingsComponent)();
66072
+ };
66073
+ 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) {
66074
+ if (rf & 1) {
66075
+ \u0275\u0275elementStart(0, "h3", 0);
66076
+ \u0275\u0275template(1, ProjectSettingsComponent_Conditional_1_Template, 1, 0)(2, ProjectSettingsComponent_Conditional_2_Template, 1, 0);
66077
+ \u0275\u0275elementEnd();
66078
+ \u0275\u0275elementStart(3, "form", 1)(4, "fieldset", 2)(5, "label", 3);
66079
+ \u0275\u0275text(6, "Data directory");
66080
+ \u0275\u0275elementEnd();
66081
+ \u0275\u0275elementStart(7, "div", 4)(8, "span", 5);
66082
+ \u0275\u0275text(9, "folder");
66083
+ \u0275\u0275elementEnd();
66084
+ \u0275\u0275element(10, "input", 6);
66085
+ \u0275\u0275elementEnd();
66086
+ \u0275\u0275elementStart(11, "p", 7);
66087
+ \u0275\u0275text(12, "Root of the data directory for the project.");
66088
+ \u0275\u0275elementEnd()();
66089
+ \u0275\u0275elementStart(13, "fieldset", 8)(14, "label", 3);
66090
+ \u0275\u0275text(15, "Model directory");
66091
+ \u0275\u0275elementEnd();
66092
+ \u0275\u0275elementStart(16, "div", 4)(17, "span", 5);
66093
+ \u0275\u0275text(18, "folder");
66094
+ \u0275\u0275elementEnd();
66095
+ \u0275\u0275element(19, "input", 9);
66096
+ \u0275\u0275elementEnd();
66097
+ \u0275\u0275elementStart(20, "p", 7);
66098
+ \u0275\u0275text(21, "Root of the model directory for the project.");
66099
+ \u0275\u0275elementEnd()();
66100
+ \u0275\u0275elementStart(22, "fieldset", 8)(23, "label", 3);
66101
+ \u0275\u0275text(24, "Camera Views");
66102
+ \u0275\u0275elementEnd();
66103
+ \u0275\u0275element(25, "textarea", 10);
66104
+ \u0275\u0275elementStart(26, "p", 7);
66105
+ \u0275\u0275text(27, " Names of views embedded in video filenames.");
66106
+ \u0275\u0275element(28, "br");
66107
+ \u0275\u0275text(29, " Used to group files into sessions. ");
66108
+ \u0275\u0275elementEnd()();
66109
+ \u0275\u0275elementStart(30, "div", 11);
66110
+ \u0275\u0275template(31, ProjectSettingsComponent_Conditional_31_Template, 2, 0, "button", 12);
66111
+ \u0275\u0275elementStart(32, "button", 13);
66112
+ \u0275\u0275listener("click", function ProjectSettingsComponent_Template_button_click_32_listener() {
66113
+ return ctx.onSaveClick();
66114
+ });
66115
+ \u0275\u0275text(33, " Save ");
66116
+ \u0275\u0275elementEnd()()();
66117
+ }
66118
+ if (rf & 2) {
66119
+ \u0275\u0275advance();
66120
+ \u0275\u0275conditional(ctx.setupMode() ? 1 : 2);
66121
+ \u0275\u0275advance(2);
66122
+ \u0275\u0275property("formGroup", ctx.projectInfoForm);
66123
+ \u0275\u0275advance(22);
66124
+ \u0275\u0275property("rows", ctx.viewsInitialRows())("placeholder", ctx.cameraViewPlaceholder);
66125
+ \u0275\u0275advance(6);
66126
+ \u0275\u0275conditional(!ctx.setupMode() ? 31 : -1);
66127
+ \u0275\u0275advance();
66128
+ \u0275\u0275property("disabled", !ctx.projectInfoForm.dirty);
66129
+ }
66130
+ }, 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-BXKZMYM3.css.map */"], changeDetection: 0 });
66131
+ };
66132
+ (() => {
66133
+ (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ProjectSettingsComponent, [{
66134
+ type: Component,
66135
+ args: [{ selector: "app-project-settings", imports: [ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `<style>
66136
+ ::placeholder {
66137
+ /** bring back the default style from daisy. dont know why it's getting overridden. */
66138
+ color: color-mix(in oklch, currentColor 50%, #0000) !important;
66139
+ }
66140
+ </style>
66141
+
66142
+ <h3 class="text-xl font-bold">
66143
+ @if (setupMode()) {
66144
+ Setup
66145
+ } @else {
66146
+ Settings
66147
+ }
66148
+ </h3>
66149
+
66150
+ <form [formGroup]="projectInfoForm">
66151
+ <fieldset class="fieldset my-6">
66152
+ <!-- We like the fieldset / fieldset-legend styles from daisy, but
66153
+ we're certainly not using it as intended. Daisy inputs
66154
+ have no other good way of putting the label on top.
66155
+ TODO fix properly. -->
66156
+ <label class="fieldset-legend text-md">Data directory</label>
66157
+ <div class="input w-full">
66158
+ <span class="material-icons !text-sm opacity-50">folder</span>
66159
+ <input
66160
+ formControlName="dataDir"
66161
+ type="text"
66162
+ class="grow"
66163
+ placeholder="/"
66164
+ />
66165
+ </div>
66166
+ <p class="label">Root of the data directory for the project.</p>
66167
+ </fieldset>
66168
+
66169
+ <fieldset class="fieldset my-4">
66170
+ <label class="fieldset-legend text-md">Model directory</label>
66171
+
66172
+ <div class="input w-full">
66173
+ <span class="material-icons !text-sm opacity-50">folder</span>
66174
+ <input
66175
+ formControlName="modelDir"
66176
+ type="text"
66177
+ class="grow"
66178
+ placeholder="/"
66179
+ />
66180
+ </div>
66181
+ <p class="label">Root of the model directory for the project.</p>
66182
+ </fieldset>
66183
+
66184
+ <fieldset class="fieldset my-4">
66185
+ <label class="fieldset-legend text-md">Camera Views</label>
66186
+
66187
+ <textarea
66188
+ class="textarea w-full"
66189
+ [rows]="viewsInitialRows()"
66190
+ [placeholder]="cameraViewPlaceholder"
66191
+ formControlName="views"
66192
+ ></textarea>
66193
+ <p class="label">
66194
+ Names of views embedded in video filenames.<br />
66195
+ Used to group files into sessions.
66196
+ </p>
66197
+ </fieldset>
66198
+
66199
+ <div class="modal-action">
66200
+ @if (!setupMode()) {
66201
+ <button class="btn btn-soft btn-secondary" (click)="done.emit(null)">
66202
+ Cancel
66203
+ </button>
66204
+ }
66205
+ <button
66206
+ class="btn btn-soft btn-primary"
66207
+ (click)="onSaveClick()"
66208
+ [disabled]="!projectInfoForm.dirty"
66209
+ >
66210
+ Save
66211
+ </button>
66212
+ </div>
66213
+ </form>
66214
+ `, 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-BXKZMYM3.css.map */\n"] }]
66215
+ }], () => [], null);
66216
+ })();
66217
+ (() => {
66218
+ (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(ProjectSettingsComponent, { className: "ProjectSettingsComponent", filePath: "src/app/project-settings/project-settings.component.ts", lineNumber: 21 });
66219
+ })();
66220
+
66002
66221
  // src/app/app.component.ts
66222
+ var _c08 = ["settingsDialog"];
66223
+ function AppComponent_Conditional_18_Template(rf, ctx) {
66224
+ if (rf & 1) {
66225
+ \u0275\u0275element(0, "router-outlet");
66226
+ }
66227
+ }
66228
+ function AppComponent_Conditional_19_Template(rf, ctx) {
66229
+ if (rf & 1) {
66230
+ \u0275\u0275element(0, "app-project-settings", 12);
66231
+ }
66232
+ if (rf & 2) {
66233
+ \u0275\u0275property("setupMode", true);
66234
+ }
66235
+ }
66236
+ function AppComponent_Conditional_22_Template(rf, ctx) {
66237
+ if (rf & 1) {
66238
+ const _r2 = \u0275\u0275getCurrentView();
66239
+ \u0275\u0275elementStart(0, "app-project-settings", 14);
66240
+ \u0275\u0275listener("done", function AppComponent_Conditional_22_Template_app_project_settings_done_0_listener() {
66241
+ \u0275\u0275restoreView(_r2);
66242
+ const ctx_r2 = \u0275\u0275nextContext();
66243
+ return \u0275\u0275resetView(ctx_r2.settingsDialogOpen.set(false));
66244
+ });
66245
+ \u0275\u0275elementEnd();
66246
+ \u0275\u0275elementStart(1, "div", 15)(2, "button", 16);
66247
+ \u0275\u0275listener("click", function AppComponent_Conditional_22_Template_button_click_2_listener() {
66248
+ \u0275\u0275restoreView(_r2);
66249
+ const ctx_r2 = \u0275\u0275nextContext();
66250
+ return \u0275\u0275resetView(ctx_r2.settingsDialogOpen.set(false));
66251
+ });
66252
+ \u0275\u0275text(3, "close");
66253
+ \u0275\u0275elementEnd()();
66254
+ }
66255
+ }
66003
66256
  var AppComponent = class _AppComponent {
66257
+ projectInfoService = inject(ProjectInfoService);
66258
+ // Whether the required initial setup has been done.
66259
+ // (Setting data directory, model directory, views).
66260
+ projectInfoRequestCompleted = signal(false);
66261
+ hasBeenSetup = signal(false);
66262
+ settingsDialog = viewChild.required("settingsDialog");
66263
+ settingsDialogOpen = signal(false);
66264
+ async ngOnInit() {
66265
+ await this.projectInfoService.loadProjectInfo();
66266
+ this.hasBeenSetup.set(Boolean(this.projectInfoService.projectInfo));
66267
+ this.projectInfoRequestCompleted.set(true);
66268
+ }
66269
+ constructor() {
66270
+ effect(() => {
66271
+ if (this.settingsDialogOpen()) {
66272
+ this.openSettingsDialog();
66273
+ } else {
66274
+ this.closeSettingsDialog();
66275
+ }
66276
+ });
66277
+ }
66278
+ openSettingsDialog() {
66279
+ const elementRef = this.settingsDialog();
66280
+ elementRef.nativeElement.showModal();
66281
+ }
66282
+ closeSettingsDialog() {
66283
+ const elementRef = this.settingsDialog();
66284
+ elementRef.nativeElement.close();
66285
+ }
66004
66286
  static \u0275fac = function AppComponent_Factory(__ngFactoryType__) {
66005
66287
  return new (__ngFactoryType__ || _AppComponent)();
66006
66288
  };
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) {
66289
+ static \u0275cmp = /* @__PURE__ */ \u0275\u0275defineComponent({ type: _AppComponent, selectors: [["app-root"]], viewQuery: function AppComponent_Query(rf, ctx) {
66008
66290
  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");
66291
+ \u0275\u0275viewQuerySignal(ctx.settingsDialog, _c08, 5);
66292
+ }
66293
+ if (rf & 2) {
66294
+ \u0275\u0275queryAdvance();
66295
+ }
66296
+ }, 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) {
66297
+ if (rf & 1) {
66298
+ const _r1 = \u0275\u0275getCurrentView();
66299
+ \u0275\u0275elementStart(0, "header")(1, "nav", 1)(2, "div", 2)(3, "span", 3);
66300
+ \u0275\u0275text(4, "Lightning Pose");
66014
66301
  \u0275\u0275elementEnd()();
66015
- \u0275\u0275elementStart(7, "li")(8, "a", 2);
66016
- \u0275\u0275text(9, "Labeler");
66302
+ \u0275\u0275elementStart(5, "div", 4)(6, "ul", 5)(7, "li")(8, "a", 6);
66303
+ \u0275\u0275text(9, "Viewer");
66304
+ \u0275\u0275elementEnd()();
66305
+ \u0275\u0275elementStart(10, "li")(11, "a", 7);
66306
+ \u0275\u0275text(12, "Labeler");
66017
66307
  \u0275\u0275elementEnd()()()();
66018
- \u0275\u0275element(10, "router-outlet");
66308
+ \u0275\u0275elementStart(13, "div", 8)(14, "button", 9);
66309
+ \u0275\u0275listener("click", function AppComponent_Template_button_click_14_listener() {
66310
+ \u0275\u0275restoreView(_r1);
66311
+ return \u0275\u0275resetView(ctx.settingsDialogOpen.set(true));
66312
+ })("keydown.enter", function AppComponent_Template_button_keydown_enter_14_listener() {
66313
+ \u0275\u0275restoreView(_r1);
66314
+ return \u0275\u0275resetView(ctx.settingsDialogOpen.set(true));
66315
+ });
66316
+ \u0275\u0275elementStart(15, "span", 10);
66317
+ \u0275\u0275text(16, "settings");
66318
+ \u0275\u0275elementEnd()()()()();
66319
+ \u0275\u0275elementStart(17, "main", 11);
66320
+ \u0275\u0275template(18, AppComponent_Conditional_18_Template, 1, 0, "router-outlet")(19, AppComponent_Conditional_19_Template, 1, 1, "app-project-settings", 12);
66321
+ \u0275\u0275elementEnd();
66322
+ \u0275\u0275elementStart(20, "dialog", 13, 0);
66323
+ \u0275\u0275template(22, AppComponent_Conditional_22_Template, 4, 0);
66324
+ \u0275\u0275elementEnd();
66325
+ }
66326
+ if (rf & 2) {
66327
+ \u0275\u0275advance(17);
66328
+ \u0275\u0275classProp("justify-center", !ctx.hasBeenSetup());
66329
+ \u0275\u0275advance();
66330
+ \u0275\u0275conditional(ctx.hasBeenSetup() ? 18 : 19);
66331
+ \u0275\u0275advance(4);
66332
+ \u0275\u0275conditional(ctx.settingsDialogOpen() ? 22 : -1);
66019
66333
  }
66020
- }, dependencies: [RouterOutlet, RouterLink, RouterLinkActive], encapsulation: 2, changeDetection: 0 });
66334
+ }, dependencies: [
66335
+ RouterOutlet,
66336
+ RouterLink,
66337
+ RouterLinkActive,
66338
+ ProjectSettingsComponent
66339
+ ], 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-IZ5OUDH2.css.map */"], changeDetection: 0 });
66021
66340
  };
66022
66341
  (() => {
66023
66342
  (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(AppComponent, [{
66024
66343
  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);
66344
+ args: [{ selector: "app-root", imports: [
66345
+ RouterOutlet,
66346
+ RouterLink,
66347
+ RouterLinkActive,
66348
+ ProjectSettingsComponent
66349
+ ], 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-IZ5OUDH2.css.map */\n"] }]
66350
+ }], () => [], null);
66027
66351
  })();
66028
66352
  (() => {
66029
- (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(AppComponent, { className: "AppComponent", filePath: "src/app/app.component.ts", lineNumber: 11 });
66353
+ (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(AppComponent, { className: "AppComponent", filePath: "src/app/app.component.ts", lineNumber: 27 });
66030
66354
  })();
66031
66355
 
66032
66356
  // src/main.ts
66033
66357
  bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
66034
- //# sourceMappingURL=main-QMBNNDJG.js.map
66358
+ //# sourceMappingURL=main-WFYIUX2C.js.map