@design.estate/wcctools 5.1.0 → 6.1.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 (262) hide show
  1. package/.smartconfig.json +7 -3
  2. package/changelog.md +28 -0
  3. package/cli.js +3 -0
  4. package/dist_shell/bundle.js +2970 -0
  5. package/dist_shell/bundle.js.map +1 -0
  6. package/dist_shell/bundle.js.third-party-notices.json +1722 -0
  7. package/dist_shell/bundle.js.third-party-notices.txt +4195 -0
  8. package/dist_shell/index.html +21 -0
  9. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  10. package/dist_ts/00_commitinfo_data.js +9 -0
  11. package/dist_ts/classes.devapi.d.ts +11 -0
  12. package/dist_ts/classes.devapi.js +21 -0
  13. package/dist_ts/classes.devserver.d.ts +49 -0
  14. package/dist_ts/classes.devserver.js +142 -0
  15. package/dist_ts/cli.d.ts +6 -0
  16. package/dist_ts/cli.js +51 -0
  17. package/dist_ts/index.d.ts +3 -0
  18. package/dist_ts/index.js +4 -0
  19. package/dist_ts/paths.d.ts +4 -0
  20. package/dist_ts/paths.js +6 -0
  21. package/dist_ts/plugins.d.ts +20 -0
  22. package/dist_ts/plugins.js +27 -0
  23. package/dist_ts/shellcontent.d.ts +9 -0
  24. package/dist_ts/shellcontent.js +42 -0
  25. package/dist_ts/standards/baselines.d.ts +27 -0
  26. package/dist_ts/standards/baselines.js +83 -0
  27. package/dist_ts/standards/checker.d.ts +21 -0
  28. package/dist_ts/standards/checker.js +88 -0
  29. package/dist_ts/standards/classes.gitrepo.d.ts +13 -0
  30. package/dist_ts/standards/classes.gitrepo.js +43 -0
  31. package/dist_ts/standards/classes.sourceedits.d.ts +15 -0
  32. package/dist_ts/standards/classes.sourceedits.js +54 -0
  33. package/dist_ts/standards/classes.standardfixer.d.ts +40 -0
  34. package/dist_ts/standards/classes.standardfixer.js +210 -0
  35. package/dist_ts/standards/classes.standardproject.d.ts +80 -0
  36. package/dist_ts/standards/classes.standardproject.js +195 -0
  37. package/dist_ts/standards/classes.standardtree.d.ts +56 -0
  38. package/dist_ts/standards/classes.standardtree.js +207 -0
  39. package/dist_ts/standards/cli.d.ts +7 -0
  40. package/dist_ts/standards/cli.js +89 -0
  41. package/dist_ts/standards/config.d.ts +33 -0
  42. package/dist_ts/standards/config.js +139 -0
  43. package/dist_ts/standards/errors.d.ts +9 -0
  44. package/dist_ts/standards/errors.js +17 -0
  45. package/dist_ts/standards/helpers.ast.d.ts +56 -0
  46. package/dist_ts/standards/helpers.ast.js +172 -0
  47. package/dist_ts/standards/helpers.paths.d.ts +36 -0
  48. package/dist_ts/standards/helpers.paths.js +86 -0
  49. package/dist_ts/standards/index.d.ts +14 -0
  50. package/dist_ts/standards/index.js +15 -0
  51. package/dist_ts/standards/output.d.ts +7 -0
  52. package/dist_ts/standards/output.js +57 -0
  53. package/dist_ts/standards/ratchet.d.ts +15 -0
  54. package/dist_ts/standards/ratchet.js +45 -0
  55. package/dist_ts/standards/rule.d.ts +50 -0
  56. package/dist_ts/standards/rule.js +9 -0
  57. package/dist_ts/standards/rules/barrel.element-index.d.ts +7 -0
  58. package/dist_ts/standards/rules/barrel.element-index.js +108 -0
  59. package/dist_ts/standards/rules/barrel.no-demo-export.d.ts +3 -0
  60. package/dist_ts/standards/rules/barrel.no-demo-export.js +29 -0
  61. package/dist_ts/standards/rules/demo.fixture-not-exported.d.ts +3 -0
  62. package/dist_ts/standards/rules/demo.fixture-not-exported.js +29 -0
  63. package/dist_ts/standards/rules/demo.groups.d.ts +6 -0
  64. package/dist_ts/standards/rules/demo.groups.js +72 -0
  65. package/dist_ts/standards/rules/demo.required.d.ts +3 -0
  66. package/dist_ts/standards/rules/demo.required.js +48 -0
  67. package/dist_ts/standards/rules/demo.separate-file.d.ts +3 -0
  68. package/dist_ts/standards/rules/demo.separate-file.js +38 -0
  69. package/dist_ts/standards/rules/demo.shape.d.ts +3 -0
  70. package/dist_ts/standards/rules/demo.shape.js +38 -0
  71. package/dist_ts/standards/rules/docs.component-index.d.ts +8 -0
  72. package/dist_ts/standards/rules/docs.component-index.js +84 -0
  73. package/dist_ts/standards/rules/element.exported.d.ts +3 -0
  74. package/dist_ts/standards/rules/element.exported.js +33 -0
  75. package/dist_ts/standards/rules/element.literal-tag.d.ts +3 -0
  76. package/dist_ts/standards/rules/element.literal-tag.js +30 -0
  77. package/dist_ts/standards/rules/element.reachable.d.ts +3 -0
  78. package/dist_ts/standards/rules/element.reachable.js +48 -0
  79. package/dist_ts/standards/rules/element.tag-map.d.ts +3 -0
  80. package/dist_ts/standards/rules/element.tag-map.js +83 -0
  81. package/dist_ts/standards/rules/i18n.literals.d.ts +3 -0
  82. package/dist_ts/standards/rules/i18n.literals.js +52 -0
  83. package/dist_ts/standards/rules/index.d.ts +12 -0
  84. package/dist_ts/standards/rules/index.js +83 -0
  85. package/dist_ts/standards/rules/layout.element-folder.d.ts +3 -0
  86. package/dist_ts/standards/rules/layout.element-folder.js +47 -0
  87. package/dist_ts/standards/rules/layout.entry-file-name.d.ts +3 -0
  88. package/dist_ts/standards/rules/layout.entry-file-name.js +71 -0
  89. package/dist_ts/standards/rules/layout.entry.d.ts +3 -0
  90. package/dist_ts/standards/rules/layout.entry.js +38 -0
  91. package/dist_ts/standards/rules/layout.group-folder.d.ts +3 -0
  92. package/dist_ts/standards/rules/layout.group-folder.js +22 -0
  93. package/dist_ts/standards/rules/layout.shared-00.d.ts +3 -0
  94. package/dist_ts/standards/rules/layout.shared-00.js +36 -0
  95. package/dist_ts/standards/rules/naming.class-matches-tag.d.ts +3 -0
  96. package/dist_ts/standards/rules/naming.class-matches-tag.js +26 -0
  97. package/dist_ts/standards/rules/naming.lowercase-files.d.ts +3 -0
  98. package/dist_ts/standards/rules/naming.lowercase-files.js +26 -0
  99. package/dist_ts/standards/rules/naming.tag-prefix.d.ts +3 -0
  100. package/dist_ts/standards/rules/naming.tag-prefix.js +23 -0
  101. package/dist_ts/standards/rules/props.accessor.d.ts +3 -0
  102. package/dist_ts/standards/rules/props.accessor.js +35 -0
  103. package/dist_ts/standards/rules/props.object-no-attribute.d.ts +3 -0
  104. package/dist_ts/standards/rules/props.object-no-attribute.js +42 -0
  105. package/dist_ts/standards/rules/props.reflect-primitive.d.ts +5 -0
  106. package/dist_ts/standards/rules/props.reflect-primitive.js +41 -0
  107. package/dist_ts/standards/rules/props.typed.d.ts +6 -0
  108. package/dist_ts/standards/rules/props.typed.js +64 -0
  109. package/dist_ts/standards/rules/shared.d.ts +38 -0
  110. package/dist_ts/standards/rules/shared.js +103 -0
  111. package/dist_ts/standards/rules/style.bdtheme.d.ts +5 -0
  112. package/dist_ts/standards/rules/style.bdtheme.js +28 -0
  113. package/dist_ts_interfaces/bridge.d.ts +88 -0
  114. package/dist_ts_interfaces/bridge.js +2 -0
  115. package/dist_ts_interfaces/catalog.d.ts +32 -0
  116. package/dist_ts_interfaces/catalog.js +6 -0
  117. package/dist_ts_interfaces/index.d.ts +4 -0
  118. package/dist_ts_interfaces/index.js +5 -0
  119. package/dist_ts_interfaces/plugins.d.ts +2 -0
  120. package/dist_ts_interfaces/plugins.js +3 -0
  121. package/dist_ts_interfaces/requests.d.ts +15 -0
  122. package/dist_ts_interfaces/requests.js +2 -0
  123. package/dist_ts_interfaces/standard.d.ts +93 -0
  124. package/dist_ts_interfaces/standard.js +6 -0
  125. package/dist_ts_shared/index.d.ts +1 -0
  126. package/dist_ts_shared/index.js +2 -0
  127. package/dist_ts_shared/plugins.d.ts +2 -0
  128. package/dist_ts_shared/plugins.js +3 -0
  129. package/dist_ts_shared/previewroute.d.ts +18 -0
  130. package/dist_ts_shared/previewroute.js +47 -0
  131. package/{dist_ts_web → dist_ts_shell}/elements/wcc-contextmenu.js +1 -1
  132. package/dist_ts_shell/elements/wcc-preview-frame.d.ts +23 -0
  133. package/dist_ts_shell/elements/wcc-preview-frame.js +197 -0
  134. package/dist_ts_shell/elements/wcc-properties.d.ts +49 -0
  135. package/{dist_ts_web → dist_ts_shell}/elements/wcc-properties.js +149 -216
  136. package/{dist_ts_web → dist_ts_shell}/elements/wcc-record-button.js +1 -1
  137. package/{dist_ts_web → dist_ts_shell}/elements/wcc-recording-panel.d.ts +2 -2
  138. package/{dist_ts_web → dist_ts_shell}/elements/wcc-recording-panel.js +12 -16
  139. package/dist_ts_shell/elements/wcc-shell.d.ts +87 -0
  140. package/dist_ts_shell/elements/wcc-shell.js +524 -0
  141. package/{dist_ts_web → dist_ts_shell}/elements/wcc-sidebar.d.ts +12 -10
  142. package/{dist_ts_web → dist_ts_shell}/elements/wcc-sidebar.js +137 -183
  143. package/dist_ts_shell/index.d.ts +4 -0
  144. package/dist_ts_shell/index.js +5 -0
  145. package/dist_ts_shell/plugins.d.ts +4 -0
  146. package/dist_ts_shell/plugins.js +5 -0
  147. package/dist_ts_shell/previewconnection.d.ts +30 -0
  148. package/dist_ts_shell/previewconnection.js +86 -0
  149. package/dist_ts_shell/services/recorder.service.js +353 -0
  150. package/dist_ts_web/00_commitinfo_data.js +2 -2
  151. package/dist_ts_web/catalog.d.ts +45 -0
  152. package/dist_ts_web/catalog.js +153 -0
  153. package/dist_ts_web/elementproperties.d.ts +15 -0
  154. package/dist_ts_web/elementproperties.js +89 -0
  155. package/dist_ts_web/elements/wcc-preview.d.ts +19 -0
  156. package/dist_ts_web/elements/wcc-preview.js +164 -0
  157. package/dist_ts_web/elements/wcc-standalone-notice.d.ts +14 -0
  158. package/dist_ts_web/elements/wcc-standalone-notice.js +110 -0
  159. package/dist_ts_web/elements/wcctools.helpers.d.ts +2 -6
  160. package/dist_ts_web/elements/wcctools.helpers.js +1 -7
  161. package/dist_ts_web/index.d.ts +2 -5
  162. package/dist_ts_web/index.js +38 -17
  163. package/dist_ts_web/previewbridge.d.ts +39 -0
  164. package/dist_ts_web/previewbridge.js +153 -0
  165. package/dist_ts_web/renderedtree.d.ts +5 -0
  166. package/dist_ts_web/renderedtree.js +37 -0
  167. package/dist_ts_web/wcctools.interfaces.d.ts +3 -6
  168. package/dist_ts_web/wcctools.interfaces.js +1 -1
  169. package/dist_ts_web/wcctools.plugins.d.ts +3 -1
  170. package/dist_ts_web/wcctools.plugins.js +4 -2
  171. package/package.json +17 -7
  172. package/readme.md +93 -28
  173. package/ts/00_commitinfo_data.ts +8 -0
  174. package/ts/classes.devapi.ts +25 -0
  175. package/ts/classes.devserver.ts +161 -0
  176. package/ts/cli.ts +53 -0
  177. package/ts/index.ts +3 -0
  178. package/ts/paths.ts +7 -0
  179. package/ts/plugins.ts +37 -0
  180. package/ts/shellcontent.ts +42 -0
  181. package/ts/standards/baselines.ts +100 -0
  182. package/ts/standards/checker.ts +114 -0
  183. package/ts/standards/classes.gitrepo.ts +48 -0
  184. package/ts/standards/classes.sourceedits.ts +58 -0
  185. package/ts/standards/classes.standardfixer.ts +234 -0
  186. package/ts/standards/classes.standardproject.ts +250 -0
  187. package/ts/standards/classes.standardtree.ts +220 -0
  188. package/ts/standards/cli.ts +86 -0
  189. package/ts/standards/config.ts +160 -0
  190. package/ts/standards/errors.ts +18 -0
  191. package/ts/standards/helpers.ast.ts +208 -0
  192. package/ts/standards/helpers.paths.ts +119 -0
  193. package/ts/standards/index.ts +22 -0
  194. package/ts/standards/output.ts +65 -0
  195. package/ts/standards/ratchet.ts +63 -0
  196. package/ts/standards/rule.ts +58 -0
  197. package/ts/standards/rules/barrel.element-index.ts +112 -0
  198. package/ts/standards/rules/barrel.no-demo-export.ts +30 -0
  199. package/ts/standards/rules/demo.fixture-not-exported.ts +30 -0
  200. package/ts/standards/rules/demo.groups.ts +74 -0
  201. package/ts/standards/rules/demo.required.ts +49 -0
  202. package/ts/standards/rules/demo.separate-file.ts +40 -0
  203. package/ts/standards/rules/demo.shape.ts +42 -0
  204. package/ts/standards/rules/docs.component-index.ts +88 -0
  205. package/ts/standards/rules/element.exported.ts +35 -0
  206. package/ts/standards/rules/element.literal-tag.ts +30 -0
  207. package/ts/standards/rules/element.reachable.ts +49 -0
  208. package/ts/standards/rules/element.tag-map.ts +87 -0
  209. package/ts/standards/rules/i18n.literals.ts +58 -0
  210. package/ts/standards/rules/index.ts +87 -0
  211. package/ts/standards/rules/layout.element-folder.ts +49 -0
  212. package/ts/standards/rules/layout.entry-file-name.ts +73 -0
  213. package/ts/standards/rules/layout.entry.ts +40 -0
  214. package/ts/standards/rules/layout.group-folder.ts +23 -0
  215. package/ts/standards/rules/layout.shared-00.ts +37 -0
  216. package/ts/standards/rules/naming.class-matches-tag.ts +27 -0
  217. package/ts/standards/rules/naming.lowercase-files.ts +27 -0
  218. package/ts/standards/rules/naming.tag-prefix.ts +24 -0
  219. package/ts/standards/rules/props.accessor.ts +37 -0
  220. package/ts/standards/rules/props.object-no-attribute.ts +45 -0
  221. package/ts/standards/rules/props.reflect-primitive.ts +44 -0
  222. package/ts/standards/rules/props.typed.ts +70 -0
  223. package/ts/standards/rules/shared.ts +127 -0
  224. package/ts/standards/rules/style.bdtheme.ts +31 -0
  225. package/ts_web/00_commitinfo_data.ts +1 -1
  226. package/ts_web/catalog.ts +194 -0
  227. package/ts_web/elementproperties.ts +96 -0
  228. package/ts_web/elements/wcc-preview.ts +111 -0
  229. package/ts_web/elements/wcc-standalone-notice.ts +65 -0
  230. package/ts_web/elements/wcctools.helpers.ts +2 -10
  231. package/ts_web/index.ts +38 -19
  232. package/ts_web/previewbridge.ts +189 -0
  233. package/ts_web/readme.md +26 -85
  234. package/ts_web/renderedtree.ts +43 -0
  235. package/ts_web/wcctools.interfaces.ts +4 -7
  236. package/ts_web/wcctools.plugins.ts +6 -1
  237. package/dist_bundle/bundle.js +0 -92419
  238. package/dist_bundle/bundle.js.map +0 -1
  239. package/dist_ts_web/elements/wcc-dashboard.d.ts +0 -119
  240. package/dist_ts_web/elements/wcc-dashboard.js +0 -700
  241. package/dist_ts_web/elements/wcc-frame.d.ts +0 -22
  242. package/dist_ts_web/elements/wcc-frame.js +0 -256
  243. package/dist_ts_web/elements/wcc-properties.d.ts +0 -43
  244. package/dist_ts_web/pages/index.d.ts +0 -1
  245. package/dist_ts_web/pages/index.js +0 -2
  246. package/dist_ts_web/services/recorder.service.js +0 -353
  247. package/ts_web/elements/wcc-contextmenu.ts +0 -291
  248. package/ts_web/elements/wcc-dashboard.ts +0 -672
  249. package/ts_web/elements/wcc-frame.ts +0 -189
  250. package/ts_web/elements/wcc-properties.ts +0 -1089
  251. package/ts_web/elements/wcc-record-button.ts +0 -108
  252. package/ts_web/elements/wcc-recording-panel.ts +0 -1010
  253. package/ts_web/elements/wcc-sidebar.ts +0 -1470
  254. package/ts_web/pages/index.ts +0 -1
  255. package/ts_web/services/recorder.service.ts +0 -451
  256. package/ts_web/types/dom-mediacapture-stub/index.d.ts +0 -12
  257. package/ts_web/types/dom-mediacapture-stub/package.json +0 -6
  258. package/ts_web/types/dom-webcodecs-stub/index.d.ts +0 -2
  259. package/ts_web/types/dom-webcodecs-stub/package.json +0 -6
  260. /package/{dist_ts_web → dist_ts_shell}/elements/wcc-contextmenu.d.ts +0 -0
  261. /package/{dist_ts_web → dist_ts_shell}/elements/wcc-record-button.d.ts +0 -0
  262. /package/{dist_ts_web → dist_ts_shell}/services/recorder.service.d.ts +0 -0
