Skip to content

Instantly share code, notes, and snippets.

@yyx990803
Last active April 18, 2024 07:10
Show Gist options
  • Star 60 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save yyx990803/8854f8f6a97631576c14b63c8acd8f2e to your computer and use it in GitHub Desktop.
Save yyx990803/8854f8f6a97631576c14b63c8acd8f2e to your computer and use it in GitHub Desktop.
<script setup>
import { useQuery, mutate } from 'vue-apollo'
import { ref, reactive, watch, nextTick } 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'
// Network
const { networkState } = useNetworkState()
// Folder
const { folders, currentFolderData } = useCurrentFolderData(networkState)
const folderNavigation = useFolderNavigation({ networkState, currentFolderData })
const { favoriteFolders, toggleFavorite } = useFavoriteFolders(currentFolderData)
const { showHiddenFolders } = useHiddenFolders()
const createFolder = useCreateFolder(folderNavigation.openFolder)
// Current working directory
resetCwdOnLeave()
const { updateOnCwdChanged } = useCwdUtils()
// Utils
const { slicePath } = usePathUtils()
// Reusable functions specific to this component
function useCurrentFolderData (networkState) {
const folders = ref(null)
const currentFolderData = useQuery({
query: FOLDER_CURRENT,
fetchPolicy: 'networkState-only',
networkState,
async result () {
await nextTick()
folders.scrollTop = 0
}
}, {})
return {
folders,
currentFolderData
}
}
function useFolderNavigation ({ networkState, currentFolderData }) {
// Path editing
const pathEditing = reactive({
editingPath: false,
editedPath: '',
})
// DOM ref
const pathInput = ref(null)
async function openPathEdit () {
pathEditing.editedPath = currentFolderData.path
pathEditing.editingPath = true
await nextTick()
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 {
pathInput,
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 = ref(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 useCreateFolder (openFolder) {
const showNewFolder = ref(false)
const newFolderName = ref('')
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>
<template>
...omitted
</template>
@scottg521
Copy link

Where does state() come from (imported from 'vue', used on line 78)? I can't find anything about it in Vue 2.0 or vue-composition API docs.

@bbugh
Copy link

bbugh commented Jan 3, 2020

It was renamed to reactive. value also became ref.

@samywang92
Copy link

Is it possible for OP to post their @/functions/network' component along with this example for more understanding? @yyx990803

Copy link

ghost commented Sep 23, 2020

How to create colored code block ?

@martinszeltins
Copy link

@yyx990803 My question is, should these composition functions be in the same file as the component or in separate files like this:

fileExplorer/useCurrentFolderData.js
fileExplorer/useFolderNavigation.js
fileExplorer/useFavoriteFolderes.js

What is the best practice here? It seems that SOLID principles require each module to be responsible for just one thing.

@Hieu-iceTea
Copy link

Nothing to preview

@rafal-ksiazek-rmtcfm-com
Copy link

rafal-ksiazek-rmtcfm-com commented Jul 5, 2023

For me this looks just awful.

If you will force us (the community) to use CompA over OptA we will move to React because there will be no more differences. We have created over 50 complex projects in Vue/Nuxt and I need to say that all employees just loved to work on them because of the OptA approach (all my employees have React background).

Maybe I'm a dinosaur with 19 years of experience, but I have to say it - I returned to programming after many years after a burnout because programming in Vue with OptA is pure pleasure.

Please don't change that.

@martinszeltins
Copy link

For me this looks just awful.

If you will force us (the community) to use CompA over OptA we will move to React because there will be no more differences. We have created over 50 complex projects in Vue/Nuxt and I need to say that all employees just loved to work on them because of the OptA approach (all my employees have React background).

Maybe I'm a dinosaur with 19 years of experience, but I have to say it - I returned to programming after many years after a burnout because programming in Vue with OptA is pure pleasure.

Please don't change that.

@rafal-ksiazek-rmtcfm-com I strongly disagree with you. Have you created a large-scale app with Vue options API? Code organization and reuse becomes a real nightmare. In contrast, with the new composition API, the code is much cleaner, better organized, more readable, and more maintainable. The Composition API is a pleasure to work with if you value things like clean code and best coding practices.

@rafal-ksiazek-rmtcfm-com

@rafal-ksiazek-rmtcfm-com I strongly disagree with you. Have you created a large-scale app with Vue options API? Code organization and reuse becomes a real nightmare. In contrast, with the new composition API, the code is much cleaner, better organized, more readable, and more maintainable. The Composition API is a pleasure to work with if you value things like clean code and best coding practices.

@martinszeltins Yes, we have. Few of our apps are the #1 in some countries. I understand that CompA has advantages over OptA and the whole process of development of VueJS framework is much better (much easier to write unit tests). From architectural point of view you are right.

But please don't remove the OptA and don't force us to use CompA. We are using it in rare cases. The reason we move from React to Vue was the simplicity and understandability of the code.

And the code I see on the top of this page is again awful. For me, it's like stepping back in time 15 years.

@martinszeltins
Copy link

But please don't remove the OptA and don't force us to use CompA. We are using it in rare cases. The reason we move from React to Vue was the simplicity and understandability of the code.

And the code I see on the top of this page is again awful. For me, it's like stepping back in time 15 years.

@rafal-ksiazek-rmtcfm-com The Options API is not being removed, it will stay and co-exist with the Composition API. However, I do not see anything awful about the code you are referring to. The only thing I would improve would be to move each composable into its own file / module. What don't you like about this code? It nicely follows the SOLID principles for clean code, which is the right direction.

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