ビューワのツールバーをカスタマイズする

このサンプルでは、独自のボタンをビューワのメインツールバーに追加する方法を紹介しています。
サンプルコードで追加した独自の保存ボタンでは、クライアント側で行ったPDFの編集内容をサーバー側(SupportApi)に送信し、ストリームとして一時的に保存しています。

window.onload = function() { //DsPdfViewer.LicenseKey = "***key***"; let viewer, React; // ビューワのオプションを準備 const options = { workerSrc: "/diodocs/pdfviewer/demos/product-bundles/build/dspdfviewer.worker.js", supportApi: getSupportApiSettings(), restoreViewStateOnLoad: false }; // ビューワに関連する任意のデータ // このデータはドキュメント保存時にサーバーサイドに送信される options.userData = { sampleName: 'SaveChangesSample', docName: new Date().getTime() + '.pdf' }; // ビューワのインスタンスを作成 viewer = new DsPdfViewer("#viewer", options); // 注釈エディタを追加 viewer.addAnnotationEditorPanel(); // 独自の「変更を保存」ボタンを作成 React = viewer.getType('React'); viewer.toolbar.addItem({ key: 'custom-save', icon: { type: 'svg', content: React.createElement('svg', { xmlns: 'http://www.w3.org/2000/svg', version: '1.1', width: '24', height: '24', viewBox: '0 0 24 24', fill: '#ff0000' }, React.createElement('path', { d: 'M20.913 9.058c0.057-0.26 0.087-0.531 0.087-0.808 0-2.071-1.679-3.75-3.75-3.75-0.333 0-0.657 0.044-0.964 0.125-0.581-1.813-2.28-3.125-4.286-3.125-2.047 0-3.775 1.367-4.32 3.238-0.533-0.155-1.097-0.238-1.68-0.238-3.314 0-6 2.686-6 6s2.686 6 6 6h3v4.5h6v-4.5h5.25c2.071 0 3.75-1.679 3.75-3.75 0-1.845-1.333-3.379-3.087-3.692zM13.5 15v4.5h-3v-4.5h-3.75l5.25-5.25 5.25 5.25h-3.75z' })) }, title: '変更を保存', enabled: false, action: function() { // 変更をサーバーサイドにストリームとして保存 viewer.saveChanges().then(function(result) { if(result) { // 保存したストリームをサーバーサイドから読み込む var docUrl = window.top.SUPPORTAPI_URL + "/GetPdfFromCloud?docName=" + options.userData.docName + '&clientId=' + viewer.supportApi.clientId; viewer.open(docUrl).then(function() { alert('ドキュメントはストリームとしてサーバーに保存されます。ドキュメントは10分間保持されます。'); }); } }); }, onUpdate: function() { return { enabled: viewer.hasDocument, title: '変更を保存' }; } }); viewer.toolbarLayout.viewer.default.splice(0, 0, 'download'); viewer.toolbarLayout.viewer.mobile.splice(0, 0, 'download'); // ビューワのツールバーのレイアウトに'custom-save'項目を追加 viewer.toolbarLayout.viewer.default.splice(0, 0, 'custom-save'); viewer.toolbarLayout.viewer.mobile.splice(0, 0, 'custom-save'); // 注釈エディタのツールバーのレイアウトを変更 var annotationEditorButtons = ['custom-save', 'download', 'edit-select', 'edit-sign-tool', '$split', 'edit-text', 'edit-free-text', 'edit-ink', 'edit-square', 'edit-circle', 'edit-line', 'edit-polyline', 'edit-polygon', 'edit-stamp', 'edit-link', '$split', 'edit-redact', 'edit-redact-apply', 'edit-erase', '$split', 'new-document', '$split', 'new-page', 'delete-page']; viewer.toolbarLayout.annotationEditor = { default: annotationEditorButtons, mobile: annotationEditorButtons, fullscreen: annotationEditorButtons }; viewer.open("/diodocs/pdfviewer/demos/product-bundles/assets/pdf/viewer-save-changes.pdf"); }
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>保存ボタンのカスタマイズ</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="./src/styles.css"> <script src="/diodocs/pdfviewer/demos/product-bundles/build/dspdfviewer.js"></script> <script src="/diodocs/pdfviewer/demos/product-bundles/build/wasmSupportApi.js"></script> <script src="/diodocs/pdfviewer/demos/resource/js/init.js"></script> <script src="./src/app.js"></script> </head> <body> <div id="viewer"></div> </body> </html>
#viewer { height: 100%; }
// Ignore Spelling: Pdf Api Gc using System; using System.Reflection; using System.IO; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using GrapeCity.Documents.Pdf; using Newtonsoft.Json.Linq; using System.Threading.Tasks; using GrapeCity.Documents.Pdf.ViewerSupportApi.Controllers; using GrapeCity.Documents.Pdf.ViewerSupportApi.Utils; using GrapeCity.Documents.Pdf.ViewerSupportApi.Models; using GrapeCity.Documents.Pdf.ViewerSupportApi.Resources.Stamps; using System.Text; namespace GcPdfViewerSupportApiDemo.Controllers { [Route("api/pdf-viewer")] [ApiController] public class SampleSupportController : GcPdfViewerController { static SampleSupportController() { #if DEBUG // VSのF5は、現在の作業ディレクトリを"bin"ディレクトリではなく"project"ディレクトリに設定しているため、 // ここで作業ディレクトリを"bin"に設定し、"Resources"サブディレクトリからファイルを取得できるようにします。 var exePath = new Uri(Assembly.GetEntryAssembly().Location).LocalPath; var directory = Path.GetDirectoryName(exePath); Directory.SetCurrentDirectory(directory); #endif Settings.AvailableUsers.AddRange(new string[] { "利用者A", "利用者B" }); } /// <summary> /// 例として、SupportAPIの基本メソッドの1つをオーバーライドします。 /// </summary> /// <returns></returns> public override string Ping(string docId) { return base.Ping(docId); } public override IStampImagesStorage StampImagesStorage { get { var token = GetQueryValue("token"); if (!string.IsNullOrEmpty(token) && token.Contains("custom-stamps-sample")) { string projectRootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); string imagesPath = Path.Combine(projectRootPath, "wwwroot/assets/example-stamps"); return new FileSystemStampImagesStorage(imagesPath); } return base.StampImagesStorage; } } public override void OnDocumentModified(GcPdfDocumentLoader documentLoader) { base.OnDocumentModified(documentLoader); CleanupSampleCloudStorage(); var userData = documentLoader.Info.documentOptions.userData as JObject; if (userData != null) { var userDataObj = userData.ToObject<Dictionary<string, string>>(); if (userDataObj != null) { if (userDataObj.ContainsKey("sampleName") && userDataObj["sampleName"] == "SaveChangesSample") { string docName = userDataObj["docName"]; SaveDocumentToCloud(documentLoader.ClientId, documentLoader.Document, docName); } } } } [Route("SubmitFormSample")] [ApiExplorerSettings(IgnoreApi = true)] public IActionResult SubmitFormSample() { if (!Request.HasFormContentType) { return Content("送信されたデータはありません。"); } else { var form = Request.Form; var e = form.Keys.GetEnumerator(); StringBuilder sb = new StringBuilder(); sb.AppendLine("送信されたデータ:"); while (e.MoveNext()) { if (form.TryGetValue(e.Current, out var val)) { sb.AppendLine($" {e.Current}: {val};"); } } return Content(sb.ToString()); } } #region ** ViewerSaveChanges サンプル: /// <summary> /// このメソッドは、クライアント側から呼び出されます。 /// </summary> /// <param name="docName"></param> /// <param name="clientId"></param> /// <returns></returns> /// <exception cref="Exception"></exception> [Route("GetPdfFromCloud")] [ApiExplorerSettings(IgnoreApi = true)] async public Task<IActionResult> GetPdfFromCloud(string docName, string clientId) { var fileBytes = await GetDocumentFromCloud(docName, clientId); if (fileBytes == null) throw new Exception($"サンプルドキュメント '{docName}' が見つかりません。"); return new FileContentResult(fileBytes, "application/pdf") { FileDownloadName = docName }; } /// <summary> /// クラウドサービスに PDF ドキュメントを保存します。 /// </summary> /// <param name="clientId"></param> /// <param name="document"></param> /// <param name="docName"></param> [ApiExplorerSettings(IgnoreApi = true)] public void SaveDocumentToCloud(string clientId, GcPdfDocument document, string docName) { // ************************************************************************************ // ここに、特定のクラウドサービスに PDF ドキュメントを保存するコードを記述します。 // ************************************************************************************ // 以下はコード例です。 SaveToMemory(clientId, document, docName); //SaveToDisk(document, docName); } /// <summary> /// クラウドサービスから PDF ドキュメントをダウンロードします。 /// </summary> /// <param name="docName"></param> /// <param name="clientId"></param> /// <returns></returns> #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously async public static Task<byte[]> GetDocumentFromCloud(string docName, string clientId) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { // ************************************************************************************ // ここに、特定のクラウドサービスから PDF ドキュメントを取得するコードを記述します。 // ************************************************************************************ // 以下はコード例です。 return LoadFromMemory(clientId, docName); //return LoadFromDisk(docName); } #region ** ディスクストレージの例 private static byte[] LoadFromDisk(string docName) { string path = $"Documents/{docName}"; return System.IO.File.ReadAllBytes(path); } private void SaveToDisk(GcPdfDocument document, string docName) { if (!Directory.Exists("Documents")) Directory.CreateDirectory("Documents"); string path = $"Documents/{docName}"; document.Save(path); } #endregion #region ** インメモリストレージの例 public static Dictionary<string, KeyValuePair<DateTime, byte[]>> DocumentsInCloud { get; private set; } = new Dictionary<string, KeyValuePair<DateTime, byte[]>>(); private static byte[] LoadFromMemory(string clientId, string docName) { var key = $"{docName}_{clientId}"; byte[] bytes = null; lock (DocumentsInCloud) { bytes = DocumentsInCloud.ContainsKey(key) ? DocumentsInCloud[key].Value : null; } CleanupSampleCloudStorage(); return bytes; } private void SaveToMemory(string clientId, GcPdfDocument document, string docName) { var key = $"{docName}_{clientId}"; MemoryStream ms = new MemoryStream(); document.Save(ms); lock (DocumentsInCloud) { if (DocumentsInCloud.ContainsKey(key)) DocumentsInCloud.Remove(key); DocumentsInCloud.Add(key, new KeyValuePair<DateTime, byte[]>(DateTime.Now, ms.ToArray())); } ms.Dispose(); } static void CleanupSampleCloudStorage() { lock (DocumentsInCloud) { foreach (var k in DocumentsInCloud.Keys) { if ((DateTime.Now - DocumentsInCloud[k].Key) > new TimeSpan(0, 10, 0) /* 10 min */) { DocumentsInCloud.Remove(k); } } } } #endregion #endregion } }