@memberjunction/ng-conversations 5.41.0 → 5.42.0

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.
Files changed (53) hide show
  1. package/dist/__tests__/remote-browser-channel.test.js +163 -1
  2. package/dist/__tests__/remote-browser-channel.test.js.map +1 -1
  3. package/dist/lib/components/collection/collections-full-view.component.d.ts +105 -2
  4. package/dist/lib/components/collection/collections-full-view.component.d.ts.map +1 -1
  5. package/dist/lib/components/collection/collections-full-view.component.js +1378 -489
  6. package/dist/lib/components/collection/collections-full-view.component.js.map +1 -1
  7. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +4 -0
  8. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  9. package/dist/lib/components/conversation/conversation-chat-area.component.js +13 -8
  10. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  11. package/dist/lib/components/conversation/conversation-list.component.d.ts +98 -5
  12. package/dist/lib/components/conversation/conversation-list.component.d.ts.map +1 -1
  13. package/dist/lib/components/conversation/conversation-list.component.js +1102 -401
  14. package/dist/lib/components/conversation/conversation-list.component.js.map +1 -1
  15. package/dist/lib/components/message/message-input.component.js +1 -1
  16. package/dist/lib/components/message/message-input.component.js.map +1 -1
  17. package/dist/lib/components/project/project-form-modal.component.d.ts +5 -1
  18. package/dist/lib/components/project/project-form-modal.component.d.ts.map +1 -1
  19. package/dist/lib/components/project/project-form-modal.component.js +104 -85
  20. package/dist/lib/components/project/project-form-modal.component.js.map +1 -1
  21. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts +22 -0
  22. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts.map +1 -1
  23. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js +40 -0
  24. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js.map +1 -1
  25. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +12 -1
  26. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -1
  27. package/dist/lib/components/realtime/realtime-agent-picker.component.js +97 -19
  28. package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -1
  29. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts +33 -0
  30. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
  31. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +164 -7
  32. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
  33. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts +35 -2
  34. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts.map +1 -1
  35. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js +67 -4
  36. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js.map +1 -1
  37. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts +1 -0
  38. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts.map +1 -1
  39. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js +13 -0
  40. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js.map +1 -1
  41. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +4 -0
  42. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  43. package/dist/lib/components/workspace/conversation-workspace.component.js +13 -8
  44. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  45. package/dist/lib/services/artifact-state.service.d.ts +1 -1
  46. package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
  47. package/dist/lib/services/artifact-state.service.js +7 -2
  48. package/dist/lib/services/artifact-state.service.js.map +1 -1
  49. package/dist/lib/services/realtime-pairing.d.ts +8 -7
  50. package/dist/lib/services/realtime-pairing.d.ts.map +1 -1
  51. package/dist/lib/services/realtime-pairing.js +19 -9
  52. package/dist/lib/services/realtime-pairing.js.map +1 -1
  53. package/package.json +26 -26
@@ -16,12 +16,16 @@ export declare class ProjectFormModalComponent extends BaseAngularComponent impl
16
16
  project: MJProjectEntity | null;
17
17
  environmentId: string;
18
18
  currentUser: UserInfo;
19
+ /** When creating a new folder, the parent folder ID for nesting (null = top level). */
20
+ parentId: string | null;
19
21
  projectSaved: EventEmitter<MJProjectEntity>;
20
22
  formData: ProjectFormData;
21
23
  showNameError: boolean;
22
24
  isEditMode: boolean;
23
25
  availableColors: string[];
24
26
  availableIcons: string[];
27
+ /** Translucent tint of the selected color, used behind the preview/icon glyph. */
28
+ get chipBackground(): string;
25
29
  constructor(cdr: ChangeDetectorRef);
26
30
  ngOnInit(): void;
27
31
  private loadProjectData;
@@ -30,6 +34,6 @@ export declare class ProjectFormModalComponent extends BaseAngularComponent impl
30
34
  onSave(): Promise<void>;
31
35
  onCancel(): void;
32
36
  static ɵfac: i0.ɵɵFactoryDeclaration<ProjectFormModalComponent, never>;
33
- static ɵcmp: i0.ɵɵComponentDeclaration<ProjectFormModalComponent, "mj-project-form-modal", never, { "dialogRef": { "alias": "dialogRef"; "required": false; }; "project": { "alias": "project"; "required": false; }; "environmentId": { "alias": "environmentId"; "required": false; }; "currentUser": { "alias": "currentUser"; "required": false; }; }, { "projectSaved": "projectSaved"; }, never, never, false, never>;
37
+ static ɵcmp: i0.ɵɵComponentDeclaration<ProjectFormModalComponent, "mj-project-form-modal", never, { "dialogRef": { "alias": "dialogRef"; "required": false; }; "project": { "alias": "project"; "required": false; }; "environmentId": { "alias": "environmentId"; "required": false; }; "currentUser": { "alias": "currentUser"; "required": false; }; "parentId": { "alias": "parentId"; "required": false; }; }, { "projectSaved": "projectSaved"; }, never, never, false, never>;
34
38
  }
35
39
  //# sourceMappingURL=project-form-modal.component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-form-modal.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/project/project-form-modal.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,MAAM,EAAa,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC7G,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAY,MAAM,sBAAsB,CAAC;;AAE1D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAgDD,qBA6Ra,yBAA0B,SAAQ,oBAAqB,YAAW,MAAM;IAoBvE,OAAO,CAAC,GAAG;IAnBd,SAAS,EAAG,WAAW,CAAC;IACxB,OAAO,EAAE,eAAe,GAAG,IAAI,CAAQ;IACvC,aAAa,EAAG,MAAM,CAAC;IACvB,WAAW,EAAG,QAAQ,CAAC;IAEtB,YAAY,gCAAuC;IAEtD,QAAQ,EAAE,eAAe,CAK9B;IAEK,aAAa,UAAS;IACtB,UAAU,UAAS;IACnB,eAAe,WAA0B;IACzC,cAAc,WAAyB;gBAE1B,GAAG,EAAE,iBAAiB;IAG1C,QAAQ,IAAI,IAAI;IAQhB,OAAO,CAAC,eAAe;IAWvB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKhC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAqC7B,QAAQ,IAAI,IAAI;yCAzFL,yBAAyB;2CAAzB,yBAAyB;CA4FrC"}
1
+ {"version":3,"file":"project-form-modal.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/project/project-form-modal.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,MAAM,EAAa,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC7G,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAY,MAAM,sBAAsB,CAAC;;AAE1D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAgDD,qBAkSa,yBAA0B,SAAQ,oBAAqB,YAAW,MAAM;IA6BvE,OAAO,CAAC,GAAG;IA5Bd,SAAS,EAAG,WAAW,CAAC;IACxB,OAAO,EAAE,eAAe,GAAG,IAAI,CAAQ;IACvC,aAAa,EAAG,MAAM,CAAC;IACvB,WAAW,EAAG,QAAQ,CAAC;IAChC,uFAAuF;IAC9E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE9B,YAAY,gCAAuC;IAEtD,QAAQ,EAAE,eAAe,CAK9B;IAEK,aAAa,UAAS;IACtB,UAAU,UAAS;IACnB,eAAe,WAA0B;IACzC,cAAc,WAAyB;IAE9C,kFAAkF;IAClF,IAAW,cAAc,IAAI,MAAM,CAIlC;gBAEmB,GAAG,EAAE,iBAAiB;IAG1C,QAAQ,IAAI,IAAI;IAQhB,OAAO,CAAC,eAAe;IAWvB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKhC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAwC7B,QAAQ,IAAI,IAAI;yCArGL,yBAAyB;2CAAzB,yBAAyB;CAwGrC"}
@@ -3,15 +3,15 @@ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
3
3
  import * as i0 from "@angular/core";
4
4
  import * as i1 from "@angular/forms";
5
5
  import * as i2 from "@memberjunction/ng-ui-components";
