Skip to content

.xmp support in license attribution #272

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 2, 2021
73 changes: 50 additions & 23 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,23 @@
/>
<help-section />
</div>
<div class="column">
<div class="column right-column">
<!-- The right column with the recommended license should be fixed until
the 'LicenseUseCard' appears, when the column should scroll to make the
'LicenseUseCard' visible -->
<div :class="{ 'fixed-right-column': !showLicenseUse }">
<transition name="appear">
<LicenseDetailsCard
v-if="showLicense"
/>
</transition>

<LicenseUseCard
v-if="showLicenseUse"
ref="licenseUseCard"
:class="{ 'shake' : shouldShake}"
/>
<transition name="appear">
<LicenseUseCard
v-if="showLicenseUse"
ref="licenseUseCard"
:class="{ 'shake' : shouldShake}"
/>
</transition>
</div>
</div>
</div>
Expand All @@ -63,7 +67,6 @@

import HelpSection from './components/HelpSection'
import Stepper from './components/Stepper'
import LicenseUseCard from './components/LicenseUseCard'
import HeaderSection from './components/HeaderSection'
import FooterSection from './components/FooterSection'
import LicenseDetailsCard from './components/LicenseDetailsCard'
Expand All @@ -74,28 +77,49 @@ export default {
HelpSection,
Stepper,
LicenseDetailsCard,
LicenseUseCard,
LicenseUseCard: () => import('@/components/LicenseUseCard'),
HeaderSection,
FooterSection
},
data() {
return {
currentStepId: 0,
showLicense: false,
shouldShake: false
shouldShake: false,
windowWidth: window.innerWidth
}
},
computed: {
showLicenseUse() {
return this.currentStepId === 7
},
isBelowTabletWidth() {
return this.windowWidth < 769
}
},
watch: {
currentStepId(newId) {
const offset = newId === 7 ? -200 : -120
this.$scrollTo(`.step-${newId}`, { offset: offset })
async currentStepId(newId, oldId) {
// When the new step opens, the page is scrolled to the top of the
// previous step. When the 'Back' button is clicked, the page is
// scrolled to the previous step.
// If the user chooses No attribution, the page is scrolled to the top
// of the disabled steps, i.e. step 2.
let stepToScroll = newId === 6 ? 2 : Math.min(newId, oldId)
if (newId === 6) {
stepToScroll = 2
}
await this.$nextTick()
this.$scrollTo(`.step-${stepToScroll}`)
}
},
mounted() {
this.$nextTick(() => {
window.addEventListener('resize', this.onResize)
})
},
beforeDestroy() {
window.removeEventListener('resize', this.onResize)
},
created: function() {
// send home to google analytics
if (process.env.NODE_ENV === 'production') {
Expand All @@ -113,13 +137,18 @@ export default {
this.showLicense = 0
},
done() {
const scrollDuration = 800
const shakeDuration = 3000
// Add 'shake' class that triggers animation to the 'LicenseUseCard',
// when 'Done' button is clicked, after the scroll has finished.
//
const scrollDuration = this.isBelowTabletWidth ? 3000 : 800
const shakeDuration = 3000 + scrollDuration
const comp = this

setTimeout(() => { comp.shouldShake = true }, scrollDuration)
setTimeout(() => { comp.shouldShake = false }, shakeDuration)
this.$scrollTo(this.$refs.licenseUseCard.$el, 800)
this.$scrollTo(this.$refs.licenseUseCard.$el, scrollDuration)
},
onResize() {
this.windowWidth = window.innerWidth
}
}
}
Expand All @@ -144,7 +173,7 @@ export default {
@import '~buefy/src/scss/components/_form.scss';
@import '~buefy/src/scss/components/_icon.scss';

@import "@creativecommons/vocabulary/scss/vocabulary.scss";
@import "~@creativecommons/vocabulary/scss/vocabulary.scss";

#app {
-webkit-font-smoothing: antialiased;
Expand Down Expand Up @@ -227,14 +256,12 @@ export default {

}
.appear-enter-active {
transition: all .8s ease;
transition: opacity .8s ease;
}
.appear-leave-active {
transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
transition: opacity .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.appear-enter, .appear-leave-to
/* .appear-leave-active below version 2.1.8 */ {
transform: translateY(-10px);
.appear-enter, .appear-leave-to {
opacity: 0;
}
</style>
11 changes: 4 additions & 7 deletions src/components/CopyTools.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,18 @@
>
{{ copyLabel }}
</v-button>
<v-button
v-else
class="donate small copy-button is-xmp"
>
{{ xmpLabel }}
</v-button>
<xmp-button v-if="clipboardTarget==='.xmp'" />
</div>
</template>

<script>
import CopyTypeSwitch from '@/components/CopyTypeSwitch'
import Clipboard from 'clipboard'
import XmpButton from '@/components/XmpButton'

export default {
name: 'CopyTools',
components: { CopyTypeSwitch },
components: { CopyTypeSwitch, XmpButton },
props: {
clipboardTarget: {
type: String,
Expand Down
34 changes: 34 additions & 0 deletions src/components/XmpButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<template>
<a
ref="xmp"
class="button donate small copy-button is-xmp"
type="text/xml"
:href="xmpHref"
:download="xmpFilename"
>
{{ xmpLabel }}
</a>
</template>

<script>
import { createXMP } from '@/utils/xmp'
import { mapGetters } from 'vuex'

export default {
name: 'XmpButton',
computed: {
...mapGetters(['shortName']),
xmpLabel() { return this.$t('license-use.xmp-label') },
xmpFilename() {
return `${this.shortName}.xmp`
},
xmpHref() {
const shortName = this.$store.getters.shortName
const { workUrl, workTitle, creatorName } = this.$store.state.attributionDetails
const xmp = createXMP({ shortName, workUrl, workTitle, creatorName })
const xmpBlob = new Blob([xmp], { type: 'text/xml;charset=utf-8' })
return URL.createObjectURL(xmpBlob)
}
}
}
</script>
66 changes: 66 additions & 0 deletions src/utils/xmp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { LICENSES, licenseSlug } from '@/utils/license-utilities'

function minify(data) {
return data.replace(/ {2,}/gi, '').replace(/\n/gi, '')
}

export const createXMP = ({ shortName, workUrl = '', workTitle = '', creatorName = '', lang = 'en-US' }) => {
const slug = licenseSlug(shortName).replace(/-/gi, '_').toUpperCase()

const copyrighted = shortName !== LICENSES.CC0.SHORT
const xapRights = copyrighted
? `<rdf:Description rdf:about='' xmlns:xapRights='http://ns.adobe.com/xap/1.0/rights/'>
<xapRights:Marked>true</xapRights:Marked></rdf:Description>`
: ''
const xapWorkUrl = workUrl
? `<rdf:Description rdf:about='' xmlns:xapRights='http://ns.adobe.com/xap/1.0/rights/'>
<xapRights:WebStatement rdf:resource='${workUrl}'/></rdf:Description>`
: ''
const xapWorkTitle = workTitle
? `
<rdf:Description rdf:about='' xmlns:dc='http://purl.org/dc/elements/1.1/'><dc:title><rdf:Alt><rdf:li xml:lang='x-default'>
${workTitle}</rdf:li><rdf:li xml:lang='${lang}'>${workTitle}</rdf:li></rdf:Alt></dc:title></rdf:Description>`
: ''

const licenseUrl = LICENSES[slug].URL

const ccLicenseUrl = `
<rdf:Description rdf:about='' xmlns:cc='http://creativecommons.org/ns#'><cc:license rdf:resource='${licenseUrl}'/></rdf:Description>`

const ccLicenseNotice = `
This work is licensed under <a rel="license noopener noreferrer" target="_blank" href="${licenseUrl}">${LICENSES[slug].FULL}</a>`

const xapRightsUsageTerms = `
<rdf:Description rdf:about='' xmlns:xapRights='http://ns.adobe.com/xap/1.0/rights/'>
<xapRights:UsageTerms><rdf:Alt><rdf:li xml:lang='${lang}' >${ccLicenseNotice}</rdf:li></rdf:Alt>
</xapRights:UsageTerms></rdf:Description>`

const xapWebStatement = workUrl
? `
<rdf:Description rdf:about='' xmlns:xapRights='http://ns.adobe.com/xap/1.0/rights/'>
<xapRights:WebStatement rdf:resource='${workUrl}'/></rdf:Description>`
: ''

const ccAttributionName = creatorName
? `
<rdf:Description rdf:about='' xmlns:cc='http://creativecommons.org/ns#'>
<cc:attributionName>${creatorName}</cc:attributionName></rdf:Description>`
: ''
// eslint-disable-line quotes
const xmpData = `
<?xpacket begin='' id=''?>
<x:xmpmeta xmlns:x='adobe:ns:meta/'>
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
${xapRights}
${xapWorkUrl}
${xapWebStatement}
${xapRightsUsageTerms}
${xapWorkTitle}
${ccLicenseUrl}
${ccAttributionName}
</rdf:RDF>
</x:xmpmeta>
<?xpacket end='r'?>`
// We return minified string to not increase the size of the licensed file
return minify(xmpData)
}
9 changes: 6 additions & 3 deletions tests/e2e/page-objects/chooser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@

const stepperCommands = {
clickYes: function() {
this.pause(500)
this.click('.radio-input[value="yes"]')
this.pause(500)
return this
},
clickNo: function() {
this.pause(500)
this.click('.radio-input[value="no"]')
this.pause(500)
return this
},
clickNext: function() {
this.click('.next-button')
this.pause(500)
return this
},
clickPrevious: function() {
this.click('.previous-button')
this.pause(500)
return this
},
chooseNo: function() {
Expand All @@ -34,7 +36,7 @@ const stepperCommands = {
clickWaiver: function() {
this.click('.v-checkbox:first-child')
.click('.v-checkbox:last-child')
.click('.next-button')
this.clickNext()
return this
},
selectFromDropdown: function(licenseName) {
Expand All @@ -43,6 +45,7 @@ const stepperCommands = {
.click('.license-dropdown')
.click(`.license-dropdown option[value="${licenseName}"]`)
.click('.next-button')
this.pause(500)
return this
},
assertStepName: function(stepName) {
Expand Down