Skip to content

Commit

Permalink
feat: adds Prompt Dialog support
Browse files Browse the repository at this point in the history
Signed-off-by: Pedro Lamas <[email protected]>
  • Loading branch information
pedrolamas committed Dec 7, 2023
1 parent 370cc42 commit 3204078
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 12 deletions.
1 change: 1 addition & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export {}

declare module 'vue' {
export interface GlobalComponents {
ActionCommandPromptDialog: typeof import('./src/components/common/ActionCommandPromptDialog.vue')['default']
AddInstanceDialog: typeof import('./src/components/common/AddInstanceDialog.vue')['default']
AppAnnouncementDismissMenu: typeof import('./src/components/layout/AppAnnouncementDismissMenu.vue')['default']
AppBar: typeof import('./src/components/layout/AppBar.vue')['default']
Expand Down
5 changes: 4 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
<file-system-download-dialog />
<updating-dialog />
<spool-selection-dialog />
<action-command-prompt-dialog />
</v-main>

<app-footer />
Expand All @@ -108,6 +109,7 @@ import SpoolSelectionDialog from '@/components/widgets/spoolman/SpoolSelectionDi
import type { FlashMessage } from '@/types'
import { getFilesFromDataTransfer, hasFilesInDataTransfer } from './util/file-system-entry'
import type { ThemeConfig } from '@/store/config/types'
import ActionCommandPromptDialog from './components/common/ActionCommandPromptDialog.vue'
@Component<App>({
metaInfo () {
Expand All @@ -119,7 +121,8 @@ import type { ThemeConfig } from '@/store/config/types'
},
components: {
SpoolSelectionDialog,
FileSystemDownloadDialog
FileSystemDownloadDialog,
ActionCommandPromptDialog
}
})
export default class App extends Mixins(StateMixin, FilesMixin, BrowserMixin) {
Expand Down
69 changes: 69 additions & 0 deletions src/components/common/ActionCommandPromptDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<template>
<app-dialog
v-model="open"
:title="dialog.title"
max-width="450"
:no-actions="dialog.footerButtons.length === 0"
>
<v-card-text>
<v-row
v-for="(item, index) in dialog.items"
:key="`item-${index}`"
>
<v-col v-if="item.type === 'text'">
{{ item.text }}
</v-col>
<v-col v-else-if="item.type === 'button'">
<v-btn
:color="item.color"
block
@click="handleClick(item)"
>
{{ item.text }}
</v-btn>
</v-col>
</v-row>
</v-card-text>

<template #actions>
<v-spacer />

<app-btn
v-for="(button, index) in dialog.footerButtons"
:key="`button-${index}`"
:color="button.color ?? 'primary'"
type="button"
@click="handleClick(button)"
>
{{ button.text }}
</app-btn>
</template>
</app-dialog>
</template>

<script lang="ts">
import { Component, Mixins } from 'vue-property-decorator'
import StateMixin from '@/mixins/state'
import type { PromptDialog, PromptDialogButton } from '@/store/console/types'
@Component({})
export default class ActionCommandPromptDialog extends Mixins(StateMixin) {
get dialog (): PromptDialog {
return this.$store.state.console.promptDialog as PromptDialog
}
get open (): boolean {
return this.dialog.open
}
set open (value: boolean) {
if (!value) {
this.sendGcode('RESPOND TYPE=command MSG="action:prompt_end"')
}
}
handleClick (button: PromptDialogButton) {
this.sendGcode(button.command || button.text)
}
}
</script>
108 changes: 99 additions & 9 deletions src/store/console/actions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { ActionTree } from 'vuex'
import { Globals } from '@/globals'
import type { ConsoleEntry, ConsoleFilter, ConsoleState } from './types'
import type { ConsoleEntry, ConsoleFilter, ConsoleState, PromptDialogButton, PromptDialogItemButton, PromptDialogItemText } from './types'
import type { RootState } from '../types'
import { SocketActions } from '@/api/socketActions'
import DOMPurify from 'dompurify'
import { takeRightWhile } from 'lodash-es'

export const actions: ActionTree<ConsoleState, RootState> = {
/**
Expand Down Expand Up @@ -41,29 +42,118 @@ export const actions: ActionTree<ConsoleState, RootState> = {
/**
* Add a console entry
*/
async onAddConsoleEntry ({ commit }, payload: ConsoleEntry) {
async onAddConsoleEntry ({ commit, dispatch }, payload: ConsoleEntry) {
payload.message = DOMPurify.sanitize(payload.message).replace(/(?:\r\n|\r|\n)/g, '<br />')
if (!payload.time || payload.time <= 0) {
payload.time = Date.now() / 1000 | 0
}
if (!payload.type) {
payload.type = 'response'
}
if (payload.type === 'response' && payload.message.startsWith('// action:')) {
payload.type = 'action'
}

commit('setConsoleEntry', payload)

dispatch('onUpdatePromptDialog', payload)
},

/**
* On a fresh load of the UI, we load prior gcode / console history
*/
async onGcodeStore ({ commit }, payload) {
async onGcodeStore ({ commit, dispatch }, payload: { gcode_store?: ConsoleEntry[] }) {
if (payload && payload.gcode_store) {
const entries = payload.gcode_store.map((s: ConsoleEntry, i: number) => {
s.message = Globals.CONSOLE_RECEIVE_PREFIX + s.message
s.message = DOMPurify.sanitize(s.message).replace(/(?:\r\n|\r|\n)/g, '<br />')
s.id = i
return s
})
const entries = payload.gcode_store
.map((entry, index: number) => {
entry.message = Globals.CONSOLE_RECEIVE_PREFIX + entry.message

entry.message = DOMPurify.sanitize(entry.message).replace(/(?:\r\n|\r|\n)/g, '<br />')

entry.id = index

if (
entry.type === 'response' &&
entry.message.startsWith('// action:')
) {
entry.type = 'action'
}

return entry
})

commit('setAllEntries', entries)

const dialogEntries = entries
.filter(entry => (
entry.type === 'action' &&
entry.message.startsWith('// action:prompt_')
))

const dialogEntriesAfterEnd = takeRightWhile(dialogEntries, entry => entry.message !== '// action:prompt_end')
const dialogEntriesAfterBegin = takeRightWhile(dialogEntriesAfterEnd, entry => entry.message !== '// action:prompt_begin')

dialogEntriesAfterBegin
.forEach(entry => dispatch('onUpdatePromptDialog', entry))
}
},

async onUpdatePromptDialog ({ commit }, payload: ConsoleEntry) {
const parsedMessage = (
payload.type === 'action' &&
/^\/\/ action:prompt_([^ ]+)(?: (.+))?/.exec(payload.message)
)

if (parsedMessage) {
const [, type, param] = parsedMessage

switch (type) {
case 'begin':
commit('setResetPromptDialog', param)
break

case 'text': {
const item: PromptDialogItemText = {
type: 'text',
text: param
}

commit('setPromptDialogItem', item)
break
}

case 'button': {
const [text, command, color] = param.split('|')

const item: PromptDialogItemButton = {
type: 'button',
text,
command,
color
}

commit('setPromptDialogItem', item)
break
}

case 'footer_button': {
const [text, command, color] = param.split('|')

const item: PromptDialogButton = {
text,
command,
color
}

commit('setPromptDialogFooterButton', item)

break
}

case 'show':
case 'end':
commit('setPromptDialogOpen', type === 'show')
}
}
},

Expand Down
25 changes: 24 additions & 1 deletion src/store/console/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { v4 as uuidv4 } from 'uuid'
import type { MutationTree } from 'vuex'
import { defaultState } from './state'
import { Globals } from '@/globals'
import type { ConsoleEntry, ConsoleFilter, ConsoleState } from './types'
import type { ConsoleEntry, ConsoleFilter, ConsoleState, PromptDialogItemButton, PromptDialogItem } from './types'
import escapeRegExp from '@/util/escape-regexp'

const compileExpression = (filter: ConsoleFilter): RegExp => {
Expand Down Expand Up @@ -47,6 +47,29 @@ export const mutations: MutationTree<ConsoleState> = {
state.console = payload
},

setResetPromptDialog (state, payload: string) {
const { promptDialog } = defaultState()

Object.assign(state, {
promptDialog: {
...promptDialog,
title: payload
}
})
},

setPromptDialogItem (state, payload: PromptDialogItem) {
state.promptDialog.items.push(payload)
},

setPromptDialogFooterButton (state, payload: PromptDialogItemButton) {
state.promptDialog.footerButtons.push(payload)
},

setPromptDialogOpen (state, payload: boolean) {
state.promptDialog.open = payload
},

/**
* Defines the list of available commands
*/
Expand Down
5 changes: 5 additions & 0 deletions src/store/console/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export const defaultState = (): ConsoleState => {
commandHistory: [],
autoScroll: true,
lastCleared: 0,
promptDialog: {
open: false,
items: [],
footerButtons: []
},
consoleFilters: [],
consoleFiltersRegexp: []
}
Expand Down
27 changes: 26 additions & 1 deletion src/store/console/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ export interface ConsoleState {
commandHistory: string[];
autoScroll: boolean;
lastCleared: number;
promptDialog: PromptDialog;
consoleFilters: ConsoleFilter[];
consoleFiltersRegexp: RegExp[];
}

export interface ConsoleEntry {
id?: number;
message: string;
type: 'command' | 'response';
type: 'command' | 'response' | 'action';
time?: number;
}

Expand All @@ -31,3 +32,27 @@ export interface ConsoleFilter {
value: string;
enabled: boolean;
}

export interface PromptDialog {
open: boolean;
title?: string;
items: PromptDialogItem[];
footerButtons: PromptDialogButton[]
}

export type PromptDialogItem = PromptDialogItemText | PromptDialogItemButton

export interface PromptDialogItemText {
type: 'text';
text: string;
}

export interface PromptDialogItemButton extends PromptDialogButton {
type: 'button';
}

export interface PromptDialogButton {
text: string;
command?: string;
color?: string;
}

0 comments on commit 3204078

Please sign in to comment.