Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion models/controlled-documents/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,7 @@ export function createModel (builder: Builder): void {
label: print.string.PrintToPDF,
icon: print.icon.Print,
category: view.category.General,
input: 'focus', // NOTE: should only work for one doc for now, not bulk
input: 'selection',
target,
context: { mode: ['context', 'browser'], group: 'tools' },
visibilityTester: documents.function.CanPrintDocument,
Expand Down
2 changes: 1 addition & 1 deletion models/print/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function createModel (builder: Builder): void {
label: print.string.PrintToPDF,
icon: print.icon.Print,
category: view.category.General,
input: 'focus', // NOTE: should only work for one doc for now, not bulk
input: 'selection',
target: core.class.Doc,
context: { mode: ['context', 'browser'], group: 'tools' },
visibilityTester: print.function.CanPrint
Expand Down
5 changes: 4 additions & 1 deletion plugins/print-assets/lang/cs.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"string": {
"PrintToPDF": "Tisk do PDF"
"PrintToPDF": "Tisk do PDF",
"PrintingDocumentOf": "Tisk dokumentu {current} z {total}",
"DownloadAll": "Stáhnout vše",
"PrintFailed": "Tisk se nezdařil"
}
}
5 changes: 4 additions & 1 deletion plugins/print-assets/lang/de.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"string": {
"PrintToPDF": "Als PDF drucken"
"PrintToPDF": "Als PDF drucken",
"PrintingDocumentOf": "Dokument {current} von {total} wird gedruckt",
"DownloadAll": "Alle herunterladen",
"PrintFailed": "Druck fehlgeschlagen"
}
}
5 changes: 4 additions & 1 deletion plugins/print-assets/lang/en.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"string": {
"PrintToPDF": "Print to PDF"
"PrintToPDF": "Print to PDF",
"PrintingDocumentOf": "Printing document {current} of {total}",
"DownloadAll": "Download all",
"PrintFailed": "Print failed"
}
}
5 changes: 4 additions & 1 deletion plugins/print-assets/lang/es.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"string": {
"PrintToPDF": "Imprimir en PDF"
"PrintToPDF": "Imprimir en PDF",
"PrintingDocumentOf": "Imprimiendo documento {current} de {total}",
"DownloadAll": "Descargar todo",
"PrintFailed": "Error al imprimir"
}
}
5 changes: 4 additions & 1 deletion plugins/print-assets/lang/fr.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"string": {
"PrintToPDF": "Imprimer en PDF"
"PrintToPDF": "Imprimer en PDF",
"PrintingDocumentOf": "Impression du document {current} sur {total}",
"DownloadAll": "Tout télécharger",
"PrintFailed": "Échec de l'impression"
}
}
5 changes: 4 additions & 1 deletion plugins/print-assets/lang/it.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"string": {
"PrintToPDF": "Stampa in PDF"
"PrintToPDF": "Stampa in PDF",
"PrintingDocumentOf": "Stampa documento {current} di {total}",
"DownloadAll": "Scarica tutto",
"PrintFailed": "Stampa non riuscita"
}
}
5 changes: 4 additions & 1 deletion plugins/print-assets/lang/ja.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"string": {
"PrintToPDF": "PDFとして印刷"
"PrintToPDF": "PDFとして印刷",
"PrintingDocumentOf": "ドキュメント {current} / {total} を印刷中",
"DownloadAll": "すべてダウンロード",
"PrintFailed": "印刷に失敗しました"
}
}
5 changes: 4 additions & 1 deletion plugins/print-assets/lang/pt.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"string": {
"PrintToPDF": "Imprimir em PDF"
"PrintToPDF": "Imprimir em PDF",
"PrintingDocumentOf": "Imprimindo documento {current} de {total}",
"DownloadAll": "Descarregar tudo",
"PrintFailed": "Falha na impressão"
}
}
5 changes: 4 additions & 1 deletion plugins/print-assets/lang/ru.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"string": {
"PrintToPDF": "Печать в PDF"
"PrintToPDF": "Печать в PDF",
"PrintingDocumentOf": "Печать документа {current} из {total}",
"DownloadAll": "Скачать все",
"PrintFailed": "Ошибка печати"
}
}
5 changes: 4 additions & 1 deletion plugins/print-assets/lang/tr.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"string": {
"PrintToPDF": "PDF'e yazdır"
"PrintToPDF": "PDF'e yazdır",
"PrintingDocumentOf": "Belge yazdırılıyor {current} / {total}",
"DownloadAll": "Tümünü indir",
"PrintFailed": "Yazdırma başarısız"
}
}
5 changes: 4 additions & 1 deletion plugins/print-assets/lang/zh.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"string": {
"PrintToPDF": "打印为 PDF"
"PrintToPDF": "打印为 PDF",
"PrintingDocumentOf": "正在打印文档 {current} / {total}",
"DownloadAll": "全部下载",
"PrintFailed": "打印失败"
}
}
203 changes: 203 additions & 0 deletions plugins/print-resources/src/components/PrintBulkToPDF.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<!--
// Copyright © 2026 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->

<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte'
import type { Blob, Doc, Ref } from '@hcengineering/core'
import presentation, { Card, getClient, PDFViewer } from '@hcengineering/presentation'
import view from '@hcengineering/view'
import { Button, Label, Loading, Scroller } from '@hcengineering/ui'

