chore: lint
This commit is contained in:
parent
97c2c312d0
commit
56eef15dc2
22
src/App.vue
22
src/App.vue
|
@ -19,23 +19,15 @@ import SkillPicker from './components/SkillPicker.vue';
|
||||||
Home,
|
Home,
|
||||||
Timeline,
|
Timeline,
|
||||||
SkillPicker,
|
SkillPicker,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
export default class App extends Vue {
|
export default class App extends Vue {
|
||||||
private selected: tID|null = null;
|
private selected: tID|null = null;
|
||||||
|
|
||||||
private mounted() {
|
|
||||||
const picker = this.$refs.picker as SkillPicker;
|
|
||||||
if( picker == null ){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
picker.select(tID.Vue, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// skill picker selection -> filters the timeline
|
// skill picker selection -> filters the timeline
|
||||||
protected onPick(id: tID|null) {
|
protected onPick(id: tID|null) {
|
||||||
const timeline = this.$refs.timeline as Timeline;
|
const timeline = this.$refs.timeline as Timeline;
|
||||||
if( timeline == null ){
|
if ( timeline == null ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
timeline.filter(id);
|
timeline.filter(id);
|
||||||
|
@ -44,12 +36,20 @@ export default class App extends Vue {
|
||||||
// skill picked from the timeline -> select on the skill picker
|
// skill picked from the timeline -> select on the skill picker
|
||||||
protected onPicked(id: tID) {
|
protected onPicked(id: tID) {
|
||||||
const picker = this.$refs.picker as SkillPicker;
|
const picker = this.$refs.picker as SkillPicker;
|
||||||
if( picker == null ){
|
if ( picker == null ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
picker.select(id, false);
|
picker.select(id, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private mounted() {
|
||||||
|
const picker = this.$refs.picker as SkillPicker;
|
||||||
|
if ( picker == null ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
picker.select(tID.Vue, false);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -37,64 +37,63 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from 'vue-property-decorator';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import { go } from '@/service/scroller';
|
import { go } from '@/service/scroller';
|
||||||
import { TypeWriter } from '@/service/typewriter';
|
import { TypeWriter } from '@/service/typewriter';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {},
|
components: {},
|
||||||
})
|
})
|
||||||
export default class Home extends Vue {
|
export default class Home extends Vue {
|
||||||
protected scrollNext() {
|
protected scrollNext() {
|
||||||
go('skill-picker', 0);
|
go('skill-picker', 0);
|
||||||
}
|
|
||||||
|
|
||||||
protected mounted() {
|
|
||||||
let last = 0;
|
|
||||||
|
|
||||||
// check if already animated in the last 10 min
|
|
||||||
const typed = localStorage.getItem('typed')
|
|
||||||
const parsed = parseInt(typed!) as number
|
|
||||||
if( !isNaN(parsed) ){
|
|
||||||
last = parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = new Date().getTime();
|
|
||||||
const diff_sec = (now - last) / 1e3;
|
|
||||||
|
|
||||||
const threshold = 10 * 60;
|
|
||||||
|
|
||||||
// skip animation if already done less than 10min ago
|
|
||||||
if( diff_sec < threshold ){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const t1 = new TypeWriter( this.$refs['text1'] as HTMLElement )
|
|
||||||
const t2 = new TypeWriter( this.$refs['text2'] as HTMLElement )
|
|
||||||
const t3 = new TypeWriter( this.$refs['text3'] as HTMLElement )
|
|
||||||
const t4 = new TypeWriter( this.$refs['text4'] as HTMLElement )
|
|
||||||
|
|
||||||
t1.init(false);
|
|
||||||
t2.init();
|
|
||||||
t3.init();
|
|
||||||
t4.init();
|
|
||||||
|
|
||||||
// wait 1s, anim 1.5s
|
|
||||||
setTimeout( () => t1.animate(1500), 1000 );
|
|
||||||
// wait 3s, anim 2s
|
|
||||||
setTimeout( () => t2.animate(2000), 3000 );
|
|
||||||
// wait 6s, anim 4s
|
|
||||||
setTimeout( () => t3.animate(4000), 6000 );
|
|
||||||
// wait 10s, anim 3s
|
|
||||||
setTimeout( () => t4.animate(3000), 10000 );
|
|
||||||
|
|
||||||
// local storage: store that we animated once
|
|
||||||
setTimeout( () => {
|
|
||||||
localStorage.setItem('typed', `${new Date().getTime()}`);
|
|
||||||
}, 13000 );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected mounted() {
|
||||||
|
let last = 0;
|
||||||
|
|
||||||
|
// check if already animated in the last 10 min
|
||||||
|
const typed = localStorage.getItem('typed');
|
||||||
|
const parsed = parseInt(typed!) as number;
|
||||||
|
if ( !isNaN(parsed) ) {
|
||||||
|
last = parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date().getTime();
|
||||||
|
const diff_sec = (now - last) / 1e3;
|
||||||
|
|
||||||
|
const threshold = 10 * 60;
|
||||||
|
|
||||||
|
// skip animation if already done less than 10min ago
|
||||||
|
if ( diff_sec < threshold ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const t1 = new TypeWriter( this.$refs.text1 as HTMLElement );
|
||||||
|
const t2 = new TypeWriter( this.$refs.text2 as HTMLElement );
|
||||||
|
const t3 = new TypeWriter( this.$refs.text3 as HTMLElement );
|
||||||
|
const t4 = new TypeWriter( this.$refs.text4 as HTMLElement );
|
||||||
|
|
||||||
|
t1.init(false);
|
||||||
|
t2.init();
|
||||||
|
t3.init();
|
||||||
|
t4.init();
|
||||||
|
|
||||||
|
// wait 1s, anim 1.5s
|
||||||
|
setTimeout( () => t1.animate(1500), 1000 );
|
||||||
|
// wait 3s, anim 2s
|
||||||
|
setTimeout( () => t2.animate(2000), 3000 );
|
||||||
|
// wait 6s, anim 4s
|
||||||
|
setTimeout( () => t3.animate(4000), 6000 );
|
||||||
|
// wait 10s, anim 3s
|
||||||
|
setTimeout( () => t4.animate(3000), 10000 );
|
||||||
|
|
||||||
|
// local storage: store that we animated once
|
||||||
|
setTimeout( () => {
|
||||||
|
localStorage.setItem('typed', `${new Date().getTime()}`);
|
||||||
|
}, 13000 );
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
|
|
@ -8,65 +8,64 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
import { tID } from '@/model/skills';
|
import { tID } from '@/model/skills';
|
||||||
import * as skills from '@/service/skills';
|
import * as skills from '@/service/skills';
|
||||||
|
|
||||||
@Component({})
|
@Component({})
|
||||||
export default class SkillCard extends Vue {
|
export default class SkillCard extends Vue {
|
||||||
// id of the skill to display (string representation)
|
// id of the skill to display (string representation)
|
||||||
@Prop(Number) readonly id: tID|undefined;
|
@Prop(Number) public readonly id: tID|undefined;
|
||||||
|
|
||||||
// whether it has the active style
|
// whether it has the active style
|
||||||
@Prop(Boolean) active: boolean|undefined;
|
@Prop(Boolean) public active: boolean|undefined;
|
||||||
|
|
||||||
// folder name to display instead of a skill to fetch
|
// folder name to display instead of a skill to fetch
|
||||||
// not empty -> used as displayed folder name
|
// not empty -> used as displayed folder name
|
||||||
// empty -> this.id is used to fetch the skill
|
// empty -> this.id is used to fetch the skill
|
||||||
@Prop(String) folder: string|undefined;
|
@Prop(String) public folder: string|undefined;
|
||||||
|
|
||||||
protected mounted() {}
|
protected mounted() {}
|
||||||
|
|
||||||
protected icon(): string {
|
protected icon(): string {
|
||||||
const unknown = () => require('../assets/skills/unknown.svg');
|
const unknown = () => require('../assets/skills/unknown.svg');
|
||||||
|
|
||||||
if( this.folder != undefined ){
|
if ( this.folder != undefined ) {
|
||||||
return require('../assets/skills/folder.svg');
|
return require('../assets/skills/folder.svg');
|
||||||
}
|
|
||||||
|
|
||||||
if( this.id == undefined ){
|
|
||||||
return unknown();
|
|
||||||
}
|
|
||||||
const skill = skills.get(this.id);
|
|
||||||
if( skill == null ){
|
|
||||||
return unknown();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( skill.icon == null ){
|
|
||||||
return unknown();
|
|
||||||
}
|
|
||||||
return require(`../assets/${skill.icon}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected name(): string {
|
if ( this.id == undefined ) {
|
||||||
if( this.folder != undefined ){
|
return unknown();
|
||||||
return this.folder;
|
}
|
||||||
}
|
const skill = skills.get(this.id);
|
||||||
if( this.id == undefined ){
|
if ( skill == null ) {
|
||||||
return '?';
|
return unknown();
|
||||||
}
|
|
||||||
const skill = skills.get(this.id);
|
|
||||||
if( skill == null ){
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return skill.name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onClick(e: MouseEvent) {
|
if ( skill.icon == null ) {
|
||||||
this.$emit('pick', !this.active);
|
return unknown();
|
||||||
}
|
}
|
||||||
|
return require(`../assets/${skill.icon}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected name(): string {
|
||||||
|
if ( this.folder != undefined ) {
|
||||||
|
return this.folder;
|
||||||
|
}
|
||||||
|
if ( this.id == undefined ) {
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
const skill = skills.get(this.id);
|
||||||
|
if ( skill == null ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return skill.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onClick(e: MouseEvent) {
|
||||||
|
this.$emit('pick', !this.active);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
|
|
@ -47,139 +47,138 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from 'vue-property-decorator';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import SkillCard from './SkillCard.vue';
|
import SkillCard from './SkillCard.vue';
|
||||||
import { tID } from '@/model/skills';
|
import { tID } from '@/model/skills';
|
||||||
import { Project } from '@/model/projects';
|
import { Project } from '@/model/projects';
|
||||||
import * as skills from '@/service/skills';
|
import * as skills from '@/service/skills';
|
||||||
import * as projects from '@/service/projects';
|
import * as projects from '@/service/projects';
|
||||||
import { go } from '@/service/scroller';
|
import { go } from '@/service/scroller';
|
||||||
|
|
||||||
interface Details {
|
interface Details {
|
||||||
icon: string|null;
|
icon: string|null;
|
||||||
title: string;
|
title: string;
|
||||||
projects: Project[];
|
projects: Project[];
|
||||||
text: string;
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_TAG = 'all';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
SkillCard,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class SkillPicker extends Vue {
|
||||||
|
// list of available skills
|
||||||
|
public readonly ids: tID[] = skills.available();
|
||||||
|
// currently selected skill
|
||||||
|
private sel: tID|null = tID.Vue;
|
||||||
|
|
||||||
|
// list of ids to display according to the current tag
|
||||||
|
private filtered: tID[] = [];
|
||||||
|
|
||||||
|
// available categories (tags)
|
||||||
|
private tags: string[] = [DEFAULT_TAG, ...skills.tags()];
|
||||||
|
// currently selected tag
|
||||||
|
private tag: string = 'web';
|
||||||
|
|
||||||
|
// details section when a skill is selected
|
||||||
|
private details: Details|null = null;
|
||||||
|
|
||||||
|
// selects or deselects a skill. If the skill is not in the current
|
||||||
|
// folder, it navigates to the DEFAULT_TAG folder beforehand. Scrolls to
|
||||||
|
// the selected skill when selected.
|
||||||
|
public select(id: tID, deselect: boolean) {
|
||||||
|
const skill = skills.get(id);
|
||||||
|
if ( deselect || skill == null ) {
|
||||||
|
this.sel = null;
|
||||||
|
this.details = null;
|
||||||
|
this.$emit('pick', null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not available in current tag filter (folder)
|
||||||
|
// -> navigate to default tag
|
||||||
|
if ( skill.tags.indexOf(this.tag) < 0 ) {
|
||||||
|
this.onTag(DEFAULT_TAG, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sel = id;
|
||||||
|
this.loadDetails(id);
|
||||||
|
this.$emit('pick', id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_TAG = "all";
|
protected onPick(id: tID, picked: boolean) {
|
||||||
|
this.select(id, !picked);
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
protected onTag(t: string, picked: boolean) {
|
||||||
components: {
|
if ( picked ) { // select
|
||||||
SkillCard,
|
this.tag = t;
|
||||||
},
|
|
||||||
})
|
|
||||||
export default class SkillPicker extends Vue {
|
|
||||||
// list of available skills
|
|
||||||
readonly ids: tID[] = skills.available();
|
|
||||||
// currently selected skill
|
|
||||||
private sel: tID|null = tID.Vue;
|
|
||||||
|
|
||||||
// list of ids to display according to the current tag
|
|
||||||
private filtered: tID[] = [];
|
|
||||||
|
|
||||||
// available categories (tags)
|
|
||||||
private tags: string[] = [DEFAULT_TAG, ...skills.tags()];
|
|
||||||
// currently selected tag
|
|
||||||
private tag: string = "web";
|
|
||||||
|
|
||||||
// details section when a skill is selected
|
|
||||||
private details: Details|null = null;
|
|
||||||
|
|
||||||
private mounted() {
|
|
||||||
this.filterByTag();
|
this.filterByTag();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if ( !picked && t == this.tag ) { // back to default
|
||||||
private scroll(proj_name: string) {
|
this.tag = DEFAULT_TAG;
|
||||||
const head = document.querySelector(`#search-header`) as HTMLElement;
|
this.filterByTag();
|
||||||
if( head == null ){
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
go(`project-${proj_name}`, -1.2*head.offsetHeight)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// selects or deselects a skill. If the skill is not in the current
|
|
||||||
// folder, it navigates to the DEFAULT_TAG folder beforehand. Scrolls to
|
|
||||||
// the selected skill when selected.
|
|
||||||
public select(id: tID, deselect: boolean) {
|
|
||||||
const skill = skills.get(id);
|
|
||||||
if( deselect || skill == null ){
|
|
||||||
this.sel = null;
|
|
||||||
this.details = null;
|
|
||||||
this.$emit('pick', null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// not available in current tag filter (folder)
|
|
||||||
// -> navigate to default tag
|
|
||||||
if( skill.tags.indexOf(this.tag) < 0 ){
|
|
||||||
this.onTag(DEFAULT_TAG, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sel = id;
|
|
||||||
this.loadDetails(id);
|
|
||||||
this.$emit('pick', id);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onPick(id: tID, picked: boolean){
|
|
||||||
this.select(id, !picked);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onTag(t: string, picked: boolean){
|
|
||||||
if( picked ){ // select
|
|
||||||
this.tag = t;
|
|
||||||
this.filterByTag();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if( !picked && t == this.tag ){ // back to default
|
|
||||||
this.tag = DEFAULT_TAG;
|
|
||||||
this.filterByTag();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply filter by tag
|
|
||||||
private filterByTag(){
|
|
||||||
if( this.tag == DEFAULT_TAG ){
|
|
||||||
this.filtered = this.ids;
|
|
||||||
} else {
|
|
||||||
this.filtered = skills.filtered(this.tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
// maintain selection behavior:
|
|
||||||
// - deselect if current is no more present
|
|
||||||
// - keep if still listed
|
|
||||||
if( this.sel != null && this.filtered.indexOf(this.sel) < 0 ){
|
|
||||||
this.select(0, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loads details about a skill
|
|
||||||
private loadDetails(id: tID){
|
|
||||||
const skill = skills.get(id);
|
|
||||||
if( skill == null ){
|
|
||||||
this.details = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let icon: string|null = null;
|
|
||||||
if( skill.icon != null ){
|
|
||||||
icon = require(`../assets/${skill.icon}`);
|
|
||||||
}
|
|
||||||
this.details = {
|
|
||||||
icon: icon,
|
|
||||||
title: skill.name,
|
|
||||||
projects: projects.bySkill(id),
|
|
||||||
text: skill.info
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected browse(){
|
|
||||||
go(`search-header`, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected browse() {
|
||||||
|
go(`search-header`, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private mounted() {
|
||||||
|
this.filterByTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
private scroll(proj_name: string) {
|
||||||
|
const head = document.querySelector(`#search-header`) as HTMLElement;
|
||||||
|
if ( head == null ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
go(`project-${proj_name}`, -1.2 * head.offsetHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply filter by tag
|
||||||
|
private filterByTag() {
|
||||||
|
if ( this.tag == DEFAULT_TAG ) {
|
||||||
|
this.filtered = this.ids;
|
||||||
|
} else {
|
||||||
|
this.filtered = skills.filtered(this.tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// maintain selection behavior:
|
||||||
|
// - deselect if current is no more present
|
||||||
|
// - keep if still listed
|
||||||
|
if ( this.sel != null && this.filtered.indexOf(this.sel) < 0 ) {
|
||||||
|
this.select(0, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loads details about a skill
|
||||||
|
private loadDetails(id: tID) {
|
||||||
|
const skill = skills.get(id);
|
||||||
|
if ( skill == null ) {
|
||||||
|
this.details = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let icon: string|null = null;
|
||||||
|
if ( skill.icon != null ) {
|
||||||
|
icon = require(`../assets/${skill.icon}`);
|
||||||
|
}
|
||||||
|
this.details = {
|
||||||
|
icon,
|
||||||
|
title: skill.name,
|
||||||
|
projects: projects.bySkill(id),
|
||||||
|
text: skill.info,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
|
|
@ -82,110 +82,110 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from 'vue-property-decorator';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import SkillCard from './SkillCard.vue';
|
import SkillCard from './SkillCard.vue';
|
||||||
import { Project } from '../model/projects';
|
import { Project } from '../model/projects';
|
||||||
import { tID } from '../model/skills';
|
import { tID } from '../model/skills';
|
||||||
import * as projects from '../service/projects';
|
import * as projects from '../service/projects';
|
||||||
|
|
||||||
function pluralize(n: number, s: string): string {
|
function pluralize(n: number, s: string): string {
|
||||||
n = Math.floor(Math.abs(n));
|
n = Math.floor(Math.abs(n));
|
||||||
const plural = (n > 1);
|
const plural = (n > 1);
|
||||||
|
|
||||||
switch(s){
|
switch (s) {
|
||||||
case 'second':
|
case 'second':
|
||||||
return plural ? `${n} seconds` : `1 second`;
|
return plural ? `${n} seconds` : `1 second`;
|
||||||
case 'minute':
|
case 'minute':
|
||||||
return plural ? `${n} minutes` : `1 minute`;
|
return plural ? `${n} minutes` : `1 minute`;
|
||||||
case 'day':
|
case 'day':
|
||||||
return plural ? `${n} days` : `1 day`;
|
return plural ? `${n} days` : `1 day`;
|
||||||
case 'month':
|
case 'month':
|
||||||
return plural ? `${n} months` : `1 month`;
|
return plural ? `${n} months` : `1 month`;
|
||||||
case 'year':
|
case 'year':
|
||||||
return plural ? `${n} years` : `1 year`;
|
return plural ? `${n} years` : `1 year`;
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
SkillCard,
|
SkillCard,
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
short_date(date: Date): string {
|
||||||
|
return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
|
||||||
},
|
},
|
||||||
filters: {
|
date_diff(date: Date): string {
|
||||||
short_date: function(date: Date): string {
|
const minute = 60 * 1000;
|
||||||
return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
|
const hour = 60 * minute;
|
||||||
},
|
const day = 24 * hour;
|
||||||
date_diff: function(date: Date): string {
|
const month = 30 * day;
|
||||||
const minute = 60*1000;
|
const year = 365 * day;
|
||||||
const hour = 60*minute;
|
|
||||||
const day = 24*hour;
|
|
||||||
const month = 30*day;
|
|
||||||
const year = 365*day;
|
|
||||||
|
|
||||||
let now = new Date();
|
const now = new Date();
|
||||||
let diff = now.getTime() - date.getTime();
|
const diff = now.getTime() - date.getTime();
|
||||||
|
|
||||||
if( diff < 0 ){
|
if ( diff < 0 ) {
|
||||||
return "sometime";
|
return 'sometime';
|
||||||
}
|
}
|
||||||
|
|
||||||
if( diff < minute ){
|
if ( diff < minute ) {
|
||||||
return pluralize(diff, 'second');
|
return pluralize(diff, 'second');
|
||||||
}
|
}
|
||||||
if( diff < hour ){
|
if ( diff < hour ) {
|
||||||
return pluralize(diff/minute, 'minute');
|
return pluralize(diff / minute, 'minute');
|
||||||
}
|
}
|
||||||
if( diff < day ){
|
if ( diff < day ) {
|
||||||
return pluralize(diff/hour, 'hour');
|
return pluralize(diff / hour, 'hour');
|
||||||
}
|
}
|
||||||
if( diff < month ){
|
if ( diff < month ) {
|
||||||
return pluralize(diff/day, 'day');
|
return pluralize(diff / day, 'day');
|
||||||
}
|
}
|
||||||
if( diff < year ){
|
if ( diff < year ) {
|
||||||
return pluralize(diff/month, 'month');
|
return pluralize(diff / month, 'month');
|
||||||
}
|
}
|
||||||
return pluralize(diff/year, 'year');
|
return pluralize(diff / year, 'year');
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class Timeline extends Vue {
|
||||||
|
private skill: tID|null = null;
|
||||||
|
private projects: Project[] = [];
|
||||||
|
|
||||||
|
public filter(skill: tID|null) {
|
||||||
|
this.skill = skill;
|
||||||
|
if ( skill == null ) {
|
||||||
|
this.projects = [];
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
})
|
this.projects = projects.bySkill(skill);
|
||||||
export default class Timeline extends Vue {
|
}
|
||||||
private skill: tID|null = null;
|
|
||||||
private projects: Project[] = [];
|
|
||||||
|
|
||||||
private mounted() {
|
private mounted() {
|
||||||
document.body.addEventListener('scroll', this.onScroll, { passive: true });
|
document.body.addEventListener('scroll', this.onScroll, { passive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
private onScroll(e: Event) {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
const header = this.$refs.header as HTMLElement;
|
||||||
|
if ( target == undefined || header == undefined ) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onScroll(e: Event) {
|
const el = this.$el as HTMLElement;
|
||||||
const target = e.target as HTMLElement;
|
if ( el == null ) {
|
||||||
const header = this.$refs.header as HTMLElement;
|
return;
|
||||||
if( target == undefined || header == undefined ){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const el = this.$el as HTMLElement;
|
|
||||||
if( el == null ){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// when scrolling after this limit, make the header fixed
|
|
||||||
const limit = el.offsetTop;
|
|
||||||
|
|
||||||
if( target.scrollTop >= limit ){
|
|
||||||
header.classList.add('fixed');
|
|
||||||
} else {
|
|
||||||
header.classList.remove('fixed');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// when scrolling after this limit, make the header fixed
|
||||||
|
const limit = el.offsetTop;
|
||||||
|
|
||||||
public filter(skill: tID|null) {
|
if ( target.scrollTop >= limit ) {
|
||||||
this.skill = skill;
|
header.classList.add('fixed');
|
||||||
if( skill == null ){
|
} else {
|
||||||
this.projects = [];
|
header.classList.remove('fixed');
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.projects = projects.bySkill(skill);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
|
|
@ -56,7 +56,7 @@ export const Projects: Project[] = [
|
||||||
stopped_at: new Date(2022, 11, 1),
|
stopped_at: new Date(2022, 11, 1),
|
||||||
info: 'TODO',
|
info: 'TODO',
|
||||||
source: null, doc: null,
|
source: null, doc: null,
|
||||||
commits: 1260+90 + 41+1 + 12+1 + 14,
|
commits: 1260 + 90 + 41 + 1 + 12 + 1 + 14,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -62,21 +62,21 @@ export const Skills: tSkills = {
|
||||||
link: 'https://mariadb.org',
|
link: 'https://mariadb.org',
|
||||||
icon: 'skills/mariadb.svg',
|
icon: 'skills/mariadb.svg',
|
||||||
tags: ['web', 'storage'],
|
tags: ['web', 'storage'],
|
||||||
info: "I started using mysql then MariaDB as my first introduction to SQL, mostly when I started learning web with PHP. I now prefer using postgreSQL instead as it tends to be more robust and performant overall.",
|
info: 'I started using mysql then MariaDB as my first introduction to SQL, mostly when I started learning web with PHP. I now prefer using postgreSQL instead as it tends to be more robust and performant overall.',
|
||||||
},
|
},
|
||||||
[tID.Postgres]: {
|
[tID.Postgres]: {
|
||||||
name: 'PostgreSQL',
|
name: 'PostgreSQL',
|
||||||
link: 'https://postgresql.org',
|
link: 'https://postgresql.org',
|
||||||
icon: 'skills/postgres.svg',
|
icon: 'skills/postgres.svg',
|
||||||
tags: ['web', 'storage'],
|
tags: ['web', 'storage'],
|
||||||
info: "My main choice of dbms when using sql orfor relational databases. Had some experience with it mainly with go services with docker.",
|
info: 'My main choice of dbms when using sql orfor relational databases. Had some experience with it mainly with go services with docker.',
|
||||||
},
|
},
|
||||||
[tID.Mongo]: {
|
[tID.Mongo]: {
|
||||||
name: 'MongoDB',
|
name: 'MongoDB',
|
||||||
link: 'https://mongodb.com',
|
link: 'https://mongodb.com',
|
||||||
icon: 'skills/mongo.svg',
|
icon: 'skills/mongo.svg',
|
||||||
tags: ['web', 'storage'],
|
tags: ['web', 'storage'],
|
||||||
info: "I learned it during my Master's degree. I used advanced Mongo with schema validation and complex filter requests. I find this technology really interesting over SQL, but I find classical relational databases are easier to comprehend and I am more used to it.",
|
info: 'I learned it during my Master\'s degree. I used advanced Mongo with schema validation and complex filter requests. I find this technology really interesting over SQL, but I find classical relational databases are easier to comprehend and I am more used to it.',
|
||||||
},
|
},
|
||||||
|
|
||||||
[tID.Vue]: {
|
[tID.Vue]: {
|
||||||
|
@ -84,35 +84,35 @@ export const Skills: tSkills = {
|
||||||
link: 'https://vuejs.org',
|
link: 'https://vuejs.org',
|
||||||
icon: 'skills/vue.svg',
|
icon: 'skills/vue.svg',
|
||||||
tags: ['web', 'UI'],
|
tags: ['web', 'UI'],
|
||||||
info: "I started learning Vue (.js) back in 2016, and never stopped practicing since then for personal and professional projects. I view it as a better alternative than angular which provides you with a strict framework that can lack flexibility among teams and projects.<br><br>Vue makes it your responsability to properly structure your project which I like to take care of myself, as it tends to provide a better workflow adjusted for every project.<br><br>It is my top choice when considering a web framework for rendering pages.",
|
info: 'I started learning Vue (.js) back in 2016, and never stopped practicing since then for personal and professional projects. I view it as a better alternative than angular which provides you with a strict framework that can lack flexibility among teams and projects.<br><br>Vue makes it your responsability to properly structure your project which I like to take care of myself, as it tends to provide a better workflow adjusted for every project.<br><br>It is my top choice when considering a web framework for rendering pages.',
|
||||||
},
|
},
|
||||||
[tID.Angular]: {
|
[tID.Angular]: {
|
||||||
name: 'Angular <i>(7+)</i>',
|
name: 'Angular <i>(7+)</i>',
|
||||||
link: 'https://angular.io',
|
link: 'https://angular.io',
|
||||||
icon: 'skills/angular.svg',
|
icon: 'skills/angular.svg',
|
||||||
tags: ['web', 'UI'],
|
tags: ['web', 'UI'],
|
||||||
info: "I used it only once to break it apart in a R&D project. I had to \"hack\" it in order to inject services and components on-the-fly.<br><br>You had a kind of App store where you could install modules. The Angular-powered website would inject new navigation menus, pages and services according to the modules you had installed.<br><br>I do not like Angular, as I think Vue.js does a better job achieving the same goal with a cleaner API, and does not force you to embrace the Angular/Google way of coding and organizing things."
|
info: 'I used it only once to break it apart in a R&D project. I had to "hack" it in order to inject services and components on-the-fly.<br><br>You had a kind of App store where you could install modules. The Angular-powered website would inject new navigation menus, pages and services according to the modules you had installed.<br><br>I do not like Angular, as I think Vue.js does a better job achieving the same goal with a cleaner API, and does not force you to embrace the Angular/Google way of coding and organizing things.',
|
||||||
},
|
},
|
||||||
[tID.Parcel]: {
|
[tID.Parcel]: {
|
||||||
name: 'Parcel',
|
name: 'Parcel',
|
||||||
link: 'https://parceljs.org/',
|
link: 'https://parceljs.org/',
|
||||||
icon: 'skills/parcel.svg',
|
icon: 'skills/parcel.svg',
|
||||||
tags: ['web'],
|
tags: ['web'],
|
||||||
info: "I used it often back in my Master's Degree period along with PHP backends. It allows to get away from webpack which I find overly complicated and a major point of failure for most frontends.<br><br>I haven't used it in a while but would highly consider it as it has no real prerequisite.",
|
info: 'I used it often back in my Master\'s Degree period along with PHP backends. It allows to get away from webpack which I find overly complicated and a major point of failure for most frontends.<br><br>I haven\'t used it in a while but would highly consider it as it has no real prerequisite.',
|
||||||
},
|
},
|
||||||
[tID.Cordova]: {
|
[tID.Cordova]: {
|
||||||
name: 'Apache Cordova',
|
name: 'Apache Cordova',
|
||||||
link: 'https://cordova.apache.org/',
|
link: 'https://cordova.apache.org/',
|
||||||
icon: 'skills/cordova.svg',
|
icon: 'skills/cordova.svg',
|
||||||
tags: ['system', 'web', 'mobile'],
|
tags: ['system', 'web', 'mobile'],
|
||||||
info: "I used it often to transform single-page applications into android, iOS or desktop apps at low cost. I haven't needed this kind of software for a while, it might now be favorable to use Capacitor or other \"alternatives\".",
|
info: 'I used it often to transform single-page applications into android, iOS or desktop apps at low cost. I haven\'t needed this kind of software for a while, it might now be favorable to use Capacitor or other "alternatives".',
|
||||||
},
|
},
|
||||||
[tID.Webpack]: {
|
[tID.Webpack]: {
|
||||||
name: 'Webpack',
|
name: 'Webpack',
|
||||||
link: 'https://webpack.js.org/',
|
link: 'https://webpack.js.org/',
|
||||||
icon: 'skills/webpack.svg',
|
icon: 'skills/webpack.svg',
|
||||||
tags: ['web'],
|
tags: ['web'],
|
||||||
info: "... I used it a lot because it is often required. But I hate that it is so complex that most users use it without understanding what is really does. I avoid it whenever possible, the issue can be transposed to node_modules and the whole node ecosystem.",
|
info: '... I used it a lot because it is often required. But I hate that it is so complex that most users use it without understanding what is really does. I avoid it whenever possible, the issue can be transposed to node_modules and the whole node ecosystem.',
|
||||||
},
|
},
|
||||||
|
|
||||||
[tID.WebGL]: {
|
[tID.WebGL]: {
|
||||||
|
@ -120,21 +120,21 @@ export const Skills: tSkills = {
|
||||||
link: 'https://www.khronos.org/webgl/',
|
link: 'https://www.khronos.org/webgl/',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['web'],
|
tags: ['web'],
|
||||||
info: "I used it during my graduation years, the potential is great but I need to find a time to explore what's new.",
|
info: 'I used it during my graduation years, the potential is great but I need to find a time to explore what\'s new.',
|
||||||
},
|
},
|
||||||
[tID.AudioAPI]: {
|
[tID.AudioAPI]: {
|
||||||
name: 'Audio API',
|
name: 'Audio API',
|
||||||
link: 'https://webaudio.github.io/web-audio-api/',
|
link: 'https://webaudio.github.io/web-audio-api/',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['web'],
|
tags: ['web'],
|
||||||
info: "I used it mainly to build a discord clone (audio and video full-duplex streaming) as a school project. I also tried the Fourier transform to build nice graphics (c.f. Soundcloud) but have not succeeded at the time.",
|
info: 'I used it mainly to build a discord clone (audio and video full-duplex streaming) as a school project. I also tried the Fourier transform to build nice graphics (c.f. Soundcloud) but have not succeeded at the time.',
|
||||||
},
|
},
|
||||||
[tID.Websocket]: {
|
[tID.Websocket]: {
|
||||||
name: 'Websocket',
|
name: 'Websocket',
|
||||||
link: 'https://tools.ietf.org/html/rfc6455',
|
link: 'https://tools.ietf.org/html/rfc6455',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['web', 'networking', 'IoT'],
|
tags: ['web', 'networking', 'IoT'],
|
||||||
info: "I use them whenever possible to avoid the classical expensive polling.<br><br>I also created my own websocket client & server implementation in Go following nothing but the RFC for learning purposes.<br><br>I've used, designed and implemented websocket communications among personal and professional projects.",
|
info: 'I use them whenever possible to avoid the classical expensive polling.<br><br>I also created my own websocket client & server implementation in Go following nothing but the RFC for learning purposes.<br><br>I\'ve used, designed and implemented websocket communications among personal and professional projects.',
|
||||||
},
|
},
|
||||||
|
|
||||||
[tID.Docker]: {
|
[tID.Docker]: {
|
||||||
|
@ -142,42 +142,42 @@ export const Skills: tSkills = {
|
||||||
link: 'https://docker.com',
|
link: 'https://docker.com',
|
||||||
icon: 'skills/docker.svg',
|
icon: 'skills/docker.svg',
|
||||||
tags: ['web', 'system'],
|
tags: ['web', 'system'],
|
||||||
info: "I am using it for a while now. Most of my go projects use it as it costs no memory for your executable (multi-stage build from scratch) and allows for isolation and a better control over the running environment. I am learning docker a bit more every time I use it. It is often coupled with docker-compose in my projects.",
|
info: 'I am using it for a while now. Most of my go projects use it as it costs no memory for your executable (multi-stage build from scratch) and allows for isolation and a better control over the running environment. I am learning docker a bit more every time I use it. It is often coupled with docker-compose in my projects.',
|
||||||
},
|
},
|
||||||
[tID.Bash]: {
|
[tID.Bash]: {
|
||||||
name: 'bash',
|
name: 'bash',
|
||||||
link: 'https://www.gnu.org/software/bash/',
|
link: 'https://www.gnu.org/software/bash/',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['system'],
|
tags: ['system'],
|
||||||
info: "Felt in love with GNU/linux and its ecosystem a while back (was 14yo back then) and I've never stopped. I'm often the linux/bash guy of the team.<br><br>I use it extensively to automate anything a human can do as it avoids a lot of mistakes in most workflows.",
|
info: 'Felt in love with GNU/linux and its ecosystem a while back (was 14yo back then) and I\'ve never stopped. I\'m often the linux/bash guy of the team.<br><br>I use it extensively to automate anything a human can do as it avoids a lot of mistakes in most workflows.',
|
||||||
},
|
},
|
||||||
[tID.Linux]: {
|
[tID.Linux]: {
|
||||||
name: 'GNU/Linux',
|
name: 'GNU/Linux',
|
||||||
link: 'https://www.linux.org',
|
link: 'https://www.linux.org',
|
||||||
icon: 'skills/linux.svg',
|
icon: 'skills/linux.svg',
|
||||||
tags: ['system'],
|
tags: ['system'],
|
||||||
info: "Felt in love with GNU/linux and its ecosystem a while back (was 14yo back then) and I've never stopped. <br><br>Linux has been my main OS choice since then. I stepped through Ubuntu, Debian, Manjaro, Elementary, Tails (live), and I am now on Solus for a few years.",
|
info: 'Felt in love with GNU/linux and its ecosystem a while back (was 14yo back then) and I\'ve never stopped. <br><br>Linux has been my main OS choice since then. I stepped through Ubuntu, Debian, Manjaro, Elementary, Tails (live), and I am now on Solus for a few years.',
|
||||||
},
|
},
|
||||||
[tID.Systemd]: {
|
[tID.Systemd]: {
|
||||||
name: 'systemd',
|
name: 'systemd',
|
||||||
link: 'https://freedesktop.org/wiki/Software/systemd/',
|
link: 'https://freedesktop.org/wiki/Software/systemd/',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['system'],
|
tags: ['system'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Git]: {
|
[tID.Git]: {
|
||||||
name: 'Git',
|
name: 'Git',
|
||||||
link: 'https://git-scm.com/',
|
link: 'https://git-scm.com/',
|
||||||
icon: 'skills/git.svg',
|
icon: 'skills/git.svg',
|
||||||
tags: ['system', 'organization'],
|
tags: ['system', 'organization'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Rpm]: {
|
[tID.Rpm]: {
|
||||||
name: 'RPM packaging',
|
name: 'RPM packaging',
|
||||||
link: 'https://rpm.org/',
|
link: 'https://rpm.org/',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['system'],
|
tags: ['system'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
[tID.RaspBerry]: {
|
[tID.RaspBerry]: {
|
||||||
|
@ -185,14 +185,14 @@ export const Skills: tSkills = {
|
||||||
link: 'https://raspberrypi.org',
|
link: 'https://raspberrypi.org',
|
||||||
icon: 'skills/raspberry.svg',
|
icon: 'skills/raspberry.svg',
|
||||||
tags: ['system', 'IoT'],
|
tags: ['system', 'IoT'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Arduino]: {
|
[tID.Arduino]: {
|
||||||
name: 'Arduino',
|
name: 'Arduino',
|
||||||
link: 'https://arduino.cc',
|
link: 'https://arduino.cc',
|
||||||
icon: 'skills/arduino.svg',
|
icon: 'skills/arduino.svg',
|
||||||
tags: ['system', 'IoT'],
|
tags: ['system', 'IoT'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
[tID.Php]: {
|
[tID.Php]: {
|
||||||
|
@ -200,70 +200,70 @@ export const Skills: tSkills = {
|
||||||
link: 'https://www.php.net',
|
link: 'https://www.php.net',
|
||||||
icon: 'skills/php.svg',
|
icon: 'skills/php.svg',
|
||||||
tags: ['language', 'web', 'IoT'],
|
tags: ['language', 'web', 'IoT'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Html]: {
|
[tID.Html]: {
|
||||||
name: 'HTML5',
|
name: 'HTML5',
|
||||||
link: 'https://www.w3.org/standards/webdesign/htmlcss',
|
link: 'https://www.w3.org/standards/webdesign/htmlcss',
|
||||||
icon: 'skills/html.svg',
|
icon: 'skills/html.svg',
|
||||||
tags: ['language', 'web', 'UI'],
|
tags: ['language', 'web', 'UI'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Css]: {
|
[tID.Css]: {
|
||||||
name: 'CSS3',
|
name: 'CSS3',
|
||||||
link: 'https://www.w3.org/standards/webdesign/htmlcss',
|
link: 'https://www.w3.org/standards/webdesign/htmlcss',
|
||||||
icon: 'skills/css.svg',
|
icon: 'skills/css.svg',
|
||||||
tags: ['language', 'web', 'UI'],
|
tags: ['language', 'web', 'UI'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Js]: {
|
[tID.Js]: {
|
||||||
name: 'Javascript',
|
name: 'Javascript',
|
||||||
link: 'http://www.ecma-international.org/publications-and-standards/standards/ecma-262/',
|
link: 'http://www.ecma-international.org/publications-and-standards/standards/ecma-262/',
|
||||||
icon: 'skills/js.svg',
|
icon: 'skills/js.svg',
|
||||||
tags: ['language', 'web', 'UI'],
|
tags: ['language', 'web', 'UI'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Ajax]: {
|
[tID.Ajax]: {
|
||||||
name: 'AJAX',
|
name: 'AJAX',
|
||||||
link: 'https://www.w3schools.com/xml/ajax_intro.asp',
|
link: 'https://www.w3schools.com/xml/ajax_intro.asp',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['web', 'networking'],
|
tags: ['web', 'networking'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Ts]: {
|
[tID.Ts]: {
|
||||||
name: 'Typescript',
|
name: 'Typescript',
|
||||||
link: 'https://www.typescript.org/',
|
link: 'https://www.typescript.org/',
|
||||||
icon: 'skills/ts.svg',
|
icon: 'skills/ts.svg',
|
||||||
tags: ['language', 'web', 'UI'],
|
tags: ['language', 'web', 'UI'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.C]: {
|
[tID.C]: {
|
||||||
name: 'C (lang)',
|
name: 'C (lang)',
|
||||||
link: 'https://www.open-std.org/jtc1/sc22/wg14/',
|
link: 'https://www.open-std.org/jtc1/sc22/wg14/',
|
||||||
icon: 'skills/c.svg',
|
icon: 'skills/c.svg',
|
||||||
tags: ['language', 'system'],
|
tags: ['language', 'system'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Cpp]: {
|
[tID.Cpp]: {
|
||||||
name: 'C++',
|
name: 'C++',
|
||||||
link: 'https://isocpp.org/',
|
link: 'https://isocpp.org/',
|
||||||
icon: 'skills/cpp.svg',
|
icon: 'skills/cpp.svg',
|
||||||
tags: ['language', 'system'],
|
tags: ['language', 'system'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Python]: {
|
[tID.Python]: {
|
||||||
name: 'Python',
|
name: 'Python',
|
||||||
link: 'https://python.org/',
|
link: 'https://python.org/',
|
||||||
icon: 'skills/python.svg',
|
icon: 'skills/python.svg',
|
||||||
tags: ['language', 'system'],
|
tags: ['language', 'system'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Go]: {
|
[tID.Go]: {
|
||||||
name: 'Go (lang)',
|
name: 'Go (lang)',
|
||||||
link: 'https://go.dev',
|
link: 'https://go.dev',
|
||||||
icon: 'skills/go.svg',
|
icon: 'skills/go.svg',
|
||||||
tags: ['language', 'system', 'IoT', 'networking'],
|
tags: ['language', 'system', 'IoT', 'networking'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
[tID.Qt]: {
|
[tID.Qt]: {
|
||||||
|
@ -271,7 +271,7 @@ export const Skills: tSkills = {
|
||||||
link: 'https://qt.io',
|
link: 'https://qt.io',
|
||||||
icon: 'skills/qt.svg',
|
icon: 'skills/qt.svg',
|
||||||
tags: ['language', 'system', 'IoT', 'networking'],
|
tags: ['language', 'system', 'IoT', 'networking'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
[tID.OpenSource]: {
|
[tID.OpenSource]: {
|
||||||
|
@ -279,104 +279,104 @@ export const Skills: tSkills = {
|
||||||
link: 'https://opensource.org/',
|
link: 'https://opensource.org/',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['human'],
|
tags: ['human'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Electronics]: {
|
[tID.Electronics]: {
|
||||||
name: 'Electronics',
|
name: 'Electronics',
|
||||||
link: 'https://en.wikipedia.org/wiki/Electronics',
|
link: 'https://en.wikipedia.org/wiki/Electronics',
|
||||||
icon: 'skills/electronics.svg',
|
icon: 'skills/electronics.svg',
|
||||||
tags: ['other', 'IoT'],
|
tags: ['other', 'IoT'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Web]: {
|
[tID.Web]: {
|
||||||
name: 'Web',
|
name: 'Web',
|
||||||
link: 'https://en.wikipedia.org/wiki/World_Wide_Web',
|
link: 'https://en.wikipedia.org/wiki/World_Wide_Web',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['web'],
|
tags: ['web'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Rest]: {
|
[tID.Rest]: {
|
||||||
name: 'REST',
|
name: 'REST',
|
||||||
link: 'https://en.wikipedia.org/wiki/Representational_state_transfer',
|
link: 'https://en.wikipedia.org/wiki/Representational_state_transfer',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['web', 'networking'],
|
tags: ['web', 'networking'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Crypto]: {
|
[tID.Crypto]: {
|
||||||
name: 'Security/crypto',
|
name: 'Security/crypto',
|
||||||
link: 'https://en.wikipedia.org/wiki/Cryptography',
|
link: 'https://en.wikipedia.org/wiki/Cryptography',
|
||||||
icon: 'skills/security.svg',
|
icon: 'skills/security.svg',
|
||||||
tags: ['system', 'networking'],
|
tags: ['system', 'networking'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.ImageProcessing]: {
|
[tID.ImageProcessing]: {
|
||||||
name: 'Image processing',
|
name: 'Image processing',
|
||||||
link: 'https://en.wikipedia.org/wiki/Digital_image_processing',
|
link: 'https://en.wikipedia.org/wiki/Digital_image_processing',
|
||||||
icon: 'skills/image-processing.svg',
|
icon: 'skills/image-processing.svg',
|
||||||
tags: ['system'],
|
tags: ['system'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.AI]: {
|
[tID.AI]: {
|
||||||
name: 'Artificial Intelligence',
|
name: 'Artificial Intelligence',
|
||||||
link: 'https://en.wikipedia.org/wiki/Artificial_intelligence',
|
link: 'https://en.wikipedia.org/wiki/Artificial_intelligence',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['other'],
|
tags: ['other'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.DeepLearning]: {
|
[tID.DeepLearning]: {
|
||||||
name: 'Deep Learning',
|
name: 'Deep Learning',
|
||||||
link: 'https://en.wikipedia.org/wiki/Deep_learning',
|
link: 'https://en.wikipedia.org/wiki/Deep_learning',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['other'],
|
tags: ['other'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.NeuralNetwork]: {
|
[tID.NeuralNetwork]: {
|
||||||
name: 'Neural Networks',
|
name: 'Neural Networks',
|
||||||
link: 'https://en.wikipedia.org/wiki/Artificial_neural_network',
|
link: 'https://en.wikipedia.org/wiki/Artificial_neural_network',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['other'],
|
tags: ['other'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Opti]: {
|
[tID.Opti]: {
|
||||||
name: 'Program optimization',
|
name: 'Program optimization',
|
||||||
link: 'https://en.wikipedia.org/wiki/Program_optimization',
|
link: 'https://en.wikipedia.org/wiki/Program_optimization',
|
||||||
icon: 'skills/opti.svg',
|
icon: 'skills/opti.svg',
|
||||||
tags: ['system', 'networking'],
|
tags: ['system', 'networking'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Sockets]: {
|
[tID.Sockets]: {
|
||||||
name: 'Sockets',
|
name: 'Sockets',
|
||||||
link: 'https://en.wikipedia.org/wiki/Computer_network_programming',
|
link: 'https://en.wikipedia.org/wiki/Computer_network_programming',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['system', 'networking', 'IoT', 'web'],
|
tags: ['system', 'networking', 'IoT', 'web'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Concurrency]: {
|
[tID.Concurrency]: {
|
||||||
name: 'Concurrency',
|
name: 'Concurrency',
|
||||||
link: 'https://en.wikipedia.org/wiki/Concurrent_computing',
|
link: 'https://en.wikipedia.org/wiki/Concurrent_computing',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['system', 'networking'],
|
tags: ['system', 'networking'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.UIUX]: {
|
[tID.UIUX]: {
|
||||||
name: 'UI/UX',
|
name: 'UI/UX',
|
||||||
link: 'https://en.wikipedia.org/wiki/UX',
|
link: 'https://en.wikipedia.org/wiki/UX',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['system', 'web', 'IoT'],
|
tags: ['system', 'web', 'IoT'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.Inkscape]: {
|
[tID.Inkscape]: {
|
||||||
name: 'Inkscape',
|
name: 'Inkscape',
|
||||||
link: 'https://inkscape.org/',
|
link: 'https://inkscape.org/',
|
||||||
icon: 'skills/inkscape.svg',
|
icon: 'skills/inkscape.svg',
|
||||||
tags: ['UI', 'web', 'organization'],
|
tags: ['UI', 'web', 'organization'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
[tID.RnD]: {
|
[tID.RnD]: {
|
||||||
name: 'R&D',
|
name: 'R&D',
|
||||||
link: 'https://en.wikipedia.org/wiki/R&D',
|
link: 'https://en.wikipedia.org/wiki/R&D',
|
||||||
icon: null,
|
icon: null,
|
||||||
tags: ['organization'],
|
tags: ['organization'],
|
||||||
info: "",
|
info: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,8 +6,8 @@ import { tID } from '@/model/skills';
|
||||||
export function bySkill(skill: tID): Project[] {
|
export function bySkill(skill: tID): Project[] {
|
||||||
const filtered: Project[] = [];
|
const filtered: Project[] = [];
|
||||||
|
|
||||||
for( const proj of Projects ){
|
for ( const proj of Projects ) {
|
||||||
if( proj.skills.indexOf(skill) >= 0 ){
|
if ( proj.skills.indexOf(skill) >= 0 ) {
|
||||||
filtered.push(proj);
|
filtered.push(proj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,14 +10,14 @@ const SCROLL_MS_STEP = 5;
|
||||||
export function go(id: string, offset: number|undefined): void {
|
export function go(id: string, offset: number|undefined): void {
|
||||||
// find item with id
|
// find item with id
|
||||||
const item = document.querySelector(`#${id}`) as HTMLElement;
|
const item = document.querySelector(`#${id}`) as HTMLElement;
|
||||||
if( item == null ){
|
if ( item == null ) {
|
||||||
console.warn(`item with id '${id}' not found`);
|
console.warn(`item with id '${id}' not found`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get item absolute y coordinates
|
// get item absolute y coordinates
|
||||||
let targety = absY(item, 0);
|
let targety = absY(item, 0);
|
||||||
if( offset != null ){
|
if ( offset != null ) {
|
||||||
targety += offset;
|
targety += offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,13 +25,13 @@ export function go(id: string, offset: number|undefined): void {
|
||||||
const step = (targety - document.body.scrollTop) / (SCROLL_MS / SCROLL_MS_STEP);
|
const step = (targety - document.body.scrollTop) / (SCROLL_MS / SCROLL_MS_STEP);
|
||||||
scroll_interval = setInterval(
|
scroll_interval = setInterval(
|
||||||
() => smooth_scroll(targety, step),
|
() => smooth_scroll(targety, step),
|
||||||
SCROLL_MS_STEP
|
SCROLL_MS_STEP,
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function absY(item: HTMLElement|null, children_y: number): number {
|
function absY(item: HTMLElement|null, children_y: number): number {
|
||||||
if( item == null || item == document.body ){
|
if ( item == null || item == document.body ) {
|
||||||
return children_y;
|
return children_y;
|
||||||
}
|
}
|
||||||
return absY(item.parentElement, children_y + item.offsetTop);
|
return absY(item.parentElement, children_y + item.offsetTop);
|
||||||
|
@ -47,10 +47,10 @@ function smooth_scroll(target: number, step: number) {
|
||||||
// moving down & reached bottom or target -> stop
|
// moving down & reached bottom or target -> stop
|
||||||
const downLimit = ( step > 0 && (cur >= max || cur >= target) );
|
const downLimit = ( step > 0 && (cur >= max || cur >= target) );
|
||||||
|
|
||||||
if( upLimit || downLimit ){
|
if ( upLimit || downLimit ) {
|
||||||
clearInterval(scroll_interval);
|
clearInterval(scroll_interval);
|
||||||
document.body.scroll(0, target);
|
document.body.scroll(0, target);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.scroll(0, cur + step);
|
document.body.scroll(0, cur + step);
|
||||||
|
|
|
@ -1,67 +1,67 @@
|
||||||
import { tID, Skills, tSkill } from '@/model/skills';
|
import { tID, Skills, tSkill } from '@/model/skills';
|
||||||
|
|
||||||
let available_cache: tID[]|null = null;
|
let availCache: tID[]|null = null;
|
||||||
let tags_cache: string[]|null = null;
|
let tagsCache: string[]|null = null;
|
||||||
|
|
||||||
// returns available skill ids.
|
// returns available skill ids.
|
||||||
export function available(): tID[] {
|
export function available(): tID[] {
|
||||||
if( available_cache != null ){
|
if ( availCache != null ) {
|
||||||
return available_cache;
|
return availCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
available_cache = [];
|
availCache = [];
|
||||||
for( const str_id in Skills ){
|
for ( const sid in Skills ) {
|
||||||
const id = str2ID(str_id)
|
const id = str2ID(sid);
|
||||||
if( id == null ){
|
if ( id == null ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
available_cache.push(id);
|
availCache.push(id);
|
||||||
}
|
}
|
||||||
return available_cache;
|
return availCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the list of ids filtered by a tag. Skills NOT featuring the provided
|
// returns the list of ids filtered by a tag. Skills NOT featuring the provided
|
||||||
// tag are filtered out of the list.
|
// tag are filtered out of the list.
|
||||||
export function filtered(tag: string): tID[] {
|
export function filtered(tag: string): tID[] {
|
||||||
if( available_cache == null ){
|
if ( availCache == null ) {
|
||||||
available();
|
available();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filtered = [];
|
const remain = [];
|
||||||
for( const id of available_cache! ){
|
for ( const id of availCache! ) {
|
||||||
const skill = get(id);
|
const skill = get(id);
|
||||||
if( skill == null ){
|
if ( skill == null ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if( skill.tags.indexOf(tag) >= 0 ){
|
if ( skill.tags.indexOf(tag) >= 0 ) {
|
||||||
filtered.push(id);
|
remain.push(id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filtered;
|
return remain;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns available tags used among skills
|
// returns available tags used among skills
|
||||||
export function tags(): string[] {
|
export function tags(): string[] {
|
||||||
if( tags_cache != null ){
|
if ( tagsCache != null ) {
|
||||||
return tags_cache;
|
return tagsCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
tags_cache = [];
|
tagsCache = [];
|
||||||
for( const skill of Object.values(Skills) ){
|
for ( const skill of Object.values(Skills) ) {
|
||||||
for( const tag of skill.tags ){
|
for ( const tag of skill.tags ) {
|
||||||
if( tags_cache.indexOf(tag) < 0 ){
|
if ( tagsCache.indexOf(tag) < 0 ) {
|
||||||
tags_cache.push(tag);
|
tagsCache.push(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tags_cache;
|
return tagsCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a skill from its id
|
// returns a skill from its id
|
||||||
export function get(id: tID): tSkill|null {
|
export function get(id: tID): tSkill|null {
|
||||||
if( Skills[id] == undefined ){
|
if ( Skills[id] === undefined ) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return Skills[id];
|
return Skills[id];
|
||||||
|
@ -69,8 +69,8 @@ export function get(id: tID): tSkill|null {
|
||||||
|
|
||||||
// converts a string to a skill id
|
// converts a string to a skill id
|
||||||
export function str2ID(str: string): tID|null {
|
export function str2ID(str: string): tID|null {
|
||||||
const id = parseInt(str) as tID;
|
const id = parseInt(str, 10) as tID;
|
||||||
if( Skills[id] == undefined ){
|
if ( Skills[id] === undefined ) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
export class TypeWriter {
|
export class TypeWriter {
|
||||||
private target: HTMLElement;
|
private target: HTMLElement;
|
||||||
private display: string|null;
|
private display: string|null = null;
|
||||||
private html: string|null;
|
private html: string|null = null;
|
||||||
|
|
||||||
constructor(target: HTMLElement){
|
constructor(target: HTMLElement) {
|
||||||
this.target = target;
|
this.target = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,34 +12,34 @@ export class TypeWriter {
|
||||||
this.html = this.target.innerHTML;
|
this.html = this.target.innerHTML;
|
||||||
this.display = this.target.style.display;
|
this.display = this.target.style.display;
|
||||||
this.target.innerHTML = '';
|
this.target.innerHTML = '';
|
||||||
if( hide ){
|
if ( hide ) {
|
||||||
this.target.style.display = 'none';
|
this.target.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public animate(duration_ms: number) {
|
public animate(msDuration: number) {
|
||||||
if( this.html == null || this.display == null ){
|
if ( this.html == null || this.display == null ) {
|
||||||
console.warn("[typewriter] init must be called first ; doing it anyway");
|
console.warn('[typewriter] init must be called first ; doing it anyway');
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
const size = this.html!.length;
|
const size = this.html!.length;
|
||||||
const letter_ms = duration_ms / size;
|
const msLetter = msDuration / size;
|
||||||
|
|
||||||
this.target.style.display = this.display!;
|
this.target.style.display = this.display!;
|
||||||
for( let i = 0, l = this.html!.length ; i < l ; i++ ){
|
for ( let i = 0, l = this.html!.length ; i < l ; i++ ) {
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => {
|
() => {
|
||||||
((i: number) => {
|
((x: number) => {
|
||||||
this.target.innerHTML = this.html!.substring(0, i+1);
|
this.target.innerHTML = this.html!.substring(0, x + 1);
|
||||||
})(i);
|
})(i);
|
||||||
},
|
},
|
||||||
i*letter_ms);
|
i * msLetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue