### WIDGET PROPERTIES EDITOR


A Svelte Component Module that will be loaded and used to edit the widget properties: design and content.

Based on the Widget's design schema and content schema it generates 2 panels of input controllers for the user to edit the widget's properties.

## How to use the component in your project

1. add the component as a dependency in the npm package of the project
2. import the component in the project: 
`import PropsEditor from '@widgetic/props-editor';`
3. load the design and content schemas and pass them as props to the component:
```js
let designSchemaUrl = '/design-debug-schema.json';

const designResponse = await fetch(designSchemaUrl);
const designSchema = await designResponse.json();

let contentSchemaUrl = '/widget-meta-sample/content.json';
const contentResponse = await fetch(contentSchemaUrl);
const contentSchema = await contentResponse.json();
```
4. use the component in the page layouts:
```svelte
<PropsEditor {designSchema} {contentSchema} />
```
5. the component will render the 2 panels and the input controllers
6. the user can edit the widget properties in the 2 panels
7. the changes will be sent from the component with an event to the parent component:
```svelte
dispatch('propertyChange', { schema, property, value});
```
8. the parent component will catch the event and save the changes in the database:
```svelte
<PropsEditor {designSchema} {contentSchema} on:propertyChange={(event) => {
  console.log('propertyChange event caught in the parent component', event);
}} />
```


## Input Controllers

The component uses the input controllers to edit the widget properties.
Each input controller is a separate component that is located in the `src/lib/components/ic` folder.

The input controllers are responsible for rendering the input elements for the widget properties and handling the user input.

The input controllers are dynamically imported from the component based on the the properties from the design and content schemas.

Note: components that uses an object as a value type, like `date_input` and `range_calendar_input`, need to handle a conversion of the defaultValue from the schema to the object type they use:
```svelte
 $: value && (value = { 
    start: new CalendarDate(value.start?.era || 'AD', value.start?.year || 0, value.start?.month || 0, value.start?.day || 0), 
    end: new CalendarDate(value.end?.era || 'AD', value.end?.year || 0, value.end?.month || 0, value.end?.day || 0) 
  });
```



### How to develop the component
## Initialize the sveltekit project

1. clone the repo
2. cd into the project
3. install dependencies

```bash
npm i --legacy-peer-deps
```
Note: use --legacy-peer-deps if you get peer dependency errors
as our project uses svelte 5 and some of the dependencies are still using svelte 4.

## Start the development server

```bash
npm run dev

# or start the server and open the app in a new browser tab
npm run dev -- --open
```

## Build the project

To create a production version of your app:

```bash
npm run build
```

## Preview the project

You can preview the production build with `npm run preview`.


## Deploy the project to a server
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment(node in our case on render.com's servers):

1. Install node adapter:
```bash
npm install --save-dev @sveltejs/adapter-node
```
Also add it to the `svelte.config.js` file:
```js
import adapter from '@sveltejs/adapter-node';
```

2. Build the project:
```bash
npm run build
```

3. Deploy to render.com

Because we deploy to render.com, the build command is run automatically if we push a new commit to the main branch.
But you must connect the gitlab repo of this project to the render.com project before and add there the secret env variables and start command.

In the future if you want to deploy a manual build you can run:
```bash
npm run deploy
```
Note that you need to add the right deploy script that copies the dist folder to the root of the project on the server.


## Deploy the component to the npm package registry

```bash
npm publish
```


### Font Loading and Batching Strategy

The font loading system uses a combination of batching, lazy loading, and scroll-based loading to optimize performance:

1. **Initial Load**:
   - First 12 fonts (initial batch) are loaded when FontManager initializes
   - These fonts are cached and immediately available to all font controllers
   - Initial fonts include most popular Google Fonts and recently used fonts
   - Use subset of the fonts for the initial load, load full fonts only when needed
   - Example:
     ```javascript
     const initialFonts = [
       'Roboto', 'Open Sans', 'Montserrat', 'Poppins', 
       'Lato', 'Inter', 'Material Icons'
     ];
     ```

2. **Batch Loading**:
   - Remaining fonts are loaded in batches of 100 fonts
   - Each font controller maintains its own batch index
   - Batches are loaded only when scrolling reaches the bottom of the list
   - Example flow:
     ```
     Initial fonts (0-12) → Batch 1 (12-112) → Batch 2 (112-212) → etc.
     ```

3. **Scroll-Based Loading**:
   - Font loading is triggered by scroll events
   - When user scrolls near bottom (100px threshold), next batch is loaded
   - Each batch load updates the controller's batch index
   - Example:
     ```javascript
     if (distanceFromBottom < threshold && !isLoadingBatch) {
       loadNextBatch();  // Loads next 100 fonts
     }
     ```

4. **Font Visibility Optimization**:
   - IntersectionObserver watches for font elements entering viewport
   - Only fonts currently visible are loaded into the browser
   - Prevents unnecessary loading of fonts not in view
   - Example:
     ```javascript
     // Font is loaded only when it becomes visible
     observer.observe(fontElement);
     ```

5. **Caching Strategy**:
   - Font list is cached in localStorage (24 hours duration)
   - Browser caches actual font files (1 year duration)
   - Each controller maintains its own loading state
   - Cache structure:
     ```javascript
     {
       fontList: [...],        // Complete list of fonts
       loadedFonts: [...],     // Fonts loaded in browser
       batchIndices: Map<controllerId, index>  // Track batch position
     }
     ```

6. **Font Loading Flow**:
   - FontManager initializes with API key
   - Checks browser cache for already loaded fonts
   - Loads initial batch of popular fonts
   - Sets up observers for lazy loading
   - Example:
     ```javascript
     // Initialize FontManager
     const fontManager = await FontManager.getInstance(apiKey);
     
     // Get cached fonts
     const cachedFonts = await fontManager.getBrowserCachedFonts();
     
     // Load initial fonts
     const initialFonts = await fontManager.getInitialFontList();
     ```

7. **Controller-Specific Handling**:
   - Each font controller gets its own batch index
   - Controllers load fonts independently
   - Prevents interference between controllers
   - Example:
     ```javascript
     // Reset batch index for new controller
     fontManager.resetBatchIndex(controllerId);
     
     // Get next batch for specific controller
     const batch = fontManager.getNextBatch(controllerId);
     ```

This approach ensures:
- Fast initial load with most popular fonts
- Smooth scrolling experience
- Efficient memory usage
- No unnecessary font loading
- Proper font caching
- Independent controller operation



8. **Font Upload Format**:
   - The fonts uploaded need to respect the name structure of fonts downloaded from Google Fonts (GeistMono-Black.ttf - FONTFAMILY-VARIANT.FORMAT)
   - The format needs to be .woff2 ideally, otherwise .woff, tiff, or otf.

9. **SVG Upload Format**:
   - The svgs uploaded need respect the name structure (Arrows-ArrowRight.ttf - TAG-NAME.SVG), where "Arrows" will list all SVGs in the group.
  


### Adding a new input controller

  - Add the new controller to the `Frontend/editor/src/lib/components/ic/index.ts` file.
  - import the new controller to the `Frontend/editor/src/lib/components/PropsEditor.svelte` file.
  