import print from '../plugin'
import { type PdfResult, printAll, downloadPdf, downloadAllPdfs } from '../printUtils'

export let objects: Doc[] = []
export let signed: boolean = false

const client = getClient()

let results: PdfResult[] = []
let currentIndex = 0
let cancelled = false
let processing = true
let downloadAllLoading = false
let selectedResult: PdfResult | undefined = undefined

$: viewerFile = (selectedResult !== undefined ? selectedResult.blobId : undefined) as Ref<Blob> | undefined

$: total = objects.length
$: successCount = results.filter((r) => r.error === undefined).length
$: failedList = results.filter((r) => r.error !== undefined)

function cancel (): void {
cancelled = true
close()
}

function openPdf (result: PdfResult): void {
if (result.error !== undefined) return
selectedResult = result
}

async function doDownloadAll (): Promise<void> {
downloadAllLoading = true
try {
await downloadAllPdfs(results)
} finally {
downloadAllLoading = false
}
}

const dispatch = createEventDispatcher()

function close (): void {
dispatch('close')
}

onMount(() => {
if (objects.length > 0) {
results = []
currentIndex = 0
cancelled = false
processing = true
void printAll(client, objects, signed, {
onProgress: (current) => {
currentIndex = current
},
getCancelled: () => cancelled
}).then((r) => {
results = r
processing = false
})
}
})
</script>

<svelte:window
on:keydown={(e) => {
if (e.key === 'Escape') {
if (selectedResult !== undefined) {
selectedResult = undefined
} else {
close()
}
}
}}
/>

{#if selectedResult === undefined}
<div class="flex p-4">
<Card
label={print.string.PrintToPDF}
okAction={() => {}}
canSave={false}
hideFooter={true}
width="medium"
on:close={close}
>
<div class="flex flex-col gap-2">
{#if processing && currentIndex <= total}
<div class="flex flex-col gap-2 py-2">
<div class="flex items-start gap-2">
<Label label={print.string.PrintingDocumentOf} params={{ current: currentIndex, total }} />
<div class="flex pl-2">
<Loading shrink={true} size="small" />
</div>
</div>
<div class="flex justify-end">
<Button kind="secondary" label={presentation.string.Cancel} on:click={cancel} />
</div>
</div>
{:else}
<div class="results-scroller">
<Scroller>
<div class="flex flex-col gap-1 w-full">
{#each results as result}
<div
class="flex items-center justify-between gap-2 py-1 border-b border-gray-200 dark:border-gray-700"
>
<span class="truncate flex-1 min-w-0 secondary-textColor" title={result.title ?? ''}>
{#if result.error !== undefined}
{result.title} – <Label label={print.string.PrintFailed} />
{:else}
{result.title}
{/if}
</span>
<div class="flex gap-1 shrink-0 w-[11rem] justify-end">
{#if result.error === undefined}
<Button
kind="ghost"
size="small"
label={presentation.string.Download}
on:click={() => {
downloadPdf(result)
}}
/>
<Button
kind="ghost"
size="small"
label={view.string.Open}
on:click={() => {
openPdf(result)
}}
/>
{/if}
</div>
</div>
{/each}
</div>
</Scroller>
</div>
{#if successCount > 0}
<div class="flex justify-end pt-2">
<Button
kind="primary"
label={print.string.DownloadAll}
disabled={downloadAllLoading}
on:click={doDownloadAll}
/>
</div>
{/if}
{#if failedList.length > 0}
<p class="secondary-textColor text-sm">
{successCount} succeeded, {failedList.length} failed.
</p>
{/if}
{/if}
</div>
</Card>
</div>
{:else}
<PDFViewer
file={viewerFile}
name={selectedResult.title}
contentType="application/pdf"
showIcon={false}
isLoading={false}
on:close={() => {
selectedResult = undefined
}}
on:fullsize
/>
{/if}

<style lang="scss">
.results-scroller {
max-height: 16rem;
min-height: 0;
flex: 1;
overflow: hidden;
}
</style>
23 changes: 20 additions & 3 deletions plugins/print-resources/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,37 @@ import { showPopup } from '@hcengineering/ui'
import { getPrintBaseURL } from '@hcengineering/print'

import PrintToPDF from './components/PrintToPDF.svelte'
import PrintBulkToPDF from './components/PrintBulkToPDF.svelte'
import DOCXViewer from './components/DOCXViewer.svelte'

export async function print (
object: Doc,
object: Doc | Doc[],
evt: Event,
props: {
signed: boolean
}
): Promise<void> {
const signed = props?.signed ?? false
const docs = Array.isArray(object) ? object : [object]

if (docs.length === 0) {
return
}
if (docs.length === 1) {
showPopup(
PrintToPDF,
{
object: docs[0],
signed
},
'float'
)
return
}
showPopup(
PrintToPDF,
PrintBulkToPDF,
{
object,
objects: docs,
signed
},
'float'
Expand All @@ -42,6 +58,7 @@ export async function canPrint (): Promise<boolean> {
export default async (): Promise<Resources> => ({
component: {
PrintToPDF,
PrintBulkToPDF,
DOCXViewer
},
actionImpl: {
Expand Down
Loading
Loading