diff --git a/package-lock.json b/package-lock.json index e7772bb4..e67b6b81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "voyager", - "version": "0.35.0", + "version": "0.37.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -175,19 +175,6 @@ } } }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "requires": { - "defer-to-connect": "^1.0.1" - } - }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -605,11 +592,6 @@ "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "dev": true }, - "an-array": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/an-array/-/an-array-1.0.0.tgz", - "integrity": "sha1-wSWlu4JXd4419LT2qpx9D6nkJmU=" - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -679,22 +661,12 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, - "array-shuffle": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-shuffle/-/array-shuffle-1.0.1.tgz", - "integrity": "sha1-fqSIKjVrS8pfVF4LblLq9tlxVXo=" - }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "dev": true }, - "as-number": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/as-number/-/as-number-1.0.0.tgz", - "integrity": "sha1-rLJ+NPj52KsNqeN287iVmGD4CmY=" - }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -930,35 +902,6 @@ } } }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - } - } - }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -1159,14 +1102,6 @@ "shallow-clone": "^3.0.0" } }, - "clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "requires": { - "mimic-response": "^1.0.0" - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1537,14 +1472,6 @@ } } }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "requires": { - "mimic-response": "^1.0.0" - } - }, "deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -1559,11 +1486,6 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1669,16 +1591,6 @@ "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", "dev": true }, - "dtype": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dtype/-/dtype-2.0.0.tgz", - "integrity": "sha1-zQUjI84GFETs0uj1dI9popvihDQ=" - }, - "duplexer3": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", - "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1739,14 +1651,6 @@ } } }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, "enhanced-resolve": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", @@ -2060,14 +1964,6 @@ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true }, - "flatten-vertex-data": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten-vertex-data/-/flatten-vertex-data-1.0.2.tgz", - "integrity": "sha512-BvCBFK2NZqerFTdMDgqfHBwxYWnxeCkwONsw6PvBMcUXqo8U/KDWwmXhqx1x2kLIg7DqIsJfOaJFOmlua3Lxuw==", - "requires": { - "dtype": "^2.0.0" - } - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -2201,14 +2097,6 @@ "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", "dev": true }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -2282,24 +2170,6 @@ } } }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", @@ -2466,7 +2336,8 @@ "http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true }, "http-errors": { "version": "2.0.0", @@ -2837,11 +2708,6 @@ "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" - }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -2916,14 +2782,6 @@ "verror": "1.10.0" } }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "requires": { - "json-buffer": "3.0.0" - } - }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -2940,16 +2798,6 @@ "resolved": "https://registry.npmjs.org/layerr/-/layerr-2.0.1.tgz", "integrity": "sha512-z0730CwG/JO24evdORnyDkwG1Q7b7mF2Tp1qRQ0YvrMMARbt1DFG694SOv439Gm7hYKolyZyaB49YIrYIfZBdg==" }, - "layout-bmfont-text": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/layout-bmfont-text/-/layout-bmfont-text-1.3.4.tgz", - "integrity": "sha1-8g8sVGR3T0jabOipl/vObUaUW4E=", - "requires": { - "as-number": "^1.0.0", - "word-wrapper": "^1.0.7", - "xtend": "^4.0.0" - } - }, "license-checker": { "version": "25.0.1", "resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz", @@ -3141,11 +2989,6 @@ } } }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3190,24 +3033,6 @@ "ssri": "^8.0.0" } }, - "map-limit": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz", - "integrity": "sha1-63lhAxwPDo0AG/LVb6toXViCLzg=", - "requires": { - "once": "~1.3.0" - }, - "dependencies": { - "once": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", - "requires": { - "wrappy": "1" - } - } - } - }, "map-obj": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", @@ -3355,11 +3180,6 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -3644,22 +3464,6 @@ "resolved": "https://registry.npmjs.org/nested-property/-/nested-property-4.0.0.tgz", "integrity": "sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==" }, - "new-array": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/new-array/-/new-array-1.0.0.tgz", - "integrity": "sha1-XbxjnZYerH8an7wacUbsEvKST78=" - }, - "nice-color-palettes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/nice-color-palettes/-/nice-color-palettes-3.0.0.tgz", - "integrity": "sha512-lL4AjabAAFi313tjrtmgm/bxCRzp4l3vCshojfV/ij3IPdtnRqv6Chcw+SqJUhbe7g3o3BecaqCJYUNLswGBhQ==", - "requires": { - "got": "^9.2.2", - "map-limit": "0.0.1", - "minimist": "^1.2.0", - "new-array": "^1.0.0" - } - }, "no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -3839,11 +3643,6 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" - }, "npm-normalize-package-bin": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", @@ -3918,7 +3717,8 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true }, "object-inspect": { "version": "1.12.3", @@ -3942,6 +3742,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -3977,11 +3778,6 @@ "os-tmpdir": "^1.0.0" } }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4510,11 +4306,6 @@ "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", "dev": true }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" - }, "pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -4572,15 +4363,6 @@ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -4594,16 +4376,6 @@ "side-channel": "^1.0.4" } }, - "quad-indices": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/quad-indices/-/quad-indices-2.0.1.tgz", - "integrity": "sha1-ppQdiaE9Y+7WwdSlpiGgRjYXqBQ=", - "requires": { - "an-array": "^1.0.0", - "dtype": "^2.0.0", - "is-buffer": "^1.0.2" - } - }, "querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -4934,14 +4706,6 @@ "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "requires": { - "lowercase-keys": "^1.0.0" - } - }, "retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -5673,27 +5437,6 @@ "resolved": "https://registry.npmjs.org/three/-/three-0.158.0.tgz", "integrity": "sha512-TALj4EOpdDPF1henk2Q+s17K61uEAAWQ7TJB68nr7FKxqwyDr3msOt5IWdbGm4TaWKjrtWS8DJJWe9JnvsWOhQ==" }, - "three-bmfont-text": { - "version": "git+ssh://git@github.com/Smithsonian/three-bmfont-text.git#50da80d887873140dd7f20b28675e68de0b2b1da", - "from": "three-bmfont-text@git+https://github.com/Smithsonian/three-bmfont-text.git#50da80d887", - "requires": { - "array-shuffle": "^1.0.1", - "inherits": "^2.0.1", - "layout-bmfont-text": "^1.2.0", - "nice-color-palettes": "^3.0.0", - "object-assign": "^4.0.1", - "quad-indices": "^2.0.1", - "three-buffer-vertex-data": "^1.0.0" - } - }, - "three-buffer-vertex-data": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/three-buffer-vertex-data/-/three-buffer-vertex-data-1.1.0.tgz", - "integrity": "sha1-zyKOeEJ2ZYhLlhpMq+H4XtOfgrE=", - "requires": { - "flatten-vertex-data": "^1.0.0" - } - }, "timsort": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", @@ -5705,11 +5448,6 @@ "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.3.1.tgz", "integrity": "sha512-+oCwXuTxAdJXVJ0130OxQz0JDNsqg3deuzgeUo8X5Vb27EzCJgXwO5eWvCxvkxpQo4oiHMVlM4tUIpTUHufHGQ==" }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5977,14 +5715,6 @@ "requires-port": "^1.0.0" } }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "requires": { - "prepend-http": "^2.0.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6351,11 +6081,6 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, - "word-wrapper": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/word-wrapper/-/word-wrapper-1.0.7.tgz", - "integrity": "sha1-HxSv6/Zt/fD+9V79NxhO+9CMKLY=" - }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -6381,7 +6106,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "xml-js": { "version": "1.6.11", @@ -6399,11 +6125,6 @@ "xml-js": "^1.6.2" } }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/source/client/annotations/AnnotationSprite.ts b/source/client/annotations/AnnotationSprite.ts index 8604c952..c58aa0a3 100755 --- a/source/client/annotations/AnnotationSprite.ts +++ b/source/client/annotations/AnnotationSprite.ts @@ -22,6 +22,7 @@ import HTMLSprite, { SpriteElement, html } from "@ff/three/HTMLSprite"; import Annotation from "../models/Annotation"; import CVAssetReader from "client/components/CVAssetReader"; +import AnnotationOverlay from "client/ui/explorer/AnnotationOverlay"; //////////////////////////////////////////////////////////////////////////////// @@ -66,6 +67,7 @@ export default class AnnotationSprite extends HTMLSprite static readonly typeName: string = "Annotation"; isAdaptive = true; + isAnimating = false; assetManager = null; audioManager = null; @@ -140,6 +142,25 @@ export default class AnnotationSprite extends HTMLSprite export class AnnotationElement extends SpriteElement { protected sprite: AnnotationSprite; + protected isTruncated: boolean = false; + protected isOverlayed: boolean = false; + + get truncated() + { + return this.isTruncated + } + set truncated(value: boolean) + { + this.isTruncated = value; + } + get overlayed() + { + return this.isOverlayed + } + set overlayed(value: boolean) + { + this.isOverlayed = value; + } constructor(sprite: AnnotationSprite) { @@ -166,4 +187,15 @@ export class AnnotationElement extends SpriteElement { event.stopPropagation(); } + + showOverlay(content: HTMLElement) + { + this.requestUpdate().then(() => { + AnnotationOverlay.show(this.parentElement, content, this.sprite).then(() => { + this.overlayed = false; + this.append(content); // attach content back to original container + this.requestUpdate(); + }); + }); + } } \ No newline at end of file diff --git a/source/client/annotations/CircleSprite.ts b/source/client/annotations/CircleSprite.ts index 1626ff13..52a4a527 100755 --- a/source/client/annotations/CircleSprite.ts +++ b/source/client/annotations/CircleSprite.ts @@ -43,6 +43,9 @@ export default class CircleSprite extends AnnotationSprite protected offset: Group; protected anchorMesh: Mesh; + protected adaptive = true; + protected originalHeight; + protected originalWidth; constructor(annotation: Annotation) { @@ -112,6 +115,16 @@ export default class CircleSprite extends AnnotationSprite this.offset.visible = isShowing; + // update adaptive settings + if(this.adaptive !== this.isAdaptive) { + if(!this.isAdaptive) { + element.truncated = false; + element.classList.remove("sv-short"); + element.requestUpdate(); + } + this.adaptive = this.isAdaptive; + } + // don't show if behind the camera this.setVisible(!this.isBehindCamera(this.offset, camera) && isShowing); if(!this.getVisible()) { @@ -119,28 +132,23 @@ export default class CircleSprite extends AnnotationSprite } // check if annotation is out of bounds and update if needed - if (annotation.expanded) { - element.classList.add("sv-expanded"); - - let x = element.getBoundingClientRect().left - container.getBoundingClientRect().left; - let y = element.getBoundingClientRect().top - container.getBoundingClientRect().top; - - if (x + element.offsetWidth >= container.offsetWidth && !element.classList.contains("sv-align-right")) { - element.classList.add("sv-align-right"); - element.requestUpdate(); - } - else if (x + element.offsetWidth < container.offsetWidth && element.classList.contains("sv-align-right")){ - element.classList.remove("sv-align-right"); - element.requestUpdate(); - } - if (y + element.offsetHeight >= container.offsetHeight && !element.classList.contains("sv-align-bottom")) { - element.classList.add("sv-align-bottom"); - element.requestUpdate(); - } - else if (y + element.offsetHeight < container.offsetHeight && element.classList.contains("sv-align-bottom")) { - element.classList.remove("sv-align-bottom"); - element.requestUpdate(); + if (this.adaptive && !this.isAnimating && annotation.expanded) { + + if(!element.truncated) { + if(!element.classList.contains("sv-expanded")) { + element.requestUpdate().then(() => { + this.originalHeight = element.offsetHeight; + this.originalWidth = element.offsetWidth; + this.checkTruncate(element, container); + }); + } + else { + this.originalHeight = element.offsetHeight; + this.originalWidth = element.offsetWidth; + } } + + this.checkTruncate(element, container); } } @@ -148,6 +156,46 @@ export default class CircleSprite extends AnnotationSprite { return new CircleAnnotation(this); } + + // Helper function to check if annotation should truncate + protected checkTruncate(element: AnnotationElement, container: HTMLElement) { + const x = element.getBoundingClientRect().left - container.getBoundingClientRect().left; + const y = element.getBoundingClientRect().top - container.getBoundingClientRect().top; + + const shouldTruncate = y + this.originalHeight >= container.offsetHeight; + if(shouldTruncate !== element.truncated) { + element.truncated = shouldTruncate; + element.requestUpdate().then(() => { + this.checkBounds(element, container); + }); + } + else { + this.checkBounds(element, container); + } + } + + // Helper function to check and handle annotation overlap with bounds of container + protected checkBounds(element: AnnotationElement, container: HTMLElement) { + const x = element.getBoundingClientRect().left - container.getBoundingClientRect().left; + const y = element.getBoundingClientRect().top - container.getBoundingClientRect().top; + + if (x + element.offsetWidth >= container.offsetWidth && !element.classList.contains("sv-align-right")) { + element.classList.add("sv-align-right"); + element.requestUpdate(); + } + else if (x + element.offsetWidth < container.offsetWidth && element.classList.contains("sv-align-right")){ + element.classList.remove("sv-align-right"); + element.requestUpdate(); + } + if (y + element.offsetHeight >= container.offsetHeight && !element.classList.contains("sv-align-bottom")) { + element.classList.add("sv-align-bottom"); + element.requestUpdate(); + } + else if (y + element.offsetHeight < container.offsetHeight && element.classList.contains("sv-align-bottom")) { + element.classList.remove("sv-align-bottom"); + element.requestUpdate(); + } + } } AnnotationFactory.registerType(CircleSprite); @@ -169,6 +217,7 @@ class CircleAnnotation extends AnnotationElement this.onClickArticle = this.onClickArticle.bind(this); this.onClickAudio = this.onClickAudio.bind(this); this.onKeyDown = this.onKeyDown.bind(this); + this.onClickOverlay = this.onClickOverlay.bind(this); this.markerElement = this.appendElement("div"); this.markerElement.classList.add("sv-marker"); @@ -197,17 +246,20 @@ class CircleAnnotation extends AnnotationElement const annotation = this.sprite.annotation; const annotationData = annotation.data; + const isTruncated = !this.overlayed && this.truncated + && (annotationData.imageUri || annotationData.articleId || annotation.lead.length > 0); // make sure we have content to truncate const audio = this.sprite.audioManager; // update title this.markerElement.innerText = annotationData.marker; - + const contentTemplate = html` -
${annotation.title}
- ${annotationData.imageUri ? html`
${annotation.imageAltText}${annotation.imageCredit ? html`
${annotation.imageCredit}
` : null}
` : null} -

