title page + fav
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-06-01 10:29:00 +03:30
parent 9d8dd549dc
commit da0069f2bc
10 changed files with 270 additions and 268 deletions
+33 -91
View File
@@ -1,91 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 961.6 222.5" style="enable-background:new 0 0 961.6 222.5;" xml:space="preserve">
<style type="text/css">
.st0{fill:#1D1D1B;}
</style>
<g>
<g>
<g>
<g>
<path class="st0" d="M246,180.4h-8.7v24.3h-3.6v-24.3H225v-2.9h21V180.4z"/>
<path class="st0" d="M279.1,205c-2.7,0-5-0.9-6.7-2.7s-2.6-4.2-2.6-7.2v-0.6c0-2,0.4-3.8,1.1-5.4s1.8-2.8,3.2-3.7
s2.9-1.3,4.5-1.3c2.6,0,4.7,0.9,6.1,2.6c1.5,1.7,2.2,4.2,2.2,7.4v1.4h-13.7c0,2,0.6,3.6,1.7,4.8s2.5,1.8,4.2,1.8
c1.2,0,2.3-0.2,3.1-0.7s1.6-1.2,2.2-2l2.1,1.6C285,203.7,282.5,205,279.1,205z M278.7,186.9c-1.4,0-2.6,0.5-3.5,1.5
s-1.5,2.4-1.8,4.3h10.1v-0.3c-0.1-1.8-0.6-3.1-1.4-4.1C281.3,187.4,280.1,186.9,278.7,186.9z"/>
<path class="st0" d="M320.4,202.2c1.2,0,2.3-0.4,3.2-1.1c0.9-0.7,1.4-1.7,1.5-2.8h3.3c-0.1,1.2-0.5,2.3-1.2,3.3
s-1.7,1.9-2.9,2.5c-1.2,0.6-2.5,0.9-3.9,0.9c-2.8,0-4.9-0.9-6.6-2.8c-1.6-1.8-2.4-4.3-2.4-7.5v-0.6c0-2,0.4-3.7,1.1-5.2
s1.8-2.7,3.1-3.6c1.4-0.8,2.9-1.3,4.8-1.3c2.3,0,4.1,0.7,5.6,2c1.5,1.4,2.3,3.1,2.4,5.3h-3.3c-0.1-1.3-0.6-2.4-1.5-3.2
s-2-1.3-3.3-1.3c-1.8,0-3.1,0.6-4.1,1.9s-1.4,3.1-1.4,5.5v0.7c0,2.3,0.5,4.1,1.4,5.4C317.2,201.6,318.6,202.2,320.4,202.2z"/>
<path class="st0" d="M357,186.9c1.5-1.9,3.5-2.8,6-2.8c4.3,0,6.4,2.4,6.5,7.2v13.4H366v-13.4c0-1.5-0.3-2.5-1-3.2s-1.7-1-3.1-1
c-1.1,0-2.1,0.3-3,0.9s-1.5,1.4-2,2.4v14.4h-3.4v-28.7h3.5V186.9z"/>
<path class="st0" d="M399.1,184.5l0.1,2.5c1.5-1.9,3.6-2.9,6.1-2.9c4.3,0,6.4,2.4,6.5,7.2v13.4h-3.5v-13.4c0-1.5-0.3-2.5-1-3.2
s-1.7-1-3.1-1c-1.1,0-2.1,0.3-3,0.9s-1.5,1.4-2,2.4v14.4h-3.5v-20.2h3.4V184.5z"/>
<path class="st0" d="M437.3,194.4c0-2,0.4-3.8,1.2-5.3c0.8-1.6,1.9-2.8,3.2-3.7s3-1.3,4.8-1.3c2.8,0,5,1,6.7,2.9
s2.5,4.4,2.5,7.6v0.2c0,2-0.4,3.7-1.1,5.3s-1.8,2.8-3.2,3.7c-1.4,0.9-3,1.3-4.8,1.3c-2.7,0-5-1-6.7-2.9s-2.5-4.4-2.5-7.6v-0.2
H437.3z M440.8,194.8c0,2.2,0.5,4,1.6,5.4c1,1.4,2.4,2,4.2,2c1.8,0,3.1-0.7,4.2-2.1s1.6-3.3,1.6-5.8c0-2.2-0.5-4-1.6-5.4
s-2.5-2.1-4.2-2.1s-3.1,0.7-4.1,2C441.3,190.3,440.8,192.3,440.8,194.8z"/>
<path class="st0" d="M485.1,204.7h-3.5V176h3.5V204.7z"/>
<path class="st0" d="M511,194.4c0-2,0.4-3.8,1.2-5.3c0.8-1.5,1.9-2.8,3.2-3.7c1.4-0.9,3-1.3,4.8-1.3c2.8,0,5,1,6.7,2.9
s2.5,4.4,2.5,7.6v0.2c0,2-0.4,3.7-1.1,5.3s-1.8,2.8-3.2,3.7s-3,1.3-4.8,1.3c-2.7,0-5-1-6.7-2.9c-1.7-1.9-2.5-4.4-2.5-7.6v-0.2
H511z M514.4,194.8c0,2.2,0.5,4,1.6,5.4c1,1.4,2.4,2,4.2,2s3.2-0.7,4.2-2.1c1-1.4,1.5-3.3,1.5-5.8c0-2.2-0.5-4-1.6-5.4
s-2.5-2.1-4.2-2.1s-3.1,0.7-4.1,2C515,190.3,514.4,192.3,514.4,194.8z"/>
<path class="st0" d="M554.1,194.4c0-3.2,0.7-5.7,2.2-7.5s3.4-2.8,5.8-2.8c2.5,0,4.4,0.9,5.8,2.6l0.2-2.2h3.2v19.7
c0,2.6-0.8,4.7-2.3,6.2c-1.6,1.5-3.6,2.3-6.2,2.3c-1.5,0-2.9-0.3-4.3-0.9c-1.4-0.6-2.5-1.5-3.2-2.6l1.8-2.1
c1.5,1.8,3.3,2.7,5.4,2.7c1.7,0,3-0.5,3.9-1.4s1.4-2.3,1.4-4v-1.7c-1.4,1.6-3.3,2.4-5.7,2.4s-4.3-1-5.7-2.9
S554.1,197.7,554.1,194.4z M557.6,194.8c0,2.3,0.5,4.1,1.4,5.4c0.9,1.3,2.2,2,3.9,2c2.2,0,3.8-1,4.8-3V190
c-1.1-1.9-2.6-2.9-4.8-2.9c-1.7,0-3,0.7-3.9,2C558.1,190.3,557.6,192.2,557.6,194.8z"/>
<path class="st0" d="M604.3,199.6l4.7-15.1h3.7l-8.1,23.3c-1.3,3.4-3.3,5-6,5l-0.7-0.1l-1.3-0.2v-2.8l0.9,0.1
c1.2,0,2.1-0.2,2.7-0.7c0.7-0.5,1.2-1.3,1.6-2.6l0.8-2.1l-7.2-20h3.8L604.3,199.6z"/>
<path class="st0" d="M688.3,196c-0.3,2.9-1.4,5.1-3.2,6.7c-1.8,1.6-4.2,2.3-7.1,2.3c-3.2,0-5.8-1.2-7.7-3.5s-2.9-5.4-2.9-9.2
v-2.6c0-2.5,0.5-4.8,1.4-6.7s2.2-3.4,3.8-4.4s3.6-1.5,5.8-1.5c2.9,0,5.2,0.8,6.9,2.4c1.7,1.6,2.8,3.8,3,6.7h-3.6
c-0.3-2.2-1-3.7-2-4.7s-2.5-1.5-4.4-1.5c-2.3,0-4.1,0.8-5.4,2.5c-1.3,1.7-2,4.1-2,7.2v2.6c0,3,0.6,5.3,1.8,7s3,2.6,5.2,2.6
c2,0,3.5-0.5,4.6-1.4s1.8-2.5,2.1-4.7h3.7V196z"/>
<path class="st0" d="M713,194.4c0-2,0.4-3.8,1.2-5.3s1.9-2.8,3.2-3.7c1.4-0.9,3-1.3,4.8-1.3c2.8,0,5,1,6.7,2.9s2.5,4.4,2.5,7.6
v0.2c0,2-0.4,3.7-1.1,5.3s-1.8,2.8-3.2,3.7s-3,1.3-4.8,1.3c-2.7,0-5-1-6.7-2.9s-2.5-4.4-2.5-7.6v-0.2H713z M716.5,194.8
c0,2.2,0.5,4,1.6,5.4c1,1.4,2.4,2,4.2,2s3.2-0.7,4.2-2.1c1-1.4,1.5-3.3,1.5-5.8c0-2.2-0.5-4-1.6-5.4s-2.5-2.1-4.2-2.1
s-3.1,0.7-4.1,2C717,190.3,716.5,192.3,716.5,194.8z"/>
<path class="st0" d="M760.3,184.5l0.1,2.2c1.5-1.7,3.5-2.6,6-2.6c2.8,0,4.8,1.1,5.8,3.2c0.7-1,1.5-1.8,2.6-2.4
c1.1-0.6,2.4-0.9,3.8-0.9c4.4,0,6.7,2.3,6.8,7v13.5h-3.5v-13.3c0-1.4-0.3-2.5-1-3.2s-1.8-1.1-3.3-1.1c-1.3,0-2.3,0.4-3.2,1.1
c-0.8,0.8-1.3,1.8-1.5,3.1v13.4h-3.5v-13.2c0-2.9-1.4-4.4-4.3-4.4c-2.3,0-3.8,1-4.7,2.9v14.8h-3.5v-20.2h3.4V184.5z"/>
<path class="st0" d="M828.9,194.8c0,3.1-0.7,5.6-2.1,7.4c-1.4,1.9-3.3,2.8-5.7,2.8c-2.5,0-4.4-0.8-5.8-2.3v9.7h-3.5v-28h3.2
l0.2,2.2c1.4-1.7,3.4-2.6,5.9-2.6c2.4,0,4.3,0.9,5.8,2.7c1.4,1.8,2.1,4.4,2.1,7.6v0.5H828.9z M825.5,194.4
c0-2.3-0.5-4.1-1.5-5.4c-1-1.3-2.3-2-4-2c-2.1,0-3.7,0.9-4.7,2.8v9.7c1,1.8,2.6,2.8,4.7,2.8c1.7,0,3-0.7,4-2
S825.5,197,825.5,194.4z"/>
<path class="st0" d="M867,204.7c-0.2-0.4-0.4-1.1-0.5-2.1c-1.6,1.7-3.5,2.5-5.8,2.5c-2,0-3.6-0.6-4.9-1.7s-1.9-2.6-1.9-4.3
c0-2.1,0.8-3.7,2.4-4.9c1.6-1.2,3.9-1.7,6.8-1.7h3.4v-1.7c0-1.2-0.4-2.2-1.1-2.9c-0.7-0.7-1.8-1.1-3.2-1.1
c-1.2,0-2.3,0.3-3.1,0.9c-0.8,0.6-1.3,1.4-1.3,2.3h-3.5c0-1,0.4-2,1.1-2.9c0.7-0.9,1.7-1.7,2.9-2.2s2.6-0.8,4-0.8
c2.3,0,4.2,0.6,5.5,1.7c1.3,1.2,2,2.8,2.1,4.8v9.3c0,1.9,0.2,3.3,0.7,4.4v0.3H867V204.7z M861.3,202c1.1,0,2.1-0.3,3.1-0.8
c1-0.6,1.7-1.3,2.1-2.2v-4.1h-2.7c-4.2,0-6.4,1.2-6.4,3.7c0,1.1,0.4,1.9,1.1,2.5C859.2,201.7,860.2,202,861.3,202z"/>
<path class="st0" d="M899.9,184.5l0.1,2.5c1.5-1.9,3.6-2.9,6.1-2.9c4.3,0,6.4,2.4,6.5,7.2v13.4h-3.5v-13.4c0-1.5-0.3-2.5-1-3.2
s-1.7-1-3.1-1c-1.1,0-2.1,0.3-3,0.9s-1.5,1.4-2,2.4v14.4h-3.5v-20.2h3.4V184.5z"/>
<path class="st0" d="M945.6,199.6l4.7-15.1h3.7l-8.1,23.3c-1.3,3.4-3.3,5-6,5l-0.7-0.1l-1.3-0.2v-2.8l0.9,0.1
c1.2,0,2.1-0.2,2.7-0.7c0.7-0.5,1.2-1.3,1.6-2.6l0.8-2.1l-7.2-20h3.8L945.6,199.6z"/>
</g>
</g>
<path class="st0" d="M221.6,148.7V44.1h29.5c9.1,0,17.1,2,24.1,6s12.4,9.7,16.2,17.2c3.8,7.4,5.7,15.9,5.8,25.6v6.7
c0,9.9-1.9,18.5-5.7,25.9c-3.8,7.4-9.2,13.1-16.3,17.1c-7.1,4-15.3,6-24.7,6.1L221.6,148.7L221.6,148.7z M235.4,55.4v82h14.5
c10.6,0,18.9-3.3,24.8-9.9c5.9-6.6,8.9-16,8.9-28.3v-6.1c0-11.9-2.8-21.1-8.4-27.7s-13.5-9.9-23.7-10L235.4,55.4L235.4,55.4z"/>
<path class="st0" d="M627.1,148.7h-13.9l-52.7-80.6v80.6h-13.9V44.1h13.9l52.8,81v-81H627v104.6H627.1z"/>
<path class="st0" d="M900.9,100.1l-12.8,13.3v35.3h-13.8V44.1h13.8v51.7l46.5-51.7h16.7l-41.2,46.2l44.4,58.4H938L900.9,100.1z"/>
<polygon class="st0" points="423.2,61.1 455.1,148.7 469.2,148.7 429.2,44.1 417.1,44.1 377.2,148.7 391.4,148.7 "/>
<polygon class="st0" points="750.6,61.1 782.6,148.7 796.7,148.7 756.7,44.1 744.6,44.1 704.7,148.7 718.9,148.7 "/>
</g>
<path class="st0" d="M132.1,140.4c0,0-26.1,25.2-48,46.5v28.9c-2.2,1.7,77.6-75.4,77.6-75.4L84.1,59v31L132.1,140.4z"/>
<g>
<polygon class="st0" points="83.7,157.5 6.5,75.1 7.5,74.2 84.7,156.6 "/>
<polygon class="st0" points="83.7,149.8 13.6,75.2 14.6,74.3 84.6,148.9 "/>
<polygon class="st0" points="83.7,142.1 20.7,75.4 21.7,74.4 84.6,141.1 "/>
<polygon class="st0" points="83.6,134.3 27.8,75.5 28.7,74.6 84.6,133.4 "/>
<polygon class="st0" points="83.6,126.6 34.9,75.6 35.8,74.7 84.6,125.7 "/>
</g>
<g>
<polygon class="st0" points="8,75.5 7.1,74.5 83.7,0 84.6,0.9 "/>
<polygon class="st0" points="15.2,75.5 14.3,74.6 83.6,7 84.5,7.9 "/>
<polygon class="st0" points="22.4,75.6 21.4,74.7 83.6,14 84.5,14.9 "/>
<polygon class="st0" points="29.5,75.6 28.6,74.7 83.5,21 84.4,22 "/>
<polygon class="st0" points="36.7,75.7 35.7,74.8 83.5,28 84.4,29 "/>
</g>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 229.9 36.8">
<defs>
<style>
.cls-1 {
fill: #000;
}
.cls-1, .cls-2 {
stroke-width: 0px;
}
.cls-2 {
fill: #231f20;
}
</style>
</defs>
<polygon class="cls-2" points="129.6 6.2 140.6 36.2 145.4 36.2 131.7 .4 127.6 .4 113.9 36.2 118.8 36.2 129.6 6.2"/>
<path class="cls-2" d="M64.8,20.1h-12.1v-3.8h12.1v3.8Z"/>
<path class="cls-1" d="M10.1,12.5v-4.1c.4,0,.9-.1,1.3-.1,4.1,0,8.3,0,12.4,0,6.2,0,10.5,4.4,10.4,10.3,0,5.7-4.5,10-10.5,10.1-2.7,0-6.5,0-9.3,0v3.5c5.1-.4,11.4,1.1,16.3-1.9,4.7-2.9,8.1-9.6,6.7-14.7-1.5-5.7-7.3-10.5-13.1-10.6-4.1,0-8.2,0-12.2,0-.6,0-1.3,0-2,0V.4c3.1,0,6.2,0,9.3,0,2.8,0,5.7,0,8.5.5,8.4,1.6,14.4,9.3,14.2,17.7-.3,8.8-6.5,16.3-15.1,17.3-5.5.6-11.1.1-16.8.1V15.8h4.1v8.2c3.6,0,8,.3,11.4,0,2.3-.3,4.5-3.8,4.2-6.4s-3.1-5-5.6-5.1c-4,0-8,0-12.1,0-.6,0-1.3,0-2,0Z"/>
<path class="cls-1" d="M5.2,36.1H.1v-1.7c0-.6.1-1,.3-1.2.2-.3.6-.4,1-.4s.4,0,.6.2c.2.1.3.3.4.6,0-.3.2-.5.4-.7.2-.2.5-.2.8-.2.5,0,.8.1,1.1.4s.4.7.4,1.3v1.8ZM2.3,35.5v-1c0-.3,0-.5-.2-.7-.1-.2-.3-.3-.6-.3s-.5,0-.6.2c-.1.2-.2.4-.2.7v1h1.6ZM2.8,35.5h1.8v-1.1c0-.3,0-.6-.2-.7-.2-.2-.4-.3-.7-.3-.6,0-.9.3-.9,1v1.1Z"/>
<path class="cls-1" d="M2.7,29.9l-2.6-1.3v-.8l3.2,1.8h1.9v.7h-1.9l-3.2,1.8v-.8l2.6-1.3Z"/>
<path class="cls-1" d="M5.1,24.7H0v-1.4c0-.4,0-.8.3-1.2.2-.3.5-.6.8-.8.4-.2.8-.3,1.2-.3h.3c.5,0,.9,0,1.3.3s.6.5.8.8c.2.3.3.7.3,1.2v1.4ZM.6,24h4v-.7c0-.5-.2-.9-.5-1.2-.3-.3-.8-.4-1.4-.4h-.3c-.6,0-1,.1-1.4.4-.3.3-.5.7-.5,1.2v.8Z"/>
<path class="cls-1" d="M5.1,10.6v.7l-3.9,2.6h3.9v.7H0v-.7l4-2.6H0v-.7h5.1Z"/>
<path class="cls-1" d="M2.7,2.6l.6.6h1.7v.7H0v-.7h2.5L0,1V.2l2.3,2L5.1,0v.8l-2.4,1.8Z"/>
<polygon class="cls-1" points=".8 17.9 5.1 16.3 5.1 15.6 0 17.6 0 18.2 5.1 20.1 5.1 19.4 .8 17.9"/>
<polygon class="cls-1" points=".8 7.3 5.1 5.8 5.1 5.1 0 7 0 7.6 5.1 9.6 5.1 8.9 .8 7.3"/>
<g>
<path class="cls-2" d="M82.1,22.3v14h-4.7V.5h13.2c3.9,0,7,1,9.2,3,2.2,2,3.3,4.6,3.3,7.9s-1.1,6.1-3.3,8c-2.2,1.9-5.3,2.8-9.3,2.8h-8.4ZM82.1,18.4h8.5c2.5,0,4.5-.6,5.8-1.8,1.3-1.2,2-2.9,2-5.2s-.7-3.8-2-5.1c-1.3-1.3-3.2-1.9-5.5-2h-8.7v14.1Z"/>
<path class="cls-2" d="M187.8,31.6c-1.2,1.7-2.9,3-5.1,3.9-2.2.9-4.7,1.3-7.6,1.3s-5.5-.7-7.8-2.1c-2.3-1.4-4-3.3-5.2-5.8s-1.9-5.4-1.9-8.8v-3.1c0-5.4,1.3-9.6,3.8-12.5,2.5-3,6-4.4,10.6-4.4s6.7,1,9,2.9c2.3,1.9,3.7,4.6,4.2,8.1h-4.7c-.9-4.8-3.7-7.1-8.5-7.1s-5.6,1.1-7.2,3.3c-1.6,2.2-2.5,5.4-2.5,9.7v2.9c0,4,.9,7.2,2.8,9.6,1.8,2.4,4.3,3.6,7.4,3.6s3.3-.2,4.6-.6c1.3-.4,2.4-1.1,3.3-2v-8h-8.3v-3.9h13v13.1Z"/>
<path class="cls-2" d="M227.4,19.7h-15.5v12.7h18v3.9h-22.7V.5h22.5v3.9h-17.8v11.5h15.5v3.9Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

+14
View File
@@ -0,0 +1,14 @@
import { useEffect } from 'react'
export const DEFAULT_PAGE_TITLE = 'کاتالوگ دیجیتال | داناک'
const BRAND_SUFFIX = ' | داناک'
export function formatPageTitle(pageTitle: string): string {
return `${pageTitle}${BRAND_SUFFIX}`
}
export function usePageTitle(pageTitle: string) {
useEffect(() => {
document.title = formatPageTitle(pageTitle)
}, [pageTitle])
}
+2
View File
@@ -1,10 +1,12 @@
import GridWrapper from "@/components/GridWrapper";
import { usePageTitle } from "@/hooks/usePageTitle";
import { type FC } from "react";
import { useGetCatalog } from "../home/hooks/useHomeData";
import ButtonAddCatalogue from "./components/ButtonAddCatalogue";
import CatalogueItem from "./components/CatalogueItem";
const CatalogueList: FC = () => {
usePageTitle("لیست کاتالوگ‌ها");
const { data, refetch } = useGetCatalog();
return (
+2
View File
@@ -1,3 +1,4 @@
import { usePageTitle } from '@/hooks/usePageTitle'
import { useMemo, useState, type FC } from 'react'
import Button from '@/components/Button'
import DatePicker from '@/components/DatePicker'
@@ -17,6 +18,7 @@ const pageCountOptions = Array.from({ length: 20 }, (_, index) => {
})
const DesignerRequest: FC = () => {
usePageTitle('درخواست طراحی')
const [catalogTitle, setCatalogTitle] = useState('')
const [, setDeliveryDate] = useState('')
const [pageCount, setPageCount] = useState('1')
+126 -103
View File
@@ -1,118 +1,141 @@
import { type FC, useEffect, useRef, useState } from 'react'
import Konva from 'konva'
import { clx } from '@/helpers/utils'
import EditorSidebar from './components/EditorSidebar'
import EditorCanvas from './components/EditorCanvas'
import LayersPanel from './components/LayersPanel'
import { useGetCatalogById, useUpdateCatalog } from '../home/hooks/useHomeData'
import { useParams } from 'react-router-dom'
import { useEditorStore } from './store/editorStore'
import { clx } from "@/helpers/utils";
import { usePageTitle } from "@/hooks/usePageTitle";
import Konva from "konva";
import { type FC, useEffect, useRef, useState } from "react";
import { useParams } from "react-router-dom";
import { useGetCatalogById, useUpdateCatalog } from "../home/hooks/useHomeData";
import EditorCanvas from "./components/EditorCanvas";
import EditorSidebar from "./components/EditorSidebar";
import LayersPanel from "./components/LayersPanel";
import { useEditorStore } from "./store/editorStore";
const Editor: FC = () => {
const { id } = useParams();
const { id } = useParams()
// Konva 10 با legacyTextRendering=false متن را با baseline جدید می‌کشد ولی getWidth/getHeight
// و کادر ترنسفورمر هنوز بر اساس جعبهٔ قدیمی‌اند؛ نتیجه: متن از کادر آبی بیرون می‌زند.
useEffect(() => {
const prev = Konva.legacyTextRendering;
Konva.legacyTextRendering = true;
return () => {
Konva.legacyTextRendering = prev;
};
}, []);
const [isLayersPanelOpen, setIsLayersPanelOpen] = useState(false);
const [isInitialContentReady, setIsInitialContentReady] = useState(false);
const loadedCatalogIdRef = useRef<string | null>(null);
const skipNextAutosaveRef = useRef(true);
const {
loadPages,
resetEditor,
pages,
documentSettings,
gallery,
objects,
currentPageId,
} = useEditorStore();
const { data, isFetched } = useGetCatalogById(id!);
const catalogName = data?.data?.name;
usePageTitle(
catalogName ? `ویرایش کاتالوگ ${catalogName}` : "ویرایش کاتالوگ",
);
const { scheduleUpdate, cancelPendingUpdate, isSaving } = useUpdateCatalog();
// Konva 10 با legacyTextRendering=false متن را با baseline جدید می‌کشد ولی getWidth/getHeight
// و کادر ترنسفورمر هنوز بر اساس جعبهٔ قدیمی‌اند؛ نتیجه: متن از کادر آبی بیرون می‌زند.
useEffect(() => {
const prev = Konva.legacyTextRendering
Konva.legacyTextRendering = true
return () => {
Konva.legacyTextRendering = prev
}
}, [])
const [isLayersPanelOpen, setIsLayersPanelOpen] = useState(false)
const [isInitialContentReady, setIsInitialContentReady] = useState(false)
const loadedCatalogIdRef = useRef<string | null>(null)
const skipNextAutosaveRef = useRef(true)
const { loadPages, resetEditor, pages, documentSettings, gallery, objects, currentPageId } = useEditorStore()
const { data, isFetched } = useGetCatalogById(id!)
const { scheduleUpdate, cancelPendingUpdate, isSaving } = useUpdateCatalog()
// هنگام تعویض کاتالوگ: لغو ذخیرهٔ معلق، پاک‌سازی store، و منتظر بارگذاری مجدد بمان
useEffect(() => {
cancelPendingUpdate();
loadedCatalogIdRef.current = null;
skipNextAutosaveRef.current = true;
setIsInitialContentReady(false);
resetEditor();
}, [id, cancelPendingUpdate, resetEditor]);
// هنگام تعویض کاتالوگ: لغو ذخیرهٔ معلق، پاک‌سازی store، و منتظر بارگذاری مجدد بمان
useEffect(() => {
cancelPendingUpdate()
loadedCatalogIdRef.current = null
skipNextAutosaveRef.current = true
setIsInitialContentReady(false)
resetEditor()
}, [id, cancelPendingUpdate, resetEditor])
// بارگذاری محتوا فقط یک‌بار برای هر id (نه در هر refetch)
useEffect(() => {
if (!id || !isFetched) return;
if (loadedCatalogIdRef.current === id) return;
// بارگذاری محتوا فقط یک‌بار برای هر id (نه در هر refetch)
useEffect(() => {
if (!id || !isFetched) return
if (loadedCatalogIdRef.current === id) return
const rawContent = data?.data?.content
if (rawContent) {
try {
const json = JSON.parse(rawContent)
if (Array.isArray(json)) {
loadPages(json)
} else if (json?.pages) {
loadPages(json.pages, json.documentSettings, json.gallery)
} else {
console.error('Invalid catalog content JSON: missing pages')
return
}
} catch (error) {
console.error('Invalid catalog content JSON:', error)
return
}
const rawContent = data?.data?.content;
if (rawContent) {
try {
const json = JSON.parse(rawContent);
if (Array.isArray(json)) {
loadPages(json);
} else if (json?.pages) {
loadPages(json.pages, json.documentSettings, json.gallery);
} else {
resetEditor()
console.error("Invalid catalog content JSON: missing pages");
return;
}
} catch (error) {
console.error("Invalid catalog content JSON:", error);
return;
}
} else {
resetEditor();
}
loadedCatalogIdRef.current = id
skipNextAutosaveRef.current = true
setIsInitialContentReady(true)
}, [id, isFetched, data?.data?.content, loadPages, resetEditor])
loadedCatalogIdRef.current = id;
skipNextAutosaveRef.current = true;
setIsInitialContentReady(true);
}, [id, isFetched, data?.data?.content, loadPages, resetEditor]);
useEffect(() => {
if (!id) return
if (!isInitialContentReady) return
if (!pages || pages.length === 0) return
if (skipNextAutosaveRef.current) {
skipNextAutosaveRef.current = false
return
}
useEffect(() => {
if (!id) return;
if (!isInitialContentReady) return;
if (!pages || pages.length === 0) return;
if (skipNextAutosaveRef.current) {
skipNextAutosaveRef.current = false;
return;
}
const pagesToSave = currentPageId
? pages.map((p) =>
p.id === currentPageId ? { ...p, objects } : p,
)
: pages
const pagesToSave = currentPageId
? pages.map((p) => (p.id === currentPageId ? { ...p, objects } : p))
: pages;
scheduleUpdate({
id,
content: JSON.stringify({ pages: pagesToSave, documentSettings, gallery }),
})
// عمداً scheduleUpdate را در وابستگی‌ها نیاوردیم تا از لوپ ذخیره‌سازی جلوگیری شود
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, isInitialContentReady, pages, documentSettings, gallery, objects, currentPageId])
scheduleUpdate({
id,
content: JSON.stringify({
pages: pagesToSave,
documentSettings,
gallery,
}),
});
// عمداً scheduleUpdate را در وابستگی‌ها نیاوردیم تا از لوپ ذخیره‌سازی جلوگیری شود
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
id,
isInitialContentReady,
pages,
documentSettings,
gallery,
objects,
currentPageId,
]);
return (
<div className={clx(
"flex h-full w-full",
isLayersPanelOpen ? "xl:pl-[296px]" : "xl:pl-[80px]"
)}>
{isSaving ? (
<div className="fixed bottom-10 left-10 z-50 rounded-full shadow-sm">
<div className="flex items-center gap-2 text-sm text-gray-700">
<span className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-black" />
</div>
</div>
) : null}
<LayersPanel
isOpen={isLayersPanelOpen}
setIsOpen={setIsLayersPanelOpen}
catalogSize={data?.data?.size}
/>
<EditorCanvas catalogSize={data?.data?.size} />
<EditorSidebar catalogSize={data?.data?.size} />
return (
<div
className={clx(
"flex h-full w-full",
isLayersPanelOpen ? "xl:pl-[296px]" : "xl:pl-[80px]",
)}
>
{isSaving ? (
<div className="fixed bottom-10 left-10 z-50 rounded-full shadow-sm">
<div className="flex items-center gap-2 text-sm text-gray-700">
<span className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-black" />
</div>
</div>
)
}
) : null}
<LayersPanel
isOpen={isLayersPanelOpen}
setIsOpen={setIsLayersPanelOpen}
catalogSize={data?.data?.size}
/>
<EditorCanvas catalogSize={data?.data?.size} />
<EditorSidebar catalogSize={data?.data?.size} />
</div>
);
};
export default Editor
export default Editor;
+2
View File
@@ -1,4 +1,5 @@
import Divider from '@/components/Divider'
import { usePageTitle } from '@/hooks/usePageTitle'
import { ArrowLeft, DocumentText } from 'iconsax-react'
import { useState, type FC } from 'react'
import PdfIcon from '@/assets/images/PDF.svg'
@@ -18,6 +19,7 @@ const CATALOG_SIZES = [
] as const
const Home: FC = () => {
usePageTitle('ساخت کاتالوگ')
const navigate = useNavigate()
const [active, setActive] = useState<string>('a4')
+79 -71
View File
@@ -1,81 +1,89 @@
import { type FC, useEffect, useState } from 'react';
import BookViewer from './components/BookViewer';
import ViewerLoading from './components/ViewerLoading';
import { transformViewerDataToPages } from './utils/dataTransformer';
import type { PageData } from './types';
import type { DocumentSettings } from '@/pages/editor/store/editorStore';
import { useParams } from 'react-router-dom';
import { useGetCatalogById } from '@/pages/home/hooks/useHomeData';
import { useBrowserFullscreen } from './hooks/useBrowserFullscreen';
import { useInitialPagesPreload } from './hooks/useInitialPagesPreload';
import { usePageTitle } from "@/hooks/usePageTitle";
import type { DocumentSettings } from "@/pages/editor/store/editorStore";
import { useGetCatalogById } from "@/pages/home/hooks/useHomeData";
import { type FC, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import BookViewer from "./components/BookViewer";
import ViewerLoading from "./components/ViewerLoading";
import { useBrowserFullscreen } from "./hooks/useBrowserFullscreen";
import { useInitialPagesPreload } from "./hooks/useInitialPagesPreload";
import type { PageData } from "./types";
import { transformViewerDataToPages } from "./utils/dataTransformer";
const Viewer: FC = () => {
const { id } = useParams<{ id: string }>();
const { data, isLoading, error: queryError } = useGetCatalogById(id ?? '');
const [pages, setPages] = useState<PageData[]>([]);
const [documentSettings, setDocumentSettings] = useState<Partial<DocumentSettings> | undefined>(undefined);
const { id } = useParams<{ id: string }>();
const { data, isLoading, error: queryError } = useGetCatalogById(id ?? "");
usePageTitle(`کاتالوگ ${data?.data?.name}`);
const [pages, setPages] = useState<PageData[]>([]);
const [documentSettings, setDocumentSettings] = useState<
Partial<DocumentSettings> | undefined
>(undefined);
useEffect(() => {
if (!data?.data?.content || !id) return;
try {
const parsed = JSON.parse(data.data.content);
const pagesArray = Array.isArray(parsed) ? parsed : (parsed.pages ?? []);
const settings: Partial<DocumentSettings> | undefined = Array.isArray(parsed)
? undefined
: parsed.documentSettings;
const transformedPages = transformViewerDataToPages(
{ pages: pagesArray } as Parameters<typeof transformViewerDataToPages>[0]
);
setPages(transformedPages);
setDocumentSettings(settings);
} catch (err) {
console.error('خطا در پردازش محتوای کاتالوگ:', err);
}
}, [data?.data?.content, id]);
useBrowserFullscreen({ enabled: Boolean(id) });
const { isReady: areInitialPagesReady, progress: preloadProgress } = useInitialPagesPreload(
pages,
documentSettings,
);
if (!id) {
return (
<div className="w-full h-full flex items-center justify-center">
<div className="text-gray-600">شناسه کاتالوگ مشخص نیست</div>
</div>
);
useEffect(() => {
if (!data?.data?.content || !id) return;
try {
const parsed = JSON.parse(data.data.content);
const pagesArray = Array.isArray(parsed) ? parsed : (parsed.pages ?? []);
const settings: Partial<DocumentSettings> | undefined = Array.isArray(
parsed,
)
? undefined
: parsed.documentSettings;
const transformedPages = transformViewerDataToPages({
pages: pagesArray,
} as Parameters<typeof transformViewerDataToPages>[0]);
setPages(transformedPages);
setDocumentSettings(settings);
} catch (err) {
console.error("خطا در پردازش محتوای کاتالوگ:", err);
}
}, [data?.data?.content, id]);
if (isLoading) {
return <ViewerLoading />;
}
if (queryError) {
return (
<div className="w-full h-full flex items-center justify-center">
<div className="text-red-600">خطا: {queryError.message}</div>
</div>
);
}
if (pages.length === 0) {
return (
<div className="w-full h-full flex items-center justify-center">
<div className="text-gray-600">هیچ صفحهای یافت نشد</div>
</div>
);
}
if (!areInitialPagesReady) {
return <ViewerLoading progress={preloadProgress} />;
}
useBrowserFullscreen({ enabled: Boolean(id) });
const { isReady: areInitialPagesReady, progress: preloadProgress } =
useInitialPagesPreload(pages, documentSettings);
if (!id) {
return (
<div className="w-full h-full min-h-0 flex flex-col">
<BookViewer pages={pages} catalogSize={data?.data?.size} documentSettings={documentSettings} />
</div>
<div className="w-full h-full flex items-center justify-center">
<div className="text-gray-600">شناسه کاتالوگ مشخص نیست</div>
</div>
);
}
if (isLoading) {
return <ViewerLoading />;
}
if (queryError) {
return (
<div className="w-full h-full flex items-center justify-center">
<div className="text-red-600">خطا: {queryError.message}</div>
</div>
);
}
if (pages.length === 0) {
return (
<div className="w-full h-full flex items-center justify-center">
<div className="text-gray-600">هیچ صفحهای یافت نشد</div>
</div>
);
}
if (!areInitialPagesReady) {
return <ViewerLoading progress={preloadProgress} />;
}
return (
<div className="w-full h-full min-h-0 flex flex-col">
<BookViewer
pages={pages}
catalogSize={data?.data?.size}
documentSettings={documentSettings}
/>
</div>
);
};
export default Viewer;
export default Viewer;