diff --git a/src/mesh/api.js b/src/mesh/api.js index d29ebd00..7a146556 100644 --- a/src/mesh/api.js +++ b/src/mesh/api.js @@ -1352,6 +1352,10 @@ const api = exports({ window.open("https://www.paypal.com/paypalme/gridspace3d"); }, + gridspace() { + window.open("https://grid.space"); + }, + kirimoto() { window.location = "/kiri"; }, diff --git a/src/mesh/build.js b/src/mesh/build.js index 188d2a76..5a578fc6 100644 --- a/src/mesh/build.js +++ b/src/mesh/build.js @@ -341,34 +341,158 @@ api.settings = function() { // create html elements function ui_build() { - const trash = FontAwesome.icon({ prefix: "fas", iconName: "trash" }).html[0]; - const eye_open = FontAwesome.icon({ prefix: "fas", iconName: "eye" }).html[0]; - const eye_closed = FontAwesome.icon({ prefix: "fas", iconName: "eye-slash" }).html[0]; + let { file, selection, mode, tool, sketch, prefs, add } = api; + let trash = FontAwesome.icon({ prefix: "fas", iconName: "trash" }).html[0]; + let eye_open = FontAwesome.icon({ prefix: "fas", iconName: "eye" }).html[0]; + let eye_closed = FontAwesome.icon({ prefix: "fas", iconName: "eye-slash" }).html[0]; - // app name and drop menu - h.bind($('app-info'), [ - h.div({ id: "app-name", _: "Mesh:Tool" }), - h.div({ id: "app-menu" }, [ - h.div({ _: "help", onclick: api.help }), - h.hr(), - // h.div({ _: "version", onclick: api.version }), - h.div({ _: "donate", onclick: api.donate }), - h.div({ _: "slicer", onclick: api.kirimoto }), - h.hr(), - h.div({ id: "app-vers", _: mesh.version }), - ]) + // top left drop menus + h.bind($('top-left'), [ + h.div({ class: "menu" }, [ + h.div('File'), + h.div({ class: "menu-items" }, [ + h.div({ _: 'Import', onclick: file.import }, [ + h.input({ + id: "import", type: "file", class: ["hide"], multiple: true, + onchange(evt) { broker.send.load_files(evt.target.files) } + }) + ]), + h.div({ _: 'Export', onclick: file.export }), + h.hr(), + h.div({ _: "Slicer", onclick: api.kirimoto }), + h.hr(), + h.div({ _: 'Close', onclick: window.close }), + ]) + ]), + h.div({ class: "menu sketch-on" }, [ + h.div('Edit'), + h.div({ class: "menu-items" }, [ + h.div({ _: 'Add Circle', onclick() { sketch.selected.one?.add.circle() } }), + h.div({ _: 'Add Rectangle', onclick() { sketch.selected.one?.add.rectangle() } }), + h.hr(), + h.div({ _: 'Add Sketch', onclick: add.sketch }), + h.hr(), + h.div({ _: 'Delete', onclick: api.selection.delete }), + ]) + ]), + h.div({ class: "menu sketch-off" }, [ + h.div('Edit'), + h.div({ class: "menu-items" }, [ + h.div({ _: 'Add Cylinder', onclick() { add.cylinder() } }), + h.div({ _: 'Add Cube', onclick() { add.cube() } }), + h.div({ _: 'Add Gear', onclick() { add.gear() } }), + h.hr(), + h.div({ _: 'Add Sketch', onclick: add.sketch }), + devel ? h.div({ _: 'Add Vertices', onclick: add.input }) : undefined, + h.hr(), + h.div({ _: 'Delete', onclick: api.selection.delete }), + ]) + ]), + h.div({ class: "menu" }, [ + h.div('View'), + h.div({ class: "menu-items" }, [ + h.div({ _: 'Bounds', onclick() { selection.boundsBox({ toggle: true }) } }), + h.div({ _: 'Normals', onclick() { api.normals() } }), + h.div({ _: 'Wireframes', onclick() { api.wireframe() } }), + h.hr(), + h.div({ _: 'Gridlines', onclick() { api.grid() } }), + h.div({ _: 'Messages', onclick() { api.log.toggle({ spinner: false }) } }), + ]) + ]), + h.div({ class: "menu" }, [ + h.div('Mode'), + h.div({ class: "menu-items" }, [ + h.div({ _: 'Sketch', id: "mode-sketch", onclick: mode.sketch }), + h.hr(), + h.div({ _: 'Object', id: "mode-object", onclick: mode.object }), + h.div({ _: 'Tool', id: "mode-tool", onclick: mode.tool }), + h.hr(), + h.div({ _: 'Surface', id: "mode-surface", onclick: mode.surface }), + h.div({ _: 'Face', id: "mode-face", onclick: mode.face }), + h.div({ _: 'Edge', id: "mode-edge", onclick: mode.edge }), + ]) + ]), + h.div({ class: "menu sketch-on" }, [ + h.div('Object'), + h.div({ class: "menu-items" }, [ + h.div({ _: 'Group', onclick: sketch.arrange.group }), + h.div({ _: 'Ungroup', onclick: sketch.arrange.ungroup }), + h.hr(), + h.div({ _: 'Center', onclick: sketch.arrange.center }), + h.hr(), + h.div({ _: 'Flip Horizontal', onclick: sketch.arrange.fliph }), + h.div({ _: 'Flip Vertical', onclick: sketch.arrange.flipv }), + h.hr(), + h.div({ _: 'Raise', onclick: sketch.arrange.up }), + h.div({ _: 'Lower', onclick: sketch.arrange.down }), + h.div({ _: 'Raise to Top', onclick: sketch.arrange.top }), + h.div({ _: 'Lower to Bottom', onclick: sketch.arrange.bottom }), + ]) + ]), + h.div({ class: "menu sketch-off" }, [ + h.div('Object'), + h.div({ class: "menu-items" }, [ + h.div({ _: 'Regroup', onclick: tool.regroup }), + h.div({ _: 'Isolate', onclick: tool.isolate }), + h.hr(), + h.div({ _: 'Duplicate', onclick: tool.duplicate }), + h.div({ _: 'Mirror', onclick: tool.mirror }), + h.div({ _: 'Merge', onclick: tool.merge }), + h.div({ _: 'Split', onclick: call.edit_split }), + h.hr(), + h.div({ _: 'Union', onclick: tool.union }), + h.div({ _: 'Subtract', onclick: tool.subtract }), + h.div({ _: 'Intersect', onclick: tool.intersect }), + h.div({ _: 'Difference', onclick: tool.difference }), + h.hr(), + h.div({ _: 'Copy Circle', onclick: api.pattern.circle }), + h.div({ _: 'Copy Grid', onclick: api.pattern.grid }), + ]) + ]), + h.div({ class: "menu sketch-off" }, [ + h.div('Faces'), + h.div({ class: "menu-items" }, [ + h.div({ _: 'Flip Normals', onclick: tool.invert }), + h.div({ _: 'Analyze', onclick: tool.analyze }), + // h.div({ _: 'Patch', onclick: tool.repair }), + // h.div({ _: 'rebuild', onclick: tool.rebuild }), + h.div({ _: 'Clean', onclick: tool.clean }), + ]) + ]), + h.div({ class: "menu sketch-on" }, [ + h.div('Shape'), + h.div({ class: "menu-items" }, [ + h.div({ _: 'Duplicate', onclick: api.tool.duplicate }), + h.div({ _: 'Extrude', onclick: sketch.extrude }), + h.hr(), + h.div({ _: 'Union', onclick: sketch.boolean.union }), + h.div({ _: 'Intersect', onclick: sketch.boolean.intersect }), + h.div({ _: 'Difference', onclick: sketch.boolean.difference }), + h.hr(), + h.div({ _: 'Nest', onclick: sketch.boolean.nest }), + h.div({ _: 'Flatten', onclick: sketch.boolean.flatten }), + h.div({ _: 'Even Odd', onclick: sketch.boolean.evenodd }), + ]) + ]), + h.div({ class: "menu" }, [ + h.div('Help'), + h.div({ class: "menu-items" }, [ + h.div({ _: 'About', onclick() { api.welcome(mesh.version) } }), + h.hr(), + h.div({ _: 'Documentation', onclick: api.help }), + h.div({ _: 'Official Site', onclick: api.gridspace }), + h.div({ _: "Donate", onclick: api.donate }), + h.hr(), + h.div({ _: 'Versions', onclick: api.version }), + ]) + ]), ]); - // add a help button + // add help buttons h.bind($('top-right'), [ - h.div({ id: "top-version", onclick: api.version }, [ - h.div({ class: "fas fa-hashtag" }), - ]), h.div({ id: "top-settings", onclick: api.settings }, [ - h.div({ class: "fas fa-sliders-h" }), - ]), - h.div({ id: "top-help", onclick: api.help }, [ - h.div({ class: "far fa-question-circle" }), + h.div({ class: "fas fa-gear" }), + h.div('Settings') ]), ]); @@ -390,185 +514,17 @@ function ui_build() { h.div({ id: 'spinner', class: 'spin' }) ]), // display and action areas - h.div({ id: 'modes' }), - h.div({ id: 'actions' }), h.div({ id: 'grouplist'}), h.div({ id: 'selectlist'}), h.div({ id: 'logger', onmouseover() { log.show() } }), h.div({ id: 'download', class: "hide" }) ]); - let { modes, actions, grouplist, selectlist, logger } = bound; + let { actions, grouplist, selectlist, logger } = bound; // create slid in/out logging window h.bind(logger, [ h.div({ id: 'logtext' }) ]); - // a few shortcuts to api calls - let { file, selection, mode, tool, sketch, prefs, add } = api; - - // top/center mode selector - h.bind(modes, [ h.div([ - h.button({ _: 'object', id: "mode-object", onclick() { mode.object() } }), - h.button({ _: 'tool', id: "mode-tool", onclick() { mode.tool() } }), - ]), h.div([ - h.button({ _: 'surface', id: "mode-surface", onclick() { mode.surface() } }), - h.button({ _: 'face', id: "mode-face", onclick() { mode.face() } }), - h.button({ _: 'edge', id: "mode-edge", onclick() { mode.edge() } }), - ]), h.div([ - h.button({ _: 'sketch', id: "mode-sketch", onclick() { mode.sketch() } }), - ]) ]); - - // create hotkey/action menu (top/left) - h.bind(actions, [ - h.div([ - // create and bind file loading elements - h.div({ _: "file", class: "head" }), - h.div({ class: "back"}), - h.div({ class: "pop"}, [ - h.button({ _: 'import', onclick: file.import }, [ - h.input({ - id: "import", type: "file", class: ["hide"], multiple: true, - onchange(evt) { broker.send.load_files(evt.target.files) } - }) - ]), - h.button({ _: 'export', onclick: file.export }), - ]) - ]), - h.div({ class: "sketch-off" }, [ - h.div({ _: "add", class: "head" }), - h.div({ class: "back"}), - h.div({ class: "pop"}, [ - h.button({ _: 'cylinder', onclick() { add.cylinder() } }), - h.button({ _: 'cube', onclick() { add.cube() } }), - h.button({ _: 'gear', onclick() { add.gear() } }), - h.hr(), - h.button({ _: 'sketch', onclick() { add.sketch() } }), - devel ? h.button({ _: 'input', onclick: add.input }) : undefined - ]) - ]), - h.div({ class: "sketch-on" }, [ - h.div({ _: "add", class: "head" }), - h.div({ class: "back"}), - h.div({ class: "pop"}, [ - h.button({ _: 'circle', onclick() { add.circle() } }), - h.button({ _: 'rectangle', onclick() { add.rectangle() } }), - // h.button({ _: 'gear', onclick() { add.gear() } }), - h.hr(), - h.button({ _: 'sketch', onclick() { add.sketch() } }), - ]) - ]), - h.div([ - h.div({ _: "view", class: "head" }), - h.div({ class: "back"}), - h.div({ class: "pop"}, [ - h.button({ _: 'bounds', onclick() { selection.boundsBox({ toggle: true }) } }), - h.button({ _: 'normals', onclick() { api.normals() } }), - h.button({ _: 'gridlines', onclick() { api.grid() } }), - h.button({ _: 'wireframe', onclick() { api.wireframe() } }), - ]) - ]), - h.div({ class: "sketch-off" }, [ - h.div({ _: "boolean", class: "head" }), - h.div({ class: "back"}), - h.div({ class: "pop"}, [ - h.button({ _: 'union', onclick: tool.union }), - h.button({ _: 'subtract', onclick: tool.subtract }), - h.button({ _: 'intersect', onclick: tool.intersect }), - h.button({ _: 'difference', onclick: tool.difference }), - ]) - ]), - h.div({ class: "sketch-on" }, [ - h.div({ _: "arrange", class: "head" }), - h.div({ class: "back"}), - h.div({ class: "pop"}, [ - h.button({ _: 'group', onclick: sketch.arrange.group }), - h.button({ _: 'ungroup', onclick: sketch.arrange.ungroup }), - h.hr(), - h.button({ _: 'center', onclick: sketch.arrange.center }), - h.hr(), - h.button({ _: 'flip-h', onclick: sketch.arrange.fliph }), - h.button({ _: 'flip-v', onclick: sketch.arrange.flipv }), - h.hr(), - h.button({ _: 'down', onclick: sketch.arrange.down }), - h.button({ _: 'up', onclick: sketch.arrange.up }), - h.hr(), - h.button({ _: 'top', onclick: sketch.arrange.top }), - h.button({ _: 'bottom', onclick: sketch.arrange.bottom }), - ]) - ]), - h.div({ class: "sketch-on" }, [ - h.div({ _: "boolean", class: "head" }), - h.div({ class: "back"}), - h.div({ class: "pop"}, [ - h.button({ _: 'union', onclick: sketch.boolean.union }), - h.button({ _: 'intersect', onclick: sketch.boolean.intersect }), - h.button({ _: 'difference', onclick: sketch.boolean.difference }), - h.hr(), - h.button({ _: 'even/odd', onclick: sketch.boolean.evenodd }), - h.button({ _: 'nest', onclick: sketch.boolean.nest }), - h.button({ _: 'flatten', onclick: sketch.boolean.flatten }), - ]) - ]), - h.div({ class: "sketch-off" }, [ - h.div({ _: "pattern", class: "head" }), - h.div({ class: "back"}), - h.div({ class: "pop"}, [ - h.button({ _: 'circular', onclick() { api.pattern.circle() } }), - h.button({ _: 'grid', onclick() { api.pattern.grid() } }), - ]) - ]), - // h.div({ class: "sketch-on" }, [ - // h.div({ _: "pattern", class: "head" }), - // h.div({ class: "back"}), - // h.div({ class: "pop"}, [ - // h.button({ _: 'circular', onclick() { sketch.pattern.circle() } }), - // h.button({ _: 'grid', onclick() { sketch.pattern.grid() } }), - // ]) - // ]), - h.div({ class: "sketch-on" }, [ - h.div({ _: "extrude", class: "head" }), - h.div({ class: "back"}), - h.div({ class: "pop"}, [ - h.button({ _: 'sketch', onclick() { sketch.extrude() } }), - h.button({ _: 'selection', onclick() { sketch.extrude({ selection: true }) } }), - ]) - ]), - h.div({ class: "sketch-off" }, [ - h.div({ _: "models", class: "head" }), - h.div({ class: "back"}), - h.div({ class: "pop"}, [ - h.button({ _: 'regroup', onclick: tool.regroup }), - h.span(), - h.button({ _: 'duplicate', onclick: tool.duplicate }), - h.button({ _: 'mirror', onclick: tool.mirror }), - h.button({ _: 'merge', onclick: tool.merge }), - h.button({ _: 'split', onclick: call.edit_split }), - h.span(), - h.button({ _: 'delete', onclick: api.selection.delete }), - ]) - ]), - h.div({ class: "sketch-off" }, [ - h.div({ _: "faces", class: "head" }), - h.div({ class: "back"}), - h.div({ class: "pop"}, [ - h.button({ _: 'invert\nnormals', onclick: tool.invert }), - h.span(), - h.button({ _: 'delete', onclick: api.selection.delete }), - ]) - ]), - h.div({ class: "sketch-off" }, [ - h.div({ _: "repair", class: "head" }), - h.div({ class: "back"}), - h.div({ class: "pop"}, [ - h.button({ _: 'analyze', onclick: tool.analyze }), - h.button({ _: 'isolate', onclick: tool.isolate }), - h.button({ _: 'patch', onclick: tool.repair }), - // h.button({ _: 'rebuild', onclick: tool.rebuild }), - h.button({ _: 'clean', onclick: tool.clean }), - ]) - ]), - ]); - // for group/model/box/area/mesh dashboard grids function grid(v1, v2, side = [ "pos", "rot"], top = [ "X", "Y", "Z" ], root) { let v = 'val', l = 'lbl'; diff --git a/web/mesh/index.css b/web/mesh/index.css index cffe2b24..f1e58484 100644 --- a/web/mesh/index.css +++ b/web/mesh/index.css @@ -1,11 +1,6 @@ :root { - --fg-gray: #495057; - --bg-top: rgba(255,255,255,0.55); - --bg-smoke: rgba(0,0,0,0.25); - --dark-bg-smoke: rgba(255,255,255,0.25); - --hover-menu: white; - --dark-hover-menu: #888; - --gradbar: linear-gradient(to right, #ddd, #666, #ddd); + --menu-blue: #007aff; + --menu-back: #ddd; } @font-face { @@ -15,39 +10,10 @@ /** dark overrides */ -.dark #logger, .dark #modal_frame, .dark #actions > div, .dark #grouplist > div, .dark #actions .pop { +.dark #logger, .dark #modal_frame, .dark #actions > div, .dark #grouplist > div { border: 1px solid #999; } -.dark #actions > div { - background-color: rgba(255,255,255,0.6); -} - -.dark #top-right > div { - color: black; -} - -.dark #top-right > div:hover { - color: white; -} - -.dark #app-info:hover { - background-color: var(--dark-hover-menu); -} - -.dark #app-info:hover { - background-color: var(--dark-bg-smoke); -} - -.dark #app-menu { - background-color: var(--dark-hover-menu); - border: 1px solid black; -} - -.dark #app-menu > div:hover { - background-color: var(--dark-bg-smoke); -} - /** defaults */ a, a:hover, a:visited { @@ -57,6 +23,7 @@ a, a:hover, a:visited { } body, div { + font-family: system-ui, Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; position: relative; display: flex; padding: 0; @@ -93,60 +60,6 @@ input { top: 0; } -#app-info { - top: 50%; - left: 50%; - padding-left: 10px !important; - padding-right: 10px !important; - transform: translateX(-50%) translateY(-50%); - flex-direction: column; - align-items: center; - user-select: none; -} - -#app-name { - flex-direction: column; - align-items: center; - width: 100%; -} - -#app-info:hover { - border-left: 1px solid rgba(0,0,0,0.5); - border-right: 1px solid rgba(0,0,0,0.5); - background-color: var(--bg-smoke); -} - -#app-info:hover #app-menu { - display: flex; -} - -#app-menu { - display: none; - font-size: smaller; - position: absolute; - align-items: stretch; - flex-direction: column; - background-color: var(--hover-menu); - white-space: nowrap; - border: 1px solid rgba(0,0,0,0.5); - border-top: 1px dashed !important; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; - padding: 3px; - width: calc(100% - 6px); - top: 100%; - gap: 3px; -} - -#app-menu > div { - flex-direction: column; - align-items: center; -} - -#app-menu > div:hover { - background-color: var(--bg-smoke); -} - #app-body { flex-grow: 1; } @@ -181,17 +94,27 @@ input { overflow: hidden; } +.dark #top { + color: #eee; + background-color: rgba(80,80,80,0.75); +} + #top { z-index: 50; + color: #000; + font-size: smaller; flex-direction: row; - font-family: 'Russo One', sans-serif; - background-color: var(--bg-top); + background-color: rgba(255,255,255,0.55); +} + +.dark #top-sep { + background-image: linear-gradient(to right, #666, #aaa, #666); } #top-sep { z-index: 2; height: 1px; - background-image: var(--gradbar); + background-image: linear-gradient(to right, #ddd, #666, #ddd); } #top > div { @@ -244,25 +167,76 @@ button:hover { height: 5px; } -#top-right { +#top-left { + margin-left: 5px; + flex-direction: row; + padding: 0 !important; +} + +.menu { + cursor: default; + align-self: stretch; + align-items: center; + padding: 5px 12px 5px 12px; + border-radius: 6px; + white-space: nowrap; + user-select: none; +} + +.dark .menu hr { + border-top: 0.5px solid rgba(255,255,255,0.5); +} + +.menu:hover, .menu-items > div:hover { + background-color: var(--menu-blue); +} + +.menu:hover .menu-items { + display: flex; +} + +.dark .menu-items { + background: #666; + border: 1px solid #999; +} + +.menu-items { + display: none; position: absolute; - padding: 3px; - bottom: 0; - right: 3px; - top: 0; - gap: 8px; + flex-direction: column; + border: 1px solid #bbb; + border-radius: 6px; + background: #eee; + padding: 4px; + top: 100%; + left: 0; + gap: 6px; +} + +.menu-items > div { + border-radius: 6px; + padding: 4px 8px 4px 8px; +} + +#top-mid { + flex-grow: 1; +} + +#top-right { + flex-direction: row; + margin-right: 5px; + padding: 0 !important; } #top-right > div { - flex-direction: column; - justify-content: center; + gap: 8px; + align-self: stretch; align-items: center; - flex-direction: row; - color: #888; -} - -#top-right > div:hover { - color: black; + padding: 5px 12px 5px 12px; + border-radius: 6px; + cursor: default; + user-select: none; + white-space: nowrap; } /** specific modal dialogs */ @@ -301,9 +275,9 @@ button:hover { grid-template-columns: 1fr 50px; } -.addgear hr { +/* .addgear hr { width: 100%; -} +} */ .addgear button, .addgear hr, .addgear code { grid-column: span 2; @@ -487,11 +461,11 @@ button:hover { border-bottom: 1px dashed gray; } -button.selected { +.menu .selected { background-color: rgba(0,255,0,0.5); } -button.selected:hover { +.menu .selected:hover { background-color: rgba(0,255,0,0.8); } @@ -499,27 +473,6 @@ button.selected:hover { color: #000 !important; } -/** modes menu top/center **/ - -#modes { - position: absolute; - gap: 2px; - top: 0; - left: 50%; - transform: translateX(-50%); -} - -#modes > div { - align-items: center; - justify-content: center; - background-color: rgba(255,255,255,0.5); - border: 1px solid rgba(0,0,0,0.5); - border-radius: 3px; - margin-top: 2px; - padding: 3px; - gap: 2px; -} - /** slide in/out logging window **/ #logger { @@ -557,85 +510,6 @@ button.selected:hover { border: 3px solid rgba(255,255,255,0.1); } -/** action and keyboard menu **/ - -#actions { - flex-direction: column; - position: absolute; - margin: 3px 0 0 3px; - left: 0; - top: 0; - gap: 1px; -} - -#actions .pop { - display: none; - position: absolute; - align-items: center; - justify-content: center; - flex-direction: column; - transform: translateX(100%); - right: -3px; - top: -1px; - gap: 3px; - margin: 0; - padding: 3px; - border-radius: 3px; - border: 1px solid gray; - background-color: rgba(255,255,255,0.5); -} - -#actions .back { - position: absolute; - top: 0; - left: 0; - right: -4px; - bottom: 0; -} - -.pop button { - width: 100%; -} - -.pop span { - margin: 1px; - height: 1px; - width: 80%; - background-color: rgba(0,0,0,0.5); -} - -.dark #actions > div:hover { - background-color: #bbb; -} - -#actions > div:hover { - background-color: #eee; -} - -#actions > div:hover .pop { - display: flex; -} - -#actions > div { - flex-direction: column; - padding: 5px; - gap: 2px; -} - -#actions .head { - color: #444; - align-items: center; - justify-content: center; - font-family: 'Russo One', monospace; - font-weight: lighter; - font-size: smaller; - border-radius: 3px; -} - -#actions .head:hover { - user-select: none; -} - /** group/model buttons top/right of workspace **/ #grouplist { diff --git a/web/mesh/index.html b/web/mesh/index.html index 924bd49e..f7a692d9 100644 --- a/web/mesh/index.html +++ b/web/mesh/index.html @@ -24,7 +24,7 @@
-
+