diff --git a/package-lock.json b/package-lock.json index e823a05b4..e587038f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "lit.dev", "version": "0.0.0", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, "packages": { "": { @@ -8441,6 +8441,290 @@ "write-file-atomic": "^3.0.3" } }, + "@lit/reactive-element": { + "version": "1.0.0-rc.4", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.0.0-rc.4.tgz", + "integrity": "sha512-dJMha+4NFYdpnUJzRrWTFV5Hdp9QHWFuPnaoqonrKl4lGJVnYez9mu8ev9F/5KM47tjAjh22DuRHrdFDHfOijA==", + "dev": true + }, + "@material/animation": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-OjxWJYSRNs4vnPe8NclaNn+TsNc8TR/wHusGtezF5F+wl+5mh+K69BMXAmURtq3idoRg4XaOSC/Ohk1ovD1fMQ==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "@material/base": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-vy5SQt+jcwwdRFfBvtpVdpULUBujecVUKOXcopaQoi2XIzI5EBHuR4gPN0cd1yfmVEucD6p2fvVv2FJ3Ngr61w==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "@material/dom": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/dom/-/dom-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-iUpZG6Bb2l/PfNV2Fb/pXfG1p4Bz4PC9A7ATPlKfcU5HioObcnYVc/+Hrtaw8eu28BNIc+VVROtbfpqG/YgKSQ==", + "dev": true, + "requires": { + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "@material/elevation": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-AqN/tsTGGyBzZ7CtoSMBY9bDYvCuUt98EUfiGjZGXcf4HgoHV3Cn/JSLrhru5Cq8Nx6HF6AmHh3dQCfNCQduew==", + "dev": true, + "requires": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "@material/feature-targeting": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-CrVoGNu0ym52OPEKy3kgeNL2oSWOCBYbYxSH3GhERxCq5FwGBN+XmK/ZDLFVQlHYy3v8x4TqVEwXviCeumNTxQ==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "@material/mwc-base": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-base/-/mwc-base-0.25.3.tgz", + "integrity": "sha512-4wvxZ9dhPr0O4jjOHPmFyn77pafe+h1gHPlT9sbQ+ly8NY/fSn/TXn7/PbxgL8g4ZHxMvD3o7PJopg+6cbHp8Q==", + "dev": true, + "requires": { + "@lit/reactive-element": "1.0.0-rc.4", + "@material/base": "=14.0.0-canary.261f2db59.0", + "@material/dom": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "@material/mwc-icon-button": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-icon-button/-/mwc-icon-button-0.25.3.tgz", + "integrity": "sha512-FexkMpK3ZSHh7NF+PIqvVhvAbBOgFDYPck/lqnxIDC3VGJ0rjD/1MqevDy2fY6IcHGlc8Ai7VuYbdQ6Cvw8WcQ==", + "dev": true, + "requires": { + "@material/mwc-ripple": "^0.25.3", + "lit": "^2.0.0", + "tslib": "^2.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "@material/mwc-ripple": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-ripple/-/mwc-ripple-0.25.3.tgz", + "integrity": "sha512-G/gt/csxgME6/sAku3GiuB0O2LLvoPWsRTLq/9iABpaGLJjqaKHvNg/IVzNDdF3YZT7EORgR9cBWWl7umA4i4Q==", + "dev": true, + "requires": { + "@material/dom": "=14.0.0-canary.261f2db59.0", + "@material/mwc-base": "^0.25.3", + "@material/ripple": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "@material/mwc-slider": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-slider/-/mwc-slider-0.25.3.tgz", + "integrity": "sha512-HhjOwi9s/ssGrMaeOwvVXAZQJ3RQc7qKBe7KBA4diRF3Fe4UvjtFsXJN5eZAXmi0LwZPWNnzxKP1JAQBcJWszQ==", + "dev": true, + "requires": { + "@material/dom": "=14.0.0-canary.261f2db59.0", + "@material/mwc-base": "^0.25.3", + "@material/mwc-ripple": "^0.25.3", + "@material/slider": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "@material/ripple": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-3FLCLj8X7KrFfuYBHJg1b7Odb3V/AW7fxk3m1i1zhDnygKmlQ/abVucH1s2qbX3Y+JIiq+5/C5407h9BFtOf+A==", + "dev": true, + "requires": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/dom": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "@material/rtl": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-bVnXBbUsHs57+EXdeFbcwaKy3lT/itI/qTLmJ88ar0qaGEujO1GmESHm3ioqkeo4kQpTfDhBwQGeEi1aDaTdFg==", + "dev": true, + "requires": { + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "@material/slider": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/slider/-/slider-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-JoTlKGDf5+Ao0JNNlpiNM1h3TezhOELpM8dmCs1IwCzw7pWepihSa4oXemdDadHKAYM0HOIo2OQVcark96/WSQ==", + "dev": true, + "requires": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/dom": "14.0.0-canary.261f2db59.0", + "@material/elevation": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/ripple": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "@material/typography": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "@material/theme": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-bUqyFT0QF8Nxx02fekt3CXIfC9DEPOPdo2hjgdtvhrNP+vftbkI2tKZ5/uRUnVA+zqQAOyIl5z6FOMg4fyemCA==", + "dev": true, + "requires": { + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "@material/typography": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-WOCdcNkD5KBRAwICcRqWBRG3cDkyrwK5USTNmG0oxnwnZAN7daOpPTdLppVAhadE7faj8d67ON+V9pH7+T62FQ==", + "dev": true, + "requires": { + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -8646,8 +8930,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "dev": true, - "requires": {} + "dev": true }, "@octokit/plugin-rest-endpoint-methods": { "version": "5.13.0", @@ -8735,6 +9018,22 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/trusted-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", + "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -9362,8 +9661,8 @@ "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", "dev": true, "requires": { - "is-text-path": "^1.0.1", "JSONStream": "^1.0.4", + "is-text-path": "^1.0.1", "lodash": "^4.17.15", "meow": "^8.0.0", "split2": "^3.0.0", @@ -10724,16 +11023,6 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, "jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -10911,6 +11200,52 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "lit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.2.1.tgz", + "integrity": "sha512-dSe++R50JqrvNGXmI9OE13de1z5U/Y3J2dTm/9GC86vedI8ILoR8ZGnxfThFpvQ9m0lR0qRnIR4IiKj/jDCfYw==", + "dev": true, + "requires": { + "@lit/reactive-element": "^1.3.0", + "lit-element": "^3.2.0", + "lit-html": "^2.2.0" + }, + "dependencies": { + "@lit/reactive-element": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.3.1.tgz", + "integrity": "sha512-nOJARIr3pReqK3hfFCSW2Zg/kFcFsSAlIE7z4a0C9D2dPrgD/YSn3ZP2ET/rxKB65SXyG7jJbkynBRm+tGlacw==", + "dev": true + } + } + }, + "lit-element": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.2.0.tgz", + "integrity": "sha512-HbE7yt2SnUtg5DCrWt028oaU4D5F4k/1cntAFHTkzY8ZIa8N0Wmu92PxSxucsQSOXlODFrICkQ5x/tEshKi13g==", + "dev": true, + "requires": { + "@lit/reactive-element": "^1.3.0", + "lit-html": "^2.2.0" + }, + "dependencies": { + "@lit/reactive-element": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.3.1.tgz", + "integrity": "sha512-nOJARIr3pReqK3hfFCSW2Zg/kFcFsSAlIE7z4a0C9D2dPrgD/YSn3ZP2ET/rxKB65SXyG7jJbkynBRm+tGlacw==", + "dev": true + } + } + }, + "lit-html": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.2.1.tgz", + "integrity": "sha512-AiJ/Rs0awjICs2FioTnHSh+Np5dhYSkyRczKy3wKjp8qjLhr1Ov+GiHrUQNdX8ou1LMuznpIME990AZsa/tR8g==", + "dev": true, + "requires": { + "@types/trusted-types": "^2.0.2" + } + }, "load-json-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz", @@ -12857,15 +13192,6 @@ "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", "dev": true }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -12908,6 +13234,15 @@ "define-properties": "^1.1.3" } }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", diff --git a/package.json b/package.json index 57300da63..c2c31e896 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,8 @@ } }, "devDependencies": { + "@material/mwc-icon-button": "^0.25.3", + "@material/mwc-slider": "^0.25.3", "lerna": "^4.0.0", "npm-run-all": "^4.1.5", "prettier": "^2.1.2", diff --git a/packages/lit-dev-content/package.json b/packages/lit-dev-content/package.json index f7f207e56..f40358a59 100644 --- a/packages/lit-dev-content/package.json +++ b/packages/lit-dev-content/package.json @@ -182,7 +182,8 @@ "rollup-plugin-minify-html-literals": "^1.2.6", "rollup-plugin-summary": "^1.2.3", "rollup-plugin-terser": "^7.0.2", - "slugify": "^1.3.6" + "slugify": "^1.3.6", + "three": "^0.139.0" }, "dependencies": { "@lion/combobox": "^0.8.3", diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/00/after/brick-viewer.ts b/packages/lit-dev-content/samples/tutorials/brick-viewer/00/after/brick-viewer.ts new file mode 100644 index 000000000..c1e3b545d --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/00/after/brick-viewer.ts @@ -0,0 +1,204 @@ +import { ifDefined } from "lit/directives/if-defined.js"; +import { LitElement,PropertyValues, css, html } from 'lit'; +import { customElement, query, property } from 'lit/decorators.js'; + +// @ts-ignore +import * as THREE from "three"; +// @ts-ignore +import { LDrawLoader } from "three/examples/jsm/loaders/LDrawLoader.js"; +// @ts-ignore +import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; + +import "@material/mwc-icon-button"; +import "@material/mwc-slider"; +// @ts-ignore +import { Slider } from "@material/mwc-slider"; + +@customElement("brick-viewer") +export class BrickViewer extends LitElement { + static styles = css` + :host { + display: block; + position: relative; + } + #controls { + position: absolute; + bottom: 0; + width: 100%; + display: flex; + } + mwc-slider { + flex-grow: 1; + } + `; + + @property({ type: String }) + src: string | null = null; + + @property({ type: Number, reflect: true }) + step: number = 1; + + @query("mwc-slider") + slider!: Slider | null; + + private _scene = new THREE.Scene(); + private _renderer = new THREE.WebGLRenderer({ antialias: true }); + private _camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10); + private _controls = new OrbitControls(this._camera, this._renderer.domElement); + private _loader = new LDrawLoader(); + private _model: any; + private _numConstructionSteps?: number; + + async firstUpdated() { + this._camera = new THREE.PerspectiveCamera( + 45, + this.clientWidth / this.clientHeight, + 1, + 10000 + ); + this._camera.position.set(150, 200, 250); + + this._scene = new THREE.Scene(); + this._scene.background = new THREE.Color(0xdeebed); + + const ambientLight = new THREE.AmbientLight(0xdeebed, 0.4); + this._scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 1); + directionalLight.position.set(-1000, 1200, 1500); + this._scene.add(directionalLight); + + this._renderer = new THREE.WebGLRenderer({ antialias: true }); + this._renderer.setPixelRatio(window.devicePixelRatio); + this._renderer.setSize(this.offsetWidth, this.offsetHeight); + + this._controls = new OrbitControls(this._camera, this._renderer.domElement); + this._controls.addEventListener("change", () => + requestAnimationFrame(this._animate) + ); + + (this._loader as any).separateObjects = true; + + this._animate(); + + const resizeObserver = new ResizeObserver(this._onResize); + resizeObserver.observe(this); + + // Buttons are loading after slider, so slider's initial width calculation is wrong. + if (this.slider) { + await this.slider.updateComplete; + this.slider.layout(); + } + } + + private _onResize = (entries: ResizeObserverEntry[]) => { + const { width, height } = entries[0].contentRect; + this._renderer.setSize(width, height); + this._camera.aspect = width / height; + this._camera.updateProjectionMatrix(); + requestAnimationFrame(this._animate); + }; + + private _restart() { + this.step! = 1; + } + + private _stepBack() { + this.step! -= 1; + } + + private _stepForward() { + this.step! += 1; + } + + private _resetCamera() { + this._controls.reset(); + } + + render() { + return html` + ${this._renderer.domElement} + +
+ + + (this.step = e.detail.value)} + > + + +
+ `; + } + + update(changedProperties: PropertyValues) { + if (changedProperties.has("src")) { + this._loadModel(); + } + if (changedProperties.has("step")) { + this._updateBricksVisibility(); + } + super.update(changedProperties); + } + + private _loadModel() { + if (this.src === null) { + return; + } + // @ts-ignore + this._loader.setPath("").load(this.src, newModel => { + if (this._model !== undefined) { + this._scene.remove(this._model); + this._model = undefined; + } + + this._model = newModel; + + // Convert from LDraw coordinates: rotate 180 degrees around OX + this._model.rotation.x = Math.PI; + this._scene.add(this._model); + + this._numConstructionSteps = this._model.userData.numConstructionSteps; + this.step = this._numConstructionSteps!; + + // Adjust camera + const bbox = new THREE.Box3().setFromObject(this._model); + this._controls.target.copy(bbox.getCenter(new THREE.Vector3())); + this._controls.update(); + this._controls.saveState(); + }); + } + + private _updateBricksVisibility() { + this._model && + this._model.traverse((c: any) => { + if (c.isGroup && this.step) { + c.visible = c.userData.constructionStep <= this.step; + } + }); + requestAnimationFrame(this._animate); + } + + private _animate = () => { + this._renderer.render(this._scene, this._camera); + }; +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/00/after/index.html b/packages/lit-dev-content/samples/tutorials/brick-viewer/00/after/index.html index a51165714..c522266bb 100644 --- a/packages/lit-dev-content/samples/tutorials/brick-viewer/00/after/index.html +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/00/after/index.html @@ -1 +1,54 @@ -
Hello world step 1 completed!
\ No newline at end of file + + + + + + + + +

Brick Viewer

+ + + Car + Bulldozer + Radar Truck + + + + + +

+ The <brick-viewer> element sits in your HTML like any other HTML + element! +

+ + + + diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/00/after/project.json b/packages/lit-dev-content/samples/tutorials/brick-viewer/00/after/project.json index 7aee5eb4f..cfc7995c0 100644 --- a/packages/lit-dev-content/samples/tutorials/brick-viewer/00/after/project.json +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/00/after/project.json @@ -1,6 +1,7 @@ { "extends": "/samples/base.json", "files": { + "brick-viewer.ts": {}, "index.html": {} } } diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/00/before/brick-viewer.ts b/packages/lit-dev-content/samples/tutorials/brick-viewer/00/before/brick-viewer.ts new file mode 100644 index 000000000..45ee38f8f --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/00/before/brick-viewer.ts @@ -0,0 +1,9 @@ +import { LitElement, html } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('brick-viewer') +class BrickViewer extends LitElement { + render() { + return html`
Brick viewer
`; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/00/before/index.html b/packages/lit-dev-content/samples/tutorials/brick-viewer/00/before/index.html index dcb9fa664..4d42a0baa 100644 --- a/packages/lit-dev-content/samples/tutorials/brick-viewer/00/before/index.html +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/00/before/index.html @@ -1 +1,19 @@ -
Hello world step 1!
\ No newline at end of file + + + + + + + + + + + diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/00/before/project.json b/packages/lit-dev-content/samples/tutorials/brick-viewer/00/before/project.json index 7aee5eb4f..cfc7995c0 100644 --- a/packages/lit-dev-content/samples/tutorials/brick-viewer/00/before/project.json +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/00/before/project.json @@ -1,6 +1,7 @@ { "extends": "/samples/base.json", "files": { + "brick-viewer.ts": {}, "index.html": {} } } diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/01/after/brick-viewer.ts b/packages/lit-dev-content/samples/tutorials/brick-viewer/01/after/brick-viewer.ts new file mode 100644 index 000000000..52d597e9a --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/01/after/brick-viewer.ts @@ -0,0 +1,12 @@ +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +@customElement('brick-viewer') +class BrickViewer extends LitElement { + @property({type: String}) + src: string|null = null; + + render() { + return html`
Brick model: ${this.src}
`; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/01/after/index.html b/packages/lit-dev-content/samples/tutorials/brick-viewer/01/after/index.html index 04115e5f0..4d677c6ec 100644 --- a/packages/lit-dev-content/samples/tutorials/brick-viewer/01/after/index.html +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/01/after/index.html @@ -1 +1,19 @@ -
Hello world completed!
\ No newline at end of file + + + + + + + + + + + diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/01/after/project.json b/packages/lit-dev-content/samples/tutorials/brick-viewer/01/after/project.json index 7aee5eb4f..cfc7995c0 100644 --- a/packages/lit-dev-content/samples/tutorials/brick-viewer/01/after/project.json +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/01/after/project.json @@ -1,6 +1,7 @@ { "extends": "/samples/base.json", "files": { + "brick-viewer.ts": {}, "index.html": {} } } diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/01/before/brick-viewer.ts b/packages/lit-dev-content/samples/tutorials/brick-viewer/01/before/brick-viewer.ts new file mode 100644 index 000000000..45ee38f8f --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/01/before/brick-viewer.ts @@ -0,0 +1,9 @@ +import { LitElement, html } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('brick-viewer') +class BrickViewer extends LitElement { + render() { + return html`
Brick viewer
`; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/01/before/index.html b/packages/lit-dev-content/samples/tutorials/brick-viewer/01/before/index.html index 288c0ea86..4d42a0baa 100644 --- a/packages/lit-dev-content/samples/tutorials/brick-viewer/01/before/index.html +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/01/before/index.html @@ -1 +1,19 @@ -
Hello world!
\ No newline at end of file + + + + + + + + + + + diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/01/before/project.json b/packages/lit-dev-content/samples/tutorials/brick-viewer/01/before/project.json index 7aee5eb4f..cfc7995c0 100644 --- a/packages/lit-dev-content/samples/tutorials/brick-viewer/01/before/project.json +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/01/before/project.json @@ -1,6 +1,7 @@ { "extends": "/samples/base.json", "files": { + "brick-viewer.ts": {}, "index.html": {} } } diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/02/after/brick-viewer.ts b/packages/lit-dev-content/samples/tutorials/brick-viewer/02/after/brick-viewer.ts new file mode 100644 index 000000000..316859bd0 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/02/after/brick-viewer.ts @@ -0,0 +1,122 @@ +import { ifDefined } from "lit/directives/if-defined.js"; +import { LitElement,PropertyValues, css, html } from 'lit'; +import { customElement, query, property } from 'lit/decorators.js'; + +// @ts-ignore +import * as THREE from "three"; +// @ts-ignore +import { LDrawLoader } from "three/examples/jsm/loaders/LDrawLoader.js"; +// @ts-ignore +import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; + +@customElement("brick-viewer") +export class BrickViewer extends LitElement { + static styles = css` + :host { + display: block; + } + `; + + @property({ type: String }) + src: string | null = null; + + @property({ type: Number, reflect: true }) + step: number = 1; + + private _scene = new THREE.Scene(); + private _renderer = new THREE.WebGLRenderer({ antialias: true }); + private _camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10); + private _controls = new OrbitControls(this._camera, this._renderer.domElement); + private _loader = new LDrawLoader(); + private _model: any; + private _numConstructionSteps?: number; + + async firstUpdated() { + this._camera = new THREE.PerspectiveCamera( + 45, + this.clientWidth / this.clientHeight, + 1, + 10000 + ); + this._camera.position.set(150, 200, 250); + + this._scene = new THREE.Scene(); + this._scene.background = new THREE.Color(0xdeebed); + + const ambientLight = new THREE.AmbientLight(0xdeebed, 0.4); + this._scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 1); + directionalLight.position.set(-1000, 1200, 1500); + this._scene.add(directionalLight); + + this._renderer = new THREE.WebGLRenderer({ antialias: true }); + this._renderer.setPixelRatio(window.devicePixelRatio); + this._renderer.setSize(this.offsetWidth, this.offsetHeight); + + this._controls = new OrbitControls(this._camera, this._renderer.domElement); + this._controls.addEventListener("change", () => + requestAnimationFrame(this._animate) + ); + + (this._loader as any).separateObjects = true; + + this._animate(); + + const resizeObserver = new ResizeObserver(this._onResize); + resizeObserver.observe(this); + } + + private _onResize = (entries: ResizeObserverEntry[]) => { + const { width, height } = entries[0].contentRect; + this._renderer.setSize(width, height); + this._camera.aspect = width / height; + this._camera.updateProjectionMatrix(); + requestAnimationFrame(this._animate); + }; + + render() { + return html` + ${this._renderer.domElement} + `; + } + + update(changedProperties: PropertyValues) { + if (changedProperties.has("src")) { + this._loadModel(); + } + super.update(changedProperties); + } + + private _loadModel() { + if (this.src === null) { + return; + } + // @ts-ignore + this._loader.setPath("").load(this.src, newModel => { + if (this._model !== undefined) { + this._scene.remove(this._model); + this._model = undefined; + } + + this._model = newModel; + + // Convert from LDraw coordinates: rotate 180 degrees around OX + this._model.rotation.x = Math.PI; + this._scene.add(this._model); + + this._numConstructionSteps = this._model.userData.numConstructionSteps; + this.step = this._numConstructionSteps!; + + // Adjust camera + const bbox = new THREE.Box3().setFromObject(this._model); + this._controls.target.copy(bbox.getCenter(new THREE.Vector3())); + this._controls.update(); + this._controls.saveState(); + }); + } + + private _animate = () => { + this._renderer.render(this._scene, this._camera); + }; +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/02/after/index.html b/packages/lit-dev-content/samples/tutorials/brick-viewer/02/after/index.html new file mode 100644 index 000000000..48b32de1f --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/02/after/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/02/after/project.json b/packages/lit-dev-content/samples/tutorials/brick-viewer/02/after/project.json new file mode 100644 index 000000000..cfc7995c0 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/02/after/project.json @@ -0,0 +1,7 @@ +{ + "extends": "/samples/base.json", + "files": { + "brick-viewer.ts": {}, + "index.html": {} + } +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/02/before/brick-viewer.ts b/packages/lit-dev-content/samples/tutorials/brick-viewer/02/before/brick-viewer.ts new file mode 100644 index 000000000..9442b6291 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/02/before/brick-viewer.ts @@ -0,0 +1,16 @@ +import { LitElement, html, css } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +// @ts-ignore +import * as THREE from 'three'; +// @ts-ignore +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; + +@customElement('brick-viewer') +class BrickViewer extends LitElement { + @property({type: String}) + src: string|null = null; + + render() { + return html`
Brick model: ${this.src}
`; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/02/before/index.html b/packages/lit-dev-content/samples/tutorials/brick-viewer/02/before/index.html new file mode 100644 index 000000000..4d42a0baa --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/02/before/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/02/before/project.json b/packages/lit-dev-content/samples/tutorials/brick-viewer/02/before/project.json new file mode 100644 index 000000000..cfc7995c0 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/02/before/project.json @@ -0,0 +1,7 @@ +{ + "extends": "/samples/base.json", + "files": { + "brick-viewer.ts": {}, + "index.html": {} + } +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/03/after/brick-viewer.ts b/packages/lit-dev-content/samples/tutorials/brick-viewer/03/after/brick-viewer.ts new file mode 100644 index 000000000..a215207b0 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/03/after/brick-viewer.ts @@ -0,0 +1,138 @@ +import { LitElement,PropertyValues, css, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +// @ts-ignore +import * as THREE from "three"; +// @ts-ignore +import { LDrawLoader } from "three/examples/jsm/loaders/LDrawLoader.js"; +// @ts-ignore +import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; + +@customElement("brick-viewer") +export class BrickViewer extends LitElement { + static styles = css` + :host { + display: block; + } + `; + + @property({ type: String }) + src: string | null = null; + + @property({ type: Number, reflect: true }) + step?: number; + + private _scene = new THREE.Scene(); + private _renderer = new THREE.WebGLRenderer({ antialias: true }); + private _camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10); + private _controls = new OrbitControls(this._camera, this._renderer.domElement); + private _loader = new LDrawLoader(); + private _model: any; + private _numConstructionSteps?: number; + + async firstUpdated() { + this._camera = new THREE.PerspectiveCamera( + 45, + this.clientWidth / this.clientHeight, + 1, + 10000 + ); + this._camera.position.set(150, 200, 250); + + this._scene = new THREE.Scene(); + this._scene.background = new THREE.Color(0xdeebed); + + const ambientLight = new THREE.AmbientLight(0xdeebed, 0.4); + this._scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 1); + directionalLight.position.set(-1000, 1200, 1500); + this._scene.add(directionalLight); + + this._renderer = new THREE.WebGLRenderer({ antialias: true }); + this._renderer.setPixelRatio(window.devicePixelRatio); + this._renderer.setSize(this.offsetWidth, this.offsetHeight); + + this._controls = new OrbitControls(this._camera, this._renderer.domElement); + this._controls.addEventListener("change", () => + requestAnimationFrame(this._animate) + ); + + (this._loader as any).separateObjects = true; + + this._animate(); + + const resizeObserver = new ResizeObserver(this._onResize); + resizeObserver.observe(this); + } + + private _onResize = (entries: ResizeObserverEntry[]) => { + const { width, height } = entries[0].contentRect; + this._renderer.setSize(width, height); + this._camera.aspect = width / height; + this._camera.updateProjectionMatrix(); + requestAnimationFrame(this._animate); + }; + + render() { + return html` + ${this._renderer.domElement} + `; + } + + update(changedProperties: PropertyValues) { + if (changedProperties.has('src')) { + this._loadModel(); + } + if (changedProperties.has('step')) { + this._updateBricksVisibility(); + } + super.update(changedProperties); + } + + private _updateBricksVisibility() { + this._model && this._model.traverse((c: any) => { + if (c.isGroup && this.step) { + c.visible = c.userData.constructionStep <= this.step; + } + }); + requestAnimationFrame(this._animate.bind(this)); + this.requestUpdate(); + } + + private _loadModel() { + if (this.src === null) { + return; + } + this._loader + .setPath('') + // Using our src property! + .load(this.src, (newModel: any) => { + + if (this._model !== undefined) { + this._scene.remove(this._model); + this._model = undefined; + } + + this._model = newModel; + + // Convert from LDraw coordinates: rotate 180 degrees around OX + this._model.rotation.x = Math.PI; + this._scene.add(this._model); + + this._numConstructionSteps = this._model.userData.numConstructionSteps; + + const bbox = new THREE.Box3().setFromObject(this._model); + this._controls.target.copy(bbox.getCenter(new THREE.Vector3())); + this._controls.update(); + this._controls.saveState(); + + this.step ??= this._numConstructionSteps!; + this._updateBricksVisibility(); + }); + } + + private _animate = () => { + this._renderer.render(this._scene, this._camera); + }; +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/03/after/index.html b/packages/lit-dev-content/samples/tutorials/brick-viewer/03/after/index.html new file mode 100644 index 000000000..c01f57338 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/03/after/index.html @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/03/after/project.json b/packages/lit-dev-content/samples/tutorials/brick-viewer/03/after/project.json new file mode 100644 index 000000000..cfc7995c0 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/03/after/project.json @@ -0,0 +1,7 @@ +{ + "extends": "/samples/base.json", + "files": { + "brick-viewer.ts": {}, + "index.html": {} + } +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/03/before/brick-viewer.ts b/packages/lit-dev-content/samples/tutorials/brick-viewer/03/before/brick-viewer.ts new file mode 100644 index 000000000..316859bd0 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/03/before/brick-viewer.ts @@ -0,0 +1,122 @@ +import { ifDefined } from "lit/directives/if-defined.js"; +import { LitElement,PropertyValues, css, html } from 'lit'; +import { customElement, query, property } from 'lit/decorators.js'; + +// @ts-ignore +import * as THREE from "three"; +// @ts-ignore +import { LDrawLoader } from "three/examples/jsm/loaders/LDrawLoader.js"; +// @ts-ignore +import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; + +@customElement("brick-viewer") +export class BrickViewer extends LitElement { + static styles = css` + :host { + display: block; + } + `; + + @property({ type: String }) + src: string | null = null; + + @property({ type: Number, reflect: true }) + step: number = 1; + + private _scene = new THREE.Scene(); + private _renderer = new THREE.WebGLRenderer({ antialias: true }); + private _camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10); + private _controls = new OrbitControls(this._camera, this._renderer.domElement); + private _loader = new LDrawLoader(); + private _model: any; + private _numConstructionSteps?: number; + + async firstUpdated() { + this._camera = new THREE.PerspectiveCamera( + 45, + this.clientWidth / this.clientHeight, + 1, + 10000 + ); + this._camera.position.set(150, 200, 250); + + this._scene = new THREE.Scene(); + this._scene.background = new THREE.Color(0xdeebed); + + const ambientLight = new THREE.AmbientLight(0xdeebed, 0.4); + this._scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 1); + directionalLight.position.set(-1000, 1200, 1500); + this._scene.add(directionalLight); + + this._renderer = new THREE.WebGLRenderer({ antialias: true }); + this._renderer.setPixelRatio(window.devicePixelRatio); + this._renderer.setSize(this.offsetWidth, this.offsetHeight); + + this._controls = new OrbitControls(this._camera, this._renderer.domElement); + this._controls.addEventListener("change", () => + requestAnimationFrame(this._animate) + ); + + (this._loader as any).separateObjects = true; + + this._animate(); + + const resizeObserver = new ResizeObserver(this._onResize); + resizeObserver.observe(this); + } + + private _onResize = (entries: ResizeObserverEntry[]) => { + const { width, height } = entries[0].contentRect; + this._renderer.setSize(width, height); + this._camera.aspect = width / height; + this._camera.updateProjectionMatrix(); + requestAnimationFrame(this._animate); + }; + + render() { + return html` + ${this._renderer.domElement} + `; + } + + update(changedProperties: PropertyValues) { + if (changedProperties.has("src")) { + this._loadModel(); + } + super.update(changedProperties); + } + + private _loadModel() { + if (this.src === null) { + return; + } + // @ts-ignore + this._loader.setPath("").load(this.src, newModel => { + if (this._model !== undefined) { + this._scene.remove(this._model); + this._model = undefined; + } + + this._model = newModel; + + // Convert from LDraw coordinates: rotate 180 degrees around OX + this._model.rotation.x = Math.PI; + this._scene.add(this._model); + + this._numConstructionSteps = this._model.userData.numConstructionSteps; + this.step = this._numConstructionSteps!; + + // Adjust camera + const bbox = new THREE.Box3().setFromObject(this._model); + this._controls.target.copy(bbox.getCenter(new THREE.Vector3())); + this._controls.update(); + this._controls.saveState(); + }); + } + + private _animate = () => { + this._renderer.render(this._scene, this._camera); + }; +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/03/before/index.html b/packages/lit-dev-content/samples/tutorials/brick-viewer/03/before/index.html new file mode 100644 index 000000000..48b32de1f --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/03/before/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/03/before/project.json b/packages/lit-dev-content/samples/tutorials/brick-viewer/03/before/project.json new file mode 100644 index 000000000..cfc7995c0 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/03/before/project.json @@ -0,0 +1,7 @@ +{ + "extends": "/samples/base.json", + "files": { + "brick-viewer.ts": {}, + "index.html": {} + } +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/04/after/brick-viewer.ts b/packages/lit-dev-content/samples/tutorials/brick-viewer/04/after/brick-viewer.ts new file mode 100644 index 000000000..c1e3b545d --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/04/after/brick-viewer.ts @@ -0,0 +1,204 @@ +import { ifDefined } from "lit/directives/if-defined.js"; +import { LitElement,PropertyValues, css, html } from 'lit'; +import { customElement, query, property } from 'lit/decorators.js'; + +// @ts-ignore +import * as THREE from "three"; +// @ts-ignore +import { LDrawLoader } from "three/examples/jsm/loaders/LDrawLoader.js"; +// @ts-ignore +import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; + +import "@material/mwc-icon-button"; +import "@material/mwc-slider"; +// @ts-ignore +import { Slider } from "@material/mwc-slider"; + +@customElement("brick-viewer") +export class BrickViewer extends LitElement { + static styles = css` + :host { + display: block; + position: relative; + } + #controls { + position: absolute; + bottom: 0; + width: 100%; + display: flex; + } + mwc-slider { + flex-grow: 1; + } + `; + + @property({ type: String }) + src: string | null = null; + + @property({ type: Number, reflect: true }) + step: number = 1; + + @query("mwc-slider") + slider!: Slider | null; + + private _scene = new THREE.Scene(); + private _renderer = new THREE.WebGLRenderer({ antialias: true }); + private _camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10); + private _controls = new OrbitControls(this._camera, this._renderer.domElement); + private _loader = new LDrawLoader(); + private _model: any; + private _numConstructionSteps?: number; + + async firstUpdated() { + this._camera = new THREE.PerspectiveCamera( + 45, + this.clientWidth / this.clientHeight, + 1, + 10000 + ); + this._camera.position.set(150, 200, 250); + + this._scene = new THREE.Scene(); + this._scene.background = new THREE.Color(0xdeebed); + + const ambientLight = new THREE.AmbientLight(0xdeebed, 0.4); + this._scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 1); + directionalLight.position.set(-1000, 1200, 1500); + this._scene.add(directionalLight); + + this._renderer = new THREE.WebGLRenderer({ antialias: true }); + this._renderer.setPixelRatio(window.devicePixelRatio); + this._renderer.setSize(this.offsetWidth, this.offsetHeight); + + this._controls = new OrbitControls(this._camera, this._renderer.domElement); + this._controls.addEventListener("change", () => + requestAnimationFrame(this._animate) + ); + + (this._loader as any).separateObjects = true; + + this._animate(); + + const resizeObserver = new ResizeObserver(this._onResize); + resizeObserver.observe(this); + + // Buttons are loading after slider, so slider's initial width calculation is wrong. + if (this.slider) { + await this.slider.updateComplete; + this.slider.layout(); + } + } + + private _onResize = (entries: ResizeObserverEntry[]) => { + const { width, height } = entries[0].contentRect; + this._renderer.setSize(width, height); + this._camera.aspect = width / height; + this._camera.updateProjectionMatrix(); + requestAnimationFrame(this._animate); + }; + + private _restart() { + this.step! = 1; + } + + private _stepBack() { + this.step! -= 1; + } + + private _stepForward() { + this.step! += 1; + } + + private _resetCamera() { + this._controls.reset(); + } + + render() { + return html` + ${this._renderer.domElement} + +
+ + + (this.step = e.detail.value)} + > + + +
+ `; + } + + update(changedProperties: PropertyValues) { + if (changedProperties.has("src")) { + this._loadModel(); + } + if (changedProperties.has("step")) { + this._updateBricksVisibility(); + } + super.update(changedProperties); + } + + private _loadModel() { + if (this.src === null) { + return; + } + // @ts-ignore + this._loader.setPath("").load(this.src, newModel => { + if (this._model !== undefined) { + this._scene.remove(this._model); + this._model = undefined; + } + + this._model = newModel; + + // Convert from LDraw coordinates: rotate 180 degrees around OX + this._model.rotation.x = Math.PI; + this._scene.add(this._model); + + this._numConstructionSteps = this._model.userData.numConstructionSteps; + this.step = this._numConstructionSteps!; + + // Adjust camera + const bbox = new THREE.Box3().setFromObject(this._model); + this._controls.target.copy(bbox.getCenter(new THREE.Vector3())); + this._controls.update(); + this._controls.saveState(); + }); + } + + private _updateBricksVisibility() { + this._model && + this._model.traverse((c: any) => { + if (c.isGroup && this.step) { + c.visible = c.userData.constructionStep <= this.step; + } + }); + requestAnimationFrame(this._animate); + } + + private _animate = () => { + this._renderer.render(this._scene, this._camera); + }; +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/04/after/index.html b/packages/lit-dev-content/samples/tutorials/brick-viewer/04/after/index.html new file mode 100644 index 000000000..7059296d8 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/04/after/index.html @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/04/after/project.json b/packages/lit-dev-content/samples/tutorials/brick-viewer/04/after/project.json new file mode 100644 index 000000000..cfc7995c0 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/04/after/project.json @@ -0,0 +1,7 @@ +{ + "extends": "/samples/base.json", + "files": { + "brick-viewer.ts": {}, + "index.html": {} + } +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/04/before/brick-viewer.ts b/packages/lit-dev-content/samples/tutorials/brick-viewer/04/before/brick-viewer.ts new file mode 100644 index 000000000..a215207b0 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/04/before/brick-viewer.ts @@ -0,0 +1,138 @@ +import { LitElement,PropertyValues, css, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +// @ts-ignore +import * as THREE from "three"; +// @ts-ignore +import { LDrawLoader } from "three/examples/jsm/loaders/LDrawLoader.js"; +// @ts-ignore +import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; + +@customElement("brick-viewer") +export class BrickViewer extends LitElement { + static styles = css` + :host { + display: block; + } + `; + + @property({ type: String }) + src: string | null = null; + + @property({ type: Number, reflect: true }) + step?: number; + + private _scene = new THREE.Scene(); + private _renderer = new THREE.WebGLRenderer({ antialias: true }); + private _camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10); + private _controls = new OrbitControls(this._camera, this._renderer.domElement); + private _loader = new LDrawLoader(); + private _model: any; + private _numConstructionSteps?: number; + + async firstUpdated() { + this._camera = new THREE.PerspectiveCamera( + 45, + this.clientWidth / this.clientHeight, + 1, + 10000 + ); + this._camera.position.set(150, 200, 250); + + this._scene = new THREE.Scene(); + this._scene.background = new THREE.Color(0xdeebed); + + const ambientLight = new THREE.AmbientLight(0xdeebed, 0.4); + this._scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 1); + directionalLight.position.set(-1000, 1200, 1500); + this._scene.add(directionalLight); + + this._renderer = new THREE.WebGLRenderer({ antialias: true }); + this._renderer.setPixelRatio(window.devicePixelRatio); + this._renderer.setSize(this.offsetWidth, this.offsetHeight); + + this._controls = new OrbitControls(this._camera, this._renderer.domElement); + this._controls.addEventListener("change", () => + requestAnimationFrame(this._animate) + ); + + (this._loader as any).separateObjects = true; + + this._animate(); + + const resizeObserver = new ResizeObserver(this._onResize); + resizeObserver.observe(this); + } + + private _onResize = (entries: ResizeObserverEntry[]) => { + const { width, height } = entries[0].contentRect; + this._renderer.setSize(width, height); + this._camera.aspect = width / height; + this._camera.updateProjectionMatrix(); + requestAnimationFrame(this._animate); + }; + + render() { + return html` + ${this._renderer.domElement} + `; + } + + update(changedProperties: PropertyValues) { + if (changedProperties.has('src')) { + this._loadModel(); + } + if (changedProperties.has('step')) { + this._updateBricksVisibility(); + } + super.update(changedProperties); + } + + private _updateBricksVisibility() { + this._model && this._model.traverse((c: any) => { + if (c.isGroup && this.step) { + c.visible = c.userData.constructionStep <= this.step; + } + }); + requestAnimationFrame(this._animate.bind(this)); + this.requestUpdate(); + } + + private _loadModel() { + if (this.src === null) { + return; + } + this._loader + .setPath('') + // Using our src property! + .load(this.src, (newModel: any) => { + + if (this._model !== undefined) { + this._scene.remove(this._model); + this._model = undefined; + } + + this._model = newModel; + + // Convert from LDraw coordinates: rotate 180 degrees around OX + this._model.rotation.x = Math.PI; + this._scene.add(this._model); + + this._numConstructionSteps = this._model.userData.numConstructionSteps; + + const bbox = new THREE.Box3().setFromObject(this._model); + this._controls.target.copy(bbox.getCenter(new THREE.Vector3())); + this._controls.update(); + this._controls.saveState(); + + this.step ??= this._numConstructionSteps!; + this._updateBricksVisibility(); + }); + } + + private _animate = () => { + this._renderer.render(this._scene, this._camera); + }; +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/04/before/index.html b/packages/lit-dev-content/samples/tutorials/brick-viewer/04/before/index.html new file mode 100644 index 000000000..4e3ea01b3 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/04/before/index.html @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/04/before/project.json b/packages/lit-dev-content/samples/tutorials/brick-viewer/04/before/project.json new file mode 100644 index 000000000..cfc7995c0 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/04/before/project.json @@ -0,0 +1,7 @@ +{ + "extends": "/samples/base.json", + "files": { + "brick-viewer.ts": {}, + "index.html": {} + } +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/05/before/brick-viewer.ts b/packages/lit-dev-content/samples/tutorials/brick-viewer/05/before/brick-viewer.ts new file mode 100644 index 000000000..c15aee363 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/05/before/brick-viewer.ts @@ -0,0 +1,207 @@ +import { ifDefined } from "lit/directives/if-defined.js"; +import { LitElement,PropertyValues, css, html } from 'lit'; +import { customElement, query, property } from 'lit/decorators.js'; + +// @ts-ignore +import * as THREE from "three"; +// @ts-ignore +import { LDrawLoader } from "three/examples/jsm/loaders/LDrawLoader.js"; +// @ts-ignore +import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; + +import "@material/mwc-icon-button"; +import "@material/mwc-slider"; +// @ts-ignore +import { Slider } from "@material/mwc-slider"; + +@customElement("brick-viewer") +export class BrickViewer extends LitElement { + static styles = css` + :host { + display: block; + position: relative; + } + #controls { + position: absolute; + bottom: 0; + width: 100%; + display: flex; + } + mwc-slider { + flex-grow: 1; + } + `; + + @property({ type: String }) + src: string | null = null; + + @property({ type: Number, reflect: true }) + step: number = 1; + + @query("mwc-slider") + slider!: Slider | null; + + private _scene = new THREE.Scene(); + private _renderer = new THREE.WebGLRenderer({ antialias: true }); + private _camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10); + private _controls = new OrbitControls(this._camera, this._renderer.domElement); + private _loader = new LDrawLoader(); + private _model: any; + private _numConstructionSteps?: number; + + async firstUpdated() { + this._camera = new THREE.PerspectiveCamera( + 45, + this.clientWidth / this.clientHeight, + 1, + 10000 + ); + this._camera.position.set(150, 200, 250); + + this._scene = new THREE.Scene(); + this._scene.background = new THREE.Color(0xdeebed); + + const ambientLight = new THREE.AmbientLight(0xdeebed, 0.4); + this._scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 1); + directionalLight.position.set(-1000, 1200, 1500); + this._scene.add(directionalLight); + + this._renderer = new THREE.WebGLRenderer({ antialias: true }); + this._renderer.setPixelRatio(window.devicePixelRatio); + this._renderer.setSize(this.offsetWidth, this.offsetHeight); + + this._controls = new OrbitControls(this._camera, this._renderer.domElement); + this._controls.addEventListener("change", () => + requestAnimationFrame(this._animate) + ); + + (this._loader as any).separateObjects = true; + + this._animate(); + + const resizeObserver = new ResizeObserver(this._onResize); + resizeObserver.observe(this); + + // Buttons are loading after slider, so slider's initial width calculation is wrong. + if (this.slider) { + await this.slider.updateComplete; + this.slider.layout(); + } + } + + private _onResize = (entries: ResizeObserverEntry[]) => { + const { width, height } = entries[0].contentRect; + this._renderer.setSize(width, height); + this._camera.aspect = width / height; + this._camera.updateProjectionMatrix(); + requestAnimationFrame(this._animate); + }; + + private _restart() { + this.step! = 1; + } + + private _stepBack() { + this.step! -= 1; + } + + private _stepForward() { + this.step! += 1; + } + + private _resetCamera() { + this._controls.reset(); + } + + render() { + return html` + ${this._renderer.domElement} + +
+ + + (this.step = e.detail.value)} + > + + +
+ `; + } + + update(changedProperties: PropertyValues) { + if (changedProperties.has("src")) { + this._loadModel(); + } + if (changedProperties.has("step")) { + this._updateBricksVisibility(); + } + super.update(changedProperties); + } + + private _loadModel() { + if (this.src === null) { + return; + } + // @ts-ignore + this._loader.setPath("").load(this.src, newModel => { + if (this._model !== undefined) { + this._scene.remove(this._model); + this._model = undefined; + } + + this._model = newModel; + + // Convert from LDraw coordinates: rotate 180 degrees around OX + this._model.rotation.x = Math.PI; + this._scene.add(this._model); + + this._numConstructionSteps = this._model.userData.numConstructionSteps; + + // Adjust camera + const bbox = new THREE.Box3().setFromObject(this._model); + this._controls.target.copy(bbox.getCenter(new THREE.Vector3())); + this._controls.update(); + this._controls.saveState(); + + this.step ??= this._numConstructionSteps!; + this._updateBricksVisibility(); + }); + } + + private _updateBricksVisibility() { + this._model && + this._model.traverse((c: any) => { + if (c.isGroup && this.step) { + c.visible = c.userData.constructionStep <= this.step; + } + }); + requestAnimationFrame(this._animate); + this.requestUpdate(); + } + + private _animate = () => { + this._renderer.render(this._scene, this._camera); + }; +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/05/before/index.html b/packages/lit-dev-content/samples/tutorials/brick-viewer/05/before/index.html new file mode 100644 index 000000000..7059296d8 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/05/before/index.html @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/05/before/project.json b/packages/lit-dev-content/samples/tutorials/brick-viewer/05/before/project.json new file mode 100644 index 000000000..cfc7995c0 --- /dev/null +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/05/before/project.json @@ -0,0 +1,7 @@ +{ + "extends": "/samples/base.json", + "files": { + "brick-viewer.ts": {}, + "index.html": {} + } +} diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/description.md b/packages/lit-dev-content/samples/tutorials/brick-viewer/description.md index 699b0e63e..cba839535 100644 --- a/packages/lit-dev-content/samples/tutorials/brick-viewer/description.md +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/description.md @@ -1,2 +1 @@ -Learn how to build a 3d brick viewer combining canvas and the best parts of Web -Components! \ No newline at end of file +In this tutorial, you'll build a Brick Viewer web component with the help of lit. \ No newline at end of file diff --git a/packages/lit-dev-content/samples/tutorials/brick-viewer/tutorial.json b/packages/lit-dev-content/samples/tutorials/brick-viewer/tutorial.json index 639c6c654..68f291faf 100644 --- a/packages/lit-dev-content/samples/tutorials/brick-viewer/tutorial.json +++ b/packages/lit-dev-content/samples/tutorials/brick-viewer/tutorial.json @@ -2,18 +2,34 @@ "header": "Build a Brick Viewer", "difficulty": "Intermediate", "size": "medium", - "duration": 70, + "duration": 60, "category": "Build", - "imgSrc": "images/logo-whitebg-padded-1600x800.png", - "imgAlt": "This is the Lit icon", + "imgSrc": "images/tutorials/brick-viewer/preview.gif", + "imgAlt": "This is the demo of the brick viewer component assembling the lego bricks", "steps": [ { - "title": "Build a Brick Viewer", + "title": "Define a Custom Element", "hasAfter": true }, { - "title": "Ya Bricked it!", + "title": "Specifying the LDraw File", "hasAfter": true + }, + { + "title": "Set the Scene with Three.js", + "hasAfter": true + }, + { + "title": "Displaying Partial Models", + "hasAfter": true + }, + { + "title": "Brick Set Navigation", + "hasAfter": true + }, + { + "title": "Conclusion", + "hasAfter": false } ] } \ No newline at end of file diff --git a/packages/lit-dev-content/samples/tutorials/intro-to-lit/tutorial.json b/packages/lit-dev-content/samples/tutorials/intro-to-lit/tutorial.json index a4f157988..f1d22cced 100644 --- a/packages/lit-dev-content/samples/tutorials/intro-to-lit/tutorial.json +++ b/packages/lit-dev-content/samples/tutorials/intro-to-lit/tutorial.json @@ -5,10 +5,6 @@ "duration": 20, "category": "Learn", "steps": [ - { - "title": "Lit tutorial", - "hasAfter": true - }, { "title": "Define a component", "hasAfter": true diff --git a/packages/lit-dev-content/site/images/tutorials/brick-viewer/buttons.png b/packages/lit-dev-content/site/images/tutorials/brick-viewer/buttons.png new file mode 100644 index 000000000..45c41ce9b Binary files /dev/null and b/packages/lit-dev-content/site/images/tutorials/brick-viewer/buttons.png differ diff --git a/packages/lit-dev-content/site/images/tutorials/brick-viewer/final.gif b/packages/lit-dev-content/site/images/tutorials/brick-viewer/final.gif new file mode 100644 index 000000000..e4df720d1 Binary files /dev/null and b/packages/lit-dev-content/site/images/tutorials/brick-viewer/final.gif differ diff --git a/packages/lit-dev-content/site/images/tutorials/brick-viewer/preview.gif b/packages/lit-dev-content/site/images/tutorials/brick-viewer/preview.gif new file mode 100644 index 000000000..e4df720d1 Binary files /dev/null and b/packages/lit-dev-content/site/images/tutorials/brick-viewer/preview.gif differ diff --git a/packages/lit-dev-content/site/tutorials/content/brick-viewer/00.md b/packages/lit-dev-content/site/tutorials/content/brick-viewer/00.md index 1862000d8..8be086f63 100644 --- a/packages/lit-dev-content/site/tutorials/content/brick-viewer/00.md +++ b/packages/lit-dev-content/site/tutorials/content/brick-viewer/00.md @@ -1,44 +1,25 @@ -This is the intro that describes the codelab. +We will be going over how to build a `` component to render a [LDraw](https://threejs.org/docs/#examples/en/loaders/LDrawLoader) model in [THREE.js](https://threejs.org/). -In this codelab we are going to cover - -* canvases -* custom elements -* 3d stuff - -Here is a TS / JS switchable sample +To get started you'll need to create a basic lit component and give it the tag name of `brick-viewer` while returning a simple `div`. {% switchable-sample %} ```ts -@customElement('my-element') -class MyElement extends LitElement { - @property({attribute: false}) items = [1,2,3]; +@customElement('brick-viewer') +class BrickViewer extends LitElement { render() { - html` - `; + return html`
Brick viewer
`; } } ``` ```js -class MyElement extends LitElement { +class BrickViewer extends LitElement { render() { - static properties = {items: {attribute: false}}; - constructor() { - super(); - this.items = [1,2,3]; - } - - html` - `; + return html`
Brick viewer
`; } } -customElements.define('my-element', MyElement); +customElements.define('brick-viewer', BrickViewer); ``` {% endswitchable-sample %} \ No newline at end of file diff --git a/packages/lit-dev-content/site/tutorials/content/brick-viewer/01.md b/packages/lit-dev-content/site/tutorials/content/brick-viewer/01.md index abe09c0fd..867b278e3 100644 --- a/packages/lit-dev-content/site/tutorials/content/brick-viewer/01.md +++ b/packages/lit-dev-content/site/tutorials/content/brick-viewer/01.md @@ -1,7 +1,53 @@ -You are DONE-ZO! Read more about [X](https://lit.dev) [Y](https://google.com) and [Z](https://web.dev). +### Define a property -See also these tutorials maybe? +It would be great if a user of the `` could specify which brick model to display using an attribute, like this: -* list -* list -* list \ No newline at end of file +```html + +``` + +Since you are building an HTML element, you can take advantage of the declarative API and define a source attribute, just like an `` or `