<template>
<div class="github-menu" v-if="mainRepo">
<div class="github-menu-wrapper"
@mouseenter="expanded = true"
@focus="expanded = true"
@mouseleave="expanded = false"
@blur="expanded = false"
>
<div class="github-menu-buttons">
<div class="github-components">
<header>
<a :href="`https://github.com/${mainRepo.ownerLogin}/${mainRepo.name}/`"
target="_blank"
class="has-text-black"
title="Open repository in GitHub"
>
<b-icon icon="github" size="is-large"/>
<span v-if="expanded">GitHub Integration</span>
</a>
</header>
<b-button icon-left="delete"
:label="expanded? 'Discard local changes' : ''"
:disabled="mainRepo.files.filter(f => f.hasChanged()).length === 0"
type="is-danger"
@click="reload"
size="is-medium"
expanded
/>
<b-button icon-left="content-save"
:label="expanded? 'Save changes to GitHub' : ''"
:disabled="mainRepo.files.filter(f => f.hasChanged()).length === 0"
type="is-success"
@click="save"
size="is-medium"
expanded
/>
</div>
<b-loading :active="$store.state.workshop.busyFlags.length !== 0" :is-full-page="false"/>
<b-button v-if="pagesURL !== ''"
tag="a"
:href="pagesURL"
target="_blank"
icon-left="link"
:label="expanded? 'View workshop website' : ''"
:disabled="!mainRepo"
type="is-info"
size="is-medium"
expanded
/>
<b-button v-if="pagesURL !== '' && $store.state.github.buildStatus"
:type="`is-light ${buildType}`"
:icon-left="buildIcon"
:loading="buildType === 'is-info'"
:label="expanded? buildMessage : ''"
:title="$store.state.github.buildStatus.error.message"
expanded
aria-roledescription="div"
/>
<div v-if="lastCheck && expanded" class="has-text-grey-light last-check-time">
Last build status check: {{ lastCheck }}
</div>
</div>
</div>
</div>
</template>
<script>
/**
* @description The GitHubMenu component provides a sidebar with buttons and information relevant to the GitHub instance of the current main repository. It offers a link to the GitHub repository, options to discard or push all changes to local files, a link to the GitHub Pages render of the repository, and details of the last Build Status Check.
*
* @vue-data expanded=false {Boolean} Whether the sidebar is expanded.
*
* @vue-computed mainRepo {Repository} Repository currently being edited in the Workshop Builder tool.
* @vue-computed pagesURL {String} Link to homepage of the repository as rendered by GitHub Pages.
* @vue-computed buildType {String} Buefy class string representing how the build status should be styled.
* @vue-computed buildIcon {String} MDI icon name for the build status icon.
* @vue-computed buildMessage {String} Description of the build status.
* @vue-computed lastCheck {String|null} Pretty datetime string giving the time of the last build.
*/
export default {
name: 'GitHubMenu',
components: {},
props: {},
data: function() {
return {
expanded: false
}
},
computed: {
mainRepo() {return this.$store.getters['workshop/Repository']()},
pagesURL() {
try {
return `https://${this.mainRepo.ownerLogin}.github.io/${this.mainRepo.name}`;
} catch(e) {return ""}
},
buildType() {
try {
switch(this.$store.state.github.buildStatus.status) {
case "built": return 'is-success';
case "errored": return 'is-danger';
default: return 'is-info';
}
} catch(e) {return ''}
},
buildIcon() {
try {
switch(this.$store.state.github.buildStatus.status) {
case "built": return 'check';
case "errored": return 'exclamation';
default: return 'dots-horizontal';
}
} catch(e) {return ''}
},
buildMessage() {
try {
const build = this.$store.state.github.buildStatus;
switch(build.status) {
case "built": return `Website built successfully in ${Math.round(build.duration / 10) / 100}s`;
case "errored": return `Website build failure.`;
default: return 'Website building in progress...';
}
} catch(e) {return ''}
},
lastCheck() {
try {
if(!this.$store.state.github.lastBuildStatusCheck)
return null;
const t = new Date(this.$store.state.github.lastBuildStatusCheck + performance.timeOrigin);
return `${t.getDate()}/${t.getMonth() + 1}/${t.getFullYear()} - ${t.getHours()}:${Math.floor(t.getMinutes()/10)}${t.getMinutes()%10}${t.getHours() < 12? 'am' : 'pm'}`
} catch(e) {return null}
}
},
methods: {
/**
* Discard local store copies of all files and retrieve fresh versions from GitHub.
* @return {Promise<BNoticeComponent>}
*/
reload() {
const self = this;
return this.$store.dispatch('workshop/loadRepository', {url: self.mainRepo.url})
.then(R => self.$store.dispatch('workshop/findRepositoryFiles', {url: R.url}))
.then(() => self.$buefy.toast.open({
message: "Repository reloaded", type: "is-success"
}))
.catch(e => {console.error(e); self.$buefy.toast.open({
message: "Failed to reload repository", type: "is-danger"
})})
},
/**
* Push all changes to all altered local store files to the remote GitHub repository.
* @return {Promise<BNoticeComponent>}
*/
save() {
const self = this;
return this.$store.dispatch('workshop/saveRepositoryChanges')
.then(({successes, failures}) => {
if(failures && !successes)
self.$buefy.toast.open({
message: `Failed to push ${failures} file${failures === 1? '' : 's'} to GitHub`,
type: 'is-danger'
});
else if(failures)
self.$buefy.toast.open({
message: `Could not push all files to GitHub. Successes: ${successes}; Failures: ${failures}`,
type: 'is-warning'
});
else if(successes)
self.$buefy.toast.open({
message: `Successfully pushed ${successes} file${successes === 1? '' : 's'} to GitHub`,
type: 'is-success'
});
})
}
},
watch: {}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss" scoped>
.github-menu {
height: 100%;
top: 0;
right: 0;
position: fixed;
display: flex;
flex-direction: row;
align-items: center;
z-index: 30;
}
.github-menu-wrapper {
background-color: rebeccapurple;
padding: .5em 0 .5em 1em;
border-top-left-radius: 1em;
border-bottom-left-radius: 1em;
opacity: 0.5;
}
.github-menu-wrapper:hover {opacity: 1;}
.github-menu-buttons {
background-color: white;
border-top-left-radius: 1em;
border-bottom-left-radius: 1em;
padding: .5em 0 .5em .5em;
.github-components {
display: flex;
flex-direction: column;
header a {
display: flex;
align-items: center;
justify-content: space-evenly;
font-size: 1.3em;
margin-bottom: .25em;
user-select: none;
}
button:first-of-type {border-bottom-left-radius: 0}
button:last-of-type {border-top-left-radius: 0}
}
a, button {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
a {
margin-top: .5em;
}
}
.last-check-time {font-size: .8em;}
</style>