script.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. const terminal = document.getElementById('terminal');
  2. const titlebar = document.getElementById('titlebar');
  3. const startBtn = document.getElementById('tb-start');
  4. const startMenu = document.getElementById('startmenu');
  5. const tbWinItem = document.getElementById('tb-terminal-item');
  6. const smTermLaunch = document.getElementById('sm-terminal-launch');
  7. const MIN_W = 320, MIN_H = 160;
  8. let winState = 'normal';
  9. let savedRect = null;
  10. let menuOpen = false;
  11. function saveRect() {
  12. savedRect = {
  13. left: terminal.offsetLeft,
  14. top: terminal.offsetTop,
  15. width: terminal.offsetWidth,
  16. height: terminal.offsetHeight,
  17. };
  18. }
  19. function setWinState(s) {
  20. winState = s;
  21. updateTaskbarItem();
  22. }
  23. function updateTaskbarItem() {
  24. const indicator = tbWinItem.querySelector('.tb-win-indicator');
  25. const closed = winState === 'closed';
  26. tbWinItem.classList.toggle('tb-item-active', winState === 'normal' || winState === 'maximized');
  27. tbWinItem.classList.toggle('tb-item-minimized', winState === 'minimized');
  28. tbWinItem.classList.toggle('tb-item-closed', closed);
  29. indicator.style.opacity = closed ? '0' : '1';
  30. }
  31. function restoreWindow() {
  32. terminal.style.display = '';
  33. terminal.classList.remove('maximized');
  34. if (savedRect) {
  35. terminal.style.left = savedRect.left + 'px';
  36. terminal.style.top = savedRect.top + 'px';
  37. terminal.style.width = savedRect.width + 'px';
  38. terminal.style.height = savedRect.height + 'px';
  39. } else {
  40. centerWindow();
  41. }
  42. setWinState('normal');
  43. }
  44. function centerWindow() {
  45. terminal.style.left = Math.round(window.innerWidth / 2 - terminal.offsetWidth / 2) + 'px';
  46. terminal.style.top = Math.round((window.innerHeight - 44) / 2 - terminal.offsetHeight / 2) + 'px';
  47. }
  48. centerWindow();
  49. updateTaskbarItem();
  50. function toggleMenu(e) {
  51. e.stopPropagation();
  52. menuOpen = !menuOpen;
  53. startMenu.classList.toggle('open', menuOpen);
  54. startMenu.setAttribute('aria-hidden', String(!menuOpen));
  55. startBtn.classList.toggle('active', menuOpen);
  56. }
  57. startBtn.addEventListener('click', toggleMenu);
  58. document.addEventListener('click', (e) => {
  59. if (menuOpen && !startMenu.contains(e.target) && e.target !== startBtn) {
  60. menuOpen = false;
  61. startMenu.classList.remove('open');
  62. startMenu.setAttribute('aria-hidden', 'true');
  63. startBtn.classList.remove('active');
  64. }
  65. });
  66. startMenu.querySelectorAll('a').forEach(a => {
  67. a.addEventListener('click', () => {
  68. menuOpen = false;
  69. startMenu.classList.remove('open');
  70. startBtn.classList.remove('active');
  71. });
  72. });
  73. smTermLaunch.addEventListener('click', () => {
  74. menuOpen = false;
  75. startMenu.classList.remove('open');
  76. startBtn.classList.remove('active');
  77. if (winState === 'closed' || winState === 'minimized') {
  78. restoreWindow();
  79. } else if (winState === 'normal' || winState === 'maximized') {
  80. terminal.classList.add('flash');
  81. setTimeout(() => terminal.classList.remove('flash'), 300);
  82. }
  83. });
  84. tbWinItem.addEventListener('click', () => {
  85. if (winState === 'closed') {
  86. restoreWindow();
  87. } else if (winState === 'minimized') {
  88. restoreWindow();
  89. } else if (winState === 'normal' || winState === 'maximized') {
  90. saveRect();
  91. terminal.style.display = 'none';
  92. setWinState('minimized');
  93. }
  94. });
  95. document.getElementById('btn-close').addEventListener('click', () => {
  96. saveRect();
  97. terminal.style.display = 'none';
  98. setWinState('closed');
  99. });
  100. document.getElementById('btn-minimize').addEventListener('click', () => {
  101. if (winState === 'minimized') return;
  102. if (winState !== 'maximized') saveRect();
  103. terminal.style.display = 'none';
  104. setWinState('minimized');
  105. });
  106. document.getElementById('btn-maximize').addEventListener('click', toggleMaximize);
  107. titlebar.addEventListener('dblclick', (e) => {
  108. if (e.target.closest('.wm-btn')) return;
  109. toggleMaximize();
  110. });
  111. function toggleMaximize() {
  112. if (winState === 'maximized') {
  113. winState = 'normal';
  114. terminal.classList.remove('maximized');
  115. if (savedRect) {
  116. terminal.style.left = savedRect.left + 'px';
  117. terminal.style.top = savedRect.top + 'px';
  118. terminal.style.width = savedRect.width + 'px';
  119. terminal.style.height = savedRect.height + 'px';
  120. }
  121. setWinState('normal');
  122. } else {
  123. saveRect();
  124. terminal.classList.add('maximized');
  125. terminal.style.left = '0';
  126. terminal.style.top = '0';
  127. terminal.style.width = '100vw';
  128. terminal.style.height = 'calc(100vh - 44px)';
  129. setWinState('maximized');
  130. }
  131. }
  132. let dragging = false, dragSX, dragSY, dragL, dragT;
  133. titlebar.addEventListener('mousedown', (e) => {
  134. if (e.target.closest('.wm-btn') || winState === 'maximized') return;
  135. dragging = true;
  136. dragSX = e.clientX; dragSY = e.clientY;
  137. dragL = terminal.offsetLeft; dragT = terminal.offsetTop;
  138. titlebar.style.cursor = 'grabbing';
  139. e.preventDefault();
  140. });
  141. document.addEventListener('mousemove', (e) => {
  142. if (dragging) {
  143. terminal.style.left = (dragL + e.clientX - dragSX) + 'px';
  144. terminal.style.top = (dragT + e.clientY - dragSY) + 'px';
  145. }
  146. if (resizing) doResize(e.clientX, e.clientY);
  147. });
  148. document.addEventListener('mouseup', () => {
  149. dragging = false;
  150. titlebar.style.cursor = 'grab';
  151. stopResize();
  152. });
  153. let resizing = false, resDir = '', resStart = {};
  154. document.querySelectorAll('.resize-handle').forEach(h => {
  155. h.addEventListener('mousedown', (e) => {
  156. if (winState === 'maximized') return;
  157. resizing = true;
  158. resDir = h.dataset.dir;
  159. resStart = { mx: e.clientX, my: e.clientY,
  160. left: terminal.offsetLeft, top: terminal.offsetTop,
  161. width: terminal.offsetWidth, height: terminal.offsetHeight };
  162. document.body.style.cursor = getComputedStyle(h).cursor;
  163. e.preventDefault();
  164. });
  165. });
  166. function doResize(mx, my) {
  167. const dx = mx - resStart.mx, dy = my - resStart.my;
  168. let { left, top, width, height } = resStart;
  169. if (resDir.includes('e')) width = Math.max(MIN_W, width + dx);
  170. if (resDir.includes('s')) height = Math.max(MIN_H, height + dy);
  171. if (resDir.includes('w')) { const nw = Math.max(MIN_W, width-dx); left = left + width - nw; width = nw; }
  172. if (resDir.includes('n')) { const nh = Math.max(MIN_H, height-dy); top = top + height - nh; height = nh; }
  173. terminal.style.left = left + 'px';
  174. terminal.style.top = top + 'px';
  175. terminal.style.width = width + 'px';
  176. terminal.style.height = height + 'px';
  177. }
  178. function stopResize() {
  179. if (resizing) { resizing = false; document.body.style.cursor = ''; }
  180. }
  181. function updateClock() {
  182. const now = new Date();
  183. const hh = String(now.getHours()).padStart(2,'0');
  184. const mm = String(now.getMinutes()).padStart(2,'0');
  185. const days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
  186. const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  187. document.getElementById('tb-time').textContent = `${hh}:${mm}`;
  188. document.getElementById('tb-date').textContent =
  189. `${days[now.getDay()]} ${String(now.getDate()).padStart(2,'0')} ${months[now.getMonth()]}`;
  190. }
  191. updateClock();
  192. setInterval(updateClock, 10000);