@things-factory/integration-headless 7.0.56 → 7.0.58

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/integration-headless",
3
- "version": "7.0.56",
3
+ "version": "7.0.58",
4
4
  "main": "dist-server/index.js",
5
5
  "things-factory": true,
6
6
  "author": "heartyoh <heartyoh@hatiolab.com>",
@@ -29,5 +29,5 @@
29
29
  "ejs": "^3.1.10",
30
30
  "pdf-lib": "^1.17.1"
31
31
  },
32
- "gitHead": "79b01a9ad44568f3b807cbddef256b262a0aa5df"
32
+ "gitHead": "b0eea04d732f7fa30e7e7803cca8e3258b750f17"
33
33
  }
@@ -1,6 +1,7 @@
1
1
  import { TaskRegistry } from '@things-factory/integration-base'
2
2
  import { PDFCaptureUtil, getCommonParameterSpec } from './pdf-capture-util'
3
3
  import { BoardFunc } from '@things-factory/board-service'
4
+ import { access } from '@things-factory/utils'
4
5
 
5
6
  const PAGE_FORMATS = {
6
7
  A4: { width: 595.28, height: 841.89 },
@@ -9,73 +10,138 @@ const PAGE_FORMATS = {
9
10
  Legal: { width: 612, height: 1008 }
10
11
  }
11
12
 
12
- type PageFormat = keyof typeof PAGE_FORMATS
13
+ function convertToPixels(value: string): number {
14
+ const dpi = 96 // PDF에서 기본적으로 사용하는 DPI
15
+
16
+ if (value.endsWith('px')) {
17
+ return parseFloat(value)
18
+ } else if (value.endsWith('in')) {
19
+ const inches = parseFloat(value)
20
+ return inches * dpi
21
+ } else if (value.endsWith('cm')) {
22
+ const cm = parseFloat(value)
23
+ return cm * (dpi / 2.54)
24
+ }
25
+
26
+ return 0
27
+ }
13
28
 
14
29
  export async function HeadlessPDFCaptureBoard(step, context) {
15
30
  const pdfUtil = new PDFCaptureUtil(context)
16
31
  await pdfUtil.initBrowser(step.connection)
17
32
 
18
33
  try {
19
- const { board, draft, format = 'A4', landscape, preferCSSPageSize } = step.params
20
-
21
- if (!board || !board.id) {
34
+ var {
35
+ board: boardObject,
36
+ accessor,
37
+ boardAccessor,
38
+ draft,
39
+ format = 'A4',
40
+ width,
41
+ height,
42
+ landscape,
43
+ marginLeft = 0,
44
+ marginRight = 0,
45
+ marginTop = 0,
46
+ marginBottom = 0
47
+ } = step.params
48
+ var { domain, data, user, logger } = context
49
+
50
+ const boardId = boardAccessor ? access(boardAccessor, data) : boardObject?.id
51
+
52
+ if (!boardId) {
22
53
  throw new Error('The board property must be set')
23
54
  }
24
55
 
25
- const { model, base } = await BoardFunc.headlessModel({ domain: context.domain, id: board.id }, draft)
26
- const [fontsToUse, fontStyles] = await BoardFunc.fonts(context.domain)
56
+ const boardInput = access(accessor, data)
57
+
58
+ const { model, base } = await BoardFunc.headlessModel({ domain, id: boardId }, draft)
59
+ const [fontsToUse, fontStyles] = await BoardFunc.fonts(domain)
27
60
 
28
61
  model.fonts = fontsToUse
29
62
  model.fontStyles = fontStyles
30
63
 
31
- let width: number | undefined = PAGE_FORMATS[format as PageFormat]?.width
32
- let height: number | undefined = PAGE_FORMATS[format as PageFormat]?.height
64
+ var widthN = width ? convertToPixels(width) : 0
65
+ var heightN = height ? convertToPixels(height) : 0
33
66
 
34
- if (preferCSSPageSize) {
35
- width = undefined
36
- height = undefined
67
+ if (!widthN && !heightN && PAGE_FORMATS[format]) {
68
+ const pageDimensions = PAGE_FORMATS[format]
69
+ widthN = pageDimensions.width * (96 / 72)
70
+ heightN = pageDimensions.height * (96 / 72)
37
71
  }
38
72
 
39
- if (landscape && width && height) {
40
- ;[width, height] = [height, width]
73
+ if (landscape && widthN && heightN) {
74
+ ;[widthN, heightN] = [heightN, widthN]
41
75
  }
42
76
 
77
+ marginLeft = convertToPixels(marginLeft)
78
+ marginTop = convertToPixels(marginTop)
79
+ marginBottom = convertToPixels(marginBottom)
80
+ marginRight = convertToPixels(marginRight)
81
+
82
+ const contentWidth = widthN - marginLeft - marginRight
83
+ const contentHeight = heightN - marginTop - marginBottom
84
+
85
+ const page = await pdfUtil.browser!.newPage()
86
+
87
+ await page.setViewport({ width: Math.round(contentWidth), height: Math.round(contentHeight) })
88
+ await page.setRequestInterception(true)
89
+ await page.setDefaultTimeout(10000)
90
+
91
+ page.on('console', async msg => {
92
+ console.log(`[browser ${msg.type()}] ${msg.text()}`)
93
+ })
94
+
95
+ page.on('requestfailed', request => {
96
+ console.log('Request failed:', request.url())
97
+ })
98
+
43
99
  const protocol = 'http'
44
100
  const host = 'localhost'
45
101
  const port = process.env.PORT ? `:${process.env.PORT}` : ''
46
102
  const path = '/internal-board-service-view'
47
103
  const url = `${protocol}://${host}${port}${path}`
48
104
 
49
- const page = await pdfUtil.browser!.newPage()
50
-
51
- await page.setViewport({
52
- width: Math.round(width || 800), // Fallback for width if not defined
53
- height: Math.round(height || 600) // Fallback for height if not defined
105
+ const token = await user?.sign()
106
+
107
+ page.on('request', request => {
108
+ if (request.url() === url) {
109
+ request.continue({
110
+ method: 'POST',
111
+ headers: {
112
+ 'Content-Type': 'application/json',
113
+ 'x-things-factory-domain': domain?.subdomain,
114
+ Authorization: 'Bearer ' + token
115
+ },
116
+ postData: JSON.stringify({
117
+ model,
118
+ base
119
+ })
120
+ })
121
+ } else if (request.url().startsWith(`${protocol}://${host}${port}`)) {
122
+ request.continue({
123
+ headers: {
124
+ ...request.headers(),
125
+ 'x-things-factory-domain': domain?.subdomain,
126
+ Authorization: 'Bearer ' + token
127
+ }
128
+ })
129
+ } else {
130
+ request.continue()
131
+ }
54
132
  })
55
133
 
56
134
  await page.goto(url)
57
135
 
58
- await page.evaluate(
59
- data => {
60
- //@ts-ignore
61
- s.data = data
62
- return new Promise(resolve => {
63
- requestAnimationFrame(() => resolve(0))
64
- })
65
- },
66
- { model, base }
67
- )
68
-
69
- // 페이지 옵션 설정
70
- const pageOptions = pdfUtil.buildPageOptions({
71
- ...step.params,
72
- width: width ? `${width}px` : undefined,
73
- height: height ? `${height}px` : undefined
74
- })
136
+ await page.evaluate(data => {
137
+ //@ts-ignore
138
+ s.data = data
139
+ return new Promise(resolve => {
140
+ requestAnimationFrame(() => resolve(0))
141
+ })
142
+ }, boardInput)
75
143
 
76
- // PDF 생성 및 추가
77
- const htmlContent = '' // 보드에서 직접 렌더링되므로 HTML 컨텐츠가 비어있음
78
- await pdfUtil.processPageAndGeneratePDF(pageOptions, htmlContent) // pageOptions 사용
144
+ await pdfUtil.processPageAndGeneratePDF(step.params, null, page)
79
145
 
80
146
  return {
81
147
  data: context.__headless_pdf
@@ -89,6 +155,11 @@ export async function HeadlessPDFCaptureBoard(step, context) {
89
155
 
90
156
  HeadlessPDFCaptureBoard.parameterSpec = [
91
157
  ...getCommonParameterSpec(),
158
+ {
159
+ type: 'string',
160
+ name: 'boardAccessor',
161
+ label: 'board-accessor'
162
+ },
92
163
  {
93
164
  type: 'resource-object',
94
165
  name: 'board',
@@ -3,3 +3,7 @@ import './headless-pdf-capture-markdown'
3
3
  import './headless-pdf-capture-board'
4
4
  import './headless-pdf-open'
5
5
  import './headless-pdf-save'
6
+ import './pdf-capture-util'
7
+
8
+ export * from './pdf-capture-util'
9
+ export * from './headless-pdf-capture-board'
@@ -70,13 +70,17 @@ export class PDFCaptureUtil {
70
70
  this.context.__headless_pdf.pageCount += copiedPages.length
71
71
  }
72
72
 
73
- async processPageAndGeneratePDF(stepOptions: StepOptions, htmlContent: string) {
73
+ async processPageAndGeneratePDF(stepOptions: StepOptions, htmlContent: string, page?: Page) {
74
74
  if (!this.browser) throw new Error('Browser not initialized. Call initBrowser() first.')
75
75
 
76
- const page = await this.browser.newPage()
76
+ if (!page) {
77
+ page = page || (await this.browser.newPage())
78
+ }
77
79
 
78
- // 페이지 컨텐츠 설정
79
- await page.setContent(htmlContent, { waitUntil: 'networkidle0' }) // HTML 컨텐츠가 완전히 로드될 때까지 대기
80
+ if (htmlContent) {
81
+ // 페이지 컨텐츠 설정
82
+ await page.setContent(htmlContent, { waitUntil: 'networkidle0' }) // HTML 컨텐츠가 완전히 로드될 때까지 대기
83
+ }
80
84
 
81
85
  // 페이지가 완전히 로드된 후에 워터마크와 머릿글 적용
82
86
  const { __headless_pdf } = this.context
package/server/index.ts CHANGED
@@ -1 +1,3 @@
1
1
  import './engine'
2
+
3
+ export * from './engine/task'
@@ -1,6 +1,7 @@
1
1
  {
2
- "label.display-header/footer": "display header/footer",
2
+ "label.board-accessor": "board ID accessor",
3
3
  "label.board-draft": "draft board",
4
+ "label.display-header/footer": "display header/footer",
4
5
  "label.filename": "filename",
5
6
  "label.footer": "footer template",
6
7
  "label.header": "header template",
@@ -1,4 +1,6 @@
1
1
  {
2
+ "label.board-accessor": "かんばんIDアクセサー",
3
+ "label.board-draft": "編集版対象",
2
4
  "label.display-header/footer": "ヘッダー/フッターを表示",
3
5
  "label.filename": "ファイル名",
4
6
  "label.footer": "フッターテンプレート",
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "label.display-header/footer": "머릿글/바닥글 표시여부",
3
+ "label.board-accessor": "보드 ID 접근자",
3
4
  "label.board-draft": "편집본 대상",
4
5
  "label.filename": "파일이름",
5
6
  "label.footer": "바닥글 템플릿",
@@ -1,4 +1,6 @@
1
1
  {
2
+ "label.board-accessor": "kanban ID akses",
3
+ "label.board-draft": "sasaran draf",
2
4
  "label.display-header/footer": "papar pengepala/pengaki",
3
5
  "label.filename": "nama fail",
4
6
  "label.footer": "templat pengaki",
@@ -1,4 +1,6 @@
1
1
  {
2
+ "label.board-accessor": "看板ID访问者",
3
+ "label.board-draft": "编辑草案对象",
2
4
  "label.display-header/footer": "显示页眉/页脚",
3
5
  "label.filename": "文件名",
4
6
  "label.footer": "页脚模板",