Compare commits

..

No commits in common. "899444e815c59995a2cc731813784504ac88fe45" and "edce32888094b2002b62a3f36d3551de1c10895d" have entirely different histories.

48 changed files with 269 additions and 5771 deletions

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 72 70" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.093797,0,0,1.093797,0.640357,-0.002979)">
<path d="M39.703,63.992C33.273,63.965 32.923,63.847 31.428,63.333C28.308,62.274 25.677,60.059 24.093,57.166L23.605,56.273L22.819,56.189C21.521,56.058 19.247,55.63 17.735,55.249C11.032,53.558 6.21,50.355 4.507,46.45C4.352,46.116 4.138,45.39 4.031,44.83C3.459,41.997 4.555,39.068 7.079,36.734C7.626,36.222 7.876,35.877 8.031,35.424C8.138,35.091 8.341,34.638 8.46,34.436C8.686,34.067 8.686,34.055 8.341,33.305C7.4,31.269 7.388,29.197 8.329,27.244L8.686,26.506L8.4,25.947C7.698,24.577 7.436,22.732 7.757,21.386L7.9,20.767L7.103,20.041C4.519,17.636 3.4,14.6 4.066,11.718C4.781,8.539 7.198,5.896 11.389,3.681C15.104,1.728 19.473,0.585 25.236,0.073C26.51,-0.046 31.999,0.109 33.488,0.3C40.929,1.252 46.93,3.824 50.157,7.467C53.669,11.432 53.36,16.338 49.371,20.041L48.573,20.767L48.716,21.386C49.038,22.744 48.776,24.565 48.061,25.97C47.775,26.554 47.775,26.566 48.002,26.935C48.537,27.792 48.799,28.876 48.799,30.245C48.799,30.465 48.795,30.668 48.786,30.858C47.381,30.973 46.02,31.246 44.722,31.661C45.016,31.051 45.191,29.981 45.025,29.697C44.93,29.518 44.858,29.53 44.43,29.828C41.763,31.638 38.048,32.948 33.547,33.65C32.059,33.876 31.285,33.924 28.237,33.924C25.189,33.924 24.415,33.876 22.926,33.65C18.545,32.959 14.878,31.709 12.294,29.995C11.484,29.459 11.496,29.459 11.413,29.804C11.222,30.554 11.639,31.709 12.425,32.686C13.687,34.257 16.783,35.865 20.152,36.734C25.553,38.129 31.889,38.015 36.956,36.571C35.707,37.901 34.654,39.418 33.845,41.075C32.625,41.258 31.365,41.384 30.082,41.449L28.499,41.532L29.237,42.33C29.654,42.771 30.308,43.58 30.689,44.128C31.344,45.069 31.404,45.128 31.785,45.069C31.904,45.049 32.16,45.014 32.465,44.973C32.161,46.294 32,47.67 32,49.083C32,51.584 32.504,53.969 33.415,56.141C33.042,56.019 32.813,55.848 32.404,55.606C31.63,55.141 30.725,54.118 30.344,53.26C30.213,52.963 29.951,52.058 29.749,51.26C28.82,47.45 26.891,44.664 23.688,42.509C22.272,41.556 21.39,41.163 18.414,40.187C15.759,39.306 13.496,38.317 12.294,37.496C11.484,36.948 11.508,36.948 11.413,37.424C11.175,38.71 12.294,40.437 14.223,41.747C15.342,42.497 17.259,43.366 19.176,43.997C21.188,44.652 22.2,45.176 23.224,46.069C24.927,47.545 25.546,48.76 26.189,51.808C26.76,54.522 27.57,56.082 29.213,57.642C30.356,58.725 31.416,59.38 32.797,59.845L33.797,60.19L35.768,60.196C36.883,61.649 38.21,62.93 39.703,63.992ZM10.186,43.271C9.71,42.794 9.115,42.08 8.841,41.675L8.365,40.949L8.019,41.687C7.329,43.175 7.662,44.997 8.889,46.462C10.913,48.867 15.223,50.962 20.152,51.927C22.724,52.427 22.498,52.415 22.414,52.022C22.105,50.605 21.605,49.617 20.926,49.01C20.283,48.45 19.438,48.057 17.366,47.355C16.366,47.009 15.068,46.509 14.497,46.235C13.008,45.545 11.198,44.295 10.186,43.271ZM12.818,23.601C12.187,23.327 11.603,23.065 11.532,23.041C11.425,22.994 11.413,23.089 11.472,23.387C12.079,26.066 16.437,28.649 22.081,29.673C24.224,30.066 25.534,30.173 28.237,30.173C30.94,30.173 32.249,30.066 34.392,29.673C40.036,28.649 44.394,26.066 45.001,23.387C45.061,23.089 45.049,22.994 44.942,23.041C44.87,23.065 44.287,23.327 43.656,23.601C40.751,24.875 37.274,25.744 32.987,26.268C31.261,26.482 25.212,26.482 23.486,26.268C19.2,25.744 15.723,24.875 12.818,23.601ZM31.19,3.872C23.391,3.241 15.259,5.098 10.663,8.563C9.448,9.48 8.71,10.337 8.138,11.48C7.722,12.302 7.674,12.516 7.674,13.361C7.674,14.635 8.138,15.623 9.305,16.85C13.115,20.886 22.414,23.327 31.309,22.613C38.429,22.041 44.287,19.922 47.168,16.85C48.335,15.623 48.799,14.635 48.799,13.361C48.799,12.516 48.752,12.302 48.335,11.48C47.764,10.337 47.025,9.48 45.811,8.563C42.37,5.979 37.345,4.36 31.19,3.872ZM25.796,7.932C29.999,7.265 34.869,8.527 36.595,10.718C37.357,11.694 37.548,12.218 37.548,13.361C37.548,14.516 37.357,15.04 36.595,16.016C34.488,18.683 28.249,19.791 23.677,18.302C21.355,17.54 20.057,16.576 19.223,14.992C18.795,14.183 18.795,12.552 19.223,11.742C20.259,9.765 22.533,8.444 25.796,7.932ZM30.118,11.67C27.32,11.206 23.915,11.873 22.807,13.099C22.581,13.361 22.581,13.373 22.807,13.635C23.605,14.504 26.082,15.242 28.237,15.242C30.392,15.242 32.868,14.504 33.666,13.635C33.892,13.373 33.892,13.361 33.666,13.099C33.083,12.468 31.797,11.944 30.118,11.67Z"/>
</g>
<g transform="matrix(1,0,0,1,35.1075,-13.675)">
<path d="M19.178,83.675L21.86,83.675C22.896,83.675 23.714,83.056 23.949,82.006L24.677,79.011C25.075,78.891 25.513,78.706 25.906,78.51L28.52,80.101C29.389,80.672 30.396,80.644 31.191,79.849L33.035,77.979C33.748,77.216 33.821,76.234 33.251,75.277L31.691,72.637C31.913,72.276 32.021,71.92 32.166,71.528L35.186,70.826C36.217,70.564 36.892,69.747 36.892,68.705L36.892,66.06C36.892,65.038 36.191,64.272 35.186,64.011L32.197,63.289C32.038,62.825 31.867,62.484 31.722,62.148L33.37,59.446C33.904,58.571 33.804,57.481 33.086,56.8L31.18,54.956C30.427,54.209 29.411,54.031 28.562,54.596L25.906,56.269C25.436,56.028 25.075,55.914 24.683,55.769L23.949,52.722C23.714,51.712 22.896,51.042 21.86,51.042L19.178,51.042C18.131,51.042 17.304,51.737 17.063,52.728L16.381,55.737C15.886,55.903 15.519,56.028 15.101,56.269L12.444,54.596C11.633,54.068 10.585,54.204 9.852,54.956L7.997,56.8C7.279,57.501 7.077,58.571 7.642,59.446L9.316,62.148C9.166,62.484 8.981,62.876 8.81,63.289L5.852,64.011C4.841,64.272 4.177,65.064 4.177,66.06L4.177,68.705C4.177,69.747 4.847,70.564 5.852,70.826L8.847,71.528C9.037,71.92 9.182,72.276 9.342,72.637L7.782,75.302C7.211,76.234 7.311,77.267 8.023,77.979L9.847,79.849C10.585,80.638 11.613,80.646 12.487,80.101L15.158,78.51C15.596,78.757 15.989,78.891 16.381,79.011L17.063,82.006C17.299,83.005 18.131,83.675 19.178,83.675ZM20.491,72.507C17.681,72.507 15.371,70.197 15.371,67.387C15.371,64.551 17.681,62.236 20.491,62.236C23.326,62.236 25.642,64.551 25.642,67.387C25.642,70.197 23.326,72.507 20.491,72.507Z" style="fill-rule:nonzero;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.824327,0,0,0.976141,2.488276,-2.712014)">
<path d="M56.78,22.995C64.017,23.632 69.667,28.805 69.667,35.084L69.667,42.696C69.667,49.405 63.217,54.851 55.273,54.851L52.514,54.851L39.525,64.685C37.406,66.289 34.196,66.289 32.077,64.685L19.088,54.851L16.329,54.851C8.385,54.851 1.935,49.405 1.935,42.696L1.935,35.084C1.935,28.805 7.585,23.632 14.822,22.995L14.822,8.007C14.822,6.476 16.294,5.233 18.107,5.233C19.921,5.233 21.393,6.476 21.393,8.007L21.393,22.929L50.209,22.929L50.209,8.007C50.209,6.476 51.681,5.233 53.495,5.233C55.308,5.233 56.78,6.476 56.78,8.007L56.78,22.995ZM28.932,54.851L35.801,60.052L42.67,54.851L28.932,54.851ZM55.273,49.302C59.591,49.302 63.096,46.342 63.096,42.696L63.096,35.084C63.096,31.438 59.591,28.478 55.273,28.478L16.329,28.478C12.011,28.478 8.506,31.438 8.506,35.084L8.506,42.696C8.506,46.342 12.011,49.302 16.329,49.302L55.273,49.302Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.881796,0,0,0.881796,2.413866,6)">
<path d="M8.144,48.624L11.105,53.718L33.55,42.441L55.994,53.718L58.987,48.624L36.537,37.341L36.537,10.769L30.594,10.769L30.594,37.341L8.144,48.624ZM0.384,53.413C-0.592,55.654 0.33,57.211 2.725,57.489L15.69,58.924C18.959,59.318 20.849,57.171 19.138,54.231L11.408,40.828C9.765,37.956 6.931,38.488 5.654,41.467L0.384,53.413ZM66.721,53.413L61.477,41.467C60.174,38.488 57.366,37.956 55.717,40.828L47.962,54.231C46.256,57.171 48.172,59.318 51.415,58.924L64.38,57.489C66.77,57.211 67.697,55.654 66.721,53.413ZM31.172,1.489L23.477,11.964C21.527,14.591 22.46,17.332 25.814,17.332L41.248,17.332C44.665,17.332 45.558,14.639 43.592,11.97L35.87,1.484C34.395,-0.486 32.642,-0.506 31.172,1.489Z" style="fill-rule:nonzero;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.783639,0,0,0.783639,3,9.136381)">
<path d="M10.966,55.715L61.631,55.715C67.901,55.715 71.026,52.48 70.761,46.429L52.249,29.059C50.874,27.784 49.224,27.139 47.553,27.139C45.84,27.139 44.303,27.727 42.877,29.003L28.763,41.616L22.999,36.414C21.686,35.233 20.243,34.639 18.78,34.639C17.334,34.639 16.041,35.201 14.754,36.383L2.961,46.953C3.114,52.711 5.792,55.715 10.966,55.715ZM10.251,58.341L63.763,58.341C70.533,58.341 74.014,54.886 74.014,48.218L74.014,10.16C74.014,3.486 70.533,0.011 63.763,0.011L10.251,0.011C3.506,0.011 0,3.486 0,10.16L0,48.218C0,54.886 3.506,58.341 10.251,58.341ZM10.606,52.219C7.701,52.219 6.122,50.714 6.122,47.678L6.122,10.694C6.122,7.658 7.701,6.134 10.606,6.134L63.407,6.134C66.287,6.134 67.891,7.658 67.891,10.694L67.891,47.678C67.891,50.714 66.287,52.219 63.407,52.219L10.606,52.219Z" style="fill-rule:nonzero;"/>
<path d="M24.209,29.743C28.302,29.743 31.677,26.369 31.677,22.239C31.677,18.146 28.302,14.74 24.209,14.74C20.079,14.74 16.705,18.146 16.705,22.239C16.705,26.369 20.079,29.743 24.209,29.743Z" style="fill-rule:nonzero;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 72 70" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M38.645,64.222L17.549,64.222C11.671,64.198 8.506,61.129 8.506,55.228L8.506,13.858C8.506,7.958 11.939,4.864 17.619,4.864L54.008,4.864C59.907,4.864 63.096,7.958 63.096,13.858L63.096,35.121C61.386,34.433 59.559,33.974 57.653,33.783L57.653,22.31L54.347,22.31L54.347,23.05C54.347,27.911 52.199,30.851 48.168,31.749L45.342,34.575C44.524,35.393 43.197,35.393 42.379,34.575L39.549,31.745C35.516,30.843 33.374,27.904 33.374,23.05L33.374,22.31L13.973,22.31L13.973,55.44C13.973,57.721 15.098,58.965 17.297,59.072C17.269,58.754 17.255,58.42 17.255,58.07L17.255,54.215C17.255,49.957 19.434,48.069 23.402,48.069L36.442,48.069C35.975,49.668 35.702,51.349 35.651,53.085L24.624,53.085C23.237,53.085 22.516,53.749 22.516,55.165L22.516,57.102C22.516,58.518 23.237,59.21 24.624,59.21L36.417,59.21C36.93,60.995 37.685,62.679 38.645,64.222ZM57.653,17.418L57.653,13.652C57.653,11.264 56.396,10.001 54.013,10.001L17.613,10.001C15.206,10.001 13.973,11.264 13.973,13.652L13.973,17.418L33.807,17.418C34.894,13.675 37.985,11.731 42.487,11.731L45.258,11.731C49.934,11.731 52.908,13.675 53.94,17.418L57.653,17.418ZM42.481,16.868C40.074,16.868 38.841,18.13 38.841,20.518L38.841,23.262C38.841,25.65 40.074,26.902 42.481,26.902L45.264,26.902C47.647,26.902 48.904,25.65 48.904,23.262L48.904,20.518C48.904,18.149 47.666,16.888 45.32,16.868L42.481,16.868Z"/>
<g transform="matrix(1,0,0,1,35.1075,-13.675)">
<path d="M19.178,83.675L21.86,83.675C22.896,83.675 23.714,83.056 23.949,82.006L24.677,79.011C25.075,78.891 25.513,78.706 25.906,78.51L28.52,80.101C29.389,80.672 30.396,80.644 31.191,79.849L33.035,77.979C33.748,77.216 33.821,76.234 33.251,75.277L31.691,72.637C31.913,72.276 32.021,71.92 32.166,71.528L35.186,70.826C36.217,70.564 36.892,69.747 36.892,68.705L36.892,66.06C36.892,65.038 36.191,64.272 35.186,64.011L32.197,63.289C32.038,62.825 31.867,62.484 31.722,62.148L33.37,59.446C33.904,58.571 33.804,57.481 33.086,56.8L31.18,54.956C30.427,54.209 29.411,54.031 28.562,54.596L25.906,56.269C25.436,56.028 25.075,55.914 24.683,55.769L23.949,52.722C23.714,51.712 22.896,51.042 21.86,51.042L19.178,51.042C18.131,51.042 17.304,51.737 17.063,52.728L16.381,55.737C15.886,55.903 15.519,56.028 15.101,56.269L12.444,54.596C11.633,54.068 10.585,54.204 9.852,54.956L7.997,56.8C7.279,57.501 7.077,58.571 7.642,59.446L9.316,62.148C9.166,62.484 8.981,62.876 8.81,63.289L5.852,64.011C4.841,64.272 4.177,65.064 4.177,66.06L4.177,68.705C4.177,69.747 4.847,70.564 5.852,70.826L8.847,71.528C9.037,71.92 9.182,72.276 9.342,72.637L7.782,75.302C7.211,76.234 7.311,77.267 8.023,77.979L9.847,79.849C10.585,80.638 11.613,80.646 12.487,80.101L15.158,78.51C15.596,78.757 15.989,78.891 16.381,79.011L17.063,82.006C17.299,83.005 18.131,83.675 19.178,83.675ZM20.491,72.507C17.681,72.507 15.371,70.197 15.371,67.387C15.371,64.551 17.681,62.236 20.491,62.236C23.326,62.236 25.642,64.551 25.642,67.387C25.642,70.197 23.326,72.507 20.491,72.507Z" style="fill-rule:nonzero;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.781552,0,0,0.781552,1,17.941979)">
<path d="M79.329,9.044C79.329,3.244 76.045,0.021 70.183,0.021L9.093,0.021C3.221,0.021 0,3.244 0,9.044L0,26.911C0,32.701 3.263,35.953 9.125,35.953L70.225,35.953C76.077,35.953 79.329,32.701 79.329,26.911L79.329,9.044ZM72.826,10.089L72.826,25.876C72.826,28.201 71.564,29.461 69.323,29.461L9.964,29.461C7.775,29.461 6.482,28.201 6.482,25.876L6.482,10.089C6.482,7.763 7.785,6.503 9.995,6.503L69.354,6.503C71.564,6.503 72.826,7.763 72.826,10.089ZM13.04,4.664L10.112,4.664L10.112,20.338C10.112,21.156 10.774,21.776 11.602,21.776C12.461,21.776 13.04,21.134 13.04,20.297L13.04,4.664ZM18.661,4.664L15.732,4.664L15.732,15.295C15.732,16.144 16.404,16.744 17.211,16.744C18.092,16.744 18.661,16.123 18.661,15.264L18.661,4.664ZM24.27,4.664L21.32,4.664L21.32,15.295C21.32,16.144 22.014,16.744 22.81,16.744C23.67,16.744 24.27,16.123 24.27,15.264L24.27,4.664ZM29.849,4.664L26.92,4.664L26.92,15.295C26.92,16.144 27.592,16.744 28.42,16.744C29.279,16.744 29.849,16.123 29.849,15.264L29.849,4.664ZM35.479,4.664L32.55,4.664L32.55,15.295C32.55,16.144 33.222,16.744 34.019,16.744C34.9,16.744 35.479,16.123 35.479,15.264L35.479,4.664ZM41.11,4.664L38.181,4.664L38.181,20.338C38.181,21.156 38.843,21.776 39.65,21.776C40.53,21.776 41.11,21.134 41.11,20.297L41.11,4.664ZM46.709,4.664L43.781,4.664L43.781,15.295C43.781,16.144 44.452,16.744 45.259,16.744C46.118,16.744 46.709,16.123 46.709,15.264L46.709,4.664ZM52.319,4.664L49.39,4.664L49.39,15.295C49.39,16.144 50.062,16.744 50.88,16.744C51.739,16.744 52.319,16.123 52.319,15.264L52.319,4.664ZM57.918,4.664L54.999,4.664L54.999,15.295C54.999,16.144 55.661,16.744 56.468,16.744C57.348,16.744 57.918,16.123 57.918,15.264L57.918,4.664ZM63.548,4.664L60.62,4.664L60.62,15.295C60.62,16.144 61.292,16.744 62.088,16.744C62.948,16.744 63.548,16.123 63.548,15.264L63.548,4.664ZM69.158,4.664L66.229,4.664L66.229,20.338C66.229,21.156 66.891,21.776 67.719,21.776C68.578,21.776 69.158,21.134 69.158,20.297L69.158,4.664Z" style="fill-rule:nonzero;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -696,66 +696,3 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
.ant-color-picker .ant-collapse .ant-collapse-item .ant-collapse-header {
margin: 5.5px 0;
}
.subSteps.ant-timeline .ant-timeline-item-last > .ant-timeline-item-content {
min-height: unset;
}
.subSteps.ant-timeline .ant-timeline-item-last {
padding-bottom: 6px;
}
.subSteps.ant-timeline {
margin-top: 16px;
}
.ant-steps.ant-steps-vertical
> .ant-steps-item
> .ant-steps-item-container
> .ant-steps-item-tail::after {
width: 1.5px;
}
.subSteps.ant-timeline .ant-timeline-item-head {
background-color: unset;
}
.subSteps.ant-timeline .subSteps-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: var(--color-primary);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.subSteps.ant-timeline .subSteps-dot svg path {
stroke: var(--layout-modal-bg);
stroke-width: 10px;
}
.subSteps.ant-timeline .subSteps-dot span {
width: 5px;
height: 5px;
margin-top: 0px;
margin-right: 0px;
}
.subSteps.ant-timeline .subSteps-dot.subSteps-dot-disabled {
background-color: var(--color-primary-disabled);
}
.subSteps.ant-timeline .subSteps-dot.subSteps-dot-disabled svg path {
stroke: var(--color-primary);
}
.ant-steps .ant-steps-item-finish .ant-steps-item-icon svg path {
stroke: var(--color-primary);
stroke-width: 3px;
}
.ant-descriptions .ant-descriptions-item-label::after {
top: 0;
}

