diff --git a/components/ISCNTypeIcon.vue b/components/ISCNTypeIcon.vue index 5b79d74d..225edb15 100644 --- a/components/ISCNTypeIcon.vue +++ b/components/ISCNTypeIcon.vue @@ -20,7 +20,7 @@ export enum RecordType { @Component export default class ISCNTypeIcon extends Vue { - @Prop(String) readonly type!: String | undefined + @Prop({ default: 'CreativeWork' }) readonly type!: String | undefined get recordType() { switch (this.type) { diff --git a/components/IscnRegisterForm.vue b/components/IscnRegisterForm.vue index e7502ddd..83352992 100644 --- a/components/IscnRegisterForm.vue +++ b/components/IscnRegisterForm.vue @@ -188,7 +188,7 @@ - + @@ -223,8 +223,38 @@ :placeholder="$t('IscnRegisterForm.placeholder.description')" /> + + + + + + + + + @@ -240,7 +270,7 @@ text-preset="h6" type="button" :text="author.name" - @click="editAuthor(index)" + @click="editAuthor({ type: 'stakeholder', index })" /> - + - + + + + + + + + + + + - - + @@ -379,7 +446,7 @@ {{ $t('IscnRegisterForm.error.buy') }} - + - - + + + + + + + + + + + Promise + typeOptions = [ + 'Book', + 'Photo', + 'Image', + 'CreativeWork', + ] + + fileTypeOptions = [ + 'epub', + 'pdf', + 'mp3', + 'jpg', + 'png', + ] + + licenseMap: { [key: string]: string | null }= { + 'Copyright. All rights reserved.': 'All Rights Reserved', + 'CC BY 4.0': 'https://creativecommons.org/licenses/by/4.0/', + 'Other': null, + } + + author: Author = { + name: '', + url: [], + wallet: [], + likerId: '', + authorDescription: '', + } authors: Author[] = [] name: string = '' @@ -644,7 +778,8 @@ export default class IscnRegisterForm extends Vue { tags: string[] = [] sameAs: string[] = [] url: string = '' - license: string = '' + license: string = this.licenseOptions[0] + customLicense: string = '' authorName: string = '' authorUrl: string[] = [] authorWalletAddress: string[] = [] @@ -657,6 +792,7 @@ export default class IscnRegisterForm extends Vue { authorDescription: string = '' contentFingerprintInput: string = '' customContentFingerprints: string[] = [] + type: string = this.defaultType arweaveFeeTargetAddress: string = '' arweaveFee = new BigNumber(0) @@ -669,6 +805,7 @@ export default class IscnRegisterForm extends Vue { isOpenFileInfoDialog = false isOpenAuthorDialog = false + isOpenSameAsDialog = false isOpenWarningSnackbar = false isOpenKeplr = true activeEditingAuthorIndex = -1 @@ -686,6 +823,9 @@ export default class IscnRegisterForm extends Vue { showUploadOnly = this.isUploadOnly + currentAuthorDialogType: AuthorDialogType = AuthorDialogType.stakeholder + sameAsList: any = [] + get tagsString(): string { return this.tags.join(',') } @@ -716,12 +856,19 @@ export default class IscnRegisterForm extends Vue { return this.exifInfo && this.exifInfo.ExifImageWidth } - get type() { + get defaultType() { if (this.isPhoto) return 'Photo' if (this.isImage) return 'Image' + if (this.fileType === 'application/pdf' || this.fileType === 'application/epub+zip') return 'Book' return 'CreativeWork' } + get authorDialogTitle() { + return this.currentAuthorDialogType === AuthorDialogType.author + ? this.$t('IscnRegisterForm.title.editAuthor') + : this.$t('IscnRegisterForm.title.editStakeholder') + } + get formattedIpfs() { return this.$t('IscnRegisterForm.ipfs.link', { hash: this.ipfsHash }) } @@ -730,6 +877,37 @@ export default class IscnRegisterForm extends Vue { return this.$t('IscnRegisterForm.arweave.link', { arweaveId: this.uploadArweaveId }) } + get contentFingerprintLinks() { + const array=[] + if (this.uploadArweaveId) { + array.push(this.formattedArweave) + } + if (this.ipfsHash){ + array.push(this.formattedIpfs) + } + if (this.customContentFingerprints.length){ + array.push(...this.customContentFingerprints) + } + return array + } + + get formattedSameAsList() { + return this.sameAsList.map((sameAs: { filename: any; filetype: any; url: any }) => { + if (sameAs.filename && sameAs.filetype) { + return `${sameAs.url}?name=${sameAs.filename}.${sameAs.filetype}`; + } + return ''; + }); + } + + get licenseOptions() { + return Object.keys(this.licenseMap) + } + + get formattedLicense() { + return this.licenseMap[this.license] || this.customLicense; + } + get errorMsg() { switch (this.error) { case 'INSUFFICIENT_BALANCE': @@ -799,14 +977,15 @@ export default class IscnRegisterForm extends Vue { name: this.name, description: this.description, tagsString: this.tagsString, - sameAs: this.sameAs, + sameAs: this.formattedSameAsList, url: this.url, exifInfo: this.exif, - license: this.license, + license: this.formattedLicense, ipfsHash: this.uploadIpfsHash || this.ipfsHash, arweaveId: this.uploadArweaveId || this.arweaveId, numbersProtocolAssetId: this.numbersProtocolAssetId, fileSHA256: this.fileSHA256, + author: this.author.name, authorNames: this.authorNames, authorUrls: this.authorUrls, authorWallets: this.authorWalletAddresses, @@ -874,8 +1053,7 @@ export default class IscnRegisterForm extends Vue { this.name && this.description && this.name.length <= CharactersLimit.name && - this.description.length <= CharactersLimit.description && - this.license.length <= CharactersLimit.license + this.description.length <= CharactersLimit.description ) } @@ -899,26 +1077,50 @@ export default class IscnRegisterForm extends Vue { addContentFingerprint() { this.customContentFingerprints.push(this.contentFingerprintInput) + this.contentFingerprintInput = '' } - handleOpenAuthorDialog() { - logTrackerEvent(this, 'ISCNCreate', 'OpenAuthorDialog', '', 1); + handleOpenAuthorDialog({ type }: { type: AuthorDialogType }) { this.checkedAuthorInfo = false this.isOpenAuthorDialog = true this.initAuthorInfo() + switch (type) { + case AuthorDialogType.author: + logTrackerEvent(this, 'ISCNCreate', 'OpenAuthorDialog', '', 1) + this.currentAuthorDialogType = AuthorDialogType.author + break + + case AuthorDialogType.stakeholder: + default: + logTrackerEvent(this, 'ISCNCreate', 'OpenStakeholderDialog', '', 1) + this.currentAuthorDialogType = AuthorDialogType.stakeholder + break + } } - editAuthor(index: number) { - logTrackerEvent(this, 'ISCNCreate', 'EditAuthor', index.toString(), 1); - const { name, wallet, url, likerId, authorDescription } = - this.authors[index] - this.authorName = name - this.authorWalletAddress = wallet - this.authorUrl = url - this.likerId = likerId - this.authorDescription = authorDescription - this.activeEditingAuthorIndex = index + editAuthor({ type = AuthorDialogType.stakeholder, index }: { type: AuthorDialogType; index: any }) { this.isOpenAuthorDialog = true + + if (type === AuthorDialogType.author) { + logTrackerEvent(this, 'ISCNCreate', 'EditAuthor', '', 1) + const { name, wallet, url, likerId, authorDescription } = this.author + this.authorName = name + this.authorWalletAddress = wallet + this.authorUrl = url + this.likerId = likerId + this.authorDescription = authorDescription + this.activeEditingAuthorIndex = this.authors.findIndex(author => author.name === name); + } else { + logTrackerEvent(this, 'ISCNCreate', 'EditStakeholder', index.toString(), 1) + const { name, wallet, url, likerId, authorDescription } = + this.authors[index] + this.authorName = name + this.authorWalletAddress = wallet + this.authorUrl = url + this.likerId = likerId + this.authorDescription = authorDescription + this.activeEditingAuthorIndex = index + } } dismissAuthorDialog() { @@ -959,6 +1161,9 @@ export default class IscnRegisterForm extends Vue { likerId: this.likerId, authorDescription: this.authorDescription, } + if (this.currentAuthorDialogType === AuthorDialogType.author) { + this.author = newAuthor + } if (this.activeEditingAuthorIndex >= 0) { this.authors.splice(this.activeEditingAuthorIndex, 1, newAuthor) } else { @@ -980,6 +1185,24 @@ export default class IscnRegisterForm extends Vue { this.authorName = value } + setType(value: string) { + this.type = value + } + + setLicense(value: string) { + this.license = value + } + + handleOpenSameAsDialog() { + this.isOpenSameAsDialog = true + } + + confirmSameAsChange(value: any) { + logTrackerEvent(this, 'ISCNCreate', 'ConfirmSameAsChange', '', 1); + this.sameAsList = value + this.isOpenSameAsDialog = false + } + async getLikerIdsAddresses(): Promise { try { this.likerIdsAddresses = await Promise.all( diff --git a/components/IscnUploadedInfo.vue b/components/IscnUploadedInfo.vue index f092888c..ff315363 100644 --- a/components/IscnUploadedInfo.vue +++ b/components/IscnUploadedInfo.vue @@ -10,10 +10,19 @@ - + + + + + + + + + @@ -178,6 +187,7 @@ import { Vue, Component, Prop } from 'vue-property-decorator' import { ISCNRecordWithID } from '~/utils/cosmos/iscn/iscn.type' +import { downloadJSON } from '~/utils/misc' import { BIG_DIPPER_TX_BASE_URL } from '~/constant' @Component({ @@ -216,7 +226,7 @@ export default class IscnUploadedInfo extends Vue { } get keywords(): Array { - return this.metadata?.keywords.split(',') || [] + return this.metadata?.keywords && this.metadata?.keywords.split(',') || [] } get contentFingerprints() { @@ -226,5 +236,20 @@ export default class IscnUploadedInfo extends Vue { get recordTimestamp() { return this.recordData?.recordTimestamp || '' } + + handleClickDownload() { + const generateData = { + contentMetadata: { + ...this.metadata, + '@type': this.type, + '@context': "http://schema.org/", + }, + stakeholders: this.recordData?.stakeholders, + contentFingerprints: this.contentFingerprints, + recordNotes: '', + } + + downloadJSON(generateData, 'iscn.json') + } } diff --git a/components/SameAsFieldList.vue b/components/SameAsFieldList.vue new file mode 100644 index 00000000..aeedeaf3 --- /dev/null +++ b/components/SameAsFieldList.vue @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + handleSelectValue({ value, index: i })" + /> + + + + + + + + + + + + + + + + + + + diff --git a/components/Selector.vue b/components/Selector.vue index 22baa95f..6e4f3f17 100644 --- a/components/Selector.vue +++ b/components/Selector.vue @@ -1,46 +1,45 @@ - - - {{ placeholder }} - - - + + + {{ currentValue }} + + + - {{ option }} - + + {{ option }} + + - - + + diff --git a/constant/index.ts b/constant/index.ts index 242adeb5..30acf60c 100644 --- a/constant/index.ts +++ b/constant/index.ts @@ -58,6 +58,14 @@ export const WALLET_TYPES = [ 'eth', ] +export const SAME_AS_FILE_TYPES = [ + 'epub', + 'pdf', + 'mp3', + 'jpg', + 'png', +] + export const SITE_URL = IS_TESTNET ? 'https://app.rinkeby.like.co' : 'https://app.like.co'; export const CONNECT_WALLET_TYPES = [ diff --git a/locales/en.json b/locales/en.json index 0ea72708..0cedbeb2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -143,7 +143,7 @@ "iscn.meta.description.placeholder": "Description", "iscn.meta.description": "Description", "iscn.meta.id": "ISCN ID", - "iscn.meta.keywords.placeholder": "Keywords (seperate by ,)", + "iscn.meta.keywords.placeholder": "Keywords (separate by ,)", "iscn.meta.keywords": "Keywords", "iscn.meta.ipfsHash": "IPFS Hash", "iscn.meta.license.placeholder": "License URL", @@ -163,7 +163,7 @@ "iscn.meta.stakeholders.name": "Stakeholder Name", "iscn.meta.stakeholders.url": "URL", "iscn.meta.stakeholders.wallet.copied": "Copied", - "iscn.meta.stakeholders.wallet.placeholder": "Wallet addresss", + "iscn.meta.stakeholders.wallet.placeholder": "Wallet addresses", "iscn.meta.stakeholders.wallet": "Wallet Address (click to copy)", "iscn.meta.stakeholders": "Stakeholders", "iscn.meta.tags.title": "Tags", @@ -205,6 +205,7 @@ "IscnRegisterForm.guide.review": "Review and edit metadata", "IscnRegisterForm.ipfs.link": "ipfs://{hash}", "IscnRegisterForm.label.author": "Author", + "IscnRegisterForm.label.stakeholder": "Stakeholders", "IscnRegisterForm.label.description": "Description", "IscnRegisterForm.label.emptyFile": "No Content", "IscnRegisterForm.label.fingerprints": "Content Fingerprints", @@ -213,11 +214,15 @@ "IscnRegisterForm.label.likerID": "LikerID", "IscnRegisterForm.label.sameAs" : "Same As URLs", "IscnRegisterForm.label.name": "Name", + "IscnRegisterForm.label.fileName": "File name", + "IscnRegisterForm.label.fileType": "File type", "IscnRegisterForm.label.numbersProtocol": "Numbers Protocol", "IscnRegisterForm.label.numbersProtocol.details": "Register your asset in {link}", "IscnRegisterForm.label.numbersProtocol.details.link": "Numbers Protocol", "IscnRegisterForm.label.registrant": "Registrant", "IscnRegisterForm.label.tags": "Tags", + "IscnRegisterForm.label.type": "Type", + "IscnRegisterForm.label.content": "Content Info", "IscnRegisterForm.label.uploading": "Loading files...", "IscnRegisterForm.label.url": "URL", "IscnRegisterForm.label.wallet": "Wallet Address", @@ -226,6 +231,8 @@ "IscnRegisterForm.placeholder.license": "License", "IscnRegisterForm.placeholder.likerID": "LikerID", "IscnRegisterForm.placeholder.name": "Name", + "IscnRegisterForm.placeholder.fileName": "File Name (en)", + "IscnRegisterForm.placeholder.fileType": "File Type", "IscnRegisterForm.placeholder.url": "URL", "IscnRegisterForm.placeholder.wallet": "Wallet Address", "IscnRegisterForm.quitAlertDialog.confirm": "Cancel Registration", @@ -240,7 +247,9 @@ "IscnRegisterForm.signDialog.sign.arweave.upload": "Please sign to upload to Arweave", "IscnRegisterForm.signDialog.sign.arweave.uploading": "Depending on the file size, it may take several minutes.\n You will be requested to sign again before finishing.", "IscnRegisterForm.signDialog.sign.iscn.register": "Please sign to register ISCN record", - "IscnRegisterForm.title.editAuthor": "Edit Author", + "IscnRegisterForm.sameAsDialog.title": "Define sameAs URLs", + "IscnRegisterForm.title.editAuthor": "Add Author", + "IscnRegisterForm.title.editStakeholder": "Edit Stakeholder", "IscnRegisterForm.title.ready": "File Ready", "IscnRegisterForm.warning.exceeded": "Character limit exceeded ({current}/{limit})", "IscnRegisterForm.warning.exist": "This tag already exists", diff --git a/pages/nft/url/index.vue b/pages/nft/url/index.vue index 3362f700..98d25eb7 100644 --- a/pages/nft/url/index.vue +++ b/pages/nft/url/index.vue @@ -297,6 +297,7 @@ export default class FetchIndex extends Vue { ipfsHash: this.ipfsHash, arweaveId: this.arweaveId, fileSHA256: '', + author, authorNames: [author], authorUrls: [[]], authorWallets: [[{ diff --git a/utils/cosmos/iscn/iscn.type.ts b/utils/cosmos/iscn/iscn.type.ts index dbe65ca7..50abb4f9 100644 --- a/utils/cosmos/iscn/iscn.type.ts +++ b/utils/cosmos/iscn/iscn.type.ts @@ -13,6 +13,7 @@ export interface ISCNRegisterPayload { fileSHA256: string; type: string; publisher?: string, + author: string; authorNames: string[]; authorUrls: string[][]; authorWallets: any[][]; diff --git a/utils/cosmos/iscn/sign.ts b/utils/cosmos/iscn/sign.ts index dfb68c1d..ba43299e 100644 --- a/utils/cosmos/iscn/sign.ts +++ b/utils/cosmos/iscn/sign.ts @@ -35,6 +35,7 @@ export function formatISCNTxPayload(payload: ISCNRegisterPayload): ISCNSignPaylo ipfsHash, arweaveId, fileSHA256, + author, authorNames, authorUrls, authorWallets, @@ -126,6 +127,7 @@ export function formatISCNTxPayload(payload: ISCNRegisterPayload): ISCNSignPaylo return { ...data, + author, keywords: tagsString.split(','), usageInfo: license, contentFingerprints: [...new Set(contentFingerprints)], diff --git a/utils/misc.ts b/utils/misc.ts index f1d5d35a..774dcb1e 100644 --- a/utils/misc.ts +++ b/utils/misc.ts @@ -36,3 +36,20 @@ export function catchAxiosError(promise: AxiosPromise) { } }); } + +export function downloadJSON(data: Object, fileName: string) { + const jsonData = JSON.stringify(data, null, 2) + + const jsonBlob = new Blob([jsonData], { type: 'application/json' }) + + const jsonUrl = URL.createObjectURL(jsonBlob) + + const jsonLink = document.createElement('a') + jsonLink.href = jsonUrl + jsonLink.download = fileName + jsonLink.style.display = 'none' + + document.body.appendChild(jsonLink) + jsonLink.click() + document.body.removeChild(jsonLink) +}