localStorage.js

import * as THREE from 'three';
import {
  checkIfSubStringIsMatrix4,
  checkIfSubStringIsVector3,
} from '@ud-viz/utils_shared';

import { createLabelInput } from './html';

/**
 * If there was a matrix4 recorded in localStorage restore it matrix4 + when tab is closing record current matrix4
 * /!\ calling this function make window reference the matrix4 avoiding it to be GC and could cause memory leak
 *
 * @param {THREE.Matrix4} matrix4 - matrix4 to track
 * @param {string} key - key of the item in localstorage
 * @returns {boolean} true if matrix4 has been setted with localStorage
 */
export function localStorageSetMatrix4(matrix4, key) {
  // listen the close tab event
  window.addEventListener('beforeunload', () => {
    const matrix4String = matrix4.toArray().toString();
    if (checkIfSubStringIsMatrix4(matrix4String.split(','))) {
      localStorage.setItem(key, matrix4String);
    }
  });

  // check if there was a previous matrix4
  const storedMatrixArrayString = localStorage.getItem(key);

  if (
    typeof storedMatrixArrayString === 'string' &&
    checkIfSubStringIsMatrix4(storedMatrixArrayString.split(','))
  ) {
    let error = false;
    try {
      const storedMatrixArray = storedMatrixArrayString
        .split(',')
        .map((x) => parseFloat(x));

      matrix4.fromArray(storedMatrixArray);
      return true;
    } catch (e) {
      error = true;
      console.error(e);
    }
    return !error;
  }

  return false;
}

/**
 * If there was a vector 3 recorded in localStorage within the key passed copy values in vector3 + when tab is closing record current vector3 values
 * /!\ calling this function make window reference the vector3 avoiding it to be GC and could cause memory leak
 *
 * @param {THREE.Vector3} vector3 - vector 3 to track
 * @param {string} key - key of the item in localstorage
 * @returns {boolean} true if vector3 has been setted with localStorage
 */
export function localStorageSetVector3(vector3, key) {
  if (!vector3) throw new Error('no vector3');
  if (!key) throw new Error('no key');

  // listen the close tab event
  window.addEventListener('beforeunload', () => {
    const vector3String = vector3.toArray().toString();
    if (checkIfSubStringIsVector3(vector3String.split(','))) {
      localStorage.setItem(key, vector3String);
    }
  });

  // check if there was a previous vector3 register within this key
  const storedVector3String = localStorage.getItem(key);

  if (
    typeof storedVector3String === 'string' &&
    checkIfSubStringIsVector3(storedVector3String.split(','))
  ) {
    let error = false;
    try {
      const storedVector3Array = storedVector3String
        .split(',')
        .map((x) => parseFloat(x));

      const storedVector3 = new THREE.Vector3().fromArray(storedVector3Array);

      vector3.copy(storedVector3);
    } catch (e) {
      error = true;
      console.error(e);
    }
    return !error;
  }

  return false;
}

/**
 *
 * @param {string} keyLocalStorage - the key of the item in localstorage
 * @param {string} summaryText - what text to display
 * @param {HTMLElement|null} [parent=null] - where to append result (optional)
 * @returns {HTMLElement} - the html element created
 */
export const createLocalStorageDetails = (
  keyLocalStorage,
  summaryText,
  parent = null
) => {
  const details = document.createElement('details');

  if (parent) parent.appendChild(details);

  const summary = document.createElement('summary');
  summary.innerText = summaryText;
  details.appendChild(summary);

  const item = localStorage.getItem(keyLocalStorage)
    ? JSON.parse(localStorage.getItem(keyLocalStorage))
    : null;
  if (item && item.opened) details.open = true;

  window.addEventListener('beforeunload', () => {
    localStorage.setItem(
      keyLocalStorage,
      JSON.stringify({ opened: details.open })
    );
  });

  return details;
};

/**
 *
 * @param {string} keyLocalStorage - the key of the item in localstorage
 * @param {string} labelText - what text to display
 * @param {HTMLElement} inputParent - where to append
 * @param {boolean} defaultValue - value to initialize with
 * @returns {HTMLElement} - the html element created
 */
export const createLocalStorageCheckbox = (
  keyLocalStorage,
  labelText,
  inputParent,
  defaultValue = false
) => {
  const { input, parent } = createLabelInput(labelText, 'checkbox');
  inputParent.appendChild(parent);

  const item = localStorage.getItem(keyLocalStorage)
    ? JSON.parse(localStorage.getItem(keyLocalStorage))
    : null;
  if (item) {
    input.checked = item.checked;
  } else {
    input.checked = defaultValue;
  }

  window.addEventListener('beforeunload', () => {
    localStorage.setItem(
      keyLocalStorage,
      JSON.stringify({ checked: input.checked })
    );
  });

  return input;
};

/**
 *
 * @param {string} keyLocalStorage - the key of the item in localstorage
 * @param {string} labelText - what text to display
 * @param {HTMLElement} inputParent - where to append
 * @param {object} options - options of the slider
 * @param {number} options.min - min of the slider
 * @param {number} options.max - max of the slider
 * @param {number} options.step - step of the slider
 * @returns {HTMLElement} - the html element created
 */
export const createLocalStorageSlider = (
  keyLocalStorage,
  labelText,
  inputParent,
  options = {}
) => {
  const { input, parent } = createLabelInput(labelText, 'range');
  inputParent.appendChild(parent);

  input.min = options.min || 0;
  input.max = options.max || 1;
  input.step = options.step || 0.1;

  const item = localStorage.getItem(keyLocalStorage)
    ? JSON.parse(localStorage.getItem(keyLocalStorage))
    : null;
  if (item) {
    input.value = item.value;
  } else {
    input.value = options.defaultValue || input.max;
  }

  window.addEventListener('beforeunload', () => {
    localStorage.setItem(
      keyLocalStorage,
      JSON.stringify({ value: input.value })
    );
  });

  return input;
};

/**
 *
 * @param {string} keyLocalStorage - the key of the item in localstorage
 * @param {string} labelText - what text to display
 * @param {HTMLElement} inputParent - where to append
 * @param {object} options - options of the slider
 * @param {number} options.min - min of the slider
 * @param {number} options.max - max of the slider
 * @param {number} options.step - step of the slider
 * @returns {HTMLElement} - the html element created
 */
export const createLocalStorageNumberInput = (
  keyLocalStorage,
  labelText,
  inputParent,
  options = {}
) => {
  const { input, parent } = createLabelInput(labelText, 'number');
  inputParent.appendChild(parent);

  input.min = options.min || 0;
  input.max = options.max || 1;
  input.step = options.step || 0.1;

  const item = localStorage.getItem(keyLocalStorage)
    ? JSON.parse(localStorage.getItem(keyLocalStorage))
    : null;
  if (item) {
    input.value = item.value;
  } else {
    input.value = options.defaultValue || input.max;
  }

  window.addEventListener('beforeunload', () => {
    localStorage.setItem(
      keyLocalStorage,
      JSON.stringify({ value: input.value })
    );
  });

  return input;
};