View File

@ -6,7 +6,6 @@ import {
Navigate
} from 'react-router-dom'
import { App, ConfigProvider } from 'antd'
import { lazy } from 'react'
import Dashboard from './components/Dashboard/Dashboard.jsx'
import PrivateRoute from './components/PrivateRoute'
@ -34,10 +33,6 @@ import AuthCallback from './components/App/AuthCallback.jsx'
import EmailNotificationTemplate from './components/Email/EmailNotificationTemplate.jsx'
import MarketplaceAuthCallback from './components/Dashboard/Sales/Marketplaces/MarketplaceAuthCallback.jsx'
import AuthLaunch from './components/App/AppLaunch.jsx'
const SlicerIntegration = lazy(
() =>
import('./components/Dashboard/Production/Printers/SlicerIntegration.jsx')
)
import {
ProductionRoutes,
@ -126,10 +121,6 @@ const AppContent = () => {
path='/email/notification'
element={<EmailNotificationTemplate />}
/>
<Route
path='/slicer'
element={<SlicerIntegration />}
/>
<Route
path='/dashboard'

View File

@ -1,144 +0,0 @@
import { useContext, useRef, useState } from 'react'
import { Button, Dropdown, Flex, Modal, Space } from 'antd'
import NewFilamentProfile, {
EditFilamentProfile
} from './FilamentProfiles/NewFilamentProfile'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
import FilterSidebarButton from '../common/FilterSidebarButton'
import ObjectTable from '../common/ObjectTable'
import ObjectTableViewButton from '../common/ObjectTableViewButton'
import { ApiServerContext } from '../context/ApiServerContext'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
import useViewMode from '../hooks/useViewMode'
const FilamentProfiles = () => {
const { deleteObject } = useContext(ApiServerContext)
const [profileModal, setProfileModal] = useState({
open: false,
profileId: null
})
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('FilamentProfiles')
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('filamentProfile')
const [showFilterSidebar, setShowFilterSidebar] =
useFilterSidebarVisibility('FilamentProfiles')
const actionItems = {
items: [
{
label: 'New Filament Profile',
key: 'newFilamentProfile',
icon: <PlusIcon />
},
{ type: 'divider' },
{
label: 'Reload List',
key: 'reloadList',
icon: <ReloadIcon />
}
],
onClick: ({ key }) => {
if (key === 'reloadList') {
tableRef.current?.reload()
} else if (key === 'newFilamentProfile') {
setProfileModal({ open: true, profileId: null })
}
}
}
const handleRowAction = (action, profile) => {
if (action.name === 'edit') {
setProfileModal({ open: true, profileId: profile._id })
return true
}
if (action.name === 'delete') {
Modal.confirm({
title: 'Delete filament profile?',
content: `This will permanently delete ${profile.name || profile._reference}.`,
okText: 'Delete',
okType: 'danger',
onOk: async () => {
await deleteObject(profile._id, 'filamentProfile')
await tableRef.current?.reload()
}
})
return true
}
return false
}
return (
<>
<Flex vertical={'true'} gap='large' className='h-100'>
<Flex justify={'space-between'}>
<Space>
<Dropdown menu={actionItems}>
<Button>Actions</Button>
</Dropdown>
<ColumnViewButton
type='filamentProfile'
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='filamentProfile' />
</Space>
<Space>
<FilterSidebarButton
active={showFilterSidebar}
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
/>
<ObjectTableViewButton
viewMode={viewMode}
setViewMode={setViewMode}
/>
</Space>
</Flex>
<ObjectTable
ref={tableRef}
type='filamentProfile'
cards={viewMode === 'cards'}
visibleColumns={columnVisibility}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
onRowAction={handleRowAction}
/>
</Flex>
<Modal
open={profileModal.open}
footer={null}
width={800}
onCancel={() => setProfileModal({ open: false, profileId: null })}
destroyOnHidden
>
{profileModal.open &&
(profileModal.profileId ? (
<EditFilamentProfile
profileId={profileModal.profileId}
onOk={() => {
setProfileModal({ open: false, profileId: null })
tableRef.current?.reload()
}}
/>
) : (
<NewFilamentProfile
onOk={() => {
setProfileModal({ open: false, profileId: null })
tableRef.current?.reload()
}}
/>
))}
</Modal>
</>
)
}
export default FilamentProfiles

View File

@ -1,529 +0,0 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Card, Flex, Space } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import ActionHandler from '../../common/ActionHandler'
import AuditLogIcon from '../../../Icons/AuditLogIcon'
import DocumentPrintButton from '../../common/DocumentPrintButton'
import EditButtons from '../../common/EditButtons'
import FilamentIcon from '../../../Icons/FilamentIcon'
import GCodeFileIcon from '../../../Icons/GCodeFileIcon'
import HotEndIcon from '../../../Icons/HotEndIcon'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon'
import InfoCollapse from '../../common/InfoCollapse'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder'
import LockIndicator from '../../common/LockIndicator'
import MultiMotionIcon from '../../../Icons/MultiMotionIcon'
import NoteIcon from '../../../Icons/NoteIcon'
import NotesPanel from '../../common/NotesPanel'
import ObjectActions from '../../common/ObjectActions'
import ObjectForm from '../../common/ObjectForm'
import ObjectInfo from '../../common/ObjectInfo'
import ObjectTable from '../../common/ObjectTable'
import RulerIcon from '../../../Icons/RulerIcon'
import ScrollBox from '../../common/ScrollBox'
import SettingsIcon from '../../../Icons/SettingsIcon'
import UserNotifierToggle from '../../common/UserNotifierToggle'
import ViewButton from '../../common/ViewButton'
import useCollapseState from '../../hooks/useCollapseState'
import {
FILAMENT_PROFILE_BED_TEMPERATURE_PROPERTIES,
FILAMENT_PROFILE_COMPATIBILITY_PROPERTIES,
FILAMENT_PROFILE_COOLING_PROPERTIES,
FILAMENT_PROFILE_EXHAUST_FAN_PROPERTIES,
FILAMENT_PROFILE_FLOW_PRESSURE_PROPERTIES,
FILAMENT_PROFILE_GCODE_PROPERTIES,
FILAMENT_PROFILE_LOADING_PROPERTIES,
FILAMENT_PROFILE_MATERIAL_PROPERTIES,
FILAMENT_PROFILE_RETRACTION_PROPERTIES,
FILAMENT_PROFILE_SHRINKAGE_DRYING_PROPERTIES,
FILAMENT_PROFILE_SPEED_PROPERTIES,
FILAMENT_PROFILE_TEMPERATURE_PROPERTIES,
FILAMENT_PROFILE_WIPE_TOWER_PROPERTIES
} from '../../../../database/models/FilamentProfile'
const toVisibleProperties = (propertyNames) =>
Object.fromEntries(propertyNames.map((name) => [name, true]))
const FilamentProfileInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const filamentProfileId = new URLSearchParams(location.search).get(
'filamentProfileId'
)
const [collapseState, updateCollapseState] = useCollapseState(
'FilamentProfileInfo',
{
info: true,
material: true,
flowPressure: true,
temperature: true,
bedTemperature: true,
speed: true,
cooling: true,
exhaustFan: true,
retraction: true,
loading: true,
wipeTower: true,
gCodeSettings: true,
compatibility: true,
shrinkageDrying: true,
notes: true,
auditLogs: false
}
)
const [objectFormState, setObjectFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false,
objectData: {}
})
const actions = {
edit: () => {
objectFormRef.current?.startEditing?.()
return false
},
cancelEdit: () => {
objectFormRef.current?.cancelEditing?.()
return true
},
finishEdit: () => {
objectFormRef.current?.handleUpdate?.()
return true
},
delete: () => {
objectFormRef.current?.handleDelete?.()
return true
}
}
return (
<Flex
gap='large'
vertical='true'
style={{ maxHeight: '100%', minHeight: 0 }}
>
<Flex justify='space-between'>
<Space size='middle'>
<Space size='small'>
<ObjectActions
type='filamentProfile'
id={filamentProfileId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Filament Profile Information' },
{ key: 'material', label: 'Material' },
{ key: 'flowPressure', label: 'Flow & Pressure' },
{ key: 'temperature', label: 'Temperature' },
{ key: 'bedTemperature', label: 'Bed Temperatures' },
{ key: 'speed', label: 'Speed' },
{ key: 'cooling', label: 'Part Cooling Fan' },
{ key: 'exhaustFan', label: 'Exhaust Fan' },
{ key: 'retraction', label: 'Retraction Overrides' },
{ key: 'loading', label: 'Loading & Multi-tool' },
{ key: 'wipeTower', label: 'Wipe Tower & Flushing' },
{ key: 'gCodeSettings', label: 'G-Code Settings' },
{ key: 'compatibility', label: 'Compatibility' },
{ key: 'shrinkageDrying', label: 'Shrinkage & Drying' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<UserNotifierToggle
type='filamentProfile'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
<DocumentPrintButton
type='filamentProfile'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space>
<LockIndicator lock={objectFormState.lock} />
</Space>
<EditButtons
isEditing={objectFormState.isEditing}
handleUpdate={() => actionHandlerRef.current.callAction('finishEdit')}
cancelEditing={() =>
actionHandlerRef.current.callAction('cancelEdit')
}
startEditing={() => actionHandlerRef.current.callAction('edit')}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Flex>
<ScrollBox>
<Flex vertical gap='large'>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={filamentProfileId}
type='filamentProfile'
ref={objectFormRef}
onStateChange={(state) =>
setObjectFormState((current) => ({ ...current, ...state }))
}
>
{({ loading, isEditing, objectData }) => (
<Flex vertical gap='large'>
<InfoCollapse
title='Filament Profile Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='140px'
visibleProperties={{
_id: true,
name: true,
_reference: true,
filamentType: true,
filament: true,
createdAt: true,
updatedAt: true
}}
/>
</InfoCollapse>
<InfoCollapse
title='Material'
icon={<FilamentIcon />}
active={collapseState.material}
onToggle={(expanded) =>
updateCollapseState('material', expanded)
}
collapseKey='material'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='220px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_MATERIAL_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Flow & Pressure'
icon={<SettingsIcon />}
active={collapseState.flowPressure}
onToggle={(expanded) =>
updateCollapseState('flowPressure', expanded)
}
collapseKey='flowPressure'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='325px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_FLOW_PRESSURE_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Temperature'
icon={<HotEndIcon />}
active={collapseState.temperature}
onToggle={(expanded) =>
updateCollapseState('temperature', expanded)
}
collapseKey='temperature'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='280px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_TEMPERATURE_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Bed Temperatures'
icon={<RulerIcon />}
active={collapseState.bedTemperature}
onToggle={(expanded) =>
updateCollapseState('bedTemperature', expanded)
}
collapseKey='bedTemperature'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='280px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_BED_TEMPERATURE_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Speed'
icon={<MultiMotionIcon />}
active={collapseState.speed}
onToggle={(expanded) =>
updateCollapseState('speed', expanded)
}
collapseKey='speed'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='265px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_SPEED_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Part Cooling Fan'
icon={<SettingsIcon />}
active={collapseState.cooling}
onToggle={(expanded) =>
updateCollapseState('cooling', expanded)
}
collapseKey='cooling'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='350px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_COOLING_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Exhaust Fan'
icon={<SettingsIcon />}
active={collapseState.exhaustFan}
onToggle={(expanded) =>
updateCollapseState('exhaustFan', expanded)
}
collapseKey='exhaustFan'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='285px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_EXHAUST_FAN_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Retraction Overrides'
icon={<SettingsIcon />}
active={collapseState.retraction}
onToggle={(expanded) =>
updateCollapseState('retraction', expanded)
}
collapseKey='retraction'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='250px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_RETRACTION_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Loading & Multi-tool'
icon={<FilamentIcon />}
active={collapseState.loading}
onToggle={(expanded) =>
updateCollapseState('loading', expanded)
}
collapseKey='loading'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='220px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_LOADING_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Wipe Tower & Flushing'
icon={<SettingsIcon />}
active={collapseState.wipeTower}
onToggle={(expanded) =>
updateCollapseState('wipeTower', expanded)
}
collapseKey='wipeTower'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='330px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_WIPE_TOWER_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='G-Code Settings'
icon={<GCodeFileIcon />}
active={collapseState.gCodeSettings}
onToggle={(expanded) =>
updateCollapseState('gCodeSettings', expanded)
}
collapseKey='gCodeSettings'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='270px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_GCODE_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Compatibility'
icon={<SettingsIcon />}
active={collapseState.compatibility}
onToggle={(expanded) =>
updateCollapseState('compatibility', expanded)
}
collapseKey='compatibility'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='270px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_COMPATIBILITY_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Shrinkage & Drying'
icon={<SettingsIcon />}
active={collapseState.shrinkageDrying}
onToggle={(expanded) =>
updateCollapseState('shrinkageDrying', expanded)
}
collapseKey='shrinkageDrying'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='340px'
visibleProperties={{
...toVisibleProperties(
FILAMENT_PROFILE_SHRINKAGE_DRYING_PROPERTIES
),
filamentNotes: true
}}
/>
</InfoCollapse>
</Flex>
)}
</ObjectForm>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={filamentProfileId} type='filamentProfile' />
</Card>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) => updateCollapseState('auditLogs', expanded)}
collapseKey='auditLogs'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': filamentProfileId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
)
}
export default FilamentProfileInfo

View File