6
- function ProjectFormModalComponent_Conditional_6_Template(rf, ctx) { if (rf & 1) {
7
- i0.ɵɵelementStart(0, "div", 5);
8
- i0.ɵɵtext(1, "Project name is required");
6
+ function ProjectFormModalComponent_Conditional_11_Template(rf, ctx) { if (rf & 1) {
7
+ i0.ɵɵelementStart(0, "div", 8);
8
+ i0.ɵɵtext(1, "Folder name is required");
9
9
  i0.ɵɵelementEnd();
10
10
  } }
11
- function ProjectFormModalComponent_For_17_Template(rf, ctx) { if (rf & 1) {
11
+ function ProjectFormModalComponent_For_22_Template(rf, ctx) { if (rf & 1) {
12
12
  const _r1 = i0.ɵɵgetCurrentView();
13
- i0.ɵɵelementStart(0, "button", 21);
14
- i0.ɵɵlistener("click", function ProjectFormModalComponent_For_17_Template_button_click_0_listener() { const color_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.selectColor(color_r2)); });
13
+ i0.ɵɵelementStart(0, "button", 22);
14
+ i0.ɵɵlistener("click", function ProjectFormModalComponent_For_22_Template_button_click_0_listener() { const color_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.selectColor(color_r2)); });
15
15
  i0.ɵɵelementEnd();
16
16
  } if (rf & 2) {
17
17
  const color_r2 = ctx.$implicit;
@@ -22,13 +22,14 @@ function ProjectFormModalComponent_For_17_Template(rf, ctx) { if (rf & 1) {
22
22
  } }
23
23
  function ProjectFormModalComponent_For_34_Template(rf, ctx) { if (rf & 1) {
24
24
  const _r4 = i0.ɵɵgetCurrentView();
25
- i0.ɵɵelementStart(0, "button", 22);
25
+ i0.ɵɵelementStart(0, "button", 23);
26
26
  i0.ɵɵlistener("click", function ProjectFormModalComponent_For_34_Template_button_click_0_listener() { const icon_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.selectIcon(icon_r5)); });
27
27
  i0.ɵɵelement(1, "i");
28
28
  i0.ɵɵelementEnd();
29
29
  } if (rf & 2) {
30
30
  const icon_r5 = ctx.$implicit;
31
31
  const ctx_r2 = i0.ɵɵnextContext();
32
+ i0.ɵɵstyleProp("color", ctx_r2.formData.icon === icon_r5 ? ctx_r2.formData.color : null);
32
33
  i0.ɵɵclassProp("selected", ctx_r2.formData.icon === icon_r5);
33
34
  i0.ɵɵproperty("title", icon_r5);
34
35
  i0.ɵɵadvance();
@@ -84,6 +85,8 @@ export class ProjectFormModalComponent extends BaseAngularComponent {
84
85
  project = null;
85
86
  environmentId;
86
87
  currentUser;
88
+ /** When creating a new folder, the parent folder ID for nesting (null = top level). */
89
+ parentId = null;
87
90
  projectSaved = new EventEmitter();
88
91
  formData = {
89
92
  name: '',
@@ -95,6 +98,12 @@ export class ProjectFormModalComponent extends BaseAngularComponent {
95
98
  isEditMode = false;
96
99
  availableColors = DEFAULT_PROJECT_COLORS;
97
100
  availableIcons = DEFAULT_PROJECT_ICONS;
101
+ /** Translucent tint of the selected color, used behind the preview/icon glyph. */
102
+ get chipBackground() {
103
+ const hex = this.formData.color || '#0076B6';
104
+ // 8-digit hex (#RRGGBBAA) — ~14% alpha tint of the chosen color
105
+ return /^#[0-9a-fA-F]{6}$/.test(hex) ? `${hex}24` : hex;
106
+ }
98
107
  constructor(cdr) {
99
108
  super();
100
109
  this.cdr = cdr;
@@ -141,6 +150,9 @@ export class ProjectFormModalComponent extends BaseAngularComponent {
141
150
  if (!this.isEditMode) {
142
151
  project.EnvironmentID = this.environmentId;
143
152
  project.IsArchived = false;
153
+ if (this.parentId) {
154
+ project.ParentID = this.parentId;
155
+ }
144
156
  }
145
157
  const saved = await project.Save();
146
158
  if (saved) {
@@ -160,64 +172,71 @@ export class ProjectFormModalComponent extends BaseAngularComponent {
160
172
  this.dialogRef.Close();
161
173
  }
162
174
  static ɵfac = function ProjectFormModalComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ProjectFormModalComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
163
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ProjectFormModalComponent, selectors: [["mj-project-form-modal"]], inputs: { dialogRef: "dialogRef", project: "project", environmentId: "environmentId", currentUser: "currentUser" }, outputs: { projectSaved: "projectSaved" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 40, vars: 16, consts: [[3, "Close", "Title", "Width", "MinWidth", "Visible"], [1, "project-form"], [1, "form-field"], ["for", "projectName", 1, "required"], ["id", "projectName", "type", "text", "placeholder", "Enter project name", "autofocus", "", 1, "mj-textbox", "full-width", 3, "ngModelChange", "keydown.enter", "ngModel"], [1, "error-message"], ["for", "projectDescription"], ["id", "projectDescription", "placeholder", "Enter project description (optional)", "rows", "3", 1, "mj-textarea", "full-width", 3, "ngModelChange", "ngModel"], [1, "color-picker-section"], [1, "color-palette"], ["type", "button", 1, "color-swatch", 3, "selected", "backgroundColor", "title"], [1, "custom-color-input"], ["for", "customColor"], ["id", "customColor", "type", "color", 1, "custom-color-picker", 3, "ngModelChange", "ngModel"], [1, "color-value"], [1, "icon-selector-section"], [1, "selected-icon-preview"], [1, "icon-grid"], ["type", "button", 1, "icon-option", 3, "selected", "title"], ["mjButton", "", 3, "click"], ["mjButton", "", "variant", "primary", 3, "click"], ["type", "button", 1, "color-swatch", 3, "click", "title"], ["type", "button", 1, "icon-option", 3, "click", "title"]], template: function ProjectFormModalComponent_Template(rf, ctx) { if (rf & 1) {
175
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ProjectFormModalComponent, selectors: [["mj-project-form-modal"]], inputs: { dialogRef: "dialogRef", project: "project", environmentId: "environmentId", currentUser: "currentUser", parentId: "parentId" }, outputs: { projectSaved: "projectSaved" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 40, vars: 18, consts: [[3, "Close", "Title", "Width", "MinWidth", "Visible"], [1, "project-form"], [1, "folder-preview"], [1, "folder-preview-chip"], [1, "folder-preview-name"], [1, "form-field"], ["for", "projectName", 1, "required"], ["id", "projectName", "type", "text", "placeholder", "e.g. Client work, Research, Ideas", "autofocus", "", 1, "mj-input", "full-width", 3, "ngModelChange", "keydown.enter", "ngModel"], [1, "error-message"], ["for", "projectDescription"], ["id", "projectDescription", "placeholder", "What goes in this folder? (optional)", "rows", "2", 1, "mj-textarea", "full-width", 3, "ngModelChange", "ngModel"], [1, "color-picker-section"], [1, "color-palette"], ["type", "button", 1, "color-swatch", 3, "selected", "backgroundColor", "title"], [1, "custom-color-input"], ["for", "customColor"], ["id", "customColor", "type", "color", 1, "custom-color-picker", 3, "ngModelChange", "ngModel"], [1, "color-value"], [1, "icon-grid"], ["type", "button", 1, "icon-option", 3, "selected", "color", "title"], ["mjButton", "", "variant", "primary", 3, "click"], ["mjButton", "", 3, "click"], ["type", "button", 1, "color-swatch", 3, "click", "title"], ["type", "button", 1, "icon-option", 3, "click", "title"]], template: function ProjectFormModalComponent_Template(rf, ctx) { if (rf & 1) {
164
176
  i0.ɵɵelementStart(0, "mj-dialog", 0);
165
177
  i0.ɵɵlistener("Close", function ProjectFormModalComponent_Template_mj_dialog_Close_0_listener() { return ctx.onCancel(); });
166
- i0.ɵɵelementStart(1, "div", 1)(2, "div", 2)(3, "label", 3);
167
- i0.ɵɵtext(4, " Project Name ");
178
+ i0.ɵɵelementStart(1, "div", 1)(2, "div", 2)(3, "span", 3);
179
+ i0.ɵɵelement(4, "i");
180
+ i0.ɵɵelementEnd();
181
+ i0.ɵɵelementStart(5, "span", 4);
182
+ i0.ɵɵtext(6);
183
+ i0.ɵɵelementEnd()();
184
+ i0.ɵɵelementStart(7, "div", 5)(8, "label", 6);
185
+ i0.ɵɵtext(9, " Folder Name ");
168
186
  i0.ɵɵelementEnd();
169
- i0.ɵɵelementStart(5, "input", 4);
170
- i0.ɵɵtwoWayListener("ngModelChange", function ProjectFormModalComponent_Template_input_ngModelChange_5_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.formData.name, $event) || (ctx.formData.name = $event); return $event; });
171
- i0.ɵɵlistener("keydown.enter", function ProjectFormModalComponent_Template_input_keydown_enter_5_listener() { return ctx.onSave(); });
187
+ i0.ɵɵelementStart(10, "input", 7);
188
+ i0.ɵɵtwoWayListener("ngModelChange", function ProjectFormModalComponent_Template_input_ngModelChange_10_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.formData.name, $event) || (ctx.formData.name = $event); return $event; });
189
+ i0.ɵɵlistener("keydown.enter", function ProjectFormModalComponent_Template_input_keydown_enter_10_listener() { return ctx.onSave(); });
172
190
  i0.ɵɵelementEnd();
173
- i0.ɵɵconditionalCreate(6, ProjectFormModalComponent_Conditional_6_Template, 2, 0, "div", 5);
191
+ i0.ɵɵconditionalCreate(11, ProjectFormModalComponent_Conditional_11_Template, 2, 0, "div", 8);
174
192
  i0.ɵɵelementEnd();
175
- i0.ɵɵelementStart(7, "div", 2)(8, "label", 6);
176
- i0.ɵɵtext(9, " Description ");
193
+ i0.ɵɵelementStart(12, "div", 5)(13, "label", 9);
194
+ i0.ɵɵtext(14, " Description ");
177
195
  i0.ɵɵelementEnd();
178
- i0.ɵɵelementStart(10, "textarea", 7);
179
- i0.ɵɵtwoWayListener("ngModelChange", function ProjectFormModalComponent_Template_textarea_ngModelChange_10_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.formData.description, $event) || (ctx.formData.description = $event); return $event; });
196
+ i0.ɵɵelementStart(15, "textarea", 10);
197
+ i0.ɵɵtwoWayListener("ngModelChange", function ProjectFormModalComponent_Template_textarea_ngModelChange_15_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.formData.description, $event) || (ctx.formData.description = $event); return $event; });
180
198
  i0.ɵɵelementEnd()();
181
- i0.ɵɵelementStart(11, "div", 2)(12, "label");
182
- i0.ɵɵtext(13, "Color");
199
+ i0.ɵɵelementStart(16, "div", 5)(17, "label");
200
+ i0.ɵɵtext(18, "Color");
183
201
  i0.ɵɵelementEnd();
184
- i0.ɵɵelementStart(14, "div", 8)(15, "div", 9);
185
- i0.ɵɵrepeaterCreate(16, ProjectFormModalComponent_For_17_Template, 1, 5, "button", 10, i0.ɵɵrepeaterTrackByIdentity);
202
+ i0.ɵɵelementStart(19, "div", 11)(20, "div", 12);
203
+ i0.ɵɵrepeaterCreate(21, ProjectFormModalComponent_For_22_Template, 1, 5, "button", 13, i0.ɵɵrepeaterTrackByIdentity);
186
204
  i0.ɵɵelementEnd();
187
- i0.ɵɵelementStart(18, "div", 11)(19, "label", 12);
188
- i0.ɵɵtext(20, "Custom:");
205
+ i0.ɵɵelementStart(23, "div", 14)(24, "label", 15);
206
+ i0.ɵɵtext(25, "Custom:");
189
207
  i0.ɵɵelementEnd();
190
- i0.ɵɵelementStart(21, "input", 13);
191
- i0.ɵɵtwoWayListener("ngModelChange", function ProjectFormModalComponent_Template_input_ngModelChange_21_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.formData.color, $event) || (ctx.formData.color = $event); return $event; });
208
+ i0.ɵɵelementStart(26, "input", 16);
209
+ i0.ɵɵtwoWayListener("ngModelChange", function ProjectFormModalComponent_Template_input_ngModelChange_26_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.formData.color, $event) || (ctx.formData.color = $event); return $event; });
192
210
  i0.ɵɵelementEnd();
193
- i0.ɵɵelementStart(22, "span", 14);
194
- i0.ɵɵtext(23);
211
+ i0.ɵɵelementStart(27, "span", 17);
212
+ i0.ɵɵtext(28);
195
213
  i0.ɵɵelementEnd()()()();
196
- i0.ɵɵelementStart(24, "div", 2)(25, "label");
197
- i0.ɵɵtext(26, "Icon");
214
+ i0.ɵɵelementStart(29, "div", 5)(30, "label");
215
+ i0.ɵɵtext(31, "Icon");
198
216
  i0.ɵɵelementEnd();
199
- i0.ɵɵelementStart(27, "div", 15)(28, "div", 16);
200
- i0.ɵɵelement(29, "i");
201
- i0.ɵɵelementStart(30, "span");
202
- i0.ɵɵtext(31);
203
- i0.ɵɵelementEnd()();
204
- i0.ɵɵelementStart(32, "div", 17);
205
- i0.ɵɵrepeaterCreate(33, ProjectFormModalComponent_For_34_Template, 2, 6, "button", 18, i0.ɵɵrepeaterTrackByIdentity);
206
- i0.ɵɵelementEnd()()()();
207
- i0.ɵɵelementStart(35, "mj-dialog-actions")(36, "button", 19);
208
- i0.ɵɵlistener("click", function ProjectFormModalComponent_Template_button_click_36_listener() { return ctx.onCancel(); });
209
- i0.ɵɵtext(37, "Cancel");
217
+ i0.ɵɵelementStart(32, "div", 18);
218
+ i0.ɵɵrepeaterCreate(33, ProjectFormModalComponent_For_34_Template, 2, 8, "button", 19, i0.ɵɵrepeaterTrackByIdentity);
219
+ i0.ɵɵelementEnd()()();
220
+ i0.ɵɵelementStart(35, "mj-dialog-actions")(36, "button", 20);
221
+ i0.ɵɵlistener("click", function ProjectFormModalComponent_Template_button_click_36_listener() { return ctx.onSave(); });
222
+ i0.ɵɵtext(37);
210
223
  i0.ɵɵelementEnd();
211
- i0.ɵɵelementStart(38, "button", 20);
212
- i0.ɵɵlistener("click", function ProjectFormModalComponent_Template_button_click_38_listener() { return ctx.onSave(); });
213
- i0.ɵɵtext(39);
224
+ i0.ɵɵelementStart(38, "button", 21);
225
+ i0.ɵɵlistener("click", function ProjectFormModalComponent_Template_button_click_38_listener() { return ctx.onCancel(); });
226
+ i0.ɵɵtext(39, "Cancel");
214
227
  i0.ɵɵelementEnd()()();
215
228
  } if (rf & 2) {
216
- i0.ɵɵproperty("Title", ctx.isEditMode ? "Edit Project" : "New Project")("Width", 600)("MinWidth", 400)("Visible", true);
217
- i0.ɵɵadvance(5);
229
+ i0.ɵɵproperty("Title", ctx.isEditMode ? "Edit Folder" : "New Folder")("Width", 560)("MinWidth", 400)("Visible", true);
230
+ i0.ɵɵadvance(3);
231
+ i0.ɵɵstyleProp("background-color", ctx.chipBackground)("color", ctx.formData.color);
232
+ i0.ɵɵadvance();
233
+ i0.ɵɵclassMap(i0.ɵɵinterpolate1("fa-solid ", ctx.formData.icon));
234
+ i0.ɵɵadvance(2);
235
+ i0.ɵɵtextInterpolate(ctx.formData.name.trim() || "Untitled folder");
236
+ i0.ɵɵadvance(4);
218
237
  i0.ɵɵtwoWayProperty("ngModel", ctx.formData.name);
219
238
  i0.ɵɵadvance();
220
- i0.ɵɵconditional(ctx.showNameError ? 6 : -1);
239
+ i0.ɵɵconditional(ctx.showNameError ? 11 : -1);
221
240
  i0.ɵɵadvance(4);
222
241
  i0.ɵɵtwoWayProperty("ngModel", ctx.formData.description);
223
242
  i0.ɵɵadvance(6);
@@ -226,43 +245,46 @@ export class ProjectFormModalComponent extends BaseAngularComponent {
226
245
  i0.ɵɵtwoWayProperty("ngModel", ctx.formData.color);
227
246
  i0.ɵɵadvance(2);
228
247
  i0.ɵɵtextInterpolate(ctx.formData.color);
229
- i0.ɵɵadvance(6);
230
- i0.ɵɵclassMap(i0.ɵɵinterpolate1("fa-solid ", ctx.formData.icon));
231
- i0.ɵɵstyleProp("color", ctx.formData.color);
232
- i0.ɵɵadvance(2);
233
- i0.ɵɵtextInterpolate(ctx.formData.icon);
234
- i0.ɵɵadvance(2);
248
+ i0.ɵɵadvance(5);
235
249
  i0.ɵɵrepeater(ctx.availableIcons);
236
- i0.ɵɵadvance(6);
237
- i0.ɵɵtextInterpolate1(" ", ctx.isEditMode ? "Save" : "Create", " ");
238
- } }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, i2.MJButtonDirective, i2.MJDialogComponent, i2.MJDialogActionsComponent], styles: [".project-form[_ngcontent-%COMP%] {\n padding: 16px;\n }\n\n .form-field[_ngcontent-%COMP%] {\n margin-bottom: 20px;\n }\n\n .form-field[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n display: block;\n margin-bottom: 8px;\n font-weight: 500;\n font-size: 14px;\n color: #333;\n }\n\n .form-field[_ngcontent-%COMP%] label.required[_ngcontent-%COMP%]::after {\n content: '*';\n color: #F44336;\n margin-left: 4px;\n }\n\n .full-width[_ngcontent-%COMP%] {\n width: 100%;\n }\n\n .error-message[_ngcontent-%COMP%] {\n color: #F44336;\n font-size: 12px;\n margin-top: 4px;\n }\n\n \n\n .color-picker-section[_ngcontent-%COMP%] {\n border: 1px solid #D9D9D9;\n border-radius: 4px;\n padding: 12px;\n background: #F9F9F9;\n }\n\n .color-palette[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(10, 1fr);\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .color-swatch[_ngcontent-%COMP%] {\n width: 100%;\n aspect-ratio: 1;\n border: 2px solid transparent;\n border-radius: 4px;\n cursor: pointer;\n transition: all 150ms ease;\n box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n }\n\n .color-swatch[_ngcontent-%COMP%]:hover {\n transform: scale(1.1);\n box-shadow: 0 2px 6px rgba(0,0,0,0.2);\n }\n\n .color-swatch.selected[_ngcontent-%COMP%] {\n border-color: #0076B6;\n box-shadow: 0 0 0 2px #fff, 0 0 0 4px #0076B6;\n }\n\n .custom-color-input[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-top: 12px;\n border-top: 1px solid #D9D9D9;\n }\n\n .custom-color-input[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n }\n\n .custom-color-picker[_ngcontent-%COMP%] {\n width: 50px;\n height: 32px;\n border: 1px solid #D9D9D9;\n border-radius: 4px;\n cursor: pointer;\n }\n\n .color-value[_ngcontent-%COMP%] {\n font-family: monospace;\n font-size: 13px;\n color: #666;\n }\n\n \n\n .icon-selector-section[_ngcontent-%COMP%] {\n border: 1px solid #D9D9D9;\n border-radius: 4px;\n padding: 12px;\n background: #F9F9F9;\n }\n\n .selected-icon-preview[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n background: white;\n border: 1px solid #D9D9D9;\n border-radius: 4px;\n margin-bottom: 12px;\n }\n\n .selected-icon-preview[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 24px;\n }\n\n .selected-icon-preview[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-family: monospace;\n font-size: 13px;\n color: #666;\n }\n\n .icon-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(10, 1fr);\n gap: 6px;\n max-height: 200px;\n overflow-y: auto;\n padding: 4px;\n }\n\n .icon-option[_ngcontent-%COMP%] {\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid #D9D9D9;\n background: white;\n border-radius: 4px;\n cursor: pointer;\n transition: all 150ms ease;\n }\n\n .icon-option[_ngcontent-%COMP%]:hover {\n background: #F0F0F0;\n border-color: #0076B6;\n transform: scale(1.05);\n }\n\n .icon-option.selected[_ngcontent-%COMP%] {\n background: #E3F2FD;\n border-color: #0076B6;\n border-width: 2px;\n }\n\n .icon-option[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 18px;\n color: #333;\n }\n\n \n\n .icon-grid[_ngcontent-%COMP%]::-webkit-scrollbar {\n width: 8px;\n }\n\n .icon-grid[_ngcontent-%COMP%]::-webkit-scrollbar-track {\n background: #F0F0F0;\n border-radius: 4px;\n }\n\n .icon-grid[_ngcontent-%COMP%]::-webkit-scrollbar-thumb {\n background: #D9D9D9;\n border-radius: 4px;\n }\n\n .icon-grid[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover {\n background: #BFBFBF;\n }"] });
250
+ i0.ɵɵadvance(4);
251
+ i0.ɵɵtextInterpolate1(" ", ctx.isEditMode ? "Save Changes" : "Create Folder", " ");
252
+ } }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, i2.MJButtonDirective, i2.MJDialogComponent, i2.MJDialogActionsComponent], styles: [".project-form[_ngcontent-%COMP%] {\n padding: 20px 24px 8px;\n display: flex;\n flex-direction: column;\n gap: 22px;\n }\n\n \n\n .folder-preview[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-subtle);\n }\n .folder-preview-chip[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n flex-shrink: 0;\n transition: background-color 150ms ease, color 150ms ease;\n }\n .folder-preview-name[_ngcontent-%COMP%] {\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .form-field[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n .form-field[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n font-weight: 600;\n font-size: 13px;\n letter-spacing: 0.01em;\n color: var(--mj-text-secondary);\n }\n\n .form-field[_ngcontent-%COMP%] label.required[_ngcontent-%COMP%]::after {\n content: '*';\n color: var(--mj-status-error);\n margin-left: 4px;\n }\n\n .full-width[_ngcontent-%COMP%] { width: 100%; }\n\n .error-message[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n font-size: 12px;\n }\n\n \n\n .color-picker-section[_ngcontent-%COMP%] {\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 14px;\n background: var(--mj-bg-surface);\n }\n\n .color-palette[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(10, 1fr);\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .color-swatch[_ngcontent-%COMP%] {\n width: 100%;\n aspect-ratio: 1;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n padding: 0;\n transition: transform 120ms ease, box-shadow 120ms ease;\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n }\n\n .color-swatch[_ngcontent-%COMP%]:hover {\n transform: scale(1.12);\n }\n\n .color-swatch.selected[_ngcontent-%COMP%] {\n box-shadow: 0 0 0 2px var(--mj-bg-surface), 0 0 0 4px var(--mj-brand-primary);\n }\n\n .custom-color-input[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding-top: 14px;\n border-top: 1px solid var(--mj-border-subtle);\n }\n\n .custom-color-input[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-secondary);\n }\n\n .custom-color-picker[_ngcontent-%COMP%] {\n width: 44px;\n height: 30px;\n padding: 2px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n cursor: pointer;\n background: var(--mj-bg-surface);\n }\n\n .color-value[_ngcontent-%COMP%] {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 13px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n }\n\n \n\n .icon-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(10, 1fr);\n gap: 8px;\n max-height: 184px;\n overflow-y: auto;\n padding: 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface);\n }\n\n .icon-option[_ngcontent-%COMP%] {\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-card);\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-secondary);\n transition: transform 120ms ease, border-color 120ms ease, background 120ms ease;\n }\n\n .icon-option[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n transform: translateY(-1px);\n }\n\n .icon-option.selected[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 1px var(--mj-brand-primary);\n }\n\n .icon-option[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { font-size: 17px; }\n\n \n\n .icon-grid[_ngcontent-%COMP%]::-webkit-scrollbar { width: 8px; }\n .icon-grid[_ngcontent-%COMP%]::-webkit-scrollbar-track {\n background: transparent;\n }\n .icon-grid[_ngcontent-%COMP%]::-webkit-scrollbar-thumb {\n background: var(--mj-border-strong);\n border-radius: 4px;\n }\n .icon-grid[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover {\n background: var(--mj-text-disabled);\n }"] });
239
253
  }
