diff --git a/src/components/Panels/ElementsPanel/ElementsPanel.vue b/src/components/Panels/ElementsPanel/ElementsPanel.vue
index 8aac793ce..94d3a4d49 100644
--- a/src/components/Panels/ElementsPanel/ElementsPanel.vue
+++ b/src/components/Panels/ElementsPanel/ElementsPanel.vue
@@ -40,6 +40,7 @@
@mouseleave="tooltipText = 'null'"
>
+
+
@@ -123,6 +125,10 @@
:src="element.imgURL"
:alt="element.name"
/>
+
+
diff --git a/src/simulator/src/data/project.ts b/src/simulator/src/data/project.ts
index 71d244b70..d916b94ac 100644
--- a/src/simulator/src/data/project.ts
+++ b/src/simulator/src/data/project.ts
@@ -120,23 +120,23 @@ function checkToSave() {
* Prompt user to save data if unsaved
* @category data
*/
-window.onbeforeunload = async function () {
- if (projectSaved || embed) return
+// window.onbeforeunload = async function () {
+// if (projectSaved || embed) return
- if (!checkToSave()) return
+// if (!checkToSave()) return
- alert(
- 'You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?'
- )
- // await confirmSingleOption(
- // 'You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?'
- // )
- const data = await generateSaveData('Untitled')
- const stringData = JSON.stringify(data)
- localStorage.setItem('recover', stringData)
- // eslint-disable-next-line consistent-return
- return 'Are u sure u want to leave? Any unsaved changes may not be recoverable'
-}
+// alert(
+// 'You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?'
+// )
+// // await confirmSingleOption(
+// // 'You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?'
+// // )
+// const data = await generateSaveData('Untitled')
+// const stringData = JSON.stringify(data)
+// localStorage.setItem('recover', stringData)
+// // eslint-disable-next-line consistent-return
+// return 'Are u sure u want to leave? Any unsaved changes may not be recoverable'
+// }
/**
* Function to clear project
@@ -155,21 +155,25 @@ export async function clearProject() {
Function used to start a new project while prompting confirmation from the user
*/
export async function newProject(verify: boolean) {
+
if (
- verify ||
- projectSaved ||
- !checkToSave() ||
- (await confirmOption(
+ !verify &&
+ (!projectSaved && checkToSave()) &&
+ !(await confirmOption(
'What you like to start a new project? Any unsaved changes will be lost.'
))
) {
- clearProject()
- localStorage.removeItem('recover')
- const baseUrl = window.location.origin !== 'null' ? window.location.origin : 'http://localhost:4000';
- window.location.assign(`${baseUrl}/simulatorvue/`);
-
- setProjectName(undefined)
- projectId = generateId()
- showMessage('New Project has been created!')
+ return
}
+
+ // Proceed to clear project and create a new one if confirmed or conditions met
+ await clearProject()
+ localStorage.removeItem('recover')
+
+ const baseUrl = window.location.origin !== 'null' ? window.location.origin : 'http://localhost:4000'
+ window.location.assign(`${baseUrl}/simulatorvue/`)
+
+ setProjectName(undefined)
+ projectId = generateId()
+ showMessage('New Project has been created!')
}
diff --git a/src/simulator/src/setup.ts b/src/simulator/src/setup.ts
new file mode 100644
index 000000000..c138730fd
--- /dev/null
+++ b/src/simulator/src/setup.ts
@@ -0,0 +1,178 @@
+/* eslint-disable import/no-cycle */
+/* eslint-disable no-restricted-syntax */
+/* eslint-disable guard-for-in */
+import { generateId, showMessage } from './utils';
+import { backgroundArea } from './backgroundArea';
+import plotArea from './plotArea';
+import { simulationArea } from './simulationArea';
+import { dots } from './canvasApi';
+import { update, updateSimulationSet, updateCanvasSet } from './engine';
+import { setupUI } from './ux';
+import startMainListeners from './listeners';
+import { newCircuit } from './circuit';
+import load from './data/load';
+import save from './data/save';
+import { showTourGuide } from './tutorials';
+import setupModules from './moduleSetup';
+import 'codemirror/lib/codemirror.css';
+import 'codemirror/addon/hint/show-hint.css';
+import 'codemirror/mode/javascript/javascript'; // verilog.js from codemirror is not working because array prototype is changed.
+import 'codemirror/addon/edit/closebrackets';
+import 'codemirror/addon/hint/anyword-hint';
+import 'codemirror/addon/hint/show-hint';
+import { setupCodeMirrorEnvironment } from './Verilog2CV';
+import '../vendor/jquery-ui.min.css';
+import '../vendor/jquery-ui.min';
+import { confirmSingleOption } from '#/components/helpers/confirmComponent/ConfirmComponent.vue';
+import { getToken } from '#/pages/simulatorHandler.vue';
+
+/**
+ * to resize window and setup things it
+ * sets up new width for the canvas variables.
+ * Also redraws the grid.
+ * @category setup
+ */
+export function resetup(): void {
+ const DPR = window.devicePixelRatio || 1;
+ if (lightMode) {
+ DPR = 1;
+ }
+ const width = document.getElementById('simulationArea')?.clientWidth! * DPR;
+ let height;
+ if (!embed) {
+ height =
+ (document.body.clientHeight -
+ document.getElementById('toolbar')?.clientHeight!) *
+ DPR;
+ } else {
+ height = document.getElementById('simulation')?.clientHeight! * DPR;
+ }
+ // setup simulationArea and backgroundArea variables used to make changes to canvas.
+ backgroundArea.setup();
+ simulationArea.setup();
+ // redraw grid
+ dots();
+ document.getElementById('backgroundArea')!.style.height =
+ height / DPR + 100 + 'px';
+ document.getElementById('backgroundArea')!.style.width =
+ width / DPR + 100 + 'px';
+ document.getElementById('canvasArea')!.style.height = height / DPR + 'px';
+ simulationArea.canvas.width = width;
+ simulationArea.canvas.height = height;
+ backgroundArea.canvas.width = width + 100 * DPR;
+ backgroundArea.canvas.height = height + 100 * DPR;
+ if (!embed) {
+ plotArea.setup();
+ }
+ updateCanvasSet(true);
+ update(); // INEFFICIENT, needs to be deprecated
+ simulationArea.prevScale = 0;
+ dots();
+}
+
+window.onresize = resetup; // listener
+window.onorientationchange = resetup; // listener
+
+// for mobiles
+window.addEventListener('orientationchange', resetup); // listener
+
+/**
+ * function to setup environment variables like projectId and DPR
+ * @category setup
+ */
+function setupEnvironment(): void {
+ setupModules();
+ const projectId = generateId();
+ (window as any).projectId = projectId;
+ updateSimulationSet(true);
+ newCircuit('Main');
+ (window as any).data = {};
+ resetup();
+ setupCodeMirrorEnvironment();
+}
+
+/**
+ * Fetches project data from API and loads it into the simulator.
+ * @param {number} projectId The ID of the project to fetch data for
+ * @category setup
+ */
+async function fetchProjectData(projectId: number): Promise {
+ try {
+ const response = await fetch(
+ `/api/v1/projects/${projectId}/circuit_data`,
+ {
+ method: 'GET',
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Token ${getToken('cvt')}`,
+ },
+ }
+ );
+ if (response.ok) {
+ const data = await response.json();
+ await load(data);
+ await simulationArea.changeClockTime(data.timePeriod || 500);
+ ($('.loadingIcon') as any).fadeOut();
+ } else {
+ throw new Error('API call failed');
+ }
+ } catch (error) {
+ console.error(error);
+ confirmSingleOption('Error: Could not load.');
+ ($('.loadingIcon') as any).fadeOut();
+ }
+}
+
+/**
+ * Load project data immediately when available.
+ * Improvement to eliminate delay caused by setTimeout in previous implementation revert if issues arise.
+ * @category setup
+ */
+async function loadProjectData(): Promise {
+ (window as any).logixProjectId = (window as any).logixProjectId ?? 0;
+ if ((window as any).logixProjectId !== 0) {
+ ($('.loadingIcon') as any).fadeIn();
+ await fetchProjectData((window as any).logixProjectId);
+ } else if (localStorage.getItem('recover_login') && (window as any).isUserLoggedIn) {
+ // Restore unsaved data and save
+ const data = JSON.parse(localStorage.getItem('recover_login')!);
+ await load(data);
+ localStorage.removeItem('recover');
+ localStorage.removeItem('recover_login');
+ await save();
+ } else if (localStorage.getItem('recover')) {
+ // Restore unsaved data which didn't get saved due to error
+ showMessage(
+ "We have detected that you did not save your last work. Don't worry we have recovered them. Access them using Project->Recover"
+ );
+ }
+}
+
+/**
+ * Show tour guide if it hasn't been completed yet.
+ * The tour is shown after a delay of 2 seconds.
+ * @category setup
+ */
+function showTour(): void {
+ if (!localStorage.tutorials_tour_done && !embed) {
+ setTimeout(() => {
+ showTourGuide();
+ }, 2000);
+ }
+}
+
+/**
+ * The first function to be called to setup the whole simulator.
+ * This function sets up the simulator environment, the UI, the listeners,
+ * loads the project data, and shows the tour guide.
+ * @category setup
+ */
+export function setup(): void {
+ setupEnvironment();
+ if (!embed) {
+ setupUI();
+ startMainListeners();
+ }
+ loadProjectData();
+ showTour();
+}
diff --git a/src/simulator/src/tutorials.ts b/src/simulator/src/tutorials.ts
new file mode 100644
index 000000000..88781ca55
--- /dev/null
+++ b/src/simulator/src/tutorials.ts
@@ -0,0 +1,149 @@
+import Driver from 'driver.js';
+
+interface Popover {
+ className?: string;
+ title: string;
+ description: string;
+ position: 'right' | 'left' | 'bottom' | 'top';
+ offset?: number;
+}
+
+interface TourStep {
+ element: string;
+ className?: string;
+ popover: Popover;
+}
+
+export const tour: TourStep[] = [
+ {
+ element: '#guide_1',
+ className: 'guide_1',
+ popover: {
+ className: '',
+ title: 'Circuit Elements panel',
+ description:
+ 'This is where you can find all the circuit elements that are offered to build amazing circuits.',
+ position: 'right',
+ offset: 160,
+ },
+ },
+ {
+ element: '.guide_2',
+ popover: {
+ title: 'Properties Panel',
+ description:
+ 'This panel lets you change element properties as they are selected. When no elements are selected, the panel displays project properties.',
+ position: 'left',
+ offset: 200,
+ },
+ },
+ {
+ element: '.quick-btn',
+ popover: {
+ title: 'Quick Access Panel',
+ description:
+ 'This movable panel offers to perform some actions like Save Online, Open, Download quickly. Hover over the icons and see for yourself',
+ position: 'bottom',
+ },
+ },
+ {
+ element: '#tabsBar',
+ popover: {
+ title: 'Circuit Tabs',
+ description:
+ 'This section displays all the circuits you have in your project. You can easily add and delete circuits.',
+ position: 'bottom',
+ offset: 250,
+ },
+ },
+ {
+ element: '.timing-diagram-panel',
+ popover: {
+ title: 'Timing Diagram Panel (Waveform)',
+ description:
+ 'This panel displays the waveform created by circuits and can be used for resolving race conditions and debugging circuits.',
+ position: 'bottom',
+ offset: 0,
+ },
+ },
+ {
+ element: '.report-sidebar a',
+ popover: {
+ className: 'bug-guide',
+ title: 'Report System',
+ description:
+ 'You can report any issues/bugs you face through this issue reporting button there and then quickly.',
+ position: 'left',
+ offset: -105,
+ },
+ },
+ {
+ element: '.tour-help',
+ popover: {
+ className: 'tourHelpStep',
+ title: 'Restart tutorial anytime',
+ description:
+ 'You can restart this tutorial anytime by clicking on "Tutorial Guide" under this dropdown.',
+ position: 'right',
+ offset: 0,
+ },
+ },
+ {
+ element: '.testbench-manual-panel',
+ popover: {
+ title: 'Test Bench Panel',
+ description: 'This panel helps you test your circuit correctness by observing how your circuit responds under different test cases, ensuring a thorough and effective validation process.',
+ position: 'right',
+ offset: 0,
+ },
+ },
+];
+
+// Not used currently
+export const tutorialWrapper = (): void => {
+ const panelHighlight = new Driver();
+ document.querySelector('.panelHeader')?.addEventListener(
+ 'click',
+ (e: MouseEvent) => {
+ if (localStorage.getItem('tutorials') === 'next') {
+ panelHighlight.highlight({
+ element: '#guide_1',
+ showButtons: false,
+ popover: {
+ title: 'Here are the elements',
+ description:
+ 'Select any element by clicking on it & then click anywhere on the grid to place the element.',
+ position: 'right',
+ offset:
+ (e.target as HTMLElement).nextElementSibling?.clientHeight! +
+ (e.target as HTMLElement).offsetTop -
+ 45,
+ },
+ });
+ localStorage.setItem('tutorials', 'done');
+ }
+ },
+ {
+ once: true,
+ }
+ );
+ document.querySelector('.icon')?.addEventListener('click', () => {
+ panelHighlight.reset(true);
+ });
+};
+
+const animatedTourDriver = new Driver({
+ animate: true,
+ opacity: 0.8,
+ padding: 5,
+ showButtons: true,
+});
+
+export function showTourGuide(): void {
+ document.querySelector('.draggable-panel .maximize')?.dispatchEvent(new Event('click'));
+ animatedTourDriver.defineSteps(tour);
+ animatedTourDriver.start();
+ localStorage.setItem('tutorials_tour_done', 'true');
+}
+
+export default showTourGuide;