1
- /*
2
- * # Copyright (c) 2016-2019 The Khronos Group Inc.
3
- * #
4
- * # Licensed under the Apache License, Version 2.0 (the "License");
5
- * # you may not use this file except in compliance with the License.
6
- * # You may obtain a copy of the License at
7
- * #
8
- * # http://www.apache.org/licenses/LICENSE-2.0
9
- * #
10
- * # Unless required by applicable law or agreed to in writing, software
11
- * # distributed under the License is distributed on an "AS IS" BASIS,
12
- * # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * # See the License for the specific language governing permissions and
14
- * # limitations under the License.
15
- */
1
+ // Copyright 2016-2024 The Khronos Group Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
16
4
17
5
// ignore_for_file: avoid_print
18
6
// ignore_for_file: avoid_dynamic_calls
21
9
22
10
import 'dart:async' ;
23
11
import 'dart:convert' ;
24
- import 'dart:html' show querySelector, InputElement, File, FileReader, window;
12
+ import 'dart:html'
13
+ show
14
+ querySelector,
15
+ DataTransferItemList,
16
+ DirectoryEntry,
17
+ Entry,
18
+ InputElement,
19
+ File,
20
+ FileEntry,
21
+ FileReader,
22
+ window;
25
23
import 'dart:js' ;
26
24
import 'dart:math' ;
27
25
@@ -38,9 +36,13 @@ final _dropZone = querySelector('#dropZone');
38
36
final _output = querySelector ('#output' );
39
37
final _input = querySelector ('#input' ) as InputElement ;
40
38
final _inputLink = querySelector ('#inputLink' );
39
+ final _fileWarning = querySelector ('#fileWarning' );
41
40
final _truncatedWarning = querySelector ('#truncatedWarning' );
42
41
final _validityLabel = querySelector ('#validityLabel' );
43
42
43
+ final _isFileUri = window.location.protocol == 'file:' ;
44
+ final _assetPattern = RegExp (r'^[^\/]*\.gl(?:tf|b)$' , caseSensitive: false );
45
+
44
46
final _sw = Stopwatch ();
45
47
46
48
void main () {
@@ -64,7 +66,13 @@ void main() {
64
66
65
67
_dropZone.onDrop.listen ((e) {
66
68
e.preventDefault ();
67
- _validate (e.dataTransfer.files);
69
+ // File and Directory Entries API may not work
70
+ // when the page is opened from a local path.
71
+ if (_isFileUri) {
72
+ _validateFiles (e.dataTransfer.files);
73
+ } else {
74
+ _validateItems (e.dataTransfer.items);
75
+ }
68
76
});
69
77
70
78
_inputLink.onClick.listen ((e) {
@@ -76,66 +84,101 @@ void main() {
76
84
_input.onChange.listen ((e) {
77
85
e.preventDefault ();
78
86
if (_input.files.isNotEmpty) {
79
- _validate (_input.files);
87
+ _validateFiles (_input.files);
80
88
}
81
89
});
82
90
83
91
print ('glTF Validator ver. $kGltfValidatorVersion .' );
84
92
print ('Supported extensions: ${Context .defaultExtensionNames .join (', ' )}' );
85
93
}
86
94
87
- void _validate ( List < File > files ) {
95
+ void _preValidate ( ) {
88
96
_output.text = '' ;
97
+ _fileWarning.style.display = 'none' ;
89
98
_truncatedWarning.style.display = 'none' ;
90
99
_validityLabel.text = 'Validating...' ;
91
100
_dropZone.classes
92
101
..clear ()
93
102
..add ('drop' );
103
+ }
94
104
95
- _doValidate (files). then (( result) {
96
- _dropZone.classes.remove ('drop' );
97
- if (result != null ) {
98
- if (result.context.isTruncated ) {
99
- _truncatedWarning .style.display = 'block' ;
100
- }
105
+ void _postValidate ( ValidationResult result) {
106
+ _dropZone.classes.remove ('drop' );
107
+ if (result != null ) {
108
+ if (_isFileUri ) {
109
+ _fileWarning .style.display = 'block' ;
110
+ }
101
111
102
- if (result.context.errors.isEmpty ) {
103
- _dropZone.classes. add ( 'valid' ) ;
104
- _validityLabel.text = 'The asset is valid.' ;
105
- } else {
106
- _dropZone.classes. add ( 'invalid' );
107
- _validityLabel.text = 'The asset contains errors.' ;
108
- }
112
+ if (result.context.isTruncated ) {
113
+ _truncatedWarning.style.display = 'block' ;
114
+ }
115
+
116
+ if (result.context.errors.isEmpty) {
117
+ _dropZone.classes. add ( 'valid' ) ;
118
+ _validityLabel.text = 'The asset is valid.' ;
109
119
} else {
110
- _validityLabel.text = 'No glTF asset provided.' ;
120
+ _dropZone.classes.add ('invalid' );
121
+ _validityLabel.text = 'The asset contains errors.' ;
111
122
}
112
- });
123
+ } else {
124
+ _validityLabel.text =
125
+ 'No glTF asset was found or a file access error has occurred.' ;
126
+ }
127
+ }
128
+
129
+ void _validateFiles (List <File > files) {
130
+ _preValidate ();
131
+ final filesMap = < String , File > {for (final file in files) file.name: file};
132
+ _doValidate (filesMap).then (_postValidate);
113
133
}
114
134
115
- Future <ValidationResult > _doValidate (List <File > files) async {
135
+ void _validateItems (DataTransferItemList items) {
136
+ _preValidate ();
137
+ _getFilesMapFromItems (items)
138
+ .then (_doValidate, onError: (Object _) => null )
139
+ .then (_postValidate);
140
+ }
141
+
142
+ Future <Map <String , File >> _getFilesMapFromItems (DataTransferItemList items) {
143
+ final entries = List .generate (items.length, (i) => items[i].getAsEntry (),
144
+ growable: false );
145
+ return _traverseDirectory (entries, < String , File > {});
146
+ }
147
+
148
+ Future <Map <String , File >> _traverseDirectory (
149
+ List <Entry > entries, Map <String , File > result) async {
150
+ for (final entry in entries) {
151
+ if (entry.isFile) {
152
+ final fileEntry = entry as FileEntry ;
153
+ result[fileEntry.fullPath.substring (1 )] = await fileEntry.file ();
154
+ } else if (entry.isDirectory) {
155
+ final directoryEntry = entry as DirectoryEntry ;
156
+ await _traverseDirectory (
157
+ await directoryEntry.createReader ().readEntries (), result);
158
+ }
159
+ }
160
+ return result;
161
+ }
162
+
163
+ Future <ValidationResult > _doValidate (Map <String , File > files) async {
116
164
_sw
117
165
..reset ()
118
166
..start ();
119
- File gltfFile;
120
167
GltfReader reader;
121
168
122
169
final context =
123
170
Context (options: ValidationOptions (maxIssues: _kMaxIssuesCount));
124
171
125
- for (gltfFile in files) {
126
- final lowerCaseName = gltfFile.name.toLowerCase ();
127
- if (lowerCaseName.endsWith ('.gltf' )) {
128
- reader = GltfJsonReader (_getFileStream (gltfFile), context);
129
- break ;
130
- }
131
- if (lowerCaseName.endsWith ('.glb' )) {
132
- reader = GlbReader (_getFileStream (gltfFile), context);
133
- break ;
134
- }
172
+ final assetFilename =
173
+ files.keys.firstWhere (_assetPattern.hasMatch, orElse: () => null );
174
+ if (assetFilename == null ) {
175
+ return null ;
135
176
}
136
177
137
- if (reader == null ) {
138
- return null ;
178
+ if (assetFilename.toLowerCase ().endsWith ('.gltf' )) {
179
+ reader = GltfJsonReader (_getFileStream (files[assetFilename]), context);
180
+ } else {
181
+ reader = GlbReader (_getFileStream (files[assetFilename]), context);
139
182
}
140
183
141
184
final readerResult = await reader.read ();
@@ -149,7 +192,7 @@ Future<ValidationResult> _doValidate(List<File> files) async {
149
192
}
150
193
final file = _getFileByUri (files, uri);
151
194
if (file != null ) {
152
- return _getFile (file);
195
+ return _getFileBytes (file);
153
196
} else {
154
197
throw GltfExternalResourceNotFoundException (uri.toString ());
155
198
}
@@ -174,7 +217,7 @@ Future<ValidationResult> _doValidate(List<File> files) async {
174
217
await resourcesLoader.load ();
175
218
}
176
219
final validationResult =
177
- ValidationResult (Uri .parse (gltfFile.name ), context, readerResult);
220
+ ValidationResult (Uri .parse (assetFilename ), context, readerResult);
178
221
179
222
_sw.stop ();
180
223
print ('Validation: ${_sw .elapsedMilliseconds }ms.' );
@@ -188,10 +231,8 @@ Future<ValidationResult> _doValidate(List<File> files) async {
188
231
return validationResult;
189
232
}
190
233
191
- File _getFileByUri (List <File > files, Uri uri) {
192
- final fileName = Uri .decodeComponent (uri.path);
193
- return files.firstWhere ((file) => file.name == fileName, orElse: () => null );
194
- }
234
+ File _getFileByUri (Map <String , File > files, Uri uri) =>
235
+ files[Uri .decodeComponent (uri.path)];
195
236
196
237
Stream <Uint8List > _getFileStream (File file) {
197
238
var isCanceled = false ;
@@ -227,7 +268,7 @@ Stream<Uint8List> _getFileStream(File file) {
227
268
return controller.stream;
228
269
}
229
270
230
- Future <Uint8List > _getFile (File file) async {
271
+ Future <Uint8List > _getFileBytes (File file) async {
231
272
final fileReader = FileReader ()..readAsArrayBuffer (file);
232
273
await fileReader.onLoadEnd.first;
233
274
final result = fileReader.result;
@@ -241,8 +282,7 @@ void _writeMap(Map<String, Object> jsonMap) {
241
282
final report = _kJsonEncoder.convert (jsonMap);
242
283
_output.text = report;
243
284
if (report.length < _kMaxReportLength) {
244
- context['Prism' ]
245
- .callMethod ('highlightAll' , [window.location.protocol != 'file:' ]);
285
+ context['Prism' ].callMethod ('highlightAll' , [! _isFileUri]);
246
286
} else {
247
287
print ('Report is too big: ${report .length } bytes. '
248
288
'Syntax highlighting disabled.' );
0 commit comments