diff --git a/bundle.js b/bundle.js
index 2199b15..962d623 100644
--- a/bundle.js
+++ b/bundle.js
@@ -175,7 +175,7 @@ function fallback_module() {
}
}
}).call(this)}).call(this,"/src/node_modules/action_bar/action_bar.js")
-},{"STATE":1,"quick_actions":7}],3:[function(require,module,exports){
+},{"STATE":1,"quick_actions":10}],3:[function(require,module,exports){
(function (__filename){(function (){
const STATE = require('STATE')
const statedb = STATE(__filename)
@@ -820,6 +820,151 @@ function fallback_module () {
(function (__filename){(function (){
const STATE = require('STATE')
const statedb = STATE(__filename)
+const { sdb, get } = statedb(fallback_module)
+
+module.exports = form_input
+async function form_input (opts, protocol) {
+ const { id, sdb } = await get(opts.sid)
+ const {drive} = sdb
+
+ const on = {
+ style: inject,
+ }
+
+ let current_step = null
+ let input_accessible = true
+
+ if(protocol){
+ send = protocol(msg => onmessage(msg))
+ _ = { up: send }
+ }
+
+ const el = document.createElement('div')
+ const shadow = el.attachShadow({ mode: 'closed' })
+ shadow.innerHTML = `
+
+ `
+ const style = shadow.querySelector('style')
+
+ const input_field_el = shadow.querySelector('.input-field')
+ const overlay_el = shadow.querySelector('.overlay-lock')
+
+ input_field_el.oninput = function () {
+ if (!input_accessible) return
+ if (this.value.length >= 10) {
+ _.up({
+ type: 'action_submitted',
+ data: {
+ value: this.value,
+ index: current_step?.index || 0
+ }
+ })
+ console.log('mark_as_complete')
+ }
+ }
+
+ const subs = await sdb.watch(onbatch)
+
+
+ return el
+
+ async function onbatch(batch) {
+ for (const { type, paths } of batch){
+ const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw)))
+ const func = on[type] || fail
+ func(data, type)
+ }
+ }
+
+ function fail(data, type) { throw new Error('invalid message', { cause: { data, type } }) }
+
+ function inject (data) {
+ style.replaceChildren((() => {
+ return document.createElement('style').textContent = data[0]
+ })())
+ }
+
+ function onmessage ({ type, data }) {
+ console.log('message from form_input', type, data)
+ if (type === 'step_data') {
+ current_step = data
+ console.log('message from form_input', input_field_el, input_field_el.value)
+ input_field_el.value = data?.data || ''
+ input_accessible = data?.is_accessible !== false
+
+ overlay_el.hidden = input_accessible
+
+ input_field_el.placeholder = input_accessible
+ ? 'Type to submit'
+ : 'Input disabled for this step'
+ }
+ }
+
+}
+function fallback_module () {
+ return {
+ api: fallback_instance,
+ }
+ function fallback_instance () {
+ return {
+ drive: {
+ 'style/': {
+ 'theme.css': {
+ raw: `
+ .input-display {
+ position: relative;
+ background: #131315;
+ border-radius: 16px;
+ border: 1px solid #3c3c3c;
+ display: flex;
+ flex: 1;
+ align-items: center;
+ padding: 0 12px;
+ min-height: 32px;
+ }
+ .input-display:focus-within {
+ border-color: #4285f4;
+ background: #1a1a1c;
+ }
+ .input-field {
+ flex: 1;
+ min-height: 32px;
+ background: transparent;
+ border: none;
+ color: #e8eaed;
+ padding: 0 12px;
+ font-size: 14px;
+ outline: none;
+ }
+ .input-field::placeholder {
+ color: #a6a6a6;
+ }
+ .overlay-lock {
+ position: absolute;
+ inset: 0;
+ background: transparent;
+ z-index: 10;
+ cursor: not-allowed;
+ }
+ `
+ }
+ }
+ }
+ }
+ }
+}
+
+}).call(this)}).call(this,"/src/node_modules/form_input.js")
+},{"STATE":1}],6:[function(require,module,exports){
+(function (__filename){(function (){
+const STATE = require('STATE')
+const statedb = STATE(__filename)
const { get } = statedb(fallback_module)
module.exports = graph_explorer
@@ -1234,7 +1379,155 @@ function fallback_module() {
}
}).call(this)}).call(this,"/src/node_modules/graph_explorer/graph_explorer.js")
-},{"STATE":1}],6:[function(require,module,exports){
+},{"STATE":1}],7:[function(require,module,exports){
+(function (__filename){(function (){
+const STATE = require('STATE')
+const statedb = STATE(__filename)
+const { sdb, get } = statedb(fallback_module)
+
+module.exports = input_test
+async function input_test (opts, protocol) {
+ const { id, sdb } = await get(opts.sid)
+ const {drive} = sdb
+
+ const on = {
+ style: inject,
+ }
+
+ let current_step = null
+ let input_accessible = true
+
+ if(protocol){
+ send = protocol(msg => onmessage(msg))
+ _ = { up: send }
+ }
+
+ const el = document.createElement('div')
+ const shadow = el.attachShadow({ mode: 'closed' })
+ shadow.innerHTML = `
+ Testing 2nd Type
+
+ `
+ const style = shadow.querySelector('style')
+
+ const input_field_el = shadow.querySelector('.input-field')
+ const overlay_el = shadow.querySelector('.overlay-lock')
+
+ input_field_el.oninput = function () {
+ if (!input_accessible) return
+ if (this.value.length >= 10) {
+ _.up({
+ type: 'action_submitted',
+ data: {
+ value: this.value,
+ index: current_step?.index || 0
+ }
+ })
+ console.log('mark_as_complete')
+ }
+ }
+
+ const subs = await sdb.watch(onbatch)
+
+
+ return el
+
+ async function onbatch(batch) {
+ for (const { type, paths } of batch){
+ const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw)))
+ const func = on[type] || fail
+ func(data, type)
+ }
+ }
+
+ function fail(data, type) { throw new Error('invalid message', { cause: { data, type } }) }
+
+ function inject (data) {
+ style.replaceChildren((() => {
+ return document.createElement('style').textContent = data[0]
+ })())
+ }
+
+ function onmessage ({ type, data }) {
+ console.log('message from input_test', type, data)
+ if (type === 'step_data') {
+ current_step = data
+ input_field_el.value = data?.data || ''
+
+ input_accessible = data?.is_accessible !== false
+
+ overlay_el.hidden = input_accessible
+
+ input_field_el.placeholder = input_accessible
+ ? 'Type to submit'
+ : 'Input disabled for this step'
+ }
+ }
+
+}
+function fallback_module () {
+ return {
+ api: fallback_instance,
+ }
+ function fallback_instance () {
+ return {
+ drive: {
+ 'style/': {
+ 'theme.css': {
+ raw: `
+ .title {
+ color: #e8eaed;
+ font-size: 18px;
+ }
+ .input-display {
+ position: relative;
+ background: #131315;
+ border-radius: 16px;
+ border: 1px solid #3c3c3c;
+ display: flex;
+ flex: 1;
+ align-items: center;
+ padding: 0 12px;
+ min-height: 32px;
+ }
+ .input-display:focus-within {
+ border-color: #4285f4;
+ background: #1a1a1c;
+ }
+ .input-field {
+ flex: 1;
+ min-height: 32px;
+ background: transparent;
+ border: none;
+ color: #e8eaed;
+ padding: 0 12px;
+ font-size: 14px;
+ outline: none;
+ }
+ .input-field::placeholder {
+ color: #a6a6a6;
+ }
+ .overlay-lock {
+ position: absolute;
+ inset: 0;
+ background: transparent;
+ z-index: 10;
+ cursor: not-allowed;
+ }
+ `
+ }
+ }
+ }
+ }
+ }
+}
+
+}).call(this)}).call(this,"/src/node_modules/input_test.js")
+},{"STATE":1}],8:[function(require,module,exports){
(function (__filename){(function (){
const STATE = require('STATE')
const statedb = STATE(__filename)
@@ -1489,7 +1782,304 @@ function fallback_module () {
}
}).call(this)}).call(this,"/src/node_modules/menu.js")
-},{"STATE":1}],7:[function(require,module,exports){
+},{"STATE":1}],9:[function(require,module,exports){
+(function (__filename){(function (){
+const STATE = require('STATE')
+const statedb = STATE(__filename)
+const { sdb, get } = statedb(fallback_module)
+
+const quick_actions = require('quick_actions')
+const actions = require('actions')
+const form_input = require('form_input')
+const steps_wizard = require('steps_wizard')
+const input_test = require('input_test')
+
+const component_modules = {
+ form_input,
+ input_test
+ // Add more form input components here if needed
+}
+
+module.exports = program
+
+async function program(opts) {
+ const { id, sdb } = await get(opts.sid)
+ const { drive } = sdb
+
+ const on = {
+ style: inject,
+ variables: onvariables,
+ }
+
+ let variables = []
+
+ const _ = {
+ send_quick_actions: null,
+ send_actions: null,
+ send_form_input: {},
+ send_steps_wizard: null
+ }
+
+ const el = document.createElement('div')
+ const shadow = el.attachShadow({ mode: 'closed' })
+ shadow.innerHTML = `
+
+
+ `
+
+ const style = shadow.querySelector('style')
+ const steps_wizard_placeholder = shadow.querySelector('steps-wizard')
+ const quick_actions_placeholder = shadow.querySelector('quick-actions')
+ const actions_placeholder = shadow.querySelector('actions')
+ const form_input_placeholder = shadow.querySelector('form-input')
+
+ const subs = await sdb.watch(onbatch)
+
+ const quick_actions_el = await quick_actions(subs[0], quick_actions_protocol)
+ quick_actions_placeholder.replaceWith(quick_actions_el)
+
+ const actions_el = await actions(subs[1], actions_protocol)
+ actions_el.classList.add('hide')
+ actions_placeholder.replaceWith(actions_el)
+
+ const steps_wizard_el = await steps_wizard(subs[2], steps_wizard_protocol)
+ steps_wizard_el.classList.add('hide')
+ steps_wizard_placeholder.replaceWith(steps_wizard_el)
+
+ const form_input_elements = {}
+
+ for (const [component_name, component_fn] of Object.entries(component_modules)) {
+ const index = get_form_input_component_index(component_name)
+ const el = await component_fn(subs[index], form_input_protocol(component_name))
+ el.classList.add('hide')
+ form_input_elements[component_name] = el
+ form_input_placeholder.parentNode.insertBefore(el, form_input_placeholder)
+ }
+
+ form_input_placeholder.remove()
+
+ return el
+
+ // --- Internal Functions ---
+ async function onbatch(batch) {
+ for (const { type, paths } of batch) {
+ const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw)))
+ const func = on[type] || fail
+ func(data, type)
+ }
+ }
+
+ function fail(data, type) {
+ throw new Error('invalid message', { cause: { data, type } })
+ }
+
+ function inject(data) {
+ style.replaceChildren((() => {
+ return document.createElement('style').textContent = data[0]
+ })())
+ }
+
+ function onvariables(data) {
+ const vars = typeof data[0] === 'string' ? JSON.parse(data[0]) : data[0]
+ variables = vars['change_path']
+ if (_.send_steps_wizard)
+ _.send_steps_wizard({ type: "init_data", data: variables })
+ }
+
+ function toggle_view(el, show) {
+ el.classList.toggle('hide', !show)
+ }
+
+ function steps_toggle_view(display) {
+ toggle_view(steps_wizard_el, display === 'block')
+ }
+
+ function actions_toggle_view(display) {
+ toggle_view(actions_el, display === 'block')
+ }
+
+ function form_input_protocol(component_name) {
+ return function (send) {
+ _.send_form_input[component_name] = send
+ return function on({ type, data }) {
+ if (type === 'action_submitted') {
+ const step = variables[data?.index]
+ Object.assign(step, {
+ is_completed: true,
+ status: 'completed',
+ data: data?.value
+ })
+ drive.put('variables/program.json', { change_path: variables })
+
+ if (variables[variables.length - 1]?.is_completed) {
+ _.send_quick_actions({ type: 'show_submit_btn' })
+ }
+ }
+ }
+ }
+ }
+
+ function steps_wizard_protocol(send) {
+ _.send_steps_wizard = send
+ return on
+ function on({ type, data }) {
+ if (type === 'step_clicked') {
+ _.send_quick_actions({
+ type: 'update_steps',
+ data: {
+ current_step: data?.index + 1,
+ total_steps: data?.total_steps
+ }
+ })
+
+ render_form_component(data.component)
+ const send = _.send_form_input[data.component]
+ console.log('step_clicked_Data', data)
+ if (send) send({ type: 'step_data', data })
+ }
+ }
+ }
+
+ function render_form_component(component_name) {
+ for (const name in form_input_elements) {
+ toggle_view(form_input_elements[name], name === component_name)
+ }
+ }
+
+ function get_form_input_component_index(component) {
+ const { _: components } = fallback_module()
+ return Object.keys(components).indexOf(component)
+ }
+
+ function actions_protocol(send) {
+ _.send_actions = send
+ return on
+ function on({ type, data }) {
+ _.send_quick_actions({
+ type,
+ data: {
+ ...data,
+ total_steps: variables.length
+ }
+ })
+ _.send_steps_wizard({ type: 'init_data', data: variables })
+ steps_toggle_view('block')
+
+ if (variables.length > 0) {
+ const firstStep = variables[0]
+ render_form_component(firstStep.component)
+ }
+ actions_toggle_view('none')
+ }
+ }
+
+ function quick_actions_protocol(send) {
+ _.send_quick_actions = send
+ return on
+ function on({ type, data }) {
+ on_quick_actions_message({ type, data })
+ }
+ }
+
+ function on_quick_actions_message({ type, data }) {
+ if (type == 'display_actions') {
+ actions_toggle_view(data)
+ if (data === 'none') {
+ steps_toggle_view(data)
+ for (const el of Object.values(form_input_elements)) {
+ toggle_view(el, false)
+ }
+ cleanup()
+ }
+ } else if (type == 'action_submitted') {
+ alert(JSON.stringify(variables.map(step => step.data), null, 2))
+ _.send_quick_actions?.({ type: 'deactivate_input_field' })
+ }
+ }
+
+ function cleanup() {
+ const cleaned = variables.map(step => ({
+ ...step,
+ is_completed: false,
+ data: ''
+ }))
+ drive.put('variables/program.json', { change_path: cleaned })
+ }
+}
+
+// --- Fallback Module ---
+function fallback_module() {
+ return {
+ api: fallback_instance,
+ _: {
+ 'quick_actions': { $: '' },
+ 'actions': { $: '' },
+ 'steps_wizard': { $: '' },
+ 'form_input': { $: '' },
+ 'input_test': { $: '' }
+ }
+ }
+
+ function fallback_instance() {
+ return {
+ _: {
+ 'quick_actions': {
+ 0: '',
+ mapping: {
+ 'style': 'style',
+ 'icons': 'icons',
+ 'actions': 'actions',
+ 'hardcons': 'hardcons'
+ }
+ },
+ 'actions': {
+ 0: '',
+ mapping: {
+ 'style': 'style',
+ 'actions': 'actions',
+ 'icons': 'icons',
+ 'hardcons': 'hardcons'
+ }
+ },
+ 'steps_wizard': {
+ 0: '',
+ mapping: {
+ 'style': 'style',
+ 'variables': 'variables'
+ }
+ },
+ 'form_input': {
+ 0: '',
+ mapping: {
+ 'style': 'style'
+ }
+ },
+ 'input_test': {
+ 0: '',
+ mapping: {
+ 'style': 'style'
+ }
+ }
+ },
+ drive: {
+ 'style/': {
+ 'program.css': { '$ref': 'program.css' }
+ },
+ 'variables/': {
+ 'program.json': { '$ref': 'program.json' }
+ }
+ }
+ }
+ }
+}
+
+}).call(this)}).call(this,"/src/node_modules/program/program.js")
+},{"STATE":1,"actions":3,"form_input":5,"input_test":7,"quick_actions":10,"steps_wizard":13}],10:[function(require,module,exports){
(function (__filename){(function (){
const STATE = require('STATE')
const statedb = STATE(__filename)
@@ -1519,6 +2109,12 @@ async function quick_actions(opts, protocol) {
/
+
+ steps:
+ 1
+ -
+ 1
+
@@ -1535,6 +2131,9 @@ async function quick_actions(opts, protocol) {
const input_field = shadow.querySelector('.input-field')
const submit_btn = shadow.querySelector('.submit-btn')
const close_btn = shadow.querySelector('.close-btn')
+ const step_display = shadow.querySelector('.step-display')
+ const current_step = shadow.querySelector('.current-step')
+ const total_steps = shadow.querySelector('.total-step')
const style = shadow.querySelector('style')
const main = shadow.querySelector('.main')
@@ -1564,6 +2163,13 @@ async function quick_actions(opts, protocol) {
function onmessage ({ type, data }) {
if (type === 'selected_action') {
select_action(data)
+ } else if (type === 'update_steps') {
+ current_step.textContent = data.current_step
+ selected_action.current_step = data.current_step
+ } else if (type === 'deactivate_input_field') {
+ deactivate_input_field()
+ } else if (type == 'show_submit_btn') {
+ show_submit_btn()
}
}
function activate_input_field() {
@@ -1606,18 +2212,26 @@ async function quick_actions(opts, protocol) {
update_input_display(selected_action)
}
+ function show_submit_btn() {
+ submit_btn.style.display = 'flex'
+ }
+
function update_input_display(selected_action = null) {
if (selected_action) {
slash_prefix.style.display = 'inline'
command_text.style.display = 'inline'
- command_text.textContent = `"${selected_action.action}"`
+ command_text.textContent = `#${selected_action.action}`
+ current_step.textContent = selected_action?.current_step || 1
+ total_steps.textContent = selected_action.total_steps
+ step_display.style.display = 'inline-flex'
+
input_field.style.display = 'none'
- submit_btn.style.display = 'flex'
} else {
slash_prefix.style.display = 'none'
command_text.style.display = 'none'
input_field.style.display = 'block'
submit_btn.style.display = 'none'
+ step_display.style.display = 'none'
input_field.placeholder = 'Type to search actions...'
}
}
@@ -1860,6 +2474,28 @@ function fallback_module() {
width: 16px;
height: 16px;
}
+ .step-display {
+ display: inline-flex;
+ align-items: center;
+ gap: 2px;
+ margin-left: 8px;
+ background: #2d2d2d;
+ border: 1px solid #666;
+ border-radius: 4px;
+ padding: 1px 6px;
+ font-size: 12px;
+ color: #fff;
+ font-family: monospace;
+ }
+ .current-step {
+ color:#f0f0f0;
+ }
+ .step-separator {
+ color: #888;
+ }
+ .total-step {
+ color: #f0f0f0;
+ }
`
}
}
@@ -1868,7 +2504,7 @@ function fallback_module() {
}
}
}).call(this)}).call(this,"/src/node_modules/quick_actions/quick_actions.js")
-},{"STATE":1}],8:[function(require,module,exports){
+},{"STATE":1}],11:[function(require,module,exports){
(function (__filename){(function (){
const STATE = require('STATE')
const statedb = STATE(__filename)
@@ -2150,7 +2786,7 @@ function fallback_module(){
}
}
}).call(this)}).call(this,"/src/node_modules/quick_editor.js")
-},{"STATE":1}],9:[function(require,module,exports){
+},{"STATE":1}],12:[function(require,module,exports){
(function (__filename){(function (){
const STATE = require('STATE')
const statedb = STATE(__filename)
@@ -2480,48 +3116,103 @@ function fallback_module () {
}
}).call(this)}).call(this,"/src/node_modules/space.js")
-},{"STATE":1,"actions":3,"console_history":4,"graph_explorer":5,"tabbed_editor":11}],10:[function(require,module,exports){
+},{"STATE":1,"actions":3,"console_history":4,"graph_explorer":6,"tabbed_editor":14}],13:[function(require,module,exports){
(function (__filename){(function (){
const STATE = require('STATE')
const statedb = STATE(__filename)
const { sdb, get } = statedb(fallback_module)
-const actions = require('actions')
-
-
module.exports = steps_wizard
-async function steps_wizard (opts) {
+async function steps_wizard (opts, protocol) {
const { id, sdb } = await get(opts.sid)
const {drive} = sdb
+
const on = {
style: inject
}
+ let variables = []
+
+ let _ = null
+ if(protocol){
+ send = protocol(msg => onmessage(msg))
+ _ = { up: send }
+ }
+
const el = document.createElement('div')
const shadow = el.attachShadow({ mode: 'closed' })
shadow.innerHTML = `
`
const style = shadow.querySelector('style')
- const main = shadow.querySelector('.main')
- const actions_slot = shadow.querySelector('.actions-slot')
-
-
+ const steps_entries = shadow.querySelector('.steps-slot')
+
const subs = await sdb.watch(onbatch)
- let actions_el = null
+ // for demo purpose
+ render_steps([
+ {name: "Optional Step", "type": "optional", "is_completed": false, "component": "form_input", "status": "default", "data": ""},
+ {name: "Step 2", "type": "mandatory", "is_completed": false, "component": "form_input", "status": "default", "data": ""}
+ ])
- actions_el = await actions(subs[0])
- actions_slot.replaceWith(actions_el)
-
return el
+ function onmessage ({ type, data }) {
+ console.log('steps_ data', type, data)
+ if (type === 'init_data') {
+ variables = data
+ render_steps(variables)
+ }
+ }
+
+ function render_steps(steps) {
+ if (!steps)
+ return;
+
+ steps_entries.innerHTML = '';
+
+ steps.forEach((step, index) => {
+ const btn = document.createElement('button')
+ btn.className = 'step-button'
+ btn.textContent = step.name + (step.type === 'optional' ? ' *' : '')
+ btn.setAttribute('data-step', index + 1)
+
+ const accessible = can_access(index, steps)
+
+ let status = 'default'
+ if (!accessible) status = 'disabled'
+ else if (step.is_completed) status = 'completed'
+ else if (step.status === 'error') status = 'error'
+ else if (step.type === 'optional') status = 'optional'
+
+ btn.classList.add(`step-${status}`)
+
+ btn.onclick = async () => {
+ console.log('Clicked:', step)
+ _?.up({type: 'step_clicked', data: {...step, index, total_steps: steps.length, is_accessible: accessible}})
+ };
+
+ steps_entries.appendChild(btn)
+ });
+
+ }
+
+ function can_access(index, steps) {
+ for (let i = 0; i < index; i++) {
+ if (!steps[i].is_completed && steps[i].type !== 'optional') {
+ return false
+ }
+ }
+
+ return true
+ }
+
async function onbatch(batch) {
for (const { type, paths } of batch){
const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw)))
@@ -2535,46 +3226,20 @@ async function steps_wizard (opts) {
return document.createElement('style').textContent = data[0]
})())
}
+
}
function fallback_module () {
return {
- api: fallback_instance,
- _: {
- 'actions': {
- $: ''
- },
- }
+ api: fallback_instance
}
function fallback_instance () {
return {
- _: {
- 'actions': {
- 0: '',
- mapping: {
- 'style': 'style',
- 'actions': 'actions',
- 'icons': 'icons',
- 'hardcons': 'hardcons'
- }
- }
- },
drive: {
'style/': {
'stepswizard.css': {
- raw: `
- .steps-wizard {
- display: flex;
- flex-direction: column;
- width: 100%;
- height: 100%;
- background: #131315;
- }
- .space{
- height: inherit;
- }
- `
+ '$ref': 'stepswizard.css'
}
}
}
@@ -2583,7 +3248,7 @@ function fallback_module () {
}
}).call(this)}).call(this,"/src/node_modules/steps_wizard/steps_wizard.js")
-},{"STATE":1,"actions":3}],11:[function(require,module,exports){
+},{"STATE":1}],14:[function(require,module,exports){
(function (__filename){(function (){
const STATE = require('STATE')
const statedb = STATE(__filename)
@@ -2977,7 +3642,7 @@ function fallback_module() {
}
}).call(this)}).call(this,"/src/node_modules/tabbed_editor/tabbed_editor.js")
-},{"STATE":1}],12:[function(require,module,exports){
+},{"STATE":1}],15:[function(require,module,exports){
(function (__filename){(function (){
const STATE = require('STATE')
const statedb = STATE(__filename)
@@ -3189,7 +3854,7 @@ function fallback_module () {
}
}).call(this)}).call(this,"/src/node_modules/tabs/tabs.js")
-},{"STATE":1}],13:[function(require,module,exports){
+},{"STATE":1}],16:[function(require,module,exports){
(function (__filename){(function (){
const state = require('STATE')
const state_db = state(__filename)
@@ -3372,7 +4037,7 @@ function fallback_module () {
}
}
}).call(this)}).call(this,"/src/node_modules/tabsbar/tabsbar.js")
-},{"STATE":1,"tabs":12,"task_manager":14}],14:[function(require,module,exports){
+},{"STATE":1,"tabs":15,"task_manager":17}],17:[function(require,module,exports){
(function (__filename){(function (){
const STATE = require('STATE')
const statedb = STATE(__filename)
@@ -3465,7 +4130,7 @@ function fallback_module () {
}
}).call(this)}).call(this,"/src/node_modules/task_manager.js")
-},{"STATE":1}],15:[function(require,module,exports){
+},{"STATE":1}],18:[function(require,module,exports){
(function (__filename){(function (){
const STATE = require('STATE')
const statedb = STATE(__filename)
@@ -3623,7 +4288,7 @@ function fallback_module() {
}
}).call(this)}).call(this,"/src/node_modules/taskbar/taskbar.js")
-},{"STATE":1,"action_bar":2,"tabsbar":13}],16:[function(require,module,exports){
+},{"STATE":1,"action_bar":2,"tabsbar":16}],19:[function(require,module,exports){
(function (__filename){(function (){
const STATE = require('STATE')
const statedb = STATE(__filename)
@@ -3759,7 +4424,7 @@ function fallback_module () {
}
}).call(this)}).call(this,"/src/node_modules/theme_widget/theme_widget.js")
-},{"STATE":1,"space":9,"taskbar":15}],17:[function(require,module,exports){
+},{"STATE":1,"space":12,"taskbar":18}],20:[function(require,module,exports){
const prefix = 'https://raw.githubusercontent.com/alyhxn/playproject/main/'
const init_url = location.hash === '#dev' ? 'web/init.js' : prefix + 'src/node_modules/init.js'
const args = arguments
@@ -3780,7 +4445,7 @@ fetch(init_url, fetch_opts).then(res => res.text()).then(async source => {
require('./page') // or whatever is otherwise the main entry of our project
})
-},{"./page":18}],18:[function(require,module,exports){
+},{"./page":21}],21:[function(require,module,exports){
(function (__filename){(function (){
const STATE = require('../src/node_modules/STATE')
const statedb = STATE(__filename)
@@ -3803,6 +4468,7 @@ const task_manager = require('../src/node_modules/task_manager')
const quick_actions = require('../src/node_modules/quick_actions')
const graph_explorer = require('../src/node_modules/graph_explorer')
const editor = require('../src/node_modules/quick_editor')
+const program = require('../src/node_modules/program')
const steps_wizard = require('../src/node_modules/steps_wizard')
const imports = {
@@ -3818,6 +4484,7 @@ const imports = {
task_manager,
quick_actions,
graph_explorer,
+ program,
steps_wizard,
}
config().then(() => boot({ sid: '' }))
@@ -4079,7 +4746,8 @@ function fallback_module () {
'../src/node_modules/task_manager',
'../src/node_modules/quick_actions',
'../src/node_modules/graph_explorer',
- '../src/node_modules/steps_wizard'
+ '../src/node_modules/program',
+ '../src/node_modules/steps_wizard',
]
const subs = {}
names.forEach(subgen)
@@ -4093,6 +4761,21 @@ function fallback_module () {
'style': 'style'
}
}
+ subs['../src/node_modules/program'] = {
+ $: '',
+ 0: '',
+ mapping: {
+ 'variables': 'variables',
+ 'style': 'style'
+ }
+ }
+ subs['../src/node_modules/steps_wizard'] = {
+ $: '',
+ 0: '',
+ mapping: {
+ 'style': 'style'
+ }
+ }
subs['../src/node_modules/tabsbar'] = {
$: '',
0: '',
@@ -4313,4 +4996,4 @@ function fallback_module () {
}
}).call(this)}).call(this,"/web/page.js")
-},{"../src/node_modules/STATE":1,"../src/node_modules/action_bar":2,"../src/node_modules/actions":3,"../src/node_modules/console_history":4,"../src/node_modules/graph_explorer":5,"../src/node_modules/menu":6,"../src/node_modules/quick_actions":7,"../src/node_modules/quick_editor":8,"../src/node_modules/space":9,"../src/node_modules/steps_wizard":10,"../src/node_modules/tabbed_editor":11,"../src/node_modules/tabs":12,"../src/node_modules/tabsbar":13,"../src/node_modules/task_manager":14,"../src/node_modules/taskbar":15,"../src/node_modules/theme_widget":16}]},{},[17]);
+},{"../src/node_modules/STATE":1,"../src/node_modules/action_bar":2,"../src/node_modules/actions":3,"../src/node_modules/console_history":4,"../src/node_modules/graph_explorer":6,"../src/node_modules/menu":8,"../src/node_modules/program":9,"../src/node_modules/quick_actions":10,"../src/node_modules/quick_editor":11,"../src/node_modules/space":12,"../src/node_modules/steps_wizard":13,"../src/node_modules/tabbed_editor":14,"../src/node_modules/tabs":15,"../src/node_modules/tabsbar":16,"../src/node_modules/task_manager":17,"../src/node_modules/taskbar":18,"../src/node_modules/theme_widget":19}]},{},[20]);
diff --git a/src/node_modules/form_input.js b/src/node_modules/form_input.js
new file mode 100644
index 0000000..8b53b51
--- /dev/null
+++ b/src/node_modules/form_input.js
@@ -0,0 +1,141 @@
+const STATE = require('STATE')
+const statedb = STATE(__filename)
+const { sdb, get } = statedb(fallback_module)
+
+module.exports = form_input
+async function form_input (opts, protocol) {
+ const { id, sdb } = await get(opts.sid)
+ const {drive} = sdb
+
+ const on = {
+ style: inject,
+ }
+
+ let current_step = null
+ let input_accessible = true
+
+ if(protocol){
+ send = protocol(msg => onmessage(msg))
+ _ = { up: send }
+ }
+
+ const el = document.createElement('div')
+ const shadow = el.attachShadow({ mode: 'closed' })
+ shadow.innerHTML = `
+
+ `
+ const style = shadow.querySelector('style')
+
+ const input_field_el = shadow.querySelector('.input-field')
+ const overlay_el = shadow.querySelector('.overlay-lock')
+
+ input_field_el.oninput = function () {
+ if (!input_accessible) return
+ if (this.value.length >= 10) {
+ _.up({
+ type: 'action_submitted',
+ data: {
+ value: this.value,
+ index: current_step?.index || 0
+ }
+ })
+ console.log('mark_as_complete')
+ }
+ }
+
+ const subs = await sdb.watch(onbatch)
+
+
+ return el
+
+ async function onbatch(batch) {
+ for (const { type, paths } of batch){
+ const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw)))
+ const func = on[type] || fail
+ func(data, type)
+ }
+ }
+
+ function fail(data, type) { throw new Error('invalid message', { cause: { data, type } }) }
+
+ function inject (data) {
+ style.replaceChildren((() => {
+ return document.createElement('style').textContent = data[0]
+ })())
+ }
+
+ function onmessage ({ type, data }) {
+ console.log('message from form_input', type, data)
+ if (type === 'step_data') {
+ current_step = data
+ console.log('message from form_input', input_field_el, input_field_el.value)
+ input_field_el.value = data?.data || ''
+ input_accessible = data?.is_accessible !== false
+
+ overlay_el.hidden = input_accessible
+
+ input_field_el.placeholder = input_accessible
+ ? 'Type to submit'
+ : 'Input disabled for this step'
+ }
+ }
+
+}
+function fallback_module () {
+ return {
+ api: fallback_instance,
+ }
+ function fallback_instance () {
+ return {
+ drive: {
+ 'style/': {
+ 'theme.css': {
+ raw: `
+ .input-display {
+ position: relative;
+ background: #131315;
+ border-radius: 16px;
+ border: 1px solid #3c3c3c;
+ display: flex;
+ flex: 1;
+ align-items: center;
+ padding: 0 12px;
+ min-height: 32px;
+ }
+ .input-display:focus-within {
+ border-color: #4285f4;
+ background: #1a1a1c;
+ }
+ .input-field {
+ flex: 1;
+ min-height: 32px;
+ background: transparent;
+ border: none;
+ color: #e8eaed;
+ padding: 0 12px;
+ font-size: 14px;
+ outline: none;
+ }
+ .input-field::placeholder {
+ color: #a6a6a6;
+ }
+ .overlay-lock {
+ position: absolute;
+ inset: 0;
+ background: transparent;
+ z-index: 10;
+ cursor: not-allowed;
+ }
+ `
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/node_modules/input_test.js b/src/node_modules/input_test.js
new file mode 100644
index 0000000..66cba25
--- /dev/null
+++ b/src/node_modules/input_test.js
@@ -0,0 +1,144 @@
+const STATE = require('STATE')
+const statedb = STATE(__filename)
+const { sdb, get } = statedb(fallback_module)
+
+module.exports = input_test
+async function input_test (opts, protocol) {
+ const { id, sdb } = await get(opts.sid)
+ const {drive} = sdb
+
+ const on = {
+ style: inject,
+ }
+
+ let current_step = null
+ let input_accessible = true
+
+ if(protocol){
+ send = protocol(msg => onmessage(msg))
+ _ = { up: send }
+ }
+
+ const el = document.createElement('div')
+ const shadow = el.attachShadow({ mode: 'closed' })
+ shadow.innerHTML = `
+ Testing 2nd Type
+
+ `
+ const style = shadow.querySelector('style')
+
+ const input_field_el = shadow.querySelector('.input-field')
+ const overlay_el = shadow.querySelector('.overlay-lock')
+
+ input_field_el.oninput = function () {
+ if (!input_accessible) return
+ if (this.value.length >= 10) {
+ _.up({
+ type: 'action_submitted',
+ data: {
+ value: this.value,
+ index: current_step?.index || 0
+ }
+ })
+ console.log('mark_as_complete')
+ }
+ }
+
+ const subs = await sdb.watch(onbatch)
+
+
+ return el
+
+ async function onbatch(batch) {
+ for (const { type, paths } of batch){
+ const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw)))
+ const func = on[type] || fail
+ func(data, type)
+ }
+ }
+
+ function fail(data, type) { throw new Error('invalid message', { cause: { data, type } }) }
+
+ function inject (data) {
+ style.replaceChildren((() => {
+ return document.createElement('style').textContent = data[0]
+ })())
+ }
+
+ function onmessage ({ type, data }) {
+ console.log('message from input_test', type, data)
+ if (type === 'step_data') {
+ current_step = data
+ input_field_el.value = data?.data || ''
+
+ input_accessible = data?.is_accessible !== false
+
+ overlay_el.hidden = input_accessible
+
+ input_field_el.placeholder = input_accessible
+ ? 'Type to submit'
+ : 'Input disabled for this step'
+ }
+ }
+
+}
+function fallback_module () {
+ return {
+ api: fallback_instance,
+ }
+ function fallback_instance () {
+ return {
+ drive: {
+ 'style/': {
+ 'theme.css': {
+ raw: `
+ .title {
+ color: #e8eaed;
+ font-size: 18px;
+ }
+ .input-display {
+ position: relative;
+ background: #131315;
+ border-radius: 16px;
+ border: 1px solid #3c3c3c;
+ display: flex;
+ flex: 1;
+ align-items: center;
+ padding: 0 12px;
+ min-height: 32px;
+ }
+ .input-display:focus-within {
+ border-color: #4285f4;
+ background: #1a1a1c;
+ }
+ .input-field {
+ flex: 1;
+ min-height: 32px;
+ background: transparent;
+ border: none;
+ color: #e8eaed;
+ padding: 0 12px;
+ font-size: 14px;
+ outline: none;
+ }
+ .input-field::placeholder {
+ color: #a6a6a6;
+ }
+ .overlay-lock {
+ position: absolute;
+ inset: 0;
+ background: transparent;
+ z-index: 10;
+ cursor: not-allowed;
+ }
+ `
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/node_modules/program/package.json b/src/node_modules/program/package.json
new file mode 100644
index 0000000..f14c75f
--- /dev/null
+++ b/src/node_modules/program/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "program",
+ "main": "program.js"
+ }
+
\ No newline at end of file
diff --git a/src/node_modules/program/program.css b/src/node_modules/program/program.css
new file mode 100644
index 0000000..f0d939f
--- /dev/null
+++ b/src/node_modules/program/program.css
@@ -0,0 +1,12 @@
+.main {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
+ background: #131315;
+}
+
+/* Visibility */
+.hide {
+ display: none;
+}
diff --git a/src/node_modules/program/program.js b/src/node_modules/program/program.js
new file mode 100644
index 0000000..a36dcee
--- /dev/null
+++ b/src/node_modules/program/program.js
@@ -0,0 +1,293 @@
+const STATE = require('STATE')
+const statedb = STATE(__filename)
+const { sdb, get } = statedb(fallback_module)
+
+const quick_actions = require('quick_actions')
+const actions = require('actions')
+const form_input = require('form_input')
+const steps_wizard = require('steps_wizard')
+const input_test = require('input_test')
+
+const component_modules = {
+ form_input,
+ input_test
+ // Add more form input components here if needed
+}
+
+module.exports = program
+
+async function program(opts) {
+ const { id, sdb } = await get(opts.sid)
+ const { drive } = sdb
+
+ const on = {
+ style: inject,
+ variables: onvariables,
+ }
+
+ let variables = []
+
+ const _ = {
+ send_quick_actions: null,
+ send_actions: null,
+ send_form_input: {},
+ send_steps_wizard: null
+ }
+
+ const el = document.createElement('div')
+ const shadow = el.attachShadow({ mode: 'closed' })
+ shadow.innerHTML = `
+
+
+ `
+
+ const style = shadow.querySelector('style')
+ const steps_wizard_placeholder = shadow.querySelector('steps-wizard')
+ const quick_actions_placeholder = shadow.querySelector('quick-actions')
+ const actions_placeholder = shadow.querySelector('actions')
+ const form_input_placeholder = shadow.querySelector('form-input')
+
+ const subs = await sdb.watch(onbatch)
+
+ const quick_actions_el = await quick_actions(subs[0], quick_actions_protocol)
+ quick_actions_placeholder.replaceWith(quick_actions_el)
+
+ const actions_el = await actions(subs[1], actions_protocol)
+ actions_el.classList.add('hide')
+ actions_placeholder.replaceWith(actions_el)
+
+ const steps_wizard_el = await steps_wizard(subs[2], steps_wizard_protocol)
+ steps_wizard_el.classList.add('hide')
+ steps_wizard_placeholder.replaceWith(steps_wizard_el)
+
+ const form_input_elements = {}
+
+ for (const [component_name, component_fn] of Object.entries(component_modules)) {
+ const index = get_form_input_component_index(component_name)
+ const el = await component_fn(subs[index], form_input_protocol(component_name))
+ el.classList.add('hide')
+ form_input_elements[component_name] = el
+ form_input_placeholder.parentNode.insertBefore(el, form_input_placeholder)
+ }
+
+ form_input_placeholder.remove()
+
+ return el
+
+ // --- Internal Functions ---
+ async function onbatch(batch) {
+ for (const { type, paths } of batch) {
+ const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw)))
+ const func = on[type] || fail
+ func(data, type)
+ }
+ }
+
+ function fail(data, type) {
+ throw new Error('invalid message', { cause: { data, type } })
+ }
+
+ function inject(data) {
+ style.replaceChildren((() => {
+ return document.createElement('style').textContent = data[0]
+ })())
+ }
+
+ function onvariables(data) {
+ const vars = typeof data[0] === 'string' ? JSON.parse(data[0]) : data[0]
+ variables = vars['change_path']
+ if (_.send_steps_wizard)
+ _.send_steps_wizard({ type: "init_data", data: variables })
+ }
+
+ function toggle_view(el, show) {
+ el.classList.toggle('hide', !show)
+ }
+
+ function steps_toggle_view(display) {
+ toggle_view(steps_wizard_el, display === 'block')
+ }
+
+ function actions_toggle_view(display) {
+ toggle_view(actions_el, display === 'block')
+ }
+
+ function form_input_protocol(component_name) {
+ return function (send) {
+ _.send_form_input[component_name] = send
+ return function on({ type, data }) {
+ if (type === 'action_submitted') {
+ const step = variables[data?.index]
+ Object.assign(step, {
+ is_completed: true,
+ status: 'completed',
+ data: data?.value
+ })
+ drive.put('variables/program.json', { change_path: variables })
+
+ if (variables[variables.length - 1]?.is_completed) {
+ _.send_quick_actions({ type: 'show_submit_btn' })
+ }
+ }
+ }
+ }
+ }
+
+ function steps_wizard_protocol(send) {
+ _.send_steps_wizard = send
+ return on
+ function on({ type, data }) {
+ if (type === 'step_clicked') {
+ _.send_quick_actions({
+ type: 'update_steps',
+ data: {
+ current_step: data?.index + 1,
+ total_steps: data?.total_steps
+ }
+ })
+
+ render_form_component(data.component)
+ const send = _.send_form_input[data.component]
+ console.log('step_clicked_Data', data)
+ if (send) send({ type: 'step_data', data })
+ }
+ }
+ }
+
+ function render_form_component(component_name) {
+ for (const name in form_input_elements) {
+ toggle_view(form_input_elements[name], name === component_name)
+ }
+ }
+
+ function get_form_input_component_index(component) {
+ const { _: components } = fallback_module()
+ return Object.keys(components).indexOf(component)
+ }
+
+ function actions_protocol(send) {
+ _.send_actions = send
+ return on
+ function on({ type, data }) {
+ _.send_quick_actions({
+ type,
+ data: {
+ ...data,
+ total_steps: variables.length
+ }
+ })
+ _.send_steps_wizard({ type: 'init_data', data: variables })
+ steps_toggle_view('block')
+
+ if (variables.length > 0) {
+ const firstStep = variables[0]
+ render_form_component(firstStep.component)
+ }
+ actions_toggle_view('none')
+ }
+ }
+
+ function quick_actions_protocol(send) {
+ _.send_quick_actions = send
+ return on
+ function on({ type, data }) {
+ on_quick_actions_message({ type, data })
+ }
+ }
+
+ function on_quick_actions_message({ type, data }) {
+ if (type == 'display_actions') {
+ actions_toggle_view(data)
+ if (data === 'none') {
+ steps_toggle_view(data)
+ for (const el of Object.values(form_input_elements)) {
+ toggle_view(el, false)
+ }
+ cleanup()
+ }
+ } else if (type == 'action_submitted') {
+ alert(JSON.stringify(variables.map(step => step.data), null, 2))
+ _.send_quick_actions?.({ type: 'deactivate_input_field' })
+ }
+ }
+
+ function cleanup() {
+ const cleaned = variables.map(step => ({
+ ...step,
+ is_completed: false,
+ data: ''
+ }))
+ drive.put('variables/program.json', { change_path: cleaned })
+ }
+}
+
+// --- Fallback Module ---
+function fallback_module() {
+ return {
+ api: fallback_instance,
+ _: {
+ 'quick_actions': { $: '' },
+ 'actions': { $: '' },
+ 'steps_wizard': { $: '' },
+ 'form_input': { $: '' },
+ 'input_test': { $: '' }
+ }
+ }
+
+ function fallback_instance() {
+ return {
+ _: {
+ 'quick_actions': {
+ 0: '',
+ mapping: {
+ 'style': 'style',
+ 'icons': 'icons',
+ 'actions': 'actions',
+ 'hardcons': 'hardcons'
+ }
+ },
+ 'actions': {
+ 0: '',
+ mapping: {
+ 'style': 'style',
+ 'actions': 'actions',
+ 'icons': 'icons',
+ 'hardcons': 'hardcons'
+ }
+ },
+ 'steps_wizard': {
+ 0: '',
+ mapping: {
+ 'style': 'style',
+ 'variables': 'variables'
+ }
+ },
+ 'form_input': {
+ 0: '',
+ mapping: {
+ 'style': 'style'
+ }
+ },
+ 'input_test': {
+ 0: '',
+ mapping: {
+ 'style': 'style'
+ }
+ }
+ },
+ drive: {
+ 'style/': {
+ 'program.css': { '$ref': 'program.css' }
+ },
+ 'variables/': {
+ 'program.json': { '$ref': 'program.json' }
+ }
+ }
+ }
+ }
+}
diff --git a/src/node_modules/program/program.json b/src/node_modules/program/program.json
new file mode 100644
index 0000000..dedfd65
--- /dev/null
+++ b/src/node_modules/program/program.json
@@ -0,0 +1,5 @@
+{"change_path": [
+ {"name": "select an element", "type": "mandatory", "is_completed": false, "component": "form_input", "status": "default", "data": ""},
+ {"name": "step 2", "type": "mandatory", "is_completed": false, "component": "input_test", "status": "default", "data": ""},
+ {"name": "step 3", "type": "mandatory", "is_completed": false, "component": "form_input", "status": "default", "data": ""}
+]}
diff --git a/src/node_modules/quick_actions/quick_actions.js b/src/node_modules/quick_actions/quick_actions.js
index 72b9e94..7895827 100644
--- a/src/node_modules/quick_actions/quick_actions.js
+++ b/src/node_modules/quick_actions/quick_actions.js
@@ -26,6 +26,12 @@ async function quick_actions(opts, protocol) {
/
+
+ steps:
+ 1
+ -
+ 1
+
@@ -42,6 +48,9 @@ async function quick_actions(opts, protocol) {
const input_field = shadow.querySelector('.input-field')
const submit_btn = shadow.querySelector('.submit-btn')
const close_btn = shadow.querySelector('.close-btn')
+ const step_display = shadow.querySelector('.step-display')
+ const current_step = shadow.querySelector('.current-step')
+ const total_steps = shadow.querySelector('.total-step')
const style = shadow.querySelector('style')
const main = shadow.querySelector('.main')
@@ -71,6 +80,13 @@ async function quick_actions(opts, protocol) {
function onmessage ({ type, data }) {
if (type === 'selected_action') {
select_action(data)
+ } else if (type === 'update_steps') {
+ current_step.textContent = data.current_step
+ selected_action.current_step = data.current_step
+ } else if (type === 'deactivate_input_field') {
+ deactivate_input_field()
+ } else if (type == 'show_submit_btn') {
+ show_submit_btn()
}
}
function activate_input_field() {
@@ -113,18 +129,26 @@ async function quick_actions(opts, protocol) {
update_input_display(selected_action)
}
+ function show_submit_btn() {
+ submit_btn.style.display = 'flex'
+ }
+
function update_input_display(selected_action = null) {
if (selected_action) {
slash_prefix.style.display = 'inline'
command_text.style.display = 'inline'
- command_text.textContent = `"${selected_action.action}"`
+ command_text.textContent = `#${selected_action.action}`
+ current_step.textContent = selected_action?.current_step || 1
+ total_steps.textContent = selected_action.total_steps
+ step_display.style.display = 'inline-flex'
+
input_field.style.display = 'none'
- submit_btn.style.display = 'flex'
} else {
slash_prefix.style.display = 'none'
command_text.style.display = 'none'
input_field.style.display = 'block'
submit_btn.style.display = 'none'
+ step_display.style.display = 'none'
input_field.placeholder = 'Type to search actions...'
}
}
@@ -367,6 +391,28 @@ function fallback_module() {
width: 16px;
height: 16px;
}
+ .step-display {
+ display: inline-flex;
+ align-items: center;
+ gap: 2px;
+ margin-left: 8px;
+ background: #2d2d2d;
+ border: 1px solid #666;
+ border-radius: 4px;
+ padding: 1px 6px;
+ font-size: 12px;
+ color: #fff;
+ font-family: monospace;
+ }
+ .current-step {
+ color:#f0f0f0;
+ }
+ .step-separator {
+ color: #888;
+ }
+ .total-step {
+ color: #f0f0f0;
+ }
`
}
}
diff --git a/src/node_modules/quick_actions/submit.svg b/src/node_modules/quick_actions/submit.svg
index 033fa0d..5abe6cb 100644
--- a/src/node_modules/quick_actions/submit.svg
+++ b/src/node_modules/quick_actions/submit.svg
@@ -1,3 +1,4 @@
diff --git a/src/node_modules/steps_wizard/guide.md b/src/node_modules/steps_wizard/guide.md
new file mode 100644
index 0000000..4623889
--- /dev/null
+++ b/src/node_modules/steps_wizard/guide.md
@@ -0,0 +1,89 @@
+# Steps Wizard Developer Guide
+
+This guide is intended for developers working with the steps wizard component to understand its structure, usage, and available statuses.
+
+---
+
+## Overview
+
+The steps wizard is a UI component that shows progress across multiple steps of a process. Each step has a status (e.g., pending, completed, error), and may include additional information such as optional flags or errors.
+
+---
+
+## Status Types
+
+Each step can have one of the following statuses:
+
+### 1. `pending(default status)`
+- Default status.
+- Appears clickable if it’s the first step or the previous one is `completed` or `optional`.
+- Greyed out if it's not reachable yet.
+
+### 2. `optional`
+- Styled with **yellow** background and border.
+- Allows skipping the step and still moving forward to the next.
+- The step number is highlighted in yellow.
+- Used when the step is not required to continue the process.
+
+### 3. `error`
+- Styled with **red** background and border.
+- Indicates invalid or incomplete user input.
+- Clicking it should bring focus to the error area and optionally display a tooltip/message.
+
+### 4. `completed`
+- Styled with **green** background and border.
+- The step number is replaced with a ✔️ tick mark.
+- User can click to revisit or edit the completed step.
+
+---
+
+## Step Accessibility Rules
+
+1. **First step** is always active/clickable unless disabled explicitly.
+2. A step becomes **clickable** if:
+ - The previous step is `completed` or `optional`.
+3. A step is **disabled** (unclickable) if:
+ - It is not the current or next possible step.
+ - It hasn't yet met conditions to be accessed.
+ - It appears grey with reduced opacity.
+
+---
+
+## Interactivity
+
+Clicking on a step will:
+- Change its status to `completed` (with green tick).
+- Move to the next step (if available).
+- Allow backward navigation to completed steps.
+
+---
+
+## 🧪 Example Status Flow
+
+| Step | Status | Notes |
+|------|-------------|---------------------------------------------------|
+| 1 | `completed` | Shows tick, clickable to revisit |
+| 2 | `optional` | Clickable, yellow style, can skip |
+| 3 | `pending` | Clickable if step 2 is `completed` or `optional` |
+| 4 | `error` | Red border, must fix before proceeding |
+
+---
+
+## Style Notes
+
+| Status | Color | Icon/Text |
+|------------|-----------|-----------------------|
+| pending | Grey | Number inside circle |
+| optional | Yellow | Number + `*` suffix |
+| error | Red | Number + error style |
+| completed | Green | ✔️ tick instead of number |
+
+---
+
+## Example Step HTML
+
+```html
+
+```
\ No newline at end of file
diff --git a/src/node_modules/steps_wizard/steps_wizard.js b/src/node_modules/steps_wizard/steps_wizard.js
index edf793f..1dfadcf 100644
--- a/src/node_modules/steps_wizard/steps_wizard.js
+++ b/src/node_modules/steps_wizard/steps_wizard.js
@@ -2,42 +2,97 @@ const STATE = require('STATE')
const statedb = STATE(__filename)
const { sdb, get } = statedb(fallback_module)
-const actions = require('actions')
-
-
module.exports = steps_wizard
-async function steps_wizard (opts) {
+async function steps_wizard (opts, protocol) {
const { id, sdb } = await get(opts.sid)
const {drive} = sdb
+
const on = {
style: inject
}
+ let variables = []
+
+ let _ = null
+ if(protocol){
+ send = protocol(msg => onmessage(msg))
+ _ = { up: send }
+ }
+
const el = document.createElement('div')
const shadow = el.attachShadow({ mode: 'closed' })
shadow.innerHTML = `
`
const style = shadow.querySelector('style')
- const main = shadow.querySelector('.main')
- const actions_slot = shadow.querySelector('.actions-slot')
-
-
+ const steps_entries = shadow.querySelector('.steps-slot')
+
const subs = await sdb.watch(onbatch)
- let actions_el = null
+ // for demo purpose
+ render_steps([
+ {name: "Optional Step", "type": "optional", "is_completed": false, "component": "form_input", "status": "default", "data": ""},
+ {name: "Step 2", "type": "mandatory", "is_completed": false, "component": "form_input", "status": "default", "data": ""}
+ ])
- actions_el = await actions(subs[0])
- actions_slot.replaceWith(actions_el)
-
return el
+ function onmessage ({ type, data }) {
+ console.log('steps_ data', type, data)
+ if (type === 'init_data') {
+ variables = data
+ render_steps(variables)
+ }
+ }
+
+ function render_steps(steps) {
+ if (!steps)
+ return;
+
+ steps_entries.innerHTML = '';
+
+ steps.forEach((step, index) => {
+ const btn = document.createElement('button')
+ btn.className = 'step-button'
+ btn.textContent = step.name + (step.type === 'optional' ? ' *' : '')
+ btn.setAttribute('data-step', index + 1)
+
+ const accessible = can_access(index, steps)
+
+ let status = 'default'
+ if (!accessible) status = 'disabled'
+ else if (step.is_completed) status = 'completed'
+ else if (step.status === 'error') status = 'error'
+ else if (step.type === 'optional') status = 'optional'
+
+ btn.classList.add(`step-${status}`)
+
+ btn.onclick = async () => {
+ console.log('Clicked:', step)
+ _?.up({type: 'step_clicked', data: {...step, index, total_steps: steps.length, is_accessible: accessible}})
+ };
+
+ steps_entries.appendChild(btn)
+ });
+
+ }
+
+ function can_access(index, steps) {
+ for (let i = 0; i < index; i++) {
+ if (!steps[i].is_completed && steps[i].type !== 'optional') {
+ return false
+ }
+ }
+
+ return true
+ }
+
async function onbatch(batch) {
for (const { type, paths } of batch){
const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw)))
@@ -51,46 +106,20 @@ async function steps_wizard (opts) {
return document.createElement('style').textContent = data[0]
})())
}
+
}
function fallback_module () {
return {
- api: fallback_instance,
- _: {
- 'actions': {
- $: ''
- },
- }
+ api: fallback_instance
}
function fallback_instance () {
return {
- _: {
- 'actions': {
- 0: '',
- mapping: {
- 'style': 'style',
- 'actions': 'actions',
- 'icons': 'icons',
- 'hardcons': 'hardcons'
- }
- }
- },
drive: {
'style/': {
'stepswizard.css': {
- raw: `
- .steps-wizard {
- display: flex;
- flex-direction: column;
- width: 100%;
- height: 100%;
- background: #131315;
- }
- .space{
- height: inherit;
- }
- `
+ '$ref': 'stepswizard.css'
}
}
}
diff --git a/src/node_modules/steps_wizard/stepswizard.css b/src/node_modules/steps_wizard/stepswizard.css
new file mode 100644
index 0000000..a2da4a8
--- /dev/null
+++ b/src/node_modules/steps_wizard/stepswizard.css
@@ -0,0 +1,114 @@
+.steps-wizard {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
+ background: #131315;
+}
+.space{
+ height: inherit;
+}
+.steps-slot {
+ display: flex;
+ gap: 8px;
+ padding: 5px;
+}
+.step-button {
+ position: relative;
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ font-size: 16px;
+ padding: 10px 16px 10px 44px;
+ margin: 10px 0;
+ border-radius: 12px;
+ font-weight: 500;
+ transition: background-color 0.3s;
+ width: fit-content;
+ border: 2px solid;
+}
+
+.step-button:before {
+ content: attr(data-step);
+ position: absolute;
+ left: 12px;
+ width: 20px;
+ height: 20px;
+ border: 1px solid white;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: bold;
+ font-size: 12px;
+}
+
+/* Default (active step) */
+.step-default {
+ background-color: #224B24;
+ border-color: #299910;
+ color: white;
+}
+.step-default:before {
+ background-color: #224B24;
+ color: white;
+ border: 1px solid white;
+}
+
+/* Optional (yellow) */
+.step-optional {
+ background-color: #4b3f22;
+ border-color: #d0a510;
+ color: #f4c842;
+}
+.step-optional:before {
+ background-color: #4b3f22;
+ border-color: #d0a510;
+ color: #f4c842;
+}
+
+/* Error (red) */
+.step-error {
+ background-color: #4b1e1e;
+ border-color: #e53935;
+ color:rgb(248, 68, 65);
+}
+.step-error:before {
+ background-color: #4b1e1e;
+ border-color: #e53935;
+ color: rgb(248, 68, 65);
+}
+
+/* Completed (green with checkmark) */
+.step-completed {
+ background-color: #224B24;
+ border-color: #299910;
+ color: white;
+}
+.step-completed:before {
+ content: "✔";
+ background-color: #224B24;
+ border: 1px solid white;
+ color: white;
+}
+
+/* Disabled (gray) */
+.step-disabled {
+ background-color: #444;
+ border-color: #444;
+ color: #ccc;
+}
+.step-disabled:before {
+ background-color: #444;
+ border-color: #ccc;
+ color: #ccc;
+}
+
+/* Visibility */
+.hide {
+ display: none;
+}
+
+.show {
+ display: block;
+}
\ No newline at end of file
diff --git a/web/page.js b/web/page.js
index c862402..25dc1f3 100644
--- a/web/page.js
+++ b/web/page.js
@@ -19,6 +19,7 @@ const task_manager = require('../src/node_modules/task_manager')
const quick_actions = require('../src/node_modules/quick_actions')
const graph_explorer = require('../src/node_modules/graph_explorer')
const editor = require('../src/node_modules/quick_editor')
+const program = require('../src/node_modules/program')
const steps_wizard = require('../src/node_modules/steps_wizard')
const imports = {
@@ -34,6 +35,7 @@ const imports = {
task_manager,
quick_actions,
graph_explorer,
+ program,
steps_wizard,
}
config().then(() => boot({ sid: '' }))
@@ -295,7 +297,8 @@ function fallback_module () {
'../src/node_modules/task_manager',
'../src/node_modules/quick_actions',
'../src/node_modules/graph_explorer',
- '../src/node_modules/steps_wizard'
+ '../src/node_modules/program',
+ '../src/node_modules/steps_wizard',
]
const subs = {}
names.forEach(subgen)
@@ -309,6 +312,21 @@ function fallback_module () {
'style': 'style'
}
}
+ subs['../src/node_modules/program'] = {
+ $: '',
+ 0: '',
+ mapping: {
+ 'variables': 'variables',
+ 'style': 'style'
+ }
+ }
+ subs['../src/node_modules/steps_wizard'] = {
+ $: '',
+ 0: '',
+ mapping: {
+ 'style': 'style'
+ }
+ }
subs['../src/node_modules/tabsbar'] = {
$: '',
0: '',