Skip to content

Commit

Permalink
✨ Add insert iscn page into epub on edit (#443)
Browse files Browse the repository at this point in the history
* ✨ Add logic for inserting iscn page into epub

* ✨ Add UI logic for uploading modified epub files

* ✨ Fetch epub language when inserting page

* 👔 Default `isAddISCNPageToEpub` to false

* 🐛 Fix epub cover ipfs hash mess up

* 🐛 Fix arweave estimation was not refresh for modified file list

* 🐛 Fix duplicated content fingerprint on update iscn

* 🐛 Fix invalid item in epub check
  • Loading branch information
williamchong committed Feb 28, 2024
1 parent 871eeb2 commit 49610cb
Show file tree
Hide file tree
Showing 8 changed files with 395 additions and 54 deletions.
156 changes: 114 additions & 42 deletions components/IscnUploadForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
<tbody class="w-full">
<tr
v-for="(
{ isFileImage, fileData, fileName, fileSize, exifInfo},
{ isFileImage, fileData, fileName, fileSize, exifInfo },
index
) of fileRecords"
:key="fileName"
Expand Down Expand Up @@ -104,6 +104,9 @@
</table>
</div>
</div>
<FormField v-if="showAddISCNPageOption">
<CheckBox v-model="isAddISCNPageToEpub">{{ $t('UploadForm.label.insertISCNPage') }}</CheckBox>
</FormField>
<!-- upload field__Submit -->
<div class="flex gap-[8px] justify-end text-medium-gray mt-[24px]">
<NuxtLink
Expand Down Expand Up @@ -242,6 +245,7 @@ import {
} from '~/utils/misc'
import { DEFAULT_TRANSFER_FEE, sendLIKE } from '~/utils/cosmos/sign';
import { getAccountBalance } from '~/utils/cosmos'
import { injectISCNQRCodePage } from '~/utils/epub/iscn'
const walletModule = namespace('wallet')
type UploadStatus = '' | 'loading' | 'signing' | 'uploading';
Expand All @@ -255,6 +259,7 @@ const MODE = {
export default class IscnUploadForm extends Vue {
@Prop(Number) readonly step: number | undefined
@Prop({ default: MODE.REGISTER }) readonly mode: string | undefined
@Prop(String) readonly iscnId: string | undefined
@walletModule.Getter('getSigner') signer!: OfflineSigner | null
@walletModule.Action('initIfNecessary') initIfNecessary!: () => Promise<any>
Expand All @@ -281,6 +286,7 @@ export default class IscnUploadForm extends Vue {
isOpenWarningSnackbar = false
isOpenKeplr = true
isSizeExceeded = false
isAddISCNPageToEpub = false
uploadSizeLimit: number = UPLOAD_FILESIZE_MAX
uploadStatus: UploadStatus = '';
Expand All @@ -300,6 +306,7 @@ export default class IscnUploadForm extends Vue {
balance = new BigNumber(0)
epubMetadataList: any[] = []
modifiedEpubMap: any = {}
get formClasses() {
return [
Expand Down Expand Up @@ -369,12 +376,29 @@ export default class IscnUploadForm extends Vue {
}
}
get showAddISCNPageOption() {
return this.mode === MODE.EDIT && this.fileRecords.some(file => file.fileType === 'application/epub+zip')
}
get modifiedFileRecords() {
if (!this.isAddISCNPageToEpub || this.mode !== MODE.EDIT) return this.fileRecords
return this.fileRecords.map((record) => {
if (record.fileType === 'application/epub+zip') {
const modifiedEpubRecord = this.modifiedEpubMap[record.ipfsHash]
if (modifiedEpubRecord) {
return modifiedEpubRecord
}
}
return record
})
}
@Watch('error')
showWarning(errormsg: any) {
if (errormsg) this.isOpenWarningSnackbar = true
}
@Watch('fileRecords')
@Watch('modifiedFileRecords')
async estimateArFee(fileRecords: any) {
if (fileRecords.length) {
this.uploadStatus = 'loading'
Expand All @@ -385,6 +409,32 @@ export default class IscnUploadForm extends Vue {
}
}
// eslint-disable-next-line class-methods-use-this
async getFileInfo(file: Blob) {
const fileBytes = (await fileToArrayBuffer(
file,
)) as unknown as ArrayBuffer
if (fileBytes) {
const [
fileSHA256,
imageType,
ipfsHash,
// eslint-disable-next-line no-await-in-loop
] = await Promise.all([
digestFileSHA256(fileBytes),
readImageType(fileBytes),
Hash.of(Buffer.from(fileBytes)),
])
return {
fileBytes,
fileSHA256,
imageType,
ipfsHash,
}
}
return null
}
async onFileUpload(event: DragEvent) {
logTrackerEvent(this, 'ISCNCreate', 'SelectFile', '', 1)
this.isSizeExceeded = false
Expand Down Expand Up @@ -412,21 +462,14 @@ export default class IscnUploadForm extends Vue {
reader.readAsDataURL(file)
// eslint-disable-next-line no-await-in-loop
const fileBytes = (await fileToArrayBuffer(
file,
)) as unknown as ArrayBuffer
if (fileBytes) {
const [
const info = await this.getFileInfo(file)
if (info) {
const {
fileBytes,
fileSHA256,
imageType,
ipfsHash,
// eslint-disable-next-line no-await-in-loop
] = await Promise.all([
digestFileSHA256(fileBytes),
readImageType(fileBytes),
Hash.of(Buffer.from(fileBytes)),
])
} = info
fileRecord = {
...fileRecord,
fileName: file.name,
Expand All @@ -453,7 +496,7 @@ export default class IscnUploadForm extends Vue {
}
if (file.type === 'application/epub+zip') {
// eslint-disable-next-line no-await-in-loop
await this.processEPub({ buffer: fileBytes, file })
await this.processEPub({ ipfsHash, buffer: fileBytes, file })
}
}
} else {
Expand All @@ -464,10 +507,41 @@ export default class IscnUploadForm extends Vue {
}
}
async processEPub({ buffer, file }: { buffer: ArrayBuffer; file: File }) {
async processEPub({ ipfsHash, buffer, file }: { ipfsHash: string, buffer: ArrayBuffer; file: File }) {
try {
const book = ePub(buffer)
await book.ready
if (this.mode === MODE.EDIT) {
const modifiedEpub = await injectISCNQRCodePage(buffer, book, this.iscnId || '')
// eslint-disable-next-line no-await-in-loop
const info = await this.getFileInfo(modifiedEpub)
if (info) {
const {
fileSHA256: modifiedEpubSHA256,
ipfsHash: modifiedEpubIpfsHash,
} = info
const modifiedEpubRecord: any = {
fileName: file.name,
fileSize: modifiedEpub.size,
fileType: modifiedEpub.type,
fileBlob: modifiedEpub,
ipfsHash: modifiedEpubIpfsHash,
fileSHA256: modifiedEpubSHA256,
isFileImage: false,
}
const epubReader = new FileReader()
epubReader.onload = (e) => {
if (!e.target) return
modifiedEpubRecord.fileData = e.target.result as string
Vue.set(this.modifiedEpubMap, ipfsHash, modifiedEpubRecord)
}
epubReader.readAsDataURL(modifiedEpub)
}
}
const epubMetadata: any = {}
// Get metadata
Expand Down Expand Up @@ -510,39 +584,34 @@ export default class IscnUploadForm extends Vue {
type: 'image/jpeg',
},
)
const fileBytes = (await fileToArrayBuffer(
coverFile,
)) as unknown as ArrayBuffer
if (fileBytes) {
const [
// eslint-disable-next-line no-await-in-loop
const coverInfo = await this.getFileInfo(coverFile)
if (coverInfo) {
const {
fileSHA256,
imageType,
ipfsHash,
// eslint-disable-next-line no-await-in-loop
] = await Promise.all([
digestFileSHA256(fileBytes),
readImageType(fileBytes),
Hash.of(Buffer.from(fileBytes)),
])
ipfsHash: ipfsThumbnailHash,
} = coverInfo
epubMetadata.thumbnailIpfsHash = ipfsHash
epubMetadata.thumbnailIpfsHash = ipfsThumbnailHash
const fileRecord: any = {
const coverFileRecord: any = {
fileName: coverFile.name,
fileSize: coverFile.size,
fileType: coverFile.type,
fileBlob: coverFile,
ipfsHash,
ipfsHash: ipfsThumbnailHash,
fileSHA256,
isFileImage: !!imageType,
}
const reader = new FileReader()
reader.onload = (e) => {
const coverReader = new FileReader()
coverReader.onload = (e) => {
if (!e.target) return
fileRecord.fileData = e.target.result as string
this.fileRecords.push(fileRecord)
coverFileRecord.fileData = e.target.result as string
this.fileRecords.push(coverFileRecord)
}
reader.readAsDataURL(coverFile)
coverReader.readAsDataURL(coverFile)
}
}
}
Expand Down Expand Up @@ -589,6 +658,9 @@ export default class IscnUploadForm extends Vue {
handleDeleteFile(index: number) {
const deletedFile = this.fileRecords[index];
if (this.modifiedEpubMap[deletedFile.ipfsHash]) {
delete this.modifiedEpubMap[deletedFile.ipfsHash]
}
this.fileRecords.splice(index, 1);
const indexToDelete = this.epubMetadataList.findIndex(item => item.thumbnailIpfsHash === deletedFile.ipfsHashList);
Expand Down Expand Up @@ -616,7 +688,7 @@ export default class IscnUploadForm extends Vue {
async estimateArweaveFee(): Promise<void> {
try {
const results = await Promise.all(
this.fileRecords.map(async (record) => {
this.modifiedFileRecords.map(async (record) => {
const priceResult = await estimateBundlrFilePrice({
fileSize: record.fileBlob?.size || 0,
ipfsHash: record.ipfsHash,
Expand Down Expand Up @@ -755,7 +827,7 @@ export default class IscnUploadForm extends Vue {
this.uploadStatus = ''
return
}
if (!this.fileRecords.some(file => file.fileBlob)) {
if (!this.modifiedFileRecords.some(file => file.fileBlob)) {
this.error = 'NO_FILE_TO_UPLOAD'
this.isOpenWarningSnackbar = true
this.uploadStatus = ''
Expand All @@ -765,7 +837,7 @@ export default class IscnUploadForm extends Vue {
try {
this.uploadStatus = 'uploading';
// eslint-disable-next-line no-restricted-syntax
for (const record of this.fileRecords) {
for (const record of this.modifiedFileRecords) {
// eslint-disable-next-line no-await-in-loop
await this.submitToArweave(record);
}
Expand All @@ -781,17 +853,17 @@ export default class IscnUploadForm extends Vue {
}
const uploadArweaveIdList = Array.from(this.sentArweaveTransactionInfo.values()).map(entry => entry.arweaveId);
this.fileRecords.forEach((record: any, index:number) => {
this.modifiedFileRecords.forEach((record: any, index:number) => {
if (this.sentArweaveTransactionInfo.has(record.ipfsHash)) {
const arweaveId = this.sentArweaveTransactionInfo.get(
record.ipfsHash,
)?.arweaveId
if (arweaveId) {
this.fileRecords[index].arweaveId = arweaveId
this.modifiedFileRecords[index].arweaveId = arweaveId
}
}
})
this.$emit('submit', { fileRecords: this.fileRecords, arweaveIds: uploadArweaveIdList, epubMetadata: this.epubMetadataList[0] })
this.$emit('submit', { fileRecords: this.modifiedFileRecords, arweaveIds: uploadArweaveIdList, epubMetadata: this.epubMetadataList[0] })
}
handleSignDialogClose() {
Expand Down
1 change: 1 addition & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@
"UploadForm.button.selectFile": "Select a file",
"UploadForm.button.skip": "Skip Upload",
"UploadForm.button": "Start Upload",
"UploadForm.label.insertISCNPage": "Add ISCN Page to Epub",
"UploadForm.guide.dropFile": "Drop your file here, or",
"UploadForm.guide.selectFile": "Select your file and publish to IPFS",
"UploadForm.title.registerISCN": "Register ISCN",
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"form-data": "^4.0.0",
"image-type": "^4.1.0",
"ipfs-only-hash": "^4.0.0",
"jszip": "^3.10.1",
"lodash.chunk": "^4.2.0",
"lodash.debounce": "^4.0.8",
"mime-types": "^2.1.34",
Expand All @@ -58,6 +59,7 @@
"puppeteer-extra": "^3.3.4",
"puppeteer-extra-plugin-anonymize-ua": "^2.4.4",
"puppeteer-extra-plugin-stealth": "^2.11.1",
"qrcode": "^1.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"uuid": "^8.3.2",
Expand All @@ -80,6 +82,7 @@
"@types/mime-types": "^2.1.1",
"@types/multer": "^1.4.7",
"@types/p5": "^1.3.0",
"@types/qrcode": "^1.5.5",
"@types/uuid": "^8.3.1",
"@vue/test-utils": "^1.2.1",
"@vue/vue2-jest": "^27.0.0",
Expand Down
1 change: 1 addition & 0 deletions pages/edit/_iscnId.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
>
<IscnUploadForm
mode="edit"
:iscn-id="iscnId"
@submit="onSubmitUpload"
@arweaveUploaded="onArweaveIdUpload"
/>
Expand Down
Loading

0 comments on commit 49610cb

Please sign in to comment.