# HART Estate Widget

The package is designed to present 2D and 3D floor plans generated by the AI service [getfloorplan.com](https://getfloorplan.com).

# Table of Contents
- [Quick Start](#quick-start)
  - [Example vite.config.ts](#vite)
  - [Example index.html](#html)
  - [Example index.js](#javascript)
  - [Example index.sass](#sass)
- [Docs](#docs)
  - [Parameters](#parameters)
  - [REST API Object](#widget-object-from-the-rest-api)
- [Versioning](#versioning)
- [Copyright and License](#copyright-and-license)

# Quick Start

As a result, you will receive a website that can display various floor plans.

![node: ^18.0.0](https://img.shields.io/badge/nodeJS-^18.0.0-blue)

- Download NodeJS 18+
- Create a new project
```shell
npm init
```
- Download the required packages
```shell
npm install -S hart-estate-widget@4.0.0  # GFP Widget package
npm install -S -D vite                   # Vite bundler
npm install -S -D sass-embedded          # Sass style compiler
```
- Example structure of project
```
.
├── src
│   ├── index.html
│   ├── index.js
│   ├── index.sass
│   └── logo.png
├── package.json
└── package-lock.json
```
- Copy-paste sample assets from below.
- Run with `rm -rf dist && npx vite`
- Open browser at:
http://localhost:1234/?id=73b833c3-072a-4ac2-9f5d-7f7ac3d1fc9c

> The query `id` parameter accepts the UUID4 received from [getfloorplan.com](https://getfloorplan.com)
> You can use these UUID4 for test purposes:
> - Scandy style: `228ba1dd-64d3-4d33-bcd7-b4c670bed40e`
> - Boho style: `f9032373-bb2c-416e-b0ab-20b8fd24d482`
> - England style: `b21871a2-2d5b-4beb-817b-ed750eebab9a`
> - Neutral style: `e8553134-0457-488c-8d3e-611b0e2be4d4`
> - Modern style: `73b833c3-072a-4ac2-9f5d-7f7ac3d1fc9c`

## VITE
Insert the example into a file `vite.config.ts`

```ts
import { defineConfig } from 'vite';

export default defineConfig({
  root: 'src',
  build: { outDir: '../dist' },
  server: { port: 1234, open: true },
});
```

## HTML
Insert the example into a file `src/index.html`

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
    <title>HART Estate Widget</title>

    <script type="module" src="./index.js"></script>
    <link rel="icon" href="./logo.png">
  </head>

  <body>
    <div id="widget"></div>
  </body>
</html>
```

## JavaScript
Insert the example into a file `src/index.js`

```js
import { Api } from 'hart-estate-widget/build/api.js'

const searchParams = new URLSearchParams(document.location.search);
const planId = searchParams.get('id');
const crmPlanId = searchParams.get('crmPlanId');

const baseUrl = 'https://backend.estate.hart-digital.com';

const loadData = async () => {
  const {loadCrmWidgetData, loadWidgetData} = new Api(baseUrl);
  return crmPlanId ? loadCrmWidgetData(crmPlanId) : loadWidgetData(planId);
};
const loadCreateWidget = async () => (await import('hart-estate-widget/build/createWidget.js')).createWidget;
const loadFovPlugin = async () => (await import('hart-estate-widget/build/plugins/FovPlugin.js')).FovPlugin;
const loadLogoUrl = async () => (await import('./logo.png')).default;
const loadStyle = async () => (await import('./index.sass')).default;
const loadDocument = async () => new Promise((resolve) => (window.onload = resolve));

const queue = [
  loadData(),
  loadCreateWidget(),
  loadFovPlugin(),
  loadLogoUrl(),
  loadStyle(),
  loadDocument(),
];

Promise.all(queue).then(([data, createWidget, FovPlugin, logoUrl]) => {
  const options = {
    baseUrl,
    logoUrl,
    logoLinkUrl: 'https://getfloorplan.com',
  };

  const plugins = [
    new FovPlugin(2),
  ];

  createWidget('#widget', data, options, plugins);
});
```

## SASS
Insert the example into a file `src/index.sass`

```sass
*, *:before, *:after
  -webkit-font-smoothing: antialiased
  -moz-osx-font-smoothing: grayscale
  font-family: 'Proxima Nova', sans-serif
  text-decoration: none
  font-weight: 400
  color: #fff
  outline: none
  padding: 0
  margin: 0
  box-sizing: border-box
  -webkit-box-sizing: border-box

html, body
  width: 100%
  height: 100%

#widget
  width: 100%
  height: 100%
  overflow: hidden
```

# Docs
## Parameters:
Here you can see a list of accessible options and examples of usage. There are accessible values for each option below in the block "Types of Elements".

```json
{
  // Widget API base URL
  "baseUrl": "https://backend.estate.hart-digital.com",

  // Widget locale can be one of
  // "en" - English language
  // "ru" - Russian language
  // "de" - German language
  // "es" - Spanish language
  // "ja" - Japanese language
  // "nl" - Dutch language
  "locale": "en",
  // Overrides locale keys with custom text or translation
  "localeOverrides": {
    "rotate-plan": "Rotate plan",
    "research-plan": "Research plan",
    "create-points": "Create a point",
    "delete-points": "Remove point",
    "instructions-hint-text": "",
    "ok": "Ok",
    "ruler-ok": "Ok",

    "made-by-template": "$0 $1",
    "made-by-prefix": "made by",
    "made-by-link": "https://getfloorplan.com/",
    "made-by-text": "getfloorplan.com",

    "style": "Style",

    "floor": "$0 floor",
    "floorNumberEndings": {
      "1": "$0st",
      "2": "$0nd",
      "3": "$0d",
      "rest": "$0th"
    }
  },

  // Path/link to the logo
  "logoUrl": "https://getfloorplan.com/img/logo.58f6cd11.svg",
  // Link opened when logo is clicked
  "logoLinkUrl": "https://getfloorplan.com",
  // Widget color settings in CSS color formal like: "rgba(255, 255, 255, 0)", "#ABCDEF", etc...
  // "main" - main color of buttons, elements
  // "mainText" - text color for buttons, elements contrasting with the main color
  "colors": {
    "main": "#FFA900",
    "mainText": "#413E3E"
  },

  // First opened tab
  "primaryTab": "panorama",
  // Available widget tabs array with order:
  // "panorama" - Panorama 360 tab
  // "rotation" - Isometric plan rotation tab
  // "plan" - Original plan tab
  "tabs": ["plan", "rotation", "panorama"],

  // Controls in bottom bar, can be:
  // "ruler" - Ruler enable/disable button (panorama tab only)
  // "scale" - Scale x1, x2, x0.5 button (panorama tab only)
  // "autoRotate" - Auto rotate enable/disable button (panorama tab only)
  // "minusScale" - Scale minus button (panorama tab only)
  // "plusScale" - Scale plus button (panorama tab only)
  // "furnished" - Furnished / bareshell button (panorama tab only)
  // "prevPanorama" - Prev panorama button (panorama tab only)
  // "nextPanorama" - Next panorama button (panorama tab only)
  // "2d" - 2D tab button
  // "3d" - 3D tab button
  // "360" - 360 tab button
  // "isometry" - Isometry rotation arrows (rotation tab only)
  // "floors" - Floors select with reverse order inside application frame
  "controls": ["ruler", "scale", "autoRotate"],

  // Enable/disable modal of instruction in 3D tour
  "isInstructionsVisible": true,
  // Enable/disable auto rotation in 3D tour
  "isAutoRotate": false,
  // Enable/disable device gyroscope for AR
  "isGyroscopeEnabled": true,
  // Show/hide fullscreen button
  "isFullscreenButtonVisible": true,
  // Show/hide current room type top label
  "isRoomLabelVisible": false,
  // Show next scale text or current
  // true: next (current: 0.5, shows: 1x)
  // false: current (current: 0.5, shows: 0.5x)
  "isShowNextScaleText": false,
  // Enable InteractiveRotationTab
  // true: InteractiveRotationTab will show on interactive_top_view capability
  // false: always use default RotationTab
  "isInteractiveTabAvailable": true,

  // Use multifloor map instead of default or topView
  "isMultifloorMap": false,
  // Automatic crop transparent part of images by rasterizer (may lag a bit)
  "isNeedCropMultifloorMap": true,
  // Precision of multifloor map crop
  "multiFloorMapCropPrecision": 100,

  // Move panorama control buttons to the application context
  "isAbsolutePanoramaControls": false,
  // Show prev/next room type labels on prev/next panorama buttons
  "isShowLabelsOnPrevNext": true,
  // Render links as div elements in UI layer
  "isOverrideLinks": false,
  // Enable room indexer button in rotation tab
  "isRoomIndexerVisible": false,
  // Enable room indexer button to rotate middle cuts
  "isRoomIndexerRotate": false,
  // Direction of rotation of rooms
  "indexerButtonDirection": false,
  // Show top view on rotation tab
  "isShowTopViewOnRotationTab": true,
  // Show left and right arrows in room scroll container
  "isInteractiveScrollArrowsVisible": true,
  // 'original' - use original_plan_img
  // 'miniplan' - use miniplan_img
  "2DTabBehavior": "miniplan",
  // Show the "hide furniture" button with a separate switch
  "isHiddenFurnitureSwitchVisible": true,

  // Apply scale offset in 2D/3D
  "isTransformScaleWithOffset": true,
  // Apply transform offset in 2D/3D
  "isTransformEnabled": true,
  // Apply scale offset from screen center instead of cursor
  "isTransformAlongScreenCenter": false,
  /** Resolve zero angle in middle cuts to rotate from top view more angle-seamlessly */
  "isNeedMiddleCutZeroAngle": false,

  // Ruler font settings
  "rulerFontSettings": {
    "fontSize": "38px",
    "fontWeight": "Bold",
    "lineHeight": "1",
    "fontFamily": "Roboto Flex",
    "textAlign": "center",
    "borderRadius": 55,
    "scale": 1.5,
    "width": 256,
    "height": 128,
    "textFillColor": "#0008",
    "textHoverColor": "#000f",
    "fillColor": "#fff8",
    "hoverColor": "#fffa",
    "feetsPositionY": 100,
    "feetsPrecision": 2,
    "feetsFormat": "$0 ft",
    "useFeetsComma": true,
    "metersPositionY": 50,
    "metersPrecision": 2,
    "metersFormat": "$0 m",
  },

  // Base camera persective FOV
  "cameraFov": 75,
  // Custom camera fovs by base FOV Scale
  "cameraFovs": [
    { "text": "1x", "value": 1 },
    { "text": "2x", "value": 0.5 },
    { "text": "4x", "value": 0.25 },
    { "text": "0.5x", "value": 1.35 },
  ],
  // Legacy camera fovs by scale texts (available: 0.5x, 1x, 2x)
  "scales": ["1x", "2x", "0.5x"],
  // Custom link icons
  "linkIcons": {
    "door": "https://site.com/door.png",
    "doorHover": "https://site.com/door-hover.png",
    "spot": "https://site.com/spot.png",
    "spotHover": "https://site.com/spot-hover.png",
    "stairUp": "https://site.com/stairUp.png",
    "stairUpHover": "https://site.com/stairUp-hover.png",
    "stairDown": "https://site.com/stairDown.png",
    "stairDownHover": "https://site.com/stairDown-hover.png",
  },
  // Custom UI icons
  "uiIcons": {
    "tabs.2D": "https://site.com/2D.png",
    "tabs.3D": "https://site.com/3D.png",
    "tabs.360": "https://site.com/360.png",
    "tabs.roomIndexer": "https://site.com/roomIndexer.png",
    "room.kidroom": "https://site.com/kidroom.png",
    "room.balcony": "https://site.com/balcony.png",
    "room.gym": "https://site.com/gym.png",
    "room.bathroom": "https://site.com/bathroom.png",
    "room.bedroom": "https://site.com/bedroom.png",
    "room.garage": "https://site.com/garage.png",
    "room.hallway": "https://site.com/hallway.png",
    "room.kitchen": "https://site.com/kitchen.png",
    "room.office": "https://site.com/office.png",
    "room.living": "https://site.com/living.png",
    "room.storage": "https://site.com/storage.png",
    "room.terrace": "https://site.com/terrace.png",
    "room.toilet": "https://site.com/toilet.png",
    "float.close": "https://site.com/close.png",
    "float.extend": "https://site.com/extend.png",
    "float.multiMap": "https://site.com/multiMap.png",
    "float.shrink": "https://site.com/shrink.png",
    "float.fullscreen": "https://site.com/fullscreen.png",
    "instructions.move": "https://site.com/move.png",
    "instructions.research": "https://site.com/research.png",
    "instructions.createPoints": "https://site.com/createPoints.png",
    "instructions.deletePoints": "https://site.com/deletePoints.png",
    "controls.rulerOn": "https://site.com/rulerOn.png",
    "controls.rulerOff": "https://site.com/rulerOff.png",
    "controls.prevPanorama": "https://site.com/prevPanorama.png",
    "controls.nextPanorama": "https://site.com/nextPanorama.png",
    "controls.minusScale": "https://site.com/minusScale.png",
    "controls.plusScale": "https://site.com/plusScale.png",
    "controls.autoRotateOn": "https://site.com/autoRotateOn.png",
    "controls.autoRotateOff": "https://site.com/autoRotateOff.png",
    "controls.arrowLeft": "https://site.com/arrowLeft.png"
  },
  // Panorama fade time in seconds
  "panoramaFadeTime": 0.5,

  // Panorama miniplan type, use svg or original
  "panoramaMiniplanType": "svg",

  // Overrides for interactive opacity
  "interactiveHoverOpacity": 0.65,
  "interactiveActiveOpacity": 0.8,

  // Camera sensitivity
  "cameraHorizontalSensitivity": 1,
  "cameraVerticalSensitivity": 1,
  "mobileCameraHorizontalSensitivity": 2,
  "mobileCameraVerticalSensitivity": 2,

  // Widget external integrations
  "integrations": {
    // https://docs.sentry.io/platforms/javascript/configuration/options/
    "sentry": {
      "dsn": "https://1234567890abcdef@sentry.com/12345"
    }
  }
}
```

## Widget object from the REST API

JSON object returned from backend

```js
export default {
  primary_variant: "<id>", // primary variant to open
  variants: [              // all plan variants
    {
      variant_info: {
        style_name: "<name>",          // the name of the variant style
        is_renovation: true,           // is there a renovation in the plan
      },
      plan_id: "<uuid>",               // variant service plan uuid
      json: "<url>",                   // json url for additional plan data
      assets_path: "<path>",           // plan assets base url
      capabilities: { /*...*/ },       // plan capabilities
      rendering_settings: { /*...*/ }, // plan rendering settings

      primary_floor: 0,                // primary plan floor index
      floors: [                        // plan floors
        {
          original_plan_img: "<url>",           // absolute path to original plan image
          miniplan_img: "<url>",                // absolute path to miniplan image

          top_view: {
            image_template: "<template>",       // template for top view decoding
            room_ids_template: "<template>",    // template for top view mask decoding
            items: ["<filename>"],              // top view image names
          },
          isometric_view: {
            image_template: "<template>",       // template for isometric images decoding
            room_ids_template: "<template>",    // template for isometric images masks decoding
            items: ["<filename>"],              // isometric images names
          },
          panorama_view: {
            template: "<template>",             // template for panorama image decoding
            scene_depth_template: "<template>", // template for panorama depth image decoding
            primary_camera_point_id: "<id>",    // primary plan camera id
          },

          location: { X: 0, Y: 0, Z: 0 },        // floor location
          height: 280,                           // floor height

          vertices: [ /*...*/ ],                 // plan vertices data
          rooms: [ /*...*/ ],                    // plan rooms data
          camera_points: [ /*...*/ ],            // plan camera points data
          walls: [ /*...*/ ],                    // plan walls data
          doors: [ /*...*/ ],                    // plan doors data
          stairs: [ /*...*/ ],                   // plan stairs data
          portals: [ /*...*/ ],                  // plan portals data
          apertures: [ /*...*/ ],                // plan apertures data
          decors: [ /*...*/ ],                   // plan decors data

          object_pairs: [],                      // color pairs for furniture data
          room_pairs: [],                        // color pairs for room data
          plan_meta: { /*...*/ },                // plan metadata
        },
      ],
    },
  ],
};
```

# Versioning
For the latest stable version refer to the latest up-to-date version in [Quick Start](#quick-start)

This project is maintained under the [Semantic Versioning guidelines](https://semver.org).

However, some versions are being developed for specific clients. **We do not recommend using them**, as changes in these versions are not documented and may affect your functionality.

# Copyright and license
The project code is licensed under the [GPLv3 license](https://www.gnu.org/licenses/gpl-3.0-standalone.html).

Project code and documentation copyright [hart-digital.com](https://hart-digital.com).
All renders of floor plans copyright [getfloorplan.com](https://getfloorplan.com).
