feat: add wireframe in docs

This commit is contained in:
insleker 2025-08-31 21:40:53 +08:00
parent 0c71399867
commit abbaf462e1
7 changed files with 3927 additions and 1 deletions

3
.gitignore vendored
View File

@ -125,3 +125,6 @@ devtools_options.yaml
test/features/*_test.dart
**/app_localizations*.dart
.env
docs/wireframe.assets/*.excalidraw.svg
docs/wireframe.assets/*.svg
node_modules/

View File

@ -1,6 +1,6 @@
# pdf_signature
A GUI app to create a signature on PDF page interactively.
A GUI app to create signatures on PDF pages interactively.
## Features
@ -15,6 +15,7 @@ flutter pub get
# generate gherkin test
flutter pub run build_runner build --delete-conflicting-outputs
# dart run tool/prune_unused_steps.dart --delete
# dart run tool/gen_view_wireframe_md.dart
# run the app
flutter run

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,981 @@
{
"type": "excalidraw",
"version": 2,
"source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor",
"elements": [
{
"id": "RJI8QD55RACPAUCo2GKoo",
"type": "rectangle",
"x": -140.36046055385032,
"y": 11.486575452108639,
"width": 186.65359497070312,
"height": 588.3137512207032,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"zR9PoqVUXJn-p5Me-QWmK"
],
"frameId": null,
"index": "Zy",
"roundness": null,
"seed": 230239768,
"version": 198,
"versionNonce": 1765613558,
"isDeleted": false,
"boundElements": [],
"updated": 1756647250652,
"link": null,
"locked": false
},
{
"id": "VWdv1VE8pzad1EwZuyYgo",
"type": "text",
"x": -131.31221189953033,
"y": 29.35618978076525,
"width": 125.17988586425781,
"height": 27,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"zR9PoqVUXJn-p5Me-QWmK"
],
"frameId": null,
"index": "Zz",
"roundness": null,
"seed": 530295576,
"version": 153,
"versionNonce": 748520042,
"isDeleted": false,
"boundElements": [],
"updated": 1756647250652,
"link": null,
"locked": false,
"text": "Recents PDFs",
"fontSize": 20,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Recents PDFs",
"autoResize": true,
"lineHeight": 1.35
},
{
"id": "WKRHjAMR0he0xqbtoR-bH",
"type": "rectangle",
"x": 0.024998256138360375,
"y": 98.93073527018237,
"width": 741.8097795758928,
"height": 474.05282317979203,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "a0",
"roundness": null,
"seed": 1506495512,
"version": 111,
"versionNonce": 657893430,
"isDeleted": false,
"boundElements": [],
"updated": 1756647231508,
"link": null,
"locked": false
},
{
"id": "gtEzgCZGX1lFA4HjD_xY_",
"type": "text",
"x": 87.19040296469939,
"y": 121.50467923112484,
"width": 84.4264505231206,
"height": 30.474824347272346,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "a1",
"roundness": null,
"seed": 532285720,
"version": 103,
"versionNonce": 1545526134,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false,
"text": "Settings",
"fontSize": 22.57394396094248,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Settings",
"autoResize": true,
"lineHeight": 1.35
},
{
"id": "pGsYJy75OHZYTrkjzSwnf",
"type": "text",
"x": 707.9738618906175,
"y": 115.86119324088922,
"width": 12.189847070657128,
"height": 27.42734191254511,
"angle": 0,
"strokeColor": "#111827",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "a2",
"roundness": null,
"seed": 315172376,
"version": 103,
"versionNonce": 510082794,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false,
"text": "×",
"fontSize": 20.31654956484823,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "×",
"autoResize": true,
"lineHeight": 1.35
},
{
"id": "blOqLsCAyD5v2UW23ped-",
"type": "text",
"x": 87.19040296469939,
"y": 166.65256715300978,
"width": 72.38740412180704,
"height": 27.42734191254511,
"angle": 0,
"strokeColor": "#111827",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "a3",
"roundness": null,
"seed": 1877779224,
"version": 103,
"versionNonce": 1306524854,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false,
"text": "General",
"fontSize": 20.31654956484823,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "General",
"autoResize": true,
"lineHeight": 1.35
},
{
"id": "de3wHTnwHHk_Sj716L0AD",
"type": "text",
"x": 109.76434692564192,
"y": 200.51348309442352,
"width": 84.84209960420075,
"height": 24.379859477817877,
"angle": 0,
"strokeColor": "#374151",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "a4",
"roundness": null,
"seed": 899186712,
"version": 103,
"versionNonce": 2108936618,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false,
"text": "Language:",
"fontSize": 18.059155168753982,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Language:",
"autoResize": true,
"lineHeight": 1.35
},
{
"id": "u274PAAExIXPOY5MjR_Sl",
"type": "rectangle",
"x": 233.92103871082554,
"y": 191.48390551004653,
"width": 338.60915941413714,
"height": 36.118310337507964,
"angle": 0,
"strokeColor": "#6b7280",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "a5",
"roundness": null,
"seed": 1942011160,
"version": 101,
"versionNonce": 1164637686,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false
},
{
"id": "d97Kf0eOcwYJybE20fjfu",
"type": "text",
"x": 86.99933596770336,
"y": 269.5432989464112,
"width": 69.07583451216198,
"height": 27.42734191254511,
"angle": 0,
"strokeColor": "#111827",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "a6",
"roundness": null,
"seed": 467229208,
"version": 112,
"versionNonce": 1398803562,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false,
"text": "Display",
"fontSize": 20.31654956484823,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Display",
"autoResize": true,
"lineHeight": 1.35
},
{
"id": "M1joCnME850ZafH3FL-E_",
"type": "text",
"x": 109.57327992864577,
"y": 303.40421488782493,
"width": 60.91367078245484,
"height": 24.379859477817877,
"angle": 0,
"strokeColor": "#374151",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "a7",
"roundness": null,
"seed": 1356224280,
"version": 112,
"versionNonce": 465880886,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false,
"text": "Theme:",
"fontSize": 18.059155168753982,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Theme:",
"autoResize": true,
"lineHeight": 1.35
},
{
"id": "8Mp1jT3NSsibB3kogMubl",
"type": "rectangle",
"x": 233.9110668529509,
"y": 279.88530391848326,
"width": 338.60915941413714,
"height": 36.118310337507964,
"angle": 0,
"strokeColor": "#6b7280",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "a8",
"roundness": null,
"seed": 925611032,
"version": 114,
"versionNonce": 87486250,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false
},
{
"id": "P2kfltnFMgp1Hpns5eRsk",
"type": "text",
"x": 109.57327992864577,
"y": 337.2651308292386,
"width": 88.30944720085046,
"height": 24.379859477817877,
"angle": 0,
"strokeColor": "#374151",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "a9",
"roundness": null,
"seed": 1154314520,
"version": 112,
"versionNonce": 1095921782,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false,
"text": "Page view:",
"fontSize": 18.059155168753982,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Page view:",
"autoResize": true,
"lineHeight": 1.35
},
{
"id": "vmM82c6vkYHi9E8_orBEx",
"type": "rectangle",
"x": 233.72997171382946,
"y": 328.23555324486165,
"width": 338.60915941413714,
"height": 36.118310337507964,
"angle": 0,
"strokeColor": "#6b7280",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "aA",
"roundness": null,
"seed": 288329240,
"version": 110,
"versionNonce": 128154090,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false
},
{
"id": "Q0v5ejctIV2msui0iDFEg",
"type": "rectangle",
"x": 414.5125903983653,
"y": 505.261726567147,
"width": 124.15669178518363,
"height": 40.63309912969646,
"angle": 0,
"strokeColor": "#1f2937",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "aB",
"roundness": null,
"seed": 625347352,
"version": 101,
"versionNonce": 1373172150,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false
},
{
"id": "QSD6mQUNvCKRLZtin0AHX",
"type": "text",
"x": 442.73002034954345,
"y": 514.291304151524,
"width": 55.13471219456543,
"height": 24.379859477817877,
"angle": 0,
"strokeColor": "#1f2937",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "aC",
"roundness": null,
"seed": 1267001368,
"version": 103,
"versionNonce": 162573482,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false,
"text": "Cancel",
"fontSize": 18.059155168753982,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Cancel",
"autoResize": true,
"lineHeight": 1.35
},
{
"id": "fmP0hKBOaNa5Ge12TEwyD",
"type": "rectangle",
"x": 561.2432261444915,
"y": 505.261726567147,
"width": 146.7306357461261,
"height": 40.63309912969646,
"angle": 0,
"strokeColor": "#1f2937",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "aD",
"roundness": null,
"seed": 1608525080,
"version": 101,
"versionNonce": 679299830,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false
},
{
"id": "iXKBOvEPDEax0jv78PxKB",
"type": "text",
"x": 601.8763252741879,
"y": 514.291304151524,
"width": 39.54961113185798,
"height": 24.379859477817877,
"angle": 0,
"strokeColor": "#1f2937",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"nQmqS53zA9IffPy8AAZwV"
],
"frameId": null,
"index": "aE",
"roundness": null,
"seed": 533447192,
"version": 103,
"versionNonce": 554272618,
"isDeleted": false,
"boundElements": [],
"updated": 1756647235527,
"link": null,
"locked": false,
"text": "Save",
"fontSize": 18.059155168753982,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Save",
"autoResize": true,
"lineHeight": 1.35
},
{
"id": "NjGNkNhtOARJpj03cEb51",
"type": "rectangle",
"x": -158.10173397972483,
"y": 0.8265845889135903,
"width": 992.4998779296876,
"height": 609.8372192382812,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "aF",
"roundness": null,
"seed": 668606488,
"version": 100,
"versionNonce": 1234867048,
"isDeleted": false,
"boundElements": [],
"updated": 1756647200609,
"link": null,
"locked": false
},
{
"id": "bb7EhwFW0ROfjqxlOkqmm",
"type": "rectangle",
"x": -155.50977434430797,
"y": -69.53801908947162,
"width": 1000,
"height": 60,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"lHEbf-Drffn9NvXktYUuc"
],
"frameId": null,
"index": "aG",
"roundness": null,
"seed": 1825463576,
"version": 115,
"versionNonce": 1176442904,
"isDeleted": false,
"boundElements": [],
"updated": 1756647163877,
"link": null,
"locked": false
},
{
"id": "D19DG1HUQ57QlAvyGFl0V",
"type": "text",
"x": -135.50977434430797,
"y": -51.53801908947162,
"width": 157.46356201171875,
"height": 32.400000000000006,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"lHEbf-Drffn9NvXktYUuc"
],
"frameId": null,
"index": "aH",
"roundness": null,
"seed": 1647507992,
"version": 132,
"versionNonce": 1739871512,
"isDeleted": false,
"boundElements": [],
"updated": 1756647163877,
"link": null,
"locked": false,
"text": "PDF Signature",
"fontSize": 24,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "PDF Signature",
"autoResize": true,
"lineHeight": 1.35
},
{
"id": "PdeNL3GtypJzGH5vBfCOZ",
"type": "rectangle",
"x": 698.590328156002,
"y": -58.47743923701887,
"width": 131.2138027615017,
"height": 39.35548782348633,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"WavtKMDY86ikKkLfh5Q4r",
"lHEbf-Drffn9NvXktYUuc"
],
"frameId": null,
"index": "aI",
"roundness": null,
"seed": 1526462232,
"version": 123,
"versionNonce": 2107240984,
"isDeleted": false,
"boundElements": [],
"updated": 1756647163877,
"link": null,
"locked": false
},
{
"id": "RzeDdJRNeKB2rEjlgqsfH",
"type": "text",
"x": 708.3768426804317,
"y": -51.69536051674481,
"width": 22.298202514648448,
"height": 32.400000000000006,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"WavtKMDY86ikKkLfh5Q4r",
"lHEbf-Drffn9NvXktYUuc"
],
"frameId": null,
"index": "aJ",
"roundness": null,
"seed": 731730968,
"version": 125,
"versionNonce": 1589899032,
"isDeleted": false,
"boundElements": [],
"updated": 1756647163877,
"link": null,
"locked": false,
"text": "⚙",
"fontSize": 24,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "⚙",
"autoResize": false,
"lineHeight": 1.35
},
{
"id": "hdqpl5nHgrXR_nhbDZDQ-",
"type": "text",
"x": 732.9285582285077,
"y": -50.26236931482936,
"width": 87.77992248535156,
"height": 27,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"WavtKMDY86ikKkLfh5Q4r",
"lHEbf-Drffn9NvXktYUuc"
],
"frameId": null,
"index": "aK",
"roundness": null,
"seed": 2047545624,
"version": 125,
"versionNonce": 1671842840,
"isDeleted": false,
"boundElements": [],
"updated": 1756647163877,
"link": null,
"locked": false,
"text": "Configure",
"fontSize": 20,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Configure",
"autoResize": true,
"lineHeight": 1.35
},
{
"id": "iIDobnzWCl-gygCOsA73n",
"type": "rectangle",
"x": 576.109131998714,
"y": -56.43111661983278,
"width": 109.23454710748254,
"height": 36.33306860750372,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffffff",
"fillStyle": "hachure",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"2xO--DSh2411Pyp1YG0B4"
],
"frameId": null,
"index": "ac",
"roundness": null,
"seed": 1897278824,
"version": 221,
"versionNonce": 536065304,
"isDeleted": false,
"boundElements": [],
"updated": 1756647186276,
"link": null,
"locked": false
},
{
"type": "rectangle",
"version": 825,
"versionNonce": 921522712,
"isDeleted": false,
"id": "rumws8Xb5KM1-COUn-SjA",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 588.2383018113569,
"y": -45.08971236581036,
"strokeColor": "#000000",
"backgroundColor": "#868e96",
"width": 22.637490885793227,
"height": 13.582494531475936,
"seed": 331104360,
"groupIds": [
"1FZGUtYp_0lg0mZX7lxmQ",
"2xO--DSh2411Pyp1YG0B4"
],
"boundElements": [],
"updated": 1756647186276,
"link": null,
"locked": false,
"index": "ad",
"frameId": null,
"roundness": null
},
{
"type": "line",
"version": 842,
"versionNonce": 1454067480,
"isDeleted": false,
"id": "l8Hqi6JegDC-MxE036F3N",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 601.8207963428326,
"y": -45.08971236581036,
"strokeColor": "#000000",
"backgroundColor": "#868e96",
"width": 13.582494531475936,
"height": 4.5274981771586456,
"seed": 1042805608,
"groupIds": [
"1FZGUtYp_0lg0mZX7lxmQ",
"2xO--DSh2411Pyp1YG0B4"
],
"boundElements": [],
"updated": 1756647186276,
"link": null,
"locked": false,
"startBinding": null,
"endBinding": null,
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": null,
"points": [
[
0,
0
],
[
-4.5274981771586456,
-4.5274981771586456
],
[
-13.582494531475934,
-4.527498177158644
],
[
-13.582494531475936,
-8.881784197001252e-16
],
[
0,
0
]
],
"index": "ae",
"frameId": null,
"roundness": null,
"polygon": false
},
{
"id": "C8V0VrPmqft0_wEEXIh2G",
"type": "text",
"x": 624.1500441133635,
"y": -52.606675675678645,
"width": 49.27995300292969,
"height": 27,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffffff",
"fillStyle": "hachure",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"2xO--DSh2411Pyp1YG0B4"
],
"frameId": null,
"index": "af",
"roundness": null,
"seed": 648365672,
"version": 132,
"versionNonce": 664906776,
"isDeleted": false,
"boundElements": [],
"updated": 1756647186276,
"link": null,
"locked": false,
"text": "Open",
"fontSize": 20,
"fontFamily": 6,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Open",
"autoResize": true,
"lineHeight": 1.35
}
],
"appState": {
"gridSize": 20,
"gridStep": 5,
"gridModeEnabled": false,
"viewBackgroundColor": "#ffffff"
},
"files": {}
}

File diff suppressed because it is too large Load Diff

69
docs/wireframe.md Normal file
View File

@ -0,0 +1,69 @@
# Wireframes
This document contains the product wireframes drawn in Excalidraw. The editable sources live under `docs/wireframe.assets/*.excalidraw`. For easy viewing, we generate an SVG-based copy at `docs/.wireframe.md`.
<!--
Note: `.excalidraw.svg` is a special Excalidraw-flavored SVG. We keep `.excalidraw` as the editable source and export to `.svg` for documentation preview.
Refs:
- https://github.com/excalidraw/excalidraw
- https://github.com/excalidraw/svg-to-excalidraw
-->
## 1 Welcome / First screen
Purpose: let the user open a PDF quickly via drag & drop or file picker.
Route: root
Design notes:
- Central drop zone with hint text: “Drag a PDF here or click to select”.
- Minimal top bar with app name and a gear icon for settings.
- Clean layout encouraging first action.
Illustration:
![](wireframe.assets/first_screen.excalidraw)
## 1-Settings dialog
Purpose: provide basic configuration before/after opening a PDF.
Route: root --> settings
Design notes:
- Opened via gear icon in the top bar.
- Modal with simple sections (e.g., General, Display).
- Primary action to save, secondary to cancel.
Illustration:
![](wireframe.assets/with_configure_screen.excalidraw)
## PDF opened
Purpose: view and navigate the PDF; prepare for signature placement.
Route: root --> opened
Design notes:
- Main canvas shows the current page.
- Navigation: previous/next page, zoom controls near the canvas.
- Space reserved for a future “Sign” tool in the toolbar.
- drag signature onto page
Illustration:
![](wireframe.assets/with_pdf_opened.excalidraw)
---
## How to view and export
We keep links in this file pointing to `.excalidraw`. To preview the SVGs and generate `docs/.wireframe.md` with `.svg` links, run from repo root:
dart run tool/gen_view_wireframe_md.dart
This will:
- Copy `docs/wireframe.md` to `docs/.wireframe.md` and rewrite image links to `.svg`.
- Export any `*.excalidraw` under `docs/` to `*.svg` if they are new or modified.
## Next wireframes (optional)
- Save/Export result dialog and success state.

View File

@ -0,0 +1,115 @@
// The script will
// 1. Copy `docs/wireframe.md` to `docs/.wireframe.md`.
// 2. In `docs/.wireframe.md`, replace all `*.excalidraw` paths (excluding `*.excalidraw.svg`)
// to use the `.svg` extension.
// 3. Export `*.excalidraw` files to svg `*.svg` by
// `npx --no-install excalidraw-to-svg {file_path}.excalidraw`.
import 'dart:io';
void main(List<String> args) async {
final cwd = Directory.current;
final docsDir = Directory('${cwd.path}/docs');
final sourceMd = File('${docsDir.path}/wireframe.md');
final targetMd = File('${docsDir.path}/.wireframe.md');
if (!await docsDir.exists()) {
stderr.writeln('docs directory not found at: ${docsDir.path}');
exitCode = 1;
return;
}
if (!await sourceMd.exists()) {
stderr.writeln('Source markdown not found: ${sourceMd.path}');
exitCode = 1;
return;
}
// 1) Copy wireframe.md to .wireframe.md (overwrite to keep it up-to-date)
stdout.writeln('Copying ${sourceMd.path} -> ${targetMd.path}');
await targetMd.writeAsBytes(await sourceMd.readAsBytes(), flush: true);
// 2) Replace *.excalidraw (not already followed by .svg) with *.svg in the copied markdown
final content = await targetMd.readAsString();
final replaced = content.replaceAll(RegExp(r"\.excalidraw(?!\.svg)"), '.svg');
if (replaced != content) {
stdout.writeln('Updating links in ${targetMd.path} to use .svg');
await targetMd.writeAsString(replaced);
} else {
stdout.writeln('No link updates needed in ${targetMd.path}');
}
// 3) Find all *.excalidraw files under docs and export to *.svg using excalidraw-to-svg
stdout.writeln('Scanning for .excalidraw assets under ${docsDir.path}');
final excalidrawFiles = <File>[];
await for (final entity in docsDir.list(
recursive: true,
followLinks: false,
)) {
if (entity is File && entity.path.endsWith('.excalidraw')) {
excalidrawFiles.add(entity);
}
}
if (excalidrawFiles.isEmpty) {
stdout.writeln('No .excalidraw files found. Done.');
return;
}
// Verify npx availability; if missing, skip export with a clear message.
bool hasNpx = false;
try {
final npxCheck = await Process.run(
'npx',
['--version'],
runInShell: true,
workingDirectory: cwd.path,
);
hasNpx = npxCheck.exitCode == 0;
} catch (_) {
hasNpx = false;
}
if (!hasNpx) {
stderr.writeln(
'npx not found. Skipping SVG export. Install Node.js (npx) and ensure excalidraw-to-svg is available locally.',
);
return;
}
int exported = 0;
for (final src in excalidrawFiles) {
final destPath = src.path.replaceFirst(RegExp(r'\.excalidraw$'), '.svg');
final dest = File(destPath);
// Skip if up-to-date
if (await dest.exists()) {
try {
final srcStat = await src.stat();
final destStat = await dest.stat();
if (!srcStat.modified.isAfter(destStat.modified)) {
stdout.writeln('Skip (up-to-date): ${dest.path}');
continue;
}
} catch (_) {
// If stats fail, proceed to export
}
}
stdout.writeln('Exporting to SVG via excalidraw-to-svg: ${src.path}');
final result = await Process.run(
'npx',
['--no-install', 'excalidraw-to-svg', src.path],
runInShell: true,
workingDirectory: cwd.path,
);
if (result.exitCode == 0) {
stdout.writeln('Exported: ${dest.path}');
exported++;
} else {
stderr.writeln(
'Failed to export ${src.path} -> ${dest.path}\n${result.stderr}\n${result.stdout}',
);
}
}
stdout.writeln('Completed. ${exported} file(s) exported.');
}