@ -1,420 +0,0 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import PropTypes from 'prop-types'
import NewObjectForm from '../../common/NewObjectForm'
import ObjectForm from '../../common/ObjectForm'
import ObjectInfo from '../../common/ObjectInfo'
import WizardView from '../../common/WizardView'
import {
FILAMENT_PROFILE_BED_TEMPERATURE_PROPERTIES,
FILAMENT_PROFILE_COMPATIBILITY_PROPERTIES,
FILAMENT_PROFILE_COOLING_PROPERTIES,
FILAMENT_PROFILE_EXHAUST_FAN_PROPERTIES,
FILAMENT_PROFILE_FLOW_PRESSURE_PROPERTIES,
FILAMENT_PROFILE_MATERIAL_PROPERTIES,
FILAMENT_PROFILE_SPEED_PROPERTIES,
FILAMENT_PROFILE_TEMPERATURE_PROPERTIES
} from '../../../../database/models/FilamentProfile'
const PRUSA_GENERIC_PETG_DEFAULTS = {
filamentType: 'filament',
filamentIsSupport: false,
filamentSoluble: false,
filamentPrintable: 3,
filamentAdhesivenessCategory: 0,
temperatureVitrification: 80,
idleTemperature: 0,
pelletFlowCoefficient: 0.4157,
requiredNozzleHrc: 0,
filamentFlowRatio: 0.98,
enablePressureAdvance: false,
pressureAdvance: 0.02,
adaptivePressureAdvance: false,
adaptivePressureAdvanceBridges: false,
adaptivePressureAdvanceOverhangs: false,
adaptivePressureAdvanceModel: '0,0,0\n0,0,0',
activateChamberTempControl: false,
chamberTemperature: 0,
chamberMinimalTemperature: 0,
nozzleTemperatureInitialLayer: 230,
nozzleTemperature: 240,
nozzleTemperatureRangeLow: 220,
nozzleTemperatureRangeHigh: 260,
hotPlateTempInitialLayer: 85,
hotPlateTemp: 85,
coolPlateTempInitialLayer: 60,
coolPlateTemp: 60,
engPlateTempInitialLayer: 0,
engPlateTemp: 0,
texturedPlateTempInitialLayer: 45,
texturedPlateTemp: 45,
texturedCoolPlateTempInitialLayer: 40,
texturedCoolPlateTemp: 40,
supertackPlateTempInitialLayer: 35,
supertackPlateTemp: 35,
filamentAdaptiveVolumetricSpeed: false,
filamentMaxVolumetricSpeed: 8,
volumetricSpeedCoefficients: '',
closeFanTheFirstXLayers: 3,
fullFanSpeedLayer: 0,
fanMinSpeed: 40,
fanMaxSpeed: 90,
reduceFanStopStartFreq: true,
slowDownForLayerCooling: true,
dontSlowDownOuterWall: false,
slowDownMinSpeed: 10,
slowDownLayerTime: 8,
fanCoolingLayerTime: 30,
enableOverhangBridgeFan: true,
overhangFanThreshold: '25%',
overhangFanSpeed: 90,
internalBridgeFanSpeed: -1,
supportMaterialInterfaceFanSpeed: -1,
ironingFanSpeed: -1,
initialLayerFanSpeed: -1,
firstXLayerFanSpeed: 0,
additionalCoolingFanSpeed: 0,
additionalFanFullSpeedLayer: 0,
closeAdditionalFanFirstXLayers: true,
activateAirFiltration: false,
activateAirFiltrationDuringPrint: true,
activateAirFiltrationOnCompletion: true,
duringPrintExhaustFanSpeed: 60,
completePrintExhaustFanSpeed: 80,
filamentLoadingSpeed: 28,
filamentLoadingSpeedStart: 3,
filamentUnloadingSpeed: 90,
filamentUnloadingSpeedStart: 100,
filamentChangeLength: 10,
filamentChangeLengthNc: 10,
filamentToolchangeDelay: 0,
filamentExtruderCompatibility: 0,
filamentExtruderVariant: 'Direct Drive Standard',
filamentMultitoolRamming: false,
filamentMultitoolRammingFlow: 10,
filamentMultitoolRammingVolume: 10,
filamentRammingParameters:
'120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6',
filamentRammingTravelTime: 0,
filamentRammingTravelTimeNc: 0,
filamentRammingVolumetricSpeed: -1,
filamentRammingVolumetricSpeedNc: -1,
filamentMinimalPurgeOnWipeTower: 15,
filamentTowerInterfacePreExtrusionDist: 10,
filamentTowerInterfacePreExtrusionLength: 0,
filamentTowerInterfacePrintTemp: -1,
filamentTowerInterfacePurgeVolume: 20,
filamentTowerIroningArea: 4,
filamentCoolingBeforeTower: 10,
filamentCoolingInitialSpeed: 2.2,
filamentCoolingFinalSpeed: 3.4,
filamentCoolingMoves: 4,
filamentFlushTemp: 0,
filamentFlushTempFast: 0,
filamentFlushVolumetricSpeed: 0,
filamentPreCoolingTemperature: 0,
filamentPreCoolingTemperatureNc: 0,
filamentPreheatTemperatureDelta: 0,
filamentPrimeVolumeNc: 60,
filamentStampingDistance: 0,
filamentStampingLoadingSpeed: 0,
longRetractionsWhenEc: false,
retractionDistancesWhenEc: 10,
filamentStartGcode:
'; filament start gcode\nM900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.02{elsif nozzle_diameter[0]==0.6}0.04{else}0.08{endif} ; Filament gcode LA 1.5',
filamentEndGcode: '; filament end gcode \n',
filamentChangeExtrusionRoleGcode: '',
filamentShrink: '100%',
filamentShrinkageCompensationZ: '100%',
compatiblePrinters: [],
compatiblePrintersCondition: '',
compatiblePrints: [],
compatiblePrintsCondition: '',
filamentNotes: ''
}
const REQUIRED_PROPERTIES = ['name', 'filamentType', 'filament']
const SUMMARY_PROPERTIES = [
...REQUIRED_PROPERTIES,
...FILAMENT_PROFILE_MATERIAL_PROPERTIES,
...FILAMENT_PROFILE_FLOW_PRESSURE_PROPERTIES,
...FILAMENT_PROFILE_TEMPERATURE_PROPERTIES,
...FILAMENT_PROFILE_BED_TEMPERATURE_PROPERTIES,
...FILAMENT_PROFILE_SPEED_PROPERTIES,
...FILAMENT_PROFILE_COOLING_PROPERTIES,
...FILAMENT_PROFILE_EXHAUST_FAN_PROPERTIES
]
const FilamentProfileWizard = ({
objectData,
formValid,
loading,
onSubmit,
editing,
filamentId = null
}) => {
const visibleProperties = useMemo(() => {
if (!filamentId) return undefined
return { filament: false, 'filament._id': false }
}, [filamentId])
const steps = useMemo(
() => [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='filamentProfile'
properties={REQUIRED_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='110px'
required={true}
objectData={objectData}
visibleProperties={visibleProperties}
/>
)
},
{
title: 'Material',
key: 'material',
content: (
<ObjectInfo
type='filamentProfile'
properties={FILAMENT_PROFILE_MATERIAL_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='210px'
objectData={objectData}
/>
)
},
{
title: 'Flow & Pressure',
key: 'flowPressure',
content: (
<ObjectInfo
type='filamentProfile'
properties={FILAMENT_PROFILE_FLOW_PRESSURE_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='290px'
objectData={objectData}
/>
)
},
{
title: 'Temperature',
key: 'temperature',
content: (
<ObjectInfo
type='filamentProfile'
properties={[
...FILAMENT_PROFILE_TEMPERATURE_PROPERTIES,
...FILAMENT_PROFILE_BED_TEMPERATURE_PROPERTIES
]}
column={1}
bordered={false}
isEditing
labelWidth='250px'
objectData={objectData}
/>
)
},
{
title: 'Speed',
key: 'speed',
content: (
<ObjectInfo
type='filamentProfile'
properties={FILAMENT_PROFILE_SPEED_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='220px'
objectData={objectData}
/>
)
},
{
title: 'Cooling',
key: 'cooling',
content: (
<ObjectInfo
type='filamentProfile'
properties={FILAMENT_PROFILE_COOLING_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='320px'
objectData={objectData}
/>
)
},
{
title: 'Exhaust Fan',
key: 'exhaustFan',
content: (
<ObjectInfo
type='filamentProfile'
properties={FILAMENT_PROFILE_EXHAUST_FAN_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='270px'
objectData={objectData}
/>
)
},
{
title: 'Compatibility',
key: 'compatibility',
content: (
<ObjectInfo
type='filamentProfile'
properties={FILAMENT_PROFILE_COMPATIBILITY_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='270px'
objectData={objectData}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='filamentProfile'
properties={SUMMARY_PROPERTIES}
column={1}
bordered={false}
isEditing={false}
labelWidth='250px'
objectData={objectData}
visibleProperties={visibleProperties}
/>
)
}
],
[objectData, visibleProperties]
)
return (
<WizardView
sizeBarWidth='185px'
steps={steps}
loading={loading}
formValid={formValid}
title={editing ? 'Edit Filament Profile' : 'New Filament Profile'}
submitText={editing ? 'Save' : 'Create'}
onSubmit={onSubmit}
/>
)
}
FilamentProfileWizard.propTypes = {
objectData: PropTypes.object,
formValid: PropTypes.bool.isRequired,
loading: PropTypes.bool,
onSubmit: PropTypes.func.isRequired,
editing: PropTypes.bool,
filamentId: PropTypes.string
}
const NewFilamentProfile = ({ onOk, filamentId = null }) => (
<NewObjectForm
type='filamentProfile'
defaultValues={{
...PRUSA_GENERIC_PETG_DEFAULTS,
...(filamentId ? { filamentType: 'filament', filament: filamentId } : {})
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => (
<FilamentProfileWizard
objectData={objectData}
formValid={formValid}
loading={submitLoading}
filamentId={filamentId}
onSubmit={async () => {
const result = await handleSubmit()
if (result?._id) onOk(result)
}}
/>
)}
</NewObjectForm>
)
NewFilamentProfile.propTypes = {
onOk: PropTypes.func.isRequired,
filamentId: PropTypes.string
}
export const EditFilamentProfile = ({ profileId, onOk, filamentId = null }) => {
const formRef = useRef(null)
const startedEditingRef = useRef(false)
const saveRequestedRef = useRef(false)
const stateRef = useRef({})
const [formState, setFormState] = useState({
loading: true,
editLoading: false,
formValid: false,
isEditing: false,
objectData: {}
})
useEffect(() => {
if (
!formState.loading &&
!formState.isEditing &&
!startedEditingRef.current
) {
startedEditingRef.current = true
formRef.current?.startEditing()
}
}, [formState.isEditing, formState.loading])
return (
<ObjectForm
id={profileId}
type='filamentProfile'
ref={formRef}
onStateChange={(nextState) => {
stateRef.current = { ...stateRef.current, ...nextState }
setFormState((current) => ({ ...current, ...nextState }))
if (saveRequestedRef.current && nextState.isEditing === false) {
saveRequestedRef.current = false
onOk(stateRef.current.objectData)
}
}}
>
{({ objectData, formValid, editLoading, handleUpdate }) => (
<FilamentProfileWizard
editing
objectData={objectData}
formValid={formValid}
loading={formState.loading || editLoading}
filamentId={filamentId}
onSubmit={() => {
saveRequestedRef.current = true
handleUpdate()
}}
/>
)}
</ObjectForm>
)
}
EditFilamentProfile.propTypes = {
profileId: PropTypes.string.isRequired,
onOk: PropTypes.func.isRequired,
filamentId: PropTypes.string
}
export default NewFilamentProfile

View File

@ -1,49 +1,35 @@
import { useMemo } from 'react'
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewGCodeFile = ({
onOk,
defaultValues,
slicerIntegration = false,
shouldPrint = false,
currentStep,
onStepChange
}) => {
const initialValues = useMemo(
() => ({
state: { type: 'draft' },
...defaultValues
}),
[defaultValues]
)
const NewGCodeFile = ({ onOk, defaultValues }) => {
return (
<NewObjectForm type={'gcodeFile'} defaultValues={initialValues}>
<NewObjectForm
type={'gcodeFile'}
defaultValues={{
state: { type: 'draft' },
...defaultValues
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
...(!slicerIntegration
? [
{
title: 'Upload',
key: 'upload',
content: (
<ObjectInfo
type='gcodeFile'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
visibleProperties={{ file: true }}
showLabels={false}
/>
)
}
]
: []),
{
title: 'Upload',
key: 'upload',
content: (
<ObjectInfo
type='gcodeFile'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
visibleProperties={{ file: true }}
showLabels={false}
/>
)
},
{
title: 'Required',
key: 'required',
@ -87,13 +73,10 @@ const NewGCodeFile = ({
loading={submitLoading}
formValid={formValid}
title='New GCode File'
showSteps={!slicerIntegration || !shouldPrint}
currentStep={currentStep}
onStepChange={onStepChange}
onSubmit={async () => {
const result = await handleSubmit()
if (result) {
onOk(result)
onOk()
}
}}
/>
@ -106,11 +89,7 @@ const NewGCodeFile = ({
NewGCodeFile.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool,
defaultValues: PropTypes.object,
slicerIntegration: PropTypes.bool,
currentStep: PropTypes.number,
onStepChange: PropTypes.func,
shouldPrint: PropTypes.bool
defaultValues: PropTypes.object
}
export default NewGCodeFile

View File

@ -2,20 +2,17 @@ import { useState, useContext, useEffect, useCallback } from 'react'
import PropTypes from 'prop-types'
import WizardView from '../../common/WizardView'
import { ApiServerContext } from '../../context/ApiServerContext'
import { Descriptions, Flex, Typography } from 'antd'
import { Flex, Typography } from 'antd'
import ObjectTable from '../../common/ObjectTable'
import ObjectProperty from '../../common/ObjectProperty'
import ObjectSelect from '../../common/ObjectSelect'
const { Text } = Typography
const DeployJob = ({
onOk,
objectData = undefined,
slicerIntegration = false
}) => {
const DeployJob = ({ onOk, objectData = undefined }) => {
const [deployLoading, setDeployLoading] = useState(false)
const [job, setJob] = useState(objectData)
const [deployedSubJobsCount, setDeployedSubJobsCount] = useState(0)
const [subJobsCount, setSubJobsCount] = useState(999)
const [allPrintersOnline, setAllPrintersOnline] = useState(false)
const { sendObjectAction, fetchObjects } = useContext(ApiServerContext)
const handleDeploy = useCallback(async () => {
@ -54,94 +51,21 @@ const DeployJob = ({
}
}, [deployedSubJobsCount, subJobsCount, deployLoading, onOk])
useEffect(() => {
if (objectData?.printers) {
if (objectData.printers.every((printer) => printer.online)) {
setAllPrintersOnline(true)
} else {
setAllPrintersOnline(false)
}
} else {
setAllPrintersOnline(false)
}
}, [objectData])
console.log(objectData)
const steps = [
{
title: 'Confirm',
key: 'confirm',
content: (
<Flex vertical gap={'middle'} style={{ width: '100%' }}>
<Flex gap={'middle'} align='center' style={{ width: '100%' }}>
<Descriptions
column={1}
items={[
{
label: (
<Flex
align='center'
gap={'small'}
style={{ height: '100%' }}
>
<Text type='secondary'>Job</Text>
</Flex>
),
children: (
<div
style={{
flexGrow: 1,
marginLeft: '3px',
marginBottom: '1px'
}}
>
<ObjectProperty
type={'object'}
name={'job'}
objectData={{ job: objectData }}
value={(object) => object.job}
readOnly={true}
isEditing={true}
objectType={'job'}
onChange={(newJob) => {
setJob(newJob)
}}
/>
</div>
)
},
{
label: (
<Flex
align='center'
gap={'small'}
style={{ height: '100%' }}
>
<Text type='secondary'>Printers</Text>
</Flex>
),
children: (
<div
style={{
flexGrow: 1,
marginLeft: '3px',
marginBottom: '1px'
}}
>
<ObjectProperty
type={'objectList'}
name={'printers'}
objectData={{ printers: objectData.printers }}
value={(object) => object.printers}
readOnly={true}
isEditing={true}
objectType={'printer'}
/>
</div>
)
}
]}
<Flex gap={'small'} align='center' style={{ width: '100%' }}>
<Text type='secondary'>Job:</Text>
<ObjectSelect
type={'job'}
style={{ flexGrow: 1 }}
value={objectData}
onChange={(newJob) => {
setJob(newJob)
}}
/>
</Flex>
@ -150,11 +74,11 @@ const DeployJob = ({
scrollHeight={'200px'}
visibleColumns={{
printer: false,
job: false
'job._id': false,
'printer._id': false
}}
masterFilter={{ 'job._id': job?._id }}
size={'small'}
showActions={!slicerIntegration}
/>
</Flex>
)
@ -165,13 +89,9 @@ const DeployJob = ({
<WizardView
steps={steps}
loading={deployLoading}
formValid={objectData != undefined && allPrintersOnline}
formValid={objectData != undefined}
title='Deploy Job'
showSteps={false}
showSkipButton={slicerIntegration}
onSkip={() => {
onOk()
}}
submitText='Deploy'
progress={(deployedSubJobsCount / subJobsCount) * 100}
onSubmit={() => {
@ -184,8 +104,7 @@ const DeployJob = ({
DeployJob.propTypes = {
onOk: PropTypes.func.isRequired,
objectData: PropTypes.object,
reset: PropTypes.bool,
slicerIntegration: PropTypes.bool
reset: PropTypes.bool
}
export default DeployJob

View File

@ -1,26 +1,17 @@
import { useMemo } from 'react'
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewJob = ({
onOk,
defaultValues,
slicerIntegration = false,
currentStep,
onStepChange
}) => {
const initialValues = useMemo(
() => ({
state: { type: 'draft' },
...defaultValues
}),
[defaultValues]
)
const NewJob = ({ onOk, defaultValues }) => {
return (
<NewObjectForm type={'job'} defaultValues={initialValues}>
<NewObjectForm
type={'job'}
defaultValues={{
state: { type: 'draft' },
...defaultValues
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
@ -68,17 +59,10 @@ const NewJob = ({
loading={submitLoading}
formValid={formValid}
title='New Job'
showSteps={!slicerIntegration}
currentStep={currentStep}
onStepChange={onStepChange}
showSkipButton={slicerIntegration}
onSkip={() => {
onOk(null)
}}
onSubmit={async () => {
const result = await handleSubmit()
if (result) {
onOk(result)
onOk()
}
}}
/>
@ -91,10 +75,7 @@ const NewJob = ({
NewJob.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool,
defaultValues: PropTypes.object,
slicerIntegration: PropTypes.bool,
currentStep: PropTypes.number,
onStepChange: PropTypes.func
defaultValues: PropTypes.object
}
export default NewJob

View File

@ -1,143 +0,0 @@
import { useContext, useRef, useState } from 'react'
import { Button, Dropdown, Flex, Modal, Space } from 'antd'
import NewPrinterProfile, {
EditPrinterProfile
} from './PrinterProfiles/NewPrinterProfile'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
import FilterSidebarButton from '../common/FilterSidebarButton'
import ObjectTable from '../common/ObjectTable'
import ObjectTableViewButton from '../common/ObjectTableViewButton'
import { ApiServerContext } from '../context/ApiServerContext'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
import useViewMode from '../hooks/useViewMode'
const PrinterProfiles = () => {
const { deleteObject } = useContext(ApiServerContext)
const [profileModal, setProfileModal] = useState({
open: false,
profileId: null
})
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('PrinterProfiles')
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('printerProfile')
const [showFilterSidebar, setShowFilterSidebar] =
useFilterSidebarVisibility('PrinterProfiles')
const actionItems = {
items: [
{
label: 'New Printer Profile',
key: 'newPrinterProfile',
icon: <PlusIcon />
},
{ type: 'divider' },
{
label: 'Reload List',
key: 'reloadList',
icon: <ReloadIcon />
}
],
onClick: ({ key }) => {
if (key === 'reloadList') {
tableRef.current?.reload()
} else if (key === 'newPrinterProfile') {
setProfileModal({ open: true, profileId: null })
}
}
}
const handleRowAction = (action, profile) => {
if (action.name === 'edit') {
setProfileModal({ open: true, profileId: profile._id })
return true
}
if (action.name === 'delete') {
Modal.confirm({
title: 'Delete printer profile?',
content: `This will permanently delete ${profile.name || profile._reference}.`,
okText: 'Delete',
okType: 'danger',
onOk: async () => {
await deleteObject(profile._id, 'printerProfile')
await tableRef.current?.reload()
}
})
return true
}
return false
}
return (
<>
<Flex vertical={'true'} gap='large' className='h-100'>
<Flex justify={'space-between'}>
<Space>
<Dropdown menu={actionItems}>
<Button>Actions</Button>
</Dropdown>
<ColumnViewButton
type='printerProfile'
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='printerProfile' />
</Space>
<Space>
<FilterSidebarButton
active={showFilterSidebar}
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
/>
<ObjectTableViewButton
viewMode={viewMode}
setViewMode={setViewMode}
/>
</Space>
</Flex>
<ObjectTable
ref={tableRef}
type='printerProfile'
cards={viewMode === 'cards'}
visibleColumns={columnVisibility}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
onRowAction={handleRowAction}
/>
</Flex>
<Modal
open={profileModal.open}
footer={null}
width={800}
onCancel={() => setProfileModal({ open: false, profileId: null })}
destroyOnHidden
>
{profileModal.open &&
(profileModal.profileId ? (
<EditPrinterProfile
profileId={profileModal.profileId}
onOk={() => {
setProfileModal({ open: false, profileId: null })
tableRef.current?.reload()
}}
/>
) : (
<NewPrinterProfile
onOk={() => {
setProfileModal({ open: false, profileId: null })
tableRef.current?.reload()
}}
/>
))}
</Modal>
</>
)
}
export default PrinterProfiles

View File

@ -1,372 +0,0 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import PropTypes from 'prop-types'
import NewObjectForm from '../../common/NewObjectForm'
import ObjectForm from '../../common/ObjectForm'
import ObjectInfo from '../../common/ObjectInfo'
import WizardView from '../../common/WizardView'
const DEFAULT_EXTRUDER = {
nozzleDiameter: 0.4,
nozzleVolume: 0,
nozzleType: 'hardened_steel',
minLayerHeight: 0.05,
maxLayerHeight: 0.3,
positionOffsetX: 0,
positionOffsetY: 0,
retractionLength: 0.8,
retractionExtraLengthOnRestart: 0,
retractionSpeed: 35,
deretractionSpeed: 0,
retractionTravelDistanceThreshold: 2,
retractOnLayerChange: false,
wipeWhileRetracting: false,
wipeDistance: 1,
retractAmountBeforeWipe: 70,
retractAmountAfterWipe: 0,
zHopOnSurfaces: 'All Surfaces',
zHopType: 'Auto',
zHopHeight: 0,
zHopTravelingAngle: 0,
zHopOnlyLiftZAbove: 0,
zHopOnlyLiftZBelow: 0,
materialSwitchRetractionLength: 10,
materialSwitchExtraLengthOnRestart: 0,
longRetractionWhenCut: false
}
const PRUSA_MK3S_PROFILE_DEFAULTS = {
extruders: [{ ...DEFAULT_EXTRUDER }],
printBedWidth: 250,
printBedHeight: 210,
originX: 0,
originY: 0,
bedExcludeArea: [{ x: 0, y: 0 }],
printableHeight: 210,
supportMultiBedTypes: false,
bestObjectPositionX: 125,
bestObjectPositionY: 105,
zOffset: 0,
preferredOrientation: 0,
machineMaxAccelerationRetracting: { min: 2500, max: 2500 },
machineMaxSpeedE: { min: 120, max: 120 },
machineMaxSpeedX: { min: 200, max: 200 },
machineMaxSpeedY: { min: 200, max: 200 },
machinePauseGcode: 'M601',
machineStartGcode: `M862.1 P[nozzle_diameter] ; nozzle diameter check
M115 U3.13.0 ; tell printer latest fw version
G90 ; use absolute coordinates
M83 ; extruder relative mode
M104 S[first_layer_temperature] ; set extruder temp
M140 S[first_layer_bed_temperature] ; set bed temp
M190 S[first_layer_bed_temperature] ; wait for bed temp
M109 S[first_layer_temperature] ; wait for extruder temp
G28 W ; home all without mesh bed level
G80 ; mesh bed leveling
G1 Z0.3 F720
G1 Y-3 F1000 ; go outside print area
G92 E0
G1 X60 E9 F1000 ; intro line
G1 X100 E9 F1000 ; intro line
{else}
G1 Z0.2 F720
G1 Y-3 F1000 ; go outside print area
G92 E0
G1 X60 E9 F1000 ; intro line
G1 X100 E12.5 F1000 ; intro line
{endif}
G92 E0
M221 S{if layer_height<0.075}100{else}95{endif}`,
machineEndGcode: `{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}
G1 X0 Y200 F3600 ; park
{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}
G4 ; wait
M221 S100 ; reset flow
M900 K0 ; reset LA
M104 S0 ; turn off temperature
M140 S0 ; turn off heatbed
M107 ; turn off fan
M84 ; disable motors
; max_layer_z = [max_layer_z]`,
layerChangeGcode: `;AFTER_LAYER_CHANGE
;[layer_z]`,
beforeLayerChangeGcode: `;BEFORE_LAYER_CHANGE
;[layer_z]
G92 E0
`,
printerNotes: `Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.
PRINTER_VENDOR_PRUSA3D
PRINTER_MODEL_MK3
`,
scanFirstLayer: false,
machineLoadFilamentTime: 17,
machineUnloadFilamentTime: 16,
thumbnails: [{ width: 160, height: 120 }],
auxiliaryFan: false,
machineMaxJunctionDeviation: { min: 0.02, max: 0 }
}
const REQUIRED_PROPERTIES = ['name', 'printer']
const DIMENSION_PROPERTIES = [
'printBedWidth',
'printBedHeight',
'originX',
'originY',
'printableHeight',
'supportMultiBedTypes',
'bestObjectPositionX',
'bestObjectPositionY',
'zOffset',
'preferredOrientation'
]
const EXTRUDER_STEP_PROPERTIES = ['extruders']
const MOTION_PROPERTIES = [
'machineMaxAccelerationRetracting',
'machineMaxSpeedE',
'machineMaxSpeedX',
'machineMaxSpeedY',
'machineMaxJunctionDeviation'
]
const OPTIONAL_PROPERTIES = [
'scanFirstLayer',
'machineLoadFilamentTime',
'machineUnloadFilamentTime',
'auxiliaryFan'
]
const SUMMARY_PROPERTIES = [
...REQUIRED_PROPERTIES,
...DIMENSION_PROPERTIES,
...MOTION_PROPERTIES,
...OPTIONAL_PROPERTIES
]
const PrinterProfileWizard = ({
objectData,
formValid,
loading,
onSubmit,
editing,
printerId = null
}) => {
const visibleProperties = useMemo(() => {
if (!printerId) return undefined
return { printer: false, 'printer._id': false }
}, [printerId])
const steps = useMemo(
() => [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='printerProfile'
properties={REQUIRED_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='75px'
required={true}
objectData={objectData}
visibleProperties={visibleProperties}
/>
)
},
{
title: 'Dimensions',
key: 'dimensions',
content: (
<ObjectInfo
type='printerProfile'
properties={DIMENSION_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='190px'
objectData={objectData}
/>
)
},
{
title: 'Extruders',
key: 'extruders',
content: (
<ObjectInfo
type='printerProfile'
properties={EXTRUDER_STEP_PROPERTIES}
column={1}
bordered={false}
isEditing
showLabels={false}
labelWidth='90px'
objectData={objectData}
/>
)
},
{
title: 'Motion',
key: 'motion',
content: (
<ObjectInfo
type='printerProfile'
properties={MOTION_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='185px'
objectData={objectData}
/>
)
},
{
title: 'Optional',
key: 'optional',
content: (
<ObjectInfo
type='printerProfile'
properties={OPTIONAL_PROPERTIES}
column={1}
labelWidth='170px'
bordered={false}
isEditing
objectData={objectData}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='printerProfile'
properties={SUMMARY_PROPERTIES}
column={1}
bordered={false}
isEditing={false}
labelWidth='210px'
objectData={objectData}
visibleProperties={visibleProperties}
/>
)
}
],
[objectData, visibleProperties]
)
return (
<WizardView
sizeBarWidth='160px'
steps={steps}
loading={loading}
formValid={formValid}
title={editing ? 'Edit Printer Profile' : 'New Printer Profile'}
submitText={editing ? 'Save' : 'Create'}
onSubmit={onSubmit}
/>
)
}
PrinterProfileWizard.propTypes = {
objectData: PropTypes.object,
formValid: PropTypes.bool.isRequired,
loading: PropTypes.bool,
onSubmit: PropTypes.func.isRequired,
editing: PropTypes.bool,
printerId: PropTypes.string
}
const NewPrinterProfile = ({ onOk, printerId = null }) => (
<NewObjectForm
type='printerProfile'
defaultValues={{
...PRUSA_MK3S_PROFILE_DEFAULTS,
...(printerId ? { printer: printerId } : {})
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => (
<PrinterProfileWizard
objectData={objectData}
formValid={formValid}
loading={submitLoading}
printerId={printerId}
onSubmit={async () => {
const result = await handleSubmit()
if (result?._id) onOk(result)
}}
/>
)}
</NewObjectForm>
)
NewPrinterProfile.propTypes = {
onOk: PropTypes.func.isRequired,
printerId: PropTypes.string
}
export const EditPrinterProfile = ({ profileId, onOk, printerId = null }) => {
const formRef = useRef(null)
const startedEditingRef = useRef(false)
const saveRequestedRef = useRef(false)
const stateRef = useRef({})
const [formState, setFormState] = useState({
loading: true,
editLoading: false,
formValid: false,
isEditing: false,
objectData: {}
})
useEffect(() => {
if (
!formState.loading &&
!formState.isEditing &&
!startedEditingRef.current
) {
startedEditingRef.current = true
formRef.current?.startEditing()
}
}, [formState.isEditing, formState.loading])
return (
<ObjectForm
id={profileId}
type='printerProfile'
ref={formRef}
onStateChange={(nextState) => {
stateRef.current = { ...stateRef.current, ...nextState }
setFormState((current) => ({ ...current, ...nextState }))
if (saveRequestedRef.current && nextState.isEditing === false) {
saveRequestedRef.current = false
onOk(stateRef.current.objectData)
}
}}
>
{({ objectData, formValid, editLoading, handleUpdate }) => (
<PrinterProfileWizard
editing
objectData={objectData}
formValid={formValid}
loading={formState.loading || editLoading}
printerId={printerId}
onSubmit={() => {
saveRequestedRef.current = true
handleUpdate()
}}
/>
)}
</ObjectForm>
)
}
EditPrinterProfile.propTypes = {
profileId: PropTypes.string.isRequired,
onOk: PropTypes.func.isRequired,
printerId: PropTypes.string
}
export default NewPrinterProfile

View File

@ -1,381 +0,0 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Card, Flex, Space } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import ActionHandler from '../../common/ActionHandler'
import AuditLogIcon from '../../../Icons/AuditLogIcon'
import DocumentPrintButton from '../../common/DocumentPrintButton'
import EditButtons from '../../common/EditButtons'
import HotEndIcon from '../../../Icons/HotEndIcon'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon'
import MultiMotionIcon from '../../../Icons/MultiMotionIcon'
import PictureIcon from '../../../Icons/PictureIcon'
import RulerIcon from '../../../Icons/RulerIcon'
import InfoCollapse from '../../common/InfoCollapse'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder'
import LockIndicator from '../../common/LockIndicator'
import NoteIcon from '../../../Icons/NoteIcon'
import NotesPanel from '../../common/NotesPanel'
import ObjectActions from '../../common/ObjectActions'
import ObjectForm from '../../common/ObjectForm'
import ObjectInfo from '../../common/ObjectInfo'
import ObjectTable from '../../common/ObjectTable'
import ScrollBox from '../../common/ScrollBox'
import UserNotifierToggle from '../../common/UserNotifierToggle'
import ViewButton from '../../common/ViewButton'
import useCollapseState from '../../hooks/useCollapseState'
import ObjectProperty from '../../common/ObjectProperty'
import { getModelProperty } from '../../../../database/ObjectModels'
import GCodeFileIcon from '../../../Icons/GCodeFileIcon'
import SettingsIcon from '../../../Icons/SettingsIcon'
import ExclamationOctagonIcon from '../../../Icons/ExclamationOctagonIcon'
const PrinterProfileInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const printerProfileId = new URLSearchParams(location.search).get(
'printerProfileId'
)
const [collapseState, updateCollapseState] = useCollapseState(
'PrinterProfileInfo',
{
info: true,
dimensions: true,
bedExcludeArea: true,
extruders: true,
motion: true,
gCodeSettings: true,
optional: true,
thumbnails: true,
notes: true,
auditLogs: false
}
)
const [objectFormState, setObjectFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false,
objectData: {}
})
const actions = {
edit: () => {
objectFormRef.current?.startEditing?.()
return false
},
cancelEdit: () => {
objectFormRef.current?.cancelEditing?.()
return true
},
finishEdit: () => {
objectFormRef.current?.handleUpdate?.()
return true
},
delete: () => {
objectFormRef.current?.handleDelete?.()
return true
}
}
return (
<Flex
gap='large'
vertical='true'
style={{ maxHeight: '100%', minHeight: 0 }}
>
<Flex justify='space-between'>
<Space size='middle'>
<Space size='small'>
<ObjectActions
type='printerProfile'
id={printerProfileId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Printer Profile Information' },
{ key: 'dimensions', label: 'Dimensions' },
{ key: 'bedExcludeArea', label: 'Excluded Bed Area' },
{ key: 'extruders', label: 'Extruders' },
{ key: 'motion', label: 'Motion' },
{ key: 'gCodeSettings', label: 'G-Code Settings' },
{ key: 'miscellaneous', label: 'Miscellaneous' },
{ key: 'thumbnails', label: 'Thumbnail Sizes' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<UserNotifierToggle
type='printerProfile'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
<DocumentPrintButton
type='printerProfile'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space>
<LockIndicator lock={objectFormState.lock} />
</Space>
<EditButtons
isEditing={objectFormState.isEditing}
handleUpdate={() => actionHandlerRef.current.callAction('finishEdit')}
cancelEditing={() =>
actionHandlerRef.current.callAction('cancelEdit')
}
startEditing={() => actionHandlerRef.current.callAction('edit')}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Flex>
<ScrollBox>
<Flex vertical gap='large'>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={printerProfileId}
type='printerProfile'
ref={objectFormRef}
onStateChange={(state) =>
setObjectFormState((current) => ({ ...current, ...state }))
}
>
{({ loading, isEditing, objectData }) => (
<Flex vertical gap='large'>
<InfoCollapse
title='Printer Profile Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='printerProfile'
objectData={objectData}
labelWidth='140px'
visibleProperties={{
_id: true,
name: true,
_reference: true,
printer: true,
createdAt: true,
updatedAt: true
}}
/>
</InfoCollapse>
<InfoCollapse
title='Dimensions'
icon={<RulerIcon />}
active={collapseState.dimensions}
onToggle={(expanded) =>
updateCollapseState('dimensions', expanded)
}
collapseKey='dimensions'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='printerProfile'
objectData={objectData}
labelWidth='230px'
visibleProperties={{
printBedWidth: true,
printBedHeight: true,
originX: true,
originY: true,
printableHeight: true,
supportMultiBedTypes: true,
bestObjectPositionX: true,
bestObjectPositionY: true,
zOffset: true,
preferredOrientation: true
}}
/>
</InfoCollapse>
<InfoCollapse
title='Excluded Bed Area'
icon={<ExclamationOctagonIcon />}
active={collapseState.bedExcludeArea}
onToggle={(expanded) =>
updateCollapseState('bedExcludeArea', expanded)
}
collapseKey='bedExcludeArea'
>
<ObjectProperty
{...getModelProperty('printerProfile', 'bedExcludeArea')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
size='medium'
/>
</InfoCollapse>
<InfoCollapse
title='Extruders'
icon={<HotEndIcon />}
active={collapseState.extruders}
onToggle={(expanded) =>
updateCollapseState('extruders', expanded)
}
collapseKey='extruders'
>
<ObjectProperty
{...getModelProperty('printerProfile', 'extruders')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
size='medium'
/>
</InfoCollapse>
<InfoCollapse
title='Motion'
icon={<MultiMotionIcon />}
active={collapseState.motion}
onToggle={(expanded) =>
updateCollapseState('motion', expanded)
}
collapseKey='motion'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='printerProfile'
objectData={objectData}
labelWidth='225px'
visibleProperties={{
machineMaxAccelerationRetracting: true,
machineMaxSpeedE: true,
machineMaxSpeedX: true,
machineMaxSpeedY: true,
machineMaxJunctionDeviation: true
}}
/>
</InfoCollapse>
<InfoCollapse
title='G-Code Settings'
icon={<GCodeFileIcon />}
active={collapseState.gCodeSettings}
onToggle={(expanded) =>
updateCollapseState('gCodeSettings', expanded)
}
collapseKey='gCodeSettings'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='printerProfile'
objectData={objectData}
labelWidth='240px'
visibleProperties={{
machinePauseGcode: true,
machineStartGcode: true,
machineEndGcode: true,
layerChangeGcode: true,
beforeLayerChangeGcode: true
}}
/>
</InfoCollapse>
<InfoCollapse
title='Miscellaneous'
icon={<SettingsIcon />}
active={collapseState.miscellaneous}
onToggle={(expanded) =>
updateCollapseState('miscellaneous', expanded)
}
collapseKey='miscellaneous'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='printerProfile'
objectData={objectData}
labelWidth='210px'
visibleProperties={{
printerNotes: true,
scanFirstLayer: true,
machineLoadFilamentTime: true,
machineUnloadFilamentTime: true,
auxiliaryFan: true
}}
/>
</InfoCollapse>
<InfoCollapse
title='Thumbnail Sizes'
icon={<PictureIcon />}
active={collapseState.thumbnails}
onToggle={(expanded) =>
updateCollapseState('thumbnails', expanded)
}
collapseKey='thumbnails'
>
<ObjectProperty
{...getModelProperty('printerProfile', 'thumbnails')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
size='medium'
/>
</InfoCollapse>
</Flex>
)}
</ObjectForm>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={printerProfileId} type='printerProfile' />
</Card>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) => updateCollapseState('auditLogs', expanded)}
collapseKey='auditLogs'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': printerProfileId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
)
}
export default PrinterProfileInfo

View File

@ -9,9 +9,9 @@ import InfoCollapse from '../../common/InfoCollapse.jsx'
import ViewButton from '../../common/ViewButton.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import ObjectForm from '../../common/ObjectForm.jsx'
import EditButtons from '../../common/EditButtons.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo.jsx'
import PrinterIcon from '../../../Icons/PrinterIcon.jsx'
@ -35,7 +35,7 @@ import ScrollBox from '../../common/ScrollBox.jsx'
const log = loglevel.getLogger('ControlPrinter')
log.setLevel(config.logLevel)
const ControlPrinter = ({ slicerIntegration = false }) => {
const ControlPrinter = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
@ -226,11 +226,7 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
type='printer'
id={printerId}
disabled={objectFormState.loading}
visibleActions={{
edit: false,
info: !slicerIntegration,
control: !slicerIntegration
}}
visibleActions={{ edit: false }}
objectData={objectFormState.objectData}
/>
<ViewButton
@ -279,6 +275,24 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
/>
</Space>
</Space>
<Space>
<EditButtons
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
cancelEditing={() => {
actionHandlerRef.current.callAction('cancelEdit')
}}
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
<ScrollBox>
@ -339,16 +353,11 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
currentSubJob: false,
'currentSubJob._id': false,
createdAt: false,
updatedAt: false,
state: !slicerIntegration,
name: !slicerIntegration
}}
objectPropertyProps={{
showHyperlink: !slicerIntegration
updatedAt: false
}}
objectData={printerObjectData}
type='printer'
labelWidth='120px'
labelWidth='100px'
/>
)
}}
@ -377,15 +386,12 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
<ObjectInfo
loading={jobObjectLoading}
column={sideBarVisible ? 1 : undefined}
showHyperlink={!slicerIntegration}
showHyperlink={true}
visibleProperties={{
printers: false,
createdAt: false,
finishedAt: false
}}
objectPropertyProps={{
showHyperlink: !slicerIntegration
}}
objectData={jobObjectData}
type='job'
/>
@ -422,15 +428,12 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
<ObjectInfo
loading={subJobObjectLoading}
column={sideBarVisible ? 1 : undefined}
showHyperlink={!slicerIntegration}
showHyperlink={true}
visibleProperties={{
printers: false,
createdAt: false,
finishedAt: false
}}
objectPropertyProps={{
showHyperlink: !slicerIntegration
}}
objectData={subJobObjectData}
type='subJob'
/>
@ -471,7 +474,7 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
<ObjectInfo
loading={filamentStockObjectLoading}
column={sideBarVisible ? 1 : undefined}
showHyperlink={!slicerIntegration}
showHyperlink={true}
visibleProperties={{
updatedAt: false,
createdAt: false
@ -555,8 +558,4 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
)
}
ControlPrinter.propTypes = {
slicerIntegration: PropTypes.bool
}
export default ControlPrinter

View File

@ -96,8 +96,8 @@ const NewPrinter = ({ onOk, defaultValues }) => {
title='New Printer'
onSubmit={async () => {
const result = await handleSubmit()
if (result?._id) {
onOk(result)
if (result) {
onOk()
}
}}
/>

View File

@ -1,225 +0,0 @@
import { useContext, useEffect, useState } from 'react'
import { Divider, Flex, Layout, Typography, Button, Badge, Modal } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import { useLocation } from 'react-router-dom'
import ControlPrinter from './ControlPrinter.jsx'
import SlicerUploadFlow from './SlicerUploadFlow.jsx'
import PrinterIcon from '../../../Icons/PrinterIcon.jsx'
import ObjectForm from '../../common/ObjectForm.jsx'
import FileIcon from '../../../Icons/FileIcon.jsx'
import ObjectProperty from '../../common/ObjectProperty.jsx'
import { getModelProperty } from '../../../../database/ObjectModels.js'
import { ApiServerContext } from '../../context/ApiServerContext.jsx'
import { useThemeContext } from '../../context/ThemeContext.jsx'
const { Content } = Layout
const { Title } = Typography
const SlicerIntegration = () => {
const location = useLocation()
const printerId = new URLSearchParams(location.search).get('printerId')
const { updateObject } = useContext(ApiServerContext)
const [pendingSlicerUploads, setPendingSlicerUploads] = useState([])
const [currentPendingSlicerUpload, setCurrentPendingSlicerUpload] =
useState(null)
const { setPrimaryColorOverride } = useThemeContext()
useEffect(() => {
setPrimaryColorOverride('#00baa8')
}, [])
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
locked: false,
loading: false,
objectData: {
pendingSlicerUploads: []
}
})
const [showSlicerUploadFlow, setShowSlicerUploadFlow] = useState(false)
useEffect(() => {
setPendingSlicerUploads(
objectFormState.objectData?.pendingSlicerUploads || []
)
}, [objectFormState.objectData?.pendingSlicerUploads])
useEffect(() => {
const nextUpload = pendingSlicerUploads.find(
(upload) => upload.new === true
)
if (nextUpload) {
setCurrentPendingSlicerUpload(nextUpload)
setShowSlicerUploadFlow(true)
} else {
setCurrentPendingSlicerUpload(null)
setShowSlicerUploadFlow(false)
}
}, [pendingSlicerUploads])
const showPendingSlicerUpload = () => {
const upload =
pendingSlicerUploads.find(
(pendingUpload) => pendingUpload.new === true
) || pendingSlicerUploads[0]
if (upload) {
setCurrentPendingSlicerUpload(upload)
setShowSlicerUploadFlow(true)
}
}
const handleGCodeFileCreated = async (gcodeFile) => {
const updatedUploads = pendingSlicerUploads.map((upload) =>
upload._id === currentPendingSlicerUpload?._id
? { ...upload, gcodeFile: gcodeFile }
: upload
)
setPendingSlicerUploads(updatedUploads)
setCurrentPendingSlicerUpload((previous) => ({
...previous,
gcodeFile
}))
await updateObject(printerId, 'printer', {
...objectFormState.objectData,
pendingSlicerUploads: updatedUploads
})
}
const handleJobCreated = async (job) => {
const updatedUploads = pendingSlicerUploads.map((upload) =>
upload._id === currentPendingSlicerUpload?._id
? { ...upload, job }
: upload
)
setPendingSlicerUploads(updatedUploads)
setCurrentPendingSlicerUpload((previous) => ({
...previous,
job
}))
await updateObject(printerId, 'printer', {
...objectFormState.objectData,
pendingSlicerUploads: updatedUploads
})
}
const handleSlicerUploadComplete = async () => {
const updatedUploads = pendingSlicerUploads.filter(
(upload) => upload._id !== currentPendingSlicerUpload?._id
)
setPendingSlicerUploads(updatedUploads)
await updateObject(printerId, 'printer', {
...objectFormState.objectData,
pendingSlicerUploads: updatedUploads
})
}
const handleSlicerUploadFlowClosed = async () => {
const updatedUploads = pendingSlicerUploads.map((upload) =>
upload._id === currentPendingSlicerUpload?._id
? { ...upload, new: false }
: upload
)
setPendingSlicerUploads(updatedUploads)
await updateObject(printerId, 'printer', {
...objectFormState.objectData,
pendingSlicerUploads: updatedUploads
})
}
const slicerUploadsButton = () => {
if (pendingSlicerUploads.length > 0) {
return (
<Badge
count={pendingSlicerUploads.length}
style={{ marginTop: '8px' }}
size='small'
>
<Button
type='text'
icon={<FileIcon />}
onClick={showPendingSlicerUpload}
/>
</Badge>
)
}
return null
}
return (
<Layout style={{ height: 'var(--unit-100vh)' }}>
<Layout style={{ padding: '24px' }}>
<Content>
<Flex vertical style={{ height: '100%' }} gap='20px'>
<ObjectForm
id={printerId}
type='printer'
onStateChange={(state) =>
setEditFormState((previousState) => ({
...previousState,
...state
}))
}
>
{({ loading, objectData }) => (
<Flex justify='space-between' align='center'>
<Flex align='center' gap='middle'>
{loading ? (
<LoadingOutlined spin style={{ fontSize: '26px' }} />
) : (
<PrinterIcon style={{ fontSize: '26px' }} />
)}
<Title level={3} style={{ margin: 0 }}>
{loading
? 'Loading...'
: objectData?.name || 'Slicer Integration'}
</Title>
{!loading && (
<ObjectProperty
objectData={objectData}
{...getModelProperty('printer', 'state')}
/>
)}
</Flex>
{slicerUploadsButton()}
</Flex>
)}
</ObjectForm>
<Divider style={{ margin: '0' }} />
<ControlPrinter slicerIntegration={true} />
</Flex>
</Content>
<Modal
open={showSlicerUploadFlow}
onCancel={() => setShowSlicerUploadFlow(false)}
afterClose={handleSlicerUploadFlowClosed}
footer={null}
width={740}
destroyOnHidden={true}
>
{currentPendingSlicerUpload && (
<SlicerUploadFlow
key={currentPendingSlicerUpload._id}
currentPendingSlicerUpload={currentPendingSlicerUpload}
printer={objectFormState.objectData}
onGCodeFileCreated={handleGCodeFileCreated}
onJobCreated={handleJobCreated}
onComplete={handleSlicerUploadComplete}
/>
)}
</Modal>
</Layout>
</Layout>
)
}
export default SlicerIntegration

View File

@ -1,152 +0,0 @@
import { useMemo, useState } from 'react'
import PropTypes from 'prop-types'
import NewGCodeFile from '../GCodeFiles/NewGCodeFile.jsx'
import NewJob from '../Jobs/NewJob.jsx'
import DeployJob from '../Jobs/DeployJob.jsx'
import WizardView from '../../common/WizardView.jsx'
const SlicerUploadFlow = ({
currentPendingSlicerUpload,
printer,
onGCodeFileCreated,
onJobCreated,
onComplete
}) => {
const [createdGCodeFile, setCreatedGCodeFile] = useState(
() => currentPendingSlicerUpload.gcodeFile || null
)
const [createdJob, setCreatedJob] = useState(
() => currentPendingSlicerUpload.job || null
)
const [currentSubStep, setCurrentSubStep] = useState(0)
const gcodeFileDefaults = useMemo(
() => ({
...(currentPendingSlicerUpload?.properties || {}),
file: currentPendingSlicerUpload?.file
}),
[currentPendingSlicerUpload]
)
const jobDefaults = useMemo(
() => ({
quantity: 1,
gcodeFile: createdGCodeFile,
printers: [printer]
}),
[createdGCodeFile, printer]
)
const steps = [
{
title: 'GCode File',
key: 'gcodeFile',
subSteps: [
{
title: 'Required',
key: 'required'
},
{
title: 'Summary',
key: 'summary'
}
],
content: (
<NewGCodeFile
defaultValues={gcodeFileDefaults}
slicerIntegration={true}
shouldPrint={currentPendingSlicerUpload.shouldPrint}
currentStep={currentSubStep}
onStepChange={setCurrentSubStep}
onOk={(gcodeFile) => {
if (currentPendingSlicerUpload.shouldPrint) {
setCurrentSubStep(0)
setCreatedGCodeFile(gcodeFile)
onGCodeFileCreated(gcodeFile)
} else {
onComplete({ gcodeFile })
}
}}
/>
)
}
]
if (currentPendingSlicerUpload.shouldPrint) {
steps.push({
title: 'Print Job',
key: 'job',
subSteps: [
{
title: 'Required',
key: 'required'
},
{
title: 'Summary',
key: 'summary'
}
],
content: createdGCodeFile ? (
<NewJob
slicerIntegration={true}
defaultValues={jobDefaults}
currentStep={currentSubStep}
onStepChange={setCurrentSubStep}
onOk={(job) => {
if (job == null || job == undefined) {
onComplete({ gcodeFile: createdGCodeFile })
} else {
setCurrentSubStep(0)
setCreatedJob(job)
onJobCreated(job)
}
}}
/>
) : null
})
steps.push({
title: 'Deploy',
key: 'deploy',
content: createdJob ? (
<DeployJob
objectData={createdJob}
slicerIntegration={true}
onOk={() =>
onComplete({ gcodeFile: createdGCodeFile, job: createdJob })
}
/>
) : null
})
}
return (
<WizardView
title='Slicer Upload'
steps={steps}
currentStep={createdJob ? 2 : createdGCodeFile ? 1 : 0}
currentSubStep={currentSubStep}
showButtons={false}
showTitle={false}
showSteps={currentPendingSlicerUpload.shouldPrint}
formValid={true}
onSubmit={() => {}}
/>
)
}
SlicerUploadFlow.propTypes = {
currentPendingSlicerUpload: PropTypes.shape({
file: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
gcodeFile: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
job: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
shouldPrint: PropTypes.bool.isRequired,
properties: PropTypes.object
}).isRequired,
printer: PropTypes.object.isRequired,
onGCodeFileCreated: PropTypes.func.isRequired,
onJobCreated: PropTypes.func.isRequired,
onComplete: PropTypes.func.isRequired
}
export default SlicerUploadFlow

View File

@ -1,30 +0,0 @@
import PropTypes from 'prop-types'
import { Flex, Tag, Typography } from 'antd'
const { Text } = Typography
const ArrayDisplay = ({ value, prefix, suffix }) => {
if (!Array.isArray(value) || value.length === 0) {
return <Text type='secondary'>n/a</Text>
}
return (
<Flex gap='small' wrap>
{value.map((item, index) => (
<Tag key={`${item}-${index}`}>
{prefix}
{item}
{suffix}
</Tag>
))}
</Flex>
)
}
ArrayDisplay.propTypes = {
value: PropTypes.array,
prefix: PropTypes.node,
suffix: PropTypes.node
}
export default ArrayDisplay

View File

@ -1,83 +0,0 @@
import PropTypes from 'prop-types'
import { Button, Flex, Input, InputNumber } from 'antd'
const ArrayInput = ({
value = [],
onChange,
disabled = false,
numeric = false,
min,
max,
step,
placeholder
}) => {
const values = Array.isArray(value) ? value : []
const updateValue = (index, nextValue) => {
const nextValues = [...values]
nextValues[index] = nextValue
onChange?.(nextValues)
}
const removeValue = (index) => {
onChange?.(values.filter((_, valueIndex) => valueIndex !== index))
}
return (
<Flex vertical gap='small' style={{ width: '100%' }}>
{values.map((item, index) => (
<Flex key={index} gap='small' style={{ width: '100%' }}>
{numeric ? (
<InputNumber
value={item}
onChange={(nextValue) => updateValue(index, nextValue)}
min={min}
max={max}
step={step}
placeholder={placeholder}
disabled={disabled}
style={{ flex: 1 }}
/>
) : (
<Input
value={item}
onChange={(event) => updateValue(index, event.target.value)}
placeholder={placeholder}
disabled={disabled}
style={{ flex: 1 }}
/>
)}
<Button
onClick={() => removeValue(index)}
disabled={disabled}
danger
aria-label={`Remove ${placeholder || 'item'}`}
>
Remove
</Button>
</Flex>
))}
<Button
onClick={() => onChange?.([...values, numeric ? null : ''])}
disabled={disabled}
type='dashed'
block
>
Add {placeholder || 'item'}
</Button>
</Flex>
)
}
ArrayInput.propTypes = {
value: PropTypes.array,
onChange: PropTypes.func,
disabled: PropTypes.bool,
numeric: PropTypes.bool,
min: PropTypes.number,
max: PropTypes.number,
step: PropTypes.number,
placeholder: PropTypes.string
}
export default ArrayInput

View File

@ -76,7 +76,7 @@ const InputNumberCal = ({
const next = e.target.value
setExprValue(next)
const num = parseFloat(next)
if (next === '') {
if (!next) {
onChange?.(null)
} else if (!next.match(/[+\-*/=]/) && !Number.isNaN(num)) {
onChange?.(num)
@ -142,11 +142,11 @@ const InputNumberCal = ({
return (
<InputNumber
{...commonProps}
value={value != null ? value : null}
value={value}
onChange={onChange}
onBlur={onBlur}
onKeyDown={handleNumberKeyDown}
{...commonProps}
/>
)
}

View File

@ -1,66 +0,0 @@
import { Flex, Typography, Tag, Space } from 'antd'
import PropTypes from 'prop-types'
const { Text } = Typography
const getPrecision = (step) => {
if (step == null) return null
const stepStr = String(step)
const dotIndex = stepStr.indexOf('.')
return dotIndex === -1 ? 0 : stepStr.length - dotIndex - 1
}
const formatValue = (value, step) => {
if (value == null || value === '') return null
const parsed = parseFloat(value)
if (Number.isNaN(parsed)) return null
const precision = getPrecision(step)
return precision == null ? parsed.toString() : parsed.toFixed(precision)
}
const MinMaxDisplay = ({ value, prefix = '', suffix = '', step }) => {
const min = formatValue(value?.min, step)
const max = formatValue(value?.max, step)
return (
<Flex gap={'middle'} align='center' style={{ height: '100%' }}>
<Flex wrap>
<Text style={{ marginRight: 4 }}>
{min != null ? (
prefix + min + suffix
) : (
<Text type='secondary'>n/a</Text>
)}
</Text>
<Space>
<Tag>Min</Tag>
</Space>
</Flex>
<Flex wrap>
<Text style={{ marginRight: 4 }}>
{max != null ? (
prefix + max + suffix
) : (
<Text type='secondary'>n/a</Text>
)}
</Text>
<Space>
<Tag>Max</Tag>
</Space>
</Flex>
</Flex>
)
}
MinMaxDisplay.propTypes = {
value: PropTypes.shape({
min: PropTypes.number,
max: PropTypes.number
}),
prefix: PropTypes.string,
suffix: PropTypes.string,
step: PropTypes.number
}
export default MinMaxDisplay

View File

@ -1,65 +0,0 @@
import { Flex, InputNumber, Typography } from 'antd'
import PropTypes from 'prop-types'
const { Text } = Typography
const MinMaxInput = ({
value = {},
onChange,
prefix = '',
suffix = '',
min,
max,
step,
disabled = false
}) => {
const handleChange = (field, val) => {
if (!onChange) return
onChange({ ...value, [field]: val })
}
return (
<Flex gap={'middle'} align='center' style={{ width: '100%' }}>
<InputNumber
addonBefore={<Text type='secondary'>Min:</Text>}
value={value?.min}
onChange={(val) => handleChange('min', val)}
prefix={prefix}
suffix={suffix}
min={min}
max={max}
step={step}
disabled={disabled}
style={{ minWidth: 120, flex: 1 }}
/>
<InputNumber
addonBefore={<Text type='secondary'>Max:</Text>}
value={value?.max}
onChange={(val) => handleChange('max', val)}
prefix={prefix}
suffix={suffix}
min={min}
max={max}
step={step}
disabled={disabled}
style={{ minWidth: 120, flex: 1 }}
/>
</Flex>
)
}
MinMaxInput.propTypes = {
value: PropTypes.shape({
min: PropTypes.number,
max: PropTypes.number
}),
onChange: PropTypes.func,
prefix: PropTypes.string,
suffix: PropTypes.string,
min: PropTypes.number,
max: PropTypes.number,
step: PropTypes.number,
disabled: PropTypes.bool
}
export default MinMaxInput

View File

@ -3,11 +3,8 @@ import PropTypes from 'prop-types'
import { Table, Skeleton, Card, Button, Flex, Typography, Modal } from 'antd'
import PlusIcon from '../../Icons/PlusIcon'
import ObjectProperty from './ObjectProperty'
import ObjectInfo from './ObjectInfo'
import { LoadingOutlined } from '@ant-design/icons'
import BinIcon from '../../Icons/BinIcon'
import EyeIcon from '../../Icons/EyeIcon'
import EditIcon from '../../Icons/EditIcon'
const { Text, Link, Title } = Typography
const DEFAULT_COLUMN_WIDTHS = {
@ -67,7 +64,6 @@ const ObjectChildTable = ({
value = [],
rollups = [],
onChange,
hiddenPropertyWidth = '300px',
minimal = false,
label = '',
...tableProps
@ -77,9 +73,6 @@ const ObjectChildTable = ({
const generatedRowKeysRef = useRef(new WeakMap())
const generatedRowKeyCountRef = useRef(0)
const [minimalModelOpen, setMinimalModelOpen] = useState(false)
const [detailModalOpen, setDetailModalOpen] = useState(false)
const [detailModalRecord, setDetailModalRecord] = useState(null)
const [detailModalIndex, setDetailModalIndex] = useState(null)
const getFallbackRowKey = (record) => {
if (!record || typeof record !== 'object') {
return `object-child-table-row-${String(record)}`
@ -131,14 +124,15 @@ const ObjectChildTable = ({
}, [columns, properties])
const resolvedProperties = useMemo(() => {
const baseProperties =
columns && columns.length > 0
? orderedPropertyNames
.map((name) => propertyMap.get(name))
.filter(Boolean)
: properties
const explicit = orderedPropertyNames
.map((name) => propertyMap.get(name))
.filter(Boolean)
return baseProperties.filter((property) => {
const remaining = properties.filter(
(property) => !orderedPropertyNames.includes(property.name)
)
return [...explicit, ...remaining].filter((property) => {
if (!property?.name) return false
if (
visibleColumns &&
@ -148,87 +142,13 @@ const ObjectChildTable = ({
}
return true
})
}, [columns, orderedPropertyNames, propertyMap, properties, visibleColumns])
const hasHiddenProperties = useMemo(() => {
return (
Array.isArray(columns) &&
columns.length > 0 &&
Array.isArray(properties) &&
columns.length < properties.length
)
}, [columns, properties])
}, [orderedPropertyNames, propertyMap, properties, visibleColumns])
// When used inside antd Form.Item without Form.List, `value` will be the controlled array.
const itemsSource = useMemo(() => {
return value ?? []
}, [value])
const handleRemoveItem = useCallback(
(record, index) => {
const currentItems = Array.isArray(itemsSource) ? itemsSource : []
let newItems
if (typeof rowKey === 'string' && record[rowKey] != null) {
newItems = currentItems.filter(
(item) => item[rowKey] !== record[rowKey]
)
} else if (typeof rowKey === 'function') {
const recordKey = getResolvedRecordKey(record)
newItems = currentItems.filter((item) => {
const itemKey = getResolvedRecordKey(item)
return itemKey !== recordKey
})
} else {
newItems = currentItems.filter((_, i) => i !== index)
}
if (typeof onChange === 'function') {
onChange(newItems)
}
},
[itemsSource, onChange, rowKey, getResolvedRecordKey]
)
const handleOpenDetailModal = useCallback((record, index) => {
setDetailModalRecord({ ...record })
setDetailModalIndex(index)
setDetailModalOpen(true)
}, [])
const handleDetailPropertyChange = useCallback(
(propName, newVal) => {
const property = propertyMap.get(propName)
const resolved = resolveChangeValue(newVal, property?.type)
setDetailModalRecord((prev) =>
prev ? { ...prev, [propName]: resolved } : prev
)
if (detailModalIndex == null) return
const currentItems = Array.isArray(itemsSource) ? [...itemsSource] : []
const existingRowKey =
currentItems[detailModalIndex] != null
? getResolvedRecordKey(currentItems[detailModalIndex])
: null
const updatedItem = {
...currentItems[detailModalIndex],
[propName]: resolved
}
if (existingRowKey) {
generatedRowKeysRef.current.set(updatedItem, existingRowKey)
}
currentItems[detailModalIndex] = updatedItem
if (typeof onChange === 'function') {
onChange(currentItems)
}
},
[propertyMap, detailModalIndex, itemsSource, onChange, getResolvedRecordKey]
)
const tableColumns = useMemo(() => {
const propertyColumns = resolvedProperties.map((property) => ({
title: property.label || property.name,
@ -277,44 +197,54 @@ const ObjectChildTable = ({
}
}))
const actionsColumn =
hasHiddenProperties || (isEditing && canAddRemove)
const deleteColumn =
isEditing && canAddRemove
? {
title: '',
key: 'actions',
width: hasHiddenProperties && isEditing && canAddRemove ? 70 : 40,
key: 'delete',
width: 10,
fixed: 'right',
render: (_text, record, index) => {
if (record?.isSkeleton) {
return null
}
return (
<Flex gap={4}>
{hasHiddenProperties && (
<Button
type='text'
size='small'
icon={isEditing ? <EditIcon /> : <EyeIcon />}
onClick={(e) => {
e.stopPropagation()
handleOpenDetailModal(record, index)
}}
/>
)}
{isEditing && canAddRemove && (
<Button
type='text'
danger
size='small'
icon={<BinIcon />}
onClick={(e) => {
e.stopPropagation()
handleRemoveItem(record, index)
}}
/>
)}
</Flex>
<Button
type='text'
danger
size='small'
icon={<BinIcon />}
onClick={(e) => {
e.stopPropagation()
const currentItems = Array.isArray(itemsSource)
? itemsSource
: []
// Use record's unique identifier if available, otherwise use index
let newItems
if (typeof rowKey === 'string' && record[rowKey] != null) {
// Use the unique key to find and remove the item
newItems = currentItems.filter(
(item) => item[rowKey] !== record[rowKey]
)
} else if (typeof rowKey === 'function') {
// If rowKey is a function, find the item by comparing resolved keys.
// Ant Design deprecates index-based rowKey callbacks.
const recordKey = getResolvedRecordKey(record)
newItems = currentItems.filter((item) => {
const itemKey = getResolvedRecordKey(item)
return itemKey !== recordKey
})
} else {
// Fallback to index-based removal
newItems = currentItems.filter((_, i) => i !== index)
}
if (typeof onChange === 'function') {
onChange(newItems)
}
}}
/>
)
}
}
@ -323,20 +253,17 @@ const ObjectChildTable = ({
return [
...propertyColumns,
...additionalColumns,
...(actionsColumn ? [actionsColumn] : [])
...(deleteColumn ? [deleteColumn] : [])
]
}, [
resolvedProperties,
additionalColumns,
hasHiddenProperties,
isEditing,
canAddRemove,
itemsSource,
onChange,
rowKey,
getResolvedRecordKey,
handleOpenDetailModal,
handleRemoveItem
getResolvedRecordKey
])
const skeletonData = useMemo(() => {
@ -424,8 +351,8 @@ const ObjectChildTable = ({
}, [properties, rollups, objectData, value, name])
const rollupColumns = useMemo(() => {
const propertyColumns = resolvedProperties.map((property, index) => {
const nextProperty = resolvedProperties[index + 1]
const propertyColumns = properties.map((property, index) => {
const nextProperty = properties[index + 1]
var nextRollup = null
if (nextProperty) {
nextRollup = rollups?.find(
@ -457,12 +384,12 @@ const ObjectChildTable = ({
}
}
})
const blankActionsColumn =
hasHiddenProperties || (isEditing && canAddRemove)
const blankDeleteColumn =
isEditing && canAddRemove
? {
title: '',
key: 'actions',
width: hasHiddenProperties && isEditing && canAddRemove ? 70 : 40,
key: 'delete',
width: 40,
fixed: 'right',
render: () => {
return <Flex></Flex>
@ -471,15 +398,9 @@ const ObjectChildTable = ({
: null
return [
...propertyColumns,
...(blankActionsColumn ? [blankActionsColumn] : [])
...(blankDeleteColumn ? [blankDeleteColumn] : [])
]
}, [
resolvedProperties,
rollups,
isEditing,
canAddRemove,
hasHiddenProperties
])
}, [properties, rollups, isEditing, canAddRemove])
const hasRollups = useMemo(
() => Array.isArray(rollups) && rollups.length > 0,
@ -574,56 +495,25 @@ const ObjectChildTable = ({
</Flex>
)
const detailModal = hasHiddenProperties ? (
<Modal
open={detailModalOpen}
onCancel={() => setDetailModalOpen(false)}
footer={null}
width='900px'
>
{label ? (
<Title
level={2}
style={{ marginTop: 0, lineHeight: '0.7', marginBottom: 20 }}
>
{label}
</Title>
) : null}
<ObjectInfo
propertyDefinitions={properties}
objectData={detailModalRecord}
parentData={objectData}
labelWidth={hiddenPropertyWidth}
isEditing={isEditing}
onPropertyChange={isEditing ? handleDetailPropertyChange : undefined}
column={1}
showLabels={true}
/>
</Modal>
) : null
if (isEditing === true) {
return (
<>
<Card>
<Flex vertical gap={'middle'}>
<Flex justify={'space-between'}>
<Button>Actions</Button>
<Card>
<Flex vertical gap={'middle'}>
<Flex justify={'space-between'}>
<Button>Actions</Button>
{canAddRemove && (
<Button
type='primary'
icon={<PlusIcon />}
onClick={handleAddItem}
/>
)}
</Flex>
{tableComponent}
{canAddRemove && (
<Button
type='primary'
icon={<PlusIcon />}
onClick={handleAddItem}
/>
)}
</Flex>
</Card>
{detailModal}
</>
{tableComponent}
</Flex>
</Card>
)
}
@ -651,17 +541,11 @@ const ObjectChildTable = ({
</Title>
{tableComponent}
</Modal>
{detailModal}
</>
)
}
return (
<>
{tableComponent}
{detailModal}
</>
)
return tableComponent
}
ObjectChildTable.propTypes = {
@ -690,8 +574,7 @@ ObjectChildTable.propTypes = {
rollups: PropTypes.arrayOf(PropTypes.object),
objectData: PropTypes.object,
canAddRemove: PropTypes.bool,
minimal: PropTypes.bool,
hiddenPropertyWidth: PropTypes.string
minimal: PropTypes.bool
}
export default ObjectChildTable

View File

@ -13,10 +13,7 @@ const ObjectInfo = ({
showHyperlink,
showLabels = true,
objectData = null,
parentData = null,
properties = [],
propertyDefinitions = null,
onPropertyChange = null,
required = undefined,
visibleProperties = {},
objectPropertyProps = {},
@ -42,9 +39,7 @@ const ObjectInfo = ({
// If properties array is empty, show all properties
// Otherwise, filter and order by the properties array
let items
if (Array.isArray(propertyDefinitions) && propertyDefinitions.length > 0) {
items = propertyDefinitions
} else if (properties.length === 0) {
if (properties.length === 0) {
items = allItems
} else {
items = properties
@ -85,8 +80,6 @@ const ObjectInfo = ({
}
})
const isControlled = typeof onPropertyChange === 'function'
// Map items to Descriptions 'items' prop format
const descriptionItems = items.map((item, idx) => {
const key = item.name || item.label || idx
@ -105,17 +98,7 @@ const ObjectInfo = ({
{...objectPropertyProps}
isEditing={isEditing}
objectData={combinedObjectData}
parentData={parentData}
showSince={true}
useFormItem={isControlled ? false : objectPropertyProps.useFormItem}
value={
isControlled ? combinedObjectData?.[item.name] : undefined
}
onChange={
isControlled
? (newVal) => onPropertyChange(item.name, newVal)
: objectPropertyProps.onChange
}
/>
</div>
),
@ -147,11 +130,8 @@ ObjectInfo.propTypes = {
bordered: PropTypes.bool,
items: PropTypes.arrayOf(PropTypes.object),
isEditing: PropTypes.bool,
type: PropTypes.string,
type: PropTypes.string.isRequired,
objectData: PropTypes.object,
parentData: PropTypes.object,
propertyDefinitions: PropTypes.array,
onPropertyChange: PropTypes.func,
required: PropTypes.bool,
visibleProperties: PropTypes.object,
objectPropertyProps: PropTypes.object,

View File

@ -27,8 +27,6 @@ import { getPropertyValue } from '../../../database/ObjectModels'
import PropertyChanges from './PropertyChanges'
import NetGrossDisplay from './NetGrossDisplay'
import NetGrossInput from './NetGrossInput'
import MinMaxDisplay from './MinMaxDisplay'
import MinMaxInput from './MinMaxInput'
import InputNumberCal from './InputNumberCal'
import ObjectList from './ObjectList'
import VarianceDisplay from './VarianceDisplay'
@ -49,35 +47,10 @@ import FileList from './FileList'
import ObjectChildTable from './ObjectChildTable'
import MiscId from './MiscId'
import AddressDisplay from './AddressDisplay'
import ArrayDisplay from './ArrayDisplay'
import ArrayInput from './ArrayInput'
import { round } from '../utils/Utils'
const { Text } = Typography
const getTextValue = (value) => {
if (value == null || typeof value === 'string' || typeof value === 'number') {
return value
}
if (typeof value === 'object') {
if (typeof value._id === 'string') {
return value._id
}
if (typeof value._id?._id === 'string') {
return value._id._id
}
try {
return JSON.stringify(value)
} catch {
return String(value)
}
}
return String(value)
}
const timeEditFormItemProps = {
getValueProps: (v) => ({ value: v ? dayjs(v) : null })
}
@ -124,10 +97,7 @@ const ObjectProperty = ({
loading = false,
rollups = [],
showCard = true,
showDownload = true,
columns = [],
style = {},
hiddenPropertyWidth = '100px',
...rest
}) => {
if (value && typeof value == 'function' && objectData) {
@ -182,11 +152,7 @@ const ObjectProperty = ({
type = type(objectData, parentData)
}
if (columns && typeof columns == 'function' && objectData) {
columns = columns(objectData, parentData)
}
if (value == null) {
if (!value) {
value = getPropertyValue(objectData, name)
}
@ -217,15 +183,6 @@ const ObjectProperty = ({
return (
<NetGrossDisplay value={value} prefix={prefix} suffix={suffix} />
)
case 'minMax':
return (
<MinMaxDisplay
value={value}
prefix={prefix}
suffix={suffix}
step={step}
/>
)
case 'secret':
if (value != null) {
return <SecretDisplay value={value} {...rest} />
@ -356,9 +313,6 @@ const ObjectProperty = ({
)
}
}
case 'numberList':
case 'stringList':
return <ArrayDisplay value={value} prefix={prefix} suffix={suffix} />
case 'variance': {
if (value != null) {
return (
@ -377,7 +331,7 @@ const ObjectProperty = ({
return (
<Text ellipsis style={style}>
{prefix}
{getTextValue(value)}
{value}
{suffix}
</Text>
)
@ -478,10 +432,8 @@ const ObjectProperty = ({
loading={loading}
rollups={rollups}
size={size}
hiddenPropertyWidth={hiddenPropertyWidth}
minimal={minimal}
label={label}
columns={columns}
canAddRemove={canAddRemove}
/>
)
@ -619,7 +571,6 @@ const ObjectProperty = ({
defaultPreviewOpen={previewOpen}
showPreview={showPreview}
showInfo={showHyperlink}
showDownload={showDownload}
/>
)
}
@ -637,8 +588,8 @@ const ObjectProperty = ({
)
}
default: {
if (value != null && value !== '') {
return <Text {...textParams}>{getTextValue(value)}</Text>
if (value) {
return <Text {...textParams}>{value}</Text>
} else {
return (
<Text type='secondary' {...textParams}>
@ -669,59 +620,6 @@ const ObjectProperty = ({
}
mergedFormItemProps.rules = rules
}
if (type === 'minMax' && disabled == false) {
const rules = [...(mergedFormItemProps.rules || [])]
rules.push({
validator: (_, range) => {
if (range == null) return Promise.resolve()
if (typeof range !== 'object' || Array.isArray(range)) {
return Promise.reject(new Error('Value must be a min/max range'))
}
const values = [range.min, range.max].filter((item) => item != null)
const valid = values.every(
(item) =>
typeof item === 'number' &&
Number.isFinite(item) &&
(min == null || item >= min) &&
(max == null || item <= max)
)
return valid
? Promise.resolve()
: Promise.reject(new Error('Enter valid min and max values'))
}
})
mergedFormItemProps.rules = rules
}
if ((type === 'numberList' || type === 'stringList') && disabled == false) {
const rules = [...(mergedFormItemProps.rules || [])]
rules.push({
validator: (_, items) => {
if (items == null) return Promise.resolve()
if (!Array.isArray(items)) {
return Promise.reject(new Error('Value must be a list'))
}
const valid = items.every((item) => {
if (type === 'numberList') {
return (
typeof item === 'number' &&
Number.isFinite(item) &&
(min == null || item >= min) &&
(max == null || item <= max)
)
}
return typeof item === 'string' && item.trim().length > 0
})
return valid
? Promise.resolve()
: Promise.reject(new Error('Complete or remove empty list items'))
}
})
mergedFormItemProps.rules = rules
}
// Remove name from mergedFormItemProps if present
if (mergedFormItemProps.name) {
delete mergedFormItemProps.name
@ -759,17 +657,6 @@ const ObjectProperty = ({
{...inputProps}
/>
)
case 'minMax':
return (
<MinMaxInput
prefix={prefix}
suffix={suffix}
min={min}
max={max}
step={step}
{...inputProps}
/>
)
case 'secret':
return (
<Input.Password
@ -864,26 +751,6 @@ const ObjectProperty = ({
{...inputProps}
/>
)
case 'numberList':
return (
<ArrayInput
numeric
min={min}
max={max}
step={step}
placeholder={label}
disabled={disabled}
{...inputProps}
/>
)
case 'stringList':
return (
<ArrayInput
placeholder={label}
disabled={disabled}
{...inputProps}
/>
)
case 'text':
return <Input placeholder={label} {...inputProps} />
case 'codeBlock':
@ -964,11 +831,9 @@ const ObjectProperty = ({
<ObjectChildTable
name={name}
properties={properties}
columns={columns}
objectData={objectData}
isEditing={true}
rollups={rollups}
hiddenPropertyWidth={hiddenPropertyWidth}
size={size}
canAddRemove={canAddRemove}
{...inputProps}
@ -1036,9 +901,7 @@ ObjectProperty.propTypes = {
rollups: PropTypes.arrayOf(PropTypes.object),
canAddRemove: PropTypes.bool,
showCard: PropTypes.bool,
showDownload: PropTypes.bool,
style: PropTypes.object,
hiddenPropertyWidth: PropTypes.string
style: PropTypes.object
}
export default ObjectProperty

View File

@ -217,14 +217,7 @@ const ObjectSelect = ({
if (pIdx >= properties.length) {
return data.map((object) => {
setObjectList((prev) => {
if (
prev.some(
(p) =>
p._id.toString().toLowerCase() ===
object._id.toString().toLowerCase()
)
)
return prev
if (prev.some((p) => p._id === object._id)) return prev
return [...prev, object]
})
return {
@ -242,8 +235,8 @@ const ObjectSelect = ({
/>
</div>
),
value: object._id.toString().toLowerCase(),
key: object._id.toString().toLowerCase(),
value: object._id,
key: object._id,
isLeaf: true,
parentKeys,
filterPath

View File

@ -106,9 +106,7 @@ const ObjectTable = forwardRef(
size = 'middle',
onStateChange,
showFilterSidebar = false,
expandHeight = false,
showActions = true,
onRowAction
expandHeight = false
},
ref
) => {
@ -188,17 +186,14 @@ const ObjectTable = forwardRef(
const rowActions =
model.actions?.filter((action) => action.row == true) || []
const createSkeletonData = useCallback(
(pageNum) => {
return Array(pageSize)
.fill(null)
.map((_, index) => ({
_id: `skeleton-${pageNum}-${index}`,
isSkeleton: true
}))
},
[pageSize]
)
const createSkeletonData = useCallback((pageNum) => {
return Array(pageSize)
.fill(null)
.map((_, index) => ({
_id: `skeleton-${pageNum}-${index}`,
isSkeleton: true
}))
}, [pageSize])
const createSkeletonPage = useCallback(
(pageNum) => ({
@ -241,12 +236,6 @@ const ObjectTable = forwardRef(
type={'text'}
size={'small'}
onClick={() => {
if (
onRowAction &&
onRowAction(action, objectData) !== false
) {
return
}
if (action.url) {
navigate(action.url(objectData._id))
}
@ -325,7 +314,9 @@ const ObjectTable = forwardRef(
hasMore: result.hasMore
}
setPages((prev) =>
prev.map((page) => (page.pageNum === pageNum ? loadedPage : page))
prev.map((page) =>
page.pageNum === pageNum ? loadedPage : page
)
)
setLoading(false)
return result.data || []
@ -339,9 +330,9 @@ const ObjectTable = forwardRef(
const findRenderedRow = useCallback((scrollTarget, id) => {
if (!scrollTarget || id == null) return null
return Array.from(scrollTarget.querySelectorAll('tr[data-row-key]')).find(
(row) => row.getAttribute('data-row-key') === String(id)
)
return Array.from(
scrollTarget.querySelectorAll('tr[data-row-key]')
).find((row) => row.getAttribute('data-row-key') === String(id))
}, [])
const captureScrollAnchor = useCallback(
@ -369,7 +360,9 @@ const ObjectTable = forwardRef(
}
const placeholderAnchor = closestRow(placeholderPage.items)
const loadedPages = currentPages.filter((page) => !page.isSkeletonPage)
const loadedPages = currentPages.filter(
(page) => !page.isSkeletonPage
)
const retainedPage =
direction === 'previous'
? loadedPages[0]
@ -387,7 +380,8 @@ const ObjectTable = forwardRef(
(scrollTarget, anchor, loadedPage) => {
if (!scrollTarget || !anchor) return
const loadedItem = loadedPage.items[anchor.placeholderAnchor?.index]
const loadedItem =
loadedPage.items[anchor.placeholderAnchor?.index]
const anchorId = loadedItem?._id ?? anchor.fallbackAnchor?.id
const previousTop = loadedItem
? anchor.placeholderAnchor?.top
@ -469,7 +463,11 @@ const ObjectTable = forwardRef(
}
}
},
[captureScrollAnchor, createPageWindow, fetchPage]
[
captureScrollAnchor,
createPageWindow,
fetchPage
]
)
const loadNextPage = useCallback(
@ -728,7 +726,10 @@ const ObjectTable = forwardRef(
]
if (firstResult.hasMore) {
setPages([...loadedPages, createSkeletonPage(pageNum + 1)])
setPages([
...loadedPages,
createSkeletonPage(pageNum + 1)
])
const secondResult = await fetchPage(pageNum + 1)
loadedPages.push({
pageNum: pageNum + 1,
@ -1003,11 +1004,7 @@ const ObjectTable = forwardRef(
}
})
if (
showActions &&
rowActions.length > 0 &&
tableData.some((item) => !item?.isSkeleton)
) {
if (rowActions.length > 0 && tableData.some((item) => !item?.isSkeleton)) {
columnsWithSkeleton.push({
title: (
<Flex gap='small' align='center' justify='center'>
@ -1188,9 +1185,7 @@ ObjectTable.propTypes = {
size: PropTypes.string,
onStateChange: PropTypes.func,
showFilterSidebar: PropTypes.bool,
expandHeight: PropTypes.bool,
showActions: PropTypes.bool,
onRowAction: PropTypes.func
expandHeight: PropTypes.bool
}
export default ObjectTable

View File

@ -9,15 +9,13 @@ import {
Divider,
Progress,
Button,
Dropdown,
Timeline
Dropdown
} from 'antd'
import NewObjectButtons from './NewObjectButtons'
const { Title } = Typography
const WizardView = ({
sizeBarWidth = '160px',
showSteps = true,
steps,
title = 'Wizard View',
@ -29,76 +27,22 @@ const WizardView = ({
progress = 0,
actions = [],
sideBarGrow = false,
disabled = false,
showButtons = true,
showTitle = true,
currentStep: controlledCurrentStep,
currentSubStep = 0,
onStepChange,
showSkipButton = false,
onSkip = () => {}
disabled = false
}) => {
const [internalCurrentStep, setInternalCurrentStep] = useState(0)
const currentStep = controlledCurrentStep ?? internalCurrentStep
const [currentStep, setCurrentStep] = useState(0)
const isMobile = useMediaQuery({ maxWidth: 768 })
const setCurrentStep = (step) => {
if (controlledCurrentStep === undefined) {
setInternalCurrentStep(step)
}
onStepChange?.(step)
}
return (
<Flex gap='middle' style={{ width: '100%' }}>
{!isMobile && showSteps == true ? (
sideBar != null ? (
sideBar
) : (
<div
style={{ minWidth: sideBarGrow == true ? '100%' : sizeBarWidth }}
>
<div style={{ minWidth: sideBarGrow == true ? '100%' : '160px' }}>
<Steps
current={currentStep}
items={steps.map(({ subSteps, ...step }, index) => ({
items={steps.map((step, index) => ({
...step,
description: subSteps ? (
<Timeline
className='subSteps'
items={subSteps.map((subStep, subStepIndex) => {
const complete =
index < currentStep ||
(index === currentStep && subStepIndex < currentSubStep)
const current =
index === currentStep && subStepIndex === currentSubStep
const disabled = index < currentStep
return {
...subStep,
children: subStep.title,
dot: complete ? (
<div
className={`subSteps-dot ${disabled ? 'subSteps-dot-disabled' : ''}`}
>
<CheckIcon
style={{
fontSize: '5px',
marginTop: '0px',
marginRight: '0px',
color: disabled
? 'var(--color-primary)'
: 'var(--layout-modal-bg)'
}}
/>
</div>
) : undefined,
color: complete ? 'blue' : current ? 'blue' : 'gray'
}
})}
/>
) : (
step.description
),
icon:
index < currentStep ? (
<div className='ant-steps-item-icon'>
@ -132,83 +76,62 @@ const WizardView = ({
}
>
<Flex vertical gap='middle' style={{ flexGrow: 1, width: '100%' }}>
{showTitle ? (
<Title level={2} style={{ margin: '0 0 8px 0', lineHeight: 0.7 }}>
{title}
</Title>
) : null}
<Title level={2} style={{ margin: '0 0 8px 0', lineHeight: 0.7 }}>
{title}
</Title>
<div style={{ minHeight: '260px', marginBottom: 4, width: '100%' }}>
{steps[currentStep].content}
</div>
</Flex>
{showButtons || progress > 0 || actions.length > 0 ? (
<Flex
gap={'middle'}
align='center'
justify={showSkipButton ? 'space-between' : 'end'}
>
{showSkipButton && showButtons && (
<Button onClick={onSkip} disabled={disabled}>
Skip
</Button>
)}
<Flex gap={'middle'} align='center' justify='end'>
{progress > 0 ? (
<Progress
style={{ maxWidth: '160px' }}
showInfo={false}
percent={progress}
/>
) : null}
{(actions || []).map((action) => {
if (action.steps.includes(steps[currentStep].key)) {
if (action.children) {
return (
<Dropdown
menu={{ items: action.children }}
key={action.key}
>
<Button
onClick={action?.onClick}
disabled={action?.disabled || disabled}
loading={action?.loading || false}
>
{action.label}
</Button>
</Dropdown>
)
}
return (
<Flex gap={'middle'} align='center' justify='end'>
{progress > 0 ? (
<Progress
style={{ maxWidth: '160px' }}
showInfo={false}
percent={progress}
/>
) : null}
{(actions || []).map((action) => {
if (action.steps.includes(steps[currentStep].key)) {
if (action.children) {
return (
<Dropdown menu={{ items: action.children }} key={action.key}>
<Button
key={action.key}
onClick={action?.onClick}
disabled={action?.disabled || disabled}
loading={action?.loading || false}
>
{action.label}
</Button>
)
}
return null
})}
</Dropdown>
)
}
return (
<Button
key={action.key}
onClick={action?.onClick}
disabled={action?.disabled || disabled}
loading={action?.loading || false}
>
{action.label}
</Button>
)
}
return null
})}
{showButtons && (
<NewObjectButtons
disabled={disabled}
currentStep={currentStep}
totalSteps={steps.length}
onPrevious={() => setCurrentStep(currentStep - 1)}
onNext={() => setCurrentStep(currentStep + 1)}
onSubmit={onSubmit}
formValid={formValid}
submitLoading={loading}
submitText={submitText}
/>
)}
</Flex>
</Flex>
) : null}
<NewObjectButtons
disabled={disabled}
currentStep={currentStep}
totalSteps={steps.length}
onPrevious={() => setCurrentStep((prev) => prev - 1)}
onNext={() => setCurrentStep((prev) => prev + 1)}
onSubmit={onSubmit}
formValid={formValid}
submitLoading={loading}
submitText={submitText}
/>
</Flex>
</Flex>
</Flex>
)
@ -226,15 +149,7 @@ WizardView.propTypes = {
submitText: PropTypes.string,
progress: PropTypes.number,
actions: PropTypes.array,
sideBarGrow: PropTypes.bool,
showButtons: PropTypes.bool,
showTitle: PropTypes.bool,
currentStep: PropTypes.number,
currentSubStep: PropTypes.number,
onStepChange: PropTypes.func,
showSkipButton: PropTypes.bool,
onSkip: PropTypes.func,
sizeBarWidth: PropTypes.string
sideBarGrow: PropTypes.bool
}
export default WizardView

View File

@ -69,7 +69,6 @@ const AuthProvider = ({ children }) => {
const [userProfile, setUserProfile] = useState(null)
const [profileImageUrl, setProfileImageUrl] = useState(null)
const profileImageUrlRef = useRef(null)
const processedAuthCodeRef = useRef(null)
const [showSessionExpiredModal, setShowSessionExpiredModal] = useState(false)
const [showUnauthorizedModal, setShowUnauthorizedModal] = useState(false)
const [showAuthErrorModal, setShowAuthErrorModal] = useState(false)
@ -166,8 +165,6 @@ const AuthProvider = ({ children }) => {
// Read token from cookies (web) or electron session storage if present
useEffect(() => {
let cancelled = false
const authCode =
new URLSearchParams(location.search).get('authCode') || null
const load = async () => {
try {
@ -230,9 +227,7 @@ const AuthProvider = ({ children }) => {
}
}
// An auth code represents a newly completed login and must take precedence
// over any existing persisted session.
if (location.pathname === '/applaunch' || authCode != null) {
if (location.pathname === '/applaunch') {
return
}
@ -436,7 +431,6 @@ const AuthProvider = ({ children }) => {
setExpiresAt(nextExpiresAt)
setUserProfile(nextUser)
setAuthenticated(true)
setShowUnauthorizedModal(false)
// Persist session (cookies on web, electron storage on desktop)
const persisted = await persistSession({
@ -475,7 +469,6 @@ const AuthProvider = ({ children }) => {
setAuthenticated(false)
} finally {
setLoading(false)
setRetreivedTokenFromCookies(true)
}
},
[
@ -730,11 +723,7 @@ const AuthProvider = ({ children }) => {
useEffect(() => {
const authCode =
new URLSearchParams(location.search).get('authCode') || null
if (
authCode != null &&
processedAuthCodeRef.current !== authCode
) {
processedAuthCodeRef.current = authCode
if (authCode != null) {
getLoginToken(authCode)
} else if (
token == null &&
@ -811,12 +800,7 @@ const AuthProvider = ({ children }) => {
Please log in to continue
</Space>
}
open={
showUnauthorizedModal &&
!authenticated &&
!loading &&
!showAuthErrorModal
}
open={showUnauthorizedModal && !loading && !showAuthErrorModal}
onOk={() => {
setShowUnauthorizedModal(false)
loginWithSSO()

View File

@ -12,8 +12,6 @@ const ThemeContext = createContext()
const COLORS = {
colorPrimary: '#0091FF',
colorPrimaryDisabledDark: '#0e3a5b',
colorPrimaryDisabledLight: '#e6f8ff',
colorSuccess: '#30D158',
colorWarning: '#FF9230',
colorError: '#FF4245',
@ -40,8 +38,6 @@ export const ThemeProvider = ({ children }) => {
return savedCompact ? JSON.parse(savedCompact) : false
})
const [primaryColorOverride, setPrimaryColorOverride] = useState(null)
// System theme detection
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
@ -128,16 +124,7 @@ export const ThemeProvider = ({ children }) => {
// Set CSS custom properties for theme colors
useEffect(() => {
const root = document.documentElement
root.style.setProperty(
'--color-primary',
primaryColorOverride == null ? COLORS.colorPrimary : primaryColorOverride
)
root.style.setProperty(
'--color-primary-disabled',
isDarkMode
? COLORS.colorPrimaryDisabledDark
: COLORS.colorPrimaryDisabledLight
)
root.style.setProperty('--color-primary', COLORS.colorPrimary)
root.style.setProperty('--color-success', COLORS.colorSuccess)
root.style.setProperty('--color-warning', COLORS.colorWarning)
root.style.setProperty('--color-error', COLORS.colorError)
@ -152,20 +139,12 @@ export const ThemeProvider = ({ children }) => {
'--layout-header-bg',
isDarkMode ? '#141414' : '#ffffff'
)
root.style.setProperty(
'--layout-modal-bg',
isDarkMode ? '#1f1f1f' : '#ffffff'
)
}, [isDarkMode, primaryColorOverride])
}, [isDarkMode])
const themeConfig = {
algorithm: getThemeAlgorithm(),
token: {
...COLORS,
colorPrimary:
primaryColorOverride == null
? COLORS.colorPrimary
: primaryColorOverride,
borderRadius: '12px'
},
components: {
@ -187,7 +166,6 @@ export const ThemeProvider = ({ children }) => {
setThemeMode,
setDensityMode,
getColors,
setPrimaryColorOverride,
themeConfig
}}
>

View File

@ -1,8 +0,0 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/filamentprofileicon.svg?react'
const FilamentProfileIcon = (props) => (
<Icon component={CustomIconSvg} {...props} />
)
export default FilamentProfileIcon

View File

@ -1,6 +0,0 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/hotendicon.svg?react'
const HotEndIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default HotEndIcon

View File

@ -1,6 +0,0 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/multimotionicon.svg?react'
const MultiMotionIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default MultiMotionIcon

View File

@ -1,6 +0,0 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/pictureicon.svg?react'
const PictureIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default PictureIcon

View File

@ -1,8 +0,0 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/printerprofileicon.svg?react'
const PrinterProfileIcon = (props) => (
<Icon component={CustomIconSvg} {...props} />
)
export default PrinterProfileIcon

View File

@ -1,6 +0,0 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/rulericon.svg?react'
const RulerIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default RulerIcon

View File

@ -1,7 +1,5 @@
import ProductionIcon from './ProductionIcon'
import PrinterIcon from './PrinterIcon'
import PrinterProfileIcon from './PrinterProfileIcon'
import FilamentProfileIcon from './FilamentProfileIcon'
import JobIcon from './JobIcon'
import GCodeFileIcon from './GCodeFileIcon'
import SubJobIcon from './SubJobIcon'
@ -57,8 +55,6 @@ const toEmoji = (emoji) => <span aria-hidden>{emoji}</span>
const sidebarIconMap = {
production: <ProductionIcon />,
printer: <PrinterIcon />,
printerProfile: <PrinterProfileIcon />,
filamentProfile: <FilamentProfileIcon />,
job: <JobIcon />,
subJob: <SubJobIcon />,
gcodeFile: <GCodeFileIcon />,

View File

@ -1,6 +1,4 @@
import { Printer } from './models/Printer.js'
import { PrinterProfile } from './models/PrinterProfile.js'
import { FilamentProfile } from './models/FilamentProfile.js'
import { Host } from './models/Host.js'
import { Filament } from './models/Filament.js'
import { Material } from './models/Material.js'
@ -51,8 +49,6 @@ import QuestionCircleIcon from '../components/Icons/QuestionCircleIcon'
export const objectModels = [
Printer,
PrinterProfile,
FilamentProfile,
Host,
Filament,
FilamentSku,
@ -104,8 +100,6 @@ export const objectModels = [
// Re-export individual models for direct access
export {
Printer,
PrinterProfile,
FilamentProfile,
Host,
Filament,
FilamentSku,

File diff suppressed because it is too large Load Diff

View File

@ -144,14 +144,9 @@ export const GCodeFile = {
label: 'Name',
columnFixed: 'left',
type: 'text',
value: null,
required: true,
columnWidth: 200,
value: (objectData) => {
if (objectData?.name == undefined) {
return objectData?.file?.name
}
return objectData?.name
}
columnWidth: 200
},
{
name: 'file',

View File

@ -1,665 +0,0 @@
import PrinterProfileIcon from '../../components/Icons/PrinterProfileIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import BinIcon from '../../components/Icons/BinIcon'
const THUMBNAIL_PROPERTIES = [
{
name: 'width',
label: 'Width',
type: 'number',
min: 0,
step: 1,
columnWidth: 120
},
{
name: 'height',
label: 'Height',
type: 'number',
min: 0,
step: 1,
columnWidth: 120
}
]
const BED_EXCLUDE_AREA_PROPERTIES = [
{
name: 'x',
label: 'X',
type: 'number',
step: 0.01,
columnWidth: 120
},
{
name: 'y',
label: 'Y',
type: 'number',
step: 0.01,
columnWidth: 120
}
]
const EXTRUDER_PROPERTIES = [
{
name: 'nozzleDiameter',
label: 'Nozzle Diameter',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.05,
columnWidth: 150
},
{
name: 'nozzleVolume',
label: 'Nozzle Volume',
type: 'number',
suffix: ' mm³',
min: 0,
step: 0.1,
columnWidth: 150
},
{
name: 'nozzleType',
label: 'Nozzle Type',
type: 'text',
columnWidth: 160
},
{
name: 'minLayerHeight',
label: 'Min Layer Height',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.01,
columnWidth: 180
},
{
name: 'maxLayerHeight',
label: 'Max Layer Height',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.01,
columnWidth: 180
},
{
name: 'positionOffsetX',
label: 'Extruder Offset X',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 180
},
{
name: 'positionOffsetY',
label: 'Extruder Offset Y',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 180
},
{
name: 'retractionLength',
label: 'Retraction Length',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 170
},
{
name: 'retractionExtraLengthOnRestart',
label: 'Extra Length on Restart',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 210
},
{
name: 'retractionSpeed',
label: 'Retraction Speed',
type: 'number',
suffix: ' mm/s',
min: 0,
step: 1,
columnWidth: 170
},
{
name: 'deretractionSpeed',
label: 'Deretraction Speed',
type: 'number',
suffix: ' mm/s',
min: 0,
step: 1,
columnWidth: 180
},
{
name: 'retractionTravelDistanceThreshold',
label: 'Travel Distance Threshold',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 220
},
{
name: 'retractOnLayerChange',
label: 'Retract on Layer Change',
type: 'bool',
columnWidth: 210
},
{
name: 'wipeWhileRetracting',
label: 'Wipe While Retracting',
type: 'bool',
columnWidth: 190
},
{
name: 'wipeDistance',
label: 'Wipe Distance',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 150
},
{
name: 'retractAmountBeforeWipe',
label: 'Retract Amount Before Wipe',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 230
},
{
name: 'retractAmountAfterWipe',
label: 'Retract Amount After Wipe',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 230
},
{
name: 'zHopOnSurfaces',
label: 'Z-Hop On Surfaces',
type: 'select',
options: [
{ label: 'All Surfaces', value: 'All Surfaces' },
{ label: 'Top Only', value: 'Top Only' },
{ label: 'Bottom Only', value: 'Bottom Only' },
{ label: 'None', value: 'None' }
],
columnWidth: 170
},
{
name: 'zHopType',
label: 'Z-Hop Type',
type: 'select',
options: [
{ label: 'Auto', value: 'Auto' },
{ label: 'Normal', value: 'Normal' },
{ label: 'Spiral', value: 'Spiral' },
{ label: 'Slope', value: 'Slope' }
],
columnWidth: 140
},
{
name: 'zHopHeight',
label: 'Z-Hop Height',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.01,
columnWidth: 150
},
{
name: 'zHopTravelingAngle',
label: 'Traveling Angle',
type: 'number',
suffix: '°',
min: 0,
step: 1,
columnWidth: 160
},
{
name: 'zHopOnlyLiftZAbove',
label: 'Only Lift Z Above',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.01,
columnWidth: 170
},
{
name: 'zHopOnlyLiftZBelow',
label: 'Only Lift Z Below',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.01,
columnWidth: 170
},
{
name: 'materialSwitchRetractionLength',
label: 'Material Switch Retraction Length',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 250
},
{
name: 'materialSwitchExtraLengthOnRestart',
label: 'Material Switch Extra Length on Restart',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 290
},
{
name: 'longRetractionWhenCut',
label: 'Long Retraction When Cut (beta)',
type: 'bool',
columnWidth: 240
}
]
export const PrinterProfile = {
name: 'printerProfile',
label: 'Printer Profile',
labelPlural: 'Printer Profiles',
url: '/dashboard/production/printerprofiles',
prefix: 'PPF',
icon: PrinterProfileIcon,
actions: [
{
name: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon,
url: (_id) =>
`/dashboard/production/printerprofiles/info?printerProfileId=${_id}`
},
{
name: 'edit',
label: 'Edit',
row: true,
icon: EditIcon,
url: (_id) =>
`/dashboard/production/printerprofiles/info?printerProfileId=${_id}&action=edit`,
visible: (objectData) => !objectData?._isEditing
},
{
name: 'finishEdit',
label: 'Save Edits',
icon: CheckIcon,
url: (_id) =>
`/dashboard/production/printerprofiles/info?printerProfileId=${_id}&action=finishEdit`,
visible: (objectData) => objectData?._isEditing === true
},
{
name: 'cancelEdit',
label: 'Cancel Edits',
icon: XMarkIcon,
url: (_id) =>
`/dashboard/production/printerprofiles/info?printerProfileId=${_id}&action=cancelEdit`,
visible: (objectData) => objectData?._isEditing === true
},
{ type: 'divider' },
{
name: 'delete',
label: 'Delete',
row: true,
icon: BinIcon,
danger: true,
url: (_id) =>
`/dashboard/production/printerprofiles/info?printerProfileId=${_id}&action=delete`
}
],
columns: [
'_reference',
'name',
'printer',
'printBedWidth',
'printBedHeight',
'printableHeight',
'extruders.0.nozzleDiameter',
'extruders.0.nozzleType',
'zOffset',
'scanFirstLayer',
'auxiliaryFan',
'updatedAt'
],
filters: ['_id', 'name', 'printer'],
sorters: [
'name',
'printBedWidth',
'printBedHeight',
'printableHeight',
'zOffset',
'createdAt',
'updatedAt'
],
properties: [
{
name: '_id',
label: 'ID',
type: 'id',
objectType: 'printerProfile',
showCopy: true,
readOnly: true,
columnWidth: 140
},
{
name: 'createdAt',
label: 'Created At',
type: 'dateTime',
readOnly: true,
columnWidth: 175
},
{
name: '_reference',
label: 'Reference',
type: 'reference',
objectType: 'printerProfile',
showCopy: true,
readOnly: true,
columnFixed: 'left',
columnWidth: 180
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true,
columnWidth: 175
},
{
name: 'name',
label: 'Name',
type: 'text',
required: true,
columnFixed: 'left',
columnWidth: 220,
value: (objectData) => {
if (objectData?.name == undefined) {
return objectData?.printer?.name
}
return objectData?.name
}
},
{
name: 'printer',
label: 'Printer',
type: 'object',
objectType: 'printer',
required: true,
showHyperlink: true,
columnWidth: 220
},
{
name: 'printBedWidth',
label: 'Print Bed Width',
type: 'number',
suffix: ' mm',
min: 0,
step: 1,
columnWidth: 170
},
{
name: 'printBedHeight',
label: 'Print Bed Height',
type: 'number',
suffix: ' mm',
min: 0,
step: 1,
columnWidth: 170
},
{
name: 'originX',
label: 'Origin X',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 150
},
{
name: 'originY',
label: 'Origin Y',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 150
},
{
name: 'bestObjectPositionX',
label: 'Best Object Position X',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 200
},
{
name: 'bestObjectPositionY',
label: 'Best Object Position Y',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 200
},
{
name: 'bedExcludeArea',
label: 'Excluded Bed Area',
type: 'objectChildren',
canAddRemove: true,
span: 2,
columns: ['x', 'y'],
properties: BED_EXCLUDE_AREA_PROPERTIES
},
{
name: 'printableHeight',
label: 'Printable Height',
type: 'number',
suffix: ' mm',
min: 0,
step: 1,
columnWidth: 170
},
{
name: 'supportMultiBedTypes',
label: 'Support Multi Bed Types',
type: 'bool',
columnWidth: 200
},
{
name: 'zOffset',
label: 'Z Offset',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 140
},
{
name: 'preferredOrientation',
label: 'Preferred Orientation',
type: 'number',
suffix: '°',
min: 0,
max: 360,
step: 1,
columnWidth: 190
},
{
name: 'extruders.0.nozzleDiameter',
label: 'Nozzle Diameter',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.05,
readOnly: true,
columnWidth: 150
},
{
name: 'extruders.0.nozzleType',
label: 'Nozzle Type',
type: 'text',
readOnly: true,
columnWidth: 160
},
{
name: 'extruders',
label: 'Extruders',
type: 'objectChildren',
canAddRemove: true,
span: 2,
columns: [
'nozzleDiameter',
'nozzleType',
'nozzleVolume',
'minLayerHeight',
'maxLayerHeight',
'positionOffsetX',
'positionOffsetY'
],
hiddenPropertyWidth: 330,
properties: EXTRUDER_PROPERTIES
},
{
name: 'machineMaxAccelerationRetracting',
label: 'Max Retracting Accel',
type: 'minMax',
suffix: ' mm/s²',
min: 0,
step: 1,
columnWidth: 260
},
{
name: 'machineMaxSpeedE',
label: 'Max Extruder Speed',
type: 'minMax',
suffix: ' mm/s',
min: 0,
step: 1,
columnWidth: 220
},
{
name: 'machineMaxSpeedX',
label: 'Max X Speed',
type: 'minMax',
suffix: ' mm/s',
min: 0,
step: 1,
columnWidth: 190
},
{
name: 'machineMaxSpeedY',
label: 'Max Y Speed',
type: 'minMax',
suffix: ' mm/s',
min: 0,
step: 1,
columnWidth: 190
},
{
name: 'machinePauseGcode',
label: 'Pause G-code',
type: 'codeBlock',
language: 'gcode',
height: '180px',
span: 2
},
{
name: 'machineStartGcode',
label: 'Start G-code',
type: 'codeBlock',
language: 'gcode',
height: '240px',
span: 2
},
{
name: 'machineEndGcode',
label: 'End G-code',
type: 'codeBlock',
language: 'gcode',
height: '240px',
span: 2
},
{
name: 'layerChangeGcode',
label: 'Layer Change G-code',
type: 'codeBlock',
language: 'gcode',
height: '180px',
span: 2
},
{
name: 'beforeLayerChangeGcode',
label: 'Before Layer Change G-code',
type: 'codeBlock',
language: 'gcode',
height: '180px',
span: 2
},
{
name: 'machineLoadFilamentTime',
label: 'Filament Load Time',
type: 'number',
suffix: ' s',
min: 0,
step: 0.1,
columnWidth: 180
},
{
name: 'scanFirstLayer',
label: 'Scan First Layer',
type: 'bool',
columnWidth: 150
},
{
name: 'machineUnloadFilamentTime',
label: 'Filament Unload Time',
type: 'number',
suffix: ' s',
min: 0,
step: 0.1,
columnWidth: 190
},
{
name: 'thumbnails',
label: 'Thumbnail Sizes',
type: 'objectChildren',
canAddRemove: true,
span: 2,
columns: ['width', 'height'],
properties: THUMBNAIL_PROPERTIES
},
{
name: 'auxiliaryFan',
label: 'Auxiliary Fan',
type: 'bool',
columnWidth: 140
},
{
name: 'printerNotes',
label: 'Printer Notes',
type: 'markdown',
span: 2
},
{
name: 'machineMaxJunctionDeviation',
label: 'Max Junction Deviation',
type: 'minMax',
suffix: ' mm',
min: 0,
step: 0.01,
columnWidth: 240
}
]
}

View File

@ -24,24 +24,11 @@ const productionSidebarItems = [
label: 'Sub Jobs',
path: '/dashboard/production/subjobs'
},
{ type: 'divider' },
{
key: 'gcodeFiles',
iconKey: 'gcodeFile',
label: 'GCode Files',
path: '/dashboard/production/gcodefiles'
},
{
key: 'printerProfiles',
iconKey: 'printerProfile',
label: 'Printer Profiles',
path: '/dashboard/production/printerprofiles'
},
{
key: 'filamentProfiles',
iconKey: 'filamentProfile',
label: 'Filament Profiles',
path: '/dashboard/production/filamentprofiles'
}
]

View File

@ -13,20 +13,6 @@ const ControlPrinter = lazy(
const PrinterInfo = lazy(
() => import('../components/Dashboard/Production/Printers/PrinterInfo.jsx')
)
const PrinterProfiles = lazy(
() => import('../components/Dashboard/Production/PrinterProfiles.jsx')
)
const PrinterProfileInfo = lazy(
() =>
import('../components/Dashboard/Production/PrinterProfiles/PrinterProfileInfo.jsx')
)
const FilamentProfiles = lazy(
() => import('../components/Dashboard/Production/FilamentProfiles.jsx')
)
const FilamentProfileInfo = lazy(
() =>
import('../components/Dashboard/Production/FilamentProfiles/FilamentProfileInfo.jsx')
)
const Jobs = lazy(() => import('../components/Dashboard/Production/Jobs.jsx'))
const JobInfo = lazy(
() => import('../components/Dashboard/Production/Jobs/JobInfo.jsx')
@ -66,26 +52,6 @@ const ProductionRoutes = [
path='production/printers/info'
element={<PrinterInfo />}
/>,
<Route
key='printerprofiles'
path='production/printerprofiles'
element={<PrinterProfiles />}
/>,
<Route
key='printerprofiles-info'
path='production/printerprofiles/info'
element={<PrinterProfileInfo />}
/>,
<Route
key='filamentprofiles'
path='production/filamentprofiles'
element={<FilamentProfiles />}
/>,
<Route
key='filamentprofiles-info'
path='production/filamentprofiles/info'
element={<FilamentProfileInfo />}
/>,
<Route key='jobs' path='production/jobs' element={<Jobs />} />,
<Route key='subjobs' path='production/subjobs' element={<SubJobs />} />,
<Route

View File

@ -6,18 +6,6 @@ const COOKIE_OPTIONS = {
maxAge: 7 * 24 * 60 * 60 // 7 days in seconds
}
const getAuthExpiryTime = (expiresAt) => {
const numericExpiry = Number(expiresAt)
if (Number.isFinite(numericExpiry) && numericExpiry > 0) return numericExpiry
return new Date(expiresAt).getTime()
}
const getAuthCookieMaxAge = (expiresAt) => {
const expiryTime = getAuthExpiryTime(expiresAt)
if (!Number.isFinite(expiryTime)) return COOKIE_OPTIONS.maxAge
return Math.max(Math.floor((expiryTime - Date.now()) / 1000), 1)
}
/**
* Set a cookie with authentication data
* @param {string} name - Cookie name
@ -149,10 +137,10 @@ export const validateAuthCookies = () => {
return false
}
const now = Date.now()
const expirationTime = getAuthExpiryTime(expiresAt)
const now = new Date()
const expirationDate = new Date(expiresAt)
if (!Number.isFinite(expirationTime) || expirationTime <= now) {
if (expirationDate <= now) {
// Cookies are expired, clean them up
clearAuthCookies()
return false
@ -179,7 +167,9 @@ export const checkAuthCookiesExpiry = (minutesBeforeExpiry = 5) => {
return { isExpiringSoon: false, timeRemaining: 0 }
}
const timeRemaining = getAuthExpiryTime(expiresAt) - Date.now()
const now = new Date()
const expirationDate = new Date(expiresAt)
const timeRemaining = expirationDate - now
const minutesRemaining = timeRemaining / (1000 * 60)
return {
@ -250,21 +240,20 @@ export const setAuthCookies = (authData) => {
}
let success = true
const maxAge = getAuthCookieMaxAge(authData.expires_at)
if (authData.access_token) {
success =
success &&
setCookie('authToken', authData.access_token, {
maxAge
})
maxAge: 7 * 24 * 60 * 60
}) // 7 days
}
if (authData.expires_at) {
success =
success &&
setCookie('authExpiresAt', authData.expires_at, {
maxAge
maxAge: 7 * 24 * 60 * 60
})
}
@ -278,7 +267,7 @@ export const setAuthCookies = (authData) => {
success =
success &&
setCookie('user', JSON.stringify(userObject), {
maxAge
maxAge: 7 * 24 * 60 * 60
})
}