${unsafeHTML(annotation.lead)}

- ${annotationData.audioId ? html`
` : null} - ${annotationData.articleId ? html`` : null}`; + ${!this.isOverlayed ? html`
${annotation.title}
` : null} + ${annotationData.imageUri && !isTruncated ? html`
${annotation.imageAltText}${annotation.imageCredit ? html`
${annotation.imageCredit}
` : null}
` : null} + ${!isTruncated ? html`

${unsafeHTML(annotation.lead)}

` : null} + ${annotationData.audioId && !this.isOverlayed ? html`
` : null} + ${annotationData.articleId && !isTruncated ? html`` : null} + ${isTruncated ? html`` : null}`; render(contentTemplate, this.contentElement); @@ -221,7 +273,7 @@ class CircleAnnotation extends AnnotationElement } // update expanded/collapsed - if (this.isExpanded !== annotationData.expanded) { + if (this.isExpanded !== annotationData.expanded && !this.overlayed) { this.isExpanded = annotationData.expanded; @@ -233,7 +285,7 @@ class CircleAnnotation extends AnnotationElement this.classList.add("sv-expanded"); //this.style.minWidth = annotationData.lead.length < 40 && (!annotationData.audioId || annotationData.audioId.length == 0) ? "0" : ""; this.contentElement.style.display = "block"; - this.contentElement.style.height = this.contentElement.scrollHeight + "px"; + this.contentElement.style.height = "auto"; //this.contentElement.scrollHeight + "px"; } else { this.classList.remove("sv-expanded"); @@ -257,7 +309,7 @@ class CircleAnnotation extends AnnotationElement } const audioView = this.querySelector(".sv-audio-view"); - if(annotationData.audioId) { + if(annotationData.audioId && !this.overlayed) { if(annotationData.expanded && !audioView) { const audioContainer = this.querySelector("#audio_container"); audioContainer.append(audio.getPlayerById(annotationData.audioId)); @@ -281,10 +333,17 @@ class CircleAnnotation extends AnnotationElement this.sprite.emitLinkEvent(this.sprite.annotation.data.articleId); } + protected onClickOverlay(event: MouseEvent) + { + event.stopPropagation(); + const content = this.contentElement; + this.overlayed = true; + this.showOverlay(content); + } + protected onClickAudio(event: MouseEvent) { event.stopPropagation(); - this.sprite.emitClickEvent(); } protected onKeyDown(event: KeyboardEvent) diff --git a/source/client/annotations/ExtendedSprite.ts b/source/client/annotations/ExtendedSprite.ts index d474e978..b1118a67 100755 --- a/source/client/annotations/ExtendedSprite.ts +++ b/source/client/annotations/ExtendedSprite.ts @@ -26,6 +26,7 @@ import "@ff/ui/Button"; import AnnotationSprite, { Annotation, AnnotationElement } from "./AnnotationSprite"; import AnnotationFactory from "./AnnotationFactory"; +import { EQuadrant } from "client/../../libs/ff-three/source/HTMLSprite"; //////////////////////////////////////////////////////////////////////////////// @@ -40,6 +41,8 @@ export default class ExtendedSprite extends AnnotationSprite protected stemLine: Line; protected quadrant = -1; protected adaptive = true; + protected originalHeight; + protected originalWidth; constructor(annotation: Annotation) { @@ -92,25 +95,73 @@ export default class ExtendedSprite extends AnnotationSprite this.quadrant = this.orientationQuadrant; } - // update adaptive width + // update adaptive settings if(this.adaptive !== this.isAdaptive) { if(this.isAdaptive) { element.classList.remove("sv-static-width"); } else { element.classList.add("sv-static-width"); + element.truncated = false; + element.classList.remove("sv-short"); + element.requestUpdate(); } this.adaptive = this.isAdaptive; } // don't show if behind the camera this.setVisible(!this.isBehindCamera(this.stemLine, camera)); + + // check if annotation is out of bounds and update if needed + if (this.adaptive && !this.isAnimating && this.annotation.data.expanded) { + + if(!element.truncated) { + if(!element.classList.contains("sv-expanded")) { + element.requestUpdate().then(() => { + this.originalHeight = element.offsetHeight; + this.originalWidth = element.offsetWidth; + this.checkTruncate(element, container); + }); + return; + } + else { + this.originalHeight = element.offsetHeight; + this.originalWidth = element.offsetWidth; + } + } + + this.checkTruncate(element, container); + } } protected createHTMLElement(): ExtendedAnnotation { return new ExtendedAnnotation(this); } + + // Helper function to check if annotation should truncate + protected checkTruncate(element: AnnotationElement, container: HTMLElement) { + const top = this.quadrant == EQuadrant.TopLeft || this.quadrant == EQuadrant.TopRight; + const right = this.quadrant == EQuadrant.TopRight || this.quadrant == EQuadrant.BottomRight; + const x = right ? element.getBoundingClientRect().left - container.getBoundingClientRect().left + : element.getBoundingClientRect().right - container.getBoundingClientRect().left; + const y = top ? element.getBoundingClientRect().bottom - container.getBoundingClientRect().top + : element.getBoundingClientRect().top - container.getBoundingClientRect().top; + + const shouldTruncateVert = !top ? y + this.originalHeight >= container.offsetHeight : y - this.originalHeight <= 0; + const shouldTruncateHoriz = right ? x + this.originalWidth >= container.offsetWidth : x - this.originalWidth <= 0; + const shouldTruncate = shouldTruncateVert || shouldTruncateHoriz; + if(shouldTruncate !== element.truncated) { + element.truncated = shouldTruncate; + shouldTruncate ? element.classList.add("sv-short") : element.classList.remove("sv-short"); + element.requestUpdate().then(() => { + //this.checkBounds(element, container); + }); + } + else { + //this.checkBounds(element, container); + } + } } AnnotationFactory.registerType(ExtendedSprite); @@ -123,7 +174,7 @@ class ExtendedAnnotation extends AnnotationElement protected titleElement: HTMLDivElement; protected contentElement: HTMLDivElement; protected wrapperElement: HTMLDivElement; - protected handler = 0; + //protected handler = 0; protected isExpanded = undefined; constructor(sprite: AnnotationSprite) @@ -134,6 +185,7 @@ class ExtendedAnnotation extends AnnotationElement this.onClickArticle = this.onClickArticle.bind(this); this.onClickAudio = this.onClickAudio.bind(this); this.onKeyDown = this.onKeyDown.bind(this); + this.onClickOverlay = this.onClickOverlay.bind(this); this.titleElement = this.appendElement("div"); this.titleElement.classList.add("sv-title"); @@ -161,15 +213,18 @@ class ExtendedAnnotation extends AnnotationElement const annotationObj = this.sprite.annotation; const annotation = this.sprite.annotation.data; const audio = this.sprite.audioManager; + const isTruncated = !this.overlayed && this.truncated + && (annotation.imageUri || annotation.articleId || annotationObj.lead.length > 0); // make sure we have content to truncate; // update title this.titleElement.innerText = this.sprite.annotation.title; const contentTemplate = html` - ${annotation.imageUri ? html`
${annotationObj.imageAltText}${annotationObj.imageCredit ? html`
${annotationObj.imageCredit}
` : null}
` : null} -

${unsafeHTML(annotationObj.lead)}

- ${annotation.audioId ? html`
` : null} - ${annotation.articleId ? html`` : null}`; + ${annotation.imageUri && !isTruncated ? html`
${annotationObj.imageAltText}${annotationObj.imageCredit ? html`
${annotationObj.imageCredit}
` : null}
` : null} + ${!isTruncated ? html`

${unsafeHTML(annotationObj.lead)}

` : null} + ${annotation.audioId && !this.overlayed ? html`
` : null} + ${annotation.articleId && !isTruncated ? html`` : null} + ${isTruncated ? html`` : null}`; render(contentTemplate, this.contentElement); @@ -183,10 +238,10 @@ class ExtendedAnnotation extends AnnotationElement } // update expanded/collapsed - if (this.isExpanded !== annotation.expanded) { + if (this.isExpanded !== annotation.expanded && !this.overlayed) { this.isExpanded = annotation.expanded; - window.clearTimeout(this.handler); + //window.clearTimeout(this.handler); if (this.isExpanded) { if(annotation.audioId) { @@ -195,19 +250,31 @@ class ExtendedAnnotation extends AnnotationElement this.classList.add("sv-expanded"); this.style.minWidth = this.sprite.annotation.lead.length < 40 && (!annotation.audioId || annotation.audioId.length == 0) ? "0" : ""; - this.contentElement.style.display = "inherit"; - this.contentElement.style.height = this.contentElement.scrollHeight + "px"; + this.contentElement.style.display = "block"; + this.contentElement.style.height = "auto"; //this.contentElement.scrollHeight + "px"; } else { this.classList.remove("sv-expanded"); this.contentElement.style.height = "0"; - this.handler = window.setTimeout(() => this.contentElement.style.display = "none", 300); + //this.handler = window.setTimeout(() => this.contentElement.style.display = "none", 300); + this.contentElement.style.display = "none"; if(audio.activeId == annotation.audioId) { this.sprite.audioManager.stop(); } } } + + const audioView = this.querySelector(".sv-audio-view"); + if(annotation.audioId && !this.overlayed) { + if(annotation.expanded && !audioView) { + const audioContainer = this.querySelector("#audio_container"); + audioContainer.append(audio.getPlayerById(annotation.audioId)); + } + else if(!annotation.expanded && audioView && audio.activeId == annotation.audioId) { + audio.stop(); + } + } } protected onClickTitle(event: MouseEvent) @@ -225,7 +292,14 @@ class ExtendedAnnotation extends AnnotationElement protected onClickAudio(event: MouseEvent) { event.stopPropagation(); - this.sprite.emitClickEvent(); + } + + protected onClickOverlay(event: MouseEvent) + { + event.stopPropagation(); + const content = this.contentElement; + this.overlayed = true; + this.showOverlay(content); } protected onKeyDown(event: KeyboardEvent) diff --git a/source/client/components/CVAnnotationView.ts b/source/client/components/CVAnnotationView.ts index d79f1740..1fc550a2 100755 --- a/source/client/components/CVAnnotationView.ts +++ b/source/client/components/CVAnnotationView.ts @@ -46,6 +46,7 @@ import CVAssetReader from "./CVAssetReader"; import CVAudioManager from "./CVAudioManager"; import CVAssetManager from "./CVAssetManager"; import CVSnapshots from "./CVSnapshots"; +import CPulse from "client/../../libs/ff-graph/source/components/CPulse"; //////////////////////////////////////////////////////////////////////////////// @@ -92,6 +93,9 @@ export default class CVAnnotationView extends CObject3D private _viewports = new Set(); private _sprites: Dictionary = {}; + private _truncateLock = false; + private _activeView = false; + protected get model() { return this.getComponent(CVModel2); } @@ -144,6 +148,12 @@ export default class CVAnnotationView extends CObject3D if (annotation) { annotation.set("expanded", true); this.updateSprite(annotation); + + // need to lock truncation checking during a tween + if(this._activeView) { + this._truncateLock = true; + this._activeView = false; + } } const ins = this.ins; @@ -315,6 +325,16 @@ export default class CVAnnotationView extends CObject3D const spriteGroup = this.object3D as HTMLSpriteGroup; spriteGroup.render(viewport.overlay, context.camera); + + // Handle locking truncation for view animation only after + // the sprite has a chance to do an initial update. + if(this._truncateLock) { + const annotation = this.activeAnnotation.data; + const sprite = this._sprites[annotation.id] as AnnotationSprite; + sprite.isAnimating = true; + this.snapshots.outs.tweening.once("value", () => { sprite.isAnimating = false; }, this); + this._truncateLock = false; + } } dispose() @@ -473,11 +493,21 @@ export default class CVAnnotationView extends CObject3D { this.emit(event); + // start view animation if it exists const annotation = event.annotation; - if(annotation && annotation.data.viewId.length) { + if(annotation && annotation.data.viewId.length && !this.arManager.outs.isPresenting.value) { this.normalizeViewOrbit(annotation.data.viewId); - this.snapshots.ins.id.setValue(annotation.data.viewId); - this.snapshots.ins.tween.set(); + + // If activeAnnotation is being tracked, make sure it is set + const activeIdx = this.snapshots.getTargetProperties().findIndex(prop => prop.name == "ActiveId"); + if(activeIdx >= 0) { + const viewState = this.snapshots.getState(annotation.data.viewId); + viewState.values[activeIdx] = annotation.data.id; + } + + const pulse = this.getMainComponent(CPulse); + this.snapshots.tweenTo(annotation.data.viewId, pulse.context.secondsElapsed); + this._activeView = true; } } @@ -574,9 +604,12 @@ export default class CVAnnotationView extends CObject3D const orbitIdx = this.snapshots.getTargetProperties().findIndex(prop => prop.name == "Orbit"); const viewState = this.snapshots.getState(viewId); const currentOrbit = this.snapshots.getCurrentValues()[orbitIdx]; + let angleOffset = 0; currentOrbit.forEach((n, i) => { const mult = Math.round((n-viewState.values[orbitIdx][i])/360); - viewState.values[orbitIdx][i] += 360*mult; + viewState.values[orbitIdx][i] += 360*mult; + angleOffset += Math.abs(n-viewState.values[orbitIdx][i]); }); + viewState.duration = angleOffset > 0.01 ? 1.0 : 0; // don't animate if we are already there } } \ No newline at end of file diff --git a/source/client/components/CVCamera.ts b/source/client/components/CVCamera.ts index 5d69be4a..0b43812e 100644 --- a/source/client/components/CVCamera.ts +++ b/source/client/components/CVCamera.ts @@ -16,7 +16,7 @@ */ import CCamera, { EProjection } from "@ff/scene/components/CCamera"; -import { Node } from "@ff/scene/components/CObject3D"; +import { Node, types } from "@ff/scene/components/CObject3D"; import { IDocument, INode, ICamera } from "client/schema/document"; @@ -29,6 +29,12 @@ export default class CVCamera extends CCamera static readonly text: string = "Camera"; static readonly icon: string = "video"; + protected static readonly cameraAddIns = { + autoNearFar: types.Boolean("Frustum.AutoNearFar", true), + }; + + addIns = this.addInputs(CVCamera.cameraAddIns); + get settingProperties() { return [ this.ins.projection, @@ -36,6 +42,7 @@ export default class CVCamera extends CCamera this.ins.size, this.ins.near, this.ins.far, + this.addIns.autoNearFar ] } @@ -53,6 +60,10 @@ export default class CVCamera extends CCamera const data = document.cameras[node.camera]; + if(data.autoNearFar != undefined) { + this.addIns.autoNearFar.setValue(data.autoNearFar); + } + if (data.type === "perspective") { this.ins.copyValues({ projection: EProjection.Perspective, @@ -96,6 +107,8 @@ export default class CVCamera extends CCamera } } + data.autoNearFar = this.addIns.autoNearFar.value; + document.cameras = document.cameras || []; const cameraIndex = document.cameras.length; document.cameras.push(data); diff --git a/source/client/components/CVDocument.ts b/source/client/components/CVDocument.ts index fa8f151c..ed1954c8 100755 --- a/source/client/components/CVDocument.ts +++ b/source/client/components/CVDocument.ts @@ -57,6 +57,7 @@ export default class CVDocument extends CRenderGraph protected static readonly validator = new DocumentValidator(); protected titles: Dictionary = {}; + protected intros: Dictionary = {}; protected meta: CVMeta = null; protected static readonly ins = { @@ -64,11 +65,13 @@ export default class CVDocument extends CRenderGraph dumpTree: types.Event("Document.DumpTree"), download: types.Event("Document.Download"), title: types.String("Document.Title"), + intro: types.String("Document.Intro", ""), }; protected static readonly outs = { assetPath: types.AssetPath("Asset.Path", { preset: "scene.svx.json" }), title: types.String("Document.Title"), + intro: types.String("Document.Intro", ""), }; ins = this.addInputs(CVDocument.ins); @@ -112,12 +115,12 @@ export default class CVDocument extends CRenderGraph { super.create(); this.innerGraph.components.on(CVMeta, this.onMetaComponent, this); - this.setup.language.outs.language.on("value", this.updateTitle, this); + this.setup.language.outs.language.on("value", this.onLanguageUpdate, this); } dispose() { - this.setup.language.outs.language.off("value", this.updateTitle, this); + this.setup.language.outs.language.off("value", this.onLanguageUpdate, this); this.innerGraph.components.off(CVMeta, this.onMetaComponent, this); super.dispose(); } @@ -151,10 +154,18 @@ export default class CVDocument extends CRenderGraph if(ins.title.value) { this.titles[ELanguageType[language.outs.language.value]] = ins.title.value; - this.updateTitlesMeta(); + this.updateMeta(); } } + if(ins.intro.changed && this.intros) { + const language = this.setup.language; + outs.intro.setValue(ins.intro.value); + + this.intros[ELanguageType[language.outs.language.value]] = ins.intro.value; + this.updateMeta(); + } + return true; } @@ -284,6 +295,7 @@ export default class CVDocument extends CRenderGraph { const meta = event.object; const propTitle = this.ins.title; + const propIntro = this.ins.intro; const language = this.setup.language; if(this.meta === null) { @@ -293,6 +305,7 @@ export default class CVDocument extends CRenderGraph if (event.add && !propTitle.value) { meta.once("load", () => { this.titles = meta.collection.get("titles") || {}; + this.intros = meta.collection.get("intros") || {}; // TODO: Temporary - remove when single string properties are phased out if(Object.keys(this.titles).length === 0) { @@ -302,23 +315,28 @@ export default class CVDocument extends CRenderGraph const title = this.titles[ELanguageType[language.outs.language.value]]; propTitle.setValue(title); + const intro = this.intros[ELanguageType[language.outs.language.value]] || ""; + propIntro.setValue(intro); this.analytics.setTitle(title); this.meta = meta; }); } } - protected updateTitle() { + protected onLanguageUpdate() { const language = this.setup.language; const newTitle = this.titles[ELanguageType[language.outs.language.value]]; this.ins.title.setValue(newTitle); + const newIntro = this.intros[ELanguageType[language.outs.language.value]]; + this.ins.intro.setValue(newIntro); } - protected updateTitlesMeta() { + protected updateMeta() { const meta = this.meta; if(meta) { - meta.collection.dictionary["titles"] = this.titles; + meta.collection.dictionary["titles"] = this.titles; + meta.collection.dictionary["intros"] = this.intros; } } } \ No newline at end of file diff --git a/source/client/components/CVGrid.ts b/source/client/components/CVGrid.ts index 1ef8ba02..c4a8ce49 100644 --- a/source/client/components/CVGrid.ts +++ b/source/client/components/CVGrid.ts @@ -99,6 +99,7 @@ export default class CVGrid extends CObject3D this.tape.ins.startPosition.setValue([0,0,0]); this.tape.ins.endPosition.setValue([0,0,0]); this.tape.ins.visible.setValue(false); + this.tape.addTag("no_settings"); // hack to exclude from scene settings super.create(); } diff --git a/source/client/components/CVModel2.ts b/source/client/components/CVModel2.ts index 8fecd140..bdb0b4d5 100644 --- a/source/client/components/CVModel2.ts +++ b/source/client/components/CVModel2.ts @@ -700,7 +700,8 @@ export default class CVModel2 extends CObject3D return derivative.load(this.assetReader) .then(() => { - if (!derivative.model || !this.node) { + if (!derivative.model || !this.node || + (this._activeDerivative && derivative.data.quality != this.ins.quality.value)) { derivative.unload(); return; } diff --git a/source/client/components/CVScene.ts b/source/client/components/CVScene.ts index 92eac59c..29e6dd9c 100644 --- a/source/client/components/CVScene.ts +++ b/source/client/components/CVScene.ts @@ -266,11 +266,13 @@ export default class CVScene extends CVNode } this.cameras.forEach(camera => { - const far = 4 * Math.max(orbitRadius, this.outs.boundingRadius.value); - const near = Math.min(far / 1000.0, this.outs.boundingRadius.value / 100.0); - if(far < camera.ins.far.value || camera.ins.far.value < 2*this.setup.navigation.ins.maxOffset.value[2]) { - camera.ins.far.setValue(far); - camera.ins.near.setValue(near); + if(camera.addIns.autoNearFar.value) { + const far = 4 * Math.max(orbitRadius, this.outs.boundingRadius.value); + const near = Math.min(far / 1000.0, this.outs.boundingRadius.value / 100.0); + if(far < camera.ins.far.value || camera.ins.far.value < 2*this.setup.navigation.ins.maxOffset.value[2]) { + camera.ins.far.setValue(far); + camera.ins.near.setValue(near); + } } }); } diff --git a/source/client/io/ModelReader.ts b/source/client/io/ModelReader.ts index fcf02bcc..a0085240 100644 --- a/source/client/io/ModelReader.ts +++ b/source/client/io/ModelReader.ts @@ -118,6 +118,12 @@ export default class ModelReader const uberMat = material.type === "MeshPhysicalMaterial" ? new UberPBRAdvMaterial() : new UberPBRMaterial(); + if (material.flatShading) { + mesh.geometry.computeVertexNormals(); + material.flatShading = false; + console.warn("Normals unavailable so they have been calculated. For best outcomes, please provide normals with geometry."); + } + // copy properties from previous material if (material.type === "MeshPhysicalMaterial" || material.type === "MeshStandardMaterial") { uberMat.copy(material); diff --git a/source/client/models/Annotation.ts b/source/client/models/Annotation.ts index ec7c5b9a..7200869c 100755 --- a/source/client/models/Annotation.ts +++ b/source/client/models/Annotation.ts @@ -228,7 +228,7 @@ export default class Annotation extends Document } const color = data.color; - if (color && (color[0] !== 1 || color[1] !== 1 || color[2] !== 1)) { + if (color && (color[0] !== 0 || color[1] !== 0.61 || color[2] !== 0.87)) { json.color = color.slice(); } diff --git a/source/client/schema/document.ts b/source/client/schema/document.ts index 2220bcf3..7b0d5e03 100644 --- a/source/client/schema/document.ts +++ b/source/client/schema/document.ts @@ -92,6 +92,7 @@ export interface ICamera type: TCameraType; perspective?: IPerspectiveCameraProps; orthographic?: IOrthographicCameraProps; + autoNearFar?: boolean; } /** diff --git a/source/client/schema/json/document.schema.json b/source/client/schema/json/document.schema.json index 238b6aaf..a5604026 100644 --- a/source/client/schema/json/document.schema.json +++ b/source/client/schema/json/document.schema.json @@ -188,13 +188,17 @@ "znear", "zfar" ] + }, + "autoNearFar": { + "type": "boolean", + "default": true } }, "required": [ "type" ], "not": { - "required": [ "perspective", "orthographic" ] + "required": [ "perspective", "orthographic", "autoNearFar" ] } }, diff --git a/source/client/schema/json/meta.schema.json b/source/client/schema/json/meta.schema.json index 9181d1ba..38de3289 100644 --- a/source/client/schema/json/meta.schema.json +++ b/source/client/schema/json/meta.schema.json @@ -78,6 +78,10 @@ "description": "Array of tags, categorizing the annotation with language key.", "type": "object" }, + "intros": { + "description": "Introductory splash screen text with language key.", + "type": "object" + }, "uri": { "description": "Location of the article resource, absolute URL or path relative to this document", "type": "string", diff --git a/source/client/schema/meta.ts b/source/client/schema/meta.ts index fc346b0e..56183c14 100644 --- a/source/client/schema/meta.ts +++ b/source/client/schema/meta.ts @@ -61,6 +61,7 @@ export interface IArticle leads?: Dictionary; tags?: string[]; taglist?: Dictionary; + intros?: Dictionary; mimeType?: string; thumbnailUri?: string; diff --git a/source/client/ui/SceneView.ts b/source/client/ui/SceneView.ts index 3b355e38..d58d4ced 100644 --- a/source/client/ui/SceneView.ts +++ b/source/client/ui/SceneView.ts @@ -86,6 +86,7 @@ export default class SceneView extends SystemView this.setAttribute("touch-action", "none"); this.tabIndex = 0; + this.id = "sv-scene" this.ariaLabel = "Interactive 3D Model. Use mouse, touch, or arrow keys to rotate."; this.setAttribute("role", "application"), diff --git a/source/client/ui/explorer/AnnotationOverlay.ts b/source/client/ui/explorer/AnnotationOverlay.ts new file mode 100644 index 00000000..7a05e200 --- /dev/null +++ b/source/client/ui/explorer/AnnotationOverlay.ts @@ -0,0 +1,136 @@ +/** + * 3D Foundation Project + * Copyright 2024 Smithsonian Institution + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Popup, { customElement, html } from "@ff/ui/Popup"; + +import "@ff/ui/Button"; +import "@ff/ui/TextEdit"; +import {getFocusableElements, focusTrap} from "../../utils/focusHelpers"; +import AnnotationSprite from "client/annotations/AnnotationSprite"; + +//////////////////////////////////////////////////////////////////////////////// + +@customElement("sv-annotation-overlay") +export default class AnnotationOverlay extends Popup +{ + protected content: HTMLElement = null; + protected sprite: AnnotationSprite = null; + protected resizeObserver: ResizeObserver = null; + + static show(parent: HTMLElement, content: HTMLElement, sprite: AnnotationSprite): Promise + { + const popup = new AnnotationOverlay(content, sprite); + parent.appendChild(popup); + + return new Promise((resolve, reject) => { + popup.on("close", () => resolve()); + }); + } + + constructor( content: HTMLElement, sprite: AnnotationSprite ) + { + super(); + + this.close = this.close.bind(this); + + this.content = content; + this.title = sprite.annotation.title; + this.sprite = sprite; + this.position = "center"; + this.modal = true; + } + + close() + { + this.dispatchEvent(new CustomEvent("close")); + this.remove(); + } + + protected firstConnected() + { + super.firstConnected(); + this.classList.add("sv-annotation-overlay", "sv-annotation"); + } + + protected connected() + { + super.connected(); + + this.sprite.addEventListener("link", this.close); + + if(!this.resizeObserver) { + this.resizeObserver = new ResizeObserver(() => this.onResize()); + } + this.resizeObserver.observe(this); + } + + protected disconnected() + { + this.resizeObserver.disconnect(); + + this.sprite.removeEventListener("link", this.close); + + super.disconnected(); + } + + protected render() + { + return html` +
this.discardEvents(e)} @pointerdown=${(e) => this.discardEvents(e)} aria-label="Annotation pop-up" @keydown=${e =>this.onKeyDownMain(e)}> +
+
${this.title}
+ +
+
+ `; + } + + protected firstUpdated(changedProperties) { + super.firstUpdated(changedProperties); + + const annoContainer = this.querySelector("#anno_container"); + annoContainer.append(this.content); + + //(Array.from(this.getElementsByClassName("sv-entry")).find(elem => elem.getAttribute("tabIndex") === "0") as HTMLElement).focus(); + } + + protected onKeyDownMain(e: KeyboardEvent) + { + if (e.code === "Escape") { + this.close(); + } + else if(e.code === "Tab") { + focusTrap(getFocusableElements(this) as HTMLElement[], e); + } + } + + // resets tabIndex if needed + protected tabReset(e: FocusEvent) { + const currentActive = e.target instanceof Element ? e.target as Element : null; + if(currentActive) { + const currentSelected = Array.from(currentActive.parentElement.children).find(elem => elem.hasAttribute("selected")); + if(currentSelected !== currentActive) { + currentActive.setAttribute("tabIndex", "-1"); + currentSelected.setAttribute("tabIndex", "0"); + } + } + } + + protected discardEvents(event: PointerEvent | WheelEvent) { + event.stopPropagation(); + } +} diff --git a/source/client/ui/explorer/ChromeView.ts b/source/client/ui/explorer/ChromeView.ts index c9f040de..1effb067 100644 --- a/source/client/ui/explorer/ChromeView.ts +++ b/source/client/ui/explorer/ChromeView.ts @@ -36,6 +36,7 @@ import DocumentView, { customElement, html } from "./DocumentView"; import LanguageMenu from "./LanguageMenu"; import { EUIElements } from "client/components/CVInterface"; import CVAssetReader from "client/components/CVAssetReader"; +import SplashScreen from "./SplashScreen"; //////////////////////////////////////////////////////////////////////////////// @@ -45,6 +46,7 @@ export default class ChromeView extends DocumentView protected documentProps = new Subscriber("value", this.onUpdate, this); protected titleElement: HTMLDivElement; protected assetPath: string = ""; + protected needsSplash: boolean = true; protected get toolProvider() { return this.system.getMainComponent(CVToolProvider); @@ -127,6 +129,14 @@ export default class ChromeView extends DocumentView const showTourEndMsg = this.activeDocument.setup.tours.outs.ending.value; this.activeDocument.setup.tours.outs.ending.setValue(false); + const introText = this.activeDocument.outs.intro.value; + if(this.needsSplash && introText && introText.length > 0) { + this.needsSplash = false; + SplashScreen.show(this, this.activeDocument.setup.language, introText).then(() => { + (this.getRootNode() as ShadowRoot).getElementById("sv-scene").focus(); + }); + } + if (!interfaceVisible) { return html``; } @@ -173,6 +183,16 @@ export default class ChromeView extends DocumentView `; } + protected firstUpdated(_changedProperties: Map): void { + const introText = this.activeDocument.outs.intro.value; + if(this.needsSplash && introText.length > 0) { + this.needsSplash = false; + SplashScreen.show(this, this.activeDocument.setup.language, introText).then(() => { + //(this.querySelector("#main-help") as HTMLElement).focus(); + }); + } + } + protected onSelectTour(event: ITourMenuSelectEvent) { const tours = this.activeDocument.setup.tours; diff --git a/source/client/ui/explorer/SplashScreen.ts b/source/client/ui/explorer/SplashScreen.ts new file mode 100644 index 00000000..bbb89237 --- /dev/null +++ b/source/client/ui/explorer/SplashScreen.ts @@ -0,0 +1,105 @@ +/** + * 3D Foundation Project + * Copyright 2023 Smithsonian Institution + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Popup, { customElement, html } from "@ff/ui/Popup"; + +import "@ff/ui/Button"; +import CVLanguageManager from "client/components/CVLanguageManager"; +import {getFocusableElements, focusTrap} from "../../utils/focusHelpers"; + +//////////////////////////////////////////////////////////////////////////////// + +@customElement("sv-splash") +export default class SplashScreen extends Popup +{ + protected url: string; + protected language: CVLanguageManager = null; + protected content: string = ""; + protected contentElement: HTMLDivElement; + + + static show(parent: HTMLElement, language: CVLanguageManager, content: string): Promise + { + const screen = new SplashScreen(language, content); + parent.appendChild(screen); + + return new Promise((resolve, reject) => { + screen.on("close", () => resolve()); + }); + } + + constructor( language: CVLanguageManager, content: string ) + { + super(); + + this.language = language; + this.content = content; + this.position = "center"; + this.modal = true; + + this.url = window.location.href; + } + + close() + { + this.dispatchEvent(new CustomEvent("close")); + this.remove(); + } + + protected firstConnected() + { + super.firstConnected(); + this.contentElement = this.createElement("div", null); + this.classList.add("sv-splash"); + } + + protected render() + { + const language = this.language; + const contentElement = this.contentElement; + + contentElement.innerHTML = this.content; + + return html` +
this.onKeyDownMain(e)}> +
+
${language.getLocalizedString("Welcome to Voyager")}
+ +
+
+ ${contentElement} +
+
+ `; + } + + protected firstUpdated(changedProperties) { + super.firstUpdated(changedProperties); + + (this.querySelector("#main") as HTMLElement).focus(); + } + + protected onKeyDownMain(e: KeyboardEvent) + { + if (e.code === "Escape") { + this.close(); + } + else if(e.code === "Tab") { + focusTrap(getFocusableElements(this) as HTMLElement[], e); + } + } +} diff --git a/source/client/ui/explorer/styles.scss b/source/client/ui/explorer/styles.scss index fa7dd388..f1befa8f 100644 --- a/source/client/ui/explorer/styles.scss +++ b/source/client/ui/explorer/styles.scss @@ -360,6 +360,11 @@ $pad: $canvas-border-width + $main-menu-button-size + 8px; z-index: 2; } + &.sv-short { + width: unset; + min-width: min-content; + } + &.sv-q0 { transform: translate(0, -100%); border-bottom-style: solid; @@ -391,6 +396,7 @@ $pad: $canvas-border-width + $main-menu-button-size + 8px; .sv-title { padding: 1px 0 2px 0; font-weight: bold; + -webkit-tap-highlight-color: transparent; } .sv-content { @@ -436,6 +442,27 @@ $pad: $canvas-border-width + $main-menu-button-size + 8px; } } +.sv-annotation-overlay { + padding: 7px; + pointer-events: auto; + background-color: $menu-color-background; + max-width: #{"min(80%, 800px)"}; + min-width: 50%; + max-height: 90%; + overflow-y: auto; + cursor: auto; + + .sv-annotation-body { + .ff-button { + background: rgba(0, 0, 0, 0.01); // hack to get click events + + &:hover { + text-decoration: underline; + } + } + } +} + //////////////////////////////////////////////////////////////////////////////// // AR MODE @@ -1390,11 +1417,27 @@ $tour-entry-indent: 12px; max-width: 85%; } +.sv-splash { + padding: 7px; + pointer-events: auto; + background-color: $menu-color-background; + max-width: #{"min(80%, 800px)"}; + min-width: 50%; + max-height: 90%; + overflow-y: auto; + + & > :focus-visible { + outline: none; + box-shadow: 0 0; + } +} + .sv-audio-view { display: flex; align-items: center; flex: 1 1 auto; max-width: 100%; + min-width: 110px; height: 30px; background-color: $menu-color-text; border-radius: 15px; diff --git a/source/client/ui/story/CollectionPanel.ts b/source/client/ui/story/CollectionPanel.ts index 3f679b07..fee6854c 100644 --- a/source/client/ui/story/CollectionPanel.ts +++ b/source/client/ui/story/CollectionPanel.ts @@ -53,6 +53,8 @@ export default class CollectionPanel extends DocumentView
Title
+
Intro
+
`; } @@ -71,6 +73,13 @@ export default class CollectionPanel extends DocumentView } )); } + else if (target.name === "intro") { + activeDoc.ins.intro.setValue(sanitizeHtml(text, + { + allowedTags: [ 'i','b','p' ] + } + )); + } } } diff --git a/source/client/ui/story/SettingsTaskView.ts b/source/client/ui/story/SettingsTaskView.ts index a662d658..f7b5f9e9 100644 --- a/source/client/ui/story/SettingsTaskView.ts +++ b/source/client/ui/story/SettingsTaskView.ts @@ -100,7 +100,7 @@ export class SettingsTree extends Tree protected createNodeTreeNode(node: Node): ITreeNode { - const components = node.components.getArray().filter(component => component["settingProperties"]); + const components = node.components.getArray().filter(component => component["settingProperties"] && !component.tags.has("no_settings")); return { id: node.id,