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) {
/ +
@@ -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) {
/ +
@@ -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: '',