Skip to content

Commit dc95111

Browse files
committed
chore(windows): polish
1 parent a7f6090 commit dc95111

23 files changed

Lines changed: 26727 additions & 26013 deletions

File tree

apps/toolbox/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@nativescript/visionos": "~9.0.0",
1818
"@nativescript/vite": "file:../../dist/packages/vite",
1919
"@nativescript/webpack": "file:../../dist/packages/webpack5",
20-
"@nativescript/windows": "0.1.0-alpha.102",
20+
"@nativescript/windows": "0.1.0-alpha.111",
2121
"typescript": "~5.8.0"
2222
}
2323
}

apps/toolbox/src/pages/css-playground.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class CssPlaygroundModel extends Observable {
3636

3737
applyCSS(args) {
3838
this.resetCSS();
39-
addTaggedAdditionalCSS(`#play { ${this.currentCSS}`, CSSTag);
39+
addTaggedAdditionalCSS(`#play { ${this.currentCSS} }`, CSSTag);
4040
playLabel._onCssStateChange();
4141
playLabel.requestLayout();
4242
}

apps/toolbox/src/pages/list-page.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
<Label text="{{ iconText }}" class="icon-label"/>
2424
</StackLayout>
2525
</android>
26+
<windows>
27+
<Label text="{{ iconText }}" class="icon-around icon-label"/>
28+
</windows>
2629
<StackLayout class="va-middle">
2730
<Label text="{{ name }}" class="component-label"></Label>
2831
</StackLayout>
@@ -47,6 +50,9 @@
4750
<Label text="{{ iconText }}" class="icon-label"/>
4851
</StackLayout>
4952
</android>
53+
<windows>
54+
<Label text="{{ iconText }}" class="icon-around icon-label"/>
55+
</windows>
5056
<StackLayout class="va-middle">
5157
<Label text="{{ name }}" class="component-label"></Label>
5258
</StackLayout>

apps/toolbox/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
"paths": {
66
"~/*": ["src/*"]
77
}
8-
}
8+
},
9+
"exclude": ["node_modules", "tmp", "platforms", "__tests__", "vite.config.ts"]
910
}

apps/toolbox/webpack.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const webpack = require("@nativescript/webpack");
2+
const path = require("path");
23

34
module.exports = (env) => {
45
webpack.init(env);
@@ -11,6 +12,11 @@ module.exports = (env) => {
1112

1213
return args
1314
})
15+
16+
// Stub Windows-unsupported packages
17+
if (env.windows) {
18+
config.resolve.alias.set('@nativescript/imagepicker', path.resolve(__dirname, 'src/stubs/imagepicker.js'));
19+
}
1420
})
1521

1622
return webpack.resolveConfig();

apps/ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"@nativescript/ios": "~9.0.0",
1616
"@nativescript/visionos": "~9.0.0",
1717
"@nativescript/webpack": "file:../../dist/packages/webpack5",
18-
"@nativescript/windows": "^0.1.0-alpha.98",
18+
"@nativescript/windows": "0.1.0-alpha.111",
1919
"typescript": "~5.8.0"
2020
},
2121
"gitHead": "8ab7726d1ee9991706069c1359c552e67ee0d1a4",

packages/core/http/http-request-internal/index.windows.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { HttpRequestOptions, HttpResponse, Headers } from '../http-interfac
22
import { HttpResponseEncoding } from '../http-interfaces';
33
import { BaseHttpContent } from '.';
44
import { addHeader } from './http-request-internal-common';
5+
import { isMainThread, dispatchToMainThread } from '../../utils/mainthread-helper';
56
export { addHeader } from './http-request-internal-common';
67

