RTL Trick
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-06-22 16:44:00 +03:30
parent ca02a621da
commit 39e18c3331
2 changed files with 637 additions and 609 deletions
+136 -120
View File
@@ -54,13 +54,13 @@
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.27.1", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-validator-identifier": "^7.27.1", "@babel/helper-validator-identifier": "^7.29.7",
"js-tokens": "^4.0.0", "js-tokens": "^4.0.0",
"picocolors": "^1.1.1" "picocolors": "^1.1.1"
}, },
@@ -69,9 +69,9 @@
} }
}, },
"node_modules/@babel/compat-data": { "node_modules/@babel/compat-data": {
"version": "7.28.5", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
"integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -79,21 +79,21 @@
} }
}, },
"node_modules/@babel/core": { "node_modules/@babel/core": {
"version": "7.28.5", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.29.7",
"@babel/generator": "^7.28.5", "@babel/generator": "^7.29.7",
"@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-compilation-targets": "^7.29.7",
"@babel/helper-module-transforms": "^7.28.3", "@babel/helper-module-transforms": "^7.29.7",
"@babel/helpers": "^7.28.4", "@babel/helpers": "^7.29.7",
"@babel/parser": "^7.28.5", "@babel/parser": "^7.29.7",
"@babel/template": "^7.27.2", "@babel/template": "^7.29.7",
"@babel/traverse": "^7.28.5", "@babel/traverse": "^7.29.7",
"@babel/types": "^7.28.5", "@babel/types": "^7.29.7",
"@jridgewell/remapping": "^2.3.5", "@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0", "convert-source-map": "^2.0.0",
"debug": "^4.1.0", "debug": "^4.1.0",
@@ -110,14 +110,14 @@
} }
}, },
"node_modules/@babel/generator": { "node_modules/@babel/generator": {
"version": "7.28.5", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
"integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.28.5", "@babel/parser": "^7.29.7",
"@babel/types": "^7.28.5", "@babel/types": "^7.29.7",
"@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28", "@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2" "jsesc": "^3.0.2"
@@ -127,14 +127,14 @@
} }
}, },
"node_modules/@babel/helper-compilation-targets": { "node_modules/@babel/helper-compilation-targets": {
"version": "7.27.2", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
"integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/compat-data": "^7.27.2", "@babel/compat-data": "^7.29.7",
"@babel/helper-validator-option": "^7.27.1", "@babel/helper-validator-option": "^7.29.7",
"browserslist": "^4.24.0", "browserslist": "^4.24.0",
"lru-cache": "^5.1.1", "lru-cache": "^5.1.1",
"semver": "^6.3.1" "semver": "^6.3.1"
@@ -144,9 +144,9 @@
} }
}, },
"node_modules/@babel/helper-globals": { "node_modules/@babel/helper-globals": {
"version": "7.28.0", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -154,29 +154,29 @@
} }
}, },
"node_modules/@babel/helper-module-imports": { "node_modules/@babel/helper-module-imports": {
"version": "7.27.1", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/traverse": "^7.27.1", "@babel/traverse": "^7.29.7",
"@babel/types": "^7.27.1" "@babel/types": "^7.29.7"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/helper-module-transforms": { "node_modules/@babel/helper-module-transforms": {
"version": "7.28.3", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
"integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-module-imports": "^7.27.1", "@babel/helper-module-imports": "^7.29.7",
"@babel/helper-validator-identifier": "^7.27.1", "@babel/helper-validator-identifier": "^7.29.7",
"@babel/traverse": "^7.28.3" "@babel/traverse": "^7.29.7"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -196,9 +196,9 @@
} }
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
"version": "7.27.1", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -206,9 +206,9 @@
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -216,9 +216,9 @@
} }
}, },
"node_modules/@babel/helper-validator-option": { "node_modules/@babel/helper-validator-option": {
"version": "7.27.1", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -226,27 +226,27 @@
} }
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.28.4", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
"integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/template": "^7.27.2", "@babel/template": "^7.29.7",
"@babel/types": "^7.28.4" "@babel/types": "^7.29.7"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.28.5", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.28.5" "@babel/types": "^7.29.7"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@@ -297,33 +297,33 @@
} }
}, },
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.27.2", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.29.7",
"@babel/parser": "^7.27.2", "@babel/parser": "^7.29.7",
"@babel/types": "^7.27.1" "@babel/types": "^7.29.7"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/traverse": { "node_modules/@babel/traverse": {
"version": "7.28.5", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
"integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.29.7",
"@babel/generator": "^7.28.5", "@babel/generator": "^7.29.7",
"@babel/helper-globals": "^7.28.0", "@babel/helper-globals": "^7.29.7",
"@babel/parser": "^7.28.5", "@babel/parser": "^7.29.7",
"@babel/template": "^7.27.2", "@babel/template": "^7.29.7",
"@babel/types": "^7.28.5", "@babel/types": "^7.29.7",
"debug": "^4.3.1" "debug": "^4.3.1"
}, },
"engines": { "engines": {
@@ -331,14 +331,14 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.28.5", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.29.7",
"@babel/helper-validator-identifier": "^7.28.5" "@babel/helper-validator-identifier": "^7.29.7"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -2570,13 +2570,16 @@
} }
}, },
"node_modules/baseline-browser-mapping": { "node_modules/baseline-browser-mapping": {
"version": "2.8.25", "version": "2.10.38",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.25.tgz", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz",
"integrity": "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==", "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"baseline-browser-mapping": "dist/cli.js" "baseline-browser-mapping": "dist/cli.cjs"
},
"engines": {
"node": ">=6.0.0"
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
@@ -2604,9 +2607,9 @@
} }
}, },
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.28.0", "version": "4.28.4",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz",
"integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -2624,11 +2627,11 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.8.25", "baseline-browser-mapping": "^2.10.38",
"caniuse-lite": "^1.0.30001754", "caniuse-lite": "^1.0.30001799",
"electron-to-chromium": "^1.5.249", "electron-to-chromium": "^1.5.376",
"node-releases": "^2.0.27", "node-releases": "^2.0.48",
"update-browserslist-db": "^1.1.4" "update-browserslist-db": "^1.2.3"
}, },
"bin": { "bin": {
"browserslist": "cli.js" "browserslist": "cli.js"
@@ -2661,9 +2664,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001754", "version": "1.0.30001799",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
"integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -2875,9 +2878,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.250", "version": "1.5.376",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz",
"integrity": "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==", "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@@ -3338,16 +3341,16 @@
} }
}, },
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.5", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0", "es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2", "hasown": "^2.0.4",
"mime-types": "^2.1.12" "mime-types": "^2.1.35"
}, },
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
@@ -3537,9 +3540,9 @@
} }
}, },
"node_modules/hasown": { "node_modules/hasown": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
@@ -3742,10 +3745,20 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.1", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
"dev": true, "dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
@@ -4292,11 +4305,14 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.27", "version": "2.0.48",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz",
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"engines": {
"node": ">=18"
}
}, },
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
@@ -5137,9 +5153,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.1.4", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
"integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -5206,9 +5222,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "7.3.3", "version": "7.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz",
"integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", "integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.27.0", "esbuild": "^0.27.0",
+129 -117
View File
@@ -1,16 +1,16 @@
import { type FC, useRef, useCallback, useState, useEffect, useMemo, useSyncExternalStore } from 'react'; import { getPaperDimensions } from "@/config/paperSizes";
import HTMLFlipBook from 'react-pageflip'; import { clx } from "@/helpers/utils";
import { type PageData } from '../types'; import type { DocumentSettings } from "@/pages/editor/store/editorStore";
import BookPage from './BookPage'; import { useBookEntranceController } from "@/pages/viewer/hooks/useBookEntranceController";
import { ArrowLeft2, ArrowRight2, Maximize4, Pause, Play, RowVertical } from 'iconsax-react'; import { useIsBrowserFullscreen } from "@/pages/viewer/hooks/useIsBrowserFullscreen";
import { getPaperDimensions } from '@/config/paperSizes'; import { useViewerViewport } from "@/pages/viewer/hooks/useViewerViewport";
import type { DocumentSettings } from '@/pages/editor/store/editorStore'; import { isLegacyIOSSafari } from "@/pages/viewer/utils/isLegacyIOSSafari";
import { useBookEntranceController } from '@/pages/viewer/hooks/useBookEntranceController'; import { toggleViewerFullscreen } from "@/pages/viewer/utils/viewerFullscreen";
import { useViewerViewport } from '@/pages/viewer/hooks/useViewerViewport'; import { ArrowLeft2, ArrowRight2, Maximize4, Pause, Play, RowVertical } from "iconsax-react";
import { useIsBrowserFullscreen } from '@/pages/viewer/hooks/useIsBrowserFullscreen'; import { type FC, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
import { toggleViewerFullscreen } from '@/pages/viewer/utils/viewerFullscreen'; import HTMLFlipBook from "react-pageflip";
import { isLegacyIOSSafari } from '@/pages/viewer/utils/isLegacyIOSSafari'; import { type PageData } from "../types";
import { clx } from '@/helpers/utils'; import BookPage from "./BookPage";
/** فاصله پیش‌فرض بین هر ورق در پخش خودکار (میلی‌ثانیه) */ /** فاصله پیش‌فرض بین هر ورق در پخش خودکار (میلی‌ثانیه) */
const AUTO_PLAY_INTERVAL_MS = 3000; const AUTO_PLAY_INTERVAL_MS = 3000;
@@ -22,19 +22,19 @@ const BOOK_TOP_GAP_MOBILE = 32;
/** نسخهٔ فعلی `page-flip.mp3` دو ضربهٔ ورق پشت‌هم دارد؛ فقط تا این سهم از طول پخش می‌شود */ /** نسخهٔ فعلی `page-flip.mp3` دو ضربهٔ ورق پشت‌هم دارد؛ فقط تا این سهم از طول پخش می‌شود */
const PAGE_FLIP_SOUND_PLAY_FRACTION = 0.5; const PAGE_FLIP_SOUND_PLAY_FRACTION = 0.5;
/** هم‌راستا با `md` تیلویند (768px) — موبایل: زیر این عرض */ /** هم‌راستا با `md` تیلویند (768px) — موبایل: زیر این عرض */
const MOBILE_QUERY = '(min-width: 768px)'; const MOBILE_QUERY = "(min-width: 768px)";
function subscribeDesktopMql(callback: () => void) { function subscribeDesktopMql(callback: () => void) {
const mq = window.matchMedia(MOBILE_QUERY); const mq = window.matchMedia(MOBILE_QUERY);
mq.addEventListener('change', callback); mq.addEventListener("change", callback);
return () => mq.removeEventListener('change', callback); return () => mq.removeEventListener("change", callback);
} }
function useIsDesktop() { function useIsDesktop() {
return useSyncExternalStore( return useSyncExternalStore(
subscribeDesktopMql, subscribeDesktopMql,
() => window.matchMedia(MOBILE_QUERY).matches, () => window.matchMedia(MOBILE_QUERY).matches,
() => true () => true,
); );
} }
@@ -42,7 +42,7 @@ function useLegacyIOSSafari() {
return useSyncExternalStore( return useSyncExternalStore(
() => () => {}, () => () => {},
isLegacyIOSSafari, isLegacyIOSSafari,
() => false () => false,
); );
} }
@@ -88,14 +88,10 @@ const AutoPlayToggleButton: FC<AutoPlayToggleButtonProps> = ({ isActive, onToggl
type="button" type="button"
onClick={onToggle} onClick={onToggle}
className="px-3 py-1.5 text-xs md:text-sm font-medium inline-flex items-center justify-center gap-1.5 rounded-full border border-gray-300 hover:bg-gray-100 active:bg-gray-200 transition-all" className="px-3 py-1.5 text-xs md:text-sm font-medium inline-flex items-center justify-center gap-1.5 rounded-full border border-gray-300 hover:bg-gray-100 active:bg-gray-200 transition-all"
aria-label={isActive ? 'توقف پخش خودکار' : 'شروع پخش خودکار'} aria-label={isActive ? "توقف پخش خودکار" : "شروع پخش خودکار"}
> >
{isActive ? ( {isActive ? <Pause color="black" size={16} className="text-gray-700 md:w-[18px] md:h-[18px]" /> : <Play color="black" size={16} className="text-gray-700 md:w-[18px] md:h-[18px]" />}
<Pause color='black' size={16} className="text-gray-700 md:w-[18px] md:h-[18px]" /> <span>{isActive ? "توقف پخش" : "پخش خودکار"}</span>
) : (
<Play color='black' size={16} className="text-gray-700 md:w-[18px] md:h-[18px]" />
)}
<span>{isActive ? 'توقف پخش' : 'پخش خودکار'}</span>
</button> </button>
); );
@@ -133,24 +129,26 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
const autoPlay = documentSettings?.autoPlay ?? false; const autoPlay = documentSettings?.autoPlay ?? false;
const pageFlipSound = documentSettings?.pageFlipSound ?? true; const pageFlipSound = documentSettings?.pageFlipSound ?? true;
const leftToRightFlip = documentSettings?.leftToRightFlip ?? false; const leftToRightFlip = documentSettings?.leftToRightFlip ?? false;
const displayStyle = documentSettings?.displayStyle ?? 'double'; /** react-pageflip از RTL پشتیبانی نمی‌کند؛ ترتیب صفحات برعکس + شروع از آخر (فقط دسکتاپ) */
const backgroundType = documentSettings?.backgroundType ?? 'color'; const isRtlBook = !leftToRightFlip && isDesktop;
const backgroundColor = documentSettings?.backgroundColor ?? '#ffffff'; const displayPages = useMemo(() => (isRtlBook ? [...pages].reverse() : pages), [pages, isRtlBook]);
const toLogicalIndex = useCallback((flipIndex: number) => (isRtlBook ? pages.length - 1 - flipIndex : flipIndex), [isRtlBook, pages.length]);
const toFlipIndex = useCallback((logicalIndex: number) => (isRtlBook ? pages.length - 1 - logicalIndex : logicalIndex), [isRtlBook, pages.length]);
const displayStyle = documentSettings?.displayStyle ?? "double";
const backgroundType = documentSettings?.backgroundType ?? "color";
const backgroundColor = documentSettings?.backgroundColor ?? "#ffffff";
const backgroundGradient = documentSettings?.backgroundGradient; const backgroundGradient = documentSettings?.backgroundGradient;
const backgroundImageUrl = documentSettings?.backgroundImageUrl ?? ''; const backgroundImageUrl = documentSettings?.backgroundImageUrl ?? "";
useEffect(() => { useEffect(() => {
setIsAutoPlayActive(autoPlay); setIsAutoPlayActive(autoPlay);
}, [autoPlay]); }, [autoPlay]);
// سایز کتاب بر اساس catalogSize (A3/A4/A5) // سایز کتاب بر اساس catalogSize (A3/A4/A5)
const { width: bookWidth, height: bookHeight } = useMemo( const { width: bookWidth, height: bookHeight } = useMemo(() => getPaperDimensions(catalogSize ?? "a4"), [catalogSize]);
() => getPaperDimensions(catalogSize ?? 'a4'),
[catalogSize]
);
const hasMultiplePages = pages.length > 1; const hasMultiplePages = pages.length > 1;
const viewport = useViewerViewport(); const viewport = useViewerViewport();
const usePortrait = !isDesktop || displayStyle === 'single'; const usePortrait = !isDesktop || displayStyle === "single";
const viewportFit = useMemo(() => { const viewportFit = useMemo(() => {
const padX = isDesktop ? 48 : 20; const padX = isDesktop ? 48 : 20;
@@ -175,7 +173,7 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
const pagePixelHeight = viewportFit.h; const pagePixelHeight = viewportFit.h;
const contentScale = viewportFit.contentScale; const contentScale = viewportFit.contentScale;
const flipKey = `${usePortrait ? 'portrait' : 'spread'}-${pagePixelWidth}-${pagePixelHeight}`; const flipKey = `${usePortrait ? "portrait" : "spread"}-${pagePixelWidth}-${pagePixelHeight}`;
const { const {
scheduleEntranceForSpread, scheduleEntranceForSpread,
@@ -190,10 +188,10 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
const commitEntranceAtCurrentSpread = useCallback(() => { const commitEntranceAtCurrentSpread = useCallback(() => {
const api = bookRef.current?.pageFlip(); const api = bookRef.current?.pageFlip();
const idx = api?.getCurrentPageIndex?.(); const idx = api?.getCurrentPageIndex?.();
if (typeof idx !== 'number' || pages.length <= 0) return; if (typeof idx !== "number" || pages.length <= 0) return;
const clamped = Math.max(0, Math.min(idx, pages.length - 1)); const flipIdx = Math.max(0, Math.min(idx, pages.length - 1));
scheduleEntranceForSpread(clamped); scheduleEntranceForSpread(toLogicalIndex(flipIdx));
}, [pages.length, scheduleEntranceForSpread]); }, [pages.length, scheduleEntranceForSpread, toLogicalIndex]);
useEffect(() => { useEffect(() => {
resetEntrance(); resetEntrance();
@@ -218,14 +216,14 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
useEffect(() => { useEffect(() => {
const base = import.meta.env.BASE_URL; const base = import.meta.env.BASE_URL;
const src = `${base}${base.endsWith('/') ? '' : '/'}sounds/page-flip.mp3`; const src = `${base}${base.endsWith("/") ? "" : "/"}sounds/page-flip.mp3`;
const audio = new Audio(src); const audio = new Audio(src);
audio.preload = 'auto'; audio.preload = "auto";
pageFlipSoundRef.current = audio; pageFlipSoundRef.current = audio;
return () => { return () => {
const trim = pageFlipSoundTrimListenerRef.current; const trim = pageFlipSoundTrimListenerRef.current;
if (trim) { if (trim) {
audio.removeEventListener('timeupdate', trim); audio.removeEventListener("timeupdate", trim);
pageFlipSoundTrimListenerRef.current = null; pageFlipSoundTrimListenerRef.current = null;
} }
audio.pause(); audio.pause();
@@ -238,7 +236,7 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
if (!audio) return; if (!audio) return;
const prevTrim = pageFlipSoundTrimListenerRef.current; const prevTrim = pageFlipSoundTrimListenerRef.current;
if (prevTrim) { if (prevTrim) {
audio.removeEventListener('timeupdate', prevTrim); audio.removeEventListener("timeupdate", prevTrim);
pageFlipSoundTrimListenerRef.current = null; pageFlipSoundTrimListenerRef.current = null;
} }
const onTimeUpdate = () => { const onTimeUpdate = () => {
@@ -248,44 +246,49 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
if (audio.currentTime >= end) { if (audio.currentTime >= end) {
audio.pause(); audio.pause();
audio.currentTime = 0; audio.currentTime = 0;
audio.removeEventListener('timeupdate', onTimeUpdate); audio.removeEventListener("timeupdate", onTimeUpdate);
pageFlipSoundTrimListenerRef.current = null; pageFlipSoundTrimListenerRef.current = null;
} }
}; };
pageFlipSoundTrimListenerRef.current = onTimeUpdate; pageFlipSoundTrimListenerRef.current = onTimeUpdate;
try { try {
audio.currentTime = 0; audio.currentTime = 0;
audio.addEventListener('timeupdate', onTimeUpdate); audio.addEventListener("timeupdate", onTimeUpdate);
void audio.play().catch(() => { void audio.play().catch(() => {
audio.removeEventListener('timeupdate', onTimeUpdate); audio.removeEventListener("timeupdate", onTimeUpdate);
pageFlipSoundTrimListenerRef.current = null; pageFlipSoundTrimListenerRef.current = null;
/* autoplay policy — معمولاً بعد از تعامل کاربر ورق می‌خورد */ /* autoplay policy — معمولاً بعد از تعامل کاربر ورق می‌خورد */
}); });
} catch { } catch {
audio.removeEventListener('timeupdate', onTimeUpdate); audio.removeEventListener("timeupdate", onTimeUpdate);
pageFlipSoundTrimListenerRef.current = null; pageFlipSoundTrimListenerRef.current = null;
} }
}, []); }, []);
const handlePageFlip = useCallback((e: FlipEvent) => { const handlePageFlip = useCallback(
(e: FlipEvent) => {
// react-pageflip شماره صفحه را به صورت 0-based برمی‌گرداند // react-pageflip شماره صفحه را به صورت 0-based برمی‌گرداند
// اما باید مطمئن شویم که در محدوده معتبر است // اما باید مطمئن شویم که در محدوده معتبر است
let pageNum = e.data; let flipIndex = e.data;
// اگر pageNum منفی است یا بزرگتر از تعداد صفحات، آن را محدود می‌کنیم // اگر flipIndex منفی است یا بزرگتر از تعداد صفحات، آن را محدود می‌کنیم
if (pageNum < 0) { if (flipIndex < 0) {
pageNum = 0; flipIndex = 0;
} else if (pageNum >= pages.length) { } else if (flipIndex >= pages.length) {
pageNum = pages.length - 1; flipIndex = pages.length - 1;
} }
const pageNum = toLogicalIndex(flipIndex);
if (pageFlipSound && lastFlipPageRef.current !== null && lastFlipPageRef.current !== pageNum) { if (pageFlipSound && lastFlipPageRef.current !== null && lastFlipPageRef.current !== pageNum) {
playPageFlipSound(); playPageFlipSound();
} }
lastFlipPageRef.current = pageNum; lastFlipPageRef.current = pageNum;
setCurrentPage(pageNum); setCurrentPage(pageNum);
}, [pages.length, pageFlipSound, playPageFlipSound]); },
[pages.length, pageFlipSound, playPageFlipSound, toLogicalIndex],
);
pageFlipHandlerRef.current = handlePageFlip; pageFlipHandlerRef.current = handlePageFlip;
@@ -296,28 +299,29 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
const syncPageIndexFromBook = useCallback(() => { const syncPageIndexFromBook = useCallback(() => {
const api = bookRef.current?.pageFlip(); const api = bookRef.current?.pageFlip();
const idx = api?.getCurrentPageIndex?.(); const idx = api?.getCurrentPageIndex?.();
if (typeof idx !== 'number' || pages.length <= 0) return; if (typeof idx !== "number" || pages.length <= 0) return;
const clamped = Math.max(0, Math.min(idx, pages.length - 1)); const flipIdx = Math.max(0, Math.min(idx, pages.length - 1));
setCurrentPage(clamped); setCurrentPage(toLogicalIndex(flipIdx));
}, [pages.length]); }, [pages.length, toLogicalIndex]);
const handleBookInit = useCallback( const handleBookInit = useCallback(
(e: PageFlipInitEvent) => { (e: PageFlipInitEvent) => {
const p = e.data?.page; const p = e.data?.page;
if (typeof p !== 'number' || pages.length <= 0) return; if (typeof p !== "number" || pages.length <= 0) return;
const clamped = Math.max(0, Math.min(p, pages.length - 1)); const flipIdx = Math.max(0, Math.min(p, pages.length - 1));
setCurrentPage(clamped); const logicalIdx = toLogicalIndex(flipIdx);
lastFlipPageRef.current = clamped; setCurrentPage(logicalIdx);
lastFlipPageRef.current = logicalIdx;
// اولین اسپرد قبل از onChangeState('read') هم باید انیمیشن ورود بگیرد // اولین اسپرد قبل از onChangeState('read') هم باید انیمیشن ورود بگیرد
scheduleEntranceForSpread(clamped); scheduleEntranceForSpread(logicalIdx);
}, },
[pages.length, scheduleEntranceForSpread], [pages.length, scheduleEntranceForSpread, toLogicalIndex],
); );
/** بعد از اتمام انیمیشن ورق، ایندکس واقعی کتاب را می‌گیریم و انیمیشن ورود را یک‌بار شروع می‌کنیم */ /** بعد از اتمام انیمیشن ورق، ایندکس واقعی کتاب را می‌گیریم و انیمیشن ورود را یک‌بار شروع می‌کنیم */
const handleChangeState = useCallback( const handleChangeState = useCallback(
(e: PageFlipStateEvent) => { (e: PageFlipStateEvent) => {
if (e.data !== 'read') return; if (e.data !== "read") return;
syncPageIndexFromBook(); syncPageIndexFromBook();
commitEntranceAtCurrentSpread(); commitEntranceAtCurrentSpread();
}, },
@@ -325,16 +329,24 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
); );
const goToNextPage = useCallback(() => { const goToNextPage = useCallback(() => {
if (currentPage < pages.length - 1) { if (currentPage >= pages.length - 1) return;
bookRef.current?.pageFlip()?.flipNext(); const api = bookRef.current?.pageFlip() as PageFlipWithFlipController | undefined;
if (isRtlBook) {
triggerFlipPrev(api);
} else {
api?.flipNext();
} }
}, [currentPage, pages.length]); }, [currentPage, pages.length, isRtlBook]);
const goToPrevPage = useCallback(() => { const goToPrevPage = useCallback(() => {
if (currentPage <= 0) return; if (currentPage <= 0) return;
const api = bookRef.current?.pageFlip() as PageFlipWithFlipController | undefined; const api = bookRef.current?.pageFlip() as PageFlipWithFlipController | undefined;
if (isRtlBook) {
api?.flipNext();
} else {
triggerFlipPrev(api); triggerFlipPrev(api);
}, [currentPage]); }
}, [currentPage, isRtlBook]);
const stopAutoPlayByUser = useCallback(() => { const stopAutoPlayByUser = useCallback(() => {
if (isAutoPlayActive) { if (isAutoPlayActive) {
@@ -344,7 +356,7 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
const toggleAutoPlay = useCallback(() => { const toggleAutoPlay = useCallback(() => {
if (pages.length <= 1) return; if (pages.length <= 1) return;
setIsAutoPlayActive(prev => !prev); setIsAutoPlayActive((prev) => !prev);
}, [pages.length]); }, [pages.length]);
const navigateToPageIndex = useCallback( const navigateToPageIndex = useCallback(
@@ -352,12 +364,13 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
const pageFlipAPI = bookRef.current?.pageFlip(); const pageFlipAPI = bookRef.current?.pageFlip();
if (!pageFlipAPI) return; if (!pageFlipAPI) return;
const clamped = Math.max(0, Math.min(targetIndex, pages.length - 1)); const logicalIdx = Math.max(0, Math.min(targetIndex, pages.length - 1));
pageFlipAPI.turnToPage(clamped); const flipIdx = toFlipIndex(logicalIdx);
lastFlipPageRef.current = clamped; pageFlipAPI.turnToPage(flipIdx);
scheduleEntranceForSpread(clamped); lastFlipPageRef.current = logicalIdx;
scheduleEntranceForSpread(logicalIdx);
}, },
[pages.length, scheduleEntranceForSpread], [pages.length, scheduleEntranceForSpread, toFlipIndex],
); );
const handleLinkClick = useCallback( const handleLinkClick = useCallback(
@@ -365,17 +378,17 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
if (pages.length <= 1) return; if (pages.length <= 1) return;
stopAutoPlayByUser(); stopAutoPlayByUser();
if (!linkUrl.startsWith('page://')) return; if (!linkUrl.startsWith("page://")) return;
const action = linkUrl.replace('page://', ''); const action = linkUrl.replace("page://", "");
if (action === 'next') { if (action === "next") {
goToNextPage(); goToNextPage();
} else if (action === 'prev') { } else if (action === "prev") {
goToPrevPage(); goToPrevPage();
} else if (action === 'first') { } else if (action === "first") {
navigateToPageIndex(0); navigateToPageIndex(0);
} else if (action === 'last') { } else if (action === "last") {
navigateToPageIndex(pages.length - 1); navigateToPageIndex(pages.length - 1);
} else { } else {
const pageNum = parseInt(action, 10); const pageNum = parseInt(action, 10);
@@ -392,40 +405,49 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
if (prevFlipKeyRef.current !== flipKey) { if (prevFlipKeyRef.current !== flipKey) {
prevFlipKeyRef.current = flipKey; prevFlipKeyRef.current = flipKey;
lastFlipPageRef.current = currentPage; lastFlipPageRef.current = currentPage;
setStartPage(currentPage); setStartPage(toFlipIndex(currentPage));
} }
}, [flipKey, currentPage]); }, [flipKey, currentPage, toFlipIndex]);
useEffect(() => {
if (pages.length <= 0) return;
const initialFlipPage = isRtlBook ? pages.length - 1 : 0;
setStartPage(initialFlipPage);
setCurrentPage(0);
lastFlipPageRef.current = 0;
}, [pages.length, pages[0]?.id, isRtlBook]);
// پخش خودکار // پخش خودکار
useEffect(() => { useEffect(() => {
if (!isAutoPlayActive || pages.length <= 1) return; if (!isAutoPlayActive || pages.length <= 1) return;
const timer = setInterval(() => { const timer = setInterval(() => {
if (currentPage >= pages.length - 1) { if (currentPage >= pages.length - 1) {
bookRef.current?.pageFlip()?.turnToPage?.(0); const firstFlipPage = isRtlBook ? pages.length - 1 : 0;
bookRef.current?.pageFlip()?.turnToPage?.(firstFlipPage);
setCurrentPage(0); setCurrentPage(0);
lastFlipPageRef.current = 0; lastFlipPageRef.current = 0;
return; return;
} }
bookRef.current?.pageFlip()?.flipNext(); const api = bookRef.current?.pageFlip() as PageFlipWithFlipController | undefined;
if (isRtlBook) {
triggerFlipPrev(api);
} else {
api?.flipNext();
}
}, AUTO_PLAY_INTERVAL_MS); }, AUTO_PLAY_INTERVAL_MS);
return () => clearInterval(timer); return () => clearInterval(timer);
}, [isAutoPlayActive, currentPage, pages.length]); }, [isAutoPlayActive, currentPage, pages.length, isRtlBook]);
return ( return (
<div <div className="flex flex-col items-center gap-4 md:gap-6 w-full h-full max-md:min-h-0 max-md:max-w-full max-md:overflow-x-hidden max-md:pb-[env(safe-area-inset-bottom,0px)]" dir="rtl">
className="flex flex-col items-center gap-4 md:gap-6 w-full h-full max-md:min-h-0 max-md:max-w-full max-md:overflow-x-hidden max-md:pb-[env(safe-area-inset-bottom,0px)]" <div className="relative w-full flex flex-col flex-1 max-md:min-h-0 pt-8 md:pt-12 min-h-0 overflow-hidden">
dir="rtl"
>
<div
className="relative w-full flex flex-col flex-1 max-md:min-h-0 pt-8 md:pt-12 min-h-0 overflow-hidden"
>
<div className="relative flex flex-1 min-h-0 w-full items-center justify-center"> <div className="relative flex flex-1 min-h-0 w-full items-center justify-center">
{/* Border و Shadow عمودی در وسط (فقط حالت دوصفحه‌ای دسکتاپ) */} {/* Border و Shadow عمودی در وسط (فقط حالت دوصفحه‌ای دسکتاپ) */}
{!usePortrait && ( {!usePortrait && (
<div <div
className="hidden md:block absolute inset-y-0 left-1/2 -translate-x-1/2 w-px border-r border-gray-300 pointer-events-none z-50" className="hidden md:block absolute inset-y-0 left-1/2 -translate-x-1/2 w-px border-r border-gray-300 pointer-events-none z-50"
style={{ style={{
boxShadow: '-5px 0 15px rgba(0, 0, 0, 0.15), 5px 0 15px rgba(0, 0, 0, 0.15)', boxShadow: "-5px 0 15px rgba(0, 0, 0, 0.15), 5px 0 15px rgba(0, 0, 0, 0.15)",
}} }}
/> />
)} )}
@@ -437,12 +459,10 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
dir="ltr" dir="ltr"
className="max-w-full flex justify-center shrink-0 rounded-sm" className="max-w-full flex justify-center shrink-0 rounded-sm"
style={{ style={{
overflow: 'hidden', overflow: "hidden",
width: usePortrait ? pagePixelWidth : pagePixelWidth * 2, width: usePortrait ? pagePixelWidth : pagePixelWidth * 2,
height: pagePixelHeight, height: pagePixelHeight,
boxShadow: isDesktop boxShadow: isDesktop ? undefined : "0 8px 32px rgba(0, 0, 0, 0.14), 0 2px 8px rgba(0, 0, 0, 0.08)",
? undefined
: '0 8px 32px rgba(0, 0, 0, 0.14), 0 2px 8px rgba(0, 0, 0, 0.08)',
}} }}
> >
<HTMLFlipBook <HTMLFlipBook
@@ -469,13 +489,13 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
startZIndex={1} startZIndex={1}
showPageCorners={hasMultiplePages} showPageCorners={hasMultiplePages}
disableFlipByClick={true} disableFlipByClick={true}
className={clx('flipbook-container mx-auto max-w-full', legacyIOS && 'flipbook-legacy-ios')} className={clx("flipbook-container mx-auto max-w-full", legacyIOS && "flipbook-legacy-ios")}
style={{}} style={{}}
onFlip={onFlipForwarded} onFlip={onFlipForwarded}
onInit={handleBookInit} onInit={handleBookInit}
onChangeState={handleChangeState} onChangeState={handleChangeState}
> >
{pages.map((page) => ( {displayPages.map((page) => (
<BookPage <BookPage
key={page.id} key={page.id}
page={page} page={page}
@@ -512,9 +532,9 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
}} }}
disabled={leftToRightFlip ? currentPage <= 0 : currentPage >= pages.length - 1} disabled={leftToRightFlip ? currentPage <= 0 : currentPage >= pages.length - 1}
className="p-2 md:p-2 min-h-11 min-w-11 md:min-h-0 md:min-w-0 inline-flex items-center justify-center rounded-full hover:bg-gray-100 active:bg-gray-200 disabled:opacity-30 disabled:cursor-not-allowed transition-all" className="p-2 md:p-2 min-h-11 min-w-11 md:min-h-0 md:min-w-0 inline-flex items-center justify-center rounded-full hover:bg-gray-100 active:bg-gray-200 disabled:opacity-30 disabled:cursor-not-allowed transition-all"
aria-label={leftToRightFlip ? 'صفحه قبل' : 'صفحه بعد'} aria-label={leftToRightFlip ? "صفحه قبل" : "صفحه بعد"}
> >
<ArrowRight2 color='black' size={20} className="text-gray-700 md:w-6 md:h-6" /> <ArrowRight2 color="black" size={20} className="text-gray-700 md:w-6 md:h-6" />
</button> </button>
</> </>
)} )}
@@ -526,8 +546,6 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
</span> </span>
</div> </div>
{hasMultiplePages && ( {hasMultiplePages && (
<> <>
{/* دکمه چپ: فارسی = صفحه قبل | لاتین = صفحه بعد */} {/* دکمه چپ: فارسی = صفحه قبل | لاتین = صفحه بعد */}
@@ -543,29 +561,23 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
}} }}
disabled={leftToRightFlip ? currentPage >= pages.length - 1 : currentPage <= 0} disabled={leftToRightFlip ? currentPage >= pages.length - 1 : currentPage <= 0}
className="p-2 md:p-2 min-h-11 min-w-11 md:min-h-0 md:min-w-0 inline-flex items-center justify-center rounded-full hover:bg-gray-100 active:bg-gray-200 disabled:opacity-30 disabled:cursor-not-allowed transition-all" className="p-2 md:p-2 min-h-11 min-w-11 md:min-h-0 md:min-w-0 inline-flex items-center justify-center rounded-full hover:bg-gray-100 active:bg-gray-200 disabled:opacity-30 disabled:cursor-not-allowed transition-all"
aria-label={leftToRightFlip ? 'صفحه بعد' : 'صفحه قبل'} aria-label={leftToRightFlip ? "صفحه بعد" : "صفحه قبل"}
> >
<ArrowLeft2 color='black' size={20} className="text-gray-700 md:w-6 md:h-6" /> <ArrowLeft2 color="black" size={20} className="text-gray-700 md:w-6 md:h-6" />
</button> </button>
</> </>
)} )}
{hasMultiplePages && autoPlay && ( {hasMultiplePages && autoPlay && <AutoPlayToggleButton isActive={isAutoPlayActive} onToggle={toggleAutoPlay} />}
<AutoPlayToggleButton isActive={isAutoPlayActive} onToggle={toggleAutoPlay} />
)}
<button <button
type="button" type="button"
onClick={toggleViewerFullscreen} onClick={toggleViewerFullscreen}
className="p-2 md:p-2 min-h-11 min-w-11 md:min-h-0 md:min-w-0 inline-flex items-center justify-center rounded-full hover:bg-gray-100 active:bg-gray-200 transition-all" className="p-2 md:p-2 min-h-11 min-w-11 md:min-h-0 md:min-w-0 inline-flex items-center justify-center rounded-full hover:bg-gray-100 active:bg-gray-200 transition-all"
aria-label={isBrowserFullscreen ? 'خروج از تمام‌صفحه' : 'تمام‌صفحه مرورگر'} aria-label={isBrowserFullscreen ? "خروج از تمام‌صفحه" : "تمام‌صفحه مرورگر"}
title={isBrowserFullscreen ? 'خروج از تمام‌صفحه' : 'تمام‌صفحه مرورگر'} title={isBrowserFullscreen ? "خروج از تمام‌صفحه" : "تمام‌صفحه مرورگر"}
> >
{isBrowserFullscreen ? ( {isBrowserFullscreen ? <RowVertical color="black" size={20} className="text-gray-700 md:w-6 md:h-6" /> : <Maximize4 color="black" size={20} className="text-gray-700 md:w-6 md:h-6" />}
<RowVertical color="black" size={20} className="text-gray-700 md:w-6 md:h-6" />
) : (
<Maximize4 color="black" size={20} className="text-gray-700 md:w-6 md:h-6" />
)}
</button> </button>
</div> </div>
</div> </div>