table_control.html 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. {% extends "base.html" %}
  2. {% block title %}Table Control - Kinetic Sand Table{% endblock %}
  3. {% block additional_styles %}
  4. /* Dark mode styles for table control page */
  5. .dark .bg-white {
  6. background-color: #262626;
  7. }
  8. .dark .text-slate-900 {
  9. color: #e5e5e5;
  10. }
  11. .dark .text-slate-800 {
  12. color: #e5e5e5;
  13. }
  14. .dark .text-slate-700 {
  15. color: #d1d5db;
  16. }
  17. .dark .border-slate-200 {
  18. border-color: #404040;
  19. }
  20. .dark .border-slate-300 {
  21. border-color: #404040;
  22. }
  23. .dark .bg-slate-50 {
  24. background-color: #262626;
  25. }
  26. .dark .hover\:bg-slate-50:hover {
  27. background-color: #404040;
  28. }
  29. .dark .form-input {
  30. background-color: #262626;
  31. border-color: #404040;
  32. color: #e5e5e5;
  33. }
  34. .dark .form-input::placeholder {
  35. color: #9ca3af;
  36. }
  37. .dark .form-input:focus {
  38. border-color: #0c7ff2;
  39. ring-color: #0c7ff2;
  40. }
  41. .dark .focus\:ring-sky-500:focus {
  42. ring-color: #0c7ff2;
  43. }
  44. .dark .focus\:border-sky-500:focus {
  45. border-color: #0c7ff2;
  46. }
  47. .dark .focus\:ring-blue-400:focus {
  48. ring-color: #0c7ff2;
  49. }
  50. .dark .focus\:ring-red-400:focus {
  51. ring-color: #ef4444;
  52. }
  53. .dark .focus\:ring-slate-400:focus {
  54. ring-color: #525252;
  55. }
  56. .dark .focus\:ring-offset-2 {
  57. ring-offset-color: #262626;
  58. }
  59. {% endblock %}
  60. {% block content %}
  61. <div class="layout-content-container flex flex-col w-full max-w-4xl gap-8 pt-2 pb-[75px]">
  62. <div class="flex flex-wrap justify-between items-center gap-4 p-4 bg-white rounded-xl shadow-sm mt-2 sm:mt-8">
  63. <h1 class="text-slate-900 tracking-tight text-2xl sm:text-3xl font-bold leading-tight">
  64. Table Control
  65. </h1>
  66. </div>
  67. <section class="bg-white p-6 rounded-xl shadow-lg">
  68. <h2 class="text-xl font-semibold text-slate-800 mb-5 border-b border-slate-200 pb-3">
  69. Movement Controls
  70. </h2>
  71. <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
  72. <button
  73. id="homeButton"
  74. class="flex items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-3 text-sm font-semibold text-white shadow-md hover:bg-blue-700 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2"
  75. >
  76. <span class="material-icons-outlined text-lg">home</span>
  77. Home
  78. </button>
  79. <button
  80. id="stopButton"
  81. class="flex items-center justify-center gap-2 rounded-lg bg-red-500 px-4 py-3 text-sm font-semibold text-white shadow-md hover:bg-red-600 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-red-400 focus:ring-offset-2"
  82. >
  83. <span class="material-icons-outlined text-lg">stop_circle</span>
  84. Stop
  85. </button>
  86. <button
  87. id="centerButton"
  88. class="flex items-center justify-center gap-2 rounded-lg bg-slate-600 px-4 py-3 text-sm font-semibold text-white shadow-md hover:bg-slate-700 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2"
  89. >
  90. <span class="material-icons-outlined text-lg">filter_center_focus</span>
  91. Move to Center
  92. </button>
  93. <button
  94. id="perimeterButton"
  95. class="flex items-center justify-center gap-2 rounded-lg bg-slate-600 px-4 py-3 text-sm font-semibold text-white shadow-md hover:bg-slate-700 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2"
  96. >
  97. <span class="material-icons-outlined text-lg">all_out</span>
  98. Move to Perimeter
  99. </button>
  100. </div>
  101. <div class="flex items-center justify-center mt-4">
  102. <button
  103. id="orientationHelpButton"
  104. class="flex items-center justify-center gap-2 rounded-lg border border-slate-300 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1"
  105. >
  106. <span class="material-icons-outlined text-lg">help_outline</span>
  107. How to Align Pattern Orientation
  108. </button>
  109. </div>
  110. </section>
  111. <section class="bg-white p-6 rounded-xl shadow-lg">
  112. <h2 class="text-xl font-semibold text-slate-800 mb-5 border-b border-slate-200 pb-3">
  113. Speed Control
  114. </h2>
  115. <div class="flex flex-col sm:flex-row items-start sm:items-center gap-4">
  116. <div class="flex items-center gap-2 bg-slate-50 rounded-lg px-3 py-2 w-full sm:w-auto">
  117. <span class="text-sm font-medium text-slate-700">Current:</span>
  118. <span id="currentSpeedDisplay" class="text-sm font-semibold text-slate-900">-- mm/s</span>
  119. </div>
  120. <div class="flex flex-col sm:flex-row gap-3 w-full sm:flex-1">
  121. <div class="flex-1">
  122. <input
  123. type="number"
  124. id="speedInput"
  125. class="form-input flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-3 text-sm font-normal leading-normal transition-colors"
  126. placeholder="Enter new speed..."
  127. min="1"
  128. step="1"
  129. />
  130. </div>
  131. <button
  132. id="setSpeedButton"
  133. class="flex items-center justify-center gap-2 rounded-lg bg-sky-600 px-4 py-2.5 text-sm font-semibold text-white shadow-md hover:bg-sky-700 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-sky-400 focus:ring-offset-2 h-10 w-full sm:w-auto whitespace-nowrap"
  134. >
  135. <span class="material-icons-outlined text-lg">speed</span>
  136. Set Speed
  137. </button>
  138. </div>
  139. </div>
  140. </section>
  141. <section class="bg-white p-6 rounded-xl shadow-lg">
  142. <h2 class="text-xl font-semibold text-slate-800 mb-5 border-b border-slate-200 pb-3">
  143. Clear Patterns
  144. </h2>
  145. <div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
  146. <button
  147. id="clearCenterButton"
  148. class="flex items-center justify-center gap-2 rounded-lg border border-slate-300 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1"
  149. >
  150. <span class="material-icons-outlined text-lg">center_focus_strong</span>
  151. Clear from Center
  152. </button>
  153. <button
  154. id="clearPerimeterButton"
  155. class="flex items-center justify-center gap-2 rounded-lg border border-slate-300 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1"
  156. >
  157. <span class="material-icons-outlined text-lg">all_out</span>
  158. Clear from Perimeter
  159. </button>
  160. <button
  161. id="clearSidewaysButton"
  162. class="flex items-center justify-center gap-2 rounded-lg border border-slate-300 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1"
  163. >
  164. <span class="material-icons-outlined text-lg">swap_horiz</span>
  165. Clear Sideway
  166. </button>
  167. </div>
  168. </section>
  169. </div>
  170. <!-- Pattern Orientation Help Modal -->
  171. <div id="orientationHelpModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden p-4">
  172. <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md">
  173. <div class="p-6">
  174. <div class="text-center mb-4">
  175. <h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200 mb-2">Pattern Orientation Alignment</h2>
  176. <p class="text-gray-600 dark:text-gray-400 text-sm">
  177. Follow these steps to align your patterns with their previews:
  178. </p>
  179. </div>
  180. <ol class="text-gray-700 dark:text-gray-300 text-sm space-y-3 mb-6">
  181. <li class="flex items-start gap-3">
  182. <span class="font-semibold text-blue-600 dark:text-blue-400 min-w-[20px]">1.</span>
  183. <span><strong>Home the table</strong> then select move to perimeter. Look at your pattern preview and decide where the "bottom" of the pattern should be.</span>
  184. </li>
  185. <li class="flex items-start gap-3">
  186. <span class="font-semibold text-blue-600 dark:text-blue-400 min-w-[20px]">2.</span>
  187. <span><strong>Manually</strong> move the radial arm or <strong>use the rotation buttons below</strong> to point 90° to the right of where you want the pattern bottom to be.</span>
  188. </li>
  189. <li class="flex items-start gap-3">
  190. <span class="font-semibold text-blue-600 dark:text-blue-400 min-w-[20px]">3.</span>
  191. <span>Click the <strong>"Home"</strong> button to establish this as the reference position.</span>
  192. </li>
  193. <li class="flex items-start gap-3">
  194. <span class="font-semibold text-blue-600 dark:text-blue-400 min-w-[20px]">4.</span>
  195. <span>All patterns will now be oriented according to their previews!</span>
  196. </li>
  197. </ol>
  198. <div class="border-t border-gray-200 dark:border-gray-600 pt-4 mb-4">
  199. <p class="text-amber-600 dark:text-amber-400 text-sm">
  200. <strong>Important:</strong> Only perform this alignment when you want to change the orientation reference. Once set, this becomes your new "home" position.
  201. </p>
  202. </div>
  203. <!-- Fine Adjustment Controls -->
  204. <div class="border-t border-gray-200 dark:border-gray-600 pt-4 mb-4">
  205. <p class="text-gray-700 dark:text-gray-300 text-sm mb-3 text-center">
  206. <strong>Fine Adjustment:</strong> Use these buttons to rotate the ball precisely
  207. </p>
  208. <div class="flex justify-center gap-3">
  209. <button
  210. id="rotateCCW"
  211. class="flex items-center gap-2 px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors"
  212. title="Rotate Counter-Clockwise 10°"
  213. >
  214. <span class="material-icons text-lg">rotate_left</span>
  215. <span class="text-sm">CCW 10°</span>
  216. </button>
  217. <button
  218. id="rotateCW"
  219. class="flex items-center gap-2 px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors"
  220. title="Rotate Clockwise 10°"
  221. >
  222. <span class="text-sm">CW 10°</span>
  223. <span class="material-icons text-lg">rotate_right</span>
  224. </button>
  225. </div>
  226. <p class="text-gray-500 dark:text-gray-400 text-xs text-center mt-2">
  227. Each click rotates 10 degrees for fine adjustment
  228. </p>
  229. </div>
  230. <div class="flex justify-center">
  231. <button
  232. id="closeOrientationHelpModal"
  233. class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
  234. >
  235. Got it
  236. </button>
  237. </div>
  238. </div>
  239. </div>
  240. </div>
  241. {% endblock %}
  242. {% block scripts %}
  243. <script src="/static/js/table_control.js"></script>
  244. <script>
  245. // Pattern orientation help modal functionality
  246. document.addEventListener('DOMContentLoaded', function() {
  247. const helpButton = document.getElementById('orientationHelpButton');
  248. const modal = document.getElementById('orientationHelpModal');
  249. const closeButton = document.getElementById('closeOrientationHelpModal');
  250. const rotateCWButton = document.getElementById('rotateCW');
  251. const rotateCCWButton = document.getElementById('rotateCCW');
  252. // Track current position (theta, rho)
  253. let currentTheta = 0;
  254. let currentRho = 1; // Always at perimeter for rotation adjustments
  255. helpButton.addEventListener('click', () => {
  256. modal.classList.remove('hidden');
  257. });
  258. closeButton.addEventListener('click', () => {
  259. modal.classList.add('hidden');
  260. });
  261. // Close modal when clicking outside
  262. modal.addEventListener('click', (e) => {
  263. if (e.target === modal) {
  264. modal.classList.add('hidden');
  265. }
  266. });
  267. // Rotation button handlers
  268. async function rotateByDegrees(degrees) {
  269. try {
  270. // Convert degrees to radians
  271. const radians = degrees * (Math.PI / 180);
  272. // Calculate new theta position
  273. const newTheta = currentTheta + radians;
  274. // Send coordinate to move to new position (always at perimeter, rho = 1)
  275. const response = await fetch('/send_coordinate', {
  276. method: 'POST',
  277. headers: {
  278. 'Content-Type': 'application/json',
  279. },
  280. body: JSON.stringify({
  281. theta: newTheta,
  282. rho: 1
  283. })
  284. });
  285. if (response.ok) {
  286. // Update tracked position
  287. currentTheta = newTheta;
  288. console.log(`Rotated ${degrees}°. New theta: ${(currentTheta * 180 / Math.PI).toFixed(1)}°`);
  289. } else {
  290. throw new Error('Failed to send coordinate');
  291. }
  292. } catch (error) {
  293. console.error('Error rotating:', error);
  294. alert('Failed to rotate. Please check connection.');
  295. }
  296. }
  297. // Clockwise rotation (positive angle)
  298. rotateCWButton.addEventListener('click', async () => {
  299. await rotateByDegrees(10);
  300. });
  301. // Counter-clockwise rotation (negative angle)
  302. rotateCCWButton.addEventListener('click', async () => {
  303. await rotateByDegrees(-10);
  304. });
  305. // Try to get current position from WebSocket status
  306. // This assumes you have a WebSocket connection for status updates
  307. if (typeof ws !== 'undefined' && ws) {
  308. const originalOnMessage = ws.onmessage;
  309. ws.onmessage = function(event) {
  310. try {
  311. const data = JSON.parse(event.data);
  312. if (data.type === 'status_update' && data.data) {
  313. // Update current theta position if available in status
  314. if (data.data.current_theta !== undefined) {
  315. currentTheta = data.data.current_theta;
  316. }
  317. // Keep rho at 1 for rotation adjustments
  318. currentRho = 1;
  319. }
  320. } catch (error) {
  321. // Ignore parsing errors
  322. }
  323. // Call original handler if it exists
  324. if (originalOnMessage) {
  325. originalOnMessage.call(ws, event);
  326. }
  327. };
  328. }
  329. });
  330. </script>
  331. {% endblock %}