diff --git a/demo_parts/test2.json b/demo_parts/test2.json index 31fdaa1..5f2c060 100644 --- a/demo_parts/test2.json +++ b/demo_parts/test2.json @@ -1 +1 @@ -[19,2,1,{"byId":{"s1":{"obj3d":{"metadata":{"version":4.5,"type":"Object","generator":"Object3D.toJSON"},"geometries":[{"uuid":"BB029FDB-D33A-447D-8E19-C1215E922073","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[0,0,0],"normalized":false}},"boundingSphere":{"center":[0,0,0],"radius":0}}},{"uuid":"153A4B76-E25F-4A9A-89C4-35A9CEDA3254","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[-75.04573822021484,56.564918518066406,-5.684341886080802e-14],"normalized":false}},"boundingSphere":{"center":[-75.04573822021484,56.564918518066406,-5.684341886080802e-14],"radius":0}}},{"uuid":"3691A6EC-17A7-45CE-A9EE-3F7701652539","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[-88.5123519897461,-28.611907958984375,0],"normalized":false}},"boundingSphere":{"center":[-88.5123519897461,-28.611907958984375,0],"radius":0}}},{"uuid":"EF532EAA-661F-4225-9CCD-FA01CFB4CE43","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[-75.04573822021484,56.564918518066406,-5.684341886080802e-14,-88.5123519897461,-28.611907958984375,0],"normalized":false}},"boundingSphere":{"center":[-81.77904510498047,13.976505279541016,-2.842170943040401e-14],"radius":43.1174020991506}}},{"uuid":"920BADB2-C151-4D71-87EF-170047E926D5","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[-88.5123519897461,-28.611907958984375,0],"normalized":false}},"boundingSphere":{"center":[-88.5123519897461,-28.611907958984375,0],"radius":0}}},{"uuid":"35E1E0C7-1840-4465-BE2B-0A3DA778210B","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[98.0614013671875,-26.660362243652344,0],"normalized":false}},"boundingSphere":{"center":[98.0614013671875,-26.660362243652344,0],"radius":0}}},{"uuid":"B9BDEC8B-919C-4CE1-B634-2C517DED9F0E","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[-88.5123519897461,-28.611907958984375,0,98.0614013671875,-26.660362243652344,0],"normalized":false}},"boundingSphere":{"center":[4.774524688720703,-27.63613510131836,0],"radius":93.2919797897613}}},{"uuid":"171E3296-610D-430E-8D80-D5DFA354FE80","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[98.0614013671875,-26.660362243652344,0],"normalized":false}},"boundingSphere":{"center":[98.0614013671875,-26.660362243652344,0],"radius":0}}},{"uuid":"FA43AE96-51C5-4BC8-993D-B2B30765B9FF","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[95.36808013916016,64.6247787475586,0],"normalized":false}},"boundingSphere":{"center":[95.36808013916016,64.6247787475586,0],"radius":0}}},{"uuid":"54C02BEE-AA46-438C-BE8A-DC1D539BDE38","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[98.0614013671875,-26.660362243652344,0,95.36808013916016,64.6247787475586,0],"normalized":false}},"boundingSphere":{"center":[96.71474075317383,18.982208251953125,0],"radius":45.66243243910305}}},{"uuid":"C791FB46-9E46-43D9-B6E3-7B6448039219","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[95.36808013916016,64.6247787475586,0],"normalized":false}},"boundingSphere":{"center":[95.36808013916016,64.6247787475586,0],"radius":0}}},{"uuid":"240A7FA7-11C9-4D1D-8451-085800230FC5","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[-43.46042251586914,47.31925582885742,0],"normalized":false}},"boundingSphere":{"center":[-43.46042251586914,47.31925582885742,0],"radius":0}}},{"uuid":"E1F74852-D243-4268-A6F5-ADA94BAA2EE7","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[95.36808013916016,64.6247787475586,0,-43.46042251586914,47.31925582885742,0],"normalized":false}},"boundingSphere":{"center":[25.953828811645508,55.97201728820801,0],"radius":69.95147295255327}}},{"uuid":"7D9C5ACC-7FD0-4E5C-B47B-A16A285CFF22","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[-43.46042251586914,47.31925582885742,0],"normalized":false}},"boundingSphere":{"center":[-43.46042251586914,47.31925582885742,0],"radius":0}}},{"uuid":"4D581DBC-325C-4E7F-A143-5C138C64DA2A","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[-75.04573822021484,56.564918518066406,-5.684341886080802e-14],"normalized":false}},"boundingSphere":{"center":[-75.04573822021484,56.564918518066406,-5.684341886080802e-14],"radius":0}}},{"uuid":"003905E1-1827-46FD-9C95-C2D2E9089C5A","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[-43.46042251586914,47.31925582885742,0,-75.04573822021484,56.564918518066406,-5.684341886080802e-14],"normalized":false}},"boundingSphere":{"center":[-59.25308036804199,51.942087173461914,-2.842170943040401e-14],"radius":16.45535206783661}}}],"materials":[{"uuid":"F412C728-B2F4-4502-8BBD-62AC48A7CE0A","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"72C080C4-2FC7-4807-82E1-4F622603ADF6","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"D9D312A3-96D4-4BD1-8797-FD96F4EA8E3D","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"C38A3920-0B26-4B66-ACAB-C6D0315B812F","type":"LineBasicMaterial","color":16777215,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"6B52C0E0-11C0-4F3D-823D-853C4D3E9726","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"D360945F-DF67-43A4-8D41-983DF279FE60","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"D9025833-648A-460F-85B8-D65FE67BE9A3","type":"LineBasicMaterial","color":16777215,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"683993EB-CF9D-4083-A1FE-C8792CC6CEEC","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"345C5856-91D2-4EE0-B867-EF0ACC1F670C","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"253A1B36-6DE1-4AB8-A927-0C3B9E9F214C","type":"LineBasicMaterial","color":16777215,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"D30E7814-1178-4505-97E9-40FD2F654460","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"AE8723D1-1DA3-423F-9424-9DE4E49632A9","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"9A89E383-01C9-42E5-8D6F-37812F983573","type":"LineBasicMaterial","color":16777215,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"E48599BA-513B-4231-B947-2D1723C541B4","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"05E578BB-D0DB-4D79-930C-4BABBE44058D","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"EEA3C27F-9757-44E9-AF60-CBEE774E9F3B","type":"LineBasicMaterial","color":16777215,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680}],"object":{"uuid":"6963FDC8-294B-4BC3-B0AE-B333815B7E30","type":"Group","name":"s1","userData":{"type":"sketch","geomStartIdx":1},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"children":[{"uuid":"E3FC2B72-B862-401E-8A80-74493519FACC","type":"Group","layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]},{"uuid":"1713BA16-925F-491A-98F8-F360CEDCAF1D","type":"Points","name":"p0","renderOrder":1,"userData":{"type":"point","constraints":[]},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"BB029FDB-D33A-447D-8E19-C1215E922073","material":"F412C728-B2F4-4502-8BBD-62AC48A7CE0A"},{"uuid":"4E923AB3-8CEF-4383-9145-21D9E7094BAE","type":"Points","name":"p1","renderOrder":1,"userData":{"type":"point","constraints":[6],"l_id":0},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"153A4B76-E25F-4A9A-89C4-35A9CEDA3254","material":"72C080C4-2FC7-4807-82E1-4F622603ADF6"},{"uuid":"EF7485DC-567C-42CA-AEE2-5C612EF5993C","type":"Points","name":"p2","renderOrder":1,"userData":{"type":"point","constraints":[2],"l_id":0},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"3691A6EC-17A7-45CE-A9EE-3F7701652539","material":"D9D312A3-96D4-4BD1-8797-FD96F4EA8E3D"},{"uuid":"A02010AF-EEA1-4F77-80BB-4F5818A07F23","type":"Line","name":"l3","frustumCulled":false,"renderOrder":1,"userData":{"type":"line","constraints":[],"l_id":0},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"EF532EAA-661F-4225-9CCD-FA01CFB4CE43","material":"C38A3920-0B26-4B66-ACAB-C6D0315B812F"},{"uuid":"4188EB3A-6503-44AF-8D08-B3F2E7EF07AE","type":"Points","name":"p4","renderOrder":1,"userData":{"type":"point","constraints":[2],"l_id":1},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"920BADB2-C151-4D71-87EF-170047E926D5","material":"6B52C0E0-11C0-4F3D-823D-853C4D3E9726"},{"uuid":"9304B314-70B6-4C4C-AA4E-E22D43B7A1C3","type":"Points","name":"p5","renderOrder":1,"userData":{"type":"point","constraints":[3],"l_id":1},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"35E1E0C7-1840-4465-BE2B-0A3DA778210B","material":"D360945F-DF67-43A4-8D41-983DF279FE60"},{"uuid":"955879B7-F003-41EC-9C67-CAB5E3C01428","type":"Line","name":"l6","frustumCulled":false,"renderOrder":1,"userData":{"type":"line","constraints":[],"l_id":1},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"B9BDEC8B-919C-4CE1-B634-2C517DED9F0E","material":"D9025833-648A-460F-85B8-D65FE67BE9A3"},{"uuid":"8931DA51-733A-4A87-941E-469CD8DB80F7","type":"Points","name":"p7","renderOrder":1,"userData":{"type":"point","constraints":[3],"l_id":2},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"171E3296-610D-430E-8D80-D5DFA354FE80","material":"683993EB-CF9D-4083-A1FE-C8792CC6CEEC"},{"uuid":"44C925A4-4CB6-488E-B2F3-ABF3952A4F37","type":"Points","name":"p8","renderOrder":1,"userData":{"type":"point","constraints":[4],"l_id":2},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"FA43AE96-51C5-4BC8-993D-B2B30765B9FF","material":"345C5856-91D2-4EE0-B867-EF0ACC1F670C"},{"uuid":"FD0F0C88-0C32-48C2-BE70-F875CE48AAD0","type":"Line","name":"l9","frustumCulled":false,"renderOrder":1,"userData":{"type":"line","constraints":[],"l_id":2},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"54C02BEE-AA46-438C-BE8A-DC1D539BDE38","material":"253A1B36-6DE1-4AB8-A927-0C3B9E9F214C"},{"uuid":"170D9F16-A2E6-4727-AD37-6786245FA233","type":"Points","name":"p10","renderOrder":1,"userData":{"type":"point","constraints":[4],"l_id":3},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"C791FB46-9E46-43D9-B6E3-7B6448039219","material":"D30E7814-1178-4505-97E9-40FD2F654460"},{"uuid":"1B4E4440-E956-469D-9813-4F717DBECDD3","type":"Points","name":"p11","renderOrder":1,"userData":{"type":"point","constraints":[5],"l_id":3},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"240A7FA7-11C9-4D1D-8451-085800230FC5","material":"AE8723D1-1DA3-423F-9424-9DE4E49632A9"},{"uuid":"351229E9-673E-4AA9-866A-F623B42892D0","type":"Line","name":"l12","frustumCulled":false,"renderOrder":1,"userData":{"type":"line","constraints":[],"l_id":3},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"E1F74852-D243-4268-A6F5-ADA94BAA2EE7","material":"9A89E383-01C9-42E5-8D6F-37812F983573"},{"uuid":"81DC4FE2-148A-495C-BFAD-B0418D23B6DA","type":"Points","name":"p13","renderOrder":1,"userData":{"type":"point","constraints":[5],"l_id":4},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"7D9C5ACC-7FD0-4E5C-B47B-A16A285CFF22","material":"E48599BA-513B-4231-B947-2D1723C541B4"},{"uuid":"4D80D600-1275-4B04-AAEF-A6F1CA80DB90","type":"Points","name":"p14","renderOrder":1,"userData":{"type":"point","constraints":[6],"l_id":4},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"4D581DBC-325C-4E7F-A143-5C138C64DA2A","material":"05E578BB-D0DB-4D79-930C-4BABBE44058D"},{"uuid":"EEC33722-9296-4B50-9293-97DB995491C6","type":"Line","name":"l15","frustumCulled":false,"renderOrder":1,"userData":{"type":"line","constraints":[],"l_id":4},"layers":5,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"003905E1-1827-46FD-9C95-C2D2E9089C5A","material":"EEA3C27F-9757-44E9-AF60-CBEE774E9F3B"}]}},"objIdx":"{\"dataType\":\"Map\",\"value\":[[\"\",0],[\"p0\",1],[\"p1\",2],[\"p2\",3],[\"l3\",4],[\"p4\",5],[\"p5\",6],[\"l6\",7],[\"p7\",8],[\"p8\",9],[\"l9\",10],[\"p10\",11],[\"p11\",12],[\"l12\",13],[\"p13\",14],[\"p14\",15],[\"l15\",16]]}","linkedObjs":"{\"dataType\":\"Map\",\"value\":[[0,[\"line\",[\"p1\",\"p2\",\"l3\"]]],[1,[\"line\",[\"p4\",\"p5\",\"l6\"]]],[2,[\"line\",[\"p7\",\"p8\",\"l9\"]]],[3,[\"line\",[\"p10\",\"p11\",\"l12\"]]],[4,[\"line\",[\"p13\",\"p14\",\"l15\"]]]]}","l_id":6,"constraints":"{\"dataType\":\"Map\",\"value\":[[2,[\"points_coincident\",-1,[\"p2\",\"p4\",-1,-1]]],[3,[\"points_coincident\",-1,[\"p5\",\"p7\",-1,-1]]],[4,[\"points_coincident\",-1,[\"p8\",\"p10\",-1,-1]]],[5,[\"points_coincident\",-1,[\"p11\",\"p13\",-1,-1]]],[6,[\"points_coincident\",-1,[\"p1\",\"p14\",-1,-1]]]]}","c_id":7}},"allIds":["s1"],"tree":{"s1":{}},"order":{"s1":0},"visible":{"s1":true}}] \ No newline at end of file +[22,2,2,{"byId":{"s1":{"obj3d":{"metadata":{"version":4.5,"type":"Object","generator":"Object3D.toJSON"},"geometries":[{"uuid":"F7EE7767-D9BA-4FC6-B269-0838A95D032B","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[0,0,0],"normalized":false}},"boundingSphere":{"center":[0,0,0],"radius":0}}},{"uuid":"ABE20737-AD19-4FEA-9EB0-04F22664CA0B","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[0.36727115511894226,13.939902305603027,0],"normalized":false}},"boundingSphere":{"center":[0.36727115511894226,13.939902305603027,0],"radius":0}}},{"uuid":"6978D305-2EAD-4B50-92D7-F7BC9F8FB63C","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[-55.70279312133789,-49.011993408203125,0],"normalized":false}},"boundingSphere":{"center":[-55.70279312133789,-49.011993408203125,0],"radius":0}}},{"uuid":"3ADC9963-2895-40D1-A860-3D86C6EE1534","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[0.36727115511894226,13.939902305603027,0,-55.70279312133789,-49.011993408203125,0],"normalized":false}},"boundingSphere":{"center":[-27.667760983109474,-17.53604555130005,0],"radius":42.15089940300185}}},{"uuid":"D4AE06B4-3428-4327-B27F-310A81E41F6C","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[-55.70279312133789,-49.011993408203125,0],"normalized":false}},"boundingSphere":{"center":[-55.70279312133789,-49.011993408203125,0],"radius":0}}},{"uuid":"0084F2DC-EF5E-4884-9393-67F22E6F4AE1","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[62.803367614746094,-49.32569885253906,-5.684341886080802e-14],"normalized":false}},"boundingSphere":{"center":[62.803367614746094,-49.32569885253906,-5.684341886080802e-14],"radius":0}}},{"uuid":"F4202D42-EA42-4896-B318-B68EE82BEDEC","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[-55.70279312133789,-49.011993408203125,0,62.803367614746094,-49.32569885253906,-5.684341886080802e-14],"normalized":false}},"boundingSphere":{"center":[3.5502872467041016,-49.168846130371094,-2.842170943040401e-14],"radius":59.25328797525159}}},{"uuid":"9EC3DE53-737E-4FFD-87D1-C30A474295D3","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[62.803367614746094,-49.32569885253906,-5.684341886080802e-14],"normalized":false}},"boundingSphere":{"center":[62.803367614746094,-49.32569885253906,-5.684341886080802e-14],"radius":0}}},{"uuid":"A176538C-79D4-476F-9250-A3EA5AF093A8","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[119.36312866210938,55.881317138671875,0],"normalized":false}},"boundingSphere":{"center":[119.36312866210938,55.881317138671875,0],"radius":0}}},{"uuid":"5EC37C44-33F4-464C-8353-D9EA96DCD9ED","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[62.803367614746094,-49.32569885253906,-5.684341886080802e-14,119.36312866210938,55.881317138671875,0],"normalized":false}},"boundingSphere":{"center":[91.08324813842773,3.2778091430664062,-2.842170943040401e-14],"radius":59.72336808885979}}},{"uuid":"96AA36DB-21C9-4C0B-AA7C-04E54BAB554B","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[119.36312866210938,55.881317138671875,0],"normalized":false}},"boundingSphere":{"center":[119.36312866210938,55.881317138671875,0],"radius":0}}},{"uuid":"3EA60621-FAEA-415A-8F18-825354CA6123","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[36.60469436645508,35.908573150634766,-5.684341886080802e-14],"normalized":false}},"boundingSphere":{"center":[36.60469436645508,35.908573150634766,-5.684341886080802e-14],"radius":0}}},{"uuid":"9EE30818-7D5A-4010-80D7-968881E604D9","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[119.36312866210938,55.881317138671875,0,36.60469436645508,35.908573150634766,-5.684341886080802e-14],"normalized":false}},"boundingSphere":{"center":[77.98391151428223,45.89494514465332,-2.842170943040401e-14],"radius":42.56720847518604}}},{"uuid":"D61E6939-E22E-4A88-8D73-AC570680B7DC","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[36.60469436645508,35.908573150634766,-5.684341886080802e-14],"normalized":false}},"boundingSphere":{"center":[36.60469436645508,35.908573150634766,-5.684341886080802e-14],"radius":0}}},{"uuid":"23EBD427-F864-4226-B769-289A58364390","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[31.12859535217285,-18.270828247070312,-5.684341886080802e-14],"normalized":false}},"boundingSphere":{"center":[31.12859535217285,-18.270828247070312,-5.684341886080802e-14],"radius":0}}},{"uuid":"8FA745D6-E322-4459-81F4-0DCED5C414FC","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[36.60469436645508,35.908573150634766,-5.684341886080802e-14,31.12859535217285,-18.270828247070312,-5.684341886080802e-14],"normalized":false}},"boundingSphere":{"center":[33.866644859313965,8.818872451782227,-5.684341886080802e-14],"radius":27.22772115063924}}},{"uuid":"74DC1E85-D62B-4C6A-A7B0-6B9B94C29FBB","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[31.12859535217285,-18.270828247070312,-5.684341886080802e-14],"normalized":false}},"boundingSphere":{"center":[31.12859535217285,-18.270828247070312,-5.684341886080802e-14],"radius":0}}},{"uuid":"1F798272-B4C0-4906-AF5F-82CC1132DEC3","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[0.36727115511894226,13.939902305603027,0],"normalized":false}},"boundingSphere":{"center":[0.36727115511894226,13.939902305603027,0],"radius":0}}},{"uuid":"3173342D-677C-42B5-AA9A-7C8D0A533EBD","type":"BufferGeometry","data":{"attributes":{"position":{"itemSize":3,"type":"Float32Array","array":[31.12859535217285,-18.270828247070312,-5.684341886080802e-14,0.36727115511894226,13.939902305603027,0],"normalized":false}},"boundingSphere":{"center":[15.747933253645897,-2.1654629707336426,-2.842170943040401e-14],"radius":22.269880046225992}}}],"materials":[{"uuid":"914F60DA-780F-464D-B20B-CA2EF8ECAE1F","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"1DE71B77-0EDC-477B-BDBE-AF73DD0B1DE7","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"92B5DD49-F3BD-438C-8572-37A6EB445D5F","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"C7872356-86C8-4C21-9AD1-F7DA82661BBA","type":"LineBasicMaterial","color":16777215,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"73EEC45F-E8EF-42C7-B14C-45810EF6B941","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"3F71C28C-1686-4466-93B2-1FE015CCEB8D","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"AED87435-6602-44EA-A728-71AB9860090D","type":"LineBasicMaterial","color":16777215,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"701D6361-6A47-4C07-AD1F-91D15CAE8EF8","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"2B14E957-8DAC-4EB8-8668-C58C636E2605","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"D5C505EC-66E5-44B2-958B-709567452E93","type":"LineBasicMaterial","color":16777215,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"39AD8732-852E-473B-B409-54939509D662","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"4C08A559-71A5-4DF6-AFB3-1A575EFCEF8E","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"8D177697-12B9-4EC7-AC96-74267BA228DF","type":"LineBasicMaterial","color":16777215,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"A6734658-17FB-4D0F-AE94-7D009B6590C2","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"BA20EA57-EF66-4CBF-AFC6-63D09B1CC521","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"75875889-EE17-4782-AAAA-EC7EEFC4E78B","type":"LineBasicMaterial","color":16777215,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"E7C193D4-8B3D-4DA9-872F-AE992167EEA1","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"9F47A1C0-81F9-4BC2-9C76-A44C625C2BBF","type":"PointsMaterial","color":16777215,"size":4,"sizeAttenuation":true,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"D260EB59-55DC-46CE-A494-BF85A846C61F","type":"LineBasicMaterial","color":16777215,"depthFunc":3,"depthTest":false,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680}],"object":{"uuid":"3A640D17-57E6-4DED-BD69-B904CD2CBF8E","type":"Group","name":"s1","visible":false,"userData":{"type":"sketch","geomStartIdx":1},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"children":[{"uuid":"6C606FBC-5937-459F-A413-2BD105298ECD","type":"Group","layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]},{"uuid":"65191BC2-DBA0-468C-8C48-0068DC33AB0A","type":"Points","name":"p0","renderOrder":1,"userData":{"type":"point","constraints":[]},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"F7EE7767-D9BA-4FC6-B269-0838A95D032B","material":"914F60DA-780F-464D-B20B-CA2EF8ECAE1F"},{"uuid":"8CE06AD2-08A8-4831-A0AE-A1F3FFC5B0C2","type":"Points","name":"p1","renderOrder":1,"userData":{"type":"point","constraints":[8],"l_id":0},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"ABE20737-AD19-4FEA-9EB0-04F22664CA0B","material":"1DE71B77-0EDC-477B-BDBE-AF73DD0B1DE7"},{"uuid":"6807E24D-EFEA-4065-A032-BF5837BA5B26","type":"Points","name":"p2","renderOrder":1,"userData":{"type":"point","constraints":[2],"l_id":0},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"6978D305-2EAD-4B50-92D7-F7BC9F8FB63C","material":"92B5DD49-F3BD-438C-8572-37A6EB445D5F"},{"uuid":"626CD051-F7DF-4E10-AC52-C0579D414089","type":"Line","name":"l3","frustumCulled":false,"renderOrder":1,"userData":{"type":"line","constraints":[],"l_id":0},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"3ADC9963-2895-40D1-A860-3D86C6EE1534","material":"C7872356-86C8-4C21-9AD1-F7DA82661BBA"},{"uuid":"01074740-FB42-4F7F-8693-7EFC47D93220","type":"Points","name":"p4","renderOrder":1,"userData":{"type":"point","constraints":[2],"l_id":1},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"D4AE06B4-3428-4327-B27F-310A81E41F6C","material":"73EEC45F-E8EF-42C7-B14C-45810EF6B941"},{"uuid":"FC8CEC13-5F5A-42B1-A7FD-D111A92C3FF5","type":"Points","name":"p5","renderOrder":1,"userData":{"type":"point","constraints":[3],"l_id":1},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"0084F2DC-EF5E-4884-9393-67F22E6F4AE1","material":"3F71C28C-1686-4466-93B2-1FE015CCEB8D"},{"uuid":"9453FD6C-F0B0-4D19-BAAA-08080A3DA994","type":"Line","name":"l6","frustumCulled":false,"renderOrder":1,"userData":{"type":"line","constraints":[],"l_id":1},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"F4202D42-EA42-4896-B318-B68EE82BEDEC","material":"AED87435-6602-44EA-A728-71AB9860090D"},{"uuid":"A17B4CF8-A8F1-42D4-9E3C-100310788DDF","type":"Points","name":"p7","renderOrder":1,"userData":{"type":"point","constraints":[3],"l_id":2},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"9EC3DE53-737E-4FFD-87D1-C30A474295D3","material":"701D6361-6A47-4C07-AD1F-91D15CAE8EF8"},{"uuid":"174D6F02-6F02-4730-909F-49FAB78D4311","type":"Points","name":"p8","renderOrder":1,"userData":{"type":"point","constraints":[4],"l_id":2},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"A176538C-79D4-476F-9250-A3EA5AF093A8","material":"2B14E957-8DAC-4EB8-8668-C58C636E2605"},{"uuid":"2A84D713-81CE-44C1-84D8-BED0A2725940","type":"Line","name":"l9","frustumCulled":false,"renderOrder":1,"userData":{"type":"line","constraints":[],"l_id":2},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"5EC37C44-33F4-464C-8353-D9EA96DCD9ED","material":"D5C505EC-66E5-44B2-958B-709567452E93"},{"uuid":"6E13D946-DB80-4D4A-A8F6-8A38E846D606","type":"Points","name":"p10","renderOrder":1,"userData":{"type":"point","constraints":[4],"l_id":3},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"96AA36DB-21C9-4C0B-AA7C-04E54BAB554B","material":"39AD8732-852E-473B-B409-54939509D662"},{"uuid":"76722DE2-1961-4FE0-85A1-16E16DA22E36","type":"Points","name":"p11","renderOrder":1,"userData":{"type":"point","constraints":[5],"l_id":3},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"3EA60621-FAEA-415A-8F18-825354CA6123","material":"4C08A559-71A5-4DF6-AFB3-1A575EFCEF8E"},{"uuid":"59F37E54-AAEA-47DE-B0C0-F092185DA1C0","type":"Line","name":"l12","frustumCulled":false,"renderOrder":1,"userData":{"type":"line","constraints":[],"l_id":3},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"9EE30818-7D5A-4010-80D7-968881E604D9","material":"8D177697-12B9-4EC7-AC96-74267BA228DF"},{"uuid":"133A0ACB-37A6-4112-A436-B5CB4813A17D","type":"Points","name":"p13","renderOrder":1,"userData":{"type":"point","constraints":[5],"l_id":4},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"D61E6939-E22E-4A88-8D73-AC570680B7DC","material":"A6734658-17FB-4D0F-AE94-7D009B6590C2"},{"uuid":"83B31730-7835-4DAD-BA78-E1E065778FFE","type":"Points","name":"p14","renderOrder":1,"userData":{"type":"point","constraints":[6],"l_id":4},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"23EBD427-F864-4226-B769-289A58364390","material":"BA20EA57-EF66-4CBF-AFC6-63D09B1CC521"},{"uuid":"C03C5395-4AAB-4CBB-AA01-8CBB09B63404","type":"Line","name":"l15","frustumCulled":false,"renderOrder":1,"userData":{"type":"line","constraints":[],"l_id":4},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"8FA745D6-E322-4459-81F4-0DCED5C414FC","material":"75875889-EE17-4782-AAAA-EC7EEFC4E78B"},{"uuid":"680522F9-13AA-4795-BDCD-99D3D1A6A02E","type":"Points","name":"p16","renderOrder":1,"userData":{"type":"point","constraints":[6],"l_id":5},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"74DC1E85-D62B-4C6A-A7B0-6B9B94C29FBB","material":"E7C193D4-8B3D-4DA9-872F-AE992167EEA1"},{"uuid":"9A6049BE-426E-4F20-8EB3-29192BA3D8AC","type":"Points","name":"p17","renderOrder":1,"userData":{"type":"point","constraints":[8],"l_id":5},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"1F798272-B4C0-4906-AF5F-82CC1132DEC3","material":"9F47A1C0-81F9-4BC2-9C76-A44C625C2BBF"},{"uuid":"F77278E6-132D-4570-B983-3B6B9C723155","type":"Line","name":"l18","frustumCulled":false,"renderOrder":1,"userData":{"type":"line","constraints":[],"l_id":5},"layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"3173342D-677C-42B5-AA9A-7C8D0A533EBD","material":"D260EB59-55DC-46CE-A494-BF85A846C61F"}]}},"objIdx":"{\"dataType\":\"Map\",\"value\":[[\"\",0],[\"p0\",1],[\"p1\",2],[\"p2\",3],[\"l3\",4],[\"p4\",5],[\"p5\",6],[\"l6\",7],[\"p7\",8],[\"p8\",9],[\"l9\",10],[\"p10\",11],[\"p11\",12],[\"l12\",13],[\"p13\",14],[\"p14\",15],[\"l15\",16],[\"p16\",17],[\"p17\",18],[\"l18\",19]]}","linkedObjs":"{\"dataType\":\"Map\",\"value\":[[0,[\"line\",[\"p1\",\"p2\",\"l3\"]]],[1,[\"line\",[\"p4\",\"p5\",\"l6\"]]],[2,[\"line\",[\"p7\",\"p8\",\"l9\"]]],[3,[\"line\",[\"p10\",\"p11\",\"l12\"]]],[4,[\"line\",[\"p13\",\"p14\",\"l15\"]]],[5,[\"line\",[\"p16\",\"p17\",\"l18\"]]]]}","l_id":7,"constraints":"{\"dataType\":\"Map\",\"value\":[[2,[\"points_coincident\",-1,[\"p2\",\"p4\",-1,-1]]],[3,[\"points_coincident\",-1,[\"p5\",\"p7\",-1,-1]]],[4,[\"points_coincident\",-1,[\"p8\",\"p10\",-1,-1]]],[5,[\"points_coincident\",-1,[\"p11\",\"p13\",-1,-1]]],[6,[\"points_coincident\",-1,[\"p14\",\"p16\",-1,-1]]],[8,[\"points_coincident\",-1,[\"p1\",\"p17\",-1,-1]]]]}","c_id":8},"e1":{"metadata":{"version":4.5,"type":"Object","generator":"Object3D.toJSON"},"geometries":[{"uuid":"9C8E3367-43A1-4A0D-9BA5-23A4D0CC47C1","type":"ExtrudeGeometry","shapes":["53078D06-148A-4F4E-AF4B-75BFE5A26B82"],"options":{"depth":"10","bevelEnabled":false}}],"materials":[{"uuid":"1DF2756A-39B3-4C37-9031-1295626FE413","type":"MeshPhongMaterial","color":10342381,"emissive":468276,"specular":1118481,"shininess":30,"size":4,"depthFunc":3,"depthTest":true,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680},{"uuid":"E0C2D8E9-78B0-4162-8A30-66AE0F78E6C4","type":"PointsMaterial","color":16777215,"size":1,"sizeAttenuation":true,"depthFunc":3,"depthTest":true,"depthWrite":true,"stencilWrite":false,"stencilWriteMask":255,"stencilFunc":519,"stencilRef":0,"stencilFuncMask":255,"stencilFail":7680,"stencilZFail":7680,"stencilZPass":7680}],"shapes":[{"arcLengthDivisions":200,"type":"Shape","autoClose":false,"curves":[{"metadata":{"version":4.5,"type":"Curve","generator":"Curve.toJSON"},"arcLengthDivisions":200,"type":"LineCurve","v1":[0.36727115511894226,13.939902305603027],"v2":[-55.70279312133789,-49.011993408203125]},{"metadata":{"version":4.5,"type":"Curve","generator":"Curve.toJSON"},"arcLengthDivisions":200,"type":"LineCurve","v1":[-55.70279312133789,-49.011993408203125],"v2":[-55.70279312133789,-49.011993408203125]},{"metadata":{"version":4.5,"type":"Curve","generator":"Curve.toJSON"},"arcLengthDivisions":200,"type":"LineCurve","v1":[-55.70279312133789,-49.011993408203125],"v2":[62.803367614746094,-49.32569885253906]},{"metadata":{"version":4.5,"type":"Curve","generator":"Curve.toJSON"},"arcLengthDivisions":200,"type":"LineCurve","v1":[62.803367614746094,-49.32569885253906],"v2":[62.803367614746094,-49.32569885253906]},{"metadata":{"version":4.5,"type":"Curve","generator":"Curve.toJSON"},"arcLengthDivisions":200,"type":"LineCurve","v1":[62.803367614746094,-49.32569885253906],"v2":[119.36312866210938,55.881317138671875]},{"metadata":{"version":4.5,"type":"Curve","generator":"Curve.toJSON"},"arcLengthDivisions":200,"type":"LineCurve","v1":[119.36312866210938,55.881317138671875],"v2":[119.36312866210938,55.881317138671875]},{"metadata":{"version":4.5,"type":"Curve","generator":"Curve.toJSON"},"arcLengthDivisions":200,"type":"LineCurve","v1":[119.36312866210938,55.881317138671875],"v2":[36.60469436645508,35.908573150634766]},{"metadata":{"version":4.5,"type":"Curve","generator":"Curve.toJSON"},"arcLengthDivisions":200,"type":"LineCurve","v1":[36.60469436645508,35.908573150634766],"v2":[36.60469436645508,35.908573150634766]},{"metadata":{"version":4.5,"type":"Curve","generator":"Curve.toJSON"},"arcLengthDivisions":200,"type":"LineCurve","v1":[36.60469436645508,35.908573150634766],"v2":[31.12859535217285,-18.270828247070312]},{"metadata":{"version":4.5,"type":"Curve","generator":"Curve.toJSON"},"arcLengthDivisions":200,"type":"LineCurve","v1":[31.12859535217285,-18.270828247070312],"v2":[31.12859535217285,-18.270828247070312]},{"metadata":{"version":4.5,"type":"Curve","generator":"Curve.toJSON"},"arcLengthDivisions":200,"type":"LineCurve","v1":[31.12859535217285,-18.270828247070312],"v2":[0.36727115511894226,13.939902305603027]}],"currentPoint":[0.36727115511894226,13.939902305603027],"uuid":"53078D06-148A-4F4E-AF4B-75BFE5A26B82","holes":[]}],"object":{"uuid":"B291875F-D8E7-4DA0-A2E5-082E729470B7","type":"Mesh","name":"e1","userData":{"type":"mesh","featureInfo":["s1","10"]},"layers":3,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"matrixAutoUpdate":false,"geometry":"9C8E3367-43A1-4A0D-9BA5-23A4D0CC47C1","material":"1DF2756A-39B3-4C37-9031-1295626FE413","children":[{"uuid":"E9A1939F-2F9B-4DCC-A9CD-03A612013EE3","type":"Points","visible":false,"userData":{"type":"point"},"layers":3,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"geometry":"9C8E3367-43A1-4A0D-9BA5-23A4D0CC47C1","material":"E0C2D8E9-78B0-4162-8A30-66AE0F78E6C4"}]}}},"allIds":["s1","e1"],"tree":{"s1":{"e1":true},"e1":{}},"order":{"s1":0,"e1":1},"visible":{"s1":false,"e1":true}}] \ No newline at end of file diff --git a/extlib/fs/directory-open.mjs b/extlib/fs/directory-open.mjs new file mode 100644 index 0000000..e51d720 --- /dev/null +++ b/extlib/fs/directory-open.mjs @@ -0,0 +1,32 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +import supported from './supported.mjs'; + +const implementation = !supported + ? import('./legacy/directory-open.mjs') + : supported === 'chooseFileSystemEntries' + ? import('./fs-access-legacy/directory-open.mjs') + : import('./fs-access/directory-open.mjs'); + +/** + * For opening directories, dynamically either loads the File System Access API + * module or the legacy method. + */ +export async function directoryOpen(...args) { + return (await implementation).default(...args); +} diff --git a/extlib/fs/file-open.mjs b/extlib/fs/file-open.mjs new file mode 100644 index 0000000..407082c --- /dev/null +++ b/extlib/fs/file-open.mjs @@ -0,0 +1,32 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +import supported from './supported.mjs'; + +const implementation = !supported + ? import('./legacy/file-open.mjs') + : supported === 'chooseFileSystemEntries' + ? import('./fs-access-legacy/file-open.mjs') + : import('./fs-access/file-open.mjs'); + +/** + * For opening files, dynamically either loads the File System Access API module + * or the legacy method. + */ +export async function fileOpen(...args) { + return (await implementation).default(...args); +} diff --git a/extlib/fs/file-save.mjs b/extlib/fs/file-save.mjs new file mode 100644 index 0000000..8364a05 --- /dev/null +++ b/extlib/fs/file-save.mjs @@ -0,0 +1,32 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +import supported from './supported.mjs'; + +const implementation = !supported + ? import('./legacy/file-save.mjs') + : supported === 'chooseFileSystemEntries' + ? import('./fs-access-legacy/file-save.mjs') + : import('./fs-access/file-save.mjs'); + +/** + * For saving files, dynamically either loads the File System Access API module + * or the legacy method. + */ +export async function fileSave(...args) { + return (await implementation).default(...args); +} diff --git a/extlib/fs/fs-access-legacy/directory-open.mjs b/extlib/fs/fs-access-legacy/directory-open.mjs new file mode 100644 index 0000000..e921f95 --- /dev/null +++ b/extlib/fs/fs-access-legacy/directory-open.mjs @@ -0,0 +1,50 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +const getFiles = async (dirHandle, recursive, path = dirHandle.name) => { + const dirs = []; + const files = []; + for await (const entry of dirHandle.getEntries()) { + const nestedPath = `${path}/${entry.name}`; + if (entry.isFile) { + files.push( + entry.getFile().then((file) => + Object.defineProperty(file, 'webkitRelativePath', { + configurable: true, + enumerable: true, + get: () => nestedPath, + }) + ) + ); + } else if (entry.isDirectory && recursive) { + dirs.push(getFiles(entry, recursive, nestedPath)); + } + } + return [...(await Promise.all(dirs)).flat(), ...(await Promise.all(files))]; +}; + +/** + * Opens a directory from disk using the (legacy) File System Access API. + * @type { typeof import("../../index").directoryOpen } + */ +export default async (options = {}) => { + options.recursive = options.recursive || false; + const handle = await window.chooseFileSystemEntries({ + type: 'open-directory', + }); + return getFiles(handle, options.recursive); +}; diff --git a/extlib/fs/fs-access-legacy/file-open.mjs b/extlib/fs/fs-access-legacy/file-open.mjs new file mode 100644 index 0000000..2cdfe6b --- /dev/null +++ b/extlib/fs/fs-access-legacy/file-open.mjs @@ -0,0 +1,43 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +const getFileWithHandle = async (handle) => { + const file = await handle.getFile(); + file.handle = handle; + return file; +}; + +/** + * Opens a file from disk using the (legacy) File System Access API. + * @type { typeof import("../../index").fileOpen } + */ +export default async (options = {}) => { + const handleOrHandles = await window.chooseFileSystemEntries({ + accepts: [ + { + description: options.description || '', + mimeTypes: options.mimeTypes || ['*/*'], + extensions: options.extensions || [''], + }, + ], + multiple: options.multiple || false, + }); + if (options.multiple) { + return Promise.all(handleOrHandles.map(getFileWithHandle)); + } + return getFileWithHandle(handleOrHandles); +}; diff --git a/extlib/fs/fs-access-legacy/file-save.mjs b/extlib/fs/fs-access-legacy/file-save.mjs new file mode 100644 index 0000000..9fe0ca6 --- /dev/null +++ b/extlib/fs/fs-access-legacy/file-save.mjs @@ -0,0 +1,40 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * Saves a file to disk using the (legacy) File System Access API. + * @type { typeof import("../../index").fileSave } + */ +export default async (blob, options = {}, handle = null) => { + options.fileName = options.fileName || 'Untitled'; + handle = + handle || + (await window.chooseFileSystemEntries({ + type: 'save-file', + accepts: [ + { + description: options.description || '', + mimeTypes: [blob.type], + extensions: options.extensions || [''], + }, + ], + })); + const writable = await handle.createWritable(); + await writable.write(blob); + await writable.close(); + return handle; +}; diff --git a/extlib/fs/fs-access/directory-open.mjs b/extlib/fs/fs-access/directory-open.mjs new file mode 100644 index 0000000..19fade2 --- /dev/null +++ b/extlib/fs/fs-access/directory-open.mjs @@ -0,0 +1,48 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +const getFiles = async (dirHandle, recursive, path = dirHandle.name) => { + const dirs = []; + const files = []; + for await (const entry of dirHandle.values()) { + const nestedPath = `${path}/${entry.name}`; + if (entry.kind === 'file') { + files.push( + entry.getFile().then((file) => + Object.defineProperty(file, 'webkitRelativePath', { + configurable: true, + enumerable: true, + get: () => nestedPath, + }) + ) + ); + } else if (entry.kind === 'directory' && recursive) { + dirs.push(getFiles(entry, recursive, nestedPath)); + } + } + return [...(await Promise.all(dirs)).flat(), ...(await Promise.all(files))]; +}; + +/** + * Opens a directory from disk using the File System Access API. + * @type { typeof import("../../index").directoryOpen } + */ +export default async (options = {}) => { + options.recursive = options.recursive || false; + const handle = await window.showDirectoryPicker(); + return getFiles(handle, options.recursive); +}; diff --git a/extlib/fs/fs-access/file-open.mjs b/extlib/fs/fs-access/file-open.mjs new file mode 100644 index 0000000..dab1119 --- /dev/null +++ b/extlib/fs/fs-access/file-open.mjs @@ -0,0 +1,51 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +const getFileWithHandle = async (handle) => { + const file = await handle.getFile(); + file.handle = handle; + return file; +}; + +/** + * Opens a file from disk using the File System Access API. + * @type { typeof import("../../index").fileOpen } + */ +export default async (options = {}) => { + const accept = {}; + if (options.mimeTypes) { + options.mimeTypes.map((mimeType) => { + accept[mimeType] = options.extensions || []; + }); + } else { + accept['*/*'] = options.extensions || []; + } + const handleOrHandles = await window.showOpenFilePicker({ + types: [ + { + description: options.description || '', + accept: accept, + }, + ], + multiple: options.multiple || false, + }); + const files = await Promise.all(handleOrHandles.map(getFileWithHandle)); + if (options.multiple) { + return files; + } + return files[0]; +}; diff --git a/extlib/fs/fs-access/file-save.mjs b/extlib/fs/fs-access/file-save.mjs new file mode 100644 index 0000000..16b8b2c --- /dev/null +++ b/extlib/fs/fs-access/file-save.mjs @@ -0,0 +1,64 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * Saves a file to disk using the File System Access API. + * @type { typeof import("../../index").fileSave } + */ +export default async ( + blob, + options = {}, + existingHandle = null, + throwIfExistingHandleNotGood = false +) => { + options.fileName = options.fileName || 'Untitled'; + const accept = {}; + if (options.mimeTypes) { + options.mimeTypes.push(blob.type); + options.mimeTypes.map((mimeType) => { + accept[mimeType] = options.extensions || []; + }); + } else { + accept[blob.type] = options.extensions || []; + } + if (existingHandle) { + try { + // Check if the file still exists. + await existingHandle.getFile(); + } catch (err) { + existingHandle = null; + if (throwIfExistingHandleNotGood) { + throw err; + } + } + } + const handle = + existingHandle || + (await window.showSaveFilePicker({ + suggestedName: options.fileName, + types: [ + { + description: options.description || '', + accept: accept, + }, + ], + })); + const writable = await handle.createWritable(); + await writable.write(blob); + await writable.close(); + return handle; +}; diff --git a/extlib/fs/index.js b/extlib/fs/index.js new file mode 100644 index 0000000..f67f6df --- /dev/null +++ b/extlib/fs/index.js @@ -0,0 +1,24 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * @module browser-fs-access + */ +export { fileOpen } from './file-open.mjs'; +export { directoryOpen } from './directory-open.mjs'; +export { fileSave } from './file-save.mjs'; +export { default as supported } from './supported.mjs'; diff --git a/extlib/fs/legacy/directory-open.mjs b/extlib/fs/legacy/directory-open.mjs new file mode 100644 index 0000000..e58a870 --- /dev/null +++ b/extlib/fs/legacy/directory-open.mjs @@ -0,0 +1,58 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * Opens a directory from disk using the legacy + * `` method. + * @type { typeof import("../../index").directoryOpen } + */ +export default async (options = {}) => { + options.recursive = options.recursive || false; + return new Promise((resolve, reject) => { + const input = document.createElement('input'); + input.type = 'file'; + input.webkitdirectory = true; + + // ToDo: Remove this workaround once + // https://github.com/whatwg/html/issues/6376 is specified and supported. + const rejectOnPageInteraction = () => { + window.removeEventListener('pointermove', rejectOnPageInteraction); + window.removeEventListener('pointerdown', rejectOnPageInteraction); + window.removeEventListener('keydown', rejectOnPageInteraction); + reject(new DOMException('The user aborted a request.', 'AbortError')); + }; + + window.addEventListener('pointermove', rejectOnPageInteraction); + window.addEventListener('pointerdown', rejectOnPageInteraction); + window.addEventListener('keydown', rejectOnPageInteraction); + + input.addEventListener('change', () => { + window.removeEventListener('pointermove', rejectOnPageInteraction); + window.removeEventListener('pointerdown', rejectOnPageInteraction); + window.removeEventListener('keydown', rejectOnPageInteraction); + let files = Array.from(input.files); + if (!options.recursive) { + files = files.filter((file) => { + return file.webkitRelativePath.split('/').length === 2; + }); + } + resolve(files); + }); + + input.click(); + }); +}; diff --git a/extlib/fs/legacy/file-open.mjs b/extlib/fs/legacy/file-open.mjs new file mode 100644 index 0000000..cfa4f7f --- /dev/null +++ b/extlib/fs/legacy/file-open.mjs @@ -0,0 +1,56 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * Opens a file from disk using the legacy `` method. + * @type { typeof import("../../index").fileOpen } + */ +export default async (options = {}) => { + return new Promise((resolve, reject) => { + const input = document.createElement('input'); + input.type = 'file'; + const accept = [ + ...(options.mimeTypes ? options.mimeTypes : []), + options.extensions ? options.extensions : [], + ].join(); + input.multiple = options.multiple || false; + // Empty string allows everything. + input.accept = accept || ''; + + // ToDo: Remove this workaround once + // https://github.com/whatwg/html/issues/6376 is specified and supported. + const rejectOnPageInteraction = () => { + window.removeEventListener('pointermove', rejectOnPageInteraction); + window.removeEventListener('pointerdown', rejectOnPageInteraction); + window.removeEventListener('keydown', rejectOnPageInteraction); + reject(new DOMException('The user aborted a request.', 'AbortError')); + }; + + window.addEventListener('pointermove', rejectOnPageInteraction); + window.addEventListener('pointerdown', rejectOnPageInteraction); + window.addEventListener('keydown', rejectOnPageInteraction); + + input.addEventListener('change', () => { + window.removeEventListener('pointermove', rejectOnPageInteraction); + window.removeEventListener('pointerdown', rejectOnPageInteraction); + window.removeEventListener('keydown', rejectOnPageInteraction); + resolve(input.multiple ? input.files : input.files[0]); + }); + + input.click(); + }); +}; diff --git a/extlib/fs/legacy/file-save.mjs b/extlib/fs/legacy/file-save.mjs new file mode 100644 index 0000000..da453f7 --- /dev/null +++ b/extlib/fs/legacy/file-save.mjs @@ -0,0 +1,32 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * Saves a file to disk using the legacy `` method. + * @type { typeof import("../../index").fileSave } + */ +export default async (blob, options = {}) => { + const a = document.createElement('a'); + a.download = options.fileName || 'Untitled'; + a.href = URL.createObjectURL(blob); + a.addEventListener('click', () => { + // `setTimeout()` due to + // https://github.com/LLK/scratch-gui/issues/1783#issuecomment-426286393 + setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000); + }); + a.click(); +}; diff --git a/extlib/fs/supported.mjs b/extlib/fs/supported.mjs new file mode 100644 index 0000000..b1ac42a --- /dev/null +++ b/extlib/fs/supported.mjs @@ -0,0 +1,43 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * Returns whether the File System Access API is supported and usable in the + * current context (for example cross-origin iframes). + * @returns {boolean} Returns `true` if the File System Access API is supported and usable, else returns `false`. + */ +const supported = (() => { + // ToDo: Remove this check once Permissions Policy integration + // has happened, tracked in + // https://github.com/WICG/file-system-access/issues/245. + if ('top' in self && self !== top) { + try { + // This will succeed on same-origin iframes, + // but fail on cross-origin iframes. + top.location + ''; + } catch { + return false; + } + } else if ('chooseFileSystemEntries' in self) { + return 'chooseFileSystemEntries'; + } else if ('showOpenFilePicker' in self) { + return 'showOpenFilePicker'; + } + return false; +})(); + +export default supported; diff --git a/extlib/src (copy)/directory-open.mjs b/extlib/src (copy)/directory-open.mjs new file mode 100644 index 0000000..e51d720 --- /dev/null +++ b/extlib/src (copy)/directory-open.mjs @@ -0,0 +1,32 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +import supported from './supported.mjs'; + +const implementation = !supported + ? import('./legacy/directory-open.mjs') + : supported === 'chooseFileSystemEntries' + ? import('./fs-access-legacy/directory-open.mjs') + : import('./fs-access/directory-open.mjs'); + +/** + * For opening directories, dynamically either loads the File System Access API + * module or the legacy method. + */ +export async function directoryOpen(...args) { + return (await implementation).default(...args); +} diff --git a/extlib/src (copy)/file-open.mjs b/extlib/src (copy)/file-open.mjs new file mode 100644 index 0000000..407082c --- /dev/null +++ b/extlib/src (copy)/file-open.mjs @@ -0,0 +1,32 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +import supported from './supported.mjs'; + +const implementation = !supported + ? import('./legacy/file-open.mjs') + : supported === 'chooseFileSystemEntries' + ? import('./fs-access-legacy/file-open.mjs') + : import('./fs-access/file-open.mjs'); + +/** + * For opening files, dynamically either loads the File System Access API module + * or the legacy method. + */ +export async function fileOpen(...args) { + return (await implementation).default(...args); +} diff --git a/extlib/src (copy)/file-save.mjs b/extlib/src (copy)/file-save.mjs new file mode 100644 index 0000000..8364a05 --- /dev/null +++ b/extlib/src (copy)/file-save.mjs @@ -0,0 +1,32 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +import supported from './supported.mjs'; + +const implementation = !supported + ? import('./legacy/file-save.mjs') + : supported === 'chooseFileSystemEntries' + ? import('./fs-access-legacy/file-save.mjs') + : import('./fs-access/file-save.mjs'); + +/** + * For saving files, dynamically either loads the File System Access API module + * or the legacy method. + */ +export async function fileSave(...args) { + return (await implementation).default(...args); +} diff --git a/extlib/src (copy)/fs-access-legacy/directory-open.mjs b/extlib/src (copy)/fs-access-legacy/directory-open.mjs new file mode 100644 index 0000000..e921f95 --- /dev/null +++ b/extlib/src (copy)/fs-access-legacy/directory-open.mjs @@ -0,0 +1,50 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +const getFiles = async (dirHandle, recursive, path = dirHandle.name) => { + const dirs = []; + const files = []; + for await (const entry of dirHandle.getEntries()) { + const nestedPath = `${path}/${entry.name}`; + if (entry.isFile) { + files.push( + entry.getFile().then((file) => + Object.defineProperty(file, 'webkitRelativePath', { + configurable: true, + enumerable: true, + get: () => nestedPath, + }) + ) + ); + } else if (entry.isDirectory && recursive) { + dirs.push(getFiles(entry, recursive, nestedPath)); + } + } + return [...(await Promise.all(dirs)).flat(), ...(await Promise.all(files))]; +}; + +/** + * Opens a directory from disk using the (legacy) File System Access API. + * @type { typeof import("../../index").directoryOpen } + */ +export default async (options = {}) => { + options.recursive = options.recursive || false; + const handle = await window.chooseFileSystemEntries({ + type: 'open-directory', + }); + return getFiles(handle, options.recursive); +}; diff --git a/extlib/src (copy)/fs-access-legacy/file-open.mjs b/extlib/src (copy)/fs-access-legacy/file-open.mjs new file mode 100644 index 0000000..2cdfe6b --- /dev/null +++ b/extlib/src (copy)/fs-access-legacy/file-open.mjs @@ -0,0 +1,43 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +const getFileWithHandle = async (handle) => { + const file = await handle.getFile(); + file.handle = handle; + return file; +}; + +/** + * Opens a file from disk using the (legacy) File System Access API. + * @type { typeof import("../../index").fileOpen } + */ +export default async (options = {}) => { + const handleOrHandles = await window.chooseFileSystemEntries({ + accepts: [ + { + description: options.description || '', + mimeTypes: options.mimeTypes || ['*/*'], + extensions: options.extensions || [''], + }, + ], + multiple: options.multiple || false, + }); + if (options.multiple) { + return Promise.all(handleOrHandles.map(getFileWithHandle)); + } + return getFileWithHandle(handleOrHandles); +}; diff --git a/extlib/src (copy)/fs-access-legacy/file-save.mjs b/extlib/src (copy)/fs-access-legacy/file-save.mjs new file mode 100644 index 0000000..9fe0ca6 --- /dev/null +++ b/extlib/src (copy)/fs-access-legacy/file-save.mjs @@ -0,0 +1,40 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * Saves a file to disk using the (legacy) File System Access API. + * @type { typeof import("../../index").fileSave } + */ +export default async (blob, options = {}, handle = null) => { + options.fileName = options.fileName || 'Untitled'; + handle = + handle || + (await window.chooseFileSystemEntries({ + type: 'save-file', + accepts: [ + { + description: options.description || '', + mimeTypes: [blob.type], + extensions: options.extensions || [''], + }, + ], + })); + const writable = await handle.createWritable(); + await writable.write(blob); + await writable.close(); + return handle; +}; diff --git a/extlib/src (copy)/fs-access/directory-open.mjs b/extlib/src (copy)/fs-access/directory-open.mjs new file mode 100644 index 0000000..19fade2 --- /dev/null +++ b/extlib/src (copy)/fs-access/directory-open.mjs @@ -0,0 +1,48 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +const getFiles = async (dirHandle, recursive, path = dirHandle.name) => { + const dirs = []; + const files = []; + for await (const entry of dirHandle.values()) { + const nestedPath = `${path}/${entry.name}`; + if (entry.kind === 'file') { + files.push( + entry.getFile().then((file) => + Object.defineProperty(file, 'webkitRelativePath', { + configurable: true, + enumerable: true, + get: () => nestedPath, + }) + ) + ); + } else if (entry.kind === 'directory' && recursive) { + dirs.push(getFiles(entry, recursive, nestedPath)); + } + } + return [...(await Promise.all(dirs)).flat(), ...(await Promise.all(files))]; +}; + +/** + * Opens a directory from disk using the File System Access API. + * @type { typeof import("../../index").directoryOpen } + */ +export default async (options = {}) => { + options.recursive = options.recursive || false; + const handle = await window.showDirectoryPicker(); + return getFiles(handle, options.recursive); +}; diff --git a/extlib/src (copy)/fs-access/file-open.mjs b/extlib/src (copy)/fs-access/file-open.mjs new file mode 100644 index 0000000..dab1119 --- /dev/null +++ b/extlib/src (copy)/fs-access/file-open.mjs @@ -0,0 +1,51 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +const getFileWithHandle = async (handle) => { + const file = await handle.getFile(); + file.handle = handle; + return file; +}; + +/** + * Opens a file from disk using the File System Access API. + * @type { typeof import("../../index").fileOpen } + */ +export default async (options = {}) => { + const accept = {}; + if (options.mimeTypes) { + options.mimeTypes.map((mimeType) => { + accept[mimeType] = options.extensions || []; + }); + } else { + accept['*/*'] = options.extensions || []; + } + const handleOrHandles = await window.showOpenFilePicker({ + types: [ + { + description: options.description || '', + accept: accept, + }, + ], + multiple: options.multiple || false, + }); + const files = await Promise.all(handleOrHandles.map(getFileWithHandle)); + if (options.multiple) { + return files; + } + return files[0]; +}; diff --git a/extlib/src (copy)/fs-access/file-save.mjs b/extlib/src (copy)/fs-access/file-save.mjs new file mode 100644 index 0000000..16b8b2c --- /dev/null +++ b/extlib/src (copy)/fs-access/file-save.mjs @@ -0,0 +1,64 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * Saves a file to disk using the File System Access API. + * @type { typeof import("../../index").fileSave } + */ +export default async ( + blob, + options = {}, + existingHandle = null, + throwIfExistingHandleNotGood = false +) => { + options.fileName = options.fileName || 'Untitled'; + const accept = {}; + if (options.mimeTypes) { + options.mimeTypes.push(blob.type); + options.mimeTypes.map((mimeType) => { + accept[mimeType] = options.extensions || []; + }); + } else { + accept[blob.type] = options.extensions || []; + } + if (existingHandle) { + try { + // Check if the file still exists. + await existingHandle.getFile(); + } catch (err) { + existingHandle = null; + if (throwIfExistingHandleNotGood) { + throw err; + } + } + } + const handle = + existingHandle || + (await window.showSaveFilePicker({ + suggestedName: options.fileName, + types: [ + { + description: options.description || '', + accept: accept, + }, + ], + })); + const writable = await handle.createWritable(); + await writable.write(blob); + await writable.close(); + return handle; +}; diff --git a/extlib/src (copy)/index.js b/extlib/src (copy)/index.js new file mode 100644 index 0000000..f67f6df --- /dev/null +++ b/extlib/src (copy)/index.js @@ -0,0 +1,24 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * @module browser-fs-access + */ +export { fileOpen } from './file-open.mjs'; +export { directoryOpen } from './directory-open.mjs'; +export { fileSave } from './file-save.mjs'; +export { default as supported } from './supported.mjs'; diff --git a/extlib/src (copy)/legacy/directory-open.mjs b/extlib/src (copy)/legacy/directory-open.mjs new file mode 100644 index 0000000..e58a870 --- /dev/null +++ b/extlib/src (copy)/legacy/directory-open.mjs @@ -0,0 +1,58 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * Opens a directory from disk using the legacy + * `` method. + * @type { typeof import("../../index").directoryOpen } + */ +export default async (options = {}) => { + options.recursive = options.recursive || false; + return new Promise((resolve, reject) => { + const input = document.createElement('input'); + input.type = 'file'; + input.webkitdirectory = true; + + // ToDo: Remove this workaround once + // https://github.com/whatwg/html/issues/6376 is specified and supported. + const rejectOnPageInteraction = () => { + window.removeEventListener('pointermove', rejectOnPageInteraction); + window.removeEventListener('pointerdown', rejectOnPageInteraction); + window.removeEventListener('keydown', rejectOnPageInteraction); + reject(new DOMException('The user aborted a request.', 'AbortError')); + }; + + window.addEventListener('pointermove', rejectOnPageInteraction); + window.addEventListener('pointerdown', rejectOnPageInteraction); + window.addEventListener('keydown', rejectOnPageInteraction); + + input.addEventListener('change', () => { + window.removeEventListener('pointermove', rejectOnPageInteraction); + window.removeEventListener('pointerdown', rejectOnPageInteraction); + window.removeEventListener('keydown', rejectOnPageInteraction); + let files = Array.from(input.files); + if (!options.recursive) { + files = files.filter((file) => { + return file.webkitRelativePath.split('/').length === 2; + }); + } + resolve(files); + }); + + input.click(); + }); +}; diff --git a/extlib/src (copy)/legacy/file-open.mjs b/extlib/src (copy)/legacy/file-open.mjs new file mode 100644 index 0000000..cfa4f7f --- /dev/null +++ b/extlib/src (copy)/legacy/file-open.mjs @@ -0,0 +1,56 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * Opens a file from disk using the legacy `` method. + * @type { typeof import("../../index").fileOpen } + */ +export default async (options = {}) => { + return new Promise((resolve, reject) => { + const input = document.createElement('input'); + input.type = 'file'; + const accept = [ + ...(options.mimeTypes ? options.mimeTypes : []), + options.extensions ? options.extensions : [], + ].join(); + input.multiple = options.multiple || false; + // Empty string allows everything. + input.accept = accept || ''; + + // ToDo: Remove this workaround once + // https://github.com/whatwg/html/issues/6376 is specified and supported. + const rejectOnPageInteraction = () => { + window.removeEventListener('pointermove', rejectOnPageInteraction); + window.removeEventListener('pointerdown', rejectOnPageInteraction); + window.removeEventListener('keydown', rejectOnPageInteraction); + reject(new DOMException('The user aborted a request.', 'AbortError')); + }; + + window.addEventListener('pointermove', rejectOnPageInteraction); + window.addEventListener('pointerdown', rejectOnPageInteraction); + window.addEventListener('keydown', rejectOnPageInteraction); + + input.addEventListener('change', () => { + window.removeEventListener('pointermove', rejectOnPageInteraction); + window.removeEventListener('pointerdown', rejectOnPageInteraction); + window.removeEventListener('keydown', rejectOnPageInteraction); + resolve(input.multiple ? input.files : input.files[0]); + }); + + input.click(); + }); +}; diff --git a/extlib/src (copy)/legacy/file-save.mjs b/extlib/src (copy)/legacy/file-save.mjs new file mode 100644 index 0000000..da453f7 --- /dev/null +++ b/extlib/src (copy)/legacy/file-save.mjs @@ -0,0 +1,32 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * Saves a file to disk using the legacy `` method. + * @type { typeof import("../../index").fileSave } + */ +export default async (blob, options = {}) => { + const a = document.createElement('a'); + a.download = options.fileName || 'Untitled'; + a.href = URL.createObjectURL(blob); + a.addEventListener('click', () => { + // `setTimeout()` due to + // https://github.com/LLK/scratch-gui/issues/1783#issuecomment-426286393 + setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000); + }); + a.click(); +}; diff --git a/extlib/src (copy)/supported.mjs b/extlib/src (copy)/supported.mjs new file mode 100644 index 0000000..b1ac42a --- /dev/null +++ b/extlib/src (copy)/supported.mjs @@ -0,0 +1,43 @@ +/** + * Copyright 2020 Google LLC + * + * 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. + */ +// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0. + +/** + * Returns whether the File System Access API is supported and usable in the + * current context (for example cross-origin iframes). + * @returns {boolean} Returns `true` if the File System Access API is supported and usable, else returns `false`. + */ +const supported = (() => { + // ToDo: Remove this check once Permissions Policy integration + // has happened, tracked in + // https://github.com/WICG/file-system-access/issues/245. + if ('top' in self && self !== top) { + try { + // This will succeed on same-origin iframes, + // but fail on cross-origin iframes. + top.location + ''; + } catch { + return false; + } + } else if ('chooseFileSystemEntries' in self) { + return 'chooseFileSystemEntries'; + } else if ('showOpenFilePicker' in self) { + return 'showOpenFilePicker'; + } + return false; +})(); + +export default supported; diff --git a/package-lock.json b/package-lock.json index 4bb79f3..f0fc1ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "@babel/preset-react": "^7.12.13", "@tailwindcss/jit": "^0.1.18", "babel-loader": "^8.2.2", + "browser-fs-access": "^0.16.4", "css-loader": "^5.1.3", "gh-pages": "^3.1.0", "immutability-helper": "^3.1.1", @@ -1397,6 +1398,12 @@ "node": ">=8" } }, + "node_modules/browser-fs-access": { + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.16.4.tgz", + "integrity": "sha512-c1A9Y3pHJTKPYFjwL5SXX3MZ0BQcK7He7l0csclr80SEADIFOUHUM5oJBdg49XUdlLmIFiWiE3tbr/5KcD5TsQ==", + "dev": true + }, "node_modules/browserslist": { "version": "4.16.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", @@ -9611,6 +9618,12 @@ "fill-range": "^7.0.1" } }, + "browser-fs-access": { + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.16.4.tgz", + "integrity": "sha512-c1A9Y3pHJTKPYFjwL5SXX3MZ0BQcK7He7l0csclr80SEADIFOUHUM5oJBdg49XUdlLmIFiWiE3tbr/5KcD5TsQ==", + "dev": true + }, "browserslist": { "version": "4.16.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", diff --git a/package.json b/package.json index e85352f..7fef46e 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@babel/preset-react": "^7.12.13", "@tailwindcss/jit": "^0.1.18", "babel-loader": "^8.2.2", + "browser-fs-access": "^0.16.4", "css-loader": "^5.1.3", "gh-pages": "^3.1.0", "immutability-helper": "^3.1.1", diff --git a/src/react/fileHelpers.js b/src/react/fileHelpers.js index 8fbf500..8079b95 100644 --- a/src/react/fileHelpers.js +++ b/src/react/fileHelpers.js @@ -1,3 +1,15 @@ + + +// import { +// fileOpen, +// fileSave, +// } from '../../extlib/fs/index'; + +import { + fileOpen, + fileSave, +} from 'browser-fs-access'; + // https://web.dev/file-system-access/ const link = document.createElement('a'); @@ -5,23 +17,15 @@ link.style.display = 'none'; document.body.appendChild(link); function saveLegacy(blob, filename) { - link.href = URL.createObjectURL(blob); link.download = filename; link.click(); - } - var tzoffset = (new Date()).getTimezoneOffset() * 60000; - - export function STLExport(filename) { - const result = STLexp.parse(sc.selected[0], { binary: true }); - const time = (new Date(Date.now() - tzoffset)).toISOString().slice(0, -5).replace(/:/g, '-'); - saveLegacy(new Blob([result], { type: 'model/stl' }), `${filename}_${time}.stl`); } @@ -32,47 +36,24 @@ export async function saveFile(fileHandle, file, dispatch) { return await saveFileAs(file, dispatch); } - const writable = await fileHandle.createWritable(); - await writable.write(file); - await writable.close(); + await fileSave(new Blob([file], { type: 'application/json' }), undefined, fileHandle, true) dispatch({ type: 'set-modified', status: false }) } catch (ex) { const msg = 'Unable to save file'; console.error(msg, ex); - console.log('heeeeeeeeerree') alert(msg); } }; export async function saveFileAs(file, dispatch) { - let fileHandle; - try { - const opts = { - types: [{ - description: 'Text file', - accept: { 'application/json': ['.json'] }, - }], - }; - fileHandle = await showSaveFilePicker(opts) - - - } catch (ex) { - if (ex.name === 'AbortError') { - console.log('aborted') - return; - } - const msg = 'An error occured trying to open the file.'; - console.error(msg, ex); - alert(msg); - return; - } try { - const writable = await fileHandle.createWritable(); - await writable.write(file); - await writable.close() + const fileHandle = await fileSave(new Blob([file], { type: 'application/json' }), { + fileName: 'untitled.json', + extensions: ['.json'], + }) dispatch({ type: 'set-file-handle', fileHandle, modified: false }) @@ -88,12 +69,19 @@ export async function saveFileAs(file, dispatch) { export async function openFile(dispatch) { - let fileHandle + let file - // If a fileHandle is provided, verify we have permission to read/write it, - // otherwise, show the file open prompt and allow the user to select the file. try { - fileHandle = await getFileHandle(); + + const options = { + mimeTypes: ['application/json'], + extensions: ['.json'], + multiple: false, + description: 'Part files', + }; + + file = await fileOpen(options); + } catch (ex) { if (ex.name === 'AbortError') { return; @@ -103,17 +91,11 @@ export async function openFile(dispatch) { alert(msg); } - if (!fileHandle) { - return; - } - - try { - const file = await fileHandle.getFile(); const text = await file.text();; dispatch({ type: 'restore-state', state: sc.loadState(text) }) - dispatch({ type: 'set-file-handle', fileHandle }) + dispatch({ type: 'set-file-handle', fileHandle:file.handle }) } catch (ex) { const msg = `An error occured reading ${fileHandle}`;