diff --git a/components.d.ts b/components.d.ts
index d153981e3e3..49ad7c9bc9d 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -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']
diff --git a/src/App.vue b/src/App.vue
index 52edf957710..8295f718574 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -84,6 +84,7 @@
+
@@ -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({
metaInfo () {
@@ -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) {
diff --git a/src/components/common/ActionCommandPromptDialog.vue b/src/components/common/ActionCommandPromptDialog.vue
new file mode 100644
index 00000000000..9dfe1b49c4e
--- /dev/null
+++ b/src/components/common/ActionCommandPromptDialog.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+ {{ item.text }}
+
+
+
+ {{ item.text }}
+
+
+
+
+
+
+
+
+
+ {{ button.text }}
+
+
+
+
+
+
diff --git a/src/store/console/actions.ts b/src/store/console/actions.ts
index 882a6d19969..ad2f43fb9ed 100644
--- a/src/store/console/actions.ts
+++ b/src/store/console/actions.ts
@@ -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 = {
/**
@@ -41,7 +42,7 @@ export const actions: ActionTree = {
/**
* 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, '
')
if (!payload.time || payload.time <= 0) {
payload.time = Date.now() / 1000 | 0
@@ -49,21 +50,110 @@ export const actions: ActionTree = {
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, '
')
- 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, '
')
+
+ 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')
+ }
}
},
diff --git a/src/store/console/mutations.ts b/src/store/console/mutations.ts
index 2b19e61691e..e8edfa5e641 100644
--- a/src/store/console/mutations.ts
+++ b/src/store/console/mutations.ts
@@ -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 => {
@@ -47,6 +47,29 @@ export const mutations: MutationTree = {
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
*/
diff --git a/src/store/console/state.ts b/src/store/console/state.ts
index 7991290b46a..2bf7290e4e9 100644
--- a/src/store/console/state.ts
+++ b/src/store/console/state.ts
@@ -9,6 +9,11 @@ export const defaultState = (): ConsoleState => {
commandHistory: [],
autoScroll: true,
lastCleared: 0,
+ promptDialog: {
+ open: false,
+ items: [],
+ footerButtons: []
+ },
consoleFilters: [],
consoleFiltersRegexp: []
}
diff --git a/src/store/console/types.ts b/src/store/console/types.ts
index 82f10be3535..df800de4985 100644
--- a/src/store/console/types.ts
+++ b/src/store/console/types.ts
@@ -7,6 +7,7 @@ export interface ConsoleState {
commandHistory: string[];
autoScroll: boolean;
lastCleared: number;
+ promptDialog: PromptDialog;
consoleFilters: ConsoleFilter[];
consoleFiltersRegexp: RegExp[];
}
@@ -14,7 +15,7 @@ export interface ConsoleState {
export interface ConsoleEntry {
id?: number;
message: string;
- type: 'command' | 'response';
+ type: 'command' | 'response' | 'action';
time?: number;
}
@@ -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;
+}