Skip to content

Instantly share code, notes, and snippets.

@Akryum
Last active November 25, 2019 18:29
Show Gist options
  • Save Akryum/05964e81d09fb5088b7769cff15f5e7c to your computer and use it in GitHub Desktop.
Save Akryum/05964e81d09fb5088b7769cff15f5e7c to your computer and use it in GitHub Desktop.
Example of migration to Vue Function-based Component API
<script>
import { isValidMultiName } from '@/util/folders'
import FOLDER_CURRENT from '@/graphql/folder/folderCurrent.gql'
import FOLDERS_FAVORITE from '@/graphql/folder/foldersFavorite.gql'
import FOLDER_OPEN from '@/graphql/folder/folderOpen.gql'
import FOLDER_OPEN_PARENT from '@/graphql/folder/folderOpenParent.gql'
import FOLDER_SET_FAVORITE from '@/graphql/folder/folderSetFavorite.gql'
import PROJECT_CWD_RESET from '@/graphql/project/projectCwdReset.gql'
import FOLDER_CREATE from '@/graphql/folder/folderCreate.gql'
const SHOW_HIDDEN = 'vue-ui.show-hidden-folders'
export default {
data () {
return {
loading: 0,
error: false,
editingPath: false,
editedPath: '',
folderCurrent: {},
foldersFavorite: [],
showHidden: localStorage.getItem(SHOW_HIDDEN) === 'true',
showNewFolder: false,
newFolderName: ''
}
},
apollo: {
folderCurrent: {
query: FOLDER_CURRENT,
fetchPolicy: 'network-only',
loadingKey: 'loading',
async result () {
await this.$nextTick()
this.$refs.folders.scrollTop = 0
}
},
foldersFavorite: FOLDERS_FAVORITE
},
computed: {
newFolderValid () {
return isValidMultiName(this.newFolderName)
}
},
watch: {
showHidden (value) {
if (value) {
localStorage.setItem(SHOW_HIDDEN, 'true')
} else {
localStorage.removeItem(SHOW_HIDDEN)
}
}
},
beforeRouteLeave (to, from, next) {
if (to.matched.some(m => m.meta.needProject)) {
this.resetProjectCwd()
}
next()
},
methods: {
async openFolder (path) {
this.editingPath = false
this.error = null
this.loading++
try {
await this.$apollo.mutate({
mutation: FOLDER_OPEN,
variables: {
path
},
update: (store, { data: { folderOpen } }) => {
store.writeQuery({ query: FOLDER_CURRENT, data: { folderCurrent: folderOpen } })
}
})
} catch (e) {
this.error = e
}
this.loading--
},
async openParentFolder (folder) {
this.editingPath = false
this.error = null
this.loading++
try {
await this.$apollo.mutate({
mutation: FOLDER_OPEN_PARENT,
update: (store, { data: { folderOpenParent } }) => {
store.writeQuery({ query: FOLDER_CURRENT, data: { folderCurrent: folderOpenParent } })
}
})
} catch (e) {
this.error = e
}
this.loading--
},
async toggleFavorite () {
await this.$apollo.mutate({
mutation: FOLDER_SET_FAVORITE,
variables: {
path: this.folderCurrent.path,
favorite: !this.folderCurrent.favorite
},
update: (store, { data: { folderSetFavorite } }) => {
store.writeQuery({ query: FOLDER_CURRENT, data: { folderCurrent: folderSetFavorite } })
let data = store.readQuery({ query: FOLDERS_FAVORITE })
// TODO this is a workaround
// See: https://github.com/apollographql/apollo-client/issues/4031#issuecomment-433668473
data = {
foldersFavorite: data.foldersFavorite.slice()
}
if (folderSetFavorite.favorite) {
data.foldersFavorite.push(folderSetFavorite)
} else {
const index = data.foldersFavorite.findIndex(
f => f.path === folderSetFavorite.path
)
index !== -1 && data.foldersFavorite.splice(index, 1)
}
store.writeQuery({ query: FOLDERS_FAVORITE, data })
}
})
},
cwdChangedUpdate (previousResult, { subscriptionData }) {
return {
cwd: subscriptionData.data.cwd
}
},
async openPathEdit () {
this.editedPath = this.folderCurrent.path
this.editingPath = true
await this.$nextTick()
this.$refs.pathInput.focus()
},
submitPathEdit () {
this.openFolder(this.editedPath)
},
refreshFolder () {
this.openFolder(this.folderCurrent.path)
},
resetProjectCwd () {
this.$apollo.mutate({
mutation: PROJECT_CWD_RESET
})
},
async createFolder () {
if (!this.newFolderValid) return
const result = await this.$apollo.mutate({
mutation: FOLDER_CREATE,
variables: {
name: this.newFolderName
}
})
this.openFolder(result.data.folderCreate.path)
this.newFolderName = ''
this.showNewFolder = false
},
slicePath (path) {
const parts = []
let startIndex = 0
let index
const findSeparator = () => {
index = path.indexOf('/', startIndex)
if (index === -1) index = path.indexOf('\\', startIndex)
return index !== -1
}
const addPart = index => {
const folder = path.substring(startIndex, index)
const slice = path.substring(0, index + 1)
parts.push({
name: folder,
path: slice
})
}
while (findSeparator()) {
addPart(index)
startIndex = index + 1
}
if (startIndex < path.length) addPart(path.length)
return parts
}
}
}
</script>
<script>
import { useQuery, mutate } from 'vue-apollo'
import { nextTick, state, value, watch } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
import { isValidMultiName } from '@/util/folders'
import FOLDER_CURRENT from '@/graphql/folder/folderCurrent.gql'
import FOLDERS_FAVORITE from '@/graphql/folder/favoriteFolders.gql'
import FOLDER_OPEN from '@/graphql/folder/folderOpen.gql'
import FOLDER_OPEN_PARENT from '@/graphql/folder/folderOpenParent.gql'
import FOLDER_SET_FAVORITE from '@/graphql/folder/folderSetFavorite.gql'
import PROJECT_CWD_RESET from '@/graphql/project/projectCwdReset.gql'
import FOLDER_CREATE from '@/graphql/folder/folderCreate.gql'
const SHOW_HIDDEN = 'vue-ui.show-hidden-folders'
export default {
setup (props, { refs }) {
const { networkState } = useNetworkState()
// Folder
const { currentFolderData } = usecurrentFolderData(networkState)
const folderNavigationFeature = useFolderNavigation({
networkState,
currentFolderData
refs,
})
const { favoriteFolders, toggleFavorite } = useFavoriteFolders(currentFolderData)
const { showHiddenFolders } = useHiddenFolders()
const createFolderFeature = useCreateFolder(folderNavigationFeature.openFolder)
// Current working directory
const { updateOnCwdChanged } = useCwd()
// Utils
const { slicePath } = usePathUtils()
return {
networkState,
currentFolderData,
...folderNavigationFeature,
refreshFolder,
favoriteFolders,
toggleFavorite,
showHiddenFolders,
...createFolderFeature,
updateOnCwdChanged,
slicePath,
}
}
}
function useNetworkState () {
const networkState = state({
loading: 0,
error: false,
})
return {
networkState
}
}
function usecurrentFolderData (networkState) {
const currentFolderData = useQuery({
query: FOLDER_CURRENT,
fetchPolicy: 'networkState-only',
networkState,
async result () {
await nextTick()
refs.folders.scrollTop = 0
}
}, {})
return {
currentFolderData
}
}
function useFolderNavigation ({ networkState, currentFolderData, refs }) {
// Path editing
const pathEditing = state({
editingPath: false,
editedPath: '',
})
async function openPathEdit () {
pathEditing.editedPath = currentFolderData.path
pathEditing.editingPath = true
await nextTick()
refs.pathInput.focus()
}
function submitPathEdit () {
openFolder(pathEditing.editedPath)
}
// Folder opening
const openFolder = async (path) => {
pathEditing.editingPath = false
networkState.error = null
networkState.loading++
try {
await mutate({
mutation: FOLDER_OPEN,
variables: {
path
},
update: (store, { data: { folderOpen } }) => {
store.writeQuery({ query: FOLDER_CURRENT, data: { currentFolderData: folderOpen } })
}
})
} catch (e) {
networkState.error = e
}
networkState.loading--
}
async function openParentFolder () {
pathEditing.editingPath = false
networkState.error = null
networkState.loading++
try {
await mutate({
mutation: FOLDER_OPEN_PARENT,
update: (store, { data: { folderOpenParent } }) => {
store.writeQuery({ query: FOLDER_CURRENT, data: { currentFolderData: folderOpenParent } })
}
})
} catch (e) {
networkState.error = e
}
networkState.loading--
}
// Refresh
function refreshFolder () {
openFolder(currentFolderData.path)
}
return {
pathEditing,
openPathEdit,
submitPathEdit,
openFolder,
openParentFolder,
refreshFolder
}
}
function useFavoriteFolders (currentFolderData) {
const favoriteFolders = useQuery(FOLDERS_FAVORITE, [])
async function toggleFavorite () {
await mutate({
mutation: FOLDER_SET_FAVORITE,
variables: {
path: currentFolderData.path,
favorite: !currentFolderData.favorite
},
update: (store, { data: { folderSetFavorite } }) => {
store.writeQuery({ query: FOLDER_CURRENT, data: { currentFolderData: folderSetFavorite } })
let data = store.readQuery({ query: FOLDERS_FAVORITE })
// TODO this is a workaround
// See: https://github.com/apollographql/apollo-client/issues/4031#issuecomment-433668473
data = {
favoriteFolders: data.favoriteFolders.slice()
}
if (folderSetFavorite.favorite) {
data.favoriteFolders.push(folderSetFavorite)
} else {
const index = data.favoriteFolders.findIndex(
f => f.path === folderSetFavorite.path
)
index !== -1 && data.favoriteFolders.splice(index, 1)
}
store.writeQuery({ query: FOLDERS_FAVORITE, data })
}
})
}
return {
favoriteFolders,
toggleFavorite
}
}
function useHiddenFolders () {
const showHiddenFolders = value(localStorage.getItem(SHOW_HIDDEN) === 'true')
watch(showHiddenFolders, value => {
if (value) {
localStorage.setItem(SHOW_HIDDEN, 'true')
} else {
localStorage.removeItem(SHOW_HIDDEN)
}
}, { lazy: true })
return {
showHiddenFolders
}
}
function useCwd () {
async function resetProjectCwd () {
await mutate({
mutation: PROJECT_CWD_RESET
})
}
onBeforeRouteLeave((to, from, next) => {
if (to.matched.some(m => m.meta.needProject)) {
resetProjectCwd()
}
next()
})
// Update apollo cache
const updateOnCwdChanged = (previousResult, { subscriptionData }) => {
return {
cwd: subscriptionData.data.cwd
}
}
return {
updateOnCwdChanged
}
}
function useCreateFolder (openFolder) {
const showNewFolder = value(false)
const newFolderName = value('')
const newFolderValid = computed(() => isValidMultiName(newFolderName.value))
async function createFolder () {
if (!newFolderValid.value) return
const result = await mutate({
mutation: FOLDER_CREATE,
variables: {
name: newFolderName.value
}
})
openFolder(result.data.folderCreate.path)
newFolderName.value = ''
showNewFolder.value = false
}
return {
showNewFolder,
newFolderName,
newFolderValid,
createFolder
}
}
function usePathUtils () {
const slicePath = (path) => {
const parts = []
let startIndex = 0
let index
function findSeparator () {
index = path.indexOf('/', startIndex)
if (index === -1) index = path.indexOf('\\', startIndex)
return index !== -1
}
const addPart = index => {
const folder = path.substring(startIndex, index)
const slice = path.substring(0, index + 1)
parts.push({
name: folder,
path: slice
})
}
while (findSeparator()) {
addPart(index)
startIndex = index + 1
}
if (startIndex < path.length) addPart(path.length)
return parts
}
return {
slicePath
}
}
</script>
<script>
import { useQuery, mutate } from 'vue-apollo'
import { nextTick, state, value, watch } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
// Reusable functions not specific to this component
import { useNetworkState } from '@/functions/network'
import { usePathUtils } from '@/functions/path'
import { resetCwdOnLeave, useCwdUtils } from '@/functions/cwd'
// GraphQL
import FOLDER_CURRENT from '@/graphql/folder/folderCurrent.gql'
import FOLDERS_FAVORITE from '@/graphql/folder/favoriteFolders.gql'
import FOLDER_OPEN from '@/graphql/folder/folderOpen.gql'
import FOLDER_OPEN_PARENT from '@/graphql/folder/folderOpenParent.gql'
import FOLDER_SET_FAVORITE from '@/graphql/folder/folderSetFavorite.gql'
import PROJECT_CWD_RESET from '@/graphql/project/projectCwdReset.gql'
import FOLDER_CREATE from '@/graphql/folder/folderCreate.gql'
// Misc
import { isValidMultiName } from '@/util/folders'
const SHOW_HIDDEN = 'vue-ui.show-hidden-folders'
export default {
setup (props, { refs }) {
const { networkState } = useNetworkState()
// Folder
const { currentFolderData } = usecurrentFolderData(networkState)
const folderNavigationFeature = useFolderNavigation({
networkState,
currentFolderData
refs,
})
const { favoriteFolders, toggleFavorite } = useFavoriteFolders(currentFolderData)
const { showHiddenFolders } = useHiddenFolders()
const createFolderFeature = useCreateFolder(folderNavigation.openFolder)
// Current working directory
resetCwdOnLeave()
const { updateOnCwdChanged } = useCwdUtils()
// Utils
const { slicePath } = usePathUtils()
return {
networkState,
currentFolderData,
...folderNavigationFeature,
refreshFolder,
favoriteFolders,
toggleFavorite,
showHiddenFolders,
...createFolderFeature,
updateOnCwdChanged,
slicePath,
}
}
}
// Reusable functions specific to this component
export function usecurrentFolderData (networkState) {
const currentFolderData = useQuery({
query: FOLDER_CURRENT,
fetchPolicy: 'networkState-only',
networkState,
async result () {
await nextTick()
refs.folders.scrollTop = 0
}
}, {})
return {
currentFolderData
}
export }
export function useFolderNavigation ({ networkState, currentFolderData, refs }) {
// Path editing
const pathEditing = state({
editingPath: false,
editedPath: '',
})
async function openPathEdit () {
pathEditing.editedPath = currentFolderData.path
pathEditing.editingPath = true
await nextTick()
refs.pathInput.focus()
}
function submitPathEdit () {
openFolder(pathEditing.editedPath)
}
// Folder opening
const openFolder = async (path) => {
pathEditing.editingPath = false
networkState.error = null
networkState.loading++
try {
await mutate({
mutation: FOLDER_OPEN,
variables: {
path
},
update: (store, { data: { folderOpen } }) => {
store.writeQuery({ query: FOLDER_CURRENT, data: { currentFolderData: folderOpen } })
}
})
} catch (e) {
networkState.error = e
}
networkState.loading--
}
async function openParentFolder () {
pathEditing.editingPath = false
networkState.error = null
networkState.loading++
try {
await mutate({
mutation: FOLDER_OPEN_PARENT,
update: (store, { data: { folderOpenParent } }) => {
store.writeQuery({ query: FOLDER_CURRENT, data: { currentFolderData: folderOpenParent } })
}
})
} catch (e) {
networkState.error = e
}
networkState.loading--
}
// Refresh
function refreshFolder () {
openFolder(currentFolderData.path)
}
return {
pathEditing,
openPathEdit,
submitPathEdit,
openFolder,
openParentFolder,
refreshFolder
}
}
export function useFavoriteFolders (currentFolderData) {
const favoriteFolders = useQuery(FOLDERS_FAVORITE, [])
async function toggleFavorite () {
await mutate({
mutation: FOLDER_SET_FAVORITE,
variables: {
path: currentFolderData.path,
favorite: !currentFolderData.favorite
},
update: (store, { data: { folderSetFavorite } }) => {
store.writeQuery({ query: FOLDER_CURRENT, data: { currentFolderData: folderSetFavorite } })
let data = store.readQuery({ query: FOLDERS_FAVORITE })
// TODO this is a workaround
// See: https://github.com/apollographql/apollo-client/issues/4031#issuecomment-433668473
data = {
favoriteFolders: data.favoriteFolders.slice()
}
if (folderSetFavorite.favorite) {
data.favoriteFolders.push(folderSetFavorite)
} else {
const index = data.favoriteFolders.findIndex(
f => f.path === folderSetFavorite.path
)
index !== -1 && data.favoriteFolders.splice(index, 1)
}
store.writeQuery({ query: FOLDERS_FAVORITE, data })
}
})
}
return {
favoriteFolders,
toggleFavorite
}
}
export function useHiddenFolders () {
const showHiddenFolders = value(localStorage.getItem(SHOW_HIDDEN) === 'true')
watch(showHiddenFolders, value => {
if (value) {
localStorage.setItem(SHOW_HIDDEN, 'true')
} else {
localStorage.removeItem(SHOW_HIDDEN)
}
}, { lazy: true })
return {
showHiddenFolders
}
}
export function useCreateFolder (openFolder) {
const showNewFolder = value(false)
const newFolderName = value('')
const newFolderValid = computed(() => isValidMultiName(newFolderName.value))
async function createFolder () {
if (!newFolderValid.value) return
const result = await mutate({
mutation: FOLDER_CREATE,
variables: {
name: newFolderName.value
}
})
openFolder(result.data.folderCreate.path)
newFolderName.value = ''
showNewFolder.value = false
}
return {
showNewFolder,
newFolderName,
newFolderValid,
createFolder
}
}
</script>
@Akryum
Copy link
Author

Akryum commented Jun 25, 2019

EDIT: unlike what @Akryum does above, I even tend not to import gql files into components, but rather to write const gql strings directly in the method bodies of components, in order to make it clear what kind of queries/mutations I am doing here.

I do that now too, the example above is pending some refactoring in vue-cli-ui. 😸

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment