Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added custom provider support #83

Closed
wants to merge 2 commits into from

Conversation

thearturca
Copy link
Contributor

@thearturca thearturca commented Aug 14, 2024

This pull request includes new feature: custom provider.

Custom provider reads refresh_fn_path and optional start_fn_path or/and stop_fn_path scripts. Then runs refresh_fn_path function on interval defined via refresh_interval field and uses the return of that function as state.

You can access state via custom provider like that: custom.state.

Example

template somewhere in config.yaml:

    template/my-last-name:
      styles: |
        display: flex;
        align-items: center;
        flex-direction: column;
        color: var(--foreground-color);
        border: none;
        border-radius: 4px;
        background: none;

      providers: 
        - type: 'custom'
          refresh_fn_path: 'script.js#fetchMe'

      template: |
        <span>{{ custom.state.lastName }}</span>

Actual script:

export async function fetchMe(event, context) {
      const res = await fetch('https://dummyjson.com/users/1', {
            method: 'GET',
      });

      return await res.json();
}

Partially closes #75

@thearturca thearturca changed the title feat(providers): added custom provider support feat: added custom provider support Aug 14, 2024
@thearturca
Copy link
Contributor Author

thearturca commented Aug 14, 2024

Also added event based custom provider. Instead calling refresh function on interval we subscribing to event. Script side became more complex but allows to update on realtime with websockets, streams etc.

In emitter function you have to return EventTarget object that will emit event with id == 'value'. Event object should have value property that will be set as state in provider and will be available in custom.state.

Example

config.yaml:

    template/random-number:
      styles: |
        display: flex;
        align-items: center;
        flex-direction: column;
        color: var(--foreground-color);
        border: none;
        border-radius: 4px;
        background: none;

      providers: 
        - type: 'custom'
          emitter_fn_path: 'script.js#test_emitter'
          stop_fn_path: 'script.js#test_emitter_stop'

      template: |
        <span>{{ custom.state }}</span>

script.js:

let interval;

export async function test_emitter(event, context) {
      const target = new EventTarget();

      interval = setInterval(() => {
            console.log("emitter");
            const event = new Event("value");
            event.value = Math.random();
            target.dispatchEvent(event);
      }, 1000);

      return target;
}

export function test_emitter_stop(event, context) {
      console.log("clearing");
      clearInterval(interval);
}

@lars-berger
Copy link
Member

Been drafting something related (no code yet, just brainstorming a config):

  providers:
    - type: 'script'
      alias: 'formatUtils'
      path: 'myscript.js'
  template: |
    {{ formatUtils.toFixed(someOtherProviderVar) }}

(loads the myscript.js module and exposes it as variable formatUtils)

Which would allow us to achieve the same as the interval example, and be more flexible about its usage (e.g. updating the interval with dynamic values, conditionally starting/stopping on events, etc)

// JS script to be loaded via `script` provider

// We need to expose the `createStore` solidjs primitive, i think it might work out-of-the-box
// to just import it in the script file like this. Could alternatively re-export `createStore` from
// the `zebar` npm lib and import it as `import { createStore } from 'zebar'`.
import { createStore } from "solid-js/store"; 

export const random = createStore(Math.random());

export function startInterval() { ... }

export function stopInterval() { ... }

Could then access the variable {{ script.random }} and the template will auto-update whenever it changes. Another big advantage of doing it this way is that event callbacks will also have access to these variables and functions, eg

export function focusWorkspace(event, context) {
  context.providers.script.stopInterval()
}

@thearturca
Copy link
Contributor Author

Been drafting something related (no code yet, just brainstorming a config):

  providers:
    - type: 'script'
      alias: 'formatUtils'
      path: 'myscript.js'
  template: |
    {{ formatUtils.toFixed(someOtherProviderVar) }}

(loads the myscript.js module and exposes it as variable formatUtils)

Which would allow us to achieve the same as the interval example, and be more flexible about its usage (e.g. updating the interval with dynamic values, conditionally starting/stopping on events, etc)

// JS script to be loaded via `script` provider

// We need to expose the `createStore` solidjs primitive, i think it might work out-of-the-box
// to just import it in the script file like this. Could alternatively re-export `createStore` from
// the `zebar` npm lib and import it as `import { createStore } from 'zebar'`.
import { createStore } from "solid-js/store"; 

export const random = createStore(Math.random());

export function startInterval() { ... }

export function stopInterval() { ... }

Could then access the variable {{ script.random }} and the template will auto-update whenever it changes. Another big advantage of doing it this way is that event callbacks will also have access to these variables and functions, eg

export function focusWorkspace(event, context) {
  context.providers.script.stopInterval()
}

I've started to implement this and realized that script will lose elementContext .
To fix that script needs lifecycle functions like init to hand over elementContext and stop to notify script that is is not used anymore and should should.

import { createStore } from "solid-js/store"; 

export const random = createStore(Math.random());

export function startInterval() { ... }

export function stopInterval() { ... }


// this is required
export function init(event, context) {
    startInterval();
}

// this is required
export function stop(event, context) {
    stopInterval();
}

Or we can require function that returns reactive solidjs object with stop method (to run it on cleanup. I'm curious if onCleanup works inside script lol)

import { createStore } from "solid-js/store"; 

function myScript(event, context) {
    
    const random = createStore(Math.random());
    function startInterval() { ... }

    function stopInterval() { ... }

    function stop(context) {
        stopInterval();
    }

    startInterval();
    return {
        startInterval,
        stopInterval,
        get random() {
            return random;
        },
        // this is required
        stop,
    }
}

@lars-berger
Copy link
Member

I've started to implement this and realized that script will lose elementContext .

Might be wrong here but is passing down the elementContext necessary?

@thearturca
Copy link
Contributor Author

Might be wrong here but is passing down the elementContext necessary?

I think it would be great to have ability to extend existing providers or have logic that depends on providers.

In my use case I have focused-window template for komorebi. I could create script that depends on komorebi provider and return focused window. It will make my config cleaner but (yes) more complex.

      template: |
        <span id="window-name">
          {{ 
            komorebi.focusedWorkspace.maximizedWindow?.title
            ?? komorebi.focusedWorkspace.tilingContainers[komorebi.focusedWorkspace.focusedContainerIndex]?.windows[0]?.title 
            ?? komorebi.focusedWorkspace.monocleContainer?.windows[0]?.title
            ?? "-" 
          }}
        </span>

@thearturca thearturca closed this Oct 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: ✅ Done
Development

Successfully merging this pull request may close these issues.

[Feature Request] Shell / text provider
2 participants