screen shot test

This commit is contained in:
hamid zarghami
2025-07-16 12:24:51 +03:30
parent 9cdd5de94c
commit fe5b7de9b1
8 changed files with 309 additions and 47 deletions
+4
View File
@@ -21,6 +21,8 @@
"axios": "^1.10.0", "axios": "^1.10.0",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dom-to-image": "^2.6.0",
"html2canvas": "^1.4.1",
"i18next": "^25.0.1", "i18next": "^25.0.1",
"iconsax-react": "^0.0.8", "iconsax-react": "^0.0.8",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
@@ -51,6 +53,8 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.22.0", "@eslint/js": "^9.22.0",
"@types/dom-to-image": "^2.6.7",
"@types/html2canvas": "^1.0.0",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/moment-jalaali": "^0.7.9", "@types/moment-jalaali": "^0.7.9",
"@types/react": "^19.0.10", "@types/react": "^19.0.10",
+66
View File
@@ -38,6 +38,12 @@ importers:
clsx: clsx:
specifier: ^2.1.1 specifier: ^2.1.1
version: 2.1.1 version: 2.1.1
dom-to-image:
specifier: ^2.6.0
version: 2.6.0
html2canvas:
specifier: ^1.4.1
version: 1.4.1
i18next: i18next:
specifier: ^25.0.1 specifier: ^25.0.1
version: 25.2.1(typescript@5.7.3) version: 25.2.1(typescript@5.7.3)
@@ -123,6 +129,12 @@ importers:
'@eslint/js': '@eslint/js':
specifier: ^9.22.0 specifier: ^9.22.0
version: 9.27.0 version: 9.27.0
'@types/dom-to-image':
specifier: ^2.6.7
version: 2.6.7
'@types/html2canvas':
specifier: ^1.0.0
version: 1.0.0
'@types/js-cookie': '@types/js-cookie':
specifier: ^3.0.6 specifier: ^3.0.6
version: 3.0.6 version: 3.0.6
@@ -1070,6 +1082,9 @@ packages:
'@types/babel__traverse@7.20.7': '@types/babel__traverse@7.20.7':
resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==}
'@types/dom-to-image@2.6.7':
resolution: {integrity: sha512-me5VbCv+fcXozblWwG13krNBvuEOm6kA5xoa4RrjDJCNFOZSWR3/QLtOXimBHk1Fisq69Gx3JtOoXtg1N1tijg==}
'@types/draco3d@1.4.10': '@types/draco3d@1.4.10':
resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==} resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==}
@@ -1082,6 +1097,10 @@ packages:
'@types/hast@3.0.4': '@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
'@types/html2canvas@1.0.0':
resolution: {integrity: sha512-BJpVf+FIN9UERmzhbtUgpXj6XBZpG67FMgBLLoj9HZKd9XifcCpSV+UnFcwTZfEyun4U/KmCrrVOG7829L589w==}
deprecated: This is a stub types definition. html2canvas provides its own type definitions, so you do not need this installed.
'@types/js-cookie@3.0.6': '@types/js-cookie@3.0.6':
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
@@ -1285,6 +1304,10 @@ packages:
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
base64-arraybuffer@1.0.2:
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
engines: {node: '>= 0.6.0'}
base64-js@1.5.1: base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@@ -1400,6 +1423,9 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
css-line-break@2.1.0:
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
csstype@3.1.3: csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
@@ -1458,6 +1484,9 @@ packages:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'} engines: {node: '>=8'}
dom-to-image@2.6.0:
resolution: {integrity: sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA==}
draco3d@1.5.7: draco3d@1.5.7:
resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==} resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==}
@@ -1802,6 +1831,10 @@ packages:
html-parse-stringify@3.0.1: html-parse-stringify@3.0.1:
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
html2canvas@1.4.1:
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
http2-client@1.3.5: http2-client@1.3.5:
resolution: {integrity: sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==} resolution: {integrity: sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==}
@@ -2795,6 +2828,9 @@ packages:
resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
engines: {node: '>=18'} engines: {node: '>=18'}
text-segmentation@1.0.3:
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
three-mesh-bvh@0.8.3: three-mesh-bvh@0.8.3:
resolution: {integrity: sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==} resolution: {integrity: sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==}
peerDependencies: peerDependencies:
@@ -2960,6 +2996,9 @@ packages:
resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
utrie@1.0.2:
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
uuid@11.1.0: uuid@11.1.0:
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
hasBin: true hasBin: true
@@ -4107,6 +4146,8 @@ snapshots:
dependencies: dependencies:
'@babel/types': 7.27.3 '@babel/types': 7.27.3
'@types/dom-to-image@2.6.7': {}
'@types/draco3d@1.4.10': {} '@types/draco3d@1.4.10': {}
'@types/es-aggregate-error@1.0.6': '@types/es-aggregate-error@1.0.6':
@@ -4119,6 +4160,10 @@ snapshots:
dependencies: dependencies:
'@types/unist': 3.0.3 '@types/unist': 3.0.3
'@types/html2canvas@1.0.0':
dependencies:
html2canvas: 1.4.1
'@types/js-cookie@3.0.6': {} '@types/js-cookie@3.0.6': {}
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
@@ -4350,6 +4395,8 @@ snapshots:
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
base64-arraybuffer@1.0.2: {}
base64-js@1.5.1: {} base64-js@1.5.1: {}
bidi-js@1.0.3: bidi-js@1.0.3:
@@ -4463,6 +4510,10 @@ snapshots:
shebang-command: 2.0.0 shebang-command: 2.0.0
which: 2.0.2 which: 2.0.2
css-line-break@2.1.0:
dependencies:
utrie: 1.0.2
csstype@3.1.3: {} csstype@3.1.3: {}
data-view-buffer@1.0.2: data-view-buffer@1.0.2:
@@ -4524,6 +4575,8 @@ snapshots:
dependencies: dependencies:
path-type: 4.0.0 path-type: 4.0.0
dom-to-image@2.6.0: {}
draco3d@1.5.7: {} draco3d@1.5.7: {}
dunder-proto@1.0.1: dunder-proto@1.0.1:
@@ -4960,6 +5013,11 @@ snapshots:
dependencies: dependencies:
void-elements: 3.1.0 void-elements: 3.1.0
html2canvas@1.4.1:
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
http2-client@1.3.5: {} http2-client@1.3.5: {}
human-signals@2.1.0: {} human-signals@2.1.0: {}
@@ -5968,6 +6026,10 @@ snapshots:
mkdirp: 3.0.1 mkdirp: 3.0.1
yallist: 5.0.0 yallist: 5.0.0
text-segmentation@1.0.3:
dependencies:
utrie: 1.0.2
three-mesh-bvh@0.8.3(three@0.177.0): three-mesh-bvh@0.8.3(three@0.177.0):
dependencies: dependencies:
three: 0.177.0 three: 0.177.0
@@ -6134,6 +6196,10 @@ snapshots:
utility-types@3.11.0: {} utility-types@3.11.0: {}
utrie@1.0.2:
dependencies:
base64-arraybuffer: 1.0.2
uuid@11.1.0: {} uuid@11.1.0: {}
validator@13.15.15: {} validator@13.15.15: {}
+28 -27
View File
@@ -13,26 +13,28 @@ import Button from '@/components/Button'
import { TickCircle } from 'iconsax-react' import { TickCircle } from 'iconsax-react'
import { usePersonalityStore } from './personality/store/Store' import { usePersonalityStore } from './personality/store/Store'
import { exportPersonalityToHTML } from './personality/utils/ExportHTML' import { exportPersonalityToHTML } from './personality/utils/ExportHTML'
import { capturePersonalityScreenshot } from './personality/utils/screenshotHelper'
import Input from '@/components/Input' import Input from '@/components/Input'
import { useSaveTemplate, useUpdateTemplate, useGetTemplate } from './personality/hooks/usePersonalityData' import { useSaveTemplate, useUpdateTemplate, useGetTemplate, useSingleUpload } from './personality/hooks/usePersonalityData'
import { toast } from '@/components/Toast' import { toast } from '@/components/Toast'
const Setting: FC = () => { const Setting: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { id } = useParams() const { id } = useParams()
const { data } = usePersonalityStore() const { data } = usePersonalityStore()
const location = useLocation() const location = useLocation()
const [activeTab, setActiveTab] = useState<SettingTabEnum>(SettingTabEnum.SETTING_DOMAIN) const [activeTab, setActiveTab] = useState<SettingTabEnum>(SettingTabEnum.SETTING_DOMAIN)
const [templateName, setTemplateName] = useState('') const [templateName, setTemplateName] = useState('')
const [isCapturingScreenshot, setIsCapturingScreenshot] = useState(false)
const { mutate: saveTemplate, isPending: isSaving } = useSaveTemplate() const { mutate: saveTemplate, isPending: isSaving } = useSaveTemplate()
const { mutate: updateTemplate, isPending: isUpdating } = useUpdateTemplate() const { mutate: updateTemplate, isPending: isUpdating } = useUpdateTemplate()
const { data: template } = useGetTemplate(id || '') const { data: template } = useGetTemplate(id || '')
const { mutateAsync: singleUpload, isPending: isUploading } = useSingleUpload()
const isEditMode = !!id const isEditMode = !!id
const isLoading = isSaving || isUpdating const isLoading = isSaving || isUpdating || isUploading || isCapturingScreenshot
// پر کردن نام قالب در حالت edit
useEffect(() => { useEffect(() => {
if (isEditMode && template?.data?.template?.name) { if (isEditMode && template?.data?.template?.name) {
setTemplateName(template.data.template.name) setTemplateName(template.data.template.name)
@@ -52,7 +54,6 @@ const Setting: FC = () => {
case Paths.settingSignature: case Paths.settingSignature:
return SettingTabEnum.SETTING_SIGNATURE return SettingTabEnum.SETTING_SIGNATURE
default: default:
// بررسی personality با id
if (pathname.startsWith(Paths.settingPersonality + '/')) { if (pathname.startsWith(Paths.settingPersonality + '/')) {
return SettingTabEnum.SETTING_PERSONALITY return SettingTabEnum.SETTING_PERSONALITY
} }
@@ -60,9 +61,6 @@ const Setting: FC = () => {
} }
} }
// Map setting tabs to URL paths
// Update active tab when URL changes
useEffect(() => { useEffect(() => {
const tabFromPath = getTabFromPath(location.pathname) const tabFromPath = getTabFromPath(location.pathname)
setActiveTab(tabFromPath) setActiveTab(tabFromPath)
@@ -100,16 +98,31 @@ const Setting: FC = () => {
return return
} }
if (activeTab !== SettingTabEnum.SETTING_PERSONALITY) {
return
}
const html = await exportPersonalityToHTML(data) const html = await exportPersonalityToHTML(data)
setIsCapturingScreenshot(true)
const screenshotFile = await capturePersonalityScreenshot(templateName)
setIsCapturingScreenshot(false)
if (!screenshotFile) {
toast('خطا در گرفتن اسکرین‌شات', 'error')
return
}
const thumbnailUrl = await singleUpload(screenshotFile)
if (isEditMode) { if (isEditMode) {
// حالت edit - استفاده از updateTemplate
updateTemplate({ updateTemplate({
id: id!, id: id!,
params: { params: {
name: templateName, name: templateName,
rawHtml: html, rawHtml: html,
structure: data structure: data,
thumbnailUrl: thumbnailUrl?.data?.url
} }
}, { }, {
onSuccess: (data) => { onSuccess: (data) => {
@@ -120,11 +133,11 @@ const Setting: FC = () => {
} }
}) })
} else { } else {
// حالت جدید - استفاده از saveTemplate
saveTemplate({ saveTemplate({
name: templateName, name: templateName,
rawHtml: html, rawHtml: html,
structure: data structure: data,
thumbnailUrl: thumbnailUrl?.data?.url
}, { }, {
onSuccess: (data) => { onSuccess: (data) => {
toast(data?.data?.message || 'قالب با موفقیت ایجاد شد', 'success') toast(data?.data?.message || 'قالب با موفقیت ایجاد شد', 'success')
@@ -136,25 +149,12 @@ const Setting: FC = () => {
} }
} }
// const handleExportHTML = async () => {
// try {
// const html = await exportPersonalityToHTML(data)
// const success = await copyHTMLToClipboard(html)
// if (success) {
// toast(t('export_success'), 'success')
// } else {
// toast(t('export_error'), 'error')
// }
// } catch {
// toast(t('export_error'), 'error')
// }
// }
return ( return (
<div className='mt-2 md:mt-4 px-2 md:px-0'> <div className='mt-2 md:mt-4 px-2 md:px-0'>
<div className='flex justify-between items-center'> <div className='flex justify-between items-center'>
<h1 className='text-lg mb-4 md:mb-0'>{t('setting.title')}</h1> <h1 className='text-lg mb-4 md:mb-0'>{t('setting.title')}</h1>
{
activeTab === SettingTabEnum.SETTING_PERSONALITY &&
<div className='flex gap-2'> <div className='flex gap-2'>
<Input <Input
placeholder='نام قالب' placeholder='نام قالب'
@@ -173,6 +173,7 @@ const Setting: FC = () => {
</div> </div>
</Button> </Button>
</div> </div>
}
</div> </div>
<div> <div>
@@ -18,7 +18,7 @@ const Personality: FC = () => {
}, [id, template, setData]) }, [id, template, setData])
return ( return (
<div className='flex-1 bg-white rounded-4xl p-8'> <div className='flex-1 bg-white rounded-4xl p-8 personality-template'>
<table width="100%" cellPadding="0" cellSpacing="0" border={0} style={{ borderCollapse: 'collapse' }}> <table width="100%" cellPadding="0" cellSpacing="0" border={0} style={{ borderCollapse: 'collapse' }}>
<tbody> <tbody>
<HeaderSection /> <HeaderSection />
@@ -50,3 +50,9 @@ export const useDeleteTemplate = () => {
}, },
}); });
}; };
export const useSingleUpload = () => {
return useMutation({
mutationFn: (file: File) => api.singleUpload(file),
});
};
@@ -1,5 +1,6 @@
import axios from "@/config/axios"; import axios from "@/config/axios";
import { TemplateType, TemplatesResponseType } from "../types/Types"; import { TemplateType, TemplatesResponseType } from "../types/Types";
import axiosDanak from "@/config/axiosDanak";
export const saveTemplate = async (params: TemplateType) => { export const saveTemplate = async (params: TemplateType) => {
const { data } = await axios.post("/templates", params); const { data } = await axios.post("/templates", params);
@@ -36,3 +37,12 @@ export const deleteTemplate = async (id: string) => {
const { data } = await axios.delete(`/templates/${id}`); const { data } = await axios.delete(`/templates/${id}`);
return data; return data;
}; };
export const singleUpload = async (file: File) => {
const formData = new FormData();
formData.append("file", file);
const { data } = await axiosDanak.post("/uploader/single-file", formData);
return data;
};
@@ -269,6 +269,7 @@ export type TemplateType = {
name: string; name: string;
rawHtml: string; rawHtml: string;
structure: PersonalityDataType; structure: PersonalityDataType;
thumbnailUrl: string;
}; };
export type GetTemplateResponse = { export type GetTemplateResponse = {
@@ -0,0 +1,174 @@
import domtoimage from "dom-to-image";
/**
* گرفتن اسکرین‌شات با dom-to-image و برگرداندن فایل برای آپلود
*/
export const captureScreenshotAsFile = async (
templateName: string
): Promise<File | null> => {
try {
console.log("📸 Starting screenshot capture for upload...");
const element = await findAndValidateElement();
if (!element) {
console.error("❌ Element not found");
return null;
}
// آماده‌سازی element
const htmlElement = element as HTMLElement;
const originalTransform = htmlElement.style.transform;
const originalPosition = htmlElement.style.position;
htmlElement.style.transform = "scale(1)";
htmlElement.style.position = "relative";
// صبر برای render کامل
await new Promise((resolve) => setTimeout(resolve, 1000));
const options = {
quality: 1,
bgcolor: "#ffffff",
width: element.offsetWidth,
height: element.offsetHeight,
style: {
transform: "scale(1)",
transformOrigin: "top left",
},
};
const dataUrl = await domtoimage.toPng(element, options);
// بازگردانی استایل‌های اصلی
htmlElement.style.transform = originalTransform;
htmlElement.style.position = originalPosition;
// تبدیل dataURL به File
const response = await fetch(dataUrl);
const blob = await response.blob();
const file = new File([blob], `${templateName}-template.png`, {
type: "image/png",
});
console.log("✅ Screenshot file created successfully:", file);
return file;
} catch (error) {
console.error("❌ Screenshot capture failed:", error);
return null;
}
};
/**
* تابع اصلی - استفاده از dom-to-image و برگرداندن فایل
*/
export const capturePersonalityScreenshot = async (
templateName: string
): Promise<File | null> => {
console.log("🎯 Starting screenshot capture...");
try {
const screenshotFile = await captureScreenshotAsFile(templateName);
if (screenshotFile) {
console.log("✅ Screenshot captured successfully");
return screenshotFile;
} else {
console.error("❌ Screenshot capture failed");
return null;
}
} catch (error) {
console.error("❌ Screenshot error:", error);
return null;
}
};
/**
* پیدا کردن و بررسی element
*/
const findAndValidateElement = async (): Promise<HTMLElement | null> => {
console.log("🔍 Looking for personality element...");
const selectors = [
".personality-template",
'[class*="personality-template"]',
".flex-1.bg-white.rounded-4xl.p-8",
".bg-white.rounded-4xl.p-8",
'div[class*="bg-white"][class*="rounded-4xl"]',
];
for (let i = 0; i < selectors.length; i++) {
const selector = selectors[i];
console.log(`🔍 Trying selector ${i + 1}/${selectors.length}: ${selector}`);
const elements = document.querySelectorAll(selector);
console.log(` Found ${elements.length} elements`);
for (let j = 0; j < elements.length; j++) {
const element = elements[j] as HTMLElement;
const rect = element.getBoundingClientRect();
const isVisible = rect.width > 0 && rect.height > 0;
const hasContent = element.innerHTML.trim().length > 0;
console.log(` Element ${j + 1}:`, {
visible: isVisible,
hasContent,
dimensions: `${rect.width}x${rect.height}`,
className: element.className.slice(0, 50) + "...",
});
if (isVisible && hasContent) {
console.log(`✅ Found valid element with selector: ${selector}`);
return element;
}
}
}
console.log("❌ No valid elements found");
return null;
};
/**
* تابع debug برای بررسی element
*/
export const debugPersonalityElement = () => {
console.log("🔍 === DEBUG: Personality Element Detection ===");
const selectors = [
".personality-template",
'[class*="personality-template"]',
".flex-1.bg-white.rounded-4xl.p-8",
".bg-white.rounded-4xl",
"table",
];
let foundElements = 0;
selectors.forEach((selector, index) => {
const elements = document.querySelectorAll(selector);
console.log(`${index + 1}. "${selector}": ${elements.length} elements`);
elements.forEach((el, elIndex) => {
const htmlEl = el as HTMLElement;
const rect = htmlEl.getBoundingClientRect();
console.log(` [${elIndex + 1}]`, {
visible: rect.width > 0 && rect.height > 0,
size: `${rect.width}x${rect.height}`,
hasContent: htmlEl.innerHTML.trim().length > 0,
className: htmlEl.className.slice(0, 40) + "...",
});
if (rect.width > 0 && rect.height > 0) {
foundElements++;
}
});
});
console.log(`📊 Summary: ${foundElements} visible elements found`);
console.log("=========================================");
return {
found: foundElements > 0,
totalElements: foundElements,
};
};