Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 36dd6b5064 | |||
| ddecfcdb4c | |||
| 8f0f8606a1 | |||
| 7dca519d9a | |||
| d48cd063c4 | |||
| bb04895be8 | |||
| 54b34b6faa | |||
| 12c85fa4cb | |||
| d90df9717b | |||
| d4b161437b | |||
| ad033c8104 |
BIN
.playwright-mcp/recording-panel.png
Normal file
BIN
.playwright-mcp/recording-panel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
BIN
.playwright-mcp/wcctools-dashboard.png
Normal file
BIN
.playwright-mcp/wcctools-dashboard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
.playwright-mcp/wcctools-with-element.png
Normal file
BIN
.playwright-mcp/wcctools-with-element.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
38
changelog.md
38
changelog.md
@@ -1,5 +1,43 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-12-19 - 3.1.0 - feat(wcc-properties)
|
||||
add Share selector with inline Record button and adjust properties panel grid layout
|
||||
|
||||
- Increase rightmost column width from 70px to 100px in properties grid
|
||||
- Introduce .shareSelector and .selectorButtons1 CSS classes and markup
|
||||
- Replace <wcc-record-button> with inline Record button that toggles icon based on isRecording
|
||||
- Add Share panel heading and integrate record control into selector area
|
||||
|
||||
## 2025-12-19 - 3.0.0 - BREAKING CHANGE(ts_web)
|
||||
Replace fullscreen boolean with native viewport mode across components, add native viewport selector and toggle, and update dev deps and npmextra config
|
||||
|
||||
- Introduce isNative (derived from selectedViewport === 'native') replacing isFullscreen on WccDashboard, WccFrame, WccProperties and WccSidebar (property and styling changes).
|
||||
- WccDashboard: remove isFullscreen property, add isNative getter, implement toggleNative to switch selectedViewport between 'native' and 'desktop', update ESC handler to exit native mode and call buildUrl().
|
||||
- WccProperties: add Native button to viewport selector, adjust grid columns and selector layout, replace fullscreen toggle with toggleNative (dispatches 'toggleNative' event).
|
||||
- WccFrame: rename isFullscreen -> isNative and update style/markup branching to use isNative.
|
||||
- WccSidebar: visibility now driven by isNative instead of isFullscreen.
|
||||
- API/event changes: 'toggleFullscreen' event renamed to 'toggleNative' — this is an incompatible change for consumers relying on the old event/property.
|
||||
- package.json: bump devDependencies @git.zone/tsbuild -> ^4.0.2, @git.zone/tsrun -> ^2.0.1, @git.zone/tswatch -> ^2.3.13, @types/node -> ^25.0.3.
|
||||
- npmextra.json: rename keys (e.g. "gitzone" -> "@git.zone/cli", "tsdoc" -> "@git.zone/tsdoc"), add @ship.zone/szci entry and add release registries/accessLevel for @git.zone/cli.
|
||||
|
||||
## 2025-12-11 - 2.0.1 - fix(@git.zone/tswatch)
|
||||
Bump @git.zone/tswatch devDependency to ^2.3.12
|
||||
|
||||
- Updated devDependency @git.zone/tswatch from ^2.3.11 to ^2.3.12 in package.json
|
||||
|
||||
## 2025-12-11 - 2.0.0 - BREAKING CHANGE(recorder)
|
||||
Remove FFmpeg-based MP4 conversion; simplify recorder/export to WebM and improve recorder/editor robustness
|
||||
|
||||
- Removed FFmpegService and all client-side MP4 conversion logic — exports are now WebM-only (MP4 conversion and related UI/controls removed).
|
||||
- ts_web/elements/wcc-recording-panel: dropped outputFormat and conversion states/UI; download flow simplified to always export WebM.
|
||||
- ts_web/index.ts: removed FFmpegService exports and conversion types from public API.
|
||||
- package.json: removed @ffmpeg/* dependencies.
|
||||
- RecorderService: handleRecordingComplete is now async and fixes recorded blob assignment and cleanup timing.
|
||||
- wcc-properties: improved element detection and robustness — recursive search through light/shadow DOM with retry/delay, plus an advanced JSON editor for Object/Array props (supports multiple open editors and frame resize events).
|
||||
- wcc-sidebar: force re-render after selecting demos to ensure child demo selection indicators update correctly.
|
||||
- dees-demowrapper: ensure slotted content is rendered before calling runAfterRender (small timing/stability improvements).
|
||||
- Test update: demo definitions can be arrays (multiple demos) — test-demoelement updated to use multiple demo entries.
|
||||
|
||||
## 2025-12-11 - 1.3.0 - feat(recording-panel)
|
||||
Add demo wrapper utilities, improve recording trim behavior, and harden property panel element detection; update documentation
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"gitzone": {
|
||||
"@git.zone/cli": {
|
||||
"projectType": "wcc",
|
||||
"module": {
|
||||
"githost": "code.foss.global",
|
||||
@@ -21,13 +21,19 @@
|
||||
"element testing",
|
||||
"page development"
|
||||
]
|
||||
},
|
||||
"release": {
|
||||
"registries": [
|
||||
"https://verdaccio.lossless.digital",
|
||||
"https://registry.npmjs.org"
|
||||
],
|
||||
"accessLevel": "public"
|
||||
}
|
||||
},
|
||||
"npmci": {
|
||||
"npmGlobalTools": [],
|
||||
"npmAccessLevel": "public"
|
||||
},
|
||||
"tsdoc": {
|
||||
"@git.zone/tsdoc": {
|
||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||
},
|
||||
"@ship.zone/szci": {
|
||||
"npmGlobalTools": []
|
||||
}
|
||||
}
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@design.estate/dees-wcctools",
|
||||
"version": "1.3.0",
|
||||
"version": "3.1.0",
|
||||
"private": false,
|
||||
"description": "A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.",
|
||||
"exports": {
|
||||
@@ -24,13 +24,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@api.global/typedserver": "^7.11.1",
|
||||
"@git.zone/tsbuild": "^3.1.2",
|
||||
"@git.zone/tsbuild": "^4.0.2",
|
||||
"@git.zone/tsbundle": "^2.6.3",
|
||||
"@git.zone/tsrun": "^2.0.0",
|
||||
"@git.zone/tsrun": "^2.0.1",
|
||||
"@git.zone/tstest": "^3.1.3",
|
||||
"@git.zone/tswatch": "^2.3.10",
|
||||
"@git.zone/tswatch": "^2.3.13",
|
||||
"@push.rocks/projectinfo": "^5.0.2",
|
||||
"@types/node": "^25.0.0"
|
||||
"@types/node": "^25.0.3"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
|
||||
1916
pnpm-lock.yaml
generated
1916
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,11 @@ enum ETestEnum {
|
||||
|
||||
@customElement('test-demoelement')
|
||||
export class TestDemoelement extends DeesElement {
|
||||
public static demo = () => html`<test-demoelement>This is a slot text</test-demoelement>`;
|
||||
public static demo = [
|
||||
() => html`<test-demoelement>This is demo 1</test-demoelement>`,
|
||||
() => html`<test-demoelement>This is demo 2</test-demoelement>`,
|
||||
() => html`<test-demoelement>This is demo 2</test-demoelement>`,
|
||||
]
|
||||
|
||||
@property()
|
||||
accessor notTyped = 'hello';
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@design.estate/dees-wcctools',
|
||||
version: '1.3.0',
|
||||
version: '3.1.0',
|
||||
description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DeesElement, property, html, customElement, type TemplateResult, queryAsync, render, domtools } from '@design.estate/dees-element';
|
||||
import { resolveTemplateFactory } from './wcctools.helpers.js';
|
||||
import { resolveTemplateFactory, getDemoAtIndex, getDemoCount, hasMultipleDemos } from './wcctools.helpers.js';
|
||||
import type { TTemplateFactory } from './wcctools.helpers.js';
|
||||
|
||||
import * as plugins from '../wcctools.plugins.js';
|
||||
@@ -25,14 +25,19 @@ export class WccDashboard extends DeesElement {
|
||||
@property()
|
||||
accessor selectedItem: TTemplateFactory | DeesElement;
|
||||
|
||||
@property({ type: Number })
|
||||
accessor selectedDemoIndex: number = 0;
|
||||
|
||||
@property()
|
||||
accessor selectedViewport: plugins.deesDomtools.breakpoints.TViewport = 'desktop';
|
||||
|
||||
@property()
|
||||
accessor selectedTheme: TTheme = 'dark';
|
||||
|
||||
@property()
|
||||
accessor isFullscreen: boolean = false;
|
||||
// Derived from selectedViewport - no need for separate property
|
||||
public get isNative(): boolean {
|
||||
return this.selectedViewport === 'native';
|
||||
}
|
||||
|
||||
@property()
|
||||
accessor pages: Record<string, TTemplateFactory> = {};
|
||||
@@ -81,7 +86,7 @@ export class WccDashboard extends DeesElement {
|
||||
<wcc-sidebar
|
||||
.dashboardRef=${this}
|
||||
.selectedItem=${this.selectedItem}
|
||||
.isFullscreen=${this.isFullscreen}
|
||||
.isNative=${this.isNative}
|
||||
@selectedType=${(eventArg) => {
|
||||
this.selectedType = eventArg.detail;
|
||||
}}
|
||||
@@ -98,7 +103,7 @@ export class WccDashboard extends DeesElement {
|
||||
.selectedItem=${this.selectedItem}
|
||||
.selectedViewport=${this.selectedViewport}
|
||||
.selectedTheme=${this.selectedTheme}
|
||||
.isFullscreen=${this.isFullscreen}
|
||||
.isNative=${this.isNative}
|
||||
@selectedViewport=${(eventArg) => {
|
||||
this.selectedViewport = eventArg.detail;
|
||||
this.scheduleUpdate();
|
||||
@@ -113,11 +118,11 @@ export class WccDashboard extends DeesElement {
|
||||
frame.requestUpdate();
|
||||
}
|
||||
}}
|
||||
@toggleFullscreen=${() => {
|
||||
this.toggleFullscreen();
|
||||
@toggleNative=${() => {
|
||||
this.toggleNative();
|
||||
}}
|
||||
></wcc-properties>
|
||||
<wcc-frame id="wccFrame" viewport=${this.selectedViewport} .isFullscreen=${this.isFullscreen}>
|
||||
<wcc-frame id="wccFrame" viewport=${this.selectedViewport} .isNative=${this.isNative}>
|
||||
</wcc-frame>
|
||||
`;
|
||||
}
|
||||
@@ -132,17 +137,20 @@ export class WccDashboard extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
public toggleFullscreen() {
|
||||
this.isFullscreen = !this.isFullscreen;
|
||||
public toggleNative() {
|
||||
// Toggle between 'native' and 'desktop' viewports
|
||||
this.selectedViewport = this.selectedViewport === 'native' ? 'desktop' : 'native';
|
||||
this.buildUrl();
|
||||
}
|
||||
|
||||
public async firstUpdated() {
|
||||
this.domtools = await plugins.deesDomtools.DomTools.setupDomTools();
|
||||
|
||||
// Add ESC key handler for fullscreen mode
|
||||
// Add ESC key handler for native mode
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape' && this.isFullscreen) {
|
||||
this.isFullscreen = false;
|
||||
if (event.key === 'Escape' && this.isNative) {
|
||||
this.selectedViewport = 'desktop';
|
||||
this.buildUrl();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -151,11 +159,13 @@ export class WccDashboard extends DeesElement {
|
||||
this.setupScrollListeners();
|
||||
}, 500);
|
||||
|
||||
// Route with demo index (new format)
|
||||
this.domtools.router.on(
|
||||
'/wcctools-route/:itemType/:itemName/:viewport/:theme',
|
||||
'/wcctools-route/:itemType/:itemName/:demoIndex/:viewport/:theme',
|
||||
async (routeInfo) => {
|
||||
this.selectedType = routeInfo.params.itemType as TElementType;
|
||||
this.selectedItemName = routeInfo.params.itemName;
|
||||
this.selectedDemoIndex = parseInt(routeInfo.params.demoIndex) || 0;
|
||||
this.selectedViewport = routeInfo.params.viewport as breakpoints.TViewport;
|
||||
this.selectedTheme = routeInfo.params.theme as TTheme;
|
||||
if (routeInfo.params.itemType === 'element') {
|
||||
@@ -163,25 +173,65 @@ export class WccDashboard extends DeesElement {
|
||||
} else if (routeInfo.params.itemType === 'page') {
|
||||
this.selectedItem = this.pages[routeInfo.params.itemName];
|
||||
}
|
||||
|
||||
|
||||
// Restore scroll positions from query parameters
|
||||
if (routeInfo.queryParams) {
|
||||
const frameScrollY = routeInfo.queryParams.frameScrollY;
|
||||
const sidebarScrollY = routeInfo.queryParams.sidebarScrollY;
|
||||
|
||||
|
||||
if (frameScrollY) {
|
||||
this.frameScrollY = parseInt(frameScrollY);
|
||||
}
|
||||
if (sidebarScrollY) {
|
||||
this.sidebarScrollY = parseInt(sidebarScrollY);
|
||||
}
|
||||
|
||||
|
||||
// Apply scroll positions after a short delay to ensure DOM is ready
|
||||
setTimeout(() => {
|
||||
this.applyScrollPositions();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
|
||||
const domtoolsInstance = await plugins.deesDomtools.elementBasic.setup();
|
||||
this.selectedTheme === 'bright'
|
||||
? domtoolsInstance.themeManager.goBright()
|
||||
: domtoolsInstance.themeManager.goDark();
|
||||
}
|
||||
);
|
||||
|
||||
// Legacy route without demo index (for backwards compatibility)
|
||||
this.domtools.router.on(
|
||||
'/wcctools-route/:itemType/:itemName/:viewport/:theme',
|
||||
async (routeInfo) => {
|
||||
this.selectedType = routeInfo.params.itemType as TElementType;
|
||||
this.selectedItemName = routeInfo.params.itemName;
|
||||
this.selectedDemoIndex = 0; // Default to first demo
|
||||
this.selectedViewport = routeInfo.params.viewport as breakpoints.TViewport;
|
||||
this.selectedTheme = routeInfo.params.theme as TTheme;
|
||||
if (routeInfo.params.itemType === 'element') {
|
||||
this.selectedItem = this.elements[routeInfo.params.itemName];
|
||||
} else if (routeInfo.params.itemType === 'page') {
|
||||
this.selectedItem = this.pages[routeInfo.params.itemName];
|
||||
}
|
||||
|
||||
// Restore scroll positions from query parameters
|
||||
if (routeInfo.queryParams) {
|
||||
const frameScrollY = routeInfo.queryParams.frameScrollY;
|
||||
const sidebarScrollY = routeInfo.queryParams.sidebarScrollY;
|
||||
|
||||
if (frameScrollY) {
|
||||
this.frameScrollY = parseInt(frameScrollY);
|
||||
}
|
||||
if (sidebarScrollY) {
|
||||
this.sidebarScrollY = parseInt(sidebarScrollY);
|
||||
}
|
||||
|
||||
// Apply scroll positions after a short delay to ensure DOM is ready
|
||||
setTimeout(() => {
|
||||
this.applyScrollPositions();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
const domtoolsInstance = await plugins.deesDomtools.elementBasic.setup();
|
||||
this.selectedTheme === 'bright'
|
||||
? domtoolsInstance.themeManager.goBright()
|
||||
@@ -218,33 +268,48 @@ export class WccDashboard extends DeesElement {
|
||||
this.setWarning(`component ${anonItem.name} does not expose a demo property.`);
|
||||
return;
|
||||
}
|
||||
if (!(typeof anonItem.demo === 'function')) {
|
||||
|
||||
// Support both single demo (function) and multiple demos (array)
|
||||
const isArray = Array.isArray(anonItem.demo);
|
||||
const isFunction = typeof anonItem.demo === 'function';
|
||||
|
||||
if (!isArray && !isFunction) {
|
||||
this.setWarning(
|
||||
`component ${anonItem.name} has demo property, but it is not of type function`
|
||||
`component ${anonItem.name} has demo property, but it is not a function or array of functions`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the specific demo to render
|
||||
const demoFactory = getDemoAtIndex(anonItem.demo, this.selectedDemoIndex);
|
||||
if (!demoFactory) {
|
||||
this.setWarning(
|
||||
`component ${anonItem.name} does not have a demo at index ${this.selectedDemoIndex + 1}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setWarning(null);
|
||||
const viewport = await wccFrame.getViewportElement();
|
||||
const demoTemplate = await resolveTemplateFactory(() => anonItem.demo());
|
||||
const demoTemplate = await resolveTemplateFactory(demoFactory);
|
||||
render(demoTemplate, viewport);
|
||||
}
|
||||
}
|
||||
|
||||
public buildUrl() {
|
||||
const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedViewport}/${this.selectedTheme}`;
|
||||
const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedDemoIndex}/${this.selectedViewport}/${this.selectedTheme}`;
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
|
||||
if (this.frameScrollY > 0) {
|
||||
queryParams.set('frameScrollY', this.frameScrollY.toString());
|
||||
}
|
||||
if (this.sidebarScrollY > 0) {
|
||||
queryParams.set('sidebarScrollY', this.sidebarScrollY.toString());
|
||||
}
|
||||
|
||||
|
||||
const queryString = queryParams.toString();
|
||||
const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
||||
|
||||
|
||||
this.domtools.router.pushUrl(fullUrl);
|
||||
}
|
||||
|
||||
@@ -286,19 +351,19 @@ export class WccDashboard extends DeesElement {
|
||||
}
|
||||
|
||||
private updateUrlWithScrollState() {
|
||||
const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedViewport}/${this.selectedTheme}`;
|
||||
const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedDemoIndex}/${this.selectedViewport}/${this.selectedTheme}`;
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
|
||||
if (this.frameScrollY > 0) {
|
||||
queryParams.set('frameScrollY', this.frameScrollY.toString());
|
||||
}
|
||||
if (this.sidebarScrollY > 0) {
|
||||
queryParams.set('sidebarScrollY', this.sidebarScrollY.toString());
|
||||
}
|
||||
|
||||
|
||||
const queryString = queryParams.toString();
|
||||
const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
||||
|
||||
|
||||
// Use replaceState to update URL without navigation
|
||||
window.history.replaceState(null, '', fullUrl);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export class WccFrame extends DeesElement {
|
||||
accessor advancedEditorOpen: boolean = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor isFullscreen: boolean = false;
|
||||
accessor isNative: boolean = false;
|
||||
|
||||
public static styles = [
|
||||
css`
|
||||
@@ -46,7 +46,7 @@ export class WccFrame extends DeesElement {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
${this.isFullscreen ? `
|
||||
${this.isNative ? `
|
||||
border: none !important;
|
||||
left: 0px !important;
|
||||
right: 0px !important;
|
||||
@@ -58,7 +58,7 @@ export class WccFrame extends DeesElement {
|
||||
left: 200px;
|
||||
`}
|
||||
transition: all 0.3s ease;
|
||||
${this.isFullscreen ? 'padding: 0px;' : (() => {
|
||||
${this.isNative ? 'padding: 0px;' : (() => {
|
||||
switch (this.viewport) {
|
||||
case 'desktop':
|
||||
return `
|
||||
@@ -87,7 +87,7 @@ export class WccFrame extends DeesElement {
|
||||
}
|
||||
|
||||
.viewport {
|
||||
${!this.isFullscreen && this.viewport !== 'desktop'
|
||||
${!this.isNative && this.viewport !== 'desktop'
|
||||
? html` border-right: 1px dotted #444; border-left: 1px dotted #444; `
|
||||
: html``
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export class WccProperties extends DeesElement {
|
||||
accessor warning: string = null;
|
||||
|
||||
@property()
|
||||
accessor isFullscreen: boolean = false;
|
||||
accessor isNative: boolean = false;
|
||||
|
||||
@state()
|
||||
accessor propertyContent: TemplateResult[] = [];
|
||||
@@ -96,11 +96,11 @@ export class WccProperties extends DeesElement {
|
||||
overflow: hidden;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
display: ${this.isFullscreen ? 'none' : 'block'};
|
||||
display: ${this.isNative ? 'none' : 'block'};
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 150px 300px 70px 70px;
|
||||
grid-template-columns: 1fr 150px 350px 100px;
|
||||
height: 100%;
|
||||
}
|
||||
.properties {
|
||||
@@ -197,7 +197,8 @@ export class WccProperties extends DeesElement {
|
||||
}
|
||||
|
||||
.viewportSelector,
|
||||
.themeSelector {
|
||||
.themeSelector,
|
||||
.shareSelector {
|
||||
user-select: none;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
@@ -214,6 +215,16 @@ export class WccProperties extends DeesElement {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
flex: 1;
|
||||
}
|
||||
.selectorButtons5 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
flex: 1;
|
||||
}
|
||||
.selectorButtons1 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
flex: 1;
|
||||
}
|
||||
.button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -623,7 +634,7 @@ export class WccProperties extends DeesElement {
|
||||
</div>
|
||||
<div class="viewportSelector">
|
||||
<div class="panelheading">Viewport</div>
|
||||
<div class="selectorButtons4">
|
||||
<div class="selectorButtons5">
|
||||
<div
|
||||
class="button ${this.selectedViewport === 'phone' ? 'selected' : null}"
|
||||
@click=${() => {
|
||||
@@ -649,29 +660,34 @@ export class WccProperties extends DeesElement {
|
||||
Tablet<i class="material-symbols-outlined">tablet</i>
|
||||
</div>
|
||||
<div
|
||||
class="button ${this.selectedViewport === 'desktop' ||
|
||||
this.selectedViewport === 'native'
|
||||
? 'selected'
|
||||
: null}"
|
||||
class="button ${this.selectedViewport === 'desktop' ? 'selected' : null}"
|
||||
@click=${() => {
|
||||
this.selectViewport('native');
|
||||
this.selectViewport('desktop');
|
||||
}}
|
||||
>
|
||||
Desktop<i class="material-symbols-outlined">desktop_windows</i>
|
||||
</div>
|
||||
<div
|
||||
class="button ${this.selectedViewport === 'native' ? 'selected' : null}"
|
||||
@click=${() => {
|
||||
this.selectViewport('native');
|
||||
}}
|
||||
>
|
||||
Native<i class="material-symbols-outlined">fullscreen</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="docs" @click=${() => this.toggleFullscreen()}>
|
||||
<i class="material-symbols-outlined" style="font-size: 20px;">
|
||||
${this.isFullscreen ? 'fullscreen_exit' : 'fullscreen'}
|
||||
</i>
|
||||
<div class="shareSelector">
|
||||
<div class="panelheading">Share</div>
|
||||
<div class="selectorButtons1">
|
||||
<div
|
||||
class="button ${this.isRecording ? 'selected' : ''}"
|
||||
@click=${() => this.handleRecordButtonClick()}
|
||||
>
|
||||
Record<i class="material-symbols-outlined">${this.isRecording ? 'stop_circle' : 'videocam'}</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Recording Button -->
|
||||
<wcc-record-button
|
||||
.state=${this.isRecording ? 'recording' : 'idle'}
|
||||
.duration=${this.recordingDuration}
|
||||
@record-click=${() => this.handleRecordButtonClick()}
|
||||
></wcc-record-button>
|
||||
</div>
|
||||
${this.warning ? html`<div class="warning">${this.warning}</div>` : null}
|
||||
</div>
|
||||
@@ -1016,9 +1032,9 @@ export class WccProperties extends DeesElement {
|
||||
);
|
||||
}
|
||||
|
||||
private toggleFullscreen() {
|
||||
private toggleNative() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('toggleFullscreen', {
|
||||
new CustomEvent('toggleNative', {
|
||||
bubbles: true
|
||||
})
|
||||
);
|
||||
|
||||
@@ -552,6 +552,7 @@ export class WccRecordingPanel extends DeesElement {
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
`
|
||||
];
|
||||
|
||||
@@ -706,6 +707,7 @@ export class WccRecordingPanel extends DeesElement {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="preview-modal-actions">
|
||||
<button class="preview-btn secondary" @click=${() => this.discardRecording()}>Discard</button>
|
||||
@@ -714,7 +716,7 @@ export class WccRecordingPanel extends DeesElement {
|
||||
?disabled=${this.isExporting}
|
||||
@click=${() => this.downloadRecording()}
|
||||
>
|
||||
${this.isExporting ? html`<span class="export-spinner"></span>Exporting...` : 'Download'}
|
||||
${this.isExporting ? html`<span class="export-spinner"></span>Exporting...` : 'Download WebM'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -815,6 +817,7 @@ export class WccRecordingPanel extends DeesElement {
|
||||
try {
|
||||
let blobToDownload: Blob;
|
||||
|
||||
// Handle trimming if needed
|
||||
const needsTrim = this.trimStart > 0.1 || this.trimEnd < this.videoDuration - 0.1;
|
||||
|
||||
if (needsTrim) {
|
||||
@@ -828,6 +831,7 @@ export class WccRecordingPanel extends DeesElement {
|
||||
blobToDownload = recordedBlob;
|
||||
}
|
||||
|
||||
// Trigger download
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
||||
const filename = `wcctools-recording-${timestamp}.webm`;
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import * as plugins from '../wcctools.plugins.js';
|
||||
import { DeesElement, property, html, customElement, type TemplateResult } from '@design.estate/dees-element';
|
||||
import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element';
|
||||
import { WccDashboard } from './wcc-dashboard.js';
|
||||
import type { TTemplateFactory } from './wcctools.helpers.js';
|
||||
import { getDemoCount, hasMultipleDemos } from './wcctools.helpers.js';
|
||||
|
||||
export type TElementType = 'element' | 'page';
|
||||
|
||||
@@ -17,7 +18,11 @@ export class WccSidebar extends DeesElement {
|
||||
accessor dashboardRef: WccDashboard;
|
||||
|
||||
@property()
|
||||
accessor isFullscreen: boolean = false;
|
||||
accessor isNative: boolean = false;
|
||||
|
||||
// Track which elements are expanded (for multi-demo elements)
|
||||
@state()
|
||||
accessor expandedElements: Set<string> = new Set();
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
@@ -40,7 +45,7 @@ export class WccSidebar extends DeesElement {
|
||||
--ring: #3b82f6;
|
||||
--radius: 4px;
|
||||
|
||||
display: ${this.isFullscreen ? 'none' : 'block'};
|
||||
display: ${this.isNative ? 'none' : 'block'};
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.08);
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||
font-size: 14px;
|
||||
@@ -110,7 +115,21 @@ export class WccSidebar extends DeesElement {
|
||||
color: #999;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
.selectOption.folder {
|
||||
grid-template-columns: 16px 20px 1fr;
|
||||
}
|
||||
|
||||
.selectOption .expand-icon {
|
||||
font-size: 14px;
|
||||
opacity: 0.5;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.selectOption.expanded .expand-icon {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.selectOption:hover {
|
||||
background: rgba(59, 130, 246, 0.05);
|
||||
color: #bbb;
|
||||
@@ -143,6 +162,42 @@ export class WccSidebar extends DeesElement {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.demo-children {
|
||||
margin-left: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.demo-child {
|
||||
user-select: none;
|
||||
position: relative;
|
||||
margin: 0.125rem 0.5rem;
|
||||
padding: 0.35rem 0.75rem;
|
||||
transition: all 0.15s ease;
|
||||
display: grid;
|
||||
grid-template-columns: 16px 1fr;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
font-size: 0.7rem;
|
||||
color: #777;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.demo-child:hover {
|
||||
background: rgba(59, 130, 246, 0.05);
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.demo-child.selected {
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.demo-child .material-symbols-outlined {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
@@ -171,7 +226,7 @@ export class WccSidebar extends DeesElement {
|
||||
class="selectOption ${this.selectedItem === item ? 'selected' : null}"
|
||||
@click=${async () => {
|
||||
const domtools = await plugins.deesDomtools.DomTools.setupDomTools();
|
||||
this.selectItem('page', pageName, item);
|
||||
this.selectItem('page', pageName, item, 0);
|
||||
}}
|
||||
>
|
||||
<i class="material-symbols-outlined">insert_drive_file</i>
|
||||
@@ -184,31 +239,83 @@ export class WccSidebar extends DeesElement {
|
||||
${(() => {
|
||||
const elements = Object.keys(this.dashboardRef.elements);
|
||||
return elements.map(elementName => {
|
||||
const item = this.dashboardRef.elements[elementName];
|
||||
return html`
|
||||
<div
|
||||
class="selectOption ${this.selectedItem === item ? 'selected' : null}"
|
||||
@click=${async () => {
|
||||
const domtools = await plugins.deesDomtools.DomTools.setupDomTools();
|
||||
this.selectItem('element', elementName, item);
|
||||
}}
|
||||
>
|
||||
<i class="material-symbols-outlined">featured_video</i>
|
||||
<div class="text">${elementName}</div>
|
||||
</div>
|
||||
`;
|
||||
const item = this.dashboardRef.elements[elementName] as any;
|
||||
const demoCount = item.demo ? getDemoCount(item.demo) : 0;
|
||||
const isMultiDemo = item.demo && hasMultipleDemos(item.demo);
|
||||
const isExpanded = this.expandedElements.has(elementName);
|
||||
const isSelected = this.selectedItem === item;
|
||||
|
||||
if (isMultiDemo) {
|
||||
// Multi-demo element - render as expandable folder
|
||||
return html`
|
||||
<div
|
||||
class="selectOption folder ${isExpanded ? 'expanded' : ''} ${isSelected ? 'selected' : ''}"
|
||||
@click=${() => this.toggleExpanded(elementName)}
|
||||
>
|
||||
<i class="material-symbols-outlined expand-icon">chevron_right</i>
|
||||
<i class="material-symbols-outlined">folder</i>
|
||||
<div class="text">${elementName}</div>
|
||||
</div>
|
||||
${isExpanded ? html`
|
||||
<div class="demo-children">
|
||||
${Array.from({ length: demoCount }, (_, i) => {
|
||||
const demoIndex = i;
|
||||
const isThisDemoSelected = isSelected && this.dashboardRef.selectedDemoIndex === demoIndex;
|
||||
return html`
|
||||
<div
|
||||
class="demo-child ${isThisDemoSelected ? 'selected' : ''}"
|
||||
@click=${async () => {
|
||||
await plugins.deesDomtools.DomTools.setupDomTools();
|
||||
this.selectItem('element', elementName, item, demoIndex);
|
||||
}}
|
||||
>
|
||||
<i class="material-symbols-outlined">play_circle</i>
|
||||
<div class="text">demo${demoIndex + 1}</div>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
` : null}
|
||||
`;
|
||||
} else {
|
||||
// Single demo element - render as normal
|
||||
return html`
|
||||
<div
|
||||
class="selectOption ${isSelected ? 'selected' : null}"
|
||||
@click=${async () => {
|
||||
await plugins.deesDomtools.DomTools.setupDomTools();
|
||||
this.selectItem('element', elementName, item, 0);
|
||||
}}
|
||||
>
|
||||
<i class="material-symbols-outlined">featured_video</i>
|
||||
<div class="text">${elementName}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
})()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: TTemplateFactory | DeesElement) {
|
||||
private toggleExpanded(elementName: string) {
|
||||
const newSet = new Set(this.expandedElements);
|
||||
if (newSet.has(elementName)) {
|
||||
newSet.delete(elementName);
|
||||
} else {
|
||||
newSet.add(elementName);
|
||||
}
|
||||
this.expandedElements = newSet;
|
||||
}
|
||||
|
||||
public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: TTemplateFactory | DeesElement, demoIndex: number = 0) {
|
||||
console.log('selected item');
|
||||
console.log(itemNameArg);
|
||||
console.log(itemArg);
|
||||
console.log('demo index:', demoIndex);
|
||||
this.selectedItem = itemArg;
|
||||
this.selectedType = typeArg;
|
||||
this.dashboardRef.selectedDemoIndex = demoIndex;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('selectedType', {
|
||||
detail: typeArg
|
||||
@@ -224,7 +331,11 @@ export class WccSidebar extends DeesElement {
|
||||
detail: itemArg
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
this.dashboardRef.buildUrl();
|
||||
|
||||
// Force re-render to update demo child selection indicator
|
||||
// (needed when switching between demos of the same element)
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,39 @@ import type { TemplateResult } from 'lit';
|
||||
|
||||
export type TTemplateFactory = () => TemplateResult | Promise<TemplateResult>;
|
||||
|
||||
// Demo can be a single function or an array of functions
|
||||
export type TDemoDefinition = TTemplateFactory | TTemplateFactory[];
|
||||
|
||||
export const resolveTemplateFactory = async (
|
||||
factoryArg: TTemplateFactory
|
||||
): Promise<TemplateResult> => {
|
||||
return await Promise.resolve(factoryArg());
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the number of demos for an element
|
||||
*/
|
||||
export const getDemoCount = (demo: TDemoDefinition): number => {
|
||||
if (Array.isArray(demo)) {
|
||||
return demo.length;
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a specific demo by index (0-based internally, displayed as 1-based)
|
||||
*/
|
||||
export const getDemoAtIndex = (demo: TDemoDefinition, index: number): TTemplateFactory | null => {
|
||||
if (Array.isArray(demo)) {
|
||||
return demo[index] ?? null;
|
||||
}
|
||||
// Single demo - only index 0 is valid
|
||||
return index === 0 ? demo : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if an element has multiple demos
|
||||
*/
|
||||
export const hasMultipleDemos = (demo: TDemoDefinition): boolean => {
|
||||
return Array.isArray(demo) && demo.length > 1;
|
||||
};
|
||||
|
||||
@@ -235,9 +235,11 @@ export class RecorderService {
|
||||
}
|
||||
}
|
||||
|
||||
private handleRecordingComplete(): void {
|
||||
private async handleRecordingComplete(): Promise<void> {
|
||||
// Create blob from recorded chunks
|
||||
this._recordedBlob = new Blob(this.recordedChunks, { type: 'video/webm' });
|
||||
const blob = new Blob(this.recordedChunks, { type: 'video/webm' });
|
||||
|
||||
this._recordedBlob = blob;
|
||||
|
||||
// Stop all tracks
|
||||
if (this.currentStream) {
|
||||
|
||||
Reference in New Issue
Block a user