Skip to content

Commit e69ff15

Browse files
author
Junhui Tong
committed
use stdin/stdout pipe for excel js communication instead of files
add navigation and review in the target excel more stable ws connection using request ID more stable excel js connection by blocking multiple requests so that most clean up code are removed. cleanup become more natural
1 parent 0b93029 commit e69ff15

File tree

10 files changed

+422
-122
lines changed

10 files changed

+422
-122
lines changed

api.ts

+27-19
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,33 @@
11
export const api = {
22
launchExcel: async function () {
3-
return await callAPI('launchExcel', arguments) as string
3+
return await callAPI('launchExcel', ...arguments) as string
44
},
55
getActiveExcelRow: async function () {
6-
return await callAPI('getActiveExcelRow', arguments) as {
6+
return await callAPI('getActiveExcelRow', ...arguments) as {
7+
result: 'ExcelNotRunning'|'SheetNotReady'|'ExcelTempError'|'Success',
78
fileName: string,
89
sheetName: string,
9-
row: string,
10+
row: number,
1011
headings: string[],
1112
data: string[]
1213
}
14+
},
15+
reviewActiveExcelRow: async function (col: number /* 1 based index*/, value: string) {
16+
return await callAPI('reviewActiveExcelRow', ...arguments) as boolean
17+
},
18+
gotoRow: async function (row: number) {
19+
return await callAPI('gotoRow', ...arguments) as boolean
20+
},
21+
navigateRow: async function (offset: number) {
22+
return await callAPI('navigateRow', ...arguments) as boolean
1323
}
1424
}
1525

1626
export type BackendAPI = typeof api
1727

18-
async function callAPI_http(cmd:string, ...args:any[]){
19-
const proto = window.location.protocol
20-
const host = window.location.hostname
21-
const params = new URLSearchParams(window.location.search)
22-
const port = params.get('apiPort') || '22311'
23-
const resp = await fetch(`${proto}//${host}:${port}/api?cmd=${cmd}&args=${encodeURIComponent(JSON.stringify(args))}`)
24-
return await resp.json()
25-
}
26-
27-
// call api using web socket
28-
export async function callAPI(cmd: string, ...args: any[]) {
29-
const ws = await getWebSocket()
30-
ws.send(JSON.stringify({ cmd, args }))
31-
return new Promise(resolve => ws.onmessage = e => resolve(JSON.parse(e.data)))
32-
}
33-
3428
let ws: WebSocket | null = null
29+
let requestID = 0
30+
const pendingPromises = new Map<number, (value: any) => void>()
3531
async function getWebSocket() {
3632
if (!ws) {
3733
const proto = window.location.protocol
@@ -40,7 +36,19 @@ async function getWebSocket() {
4036
const port = params.get('apiPort') || '22311'
4137
ws = new WebSocket(`${proto === 'https:' ? 'wss:' : 'ws:'}//${host}:${port}`)
4238
ws.onclose = () => { close() }
39+
ws.onmessage = e => {
40+
const { id, result } = JSON.parse(e.data)
41+
pendingPromises.get(id)!(result)
42+
pendingPromises.delete(id)
43+
}
4344
await new Promise(resolve => ws!.onopen = resolve)
4445
}
4546
return ws
4647
}
48+
49+
// call api using web socket
50+
export async function callAPI(cmd: string, ...args: any[]) {
51+
const ws = await getWebSocket()
52+
ws.send(JSON.stringify({ id: ++requestID, cmd, args }))
53+
return new Promise(resolve => pendingPromises.set(requestID, resolve))
54+
}

backend/api_impl.ts

+9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ export const apiImpl: BackendAPI = {
88
},
99
getActiveExcelRow: async () => {
1010
return await et.getActiveExcelRow()
11+
},
12+
reviewActiveExcelRow: async (col: number, value: string) => {
13+
return await et.setActiveExcelRowValue(col, value)
14+
},
15+
gotoRow: async (row: number) => {
16+
return await et.gotoRow(row)
17+
},
18+
navigateRow: async (offset: number) => {
19+
return await et.navigateRow(offset)
1120
}
1221
}
1322

debug.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export let debug = false
2+
export function setDebug(d: boolean) {
3+
debug = d
4+
}

dwa/dwa_service.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {typeByExtension} from 'jsr:@std/[email protected]'
22
import { extname } from 'jsr:@std/[email protected]'
33
import staticAssets from '../static_assets.json' with { type: "json" }
44
import * as enc from 'jsr:@std/[email protected]'
5+
import {debug} from '../debug.ts'
56

67
const clients: WebSocket[] = []
78
let server: Deno.HttpServer | null = null
@@ -11,22 +12,26 @@ export function startDenoWebAppService(root: string, port: number, apiImpl: {[ke
1112
// handle websocket connection
1213
if (req.headers.get("upgrade") === "websocket") {
1314
const { socket, response } = Deno.upgradeWebSocket(req);
15+
let closeTimer = 0
1416
socket.onopen = () => {
1517
console.log("socket opened");
18+
clearTimeout(closeTimer)
1619
clients.push(socket)
1720
}
1821
socket.onmessage = async (e) => {
19-
const {cmd, args} = JSON.parse(e.data)
22+
if (debug) console.log("socket message", e.data);
23+
const {id, cmd, args} = JSON.parse(e.data)
2024
try {
25+
let result = `unknown command: ${cmd}`
2126
if (cmd in apiImpl) {
2227
const func = apiImpl[cmd as keyof typeof apiImpl]
23-
const result = await func.apply(apiImpl, args)
24-
socket.send(JSON.stringify(result))
25-
} else {
26-
socket.send(`'invalid command ${cmd}'`)
28+
result = await func.apply(apiImpl, args)
2729
}
30+
// console.log('sending response:', result)
31+
socket.send(JSON.stringify({id, result}))
2832
} catch (_e) {
29-
// ignore
33+
console.error(_e)
34+
socket.send(JSON.stringify({id, result:`error: ${_e}`}))
3035
}
3136
}
3237
socket.onclose = () => {
@@ -35,7 +40,7 @@ export function startDenoWebAppService(root: string, port: number, apiImpl: {[ke
3540
if (i >= 0) {
3641
clients.splice(i, 1)
3742
}
38-
setTimeout(() => {
43+
closeTimer = setTimeout(() => {
3944
if (clients.length === 0) {
4045
console.log('no more clients, shutting down server')
4146
ac.abort()
@@ -89,5 +94,5 @@ export function startDenoWebAppService(root: string, port: number, apiImpl: {[ke
8994
}
9095

9196
export function stopDenoWebAppService() {
92-
ac.abort()
97+
clients.forEach(c => c.close())
9398
}

excel.js

+109-45
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,129 @@
11
// deno-lint-ignore-file no-var no-inner-declarations
22

3-
function WriteTextFile (text, path, encoding) {
4-
var stream = new ActiveXObject('ADODB.Stream');
5-
stream.Type = 2;
6-
stream.Mode = 3;
7-
if (encoding) stream.Charset = encoding;
8-
stream.Open();
9-
stream.Position = 0;
10-
stream.WriteText(text);
11-
stream.SaveToFile(path, 2);
12-
stream.Close();
13-
}
14-
15-
function saveActiveRow(excel, path) {
3+
function saveActiveRow(excel) {
164
var sheet = excel.ActiveSheet;
175
if (sheet) {
186
// get max column that have data
197
var maxColumn = Math.min(sheet.UsedRange.Columns.Count, 100);
208
// get active range
219
var cell = excel.ActiveCell;
2210
if (cell) {
23-
var activeRow = cell.Row;
24-
var rowValues = []
25-
var headings = []
26-
for (var i = 1; i <= maxColumn; i++) {
27-
var value = sheet.Cells(activeRow, i).Value;
28-
rowValues.push(value);
29-
var heading = sheet.Cells(1, i).Value;
30-
headings.push(heading);
31-
}
32-
// get excel file name
33-
var fileName = excel.ActiveWorkbook.FullName;
34-
// get sheet name
35-
var sheetName = sheet.Name;
36-
// get active row number
37-
var headingsStr = headings.join('_@@HS@@_');
38-
var rowValuesStr = rowValues.join('_@@VS@@_');
39-
var content = [fileName, sheetName, activeRow, headingsStr, rowValuesStr].join('_@@RS@@_');
40-
// WScript.Echo(content);
41-
WriteTextFile(content, path, 'utf-8');
11+
var activeRow = cell.Row;
12+
var rowValues = []
13+
var headings = []
14+
for (var i = 1; i <= maxColumn; i++) {
15+
var value = sheet.Cells(activeRow, i).Value;
16+
rowValues.push(value);
17+
var heading = sheet.Cells(1, i).Value;
18+
headings.push(heading);
19+
}
20+
// get excel file name
21+
var fileName = excel.ActiveWorkbook.FullName;
22+
// get sheet name
23+
var sheetName = sheet.Name;
24+
// get active row number
25+
var headingsStr = headings.join('_@@HS@@_');
26+
var rowValuesStr = rowValues.join('_@@VS@@_');
27+
var content = [fileName, sheetName, activeRow, headingsStr, rowValuesStr].join('_@@RS@@_');
28+
WScript.Echo('CCE:' + encodeStr(content));
29+
// WScript.Echo(content);
30+
return
4231
}
4332
}
33+
WScript.Echo('');
4434
}
4535

46-
var excel = new ActiveXObject('Excel.Application');
47-
excel.Visible = true;
36+
function updateActiveRow(col, value) {
37+
var sheet = excel.ActiveSheet;
38+
if (sheet) {
39+
var cell = excel.ActiveCell;
40+
if (cell) {
41+
var activeRow = cell.Row;
42+
sheet.Cells(activeRow, col).Value = value;
43+
}
44+
}
45+
}
4846

49-
var infoFile = WScript.Arguments(0);
47+
function gotoRow(row) {
48+
var sheet = excel.ActiveSheet;
49+
if (sheet) {
50+
sheet.Cells(row, 1).Select();
51+
}
52+
}
5053

51-
for (;;) {
52-
var cmd = WScript.StdIn.ReadLine();
53-
if (cmd == "exit") {
54-
WScript.Echo("excel sync: received exit");
55-
break;
54+
function navigateRow(offset) {
55+
var sheet = excel.ActiveSheet;
56+
if (sheet) {
57+
var cell = excel.ActiveCell;
58+
if (cell) {
59+
var activeRow = cell.Row;
60+
var newRow = activeRow + offset;
61+
// keep current column
62+
sheet.Cells(newRow, cell.Column).Select();
63+
}
5664
}
65+
}
5766

58-
try {
59-
saveActiveRow(excel, infoFile);
60-
} catch (e) {
61-
WScript.Echo(e.message);
67+
var excel
68+
69+
function main() {
70+
excel = new ActiveXObject('Excel.Application');
71+
excel.Visible = true;
72+
73+
for (;;) {
74+
var arr = WScript.StdIn.ReadLine().split(' ');
75+
var cmd = arr[0];
76+
var args = arr.slice(1);
77+
78+
if (cmd == "exit") {
79+
WScript.Echo("ok");
80+
break;
81+
}
82+
83+
try {
84+
if (cmd === 'getActiveRow') {
85+
try {
86+
saveActiveRow(excel);
87+
} catch (e) {
88+
WScript.Echo('Error: ' + e.message);
89+
}
90+
} else if (cmd === 'updateActiveRow') {
91+
var col = parseInt(args[0]);
92+
var value = args[1];
93+
updateActiveRow(col, value);
94+
WScript.Echo("done");
95+
} else if (cmd === 'gotoRow') {
96+
var row = parseInt(args[0]);
97+
gotoRow(row);
98+
WScript.Echo("done");
99+
} else if (cmd === 'navigateRow') {
100+
var offset = parseInt(args[0]);
101+
navigateRow(offset);
102+
WScript.Echo("done");
103+
} else if (cmd === 'test') {
104+
WScript.Echo("excel sync: test");
105+
WScript.Echo(encodeStr(excel.ActiveCell.Value));
106+
} else if (cmd === 'eval') {
107+
WScript.Echo(eval(args.join(' ')));
108+
}
109+
} catch (e) {
110+
WScript.Echo(e.message);
111+
}
62112
}
63113
}
64114

65-
excel.Quit();
115+
function encodeStr(s) {
116+
var e = []
117+
for (var i = 0; i < s.length; i++) {
118+
e.push(s.charCodeAt(i));
119+
}
120+
return e.join(",");
121+
}
122+
123+
try {
124+
main();
125+
} catch (e) {
126+
WScript.Echo('Error: ' + e.message);
127+
}
128+
129+
excel.Quit()

0 commit comments

Comments
 (0)