78
export function requestInternal<T extends object>(options: HttpRequestOptions, contentHandler?: T): Promise<HttpResponse<BaseHttpContent & T>> {
@@ -79,14 +80,30 @@ export function requestInternal<T extends object>(options: HttpRequestOptions, c
7980
raw: rawBuffer,
8081
requestURL: options.url,
8182
toNativeImage: (): Promise<any> => {
82-
const writer = new Windows.Storage.Streams.DataWriter();
83-
writer.WriteBytes(bytes as never);
84-
const buf = writer.DetachBuffer();
85-
const stream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
86-
return NSWinRT.toPromise((stream as any).WriteAsync(buf)).then(() => {
87-
(stream as any).Seek(0);
88-
const bmp = new (Windows as any).UI.Xaml.Media.Imaging.BitmapImage();
89-
return NSWinRT.toPromise(bmp.SetSourceAsync(stream)).then(() => bmp);
83+
return new Promise((resolve, reject) => {
84+
try {
85+
const writer = new Windows.Storage.Streams.DataWriter();
86+
writer.WriteBytes(bytes as never);
87+
const freshBuffer = writer.DetachBuffer();
88+
const stream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
89+
NSWinRT.toPromise((stream as any).WriteAsync(freshBuffer)).then(() => {
90+
const createBitmap = () => {
91+
try {
92+
(stream as any).Seek(0);
93+
const bmp = new Windows.UI.Xaml.Media.Imaging.BitmapImage();
94+
NSWinRT.toPromise(bmp.SetSourceAsync(stream)).then(
95+
() => { stream.Close(); resolve(bmp); },
96+
reject
97+
);
98+
} catch (e) { reject(e); }
99+
};
100+
if (isMainThread()) {
101+
createBitmap();
102+
} else {
103+
dispatchToMainThread(createBitmap);
104+
}
105+
}, reject);
106+
} catch (e) { reject(e); }
90107
});
91108
},
92109
toNativeString: (encoding?: HttpResponseEncoding) => {

packages/core/image-source/index.windows.ts

Lines changed: 56 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { ImageAsset } from '../image-asset';
33
import { path as fsPath, knownFolders } from '../file-system';
44
import { isFileOrResourcePath } from '../utils';
55
import { Trace } from '../trace';
6-
6+
import { isMainThread, dispatchToMainThread } from '../utils/mainthread-helper';
7+
import { requestInternal as httpRequest } from '../http/http-request-internal';
78
export { isFileOrResourcePath };
89

910
// Cached constants for resource name handling
@@ -20,13 +21,9 @@ function bitmapFromUriAsync(uriStr: string): Promise<Windows.UI.Xaml.Media.Imagi
2021
try {
2122
const bmp = makeBitmapImage();
2223
const onOpened = () => {
23-
bmp.ImageOpened = null;
24-
bmp.ImageFailed = null;
2524
resolve(bmp);
2625
};
2726
const onFailed = (s: any, e: any) => {
28-
bmp.ImageOpened = null;
29-
bmp.ImageFailed = null;
3027
reject(e || new Error('Image load failed'));
3128
};
3229
bmp.ImageOpened = onOpened;
@@ -58,28 +55,34 @@ function bytesToStream(bytes: Uint8Array): Promise<any> {
5855

5956
function bitmapFromStream(stream: any): Promise<any> {
6057
return new Promise((resolve, reject) => {
61-
try {
62-
const bmp = makeBitmapImage();
63-
NSWinRT.toPromise(bmp.SetSourceAsync(stream)).then(
64-
() => resolve(bmp),
65-
reject
66-
);
67-
} catch (e) { reject(e); }
58+
// BitmapImage has UI thread affinity — create and load on the UI thread.
59+
const create = () => {
60+
try {
61+
(stream as any).Seek(0);
62+
const bmp = makeBitmapImage();
63+
NSWinRT.toPromise(bmp.SetSourceAsync(stream)).then(
64+
() => resolve(bmp),
65+
reject
66+
);
67+
} catch (e) { reject(e); }
68+
};
69+
if (isMainThread()) {
70+
create();
71+
} else {
72+
dispatchToMainThread(create);
73+
}
6874
});
6975
}
7076

7177
function bitmapFromBytesAsync(bytes: Uint8Array): Promise<any> {
72-
try { console.log(`[Image.Windows] bitmapFromBytesAsync: bytes.length=${bytes?.length ?? 0}`); } catch (_e) { }
7378
return bytesToStream(bytes).then((stream) => {
74-
try { console.log(`[Image.Windows] bitmapFromBytesAsync: streamReady`); } catch (_e) { }
7579
return bitmapFromStream(stream);
7680
});
7781
}
7882

7983
function fetchBytesAsync(url: string): Promise<Uint8Array> {
8084
return new Promise((resolve, reject) => {
8185
try {
82-
try { console.log(`[Image.Windows] fetchBytesAsync: fetching ${url}`); } catch (_e) { }
8386
const httpClient = new Windows.Web.Http.HttpClient();
8487
const uri = new Windows.Foundation.Uri(url);
8588
NSWinRT.toPromise(httpClient.GetBufferAsync(uri)).then(
@@ -88,13 +91,12 @@ function fetchBytesAsync(url: string): Promise<Uint8Array> {
8891
const reader = Windows.Storage.Streams.DataReader.FromBuffer(buffer);
8992
const bytes = new Uint8Array(buffer.Length);
9093
reader.ReadBytes(bytes as never);
91-
try { console.log(`[Image.Windows] fetchBytesAsync: fetched ${bytes.length} bytes for ${url}`); } catch (_e) { }
9294
resolve(bytes);
93-
} catch (e) { try { console.log(`[Image.Windows] fetchBytesAsync: error reading buffer -> ${e}`); } catch (_ee) {} ; reject(e); }
95+
} catch (e) { reject(e); }
9496
},
95-
(err: any) => { try { console.log(`[Image.Windows] fetchBytesAsync: http error -> ${err}`); } catch (_e) {} ; reject(err); }
97+
(err: any) => { reject(err); }
9698
);
97-
} catch (e) { try { console.log(`[Image.Windows] fetchBytesAsync: exception -> ${e}`); } catch (_e) {} ; reject(e); }
99+
} catch (e) { reject(e); }
98100
});
99101
}
100102

@@ -198,60 +200,27 @@ export class ImageSource implements ImageSourceDefinition {
198200
}
199201

200202
static fromUrl(url: string): Promise<ImageSource> {
201-
// Use the JS-side HttpClient + bitmap creation path to avoid using
202-
// the native ImageHelper URL loader which can surface unobserved
203-
// Task exceptions in some network failure scenarios.
204-
return fetchBytesAsync(url).then((bytes) => {
205-
return bitmapFromBytesAsync(bytes).then((bmp) => {
206-
const src = new ImageSource(bmp);
207-
src._rawBytes = bytes;
208-
src._width = bmp.PixelWidth ?? 0;
209-
src._height = bmp.PixelHeight ?? 0;
210-
return src;
211-
});
212-
}).catch((err) => {
213-
try { console.log(`[Image.Windows] fromUrl: fetch/bitmap path failed -> ${err}. Trying uri-based loader fallback for ${url}`); } catch (_e) { }
214-
// Try the native Uri loader as a fallback
215-
return bitmapFromUriAsync(url).then((bmp) => {
216-
const src = new ImageSource(bmp);
217-
src._width = bmp.PixelWidth ?? 0;
218-
src._height = bmp.PixelHeight ?? 0;
219-
return src;
220-
});
221-
});
203+
return httpRequest({ url, method: 'GET' }).then((response) => response.content.toNativeImage().then((value) => new ImageSource(value)));
222204
}
223205

224206
static fromResourceSync(name: string): ImageSource {
225207
if (!name) return null as any;
226208
try {
227-
// Do not mutate the input `name`. Normalize into a local variable.
228209
const resourceName = name.startsWith(RES_PREFIX) ? name.slice(RES_PREFIX.length) : name;
229-
// Strip leading slashes using slice in a small loop (avoids regex)
230-
let normalized = resourceName;
231-
while (normalized.length && (normalized.charAt(0) === '/' || normalized.charAt(0) === '\\')) {
232-
normalized = normalized.slice(1);
233-
}
234-
// Replace backslashes with forward slashes without regex
235-
if (normalized.indexOf('\\') !== -1) {
236-
normalized = normalized.split('\\').join('/');
237-
}
210+
const normalized = resourceName.replace(LEADING_SLASHES_RE, '').replace(BACKSLASH_RE, '/');
238211
const exts = ['', '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg', '.webp'];
239-
240-
// Always map to the packaged app assets so Windows picks scale-qualified files
241-
for (const ext of exts) {
242-
try {
243-
const msAppx = `ms-appx:///Assets/${normalized}${ext}`;
244-
try { console.log(`[Image.Windows] fromResourceSync: trying ${msAppx}`); } catch (_e) { }
245-
const src = ImageSource.fromFileSync(msAppx);
246-
if (src) {
247-
try { console.log(`[Image.Windows] fromResourceSync: loaded ${msAppx}`); } catch (_e) { }
248-
return src;
212+
const resolve = (globalThis as any).__nsMsAppxResolve;
213+
if (typeof resolve === 'function') {
214+
for (const ext of exts) {
215+
const uri = `ms-appx:///Assets/${normalized}${ext}`;
216+
if (resolve(uri) != null) {
217+
return ImageSource.fromFileSync(uri);
249218
}
250-
} catch { /* ignore and try next */ }
219+
}
220+
return null as any;
251221
}
252-
try { console.log(`[Image.Windows] fromResourceSync: no resource found for ${name}`); } catch (_e) { }
253-
254-
return null as any;
222+
// Fallback when runtime hasn't registered the resolver yet
223+
return ImageSource.fromFileSync(`ms-appx:///Assets/${normalized}`);
255224
} catch {
256225
return null as any;
257226
}
@@ -264,15 +233,16 @@ export class ImageSource implements ImageSourceDefinition {
264233
const normalized = resourceName.replace(LEADING_SLASHES_RE, '').replace(BACKSLASH_RE, '/');
265234
const exts = ['', '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg', '.webp'];
266235

267-
console.log(`ImageSource.fromResource: loading resource '${name}' (normalized: '${normalized}')`);
268236
for (const ext of exts) {
269-
const msAppx = `ms-appx:///Assets/${normalized}${ext}`;
270237
try {
271-
const result = await ImageSource.fromFile(msAppx);
272-
if (result) return result;
273-
} catch (err) {
274-
try { console.log(`[Image.Windows] fromResource: failed to load ${msAppx} -> ${err}`); } catch (_e) { }
275-
}
238+
const bmp = await bitmapFromUriAsync(`ms-appx:///Assets/${normalized}${ext}`);
239+
if (bmp) {
240+
const src = new ImageSource(bmp);
241+
src._width = bmp.PixelWidth ?? 0;
242+
src._height = bmp.PixelHeight ?? 0;
243+
return src;
244+
}
245+
} catch { /* try next extension */ }
276246
}
277247

278248
return null as any;
@@ -288,8 +258,15 @@ export class ImageSource implements ImageSourceDefinition {
288258
bmp.ImageOpened = () => {
289259
src._width = bmp.PixelWidth ?? 0;
290260
src._height = bmp.PixelHeight ?? 0;
261+
//@ts-ignore
291262
bmp.ImageOpened = null;
292263
};
264+
bmp.ImageFailed = () => {
265+
//@ts-ignore
266+
bmp.ImageOpened = null;
267+
//@ts-ignore
268+
bmp.ImageFailed = null;
269+
}
293270
bmp.UriSource = new Windows.Foundation.Uri(fileUri(filePath));
294271
src.windows = bmp;
295272
return src;
@@ -354,6 +331,14 @@ export class ImageSource implements ImageSourceDefinition {
354331
}
355332
}
356333

334+
static fromSystemImageSync(name: string): ImageSource {
335+
return ImageSource.fromResourceSync(name);
336+
}
337+
338+
static fromSystemImage(name: string): Promise<ImageSource> {
339+
return ImageSource.fromResource(name);
340+
}
341+
357342
static fromFontIconCodeSync(_source: string, _font: any, _color: any): ImageSource {
358343
return null as any;
359344
}

0 commit comments

Comments
 (0)