@lvce-editor/preview-worker 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/previewWorkerMain.js +226 -37
- package/package.json +2 -2
|
@@ -1293,6 +1293,14 @@ const terminate = () => {
|
|
|
1293
1293
|
globalThis.close();
|
|
1294
1294
|
};
|
|
1295
1295
|
|
|
1296
|
+
const createSandboxRpc = async () => {
|
|
1297
|
+
const sandboxRpc = await LazyTransferMessagePortRpcParent.create({
|
|
1298
|
+
commandMap: {},
|
|
1299
|
+
send: async port => await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToPreviewSandBoxWorker', port, 'SandBox.handleMessagePort')
|
|
1300
|
+
});
|
|
1301
|
+
return sandboxRpc;
|
|
1302
|
+
};
|
|
1303
|
+
|
|
1296
1304
|
const {
|
|
1297
1305
|
get: get$1,
|
|
1298
1306
|
getCommandIds,
|
|
@@ -1303,7 +1311,8 @@ const {
|
|
|
1303
1311
|
wrapGetter
|
|
1304
1312
|
} = create$1();
|
|
1305
1313
|
|
|
1306
|
-
const create = (uid, uri, x, y, width, height, platform, assetDir) => {
|
|
1314
|
+
const create = async (uid, uri, x, y, width, height, platform, assetDir) => {
|
|
1315
|
+
const sandboxRpc = await createSandboxRpc();
|
|
1307
1316
|
const state = {
|
|
1308
1317
|
assetDir,
|
|
1309
1318
|
content: '',
|
|
@@ -1314,9 +1323,11 @@ const create = (uid, uri, x, y, width, height, platform, assetDir) => {
|
|
|
1314
1323
|
parsedDom: [],
|
|
1315
1324
|
parsedNodesChildNodeCount: 0,
|
|
1316
1325
|
platform,
|
|
1326
|
+
sandboxRpc,
|
|
1317
1327
|
scripts: [],
|
|
1318
1328
|
uid,
|
|
1319
1329
|
uri,
|
|
1330
|
+
useSandboxWorker: false,
|
|
1320
1331
|
warningCount: 0
|
|
1321
1332
|
};
|
|
1322
1333
|
set$2(uid, state, state);
|
|
@@ -2121,11 +2132,26 @@ const serialize = (document, elementMap) => {
|
|
|
2121
2132
|
};
|
|
2122
2133
|
};
|
|
2123
2134
|
|
|
2124
|
-
const
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
}
|
|
2135
|
+
const handleClickSandbox = async (state, hdId) => {
|
|
2136
|
+
const {
|
|
2137
|
+
sandboxRpc,
|
|
2138
|
+
uid
|
|
2139
|
+
} = state;
|
|
2140
|
+
await sandboxRpc.invoke('SandBox.handleClick', uid, hdId);
|
|
2141
|
+
const serialized = await sandboxRpc.invoke('SandBox.getSerializedDom', uid);
|
|
2142
|
+
const parsedDom = serialized.dom;
|
|
2143
|
+
const {
|
|
2144
|
+
css
|
|
2145
|
+
} = serialized;
|
|
2146
|
+
const parsedNodesChildNodeCount = getParsedNodesChildNodeCount(parsedDom);
|
|
2147
|
+
return {
|
|
2148
|
+
...state,
|
|
2149
|
+
css,
|
|
2150
|
+
parsedDom,
|
|
2151
|
+
parsedNodesChildNodeCount
|
|
2152
|
+
};
|
|
2153
|
+
};
|
|
2154
|
+
const handleClickLocal = (state, hdId) => {
|
|
2129
2155
|
const happyDomInstance = get(state.uid);
|
|
2130
2156
|
if (!happyDomInstance) {
|
|
2131
2157
|
return state;
|
|
@@ -2134,16 +2160,9 @@ const handleClick = (state, hdId) => {
|
|
|
2134
2160
|
if (!element) {
|
|
2135
2161
|
return state;
|
|
2136
2162
|
}
|
|
2137
|
-
|
|
2138
|
-
// console.log({ element })
|
|
2139
|
-
// Dispatch click event in happy-dom so event listeners fire
|
|
2140
2163
|
dispatchClickEvent(element, happyDomInstance.window);
|
|
2141
|
-
|
|
2142
|
-
// Re-serialize the (potentially mutated) DOM
|
|
2143
2164
|
const elementMap = new Map();
|
|
2144
2165
|
const serialized = serialize(happyDomInstance.document, elementMap);
|
|
2145
|
-
|
|
2146
|
-
// Update happy-dom state with new element map
|
|
2147
2166
|
set$1(state.uid, {
|
|
2148
2167
|
document: happyDomInstance.document,
|
|
2149
2168
|
elementMap,
|
|
@@ -2161,6 +2180,15 @@ const handleClick = (state, hdId) => {
|
|
|
2161
2180
|
parsedNodesChildNodeCount
|
|
2162
2181
|
};
|
|
2163
2182
|
};
|
|
2183
|
+
const handleClick = (state, hdId) => {
|
|
2184
|
+
if (!hdId) {
|
|
2185
|
+
return state;
|
|
2186
|
+
}
|
|
2187
|
+
if (state.useSandboxWorker) {
|
|
2188
|
+
return handleClickSandbox(state, hdId);
|
|
2189
|
+
}
|
|
2190
|
+
return handleClickLocal(state, hdId);
|
|
2191
|
+
};
|
|
2164
2192
|
|
|
2165
2193
|
const None = 0;
|
|
2166
2194
|
const OpeningAngleBracket = 1;
|
|
@@ -90456,11 +90484,51 @@ const getTopLevelFunctionNames = script => {
|
|
|
90456
90484
|
return names;
|
|
90457
90485
|
};
|
|
90458
90486
|
|
|
90487
|
+
const createLocalStorage = () => {
|
|
90488
|
+
const store = new Map();
|
|
90489
|
+
const getItem = key => {
|
|
90490
|
+
if (store.has(key)) {
|
|
90491
|
+
return store.get(key);
|
|
90492
|
+
}
|
|
90493
|
+
return null;
|
|
90494
|
+
};
|
|
90495
|
+
const setItem = (key, value) => {
|
|
90496
|
+
store.set(key, String(value));
|
|
90497
|
+
};
|
|
90498
|
+
const removeItem = key => {
|
|
90499
|
+
store.delete(key);
|
|
90500
|
+
};
|
|
90501
|
+
const clear = () => {
|
|
90502
|
+
store.clear();
|
|
90503
|
+
};
|
|
90504
|
+
const key = index => {
|
|
90505
|
+
const keys = [...store.keys()];
|
|
90506
|
+
if (index < 0 || index >= keys.length) {
|
|
90507
|
+
return null;
|
|
90508
|
+
}
|
|
90509
|
+
return keys[index];
|
|
90510
|
+
};
|
|
90511
|
+
const localStorage = {
|
|
90512
|
+
clear,
|
|
90513
|
+
getItem,
|
|
90514
|
+
key,
|
|
90515
|
+
get length() {
|
|
90516
|
+
return store.size;
|
|
90517
|
+
},
|
|
90518
|
+
removeItem,
|
|
90519
|
+
setItem
|
|
90520
|
+
};
|
|
90521
|
+
return localStorage;
|
|
90522
|
+
};
|
|
90523
|
+
|
|
90459
90524
|
/* eslint-disable @typescript-eslint/no-implied-eval */
|
|
90460
90525
|
const executeScripts = (window, document, scripts) => {
|
|
90461
90526
|
window.alert = alert;
|
|
90462
90527
|
// @ts-ignore
|
|
90463
90528
|
globalThis.alert = alert;
|
|
90529
|
+
const localStorage = createLocalStorage();
|
|
90530
|
+
// @ts-ignore
|
|
90531
|
+
globalThis.localStorage = localStorage;
|
|
90464
90532
|
// Execute each script with the happy-dom window and document as context
|
|
90465
90533
|
for (const scriptContent of scripts) {
|
|
90466
90534
|
try {
|
|
@@ -90538,8 +90606,8 @@ const updateContent = async (state, uri) => {
|
|
|
90538
90606
|
scripts
|
|
90539
90607
|
} = parseResult;
|
|
90540
90608
|
|
|
90541
|
-
// If scripts are present, execute them via happy-dom and re-serialize the DOM
|
|
90542
|
-
if (scripts.length > 0) {
|
|
90609
|
+
// If scripts are present and not using sandbox worker, execute them via happy-dom and re-serialize the DOM
|
|
90610
|
+
if (scripts.length > 0 && !state.useSandboxWorker) {
|
|
90543
90611
|
try {
|
|
90544
90612
|
const {
|
|
90545
90613
|
document: happyDomDocument,
|
|
@@ -90611,11 +90679,26 @@ const dispatchInputEvent = (element, window) => {
|
|
|
90611
90679
|
dispatchEvent(element, inputEvent);
|
|
90612
90680
|
};
|
|
90613
90681
|
|
|
90614
|
-
const
|
|
90615
|
-
|
|
90616
|
-
|
|
90617
|
-
|
|
90618
|
-
}
|
|
90682
|
+
const handleInputSandbox = async (state, hdId, value) => {
|
|
90683
|
+
const {
|
|
90684
|
+
sandboxRpc,
|
|
90685
|
+
uid
|
|
90686
|
+
} = state;
|
|
90687
|
+
await sandboxRpc.invoke('SandBox.handleInput', uid, hdId, value);
|
|
90688
|
+
const serialized = await sandboxRpc.invoke('SandBox.getSerializedDom', uid);
|
|
90689
|
+
const parsedDom = serialized.dom;
|
|
90690
|
+
const {
|
|
90691
|
+
css
|
|
90692
|
+
} = serialized;
|
|
90693
|
+
const parsedNodesChildNodeCount = getParsedNodesChildNodeCount(parsedDom);
|
|
90694
|
+
return {
|
|
90695
|
+
...state,
|
|
90696
|
+
css,
|
|
90697
|
+
parsedDom,
|
|
90698
|
+
parsedNodesChildNodeCount
|
|
90699
|
+
};
|
|
90700
|
+
};
|
|
90701
|
+
const handleInputLocal = (state, hdId, value) => {
|
|
90619
90702
|
const happyDomInstance = get(state.uid);
|
|
90620
90703
|
if (!happyDomInstance) {
|
|
90621
90704
|
return state;
|
|
@@ -90624,18 +90707,10 @@ const handleInput = (state, hdId, value) => {
|
|
|
90624
90707
|
if (!element) {
|
|
90625
90708
|
return state;
|
|
90626
90709
|
}
|
|
90627
|
-
|
|
90628
|
-
// console.log({ element })
|
|
90629
|
-
// Update the element's value from the preview
|
|
90630
90710
|
element.value = value;
|
|
90631
|
-
// Dispatch input event in happy-dom so event listeners fire
|
|
90632
90711
|
dispatchInputEvent(element, happyDomInstance.window);
|
|
90633
|
-
|
|
90634
|
-
// Re-serialize the (potentially mutated) DOM
|
|
90635
90712
|
const elementMap = new Map();
|
|
90636
90713
|
const serialized = serialize(happyDomInstance.document, elementMap);
|
|
90637
|
-
|
|
90638
|
-
// Update happy-dom state with new element map
|
|
90639
90714
|
set$1(state.uid, {
|
|
90640
90715
|
document: happyDomInstance.document,
|
|
90641
90716
|
elementMap,
|
|
@@ -90653,6 +90728,15 @@ const handleInput = (state, hdId, value) => {
|
|
|
90653
90728
|
parsedNodesChildNodeCount
|
|
90654
90729
|
};
|
|
90655
90730
|
};
|
|
90731
|
+
const handleInput = (state, hdId, value) => {
|
|
90732
|
+
if (!hdId) {
|
|
90733
|
+
return state;
|
|
90734
|
+
}
|
|
90735
|
+
if (state.useSandboxWorker) {
|
|
90736
|
+
return handleInputSandbox(state, hdId, value);
|
|
90737
|
+
}
|
|
90738
|
+
return handleInputLocal(state, hdId, value);
|
|
90739
|
+
};
|
|
90656
90740
|
|
|
90657
90741
|
const dispatchKeydownEvent = (element, window, key, code) => {
|
|
90658
90742
|
const keydownEvent = new window.KeyboardEvent('keydown', {
|
|
@@ -90663,7 +90747,26 @@ const dispatchKeydownEvent = (element, window, key, code) => {
|
|
|
90663
90747
|
dispatchEvent(element, keydownEvent);
|
|
90664
90748
|
};
|
|
90665
90749
|
|
|
90666
|
-
const
|
|
90750
|
+
const handleKeydownSandbox = async (state, hdId, key, code) => {
|
|
90751
|
+
const {
|
|
90752
|
+
sandboxRpc,
|
|
90753
|
+
uid
|
|
90754
|
+
} = state;
|
|
90755
|
+
await sandboxRpc.invoke('SandBox.handleKeyDown', uid, hdId, key, code);
|
|
90756
|
+
const serialized = await sandboxRpc.invoke('SandBox.getSerializedDom', uid);
|
|
90757
|
+
const parsedDom = serialized.dom;
|
|
90758
|
+
const {
|
|
90759
|
+
css
|
|
90760
|
+
} = serialized;
|
|
90761
|
+
const parsedNodesChildNodeCount = getParsedNodesChildNodeCount(parsedDom);
|
|
90762
|
+
return {
|
|
90763
|
+
...state,
|
|
90764
|
+
css,
|
|
90765
|
+
parsedDom,
|
|
90766
|
+
parsedNodesChildNodeCount
|
|
90767
|
+
};
|
|
90768
|
+
};
|
|
90769
|
+
const handleKeydownLocal = (state, hdId, key, code) => {
|
|
90667
90770
|
const happyDomInstance = get(state.uid);
|
|
90668
90771
|
if (!happyDomInstance) {
|
|
90669
90772
|
return state;
|
|
@@ -90672,15 +90775,73 @@ const handleKeydown = (state, hdId, key, code) => {
|
|
|
90672
90775
|
if (!element) {
|
|
90673
90776
|
return state;
|
|
90674
90777
|
}
|
|
90675
|
-
|
|
90676
|
-
// Dispatch keydown event in happy-dom so event listeners fire
|
|
90677
90778
|
dispatchKeydownEvent(element, happyDomInstance.window, key, code);
|
|
90678
|
-
|
|
90679
|
-
// Re-serialize the (potentially mutated) DOM
|
|
90680
90779
|
const elementMap = new Map();
|
|
90681
90780
|
const serialized = serialize(happyDomInstance.document, elementMap);
|
|
90781
|
+
set$1(state.uid, {
|
|
90782
|
+
document: happyDomInstance.document,
|
|
90783
|
+
elementMap,
|
|
90784
|
+
window: happyDomInstance.window
|
|
90785
|
+
});
|
|
90786
|
+
const parsedDom = serialized.dom;
|
|
90787
|
+
const {
|
|
90788
|
+
css
|
|
90789
|
+
} = serialized;
|
|
90790
|
+
const parsedNodesChildNodeCount = getParsedNodesChildNodeCount(parsedDom);
|
|
90791
|
+
return {
|
|
90792
|
+
...state,
|
|
90793
|
+
css,
|
|
90794
|
+
parsedDom,
|
|
90795
|
+
parsedNodesChildNodeCount
|
|
90796
|
+
};
|
|
90797
|
+
};
|
|
90798
|
+
const handleKeydown = (state, hdId, key, code) => {
|
|
90799
|
+
if (state.useSandboxWorker) {
|
|
90800
|
+
return handleKeydownSandbox(state, hdId, key, code);
|
|
90801
|
+
}
|
|
90802
|
+
return handleKeydownLocal(state, hdId, key, code);
|
|
90803
|
+
};
|
|
90804
|
+
|
|
90805
|
+
const dispatchKeyupEvent = (element, window, key, code) => {
|
|
90806
|
+
const keyupEvent = new window.KeyboardEvent('keyup', {
|
|
90807
|
+
bubbles: true,
|
|
90808
|
+
code,
|
|
90809
|
+
key
|
|
90810
|
+
});
|
|
90811
|
+
dispatchEvent(element, keyupEvent);
|
|
90812
|
+
};
|
|
90682
90813
|
|
|
90683
|
-
|
|
90814
|
+
const handleKeyupSandbox = async (state, hdId, key, code) => {
|
|
90815
|
+
const {
|
|
90816
|
+
sandboxRpc,
|
|
90817
|
+
uid
|
|
90818
|
+
} = state;
|
|
90819
|
+
await sandboxRpc.invoke('SandBox.handleKeyUp', uid, hdId, key, code);
|
|
90820
|
+
const serialized = await sandboxRpc.invoke('SandBox.getSerializedDom', uid);
|
|
90821
|
+
const parsedDom = serialized.dom;
|
|
90822
|
+
const {
|
|
90823
|
+
css
|
|
90824
|
+
} = serialized;
|
|
90825
|
+
const parsedNodesChildNodeCount = getParsedNodesChildNodeCount(parsedDom);
|
|
90826
|
+
return {
|
|
90827
|
+
...state,
|
|
90828
|
+
css,
|
|
90829
|
+
parsedDom,
|
|
90830
|
+
parsedNodesChildNodeCount
|
|
90831
|
+
};
|
|
90832
|
+
};
|
|
90833
|
+
const handleKeyupLocal = (state, hdId, key, code) => {
|
|
90834
|
+
const happyDomInstance = get(state.uid);
|
|
90835
|
+
if (!happyDomInstance) {
|
|
90836
|
+
return state;
|
|
90837
|
+
}
|
|
90838
|
+
const element = hdId ? happyDomInstance.elementMap.get(hdId) : happyDomInstance.document;
|
|
90839
|
+
if (!element) {
|
|
90840
|
+
return state;
|
|
90841
|
+
}
|
|
90842
|
+
dispatchKeyupEvent(element, happyDomInstance.window, key, code);
|
|
90843
|
+
const elementMap = new Map();
|
|
90844
|
+
const serialized = serialize(happyDomInstance.document, elementMap);
|
|
90684
90845
|
set$1(state.uid, {
|
|
90685
90846
|
document: happyDomInstance.document,
|
|
90686
90847
|
elementMap,
|
|
@@ -90698,6 +90859,12 @@ const handleKeydown = (state, hdId, key, code) => {
|
|
|
90698
90859
|
parsedNodesChildNodeCount
|
|
90699
90860
|
};
|
|
90700
90861
|
};
|
|
90862
|
+
const handleKeyup = (state, hdId, key, code) => {
|
|
90863
|
+
if (state.useSandboxWorker) {
|
|
90864
|
+
return handleKeyupSandbox(state, hdId, key, code);
|
|
90865
|
+
}
|
|
90866
|
+
return handleKeyupLocal(state, hdId, key, code);
|
|
90867
|
+
};
|
|
90701
90868
|
|
|
90702
90869
|
const loadContent = async state => {
|
|
90703
90870
|
// Try to register to receive editor change notifications from the editor worker.
|
|
@@ -90727,15 +90894,29 @@ const loadContent = async state => {
|
|
|
90727
90894
|
parsedNodesChildNodeCount: state.parsedNodesChildNodeCount,
|
|
90728
90895
|
scripts: state.scripts
|
|
90729
90896
|
};
|
|
90897
|
+
const {
|
|
90898
|
+
sandboxRpc
|
|
90899
|
+
} = state;
|
|
90900
|
+
let finalParsedDom = parsedDom;
|
|
90901
|
+
let finalCss = css;
|
|
90902
|
+
let finalParsedNodesChildNodeCount = parsedNodesChildNodeCount;
|
|
90903
|
+
if (state.useSandboxWorker && scripts.length > 0) {
|
|
90904
|
+
await sandboxRpc.invoke('SandBox.initialize', state.uid, content, scripts);
|
|
90905
|
+
const serialized = await sandboxRpc.invoke('SandBox.getSerializedDom', state.uid);
|
|
90906
|
+
finalParsedDom = serialized.dom;
|
|
90907
|
+
finalCss = serialized.css;
|
|
90908
|
+
finalParsedNodesChildNodeCount = finalParsedDom.length > 0 ? finalParsedDom[0].childCount || 0 : 0;
|
|
90909
|
+
}
|
|
90730
90910
|
return {
|
|
90731
90911
|
...state,
|
|
90732
90912
|
content,
|
|
90733
|
-
css,
|
|
90913
|
+
css: finalCss,
|
|
90734
90914
|
errorCount: 0,
|
|
90735
90915
|
errorMessage,
|
|
90736
90916
|
initial: false,
|
|
90737
|
-
parsedDom,
|
|
90738
|
-
parsedNodesChildNodeCount,
|
|
90917
|
+
parsedDom: finalParsedDom,
|
|
90918
|
+
parsedNodesChildNodeCount: finalParsedNodesChildNodeCount,
|
|
90919
|
+
sandboxRpc,
|
|
90739
90920
|
scripts,
|
|
90740
90921
|
warningCount: 1
|
|
90741
90922
|
};
|
|
@@ -90789,6 +90970,7 @@ const renderCss = (oldState, newState) => {
|
|
|
90789
90970
|
const HandleInput = 4;
|
|
90790
90971
|
const HandleClick = 11;
|
|
90791
90972
|
const HandleKeydown = 12;
|
|
90973
|
+
const HandleKeyup = 13;
|
|
90792
90974
|
|
|
90793
90975
|
const getEmptyPreviewDom = () => {
|
|
90794
90976
|
return [{
|
|
@@ -90822,6 +91004,7 @@ const getPreviewDom = state => {
|
|
|
90822
91004
|
onClick: HandleClick,
|
|
90823
91005
|
onInput: HandleInput,
|
|
90824
91006
|
onKeyDown: HandleKeydown,
|
|
91007
|
+
onKeyUp: HandleKeyup,
|
|
90825
91008
|
tabIndex: 0,
|
|
90826
91009
|
type: Div$1
|
|
90827
91010
|
}, ...parsedDom];
|
|
@@ -90832,6 +91015,7 @@ const getPreviewDom = state => {
|
|
|
90832
91015
|
onClick: HandleClick,
|
|
90833
91016
|
onInput: HandleInput,
|
|
90834
91017
|
onKeyDown: HandleKeydown,
|
|
91018
|
+
onKeyUp: HandleKeyup,
|
|
90835
91019
|
tabIndex: 0,
|
|
90836
91020
|
type: Div$1
|
|
90837
91021
|
}, {
|
|
@@ -90910,6 +91094,10 @@ const renderEventListeners = () => {
|
|
|
90910
91094
|
capture: true,
|
|
90911
91095
|
name: HandleKeydown,
|
|
90912
91096
|
params: ['handleKeyDown', 'event.target.dataset.id', 'event.key', 'event.code']
|
|
91097
|
+
}, {
|
|
91098
|
+
capture: true,
|
|
91099
|
+
name: HandleKeyup,
|
|
91100
|
+
params: ['handleKeyUp', 'event.target.dataset.id', 'event.key', 'event.code']
|
|
90913
91101
|
}];
|
|
90914
91102
|
};
|
|
90915
91103
|
|
|
@@ -90969,6 +91157,7 @@ const commandMap = {
|
|
|
90969
91157
|
'Preview.handleFileEdited': wrapCommand(handleFileEdited),
|
|
90970
91158
|
'Preview.handleInput': wrapCommand(handleInput),
|
|
90971
91159
|
'Preview.handleKeyDown': wrapCommand(handleKeydown),
|
|
91160
|
+
'Preview.handleKeyUp': wrapCommand(handleKeyup),
|
|
90972
91161
|
'Preview.loadContent': wrapCommand(loadContent),
|
|
90973
91162
|
'Preview.render2': render2,
|
|
90974
91163
|
'Preview.renderEventListeners': renderEventListeners,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lvce-editor/preview-worker",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "Preview Worker",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"type": "module",
|
|
12
12
|
"main": "dist/previewWorkerMain.js",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@lvce-editor/virtual-dom-worker": "^8.
|
|
14
|
+
"@lvce-editor/virtual-dom-worker": "^8.2.0",
|
|
15
15
|
"happy-dom-without-node": "^14.12.3"
|
|
16
16
|
}
|
|
17
17
|
}
|