Skip to content
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

Feature/add presentations #108

Merged
merged 12 commits into from
Apr 9, 2020
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"description": "Source code for events.vuejs.org",
"main": "index.js",
"scripts": {
"dev": "vuepress dev src",
"build": "vuepress build src"
"dev": "NODE_ENV=development vuepress dev src",
"build": "NODE_ENV=production vuepress build src"
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
},
"repository": {
"type": "git",
Expand Down
81 changes: 81 additions & 0 deletions src/.vuepress/components/PresentationFilters.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<template>
<div>
<select label="Theme" v-model="value.theme">
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
<option value="">Choose</option>
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
<option v-for="item in themes">{{item}}</option>
</select>
<select label="Year" v-model="value.year">
<option value="">Choose</option>
<option v-for="item in years">{{item}}</option>
</select>
<select label="Author" v-model="value.author">
<option value="">Choose</option>
<option v-for="item in getItemsForPresentationKey('author')">{{item}}</option>
</select>
<select label="Event" v-model="value.event">
<option value="">Choose</option>
<option v-for="item in getItemsForPresentationKey('event', 'name')">{{item}}</option>
</select>
<select label="Country" v-model="value.country">
<option value="">Choose</option>
<option v-for="item in getItemsForPresentationKey('country')">{{item}}</option>
</select>
<select label="Language" v-model="value.language">
<option value="">Choose</option>
<option v-for="item in getItemsForPresentationKey('sourceLanguage')">{{item}}</option>
</select>
</div>
</template>

<script>
import {presentationData} from '../data';
export default {
name: "PresentationFilters",
props: {
value: {
type: Object,
required: true
},
},
computed: {
themes() {
return Object.keys(presentationData);
},
years() {
let years = [];
Object.values(presentationData).forEach(presentationByYear => {
// presentationByYear look like {2019: [...], 2020: [...]}
// So basically we concat the keys of presentationByYear with already found years and put it in a set to avoid duplcate
years = [...new Set(years.concat(Object.keys(presentationByYear)))]
})
return years
}
},
data() {
return {};
},
methods: {
getItemsForPresentationKey(key, subKey=null) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting all the filter item get methods can be improved to only loop over presentation once (like done in presentationFiltered). But originally I wanted the filter to be related to each others (only display items left depends of other filters selectionned but that trigger some UX issues).
Can be change to loop only once if needed.

const items = new Set();
Object.values(presentationData).forEach(presentationsByYear => {
// presentationByYear look like {2019: [...], 2020: [...]}

Object.values(presentationsByYear).forEach(presentations => {
presentations.forEach(presentation => {
// presentation is a presentation object as declared in json file
if(!subKey && presentation[key]) {
return items.add(presentation[key])
}
else if(presentation[key] && presentation[key][subKey]) {
items.add(presentation[key][subKey])
}
})
})
})
return items
}
}
};
</script>

<style lang="scss" scoped></style>
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
34 changes: 34 additions & 0 deletions src/.vuepress/components/PresentationInfoLine.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<template>
<li>{{text}}
<a
v-if="link"
:href="link"
target="_blank"
rel="noopener noreferrer">
{{value}}
</a>
<span v-else>{{value}}</span>
</li>
</template>

<script>
export default {
name: "PresentationInfoLine",
props: {
text: {
type: String,
default: ""
},
value: {
type: String,
default: ""
},
link: {
type: String,
default: ""
}
},
};
</script>

<style lang="scss" scoped></style>
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
61 changes: 61 additions & 0 deletions src/.vuepress/components/PresentationItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<template>
<div>
<h4>{{presentation.title}} ({{year}}) <span class="author-by">by {{presentation.author}}</span></h4>
<ul>
<PresentationInfoLine text="Source Language:" :value="presentation.sourceLanguage" />
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
<PresentationInfoLine text="City:" :value="`${presentation.city}, ${presentation.country}`" />
<PresentationInfoLine text="Event:" :value="presentation.event.name" :link="presentation.event.link" />
<PresentationInfoLine text="Git:" :value="presentation.gitRepository" :link="presentation.gitRepository" />
<PresentationInfoLine text="Video:" :value="presentation.video" :link="presentation.video" />
<PresentationInfoLine text="Source Language:" :value="presentation.sourceLanguage" />
<li>Reach Speaker:
<ul>
<PresentationInfoLine text="Twitter:" :value="presentation.reachSpeaker.twitter" :link="presentation.reachSpeaker.twitter" />
<PresentationInfoLine text="Github:" :value="presentation.reachSpeaker.github" :link="presentation.reachSpeaker.github" />
<PresentationInfoLine text="Vue Discord:" :value="presentation.reachSpeaker.vueDiscord" />
<PresentationInfoLine text="Email:" :value="presentation.reachSpeaker.email" />
</ul>
</li>
</ul>
<strong>Description:</strong>
<p>{{presentation.description}}</p>
</div>
</template>

<script>
import PresentationInfoLine from './PresentationInfoLine';

export default {
name: "PresentationItem",
components: {
PresentationInfoLine,
},
props: {
presentation: {
type: Object,
required: true
},
year: {
type: String,
required: true
}
},
computed: {
presentationWithMeta() {
const presentationWithMeta = {};
Object.entries(this.presentation).forEach(([key, value]) => {
if(["description"].includes(key)) {
return;
}

})
}
}
};
</script>

<style lang="css" scoped>
.author-by {
float: right;
}
</style>
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
113 changes: 113 additions & 0 deletions src/.vuepress/components/PresentationList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<template>
<div>
<PresentationFilters :value="filters" />
<div v-for="(yearPresentation, theme) in presentationFiltered" :key="theme">
<h2>{{ theme }}</h2>
<div v-for="(presentations, year) in yearPresentation" :key="`${theme}-${year}`">
<!--<h3>{{ year }}</h3>-->
<!-- put a message that say that no presentation with current filter are available and user need to change filters to see presentation in this theme ? -->
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
<div v-for="presentation in presentations" :key="presentation.title">
<PresentationItem :presentation="presentation" :year="year" />
</div>
</div>
</div>
</div>
</template>

<script>
import {presentationData} from '../data';
import PresentationItem from './PresentationItem';
import PresentationFilters from './PresentationFilters';

export default {
name: "PresentationList",
components: {
PresentationItem
},
data() {
return {
filters: {
theme: "",
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
year: "",
author: "",
event: "",
country: "",
language: "",
}
};
},
computed: {
presentationFiltered() {
// The filter loop over themes then years then presentations to filter them. With this logic we iterate only once on all presentations. The logic look like this:
// Is theme in the theme filtered ?
// yes -> Is the year in the year filtered ?
// yes -> is the presentation has the same value that the filter ?
// yes -> display this presentation
// no -> remove this presentation
// no -> remove all the presentation of this year
// no -> remove all presentation of this theme
return this.filterByTheme(presentationData, this.filters);
}
},
methods: {
filterByTheme(presentationByTheme, filters) {
// presentationByTheme is presentationData object
let allowed;
// If we want to filter by theme we assign it. If not we assign all the key to not filter it
if(filters["theme"]) {
allowed = [filters["theme"]]
} else {
allowed = Object.keys(presentationByTheme);
}
// This code basically just filter by key
return Object.keys(presentationByTheme)
.filter(key => allowed.includes(key))
.reduce((obj, key) => {
obj[key] = this.filterByYear(presentationByTheme[key], filters);
return obj;
}, {});
},
filterByYear(presentationByYear, filters) {
// presentationByYear look like {2019: [...], 2020: [...]}
// If we want to filter by year we assign it. If not we assign all the key to not filter it
let allowed;
if(filters["year"]) {
allowed = [filters["year"]]
} else {
allowed = Object.keys(presentationByYear);
}
// we filter presentation by year and when we set the presentations data we filter them directly to avoid multiple loop
return Object.keys(presentationByYear)
.filter(key => allowed.includes(key))
.reduce((obj, key) => {
obj[key] = this.filterPresentations(presentationByYear[key], filters);
return obj;
}, {});
},
filterPresentations(presentations, filters) {
// prepare filter by multiple value
return presentations.filter(presentation => {
return this.presentationMatchFilter(presentation, filters)
})

},
presentationMatchFilter(presentation, filters) {
if (filters["author"] && filters["author"] !== presentation["author"]) {
return false;
}
if (filters["country"] && filters["country"] !== presentation["country"]) {
return false;
}
if (filters["language"] && filters["language"] !== presentation["language"]) {
return false;
}
if (filters["event"] && filters["event"] !== presentation["event"]["name"]) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if presentation["event"] not defined it will crash. But can't add the check because we don't want to return a presentation without event if the event filter is on.
This need a second condition just for it. Like json format can be mandatory I didn't do it. I can if asked.

return false;
}
return true;
}
}
};
</script>

<style lang="scss" scoped></style>
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
18 changes: 17 additions & 1 deletion src/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ module.exports = {
}
]
},
{
text: 'Presentation',
items: [
{
text: 'Discover',
link: '/presentations/#discover'
},
{
text: "Submit your",
bencodezen marked this conversation as resolved.
Show resolved Hide resolved
link: '/presentations/#submit'
},
]
},
{
text: 'Contact',
items: [
Expand Down Expand Up @@ -179,5 +192,8 @@ module.exports = {
ga: 'UA-46852172-1'
}
]
]
],
define: {
NODE_ENV: process.env.NODE_ENV,
}
}
22 changes: 22 additions & 0 deletions src/.vuepress/data/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
const dataFiles = require.context(".", false, /\.json$/);
const presentationDataFiles = require.context("./presentations", true, /\.json$/);

const data = {};
const presentationData = {};

dataFiles.keys().forEach((fileName) => {
const fileData = dataFiles(fileName);
const year = fileName.replace(/^\..*\//, '').replace(/\.\w+$/, '');
data[year] = fileData.default || fileData;
});

presentationDataFiles.keys().forEach((fileName) => {
let fileData = presentationDataFiles(fileName);
fileName = fileName.replace(/\.\//, '').replace(/\.json/, '')
const [theme, year, title] = fileName.split("/", 3)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefered keep a directory architecture. It really simple to put everything in a big json.
But I really think it will be more easier to contribute with this directory architecture.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So are you thinking each person will add their own markdown file that has YAML / JSON in it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a possibility if they want to. Actually I will strongly recommend json to keep same format across every talk.
Just saying that finding informations can be easier in directory architecture than in big json. And it will be really esay to convert to NoSql if needed.

// Example folder are juste for test and pr review. We do not want them in production
if (["example", "example2"].includes(theme.toLowerCase()) && NODE_ENV !== "development") {
return;
}
if (!presentationData[theme]) {
presentationData[theme] = {}
}
if (!presentationData[theme][year]) {
presentationData[theme][year] = []
}
fileData = fileData.default || fileData;
fileData = {...fileData, title}
presentationData[theme][year].push(fileData.default || fileData)
});

export default data;
export { data, presentationData};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"author": "Adrien Montagu",
"sourceLanguage": "English",
"city": "Lyon",
"country": "France",
"event": {
"name": "event",
"link": null
},
"gitRepository": null,
"videoLink": null,
"reachSpeaker": {
"twitter": null,
"github": "https://github.com/amontagu",
"vueDiscord": "Adrien Montagu#0597",
"email": null
},
"description": "This presentation is only an example on how a speaker can share his presentation on this github."
}
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
Loading