@@ -1,1010 +0,0 @@
1
- import { DeesElement, customElement, html, css, property, state, type TemplateResult } from '@design.estate/dees-element';
2
- import { RecorderService, type TOutputFormat } from '../services/recorder.service.js';
3
- import type { WccDashboard } from './wcc-dashboard.js';
4
-
5
- @customElement('wcc-recording-panel')
6
- export class WccRecordingPanel extends DeesElement {
7
- // External configuration
8
- @property({ attribute: false })
9
- accessor dashboardRef: WccDashboard;
10
-
11
- // Panel state
12
- @state()
13
- accessor panelState: 'options' | 'recording' | 'preview' = 'options';
14
-
15
- // Recording options
16
- @state()
17
- accessor recordingMode: 'viewport' | 'screen' = 'viewport';
18
-
19
- @state()
20
- accessor outputFormat: TOutputFormat = 'mp4';
21
-
22
- @state()
23
- accessor audioEnabled: boolean = false;
24
-
25
- @state()
26
- accessor selectedMicrophoneId: string = '';
27
-
28
- @state()
29
- accessor availableMicrophones: MediaDeviceInfo[] = [];
30
-
31
- @state()
32
- accessor audioLevel: number = 0;
33
-
34
- // Recording state
35
- @state()
36
- accessor recordingDuration: number = 0;
37
-
38
- // Preview/trim state
39
- @state()
40
- accessor previewVideoUrl: string = '';
41
-
42
- @state()
43
- accessor trimStart: number = 0;
44
-
45
- @state()
46
- accessor trimEnd: number = 0;
47
-
48
- @state()
49
- accessor videoDuration: number = 0;
50
-
51
- @state()
52
- accessor isDraggingTrim: 'start' | 'end' | null = null;
53
-
54
- @state()
55
- accessor isExporting: boolean = false;
56
-
57
- // Service instance
58
- private recorderService: RecorderService;
59
-
60
- constructor() {
61
- super();
62
- this.recorderService = new RecorderService({
63
- onDurationUpdate: (duration) => {
64
- this.recordingDuration = duration;
65
- this.dispatchEvent(new CustomEvent('duration-update', {
66
- detail: { duration },
67
- bubbles: true,
68
- composed: true
69
- }));
70
- },
71
- onRecordingComplete: (blob) => {
72
- this.handleRecordingComplete(blob);
73
- },
74
- onAudioLevelUpdate: (level) => {
75
- this.audioLevel = level;
76
- },
77
- onStreamEnded: () => {
78
- this.stopRecording();
79
- }
80
- });
81
- }
82
-
83
- public static styles = [
84
- css`
85
- :host {
86
- /* CSS Variables */
87
- --background: #0a0a0a;
88
- --foreground: #e5e5e5;
89
- --input: #141414;
90
- --primary: #3b82f6;
91
- --border: rgba(255, 255, 255, 0.06);
92
- --radius-sm: 2px;
93
- --radius-md: 4px;
94
- --radius-lg: 6px;
95
- }
96
-
97
- /* Recording Options Panel */
98
- .recording-options-panel {
99
- position: fixed;
100
- right: 16px;
101
- bottom: 116px;
102
- width: 360px;
103
- background: #0c0c0c;
104
- border: 1px solid rgba(255, 255, 255, 0.1);
105
- border-radius: var(--radius-md);
106
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
107
- z-index: 1000;
108
- overflow: hidden;
109
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
110
- }
111
-
112
- .recording-options-header {
113
- padding: 0.75rem 1rem;
114
- background: rgba(255, 255, 255, 0.02);
115
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
116
- display: flex;
117
- justify-content: space-between;
118
- align-items: center;
119
- }
120
-
121
- .recording-options-title {
122
- font-size: 0.8rem;
123
- font-weight: 500;
124
- color: #ccc;
125
- }
126
-
127
- .recording-options-close {
128
- width: 24px;
129
- height: 24px;
130
- background: transparent;
131
- border: none;
132
- color: #666;
133
- cursor: pointer;
134
- display: flex;
135
- align-items: center;
136
- justify-content: center;
137
- border-radius: var(--radius-sm);
138
- transition: all 0.15s ease;
139
- }
140
-
141
- .recording-options-close:hover {
142
- background: rgba(255, 255, 255, 0.05);
143
- color: #999;
144
- }
145
-
146
- .recording-options-content {
147
- padding: 1rem;
148
- }
149
-
150
- .recording-option-group {
151
- margin-bottom: 1rem;
152
- }
153
-
154
- .recording-option-group:last-child {
155
- margin-bottom: 0;
156
- }
157
-
158
- .recording-option-label {
159
- font-size: 0.7rem;
160
- font-weight: 500;
161
- color: #888;
162
- text-transform: uppercase;
163
- letter-spacing: 0.05em;
164
- margin-bottom: 0.5rem;
165
- }
166
-
167
- .recording-mode-buttons {
168
- display: flex;
169
- gap: 0.5rem;
170
- }
171
-
172
- .recording-mode-btn {
173
- flex: 1;
174
- padding: 0.6rem 0.75rem;
175
- background: var(--input);
176
- border: 1px solid transparent;
177
- border-radius: var(--radius-sm);
178
- color: #999;
179
- font-size: 0.75rem;
180
- cursor: pointer;
181
- transition: all 0.15s ease;
182
- text-align: center;
183
- }
184
-
185
- .recording-mode-btn:hover {
186
- border-color: var(--primary);
187
- color: #ccc;
188
- }
189
-
190
- .recording-mode-btn.selected {
191
- background: rgba(59, 130, 246, 0.15);
192
- border-color: var(--primary);
193
- color: var(--primary);
194
- }
195
-
196
- .audio-toggle {
197
- display: flex;
198
- align-items: center;
199
- gap: 0.5rem;
200
- margin-bottom: 0.75rem;
201
- }
202
-
203
- .audio-toggle input[type="checkbox"] {
204
- width: 1rem;
205
- height: 1rem;
206
- accent-color: var(--primary);
207
- }
208
-
209
- .audio-toggle label {
210
- font-size: 0.75rem;
211
- color: #999;
212
- cursor: pointer;
213
- }
214
-
215
- .microphone-select {
216
- width: 100%;
217
- padding: 0.5rem 0.75rem;
218
- background: var(--input);
219
- border: 1px solid transparent;
220
- border-radius: var(--radius-sm);
221
- color: var(--foreground);
222
- font-size: 0.75rem;
223
- outline: none;
224
- cursor: pointer;
225
- transition: all 0.15s ease;
226
- }
227
-
228
- .microphone-select:focus {
229
- border-color: var(--primary);
230
- }
231
-
232
- .microphone-select:disabled {
233
- opacity: 0.5;
234
- cursor: not-allowed;
235
- }
236
-
237
- .audio-level-container {
238
- margin-top: 0.75rem;
239
- padding: 0.5rem;
240
- background: rgba(255, 255, 255, 0.02);
241
- border-radius: var(--radius-sm);
242
- }
243
-
244
- .audio-level-label {
245
- font-size: 0.65rem;
246
- color: #666;
247
- margin-bottom: 0.25rem;
248
- }
249
-
250
- .audio-level-bar {
251
- height: 8px;
252
- background: var(--input);
253
- border-radius: 4px;
254
- overflow: hidden;
255
- }
256
-
257
- .audio-level-fill {
258
- height: 100%;
259
- background: linear-gradient(90deg, #22c55e, #84cc16, #eab308);
260
- border-radius: 4px;
261
- transition: width 0.1s ease;
262
- }
263
-
264
- .start-recording-btn {
265
- width: 100%;
266
- padding: 0.75rem;
267
- background: #dc2626;
268
- border: none;
269
- border-radius: var(--radius-sm);
270
- color: white;
271
- font-size: 0.8rem;
272
- font-weight: 500;
273
- cursor: pointer;
274
- transition: all 0.15s ease;
275
- margin-top: 1rem;
276
- display: flex;
277
- align-items: center;
278
- justify-content: center;
279
- gap: 0.5rem;
280
- }
281
-
282
- .start-recording-btn:hover {
283
- background: #b91c1c;
284
- }
285
-
286
- .start-recording-btn .rec-dot {
287
- width: 10px;
288
- height: 10px;
289
- background: white;
290
- border-radius: 50%;
291
- }
292
-
293
- /* Preview Modal */
294
- .preview-modal-overlay {
295
- position: fixed;
296
- top: 0;
297
- left: 0;
298
- right: 0;
299
- bottom: 0;
300
- background: rgba(0, 0, 0, 0.8);
301
- display: flex;
302
- align-items: center;
303
- justify-content: center;
304
- z-index: 1000;
305
- backdrop-filter: blur(4px);
306
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
307
- }
308
-
309
- .preview-modal {
310
- width: 90%;
311
- max-width: 800px;
312
- background: #0c0c0c;
313
- border: 1px solid rgba(255, 255, 255, 0.1);
314
- border-radius: var(--radius-lg);
315
- overflow: hidden;
316
- box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
317
- }
318
-
319
- .preview-modal-header {
320
- padding: 1rem 1.25rem;
321
- background: rgba(255, 255, 255, 0.02);
322
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
323
- display: flex;
324
- justify-content: space-between;
325
- align-items: center;
326
- }
327
-
328
- .preview-modal-title {
329
- font-size: 0.9rem;
330
- font-weight: 500;
331
- color: #ccc;
332
- }
333
-
334
- .preview-modal-close {
335
- width: 28px;
336
- height: 28px;
337
- background: transparent;
338
- border: none;
339
- color: #666;
340
- cursor: pointer;
341
- display: flex;
342
- align-items: center;
343
- justify-content: center;
344
- border-radius: var(--radius-sm);
345
- font-size: 1.2rem;
346
- transition: all 0.15s ease;
347
- }
348
-
349
- .preview-modal-close:hover {
350
- background: rgba(255, 255, 255, 0.05);
351
- color: #999;
352
- }
353
-
354
- .preview-modal-content {
355
- padding: 1.25rem;
356
- }
357
-
358
- .preview-video-container {
359
- background: #000;
360
- border-radius: var(--radius-sm);
361
- overflow: hidden;
362
- aspect-ratio: 16 / 9;
363
- }
364
-
365
- .preview-video {
366
- width: 100%;
367
- height: 100%;
368
- object-fit: contain;
369
- }
370
-
371
- .preview-modal-actions {
372
- padding: 1rem 1.25rem;
373
- border-top: 1px solid rgba(255, 255, 255, 0.05);
374
- display: flex;
375
- justify-content: flex-end;
376
- gap: 0.75rem;
377
- }
378
-
379
- .preview-btn {
380
- padding: 0.6rem 1.25rem;
381
- border-radius: var(--radius-sm);
382
- font-size: 0.8rem;
383
- font-weight: 500;
384
- cursor: pointer;
385
- transition: all 0.15s ease;
386
- display: inline-flex;
387
- align-items: center;
388
- gap: 0.5rem;
389
- }
390
-
391
- .preview-btn.secondary {
392
- background: transparent;
393
- border: 1px solid rgba(255, 255, 255, 0.1);
394
- color: #999;
395
- }
396
-
397
- .preview-btn.secondary:hover {
398
- border-color: rgba(255, 255, 255, 0.2);
399
- color: #ccc;
400
- }
401
-
402
- .preview-btn.primary {
403
- background: var(--primary);
404
- border: none;
405
- color: white;
406
- }
407
-
408
- .preview-btn.primary:hover {
409
- background: #2563eb;
410
- }
411
-
412
- .preview-btn.primary:disabled {
413
- background: #1e3a5f;
414
- cursor: not-allowed;
415
- opacity: 0.7;
416
- }
417
-
418
- /* Trim Timeline Styles */
419
- .trim-section {
420
- margin-top: 1.25rem;
421
- padding-top: 1.25rem;
422
- border-top: 1px solid rgba(255, 255, 255, 0.05);
423
- }
424
-
425
- .trim-section-header {
426
- display: flex;
427
- justify-content: space-between;
428
- align-items: center;
429
- margin-bottom: 0.75rem;
430
- }
431
-
432
- .trim-section-title {
433
- font-size: 0.75rem;
434
- font-weight: 500;
435
- color: #888;
436
- text-transform: uppercase;
437
- letter-spacing: 0.05em;
438
- }
439
-
440
- .trim-duration-info {
441
- font-size: 0.7rem;
442
- color: #666;
443
- font-family: 'Consolas', 'Monaco', monospace;
444
- }
445
-
446
- .trim-timeline {
447
- position: relative;
448
- height: 48px;
449
- background: var(--input);
450
- border-radius: var(--radius-sm);
451
- margin-bottom: 0.75rem;
452
- user-select: none;
453
- }
454
-
455
- .trim-track {
456
- position: absolute;
457
- top: 50%;
458
- left: 12px;
459
- right: 12px;
460
- height: 6px;
461
- background: #333;
462
- transform: translateY(-50%);
463
- border-radius: 3px;
464
- }
465
-
466
- .trim-selected {
467
- position: absolute;
468
- top: 50%;
469
- height: 6px;
470
- background: var(--primary);
471
- transform: translateY(-50%);
472
- border-radius: 3px;
473
- pointer-events: none;
474
- }
475
-
476
- .trim-handle {
477
- position: absolute;
478
- top: 50%;
479
- width: 16px;
480
- height: 36px;
481
- background: white;
482
- border: 2px solid var(--primary);
483
- border-radius: 4px;
484
- transform: translate(-50%, -50%);
485
- cursor: ew-resize;
486
- z-index: 2;
487
- display: flex;
488
- align-items: center;
489
- justify-content: center;
490
- transition: background 0.15s ease, transform 0.1s ease;
491
- }
492
-
493
- .trim-handle:hover {
494
- background: #e0e0e0;
495
- }
496
-
497
- .trim-handle:active {
498
- background: var(--primary);
499
- transform: translate(-50%, -50%) scale(1.05);
500
- }
501
-
502
- .trim-handle::before {
503
- content: '';
504
- width: 2px;
505
- height: 16px;
506
- background: #666;
507
- border-radius: 1px;
508
- }
509
-
510
- .trim-handle:active::before {
511
- background: white;
512
- }
513
-
514
- .trim-time-labels {
515
- display: flex;
516
- justify-content: space-between;
517
- font-size: 0.65rem;
518
- color: #666;
519
- font-family: 'Consolas', 'Monaco', monospace;
520
- padding: 0 12px;
521
- }
522
-
523
- .trim-actions {
524
- display: flex;
525
- gap: 0.5rem;
526
- margin-top: 0.75rem;
527
- }
528
-
529
- .trim-action-btn {
530
- flex: 1;
531
- padding: 0.5rem 0.75rem;
532
- background: var(--input);
533
- border: 1px solid transparent;
534
- border-radius: var(--radius-sm);
535
- color: #999;
536
- font-size: 0.75rem;
537
- cursor: pointer;
538
- transition: all 0.15s ease;
539
- text-align: center;
540
- }
541
-
542
- .trim-action-btn:hover {
543
- border-color: var(--primary);
544
- color: #ccc;
545
- }
546
-
547
- .export-spinner {
548
- display: inline-block;
549
- width: 14px;
550
- height: 14px;
551
- border: 2px solid rgba(255, 255, 255, 0.3);
552
- border-radius: 50%;
553
- border-top-color: white;
554
- animation: spin 0.8s linear infinite;
555
- flex-shrink: 0;
556
- }
557
-
558
- @keyframes spin {
559
- to { transform: rotate(360deg); }
560
- }
561
-
562
- `
563
- ];
564
-
565
- public render(): TemplateResult {
566
- if (this.panelState === 'options') {
567
- return this.renderOptionsPanel();
568
- } else if (this.panelState === 'preview') {
569
- return this.renderPreviewModal();
570
- }
571
- return html``;
572
- }
573
-
574
- private renderOptionsPanel(): TemplateResult {
575
- return html`
576
- <div class="recording-options-panel">
577
- <div class="recording-options-header">
578
- <span class="recording-options-title">Recording Settings</span>
579
- <button class="recording-options-close" @click=${() => this.close()}>✕</button>
580
- </div>
581
- <div class="recording-options-content">
582
- <div class="recording-option-group">
583
- <div class="recording-option-label">Record Area</div>
584
- <div class="recording-mode-buttons">
585
- <button
586
- class="recording-mode-btn ${this.recordingMode === 'viewport' ? 'selected' : ''}"
587
- @click=${() => this.recordingMode = 'viewport'}
588
- >
589
- Viewport Only
590
- </button>
591
- <button
592
- class="recording-mode-btn ${this.recordingMode === 'screen' ? 'selected' : ''}"
593
- @click=${() => this.recordingMode = 'screen'}
594
- >
595
- Entire Screen
596
- </button>
597
- </div>
598
- </div>
599
-
600
- <div class="recording-option-group">
601
- <div class="recording-option-label">Format</div>
602
- <div class="recording-mode-buttons">
603
- <button
604
- class="recording-mode-btn ${this.outputFormat === 'mp4' ? 'selected' : ''}"
605
- @click=${() => this.outputFormat = 'mp4'}
606
- >
607
- MP4 (H.264)
608
- </button>
609
- <button
610
- class="recording-mode-btn ${this.outputFormat === 'webm' ? 'selected' : ''}"
611
- @click=${() => this.outputFormat = 'webm'}
612
- >
613
- WebM (VP9)
614
- </button>
615
- </div>
616
- </div>
617
-
618
- <div class="recording-option-group">
619
- <div class="recording-option-label">Audio</div>
620
- <div class="audio-toggle">
621
- <input
622
- type="checkbox"
623
- id="audioToggle"
624
- ?checked=${this.audioEnabled}
625
- @change=${(e: Event) => this.handleAudioToggle((e.target as HTMLInputElement).checked)}
626
- />
627
- <label for="audioToggle">Enable Microphone</label>
628
- </div>
629
-
630
- ${this.audioEnabled ? html`
631
- <select
632
- class="microphone-select"
633
- .value=${this.selectedMicrophoneId}
634
- @change=${(e: Event) => this.handleMicrophoneChange((e.target as HTMLSelectElement).value)}
635
- >
636
- <option value="">Select Microphone...</option>
637
- ${this.availableMicrophones.map(mic => html`
638
- <option value=${mic.deviceId}>${mic.label || `Microphone ${mic.deviceId.slice(0, 8)}`}</option>
639
- `)}
640
- </select>
641
-
642
- ${this.selectedMicrophoneId ? html`
643
- <div class="audio-level-container">
644
- <div class="audio-level-label">Input Level</div>
645
- <div class="audio-level-bar">
646
- <div class="audio-level-fill" style="width: ${this.audioLevel}%"></div>
647
- </div>
648
- </div>
649
- ` : null}
650
- ` : null}
651
- </div>
652
-
653
- <button class="start-recording-btn" @click=${() => this.startRecording()}>
654
- <div class="rec-dot"></div>
655
- Start Recording
656
- </button>
657
- </div>
658
- </div>
659
- `;
660
- }
661
-
662
- private renderPreviewModal(): TemplateResult {
663
- return html`
664
- <div class="preview-modal-overlay" @click=${(e: Event) => {
665
- if ((e.target as HTMLElement).classList.contains('preview-modal-overlay')) {
666
- this.discardRecording();
667
- }
668
- }}>
669
- <div class="preview-modal">
670
- <div class="preview-modal-header">
671
- <span class="preview-modal-title">Recording Preview</span>
672
- <button class="preview-modal-close" @click=${() => this.discardRecording()}>✕</button>
673
- </div>
674
- <div class="preview-modal-content">
675
- <div class="preview-video-container">
676
- <video
677
- class="preview-video"
678
- src=${this.previewVideoUrl}
679
- controls
680
- @loadedmetadata=${(e: Event) => this.handleVideoLoaded(e.target as HTMLVideoElement)}
681
- ></video>
682
- </div>
683
-
684
- <!-- Trim Section -->
685
- <div class="trim-section">
686
- <div class="trim-section-header">
687
- <span class="trim-section-title">Trim Video</span>
688
- <span class="trim-duration-info">
689
- ${this.formatDuration(Math.floor(this.trimEnd - this.trimStart))}
690
- ${this.trimStart > 0 || this.trimEnd < this.videoDuration
691
- ? `(trimmed from ${this.formatDuration(Math.floor(this.videoDuration))})`
692
- : ''}
693
- </span>
694
- </div>
695
-
696
- <div
697
- class="trim-timeline"
698
- @mousedown=${(e: MouseEvent) => this.handleTimelineClick(e)}
699
- @mousemove=${(e: MouseEvent) => this.handleTimelineDrag(e)}
700
- @mouseup=${() => this.handleTimelineDragEnd()}
701
- @mouseleave=${() => this.handleTimelineDragEnd()}
702
- >
703
- <div class="trim-track"></div>
704
- <div
705
- class="trim-selected"
706
- style="left: ${this.getHandlePositionStyle(this.trimStart)}; right: ${this.getHandlePositionFromEndStyle(this.trimEnd)};"
707
- ></div>
708
- <div
709
- class="trim-handle start-handle"
710
- style="left: ${this.getHandlePositionStyle(this.trimStart)};"
711
- @mousedown=${(e: MouseEvent) => { e.stopPropagation(); this.isDraggingTrim = 'start'; }}
712
- ></div>
713
- <div
714
- class="trim-handle end-handle"
715
- style="left: ${this.getHandlePositionStyle(this.trimEnd)};"
716
- @mousedown=${(e: MouseEvent) => { e.stopPropagation(); this.isDraggingTrim = 'end'; }}
717
- ></div>
718
- </div>
719
-
720
- <div class="trim-time-labels">
721
- <span>${this.formatDuration(Math.floor(this.trimStart))}</span>
722
- <span>${this.formatDuration(Math.floor(this.trimEnd))}</span>
723
- </div>
724
-
725
- <div class="trim-actions">
726
- <button class="trim-action-btn" @click=${() => this.resetTrim()}>
727
- Reset Trim
728
- </button>
729
- <button class="trim-action-btn" @click=${() => this.previewTrimmedSection()}>
730
- Preview Selection
731
- </button>
732
- </div>
733
- </div>
734
-
735
- </div>
736
- <div class="preview-modal-actions">
737
- <button class="preview-btn secondary" @click=${() => this.discardRecording()}>Discard</button>
738
- <button
739
- class="preview-btn primary"
740
- ?disabled=${this.isExporting}
741
- @click=${() => this.downloadRecording()}
742
- >
743
- ${this.isExporting
744
- ? html`<span class="export-spinner"></span>${this.outputFormat === 'mp4' ? 'Converting to MP4...' : 'Exporting...'}`
745
- : `Download ${this.outputFormat === 'mp4' ? 'MP4' : 'WebM'}`}
746
- </button>
747
- </div>
748
- </div>
749
- </div>
750
- `;
751
- }
752
-
753
- // ==================== Audio Methods ====================
754
-
755
- private async handleAudioToggle(enabled: boolean): Promise<void> {
756
- this.audioEnabled = enabled;
757
- if (enabled) {
758
- this.availableMicrophones = await this.recorderService.loadMicrophones(true);
759
- if (this.availableMicrophones.length > 0 && !this.selectedMicrophoneId) {
760
- this.selectedMicrophoneId = this.availableMicrophones[0].deviceId;
761
- await this.recorderService.startAudioMonitoring(this.selectedMicrophoneId);
762
- }
763
- } else {
764
- this.recorderService.stopAudioMonitoring();
765
- this.selectedMicrophoneId = '';
766
- this.audioLevel = 0;
767
- }
768
- }
769
-
770
- private async handleMicrophoneChange(deviceId: string): Promise<void> {
771
- this.selectedMicrophoneId = deviceId;
772
- if (deviceId) {
773
- await this.recorderService.startAudioMonitoring(deviceId);
774
- } else {
775
- this.recorderService.stopAudioMonitoring();
776
- this.audioLevel = 0;
777
- }
778
- }
779
-
780
- // ==================== Recording Methods ====================
781
-
782
- private async startRecording(): Promise<void> {
783
- try {
784
- let viewportElement: HTMLElement | undefined;
785
- if (this.recordingMode === 'viewport' && this.dashboardRef) {
786
- const wccFrame = await this.dashboardRef.wccFrame;
787
- viewportElement = await wccFrame.getViewportElement();
788
- }
789
-
790
- await this.recorderService.startRecording({
791
- mode: this.recordingMode,
792
- audioDeviceId: this.audioEnabled ? this.selectedMicrophoneId : undefined,
793
- viewportElement,
794
- });
795
-
796
- this.panelState = 'recording';
797
- this.dispatchEvent(new CustomEvent('recording-start', {
798
- bubbles: true,
799
- composed: true
800
- }));
801
- } catch (error) {
802
- console.error('Failed to start recording:', error);
803
- this.panelState = 'options';
804
- }
805
- }
806
-
807
- public stopRecording(): void {
808
- this.recorderService.stopRecording();
809
- }
810
-
811
- private handleRecordingComplete(blob: Blob): void {
812
- if (this.previewVideoUrl) {
813
- URL.revokeObjectURL(this.previewVideoUrl);
814
- }
815
- this.previewVideoUrl = URL.createObjectURL(blob);
816
- this.panelState = 'preview';
817
- this.dispatchEvent(new CustomEvent('recording-stop', {
818
- bubbles: true,
819
- composed: true
820
- }));
821
- }
822
-
823
- private discardRecording(): void {
824
- if (this.previewVideoUrl) {
825
- URL.revokeObjectURL(this.previewVideoUrl);
826
- this.previewVideoUrl = '';
827
- }
828
- this.recorderService.reset();
829
- this.trimStart = 0;
830
- this.trimEnd = 0;
831
- this.videoDuration = 0;
832
- this.isExporting = false;
833
- this.recordingDuration = 0;
834
- this.close();
835
- }
836
-
837
- private async downloadRecording(): Promise<void> {
838
- const recordedBlob = this.recorderService.recordedBlob;
839
- if (!recordedBlob) return;
840
-
841
- this.isExporting = true;
842
-
843
- try {
844
- let blobToDownload: Blob;
845
-
846
- // Handle trimming if needed — always produces WebM
847
- const needsTrim = this.trimStart > 0.1 || this.trimEnd < this.videoDuration - 0.1;
848
-
849
- if (needsTrim) {
850
- const video = this.shadowRoot?.querySelector('.preview-video') as HTMLVideoElement;
851
- if (video) {
852
- blobToDownload = await this.recorderService.exportTrimmedVideo(video, this.trimStart, this.trimEnd);
853
- } else {
854
- blobToDownload = recordedBlob;
855
- }
856
- } else {
857
- blobToDownload = recordedBlob;
858
- }
859
-
860
- // Convert WebM → MP4 if MP4 format selected
861
- if (this.outputFormat === 'mp4') {
862
- blobToDownload = await this.recorderService.convertToMp4(blobToDownload);
863
- }
864
-
865
- // Trigger download
866
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
867
- const ext = this.outputFormat === 'mp4' ? 'mp4' : 'webm';
868
- const filename = `wcctools-recording-${timestamp}.${ext}`;
869
-
870
- const url = URL.createObjectURL(blobToDownload);
871
- const a = document.createElement('a');
872
- a.href = url;
873
- a.download = filename;
874
- document.body.appendChild(a);
875
- a.click();
876
- document.body.removeChild(a);
877
- URL.revokeObjectURL(url);
878
-
879
- this.discardRecording();
880
- } catch (error) {
881
- console.error('Error exporting video:', error);
882
- this.isExporting = false;
883
- }
884
- }
885
-
886
- // ==================== Trim Methods ====================
887
-
888
- private handleVideoLoaded(video: HTMLVideoElement): void {
889
- // WebM files from MediaRecorder may have Infinity/NaN duration
890
- // Fall back to the tracked recording duration
891
- const duration = Number.isFinite(video.duration) ? video.duration : this.recordingDuration;
892
- this.videoDuration = duration;
893
- this.trimStart = 0;
894
- this.trimEnd = duration;
895
- }
896
-
897
- private formatDuration(seconds: number): string {
898
- const mins = Math.floor(seconds / 60);
899
- const secs = seconds % 60;
900
- return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
901
- }
902
-
903
- private getHandlePositionStyle(time: number): string {
904
- if (this.videoDuration === 0) return '12px';
905
- const percentage = time / this.videoDuration;
906
- // Formula: 12px padding + percentage of remaining width (total - 24px padding)
907
- // At 0%: 12px (left edge of track)
908
- // At 100%: calc(100% - 12px) (right edge of track)
909
- return `calc(12px + ${(percentage * 100).toFixed(2)}% - ${(percentage * 24).toFixed(2)}px)`;
910
- }
911
-
912
- private getHandlePositionFromEndStyle(time: number): string {
913
- if (this.videoDuration === 0) return '12px';
914
- const percentage = time / this.videoDuration;
915
- const remainingPercentage = 1 - percentage;
916
- // For CSS 'right' property: distance from right edge
917
- // At trimEnd = 100%: right = 12px (at right edge of track)
918
- // At trimEnd = 0%: right = calc(100% - 12px) (at left edge of track)
919
- return `calc(12px + ${(remainingPercentage * 100).toFixed(2)}% - ${(remainingPercentage * 24).toFixed(2)}px)`;
920
- }
921
-
922
- private handleTimelineClick(e: MouseEvent): void {
923
- if (this.isDraggingTrim) return;
924
-
925
- const timeline = e.currentTarget as HTMLElement;
926
- const rect = timeline.getBoundingClientRect();
927
- const x = e.clientX - rect.left;
928
- const percentage = Math.max(0, Math.min(1, (x - 12) / (rect.width - 24)));
929
- const time = percentage * this.videoDuration;
930
-
931
- const video = this.shadowRoot?.querySelector('.preview-video') as HTMLVideoElement;
932
- if (video) {
933
- video.currentTime = time;
934
- }
935
- }
936
-
937
- private handleTimelineDrag(e: MouseEvent): void {
938
- if (!this.isDraggingTrim) return;
939
-
940
- const timeline = e.currentTarget as HTMLElement;
941
- const rect = timeline.getBoundingClientRect();
942
- const x = e.clientX - rect.left;
943
- const percentage = Math.max(0, Math.min(1, (x - 12) / (rect.width - 24)));
944
- const time = percentage * this.videoDuration;
945
-
946
- const minDuration = 1;
947
-
948
- if (this.isDraggingTrim === 'start') {
949
- this.trimStart = Math.min(time, this.trimEnd - minDuration);
950
- this.trimStart = Math.max(0, this.trimStart);
951
- } else if (this.isDraggingTrim === 'end') {
952
- this.trimEnd = Math.max(time, this.trimStart + minDuration);
953
- this.trimEnd = Math.min(this.videoDuration, this.trimEnd);
954
- }
955
-
956
- const video = this.shadowRoot?.querySelector('.preview-video') as HTMLVideoElement;
957
- if (video) {
958
- video.currentTime = this.isDraggingTrim === 'start' ? this.trimStart : this.trimEnd;
959
- }
960
- }
961
-
962
- private handleTimelineDragEnd(): void {
963
- this.isDraggingTrim = null;
964
- }
965
-
966
- private resetTrim(): void {
967
- this.trimStart = 0;
968
- this.trimEnd = this.videoDuration;
969
-
970
- const video = this.shadowRoot?.querySelector('.preview-video') as HTMLVideoElement;
971
- if (video) {
972
- video.currentTime = 0;
973
- }
974
- }
975
-
976
- private previewTrimmedSection(): void {
977
- const video = this.shadowRoot?.querySelector('.preview-video') as HTMLVideoElement;
978
- if (!video) return;
979
-
980
- video.currentTime = this.trimStart;
981
- video.play();
982
-
983
- const checkTime = () => {
984
- if (video.currentTime >= this.trimEnd) {
985
- video.pause();
986
- video.removeEventListener('timeupdate', checkTime);
987
- }
988
- };
989
-
990
- video.addEventListener('timeupdate', checkTime);
991
- }
992
-
993
- // ==================== Lifecycle ====================
994
-
995
- private close(): void {
996
- this.recorderService.stopAudioMonitoring();
997
- this.dispatchEvent(new CustomEvent('close', {
998
- bubbles: true,
999
- composed: true
1000
- }));
1001
- }
1002
-
1003
- async disconnectedCallback(): Promise<void> {
1004
- await super.disconnectedCallback();
1005
- this.recorderService.dispose();
1006
- if (this.previewVideoUrl) {
1007
- URL.revokeObjectURL(this.previewVideoUrl);
1008
- }
1009
- }
1010
- }