240
254
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ProjectFormModalComponent, [{
241
255
  type: Component,
242
256
  args: [{ standalone: false, selector: 'mj-project-form-modal', template: `
243
257
  <mj-dialog
244
- [Title]="isEditMode ? 'Edit Project' : 'New Project'"
245
- [Width]="600"
258
+ [Title]="isEditMode ? 'Edit Folder' : 'New Folder'"
259
+ [Width]="560"
246
260
  [MinWidth]="400"
247
261
  [Visible]="true"
248
262
  (Close)="onCancel()">
249
263
 
250
264
  <div class="project-form">
265
+ <!-- Live preview chip -->
266
+ <div class="folder-preview">
267
+ <span class="folder-preview-chip" [style.backgroundColor]="chipBackground" [style.color]="formData.color">
268
+ <i class="fa-solid {{ formData.icon }}"></i>
269
+ </span>
270
+ <span class="folder-preview-name">{{ formData.name.trim() || 'Untitled folder' }}</span>
271
+ </div>
272
+
251
273
  <!-- Name Input -->
252
274
  <div class="form-field">
253
275
  <label for="projectName" class="required">
254
- Project Name
276
+ Folder Name
255
277
  </label>
256
278
  <input
257
279
  id="projectName"
258
280
  type="text"
259
281
  [(ngModel)]="formData.name"
260
- placeholder="Enter project name"
261
- class="mj-textbox full-width"
282
+ placeholder="e.g. Client work, Research, Ideas"
283
+ class="mj-input full-width"
262
284
  (keydown.enter)="onSave()"
263
285
  autofocus />
264
286
  @if (showNameError) {
265
- <div class="error-message">Project name is required</div>
287
+ <div class="error-message">Folder name is required</div>
266
288
  }
267
289
  </div>
268
290
 
@@ -274,9 +296,9 @@ export class ProjectFormModalComponent extends BaseAngularComponent {
274
296
  <textarea
275
297
  id="projectDescription"
276
298
  [(ngModel)]="formData.description"
277
- placeholder="Enter project description (optional)"
299
+ placeholder="What goes in this folder? (optional)"
278
300
  class="mj-textarea full-width"
279
- rows="3"></textarea>
301
+ rows="2"></textarea>
280
302
  </div>
281
303
 
282
304
  <!-- Color Picker -->
@@ -310,35 +332,30 @@ export class ProjectFormModalComponent extends BaseAngularComponent {
310
332
  <!-- Icon Selector -->
311
333
  <div class="form-field">
312
334
  <label>Icon</label>
313
- <div class="icon-selector-section">
314
- <div class="selected-icon-preview">
315
- <i class="fa-solid {{ formData.icon }}" [style.color]="formData.color"></i>
316
- <span>{{ formData.icon }}</span>
317
- </div>
318
- <div class="icon-grid">
319
- @for (icon of availableIcons; track icon) {
320
- <button
321
- type="button"
322
- class="icon-option"
323
- [class.selected]="formData.icon === icon"
324
- (click)="selectIcon(icon)"
325
- [title]="icon">
326
- <i class="fa-solid {{ icon }}"></i>
327
- </button>
328
- }
329
- </div>
335
+ <div class="icon-grid">
336
+ @for (icon of availableIcons; track icon) {
337
+ <button
338
+ type="button"
339
+ class="icon-option"
340
+ [class.selected]="formData.icon === icon"
341
+ [style.color]="formData.icon === icon ? formData.color : null"
342
+ (click)="selectIcon(icon)"
343
+ [title]="icon">
344
+ <i class="fa-solid {{ icon }}"></i>
345
+ </button>
346
+ }
330
347
  </div>
331
348
  </div>
332
349
  </div>
333
350
 
334
351
  <mj-dialog-actions>
335
- <button mjButton (click)="onCancel()">Cancel</button>
336
352
  <button mjButton variant="primary" (click)="onSave()">
337
- {{ isEditMode ? 'Save' : 'Create' }}
353
+ {{ isEditMode ? 'Save Changes' : 'Create Folder' }}
338
354
  </button>
355
+ <button mjButton (click)="onCancel()">Cancel</button>
339
356
  </mj-dialog-actions>
340
357
  </mj-dialog>
341
- `, styles: ["\n .project-form {\n padding: 16px;\n }\n\n .form-field {\n margin-bottom: 20px;\n }\n\n .form-field label {\n display: block;\n margin-bottom: 8px;\n font-weight: 500;\n font-size: 14px;\n color: #333;\n }\n\n .form-field label.required::after {\n content: '*';\n color: #F44336;\n margin-left: 4px;\n }\n\n .full-width {\n width: 100%;\n }\n\n .error-message {\n color: #F44336;\n font-size: 12px;\n margin-top: 4px;\n }\n\n /* Color Picker Styles */\n .color-picker-section {\n border: 1px solid #D9D9D9;\n border-radius: 4px;\n padding: 12px;\n background: #F9F9F9;\n }\n\n .color-palette {\n display: grid;\n grid-template-columns: repeat(10, 1fr);\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .color-swatch {\n width: 100%;\n aspect-ratio: 1;\n border: 2px solid transparent;\n border-radius: 4px;\n cursor: pointer;\n transition: all 150ms ease;\n box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n }\n\n .color-swatch:hover {\n transform: scale(1.1);\n box-shadow: 0 2px 6px rgba(0,0,0,0.2);\n }\n\n .color-swatch.selected {\n border-color: #0076B6;\n box-shadow: 0 0 0 2px #fff, 0 0 0 4px #0076B6;\n }\n\n .custom-color-input {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-top: 12px;\n border-top: 1px solid #D9D9D9;\n }\n\n .custom-color-input label {\n margin: 0;\n font-size: 13px;\n }\n\n .custom-color-picker {\n width: 50px;\n height: 32px;\n border: 1px solid #D9D9D9;\n border-radius: 4px;\n cursor: pointer;\n }\n\n .color-value {\n font-family: monospace;\n font-size: 13px;\n color: #666;\n }\n\n /* Icon Selector Styles */\n .icon-selector-section {\n border: 1px solid #D9D9D9;\n border-radius: 4px;\n padding: 12px;\n background: #F9F9F9;\n }\n\n .selected-icon-preview {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n background: white;\n border: 1px solid #D9D9D9;\n border-radius: 4px;\n margin-bottom: 12px;\n }\n\n .selected-icon-preview i {\n font-size: 24px;\n }\n\n .selected-icon-preview span {\n font-family: monospace;\n font-size: 13px;\n color: #666;\n }\n\n .icon-grid {\n display: grid;\n grid-template-columns: repeat(10, 1fr);\n gap: 6px;\n max-height: 200px;\n overflow-y: auto;\n padding: 4px;\n }\n\n .icon-option {\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid #D9D9D9;\n background: white;\n border-radius: 4px;\n cursor: pointer;\n transition: all 150ms ease;\n }\n\n .icon-option:hover {\n background: #F0F0F0;\n border-color: #0076B6;\n transform: scale(1.05);\n }\n\n .icon-option.selected {\n background: #E3F2FD;\n border-color: #0076B6;\n border-width: 2px;\n }\n\n .icon-option i {\n font-size: 18px;\n color: #333;\n }\n\n /* Scrollbar Styles */\n .icon-grid::-webkit-scrollbar {\n width: 8px;\n }\n\n .icon-grid::-webkit-scrollbar-track {\n background: #F0F0F0;\n border-radius: 4px;\n }\n\n .icon-grid::-webkit-scrollbar-thumb {\n background: #D9D9D9;\n border-radius: 4px;\n }\n\n .icon-grid::-webkit-scrollbar-thumb:hover {\n background: #BFBFBF;\n }\n "] }]
358
+ `, styles: ["\n .project-form {\n padding: 20px 24px 8px;\n display: flex;\n flex-direction: column;\n gap: 22px;\n }\n\n /* Live preview chip */\n .folder-preview {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-subtle);\n }\n .folder-preview-chip {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n flex-shrink: 0;\n transition: background-color 150ms ease, color 150ms ease;\n }\n .folder-preview-name {\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .form-field {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n .form-field label {\n font-weight: 600;\n font-size: 13px;\n letter-spacing: 0.01em;\n color: var(--mj-text-secondary);\n }\n\n .form-field label.required::after {\n content: '*';\n color: var(--mj-status-error);\n margin-left: 4px;\n }\n\n .full-width { width: 100%; }\n\n .error-message {\n color: var(--mj-status-error);\n font-size: 12px;\n }\n\n /* Color Picker */\n .color-picker-section {\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 14px;\n background: var(--mj-bg-surface);\n }\n\n .color-palette {\n display: grid;\n grid-template-columns: repeat(10, 1fr);\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .color-swatch {\n width: 100%;\n aspect-ratio: 1;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n padding: 0;\n transition: transform 120ms ease, box-shadow 120ms ease;\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n }\n\n .color-swatch:hover {\n transform: scale(1.12);\n }\n\n .color-swatch.selected {\n box-shadow: 0 0 0 2px var(--mj-bg-surface), 0 0 0 4px var(--mj-brand-primary);\n }\n\n .custom-color-input {\n display: flex;\n align-items: center;\n gap: 10px;\n padding-top: 14px;\n border-top: 1px solid var(--mj-border-subtle);\n }\n\n .custom-color-input label {\n margin: 0;\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-secondary);\n }\n\n .custom-color-picker {\n width: 44px;\n height: 30px;\n padding: 2px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n cursor: pointer;\n background: var(--mj-bg-surface);\n }\n\n .color-value {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 13px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n }\n\n /* Icon Selector */\n .icon-grid {\n display: grid;\n grid-template-columns: repeat(10, 1fr);\n gap: 8px;\n max-height: 184px;\n overflow-y: auto;\n padding: 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface);\n }\n\n .icon-option {\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-card);\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-secondary);\n transition: transform 120ms ease, border-color 120ms ease, background 120ms ease;\n }\n\n .icon-option:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n transform: translateY(-1px);\n }\n\n .icon-option.selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 1px var(--mj-brand-primary);\n }\n\n .icon-option i { font-size: 17px; }\n\n /* Scrollbar */\n .icon-grid::-webkit-scrollbar { width: 8px; }\n .icon-grid::-webkit-scrollbar-track {\n background: transparent;\n }\n .icon-grid::-webkit-scrollbar-thumb {\n background: var(--mj-border-strong);\n border-radius: 4px;\n }\n .icon-grid::-webkit-scrollbar-thumb:hover {\n background: var(--mj-text-disabled);\n }\n "] }]
342
359
  }], () => [{ type: i0.ChangeDetectorRef }], { dialogRef: [{
343
360
  type: Input
344
361
  }], project: [{
@@ -347,8 +364,10 @@ export class ProjectFormModalComponent extends BaseAngularComponent {
347
364
  type: Input
348
365
  }], currentUser: [{
349
366
  type: Input
367
+ }], parentId: [{
368
+ type: Input
350
369
  }], projectSaved: [{
351
370
  type: Output
352
371
  }] }); })();
353
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ProjectFormModalComponent, { className: "ProjectFormModalComponent", filePath: "src/lib/components/project/project-form-modal.component.ts", lineNumber: 345 }); })();
372
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ProjectFormModalComponent, { className: "ProjectFormModalComponent", filePath: "src/lib/components/project/project-form-modal.component.ts", lineNumber: 350 }); })();
354
373
  //# sourceMappingURL=project-form-modal.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-form-modal.component.js","sourceRoot":"","sources":["../../../../src/lib/components/project/project-form-modal.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, OnInit, ViewChild, ChangeDetectorRef } from '@angular/core';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { MJDialogRef } from '@memberjunction/ng-ui-components';\nimport { MJProjectEntity } from '@memberjunction/core-entities';\nimport { UserInfo, Metadata } from '@memberjunction/core';\n\nexport interface ProjectFormData {\n name: string;\n description: string;\n color: string;\n icon: string;\n}\n\nconst DEFAULT_PROJECT_COLORS = [\n '#0076B6', // MJ Blue\n '#F44336', // Red\n '#E91E63', // Pink\n '#9C27B0', // Purple\n '#673AB7', // Deep Purple\n '#3F51B5', // Indigo\n '#2196F3', // Blue\n '#03A9F4', // Light Blue\n '#00BCD4', // Cyan\n '#009688', // Teal\n '#4CAF50', // Green\n '#8BC34A', // Light Green\n '#CDDC39', // Lime\n '#FFEB3B', // Yellow\n '#FFC107', // Amber\n '#FF9800', // Orange\n '#FF5722', // Deep Orange\n '#795548', // Brown\n '#607D8B', // Blue Grey\n '#9E9E9E' // Grey\n];\n\nconst DEFAULT_PROJECT_ICONS = [\n 'fa-folder',\n 'fa-folder-open',\n 'fa-briefcase',\n 'fa-project-diagram',\n 'fa-chart-line',\n 'fa-tasks',\n 'fa-clipboard-list',\n 'fa-bullseye',\n 'fa-rocket',\n 'fa-lightbulb',\n 'fa-brain',\n 'fa-cogs',\n 'fa-code',\n 'fa-database',\n 'fa-server',\n 'fa-cloud',\n 'fa-mobile-alt',\n 'fa-desktop',\n 'fa-globe',\n 'fa-users'\n];\n\n@Component({\n standalone: false,\n selector: 'mj-project-form-modal',\n template: `\n <mj-dialog\n [Title]=\"isEditMode ? 'Edit Project' : 'New Project'\"\n [Width]=\"600\"\n [MinWidth]=\"400\"\n [Visible]=\"true\"\n (Close)=\"onCancel()\">\n\n <div class=\"project-form\">\n <!-- Name Input -->\n <div class=\"form-field\">\n <label for=\"projectName\" class=\"required\">\n Project Name\n </label>\n <input\n id=\"projectName\"\n type=\"text\"\n [(ngModel)]=\"formData.name\"\n placeholder=\"Enter project name\"\n class=\"mj-textbox full-width\"\n (keydown.enter)=\"onSave()\"\n autofocus />\n @if (showNameError) {\n <div class=\"error-message\">Project name is required</div>\n }\n </div>\n\n <!-- Description Textarea -->\n <div class=\"form-field\">\n <label for=\"projectDescription\">\n Description\n </label>\n <textarea\n id=\"projectDescription\"\n [(ngModel)]=\"formData.description\"\n placeholder=\"Enter project description (optional)\"\n class=\"mj-textarea full-width\"\n rows=\"3\"></textarea>\n </div>\n\n <!-- Color Picker -->\n <div class=\"form-field\">\n <label>Color</label>\n <div class=\"color-picker-section\">\n <div class=\"color-palette\">\n @for (color of availableColors; track color) {\n <button\n type=\"button\"\n class=\"color-swatch\"\n [class.selected]=\"formData.color === color\"\n [style.backgroundColor]=\"color\"\n (click)=\"selectColor(color)\"\n [title]=\"color\">\n </button>\n }\n </div>\n <div class=\"custom-color-input\">\n <label for=\"customColor\">Custom:</label>\n <input\n id=\"customColor\"\n type=\"color\"\n [(ngModel)]=\"formData.color\"\n class=\"custom-color-picker\" />\n <span class=\"color-value\">{{ formData.color }}</span>\n </div>\n </div>\n </div>\n\n <!-- Icon Selector -->\n <div class=\"form-field\">\n <label>Icon</label>\n <div class=\"icon-selector-section\">\n <div class=\"selected-icon-preview\">\n <i class=\"fa-solid {{ formData.icon }}\" [style.color]=\"formData.color\"></i>\n <span>{{ formData.icon }}</span>\n </div>\n <div class=\"icon-grid\">\n @for (icon of availableIcons; track icon) {\n <button\n type=\"button\"\n class=\"icon-option\"\n [class.selected]=\"formData.icon === icon\"\n (click)=\"selectIcon(icon)\"\n [title]=\"icon\">\n <i class=\"fa-solid {{ icon }}\"></i>\n </button>\n }\n </div>\n </div>\n </div>\n </div>\n\n <mj-dialog-actions>\n <button mjButton (click)=\"onCancel()\">Cancel</button>\n <button mjButton variant=\"primary\" (click)=\"onSave()\">\n {{ isEditMode ? 'Save' : 'Create' }}\n </button>\n </mj-dialog-actions>\n </mj-dialog>\n `,\n styles: [`\n .project-form {\n padding: 16px;\n }\n\n .form-field {\n margin-bottom: 20px;\n }\n\n .form-field label {\n display: block;\n margin-bottom: 8px;\n font-weight: 500;\n font-size: 14px;\n color: #333;\n }\n\n .form-field label.required::after {\n content: '*';\n color: #F44336;\n margin-left: 4px;\n }\n\n .full-width {\n width: 100%;\n }\n\n .error-message {\n color: #F44336;\n font-size: 12px;\n margin-top: 4px;\n }\n\n /* Color Picker Styles */\n .color-picker-section {\n border: 1px solid #D9D9D9;\n border-radius: 4px;\n padding: 12px;\n background: #F9F9F9;\n }\n\n .color-palette {\n display: grid;\n grid-template-columns: repeat(10, 1fr);\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .color-swatch {\n width: 100%;\n aspect-ratio: 1;\n border: 2px solid transparent;\n border-radius: 4px;\n cursor: pointer;\n transition: all 150ms ease;\n box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n }\n\n .color-swatch:hover {\n transform: scale(1.1);\n box-shadow: 0 2px 6px rgba(0,0,0,0.2);\n }\n\n .color-swatch.selected {\n border-color: #0076B6;\n box-shadow: 0 0 0 2px #fff, 0 0 0 4px #0076B6;\n }\n\n .custom-color-input {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-top: 12px;\n border-top: 1px solid #D9D9D9;\n }\n\n .custom-color-input label {\n margin: 0;\n font-size: 13px;\n }\n\n .custom-color-picker {\n width: 50px;\n height: 32px;\n border: 1px solid #D9D9D9;\n border-radius: 4px;\n cursor: pointer;\n }\n\n .color-value {\n font-family: monospace;\n font-size: 13px;\n color: #666;\n }\n\n /* Icon Selector Styles */\n .icon-selector-section {\n border: 1px solid #D9D9D9;\n border-radius: 4px;\n padding: 12px;\n background: #F9F9F9;\n }\n\n .selected-icon-preview {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n background: white;\n border: 1px solid #D9D9D9;\n border-radius: 4px;\n margin-bottom: 12px;\n }\n\n .selected-icon-preview i {\n font-size: 24px;\n }\n\n .selected-icon-preview span {\n font-family: monospace;\n font-size: 13px;\n color: #666;\n }\n\n .icon-grid {\n display: grid;\n grid-template-columns: repeat(10, 1fr);\n gap: 6px;\n max-height: 200px;\n overflow-y: auto;\n padding: 4px;\n }\n\n .icon-option {\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid #D9D9D9;\n background: white;\n border-radius: 4px;\n cursor: pointer;\n transition: all 150ms ease;\n }\n\n .icon-option:hover {\n background: #F0F0F0;\n border-color: #0076B6;\n transform: scale(1.05);\n }\n\n .icon-option.selected {\n background: #E3F2FD;\n border-color: #0076B6;\n border-width: 2px;\n }\n\n .icon-option i {\n font-size: 18px;\n color: #333;\n }\n\n /* Scrollbar Styles */\n .icon-grid::-webkit-scrollbar {\n width: 8px;\n }\n\n .icon-grid::-webkit-scrollbar-track {\n background: #F0F0F0;\n border-radius: 4px;\n }\n\n .icon-grid::-webkit-scrollbar-thumb {\n background: #D9D9D9;\n border-radius: 4px;\n }\n\n .icon-grid::-webkit-scrollbar-thumb:hover {\n background: #BFBFBF;\n }\n `]\n})\nexport class ProjectFormModalComponent extends BaseAngularComponent implements OnInit {\n @Input() dialogRef!: MJDialogRef;\n @Input() project: MJProjectEntity | null = null;\n @Input() environmentId!: string;\n @Input() currentUser!: UserInfo;\n\n @Output() projectSaved = new EventEmitter<MJProjectEntity>();\n\n public formData: ProjectFormData = {\n name: '',\n description: '',\n color: '#0076B6',\n icon: 'fa-folder'\n };\n\n public showNameError = false;\n public isEditMode = false;\n public availableColors = DEFAULT_PROJECT_COLORS;\n public availableIcons = DEFAULT_PROJECT_ICONS;\n\n constructor(private cdr: ChangeDetectorRef) {\n super();}\n\n ngOnInit(): void {\n this.isEditMode = this.project != null;\n\n if (this.project) {\n this.loadProjectData();\n }\n }\n\n private loadProjectData(): void {\n if (!this.project) return;\n\n this.formData = {\n name: this.project.Name || '',\n description: this.project.Description || '',\n color: this.project.Color || '#0076B6',\n icon: this.project.Icon || 'fa-folder'\n };\n }\n\n selectColor(color: string): void {\n this.formData.color = color;\n this.cdr.detectChanges();\n }\n\n selectIcon(icon: string): void {\n this.formData.icon = icon;\n this.cdr.detectChanges();\n }\n\n async onSave(): Promise<void> {\n // Validate\n if (!this.formData.name.trim()) {\n this.showNameError = true;\n this.cdr.detectChanges();\n return;\n }\n\n this.showNameError = false;\n\n try {\n const md = this.ProviderToUse;\n const project = this.project || await md.GetEntityObject<MJProjectEntity>('MJ: Projects', this.currentUser);\n\n project.Name = this.formData.name.trim();\n project.Description = this.formData.description.trim() || null;\n project.Color = this.formData.color;\n project.Icon = this.formData.icon;\n\n if (!this.isEditMode) {\n project.EnvironmentID = this.environmentId;\n project.IsArchived = false;\n }\n\n const saved = await project.Save();\n if (saved) {\n this.projectSaved.emit(project);\n this.dialogRef.Close();\n } else {\n throw new Error('Failed to save project');\n }\n } catch (error) {\n console.error('Error saving project:', error);\n alert('Failed to save project. Please try again.');\n }\n }\n\n onCancel(): void {\n this.dialogRef.Close();\n }\n}\n"]}
