Skip to content

Commit

Permalink
fix: match selected node and style rules in client side (#8)
Browse files Browse the repository at this point in the history
* chore: add style matcher module in view

* fix: match selected node and style rules in client side

so that we immediately react the style changes in editor for updating preview appearance
  • Loading branch information
ktsn committed Mar 15, 2018
1 parent 50e56d3 commit 031ebad
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 69 deletions.
16 changes: 8 additions & 8 deletions src/message/bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
parseVueFile,
vueFileToPayload
} from '../parser/vue-file'
import { transformRuleForPrint } from '../parser/style/transform'
import { getNode } from '../parser/template/manipulate'
import { getNode as getTemplateNode } from '../parser/template/manipulate'
import { getNode as getStyleNode } from '../parser/style/manipulate'
import {
Modifiers,
insertToTemplate,
Expand All @@ -33,23 +33,23 @@ export function observeServerEvents(

bus.on('selectNode', payload => {
const vueFile = vueFiles[payload.uri]
if (!vueFile || !vueFile.template || payload.path.length === 0) {
if (!vueFile || !vueFile.template || payload.templatePath.length === 0) {
return
}

const element = getNode(vueFile.template, payload.path)
const element = getTemplateNode(vueFile.template, payload.templatePath)
if (!element) {
return
}

const styleRules = vueFile.matchSelector(vueFile.template, payload.path)
const styleRules = payload.stylePaths
.map(path => getStyleNode(vueFile.styles, path))
.filter(<T>(n: T | undefined): n is T => n !== undefined)

const highlightRanges = [element, ...styleRules].map(node => {
return node.range
})

// Notify matched rules to client
bus.emit('matchRules', styleRules.map(transformRuleForPrint))

// Highlight matched ranges on editor
bus.emit('highlightEditor', {
uri: vueFile.uri.toString(),
Expand Down
6 changes: 3 additions & 3 deletions src/message/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { RuleForPrint, DeclarationUpdater } from '../parser/style/types'
import { DeclarationUpdater } from '../parser/style/types'
import { VueFilePayload } from '../parser/vue-file'

export interface Events {
initClient: undefined
selectNode: {
uri: string
path: number[]
templatePath: number[]
stylePaths: number[][]
}
addNode: {
currentUri: string
Expand Down Expand Up @@ -35,7 +36,6 @@ export interface Events {
export interface Commands {
initProject: Record<string, VueFilePayload>
changeDocument: string
matchRules: RuleForPrint[]
highlightEditor: {
uri: string
ranges: [number, number][]
Expand Down
12 changes: 9 additions & 3 deletions src/parser/style/manipulate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ export function addScope(node: t.Style, scope: string): t.Style {
})
}

export function getDeclaration(
export function getNode(
styles: t.Style[],
path: number[]
): t.Declaration | undefined {
const res = path.reduce<any | undefined>(
): t.ChildNode | undefined {
return path.reduce<any | undefined>(
(acc, i) => {
if (!acc) return

Expand All @@ -62,6 +62,12 @@ export function getDeclaration(
},
{ children: styles }
)
}

export function getDeclaration(
styles: t.Style[],
path: number[]
): t.Declaration | undefined {
const res = getNode(styles, path)
return res && res.type === 'Declaration' ? res : undefined
}
7 changes: 2 additions & 5 deletions src/parser/vue-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ import {
extractProps,
extractData
} from './script/manipulate'
import { Style, Rule } from './style/types'
import { Style } from './style/types'
import { transformStyle } from './style/transform'
import { createStyleMatcher } from './style/match'

export interface VueFilePayload {
uri: string
Expand All @@ -38,7 +37,6 @@ export interface VueFile {
data: Data[]
childComponents: ChildComponent[]
styles: Style[]
matchSelector: (template: Template, targetPath: number[]) => Rule[]
}

export function parseVueFile(code: string, uri: string): VueFile {
Expand Down Expand Up @@ -75,8 +73,7 @@ export function parseVueFile(code: string, uri: string): VueFile {
props: extractProps(scriptBody),
data: extractData(scriptBody),
childComponents,
styles: styleAsts,
matchSelector: createStyleMatcher(styleAsts)
styles: styleAsts
}
}

Expand Down
12 changes: 4 additions & 8 deletions src/payload.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { VueFilePayload } from './parser/vue-file'
import { RuleForPrint, DeclarationUpdater } from './parser/style/types'
import { DeclarationUpdater } from './parser/style/types'

export type ServerPayload = InitProject | ChangeDocument | MatchRules
export type ServerPayload = InitProject | ChangeDocument
export type ClientPayload = SelectNode | AddNode | UpdateDeclaration

export interface InitProject {
Expand All @@ -14,15 +14,11 @@ export interface ChangeDocument {
uri: string
}

export interface MatchRules {
type: 'MatchRules'
rules: RuleForPrint[]
}

export interface SelectNode {
type: 'SelectNode'
uri: string
path: number[]
templatePath: number[]
stylePaths: number[][]
}

export interface AddNode {
Expand Down
4 changes: 0 additions & 4 deletions src/server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,5 @@ export function wsCommandEmiter(
observe('changeDocument', payload => {
send({ type: 'ChangeDocument', uri: payload })
})

observe('matchRules', payload => {
send({ type: 'MatchRules', rules: payload })
})
})
}
7 changes: 6 additions & 1 deletion src/view/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Vue from 'vue'
import { Store, install as vuexInstall } from 'vuex'
import modules from './modules'
import { ClientConnection } from '../communication'
import { StyleMatcher } from './style-matcher'

Vue.use(vuexInstall)

Expand All @@ -10,8 +11,12 @@ export const store = new Store({
})

const connection = new ClientConnection()
const styleMatcher = new StyleMatcher()
connection.connect(location.port)
store.dispatch('project/init', connection)
store.dispatch('project/init', {
connection,
styleMatcher
})

declare const module: any
if (module.hot) {
Expand Down
75 changes: 39 additions & 36 deletions src/view/store/modules/project.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import assert from 'assert'
import Vue from 'vue'
import { DefineModule, createNamespacedHelpers } from 'vuex'
import { VueFilePayload } from '@/parser/vue-file'
import { Template, Element } from '@/parser/template/types'
Expand All @@ -13,7 +12,9 @@ import { addScope as addScopeToStyle } from '@/parser/style/manipulate'
import { genStyle } from '@/parser/style/codegen'
import { Prop, Data, ChildComponent } from '@/parser/script/types'
import { ClientConnection } from '@/view/communication'
import { mapValues, clone } from '@/utils'
import { mapValues } from '@/utils'
import { StyleMatcher } from '@/view/store/style-matcher'
import { transformRuleForPrint } from '@/parser/style/transform'

export interface ScopedDocument {
uri: string
Expand Down Expand Up @@ -46,7 +47,10 @@ interface ProjectGetters {
}

interface ProjectActions {
init: ClientConnection
init: {
connection: ClientConnection
styleMatcher: StyleMatcher
}
select: Element | undefined
applyDraggingElement: undefined
startDragging: string
Expand All @@ -57,6 +61,7 @@ interface ProjectActions {
prop?: string
value?: string
}
matchSelectedNodeWithStyles: undefined
}

interface ProjectMutations {
Expand All @@ -68,7 +73,6 @@ interface ProjectMutations {
setDraggingUri: string | undefined
setDraggingPath: number[]
setMatchedRules: RuleForPrint[]
updateMachedRuleDeclaration: DeclarationUpdater
}

export const projectHelpers = createNamespacedHelpers<
Expand All @@ -79,6 +83,7 @@ export const projectHelpers = createNamespacedHelpers<
>('project')

let connection: ClientConnection
let styleMatcher: StyleMatcher
let draggingTimer: any
const draggingInterval = 80

Expand Down Expand Up @@ -211,36 +216,44 @@ export const project: DefineModule<
},

actions: {
init({ commit }, conn) {
connection = conn
init({ commit, dispatch }, payload) {
connection = payload.connection
styleMatcher = payload.styleMatcher

connection.onMessage(data => {
switch (data.type) {
case 'InitProject':
commit('setDocuments', data.vueFiles)
styleMatcher.clear()
Object.keys(data.vueFiles).forEach(key => {
const file = data.vueFiles[key]
styleMatcher.register(file.uri, file.styles)
})
dispatch('matchSelectedNodeWithStyles', undefined)
break
case 'ChangeDocument':
commit('changeDocument', data.uri)
break
case 'MatchRules':
commit('setMatchedRules', data.rules)
break
default: // Do nothing
}
})
},

select({ commit, getters }, node) {
select({ commit, dispatch, getters, state }, node) {
const current = getters.currentDocument
if (!current) return

const path = node ? node.path : []

connection.send({
type: 'SelectNode',
uri: current.uri,
path
})
commit('select', path)
dispatch('matchSelectedNodeWithStyles', undefined).then(() => {
connection.send({
type: 'SelectNode',
uri: current.uri,
templatePath: path,
stylePaths: state.matchedRules.map(r => r.path)
})
})
},

applyDraggingElement({ commit, state, getters }) {
Expand Down Expand Up @@ -335,7 +348,7 @@ export const project: DefineModule<
}, draggingInterval)
},

updateDeclaration({ state, commit }, payload) {
updateDeclaration({ state }, payload) {
if (!state.currentUri) return

const updater: DeclarationUpdater = {
Expand All @@ -362,8 +375,17 @@ export const project: DefineModule<
uri: state.currentUri,
declaration: updater
})
},

matchSelectedNodeWithStyles({ commit, getters, state }) {
const doc = getters.currentDocument
const selected = state.selectedPath
if (!doc || !doc.template || !selected) return

commit('updateMachedRuleDeclaration', updater)
const matchedRules = styleMatcher.match(doc.uri, doc.template, selected)
const forPrint = matchedRules.map(transformRuleForPrint)

commit('setMatchedRules', forPrint)
}
},

Expand Down Expand Up @@ -415,25 +437,6 @@ export const project: DefineModule<

setMatchedRules(state, rules) {
state.matchedRules = rules
},

// TODO: we should find better approach to update client declaration
updateMachedRuleDeclaration(state, declaration) {
const path = declaration.path
const parentPath = path.slice(0, -1)
const index = path[path.length - 1]

const targetRule = state.matchedRules.find(rule => {
if (parentPath.length !== rule.path.length) {
return false
}
return rule.path.reduce((acc, p, i) => acc && p === parentPath[i], true)
})

if (targetRule) {
const original = targetRule.declarations[index]
Vue.set(targetRule.declarations, index, clone(original, declaration))
}
}
}
}
30 changes: 30 additions & 0 deletions src/view/store/style-matcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Style, Rule } from '@/parser/style/types'
import { Template } from '@/parser/template/types'
import { createStyleMatcher } from '@/parser/style/match'

type Matcher = (template: Template, path: number[]) => Rule[]

export class StyleMatcher {
private matchers = new Map<string, Matcher>()

match(uri: string, template: Template, path: number[]): Rule[] {
const matcher = this.matchers.get(uri)
if (!matcher) {
return []
}
return matcher(template, path)
}

register(uri: string, styles: Style[]): void {
const matcher = createStyleMatcher(styles)
this.matchers.set(uri, matcher)
}

unregister(uri: string): void {
this.matchers.delete(uri)
}

clear(): void {
this.matchers.clear()
}
}
4 changes: 3 additions & 1 deletion test/view/store/project.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,9 @@ describe('Store project actions', () => {
send: jest.fn(),
onMessage: jest.fn()
} as any
store.dispatch('project/init', mockConnection)
store.dispatch('project/init', {
connection: mockConnection
})
})

describe('updateDeclaration', () => {
Expand Down

0 comments on commit 031ebad

Please sign in to comment.