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

[Work in progress] Separation of concerns using Vue #121

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-typescript"
],
"@babel/preset-typescript",
["vue", {
"eventModifiers": false
}]
]
}
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
dist
node_modules
typings/rdflib
4 changes: 3 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ if (typeof window !== 'undefined') {

let register = panes.register

register(require('./markdown/index').Pane)
register(require('./trustedApplications/index').Pane) // manage your trusted applications

register(require('issue-pane'))
register(require('contacts-pane'))

Expand Down Expand Up @@ -130,7 +133,6 @@ register(require('./internalPane.js'))
// The home pane is a 2016 experiment. Always there.

register(require('./profile/profilePane').default) // edit your public profile
register(require('./trustedApplications/trustedApplicationsPane').default) // manage your trusted applications
register(require('./home/homePane').default)

// ENDS
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ module.exports = {
collectCoverage: true,
// For some reason Jest is not measuring coverage without the below option.
// Unfortunately, despite `!(.test)`, it still measures coverage of test files as well:
testPathIgnorePatterns: [
'/dist/',
'/node_modules/'
],
forceCoverageMatch: ['**/*!(.test).ts'],
// Since we're only measuring coverage for TypeScript (i.e. added with test infrastructure in place),
// we can be fairly strict. However, if you feel that something is not fit for coverage,
Expand Down
67 changes: 67 additions & 0 deletions markdown/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<template>
<div id="MarkdownApp">
<div v-if="isLoading">LOADING</div>
<MarkdownView v-if="isRendering" v-bind:markdown="markdown" v-on:edit="toggle" />
<MarkdownEdit v-if="isEditing" v-bind:markdown="markdown" v-on:save="toggle" />
</div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import MarkdownView from './components/MarkdownView.vue'
import { NamedNode } from 'rdflib'
import { loadMarkdown, saveMarkdown, STATE } from './markdown.service'
import MarkdownEdit from './components/MarkdownEdit.vue'

@Component({
components: {
MarkdownEdit,
MarkdownView
}
})
export default class App extends Vue {
Vinnl marked this conversation as resolved.
Show resolved Hide resolved
state: STATE = STATE.LOADING
markdown: string = ''

@Prop(NamedNode) subject!: NamedNode
Vinnl marked this conversation as resolved.
Show resolved Hide resolved

created (): void {
loadMarkdown(this.subject.uri)
.then(responseText => {
this.markdown = responseText
this.state = STATE.RENDERING
})
}

toggle (markdown: string): void {
const wasEditing = this.state === STATE.EDITING
if (wasEditing) {
this.state = STATE.LOADING
saveMarkdown(this.subject.uri, markdown)
.then(() => this.markdown = markdown)
.then(() => this.state = STATE.RENDERING)
return
}
this.state = STATE.EDITING
}

get isEditing (): boolean {
return this.state === STATE.EDITING
}

get isLoading (): boolean {
return this.state === STATE.LOADING
}

get isRendering (): boolean {
return this.state === STATE.RENDERING
}
}
</script>

<style scoped lang="scss">
#MarkdownApp {
border: solid 3px red;
padding: 3px;
}
</style>
34 changes: 34 additions & 0 deletions markdown/components/MarkdownEdit.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<template>
<form @submit="save">
<label>
<textarea v-model="raw"></textarea>
</label>
<button>Save</button>
</form>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class MarkdownEdit extends Vue {
@Prop() markdown!: string;
raw!: string

created () {
this.raw = this.markdown
}

save (): void {
this.$emit('save', this.raw)
Copy link
Contributor

Choose a reason for hiding this comment

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

I presume this is throwing an event, but I haven't found yet where it is caught - can we/TypeScript trace that?

(I've seen projects where it was unclear whether an event could be removed, because there was no good way to tell whether there were still listeners that were expecting it - or vice versa, that it was not clear whether a listener could be removed. Debugging was also hampered because stack traces ended at the event.)

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 is caught by the v-on:save="toggle" that you'll find in Markdown/App.vue.

This is how event handling are done in Vue. I think it's fairly easy to follow the trace, as it's always direct connection between parent-child, not grandparent-child (not 100% sure on this though).

}
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
textarea {
height: 10em;
width: 98%;
}
</style>
27 changes: 27 additions & 0 deletions markdown/components/MarkdownView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<template>
<div>
<div v-html="text"></div>
<button @click="edit">Edit</button>
</div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import marked from 'marked'

@Component
export default class MarkdownView extends Vue {
@Prop() markdown!: string;

get text (): string {
return marked(this.markdown)
}

edit (): void {
this.$emit('edit')
}
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss"></style>
45 changes: 45 additions & 0 deletions markdown/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { NewPaneOptions, PaneDefinition } from '../types'
import solidUi from 'solid-ui'
import { NamedNode, sym } from 'rdflib'
import { saveMarkdown } from './markdown.service'

import Vue from 'vue'
import App from './App.vue'

const { icons } = solidUi

export const Pane: PaneDefinition = {
icon: `${icons.iconBase}noun_79217.svg`,
name: 'MarkdownPane',
label: (subject: NamedNode) => subject.uri.endsWith('.md') ? 'Handle markdown file' : null,
mintNew: function (options: NewPaneOptions) {
const newInstance = createFileName(options)
return saveMarkdown(newInstance.uri, '# This is your markdown file\n\nHere be stuff!')
.then((): NewPaneOptions => ({
newInstance,
...options
}))
.catch((err: any) => {
console.error('Error creating new instance of markdown file', err)
return options
})
},
render: (subject: NamedNode) => {
return new Vue({
el: '#MarkdownApp',
render: h => h(App, {
props: {
subject
}
})
}).$el
}
}

function createFileName (options: NewPaneOptions): NamedNode {
let uri = options.newBase
if (uri.endsWith('/')) {
uri = uri.slice(0, -1) + '.md'
}
return sym(uri)
}
21 changes: 21 additions & 0 deletions markdown/markdown.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import solidUi from 'solid-ui'

const { store } = solidUi

export function loadMarkdown (uri: string): Promise<string> {
return store.fetcher.webOperation('GET', uri)
.then((response: any) => response.responseText)
}

export function saveMarkdown (uri: string, data: string): Promise<any> {
return store.fetcher.webOperation('PUT', uri, {
data,
contentType: 'text/markdown; charset=UTF-8'
})
}

export enum STATE {
'LOADING',
'RENDERING',
'EDITING'
}
Loading