@plasosdk/plaso-electron-sdk 1.3.14 → 1.3.15-beta.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/js/render.js CHANGED
@@ -1,420 +1,434 @@
1
- const LogFormatter = require('../scripts/logger');
2
- const path = require('path');
3
- const { getElectronRemote, parseUrlParams, getEnvironment, getDisplayMatching } = require('./util');
4
- const remote = getElectronRemote();
5
- const defaultLoggerPath = remote ? path.join(remote.app.getPath('userData'), '/P403FileTemp/') : '';
6
-
7
- const logger = new LogFormatter(defaultLoggerPath);
8
- logger.init();
9
- const getConfig = require('../scripts/getConfig');
10
-
11
- const { platform } = getConfig();
12
-
13
- const isMac = platform === 'darwin';
14
-
15
- const { ERROR_CODE, LESSON_TYPE, CLASS_WINDOW_MESG_TYPE, RENDER_TO_MAIN_MESG_TYPE, FILE_TYPE } = require('./macro');
16
-
17
- let currentWinId = null;
18
-
19
- let currentWebContentsId = null;
20
-
21
- /**
22
- * @typedef {Object} classOptionsType
23
- * @property {boolean} [debug] - 是否开启课堂窗口debug模式
24
- * @property {string} query - 进课堂的必备query
25
- * @property {string} [version] - 格式参考:1.53.901
26
- */
27
-
28
- /**
29
- * @typedef {Object} FileParams
30
- * @property {string[]} filePath - 备课文件本地地址
31
- * @property {'png' | 'pb' | string} fileType
32
- */
33
-
34
- /**
35
- * @typedef {Object} classWindowType
36
- * @property {classOptionsType} classOptions 进课堂参数对象
37
- * @property {Object} [electronWinOptions] 自定义electron的窗口参数
38
- * @property {(winId: number)=>void} [onClassWindowReadyFn] 课堂窗口打开渲染成功后的回调
39
- * @property {(winId: number)=>void} [onClassWindowLeaveFn] 课堂窗口关闭后的回调
40
- * @property {(meetingId: string)=>void} [onClassFinishedFn] 课堂结束后的回调
41
- * @property {(
42
- * params: {
43
- * fileInfo: FileParams[],
44
- * fileName?: string
45
- * },
46
- * callback: (result: boolean) => void
47
- * ) => void} [onSaveBoardFn] 保存板书,具体的保存逻辑由外部实现,取消保存板书时,callback传false, 不然传true
48
- * @property {()=>void} [onOpenResourceCenterFn] 通知外部用户打开自己的资料中心,资料中心的具体ui和逻辑由外部用户自己实现
49
- * @property {(info: any, option: any)=>Promise<string>} [onGetExtFileNameFn] 通过insertObject插入的文件传入 参数 info 时,怎么从info中获取文件的可访问地址的逻辑在用户那,所以需要函数从外部用户获取外部用户传入的文件地址
50
- * @property {(info: any, option: { suffix: string })=>Promise<string>} [onGetPreParseFileNameFn] 类似onGetExtFileNameFn,只是获取的文件地址是预解析文件地址,通过suffix得到预解析文件路径
51
- *
52
- */
53
-
54
- /**
55
- * @param {classWindowType} classWindowProps
56
- * @returns
57
- */
58
- function createClassWindow(classWindowProps) {
59
- const {
60
- classOptions,
61
- electronWinOptions,
62
- onClassWindowReadyFn,
63
- onClassWindowLeaveFn,
64
- onClassFinishedFn,
65
- onSaveBoardFn,
66
- onOpenResourceCenterFn,
67
- onGetExtFileNameFn,
68
- onGetPreParseFileNameFn,
69
- } = classWindowProps;
70
-
71
- logger.info(
72
- `课堂窗口创建信息: version is ${getVersion()}, classOptions is ${JSON.stringify(classOptions)},electronWinOptions is ${JSON.stringify(
73
- electronWinOptions,
74
- )}`,
75
- );
76
- if (currentWinId) {
77
- logger.warn('课堂窗口同时仅能存在一个');
78
- return ERROR_CODE.CLASS_WINODW_GREATER_THAN_ONE;
79
- }
80
-
81
- const isElectron = !!process?.versions?.['electron'];
82
- if (isElectron) {
83
- try {
84
- const appProxy = require('@plasosdk/winproxy');
85
- const { ipcRenderer } = window.require('electron');
86
- const displayInfo = getDisplayMatching();
87
-
88
- if (remote && displayInfo) {
89
- const webContents = remote.getCurrentWebContents();
90
- const win = remote.getCurrentWindow();
91
-
92
- /**----------------------------------------------------课堂入参相关-----------------------------------*/
93
-
94
- const classInfo = classOptions.query ? parseUrlParams(classOptions.query) : {};
95
-
96
- const autoMaximized = true;
97
- /** 课堂最大化按钮是否是切换全屏模式 */
98
- let fullScreenable = false;
99
- if (classInfo.userType === 'listener') {
100
- fullScreenable = true;
101
- }
102
-
103
- // 环境确认
104
- const env = classOptions.env ? classOptions.env.toLowerCase() : 'www';
105
- let rhost, dhost;
106
- if (env == 'local') {
107
- rhost = 'http://localhost:4399/';
108
- dhost = 'https://dev.plaso.cn/';
109
- } else if (env == 'dev') {
110
- rhost = `https://${env}.plaso.cn/static/yxtelectronsdk/`;
111
- dhost = `https://${env}.plaso.cn/`;
112
- } else if (env == 'test' || env == 'itest' || env == 'ftest') {
113
- rhost = `https://${env}.plaso.cn/static/yxtsdk/`;
114
- dhost = `https://${env}.plaso.cn/`;
115
- } else {
116
- rhost = `https://wwwr.plaso.cn/static/sdk/styleupime/${classOptions.version ?? '1.60.132'}/`;
117
- dhost = 'https://www.plaso.cn/';
118
- }
119
-
120
- // 截图和虚拟声卡地址确认
121
- let flameshotPath = path.join(__dirname, '../lib/flameshot/flameshot.exe');
122
- let PlasoALD = '';
123
- if (isMac) {
124
- flameshotPath = path.join(__dirname, '../lib/flameshot.app/Contents/MacOS/flameshot');
125
- PlasoALD = path.join(__dirname, '../lib/PlasoALD');
126
- }
127
-
128
- const classOptionsObj = {
129
- ...classOptions,
130
- ...classInfo,
131
- rhost,
132
- dhost,
133
- query: classOptions.query,
134
- appType: classOptions.appType,
135
- markString: env,
136
- env,
137
- classType: classOptions.classType,
138
- // 让课堂里退出时标识状态,详情见commFunction.js
139
- onClose: 'close',
140
- electronSdkLoggerPath: logger.loggerPath ?? '',
141
- electronSdkLoggerName: logger.logFileName ?? '',
142
- flameshotPath: flameshotPath ?? '',
143
- PlasoALDPath: PlasoALD ?? '',
144
- clientType: 'electron',
145
- isElectronSdk: true,
146
- openerId: webContents.id,
147
- memoryConfigKey: classOptions.memoryConfigKey ?? classInfo.loginName ?? classOptions.loginName ?? '',
148
- maximized: autoMaximized,
149
- fullScreenable,
150
- };
151
-
152
- if (classOptionsObj.userType === 'monitor') {
153
- classOptionsObj.monitor = true;
154
- }
155
-
156
- /**----------------------------------------------------electron 窗口相关设置-----------------------------------*/
157
-
158
- const bounds = win.getBounds();
159
- const screenWidth = displayInfo.size.width;
160
- const screenWorkAreaWidth = displayInfo.workAreaSize.width;
161
- const screenHeight = displayInfo.size.height;
162
- const screenWorkAreaHeight = displayInfo.workAreaSize.height;
163
-
164
- const changeBoundsDate = (ischangeWidth, baseWidthOrHeight) => {
165
- const newWidthOrHeight = baseWidthOrHeight - 10;
166
- if (ischangeWidth) {
167
- bounds.height = Math.round((bounds.height * newWidthOrHeight) / bounds.width);
168
- bounds.width = newWidthOrHeight;
169
- } else {
170
- bounds.width = Math.round((bounds.width * newWidthOrHeight) / bounds.height);
171
- bounds.height = newWidthOrHeight;
172
- }
173
- };
174
- // 课堂窗口创建时的宽或高和 屏幕宽高或屏幕工作区宽高 一致时,此时透明窗口会失效,需要调整进课堂时的窗口宽高
175
- if (bounds.width === screenWidth || bounds.width === screenWorkAreaWidth) {
176
- changeBoundsDate(true, Math.min(screenWidth, screenWorkAreaWidth));
177
- }
178
- if (bounds.height === screenHeight || bounds.height === screenWorkAreaHeight) {
179
- changeBoundsDate(false, Math.min(screenHeight, screenWorkAreaHeight));
180
- }
181
-
182
- const defaultElecteonWinOptions = {
183
- frame: false,
184
- /**
185
- * electron 12.0.18之后,resizable设为true才能全屏和最大化,
186
- * 而课堂窗口不允许通过electron自身的缩放行为来改变大小,因此进入课堂后会将resizable设为false
187
- */
188
- resizable: true,
189
- /** 设置为true,mac上setFullScreen(true)才生效,setSimpleFullScreen(true)不依赖此参数 */
190
- fullscreenable: true,
191
- ...bounds,
192
- webPreferences: {
193
- nodeIntegration: true,
194
- enableRemoteModule: true,
195
- contextIsolation: false,
196
- nodeIntegrationInWorker: true,
197
- },
198
- };
199
- if (isMac) {
200
- // 在Mac下要显式设置fullscreen为false,防止客户端全屏时进入课堂,课堂窗口也默认全屏,
201
- // 导致部分按钮失效,以及独立窗口打开异常
202
- defaultElecteonWinOptions.fullscreen = false;
203
- }
204
- // 学生无需使用透明窗口(透明窗口问题多),减少影响
205
- if (classInfo.userType !== 'listener') {
206
- defaultElecteonWinOptions.transparent = true;
207
- defaultElecteonWinOptions.backgroundColor = '#00ffffff';
208
- }
209
- if (classInfo.topic) defaultElecteonWinOptions.title = classInfo.topic + '';
210
- if (classOptions.topic && classOptions.classType === LESSON_TYPE.PREPARE_LESSONS)
211
- defaultElecteonWinOptions.title = classOptions.topic + '';
212
-
213
- const _electeonWinOptions = electronWinOptions
214
- ? {
215
- ...defaultElecteonWinOptions,
216
- ...electronWinOptions,
217
- }
218
- : defaultElecteonWinOptions;
219
-
220
- /**---------------------------------------------------- 创建课堂/备课窗口-----------------------------------*/
221
-
222
- const onClassWindowReady = (event) => {
223
- const classWindow = remote.BrowserWindow.fromId(currentWinId);
224
- classWindow.moveTop();
225
- classWindow.focus();
226
- logger.info(`课堂窗口ready,id:${currentWinId}`);
227
- if (onClassWindowReadyFn && currentWinId) onClassWindowReadyFn(currentWinId);
228
- };
229
- const onClassFinished = (event, value) => {
230
- const meetingId = value?.meetingId;
231
- logger.info(`课堂已结束,id:${currentWinId}, meetingId is ${meetingId}`);
232
- if (onClassFinishedFn && currentWinId) onClassFinishedFn(meetingId);
233
- };
234
- const onHtmlReady = (event, value) => {
235
- currentWebContentsId = value?.webContentId;
236
- logger.info(`课堂窗口 html ready,web contents id:${currentWebContentsId}`);
237
-
238
- if (currentWebContentsId) {
239
- ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.INIT_GLOBAL_APPINFO, classOptionsObj);
240
- }
241
- };
242
- const onSaveBoard = (event, value) => {
243
- logger.info(`保存板书:${JSON.stringify(value)}`);
244
- const cb = (value) => {
245
- if (currentWebContentsId) {
246
- ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.RESP_SAVE_BOARD, value);
247
- }
248
- };
249
- if (onSaveBoardFn && value) onSaveBoardFn(value, cb);
250
- };
251
-
252
- const onOpenResourceCenter = (event) => {
253
- if (onOpenResourceCenterFn) onOpenResourceCenterFn();
254
- };
255
-
256
- const onGetExtFileName = async (event, requestId, ...args) => {
257
- if (onGetExtFileNameFn && args.length > 0) {
258
- const url = await onGetExtFileNameFn(...args);
259
- if (url) {
260
- if (currentWebContentsId) {
261
- ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.RESP_GET_EXT_FILE_NAME, requestId, url);
262
- }
263
- }
264
- }
265
- };
266
-
267
- const onGetPreParseFileName = async (event, requestId, ...args) => {
268
- if (onGetPreParseFileNameFn && args.length > 0) {
269
- const url = await onGetPreParseFileNameFn(...args);
270
- if (url) {
271
- ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.RESP_GET_PRE_PARSE_FILE_NAME, requestId, url);
272
- }
273
- }
274
- };
275
-
276
- ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.LIVE_WINDOW_READY, onClassWindowReady);
277
- ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.ON_CLASS_FINISHED, onClassFinished);
278
- ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.HTML_READY, onHtmlReady);
279
- ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.SAVE_BOARD, onSaveBoard);
280
- ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.OPEN_RESOURCE_CENTER, onOpenResourceCenter);
281
- ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.GET_EXT_FILE_NAME, onGetExtFileName);
282
- ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.GET_PRE_PARSE_FILE_NAME, onGetPreParseFileName);
283
-
284
- const url = require('url');
285
- const htmlPath =
286
- (isMac
287
- ? url.format({
288
- pathname: path.join(__dirname, '../index.html'),
289
- protocol: 'file:',
290
- slashes: true,
291
- })
292
- : path.join(__dirname, '../index.html')) + `?openerId=${webContents.id}`;
293
-
294
- appProxy.createWindow(
295
- {
296
- path: htmlPath,
297
- debug: classOptions?.debug ?? false,
298
- maximize: autoMaximized,
299
- windowOptions: _electeonWinOptions,
300
- },
301
- async (id) => {
302
- currentWinId = id;
303
-
304
- appProxy.addWinEventListener(id, 'closed', () => {
305
- logger.info(`课堂窗口关闭,id:${currentWinId}`);
306
-
307
- if (onClassWindowLeaveFn) onClassWindowLeaveFn(currentWinId);
308
- currentWinId = null;
309
- currentWebContentsId = null;
310
- ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.LIVE_WINDOW_READY, onClassWindowReady);
311
- ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.ON_CLASS_FINISHED, onClassFinished);
312
- ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.HTML_READY, onHtmlReady);
313
- ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.SAVE_BOARD, onSaveBoard);
314
- ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.OPEN_RESOURCE_CENTER, onOpenResourceCenter);
315
- ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.GET_EXT_FILE_NAME, onGetExtFileName);
316
- ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.GET_PRE_PARSE_FILE_NAME, onGetPreParseFileName);
317
- });
318
- },
319
- );
320
- return 0;
321
- } else {
322
- logger.error('获取remote、displayInfo 失败');
323
- if (!remote) return ERROR_CODE.GET_REMOTE_FAIL;
324
- if (!displayInfo) return ERROR_CODE.GET_DISPLAY_INFO_FAIL;
325
- }
326
- } catch (error) {
327
- logger.error(`异常错误,${JSON.stringify(error)}`);
328
- return ERROR_CODE.COMMOM_ERROR;
329
- }
330
- } else {
331
- logger.error('非Electron环境');
332
- return ERROR_CODE.NO_ELECTRON_ENVIRONMENT;
333
- }
334
- }
335
-
336
- /**
337
- * @param {classWindowType} liveClassWindowProps
338
- * @returns
339
- */
340
- function createLiveClassWindow(liveClassWindowProps) {
341
- const classWindowProps = {
342
- ...liveClassWindowProps,
343
- classOptions: {
344
- ...liveClassWindowProps.classOptions,
345
- appType: 'liveclassSDK',
346
- classType: LESSON_TYPE.LIVECLASS,
347
- },
348
- };
349
- return createClassWindow(classWindowProps);
350
- }
351
-
352
- /**
353
- * @param {classWindowType} prepareClassWindowProps
354
- * @returns
355
- */
356
- function createPrepareClassWindow(prepareClassWindowProps) {
357
- const classWindowProps = {
358
- ...prepareClassWindowProps,
359
- classOptions: {
360
- ...prepareClassWindowProps.classOptions,
361
- appType: 'prepareLessonSdk',
362
- meetingType: LESSON_TYPE.PREPARE_LESSONS,
363
- classType: LESSON_TYPE.PREPARE_LESSONS,
364
- userType: 'speaker',
365
- topic: prepareClassWindowProps.classOptions.topic ?? '备课课堂',
366
- },
367
- };
368
- return createClassWindow(classWindowProps);
369
- }
370
-
371
- /** 插入外部云盘里的文件,文件需要遵循特定的数据结构 */
372
- /**
373
- * @typedef {Object} fileDataObj
374
- * @property {number} type 插入文件的格式,内容参考 FILE_TYPE
375
- * @property {string} [title] 文件名称,传入后会显示在文件窗口标题栏上,建议带上文件后缀名
376
- * @property {string} [url] 公开的访问权限的文件全地址,建议https协议全地址
377
- * @property {any[]} [info] 具体的文件信息,除备课外,内容都由用户自己定义
378
- */
379
- /**
380
- * @param {fileDataObj} fileData 文件数据
381
- */
382
- function insertObject(fileData) {
383
- try {
384
- const { ipcRenderer } = window.require('electron');
385
- if (currentWebContentsId) ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.INSERT_OBJECT, fileData);
386
- } catch (error) {
387
- logger.error(`insertObject error is: ${JSON.stringify(error)}}`);
388
- return ERROR_CODE.COMMOM_ERROR;
389
- }
390
- }
391
-
392
- function getVersion() {
393
- try {
394
- const packageJson = require('../package.json');
395
- return packageJson.version;
396
- } catch (error) {
397
- logger.error(`getVersion error is: ${JSON.stringify(error)}}`);
398
- return ERROR_CODE.COMMOM_ERROR;
399
- }
400
- }
401
-
402
- /** 日志文件地址 */
403
- function initLogConfig(logFilePath) {
404
- if (logFilePath) {
405
- logger.initLoggerFilePath(logFilePath);
406
- const { ipcRenderer } = require('electron');
407
- ipcRenderer.send(RENDER_TO_MAIN_MESG_TYPE.PLASO_INIT_LOG_PATH, logFilePath);
408
- }
409
- }
410
-
411
- const PlasoElectronSdk = {
412
- createLiveClassWindow: createLiveClassWindow,
413
- createPrepareClassWindow: createPrepareClassWindow,
414
- getVersion: getVersion,
415
- initLogConfig: initLogConfig,
416
- insertObject: insertObject,
417
- FILE_TYPE: FILE_TYPE,
418
- };
419
-
420
- module.exports = PlasoElectronSdk;
1
+ const LogFormatter = require('../scripts/logger');
2
+ const path = require('path');
3
+ const { getElectronRemote, parseUrlParams, getEnvironment, getDisplayMatching } = require('./util');
4
+ const remote = getElectronRemote();
5
+ const defaultLoggerPath = remote ? path.join(remote.app.getPath('userData'), '/P403FileTemp/') : '';
6
+
7
+ const logger = new LogFormatter(defaultLoggerPath);
8
+ logger.init();
9
+ const getConfig = require('../scripts/getConfig');
10
+
11
+ const { platform } = getConfig();
12
+
13
+ const isMac = platform === 'darwin';
14
+
15
+ const { ERROR_CODE, LESSON_TYPE, CLASS_WINDOW_MESG_TYPE, RENDER_TO_MAIN_MESG_TYPE, FILE_TYPE } = require('./macro');
16
+
17
+ let currentWinId = null;
18
+
19
+ let currentWebContentsId = null;
20
+
21
+ /**
22
+ * @typedef {Object} classOptionsType
23
+ * @property {boolean} [debug] - 是否开启课堂窗口debug模式
24
+ * @property {string} query - 进课堂的必备query
25
+ * @property {string} [version] - 格式参考:1.53.901
26
+ */
27
+
28
+ /**
29
+ * @typedef {Object} FileParams
30
+ * @property {string[]} filePath - 备课文件本地地址
31
+ * @property {'png' | 'pb' | string} fileType
32
+ */
33
+
34
+ /**
35
+ * @typedef {Object} classWindowType
36
+ * @property {classOptionsType} classOptions 进课堂参数对象
37
+ * @property {Object} [electronWinOptions] 自定义electron的窗口参数
38
+ * @property {(winId: number)=>void} [onClassWindowReadyFn] 课堂窗口打开渲染成功后的回调
39
+ * @property {(winId: number)=>void} [onClassWindowLeaveFn] 课堂窗口关闭后的回调
40
+ * @property {(meetingId: string)=>void} [onClassFinishedFn] 课堂结束后的回调
41
+ * @property {(
42
+ * params: {
43
+ * fileInfo: FileParams[],
44
+ * fileName?: string
45
+ * },
46
+ * callback: (result: boolean) => void
47
+ * ) => void} [onSaveBoardFn] 保存板书,具体的保存逻辑由外部实现,取消保存板书时,callback传false, 不然传true
48
+ * @property {()=>void} [onOpenResourceCenterFn] 通知外部用户打开自己的资料中心,资料中心的具体ui和逻辑由外部用户自己实现
49
+ * @property {(info: any, option: any)=>Promise<string>} [onGetExtFileNameFn] 通过insertObject插入的文件传入 参数 info 时,怎么从info中获取文件的可访问地址的逻辑在用户那,所以需要函数从外部用户获取外部用户传入的文件地址
50
+ * @property {(info: any, option: { suffix: string })=>Promise<string>} [onGetPreParseFileNameFn] 类似onGetExtFileNameFn,只是获取的文件地址是预解析文件地址,通过suffix得到预解析文件路径
51
+ * @property {(info: {
52
+ * time: string;
53
+ * id: string;
54
+ * name: string;
55
+ * bugDescription: string;
56
+ * })=>void} [onReportIssuesFn] 上报问题,具体的上报逻辑由外部实现
57
+ *
58
+ */
59
+
60
+ /**
61
+ * @param {classWindowType} classWindowProps
62
+ * @returns
63
+ */
64
+ function createClassWindow(classWindowProps) {
65
+ const {
66
+ classOptions,
67
+ electronWinOptions,
68
+ onClassWindowReadyFn,
69
+ onClassWindowLeaveFn,
70
+ onClassFinishedFn,
71
+ onSaveBoardFn,
72
+ onOpenResourceCenterFn,
73
+ onGetExtFileNameFn,
74
+ onGetPreParseFileNameFn,
75
+ onReportIssuesFn,
76
+ } = classWindowProps;
77
+
78
+ logger.info(
79
+ `课堂窗口创建信息: version is ${getVersion()}, classOptions is ${JSON.stringify(classOptions)},electronWinOptions is ${JSON.stringify(
80
+ electronWinOptions,
81
+ )}`,
82
+ );
83
+ if (currentWinId) {
84
+ logger.warn('课堂窗口同时仅能存在一个');
85
+ return ERROR_CODE.CLASS_WINODW_GREATER_THAN_ONE;
86
+ }
87
+
88
+ const isElectron = !!process?.versions?.['electron'];
89
+ if (isElectron) {
90
+ try {
91
+ const appProxy = require('@plasosdk/winproxy');
92
+ const { ipcRenderer } = window.require('electron');
93
+ const displayInfo = getDisplayMatching();
94
+
95
+ if (remote && displayInfo) {
96
+ const webContents = remote.getCurrentWebContents();
97
+ const win = remote.getCurrentWindow();
98
+
99
+ /**----------------------------------------------------课堂入参相关-----------------------------------*/
100
+
101
+ const classInfo = classOptions.query ? parseUrlParams(classOptions.query) : {};
102
+
103
+ const autoMaximized = true;
104
+ /** 课堂最大化按钮是否是切换全屏模式 */
105
+ let fullScreenable = false;
106
+ if (classInfo.userType === 'listener') {
107
+ fullScreenable = true;
108
+ }
109
+
110
+ // 环境确认
111
+ const env = classOptions.env ? classOptions.env.toLowerCase() : 'www';
112
+ let rhost, dhost;
113
+ if (env == 'local') {
114
+ rhost = 'http://localhost:4399/';
115
+ dhost = 'https://dev.plaso.cn/';
116
+ } else if (env == 'dev') {
117
+ rhost = `https://${env}.plaso.cn/static/yxtelectronsdk/`;
118
+ dhost = `https://${env}.plaso.cn/`;
119
+ } else if (env == 'test' || env == 'itest' || env == 'ftest') {
120
+ rhost = `https://${env}.plaso.cn/static/yxtsdk/`;
121
+ dhost = `https://${env}.plaso.cn/`;
122
+ } else {
123
+ rhost = `https://wwwr.plaso.cn/static/sdk/styleupime/${classOptions.version ?? '1.60.137'}/`;
124
+ dhost = 'https://www.plaso.cn/';
125
+ }
126
+
127
+ // 截图和虚拟声卡地址确认
128
+ let flameshotPath = path.join(__dirname, '../lib/flameshot/flameshot.exe');
129
+ let PlasoALD = '';
130
+ if (isMac) {
131
+ flameshotPath = path.join(__dirname, '../lib/flameshot.app/Contents/MacOS/flameshot');
132
+ PlasoALD = path.join(__dirname, '../lib/PlasoALD');
133
+ }
134
+
135
+ const classOptionsObj = {
136
+ ...classOptions,
137
+ ...classInfo,
138
+ rhost,
139
+ dhost,
140
+ query: classOptions.query,
141
+ appType: classOptions.appType,
142
+ markString: env,
143
+ env,
144
+ classType: classOptions.classType,
145
+ // 让课堂里退出时标识状态,详情见commFunction.js
146
+ onClose: 'close',
147
+ electronSdkLoggerPath: logger.loggerPath ?? '',
148
+ electronSdkLoggerName: logger.logFileName ?? '',
149
+ flameshotPath: flameshotPath ?? '',
150
+ PlasoALDPath: PlasoALD ?? '',
151
+ clientType: 'electron',
152
+ isElectronSdk: true,
153
+ openerId: webContents.id,
154
+ memoryConfigKey: classOptions.memoryConfigKey ?? classInfo.loginName ?? classOptions.loginName ?? '',
155
+ maximized: autoMaximized,
156
+ fullScreenable,
157
+ supportOnReportIssues: !!onReportIssuesFn,
158
+ };
159
+
160
+ if (classOptionsObj.userType === 'monitor') {
161
+ classOptionsObj.monitor = true;
162
+ }
163
+
164
+ /**----------------------------------------------------electron 窗口相关设置-----------------------------------*/
165
+
166
+ const bounds = win.getBounds();
167
+ const screenWidth = displayInfo.size.width;
168
+ const screenWorkAreaWidth = displayInfo.workAreaSize.width;
169
+ const screenHeight = displayInfo.size.height;
170
+ const screenWorkAreaHeight = displayInfo.workAreaSize.height;
171
+
172
+ const changeBoundsDate = (ischangeWidth, baseWidthOrHeight) => {
173
+ const newWidthOrHeight = baseWidthOrHeight - 10;
174
+ if (ischangeWidth) {
175
+ bounds.height = Math.round((bounds.height * newWidthOrHeight) / bounds.width);
176
+ bounds.width = newWidthOrHeight;
177
+ } else {
178
+ bounds.width = Math.round((bounds.width * newWidthOrHeight) / bounds.height);
179
+ bounds.height = newWidthOrHeight;
180
+ }
181
+ };
182
+ // 课堂窗口创建时的宽或高和 屏幕宽高或屏幕工作区宽高 一致时,此时透明窗口会失效,需要调整进课堂时的窗口宽高
183
+ if (bounds.width === screenWidth || bounds.width === screenWorkAreaWidth) {
184
+ changeBoundsDate(true, Math.min(screenWidth, screenWorkAreaWidth));
185
+ }
186
+ if (bounds.height === screenHeight || bounds.height === screenWorkAreaHeight) {
187
+ changeBoundsDate(false, Math.min(screenHeight, screenWorkAreaHeight));
188
+ }
189
+
190
+ const defaultElecteonWinOptions = {
191
+ frame: false,
192
+ /**
193
+ * electron 12.0.18之后,resizable设为true才能全屏和最大化,
194
+ * 而课堂窗口不允许通过electron自身的缩放行为来改变大小,因此进入课堂后会将resizable设为false
195
+ */
196
+ resizable: true,
197
+ /** 设置为true,mac上setFullScreen(true)才生效,setSimpleFullScreen(true)不依赖此参数 */
198
+ fullscreenable: true,
199
+ ...bounds,
200
+ webPreferences: {
201
+ nodeIntegration: true,
202
+ enableRemoteModule: true,
203
+ contextIsolation: false,
204
+ nodeIntegrationInWorker: true,
205
+ },
206
+ };
207
+ if (isMac) {
208
+ // 在Mac下要显式设置fullscreen为false,防止客户端全屏时进入课堂,课堂窗口也默认全屏,
209
+ // 导致部分按钮失效,以及独立窗口打开异常
210
+ defaultElecteonWinOptions.fullscreen = false;
211
+ }
212
+ // 学生无需使用透明窗口(透明窗口问题多),减少影响
213
+ if (classInfo.userType !== 'listener') {
214
+ defaultElecteonWinOptions.transparent = true;
215
+ defaultElecteonWinOptions.backgroundColor = '#00ffffff';
216
+ }
217
+ if (classInfo.topic) defaultElecteonWinOptions.title = classInfo.topic + '';
218
+ if (classOptions.topic && classOptions.classType === LESSON_TYPE.PREPARE_LESSONS)
219
+ defaultElecteonWinOptions.title = classOptions.topic + '';
220
+
221
+ const _electeonWinOptions = electronWinOptions
222
+ ? {
223
+ ...defaultElecteonWinOptions,
224
+ ...electronWinOptions,
225
+ }
226
+ : defaultElecteonWinOptions;
227
+
228
+ /**---------------------------------------------------- 创建课堂/备课窗口-----------------------------------*/
229
+
230
+ const onClassWindowReady = (event) => {
231
+ const classWindow = remote.BrowserWindow.fromId(currentWinId);
232
+ classWindow.moveTop();
233
+ classWindow.focus();
234
+ logger.info(`课堂窗口ready,id:${currentWinId}`);
235
+ if (onClassWindowReadyFn && currentWinId) onClassWindowReadyFn(currentWinId);
236
+ };
237
+ const onClassFinished = (event, value) => {
238
+ const meetingId = value?.meetingId;
239
+ logger.info(`课堂已结束,id:${currentWinId}, meetingId is ${meetingId}`);
240
+ if (onClassFinishedFn && currentWinId) onClassFinishedFn(meetingId);
241
+ };
242
+ const onHtmlReady = (event, value) => {
243
+ currentWebContentsId = value?.webContentId;
244
+ logger.info(`课堂窗口 html ready,web contents id:${currentWebContentsId}`);
245
+
246
+ if (currentWebContentsId) {
247
+ ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.INIT_GLOBAL_APPINFO, classOptionsObj);
248
+ }
249
+ };
250
+ const onSaveBoard = (event, value) => {
251
+ logger.info(`保存板书:${JSON.stringify(value)}`);
252
+ const cb = (value) => {
253
+ if (currentWebContentsId) {
254
+ ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.RESP_SAVE_BOARD, value);
255
+ }
256
+ };
257
+ if (onSaveBoardFn && value) onSaveBoardFn(value, cb);
258
+ };
259
+
260
+ const onOpenResourceCenter = (event) => {
261
+ if (onOpenResourceCenterFn) onOpenResourceCenterFn();
262
+ };
263
+
264
+ const onGetExtFileName = async (event, requestId, ...args) => {
265
+ if (onGetExtFileNameFn && args.length > 0) {
266
+ const url = await onGetExtFileNameFn(...args);
267
+ if (url) {
268
+ if (currentWebContentsId) {
269
+ ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.RESP_GET_EXT_FILE_NAME, requestId, url);
270
+ }
271
+ }
272
+ }
273
+ };
274
+
275
+ const onGetPreParseFileName = async (event, requestId, ...args) => {
276
+ if (onGetPreParseFileNameFn && args.length > 0) {
277
+ const url = await onGetPreParseFileNameFn(...args);
278
+ if (url) {
279
+ ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.RESP_GET_PRE_PARSE_FILE_NAME, requestId, url);
280
+ }
281
+ }
282
+ };
283
+
284
+ const onReportIssues = (event, info) => {
285
+ onReportIssuesFn?.(info);
286
+ };
287
+
288
+ ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.LIVE_WINDOW_READY, onClassWindowReady);
289
+ ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.ON_CLASS_FINISHED, onClassFinished);
290
+ ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.HTML_READY, onHtmlReady);
291
+ ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.SAVE_BOARD, onSaveBoard);
292
+ ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.OPEN_RESOURCE_CENTER, onOpenResourceCenter);
293
+ ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.GET_EXT_FILE_NAME, onGetExtFileName);
294
+ ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.GET_PRE_PARSE_FILE_NAME, onGetPreParseFileName);
295
+ ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.REPORT_ISSUES, onReportIssues);
296
+
297
+ const url = require('url');
298
+ const htmlPath =
299
+ (isMac
300
+ ? url.format({
301
+ pathname: path.join(__dirname, '../index.html'),
302
+ protocol: 'file:',
303
+ slashes: true,
304
+ })
305
+ : path.join(__dirname, '../index.html')) + `?openerId=${webContents.id}`;
306
+
307
+ appProxy.createWindow(
308
+ {
309
+ path: htmlPath,
310
+ debug: classOptions?.debug ?? false,
311
+ maximize: autoMaximized,
312
+ windowOptions: _electeonWinOptions,
313
+ },
314
+ async (id) => {
315
+ currentWinId = id;
316
+
317
+ appProxy.addWinEventListener(id, 'closed', () => {
318
+ logger.info(`课堂窗口关闭,id:${currentWinId}`);
319
+
320
+ if (onClassWindowLeaveFn) onClassWindowLeaveFn(currentWinId);
321
+ currentWinId = null;
322
+ currentWebContentsId = null;
323
+ ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.LIVE_WINDOW_READY, onClassWindowReady);
324
+ ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.ON_CLASS_FINISHED, onClassFinished);
325
+ ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.HTML_READY, onHtmlReady);
326
+ ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.SAVE_BOARD, onSaveBoard);
327
+ ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.OPEN_RESOURCE_CENTER, onOpenResourceCenter);
328
+ ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.GET_EXT_FILE_NAME, onGetExtFileName);
329
+ ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.GET_PRE_PARSE_FILE_NAME, onGetPreParseFileName);
330
+ ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.REPORT_ISSUES, onReportIssues);
331
+ });
332
+ },
333
+ );
334
+ return 0;
335
+ } else {
336
+ logger.error('获取remote、displayInfo 失败');
337
+ if (!remote) return ERROR_CODE.GET_REMOTE_FAIL;
338
+ if (!displayInfo) return ERROR_CODE.GET_DISPLAY_INFO_FAIL;
339
+ }
340
+ } catch (error) {
341
+ logger.error(`异常错误,${JSON.stringify(error)}`);
342
+ return ERROR_CODE.COMMOM_ERROR;
343
+ }
344
+ } else {
345
+ logger.error('非Electron环境');
346
+ return ERROR_CODE.NO_ELECTRON_ENVIRONMENT;
347
+ }
348
+ }
349
+
350
+ /**
351
+ * @param {classWindowType} liveClassWindowProps
352
+ * @returns
353
+ */
354
+ function createLiveClassWindow(liveClassWindowProps) {
355
+ const classWindowProps = {
356
+ ...liveClassWindowProps,
357
+ classOptions: {
358
+ ...liveClassWindowProps.classOptions,
359
+ appType: 'liveclassSDK',
360
+ classType: LESSON_TYPE.LIVECLASS,
361
+ },
362
+ };
363
+ return createClassWindow(classWindowProps);
364
+ }
365
+
366
+ /**
367
+ * @param {classWindowType} prepareClassWindowProps
368
+ * @returns
369
+ */
370
+ function createPrepareClassWindow(prepareClassWindowProps) {
371
+ const classWindowProps = {
372
+ ...prepareClassWindowProps,
373
+ classOptions: {
374
+ ...prepareClassWindowProps.classOptions,
375
+ appType: 'prepareLessonSdk',
376
+ meetingType: LESSON_TYPE.PREPARE_LESSONS,
377
+ classType: LESSON_TYPE.PREPARE_LESSONS,
378
+ userType: 'speaker',
379
+ topic: prepareClassWindowProps.classOptions.topic ?? '备课课堂',
380
+ },
381
+ };
382
+ return createClassWindow(classWindowProps);
383
+ }
384
+
385
+ /** 插入外部云盘里的文件,文件需要遵循特定的数据结构 */
386
+ /**
387
+ * @typedef {Object} fileDataObj
388
+ * @property {number} type 插入文件的格式,内容参考 FILE_TYPE
389
+ * @property {string} [title] 文件名称,传入后会显示在文件窗口标题栏上,建议带上文件后缀名
390
+ * @property {string} [url] 公开的访问权限的文件全地址,建议https协议全地址
391
+ * @property {any[]} [info] 具体的文件信息,除备课外,内容都由用户自己定义
392
+ */
393
+ /**
394
+ * @param {fileDataObj} fileData 文件数据
395
+ */
396
+ function insertObject(fileData) {
397
+ try {
398
+ const { ipcRenderer } = window.require('electron');
399
+ if (currentWebContentsId) ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.INSERT_OBJECT, fileData);
400
+ } catch (error) {
401
+ logger.error(`insertObject error is: ${JSON.stringify(error)}}`);
402
+ return ERROR_CODE.COMMOM_ERROR;
403
+ }
404
+ }
405
+
406
+ function getVersion() {
407
+ try {
408
+ const packageJson = require('../package.json');
409
+ return packageJson.version;
410
+ } catch (error) {
411
+ logger.error(`getVersion error is: ${JSON.stringify(error)}}`);
412
+ return ERROR_CODE.COMMOM_ERROR;
413
+ }
414
+ }
415
+
416
+ /** 日志文件地址 */
417
+ function initLogConfig(logFilePath) {
418
+ if (logFilePath) {
419
+ logger.initLoggerFilePath(logFilePath);
420
+ const { ipcRenderer } = require('electron');
421
+ ipcRenderer.send(RENDER_TO_MAIN_MESG_TYPE.PLASO_INIT_LOG_PATH, logFilePath);
422
+ }
423
+ }
424
+
425
+ const PlasoElectronSdk = {
426
+ createLiveClassWindow: createLiveClassWindow,
427
+ createPrepareClassWindow: createPrepareClassWindow,
428
+ getVersion: getVersion,
429
+ initLogConfig: initLogConfig,
430
+ insertObject: insertObject,
431
+ FILE_TYPE: FILE_TYPE,
432
+ };
433
+
434
+ module.exports = PlasoElectronSdk;