Skip to content

Commit

Permalink
feat: save on new document + split tools pane + footer bar improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
NGPixel committed Mar 10, 2024
1 parent daba905 commit 49f648c
Show file tree
Hide file tree
Showing 12 changed files with 402 additions and 248 deletions.
1 change: 1 addition & 0 deletions src-electron/electron-preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ contextBridge.exposeInMainWorld('ipcBridge', {

versions: process.versions,

createNewDocument: (type, filename, data) => ipcRenderer.invoke('createNewDocument', { type, filename, data }),
promptSelectDirectory: (current, title) => ipcRenderer.invoke('promptSelectDirectory', { current, title }),
readDirectory: (dirPath) => ipcRenderer.invoke('readDirectory', { dirPath }),
fetchGitConfig: () => ipcRenderer.invoke('fetchGitConfig'),
Expand Down
92 changes: 67 additions & 25 deletions src-electron/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,41 +79,43 @@ export async function saveDocument (mainWindow, filePath, contents) {
* @param {string} defaultFileName Default filename
*/
export async function saveDocumentAs (mainWindow, type, defaultFileName) {
const filters = []
switch (type) {
case 'xml': {
filters.push({
name: 'XML RFC v3',
extensions: ['xml']
})
break
}
case 'md': {
filters.push({
name: 'Markdown',
extensions: ['md']
})
break
}
case 'txt': {
filters.push({
name: 'Plain Text',
extensions: ['txt']
})
break
}
}
const saveOpts = await dialog.showSaveDialog(mainWindow, {
title: 'Save As...',
defaultPath: defaultFileName,
filters,
filters: getFiltersForType(type),
properties: ['showOverwriteConfirmation', 'createDirectory']
})
if (!saveOpts.canceled) {
mainWindow.webContents.send('save', saveOpts.filePath)
}
}

/**
* Create new document
*
* @param {Object} mainWindow MainWindow instance
* @param {string} type File Type
* @param {string} defaultFileName Default filename
*/
export async function createNewDocument (mainWindow, type, defaultFileName, contents) {
const saveOpts = await dialog.showSaveDialog(mainWindow, {
title: 'New Document',
defaultPath: defaultFileName,
filters: getFiltersForType(type),
properties: ['showOverwriteConfirmation', 'createDirectory']
})
if (!saveOpts.canceled) {
const pathParts = path.parse(saveOpts.filePath)
await fs.writeFile(saveOpts.filePath, contents, 'utf8')
return {
fileName: pathParts.base,
filePath: saveOpts.filePath
}
} else {
return null
}
}

/**
* Show the Select Directory dialog
*
Expand Down Expand Up @@ -155,6 +157,40 @@ export async function readDirectory (dirPath) {
return orderBy(dirItems, ['isDirectory', 'name'], ['desc', 'asc'])
}

/**
* Get Save Dialog filters based on the provided type.
*
* @param {string} type - The type of filters to retrieve.
* @returns {Array} - An array of filters.
*/
function getFiltersForType (type) {
const filters = []
switch (type) {
case 'xml': {
filters.push({
name: 'XML RFC v3',
extensions: ['xml']
})
break
}
case 'md': {
filters.push({
name: 'Markdown',
extensions: ['md']
})
break
}
case 'txt': {
filters.push({
name: 'Plain Text',
extensions: ['txt']
})
break
}
}
return filters
}

/**
* Register callback handlers
*
Expand All @@ -167,12 +203,18 @@ export function registerCallbacks (mainWindow, mainMenu, git, lsp) {
// ----------------------------------------------------------
// FILE SYSTEM
// ----------------------------------------------------------
ipcMain.handle('createNewDocument', (ev, opts) => {
return createNewDocument(mainWindow, opts.type, opts.filename, opts.data)
})
ipcMain.on('open', (ev) => {
openDocument(mainWindow)
})
ipcMain.on('save', (ev, opts) => {
saveDocument(mainWindow, opts.path, opts.data)
})
ipcMain.handle('promptSave', async (ev, opts) => {
return git.performFetch({ dir: opts.dir, remote: opts.remote })
})
ipcMain.on('promptSaveAs', (ev, opts) => {
saveDocumentAs(mainWindow, opts.type, opts.fileName)
})
Expand Down
2 changes: 1 addition & 1 deletion src-electron/lsp.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ export default {
fileAssociations: [
{
systemId: path.join(app.getPath('appData'), app.name, 'rnc/rfc7991bis.rnc'),
pattern: '*'
pattern: '**/*.xml'
}
]
}))
Expand Down
3 changes: 1 addition & 2 deletions src-electron/lsp/init-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,7 @@ export function makeInitConfig ({ rootPath = app.getPath('home'), fileAssociatio
},
validation: {
noGrammar: 'hint',
enabled: true,
schema: true
enabled: true
},
fileAssociations,
preferences: {
Expand Down
202 changes: 202 additions & 0 deletions src/components/DrawerChecks.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
<template lang="pug">
.q-px-md.q-pt-md.q-pb-sm
.flex.items-center
.text-caption.text-light-blue-3
strong Validation Checks
q-space
q-btn.q-mr-sm(
v-if='editorStore.validationChecksDirty'
icon='mdi-close'
padding='none'
size='sm'
no-caps
flat
color='light-blue-3'
@click='editorStore.clearErrors'
)
q-tooltip Clear Validation Checks
q-btn(
label='Run All'
padding='none xs'
size='sm'
no-caps
outline
color='light-blue-3'
@click='runAllChecks'
)
q-list
q-item(
v-for='chk of valChecks'
:key='chk.key'
clickable
@click='chk.click'
)
q-item-section(side)
q-icon(:name='chk.icon' size='xs' color='amber')
q-item-section
q-item-label {{ chk.title }}
q-item-label.text-amber(caption) {{ chk.description }}
q-item-section(side)
q-icon(v-if='editorStore.validationChecks[chk.key] === 0' name='mdi-circle-outline' size='xs' color='blue-grey')
q-icon(v-else-if='editorStore.validationChecks[chk.key] === 1' name='mdi-check-circle' size='xs' color='positive')
q-icon(v-else-if='editorStore.validationChecks[chk.key] === 2' name='mdi-information' size='xs' color='light-blue-5')
q-icon(v-else-if='editorStore.validationChecks[chk.key] === -1' name='mdi-close-circle' size='xs' color='red-5')
q-icon(v-else-if='editorStore.validationChecks[chk.key] === -2' name='mdi-alert-circle' size='xs' color='orange-5')
</template>

<script setup>
import { onBeforeUnmount, onMounted } from 'vue'
import { useQuasar } from 'quasar'
import { checkArticles } from 'src/tools/articles'
import { checkInclusiveLanguage } from 'src/tools/inclusive-language'
import { checkNonAscii } from 'src/tools/non-ascii'
import { useDocsStore } from 'src/stores/docs'
import { useEditorStore } from 'src/stores/editor'
import { modelStore } from 'src/stores/models'
const $q = useQuasar()
// STORES
const docsStore = useDocsStore()
const editorStore = useEditorStore()
const valChecks = [
{
key: 'articles',
title: 'Articles Check',
description: 'Check for bad indefinite articles usage',
icon: 'mdi-alpha-a-box-outline',
click: () => articlesCheck()
},
{
key: 'inclusiveLanguage',
title: 'Inclusive Language Check',
description: 'Check for usage of non-inclusive terms',
icon: 'mdi-atom-variant',
click: () => inclusiveLangCheck()
},
{
key: 'nonAscii',
title: 'Non-ASCII Check',
description: 'Check for non-ASCII characters',
icon: 'mdi-translate',
click: () => nonAsciiCheck()
}
]
// METHODS
function articlesCheck (silent) {
const warnings = checkArticles(modelStore[docsStore.activeDocument.id].getValue())
if (warnings.length < 1) {
editorStore.setValidationCheckState('articles', 1)
if (!silent) {
$q.notify({
message: 'Looks good!',
caption: 'No bad usage of indefinite articles found.',
color: 'positive',
icon: 'mdi-alpha-a-box-outline'
})
}
} else {
editorStore.setValidationCheckState('articles', -2)
if (!silent) {
setTimeout(() => {
EVENT_BUS.emit('editorAction', 'markerNext')
})
}
}
if (silent) {
editorStore.errors.push(...warnings)
} else {
editorStore.errors = warnings
}
}
function inclusiveLangCheck (silent = false) {
const warnings = checkInclusiveLanguage(modelStore[docsStore.activeDocument.id].getValue())
if (warnings.length < 1) {
editorStore.setValidationCheckState('inclusiveLanguage', 1)
if (!silent) {
$q.notify({
message: 'Looks good!',
caption: 'No non-inclusive terms found.',
color: 'positive',
icon: 'mdi-atom-variant'
})
}
} else {
editorStore.setValidationCheckState('inclusiveLanguage', -2)
if (!silent) {
setTimeout(() => {
EVENT_BUS.emit('editorAction', 'markerNext')
})
}
}
if (silent) {
editorStore.errors.push(...warnings)
} else {
editorStore.errors = warnings
}
}
function nonAsciiCheck (silent = false) {
const infos = checkNonAscii(modelStore[docsStore.activeDocument.id].getValue())
if (infos.length < 1) {
editorStore.setValidationCheckState('nonAscii', 1)
if (!silent) {
$q.notify({
message: 'Looks good!',
caption: 'No non-ASCII characters found.',
color: 'positive',
icon: 'mdi-translate'
})
}
} else {
editorStore.setValidationCheckState('nonAscii', 2)
if (!silent) {
setTimeout(() => {
EVENT_BUS.emit('editorAction', 'markerNext')
})
}
}
if (silent) {
editorStore.errors.push(...infos)
} else {
editorStore.errors = infos
}
}
function runAllChecks () {
editorStore.clearErrors()
articlesCheck(true)
inclusiveLangCheck(true)
nonAsciiCheck(true)
if (editorStore.errors.length < 1) {
$q.notify({
message: 'Looks good!',
caption: 'No validation errors found.',
color: 'positive',
icon: 'mdi-check'
})
} else {
setTimeout(() => {
EVENT_BUS.emit('editorAction', 'markerNext')
})
}
}
// MOUNTED
onMounted(() => {
EVENT_BUS.on('runAllChecks', runAllChecks)
})
onBeforeUnmount(() => {
EVENT_BUS.off('runAllChecks')
})
</script>
2 changes: 2 additions & 0 deletions src/components/DrawerPane.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useEditorStore } from 'src/stores/editor'
import DrawerFiles from 'src/components/DrawerFiles.vue'
import DrawerGit from 'src/components/DrawerGit.vue'
import DrawerChecks from 'src/components/DrawerChecks.vue'
import DrawerTools from 'src/components/DrawerTools.vue'
import DrawerSnippets from 'src/components/DrawerSnippets.vue'
Expand All @@ -26,6 +27,7 @@ const editorStore = useEditorStore()
const panes = {
DrawerFiles,
DrawerGit,
DrawerChecks,
DrawerTools,
DrawerSnippets
}
Expand Down
Loading

0 comments on commit 49f648c

Please sign in to comment.