1
+ {"version":3,"file":"project-form-modal.component.js","sourceRoot":"","sources":["../../../../src/lib/components/project/project-form-modal.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, OnInit, ViewChild, ChangeDetectorRef } from '@angular/core';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { MJDialogRef } from '@memberjunction/ng-ui-components';\nimport { MJProjectEntity } from '@memberjunction/core-entities';\nimport { UserInfo, Metadata } from '@memberjunction/core';\n\nexport interface ProjectFormData {\n name: string;\n description: string;\n color: string;\n icon: string;\n}\n\nconst DEFAULT_PROJECT_COLORS = [\n '#0076B6', // MJ Blue\n '#F44336', // Red\n '#E91E63', // Pink\n '#9C27B0', // Purple\n '#673AB7', // Deep Purple\n '#3F51B5', // Indigo\n '#2196F3', // Blue\n '#03A9F4', // Light Blue\n '#00BCD4', // Cyan\n '#009688', // Teal\n '#4CAF50', // Green\n '#8BC34A', // Light Green\n '#CDDC39', // Lime\n '#FFEB3B', // Yellow\n '#FFC107', // Amber\n '#FF9800', // Orange\n '#FF5722', // Deep Orange\n '#795548', // Brown\n '#607D8B', // Blue Grey\n '#9E9E9E' // Grey\n];\n\nconst DEFAULT_PROJECT_ICONS = [\n 'fa-folder',\n 'fa-folder-open',\n 'fa-briefcase',\n 'fa-project-diagram',\n 'fa-chart-line',\n 'fa-tasks',\n 'fa-clipboard-list',\n 'fa-bullseye',\n 'fa-rocket',\n 'fa-lightbulb',\n 'fa-brain',\n 'fa-cogs',\n 'fa-code',\n 'fa-database',\n 'fa-server',\n 'fa-cloud',\n 'fa-mobile-alt',\n 'fa-desktop',\n 'fa-globe',\n 'fa-users'\n];\n\n@Component({\n standalone: false,\n selector: 'mj-project-form-modal',\n template: `\n <mj-dialog\n [Title]=\"isEditMode ? 'Edit Folder' : 'New Folder'\"\n [Width]=\"560\"\n [MinWidth]=\"400\"\n [Visible]=\"true\"\n (Close)=\"onCancel()\">\n\n <div class=\"project-form\">\n <!-- Live preview chip -->\n <div class=\"folder-preview\">\n <span class=\"folder-preview-chip\" [style.backgroundColor]=\"chipBackground\" [style.color]=\"formData.color\">\n <i class=\"fa-solid {{ formData.icon }}\"></i>\n </span>\n <span class=\"folder-preview-name\">{{ formData.name.trim() || 'Untitled folder' }}</span>\n </div>\n\n <!-- Name Input -->\n <div class=\"form-field\">\n <label for=\"projectName\" class=\"required\">\n Folder Name\n </label>\n <input\n id=\"projectName\"\n type=\"text\"\n [(ngModel)]=\"formData.name\"\n placeholder=\"e.g. Client work, Research, Ideas\"\n class=\"mj-input full-width\"\n (keydown.enter)=\"onSave()\"\n autofocus />\n @if (showNameError) {\n <div class=\"error-message\">Folder name is required</div>\n }\n </div>\n\n <!-- Description Textarea -->\n <div class=\"form-field\">\n <label for=\"projectDescription\">\n Description\n </label>\n <textarea\n id=\"projectDescription\"\n [(ngModel)]=\"formData.description\"\n placeholder=\"What goes in this folder? (optional)\"\n class=\"mj-textarea full-width\"\n rows=\"2\"></textarea>\n </div>\n\n <!-- Color Picker -->\n <div class=\"form-field\">\n <label>Color</label>\n <div class=\"color-picker-section\">\n <div class=\"color-palette\">\n @for (color of availableColors; track color) {\n <button\n type=\"button\"\n class=\"color-swatch\"\n [class.selected]=\"formData.color === color\"\n [style.backgroundColor]=\"color\"\n (click)=\"selectColor(color)\"\n [title]=\"color\">\n </button>\n }\n </div>\n <div class=\"custom-color-input\">\n <label for=\"customColor\">Custom:</label>\n <input\n id=\"customColor\"\n type=\"color\"\n [(ngModel)]=\"formData.color\"\n class=\"custom-color-picker\" />\n <span class=\"color-value\">{{ formData.color }}</span>\n </div>\n </div>\n </div>\n\n <!-- Icon Selector -->\n <div class=\"form-field\">\n <label>Icon</label>\n <div class=\"icon-grid\">\n @for (icon of availableIcons; track icon) {\n <button\n type=\"button\"\n class=\"icon-option\"\n [class.selected]=\"formData.icon === icon\"\n [style.color]=\"formData.icon === icon ? formData.color : null\"\n (click)=\"selectIcon(icon)\"\n [title]=\"icon\">\n <i class=\"fa-solid {{ icon }}\"></i>\n </button>\n }\n </div>\n </div>\n </div>\n\n <mj-dialog-actions>\n <button mjButton variant=\"primary\" (click)=\"onSave()\">\n {{ isEditMode ? 'Save Changes' : 'Create Folder' }}\n </button>\n <button mjButton (click)=\"onCancel()\">Cancel</button>\n </mj-dialog-actions>\n </mj-dialog>\n `,\n styles: [`\n .project-form {\n padding: 20px 24px 8px;\n display: flex;\n flex-direction: column;\n gap: 22px;\n }\n\n /* Live preview chip */\n .folder-preview {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-subtle);\n }\n .folder-preview-chip {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n flex-shrink: 0;\n transition: background-color 150ms ease, color 150ms ease;\n }\n .folder-preview-name {\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .form-field {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n .form-field label {\n font-weight: 600;\n font-size: 13px;\n letter-spacing: 0.01em;\n color: var(--mj-text-secondary);\n }\n\n .form-field label.required::after {\n content: '*';\n color: var(--mj-status-error);\n margin-left: 4px;\n }\n\n .full-width { width: 100%; }\n\n .error-message {\n color: var(--mj-status-error);\n font-size: 12px;\n }\n\n /* Color Picker */\n .color-picker-section {\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 14px;\n background: var(--mj-bg-surface);\n }\n\n .color-palette {\n display: grid;\n grid-template-columns: repeat(10, 1fr);\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .color-swatch {\n width: 100%;\n aspect-ratio: 1;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n padding: 0;\n transition: transform 120ms ease, box-shadow 120ms ease;\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n }\n\n .color-swatch:hover {\n transform: scale(1.12);\n }\n\n .color-swatch.selected {\n box-shadow: 0 0 0 2px var(--mj-bg-surface), 0 0 0 4px var(--mj-brand-primary);\n }\n\n .custom-color-input {\n display: flex;\n align-items: center;\n gap: 10px;\n padding-top: 14px;\n border-top: 1px solid var(--mj-border-subtle);\n }\n\n .custom-color-input label {\n margin: 0;\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-secondary);\n }\n\n .custom-color-picker {\n width: 44px;\n height: 30px;\n padding: 2px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n cursor: pointer;\n background: var(--mj-bg-surface);\n }\n\n .color-value {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 13px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n }\n\n /* Icon Selector */\n .icon-grid {\n display: grid;\n grid-template-columns: repeat(10, 1fr);\n gap: 8px;\n max-height: 184px;\n overflow-y: auto;\n padding: 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface);\n }\n\n .icon-option {\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-card);\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-secondary);\n transition: transform 120ms ease, border-color 120ms ease, background 120ms ease;\n }\n\n .icon-option:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n transform: translateY(-1px);\n }\n\n .icon-option.selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 1px var(--mj-brand-primary);\n }\n\n .icon-option i { font-size: 17px; }\n\n /* Scrollbar */\n .icon-grid::-webkit-scrollbar { width: 8px; }\n .icon-grid::-webkit-scrollbar-track {\n background: transparent;\n }\n .icon-grid::-webkit-scrollbar-thumb {\n background: var(--mj-border-strong);\n border-radius: 4px;\n }\n .icon-grid::-webkit-scrollbar-thumb:hover {\n background: var(--mj-text-disabled);\n }\n `]\n})\nexport class ProjectFormModalComponent extends BaseAngularComponent implements OnInit {\n @Input() dialogRef!: MJDialogRef;\n @Input() project: MJProjectEntity | null = null;\n @Input() environmentId!: string;\n @Input() currentUser!: UserInfo;\n /** When creating a new folder, the parent folder ID for nesting (null = top level). */\n @Input() parentId: string | null = null;\n\n @Output() projectSaved = new EventEmitter<MJProjectEntity>();\n\n public formData: ProjectFormData = {\n name: '',\n description: '',\n color: '#0076B6',\n icon: 'fa-folder'\n };\n\n public showNameError = false;\n public isEditMode = false;\n public availableColors = DEFAULT_PROJECT_COLORS;\n public availableIcons = DEFAULT_PROJECT_ICONS;\n\n /** Translucent tint of the selected color, used behind the preview/icon glyph. */\n public get chipBackground(): string {\n const hex = this.formData.color || '#0076B6';\n // 8-digit hex (#RRGGBBAA) — ~14% alpha tint of the chosen color\n return /^#[0-9a-fA-F]{6}$/.test(hex) ? `${hex}24` : hex;\n }\n\n constructor(private cdr: ChangeDetectorRef) {\n super();}\n\n ngOnInit(): void {\n this.isEditMode = this.project != null;\n\n if (this.project) {\n this.loadProjectData();\n }\n }\n\n private loadProjectData(): void {\n if (!this.project) return;\n\n this.formData = {\n name: this.project.Name || '',\n description: this.project.Description || '',\n color: this.project.Color || '#0076B6',\n icon: this.project.Icon || 'fa-folder'\n };\n }\n\n selectColor(color: string): void {\n this.formData.color = color;\n this.cdr.detectChanges();\n }\n\n selectIcon(icon: string): void {\n this.formData.icon = icon;\n this.cdr.detectChanges();\n }\n\n async onSave(): Promise<void> {\n // Validate\n if (!this.formData.name.trim()) {\n this.showNameError = true;\n this.cdr.detectChanges();\n return;\n }\n\n this.showNameError = false;\n\n try {\n const md = this.ProviderToUse;\n const project = this.project || await md.GetEntityObject<MJProjectEntity>('MJ: Projects', this.currentUser);\n\n project.Name = this.formData.name.trim();\n project.Description = this.formData.description.trim() || null;\n project.Color = this.formData.color;\n project.Icon = this.formData.icon;\n\n if (!this.isEditMode) {\n project.EnvironmentID = this.environmentId;\n project.IsArchived = false;\n if (this.parentId) {\n project.ParentID = this.parentId;\n }\n }\n\n const saved = await project.Save();\n if (saved) {\n this.projectSaved.emit(project);\n this.dialogRef.Close();\n } else {\n throw new Error('Failed to save project');\n }\n } catch (error) {\n console.error('Error saving project:', error);\n alert('Failed to save project. Please try again.');\n }\n }\n\n onCancel(): void {\n this.dialogRef.Close();\n }\n}\n"]}
@@ -246,6 +246,28 @@ export declare abstract class BaseRealtimeChannelClient<TSurface extends object
246
246
  * Default: no-op.
247
247
  */
248
248
  protected OnInitialize(): void;
249
+ /**
250
+ * Max time {@link ResolveAgentSessionId} waits for the session id to bind before giving up, and the
251
+ * poll interval it re-checks on. Protected so tests can shrink the wait; production keeps the
252
+ * defaults (the real mint race is sub-second, 8s is generous headroom).
253
+ */
254
+ protected SessionIdWaitTimeoutMs: number;
255
+ protected SessionIdWaitIntervalMs: number;
256
+ /**
257
+ * Resolves the live {@link RealtimeChannelContext.AgentSessionID}, briefly WAITING for it when it
258
+ * isn't bound yet rather than giving up instantly. `AgentSessionID` is a live getter over the
259
+ * session service's current id: it reads `null` in the window BEFORE the session mints (the
260
+ * realtime model can fire a tool call the very first beat it connects, before `mintSession`
261
+ * resolves) and again AFTER teardown. Server-backed tool paths (e.g. the Remote Browser channel's
262
+ * `browser_*` tools) call this instead of reading `Context?.AgentSessionID` synchronously, so a tool
263
+ * invoked a beat early WAITS for the session to come live — defense-in-depth against the
264
+ * "session id missing" race — instead of returning a hard failure to the model.
265
+ *
266
+ * Returns the id as soon as it's non-null (the common path resolves immediately, no delay), or
267
+ * `null` if it's still unbound after {@link SessionIdWaitTimeoutMs} — or the channel was
268
+ * {@link Dispose}d in the meantime (`Context` goes null, so we stop waiting on a torn-down session).
269
+ */
270
+ protected ResolveAgentSessionId(): Promise<string | null>;
249
271
  /**
250
272
  * Serializes the channel's current state of record (the payload persisted on the
251
273
  * session's channel row), or `null` when the channel keeps no persistent state.
@@ -1 +1 @@
1
- {"version":3,"file":"base-realtime-channel-client.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/channels/base-realtime-channel-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEvE;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,sBAAsB;IACrC,iGAAiG;IACjG,SAAS,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEpC;;;;;OAKG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAErC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAEhC;;;;;;;OAOG;IACH,qBAAqB,CAAC,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAEnD;;;;;;;OAOG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE1E;;;;;;;OAOG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAE9B;;;;;;;;;;;;;;;;OAgBG;IACH,mBAAmB,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAC5G;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,wBAAwB;IACvC,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,8FAA8F;IAC9F,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,8BAAsB,yBAAyB,CAAC,QAAQ,SAAS,MAAM,GAAG,MAAM;IAC9E;;;OAGG;IACH,SAAS,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAAQ;IAExD;;;;OAIG;IACH,aAAoB,WAAW,IAAI,MAAM,CAAC;IAE1C;;;;OAIG;IACH,aAAoB,cAAc,IAAI,MAAM,CAAC;IAE7C,wFAAwF;IACxF,aAAoB,QAAQ,IAAI,MAAM,CAAC;IAEvC,uFAAuF;IACvF,aAAoB,OAAO,IAAI,MAAM,CAAC;IAEtC;;;;OAIG;aACa,kBAAkB,IAAI,sBAAsB,EAAE;IAE9D;;;;;;;;OAQG;aACa,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAE5F;;;;;;;;;;;;;OAaG;IACI,mBAAmB,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI;IAInD;;;;;OAKG;IACI,UAAU,IAAI,OAAO;IAI5B;;;;;;;;;;OAUG;IACI,oBAAoB,IAAI,wBAAwB,GAAG,IAAI;IAI9D;;;;;;;OAOG;aACa,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAErD;;;;;OAKG;IACI,aAAa,IAAI,IAAI;IAI5B;;;;OAIG;IACI,UAAU,CAAC,GAAG,EAAE,sBAAsB,GAAG,IAAI;IAKpD;;;;OAIG;IACH,SAAS,CAAC,YAAY,IAAI,IAAI;IAI9B;;;;OAIG;IACI,cAAc,IAAI,MAAM,GAAG,IAAI;IAItC;;;;;;;;;;OAUG;IACI,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAI/C;;;;;OAKG;IACI,gBAAgB,IAAI,IAAI;IAI/B;;;;;OAKG;IACI,OAAO,IAAI,IAAI;CAIvB"}
1
+ {"version":3,"file":"base-realtime-channel-client.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/channels/base-realtime-channel-client.ts"],"names":[],"mappings":"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"}
@@ -113,6 +113,46 @@ export class BaseRealtimeChannelClient {
113
113
  OnInitialize() {
114
114
  // default: nothing to initialize
115
115
  }
116
+ /**
117
+ * Max time {@link ResolveAgentSessionId} waits for the session id to bind before giving up, and the
118
+ * poll interval it re-checks on. Protected so tests can shrink the wait; production keeps the
119
+ * defaults (the real mint race is sub-second, 8s is generous headroom).
120
+ */
121
+ SessionIdWaitTimeoutMs = 8000;
122
+ SessionIdWaitIntervalMs = 200;
123
+ /**
124
+ * Resolves the live {@link RealtimeChannelContext.AgentSessionID}, briefly WAITING for it when it
125
+ * isn't bound yet rather than giving up instantly. `AgentSessionID` is a live getter over the
126
+ * session service's current id: it reads `null` in the window BEFORE the session mints (the
127
+ * realtime model can fire a tool call the very first beat it connects, before `mintSession`
128
+ * resolves) and again AFTER teardown. Server-backed tool paths (e.g. the Remote Browser channel's
129
+ * `browser_*` tools) call this instead of reading `Context?.AgentSessionID` synchronously, so a tool
130
+ * invoked a beat early WAITS for the session to come live — defense-in-depth against the
131
+ * "session id missing" race — instead of returning a hard failure to the model.
132
+ *
133
+ * Returns the id as soon as it's non-null (the common path resolves immediately, no delay), or
134
+ * `null` if it's still unbound after {@link SessionIdWaitTimeoutMs} — or the channel was
135
+ * {@link Dispose}d in the meantime (`Context` goes null, so we stop waiting on a torn-down session).
136
+ */
137
+ async ResolveAgentSessionId() {
138
+ const immediate = this.Context?.AgentSessionID ?? null;
139
+ if (immediate) {
140
+ return immediate;
141
+ }
142
+ const intervalMs = Math.max(1, this.SessionIdWaitIntervalMs);
143
+ for (let waited = 0; waited < this.SessionIdWaitTimeoutMs; waited += intervalMs) {
144
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
145
+ // Context goes null on Dispose() — the session is gone, stop waiting.
146
+ if (!this.Context) {
147
+ return null;
148
+ }
149
+ const id = this.Context.AgentSessionID;
150
+ if (id) {
151
+ return id;
152
+ }
153
+ }
154
+ return this.Context?.AgentSessionID ?? null;
155
+ }
116
156
  /**
117
157
  * Serializes the channel's current state of record (the payload persisted on the
118
158
  * session's channel row), or `null` when the channel keeps no persistent state.