Skip to content

Commit

Permalink
docs: variable
Browse files Browse the repository at this point in the history
  • Loading branch information
Aylur committed Sep 1, 2024
1 parent 9be159f commit 8da6a21
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 10 deletions.
19 changes: 12 additions & 7 deletions core/gjs/src/variable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,14 @@ class VariableWrapper<T> extends Function {

observe(
objs: Array<[obj: Connectable, signal: string]>,
callback: (...args: any[]) => T): Variable<T>
callback: (...args: any[]) => T,
): Variable<T>

observe(
obj: Connectable,
signal: string,
callback: (...args: any[]) => T): Variable<T>
callback: (...args: any[]) => T,
): Variable<T>

observe(
objs: Connectable | Array<[obj: Connectable, signal: string]>,
Expand All @@ -184,12 +186,15 @@ class VariableWrapper<T> extends Function {
if (Array.isArray(objs)) {
for (const obj of objs) {
const [o, s] = obj
o.connect(s, set)
const id = o.connect(s, set)
this.onDropped(() => o.disconnect(id))
}
}
else {
if (typeof sigOrFn === "string")
objs.connect(sigOrFn, set)
if (typeof sigOrFn === "string") {
const id = objs.connect(sigOrFn, set)
this.onDropped(() => objs.disconnect(id))
}
}

return this as unknown as Variable<T>
Expand All @@ -199,7 +204,7 @@ class VariableWrapper<T> extends Function {
const Deps extends Array<Variable<any> | Binding<any>>,
Args extends {
[K in keyof Deps]: Deps[K] extends Variable<infer T>
? T : Deps[K] extends Binding<infer T> ? T : never
? T : Deps[K] extends Binding<infer T> ? T : never
},
V = Args,
>(deps: Deps, fn: (...args: Args) => V = (...args) => args as unknown as V) {
Expand All @@ -221,7 +226,7 @@ export const Variable = new Proxy(VariableWrapper as any, {
}) as {
derive: typeof VariableWrapper["derive"]
<T>(init: T): Variable<T>
new<T>(init: T): Variable<T>
new <T>(init: T): Variable<T>
}

export default Variable
79 changes: 79 additions & 0 deletions docs/src/content/docs/ags/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,82 @@ App.start({
If there is a name clash with an icon from your current icon pack
the icon pack will take precedence
:::

## Logging

The `console` API in gjs uses glib logging functions.
If you just want to print some text as is to stdout
use the globally available `print` function or `printerr` for stderr.

```js
print("print this line to stdout")
printerr("print this line to stderr")
```

## Binding custom structures

The `bind` function can take two types of objects.

```typescript
interface Subscribable<T = unknown> {
subscribe(callback: (value: T) => void): () => void
get(): T
}

interface Connectable {
connect(signal: string, callback: (...args: any[]) => unknown): number
disconnect(id: number): void
}
```

`Connectable` is for mostly gobjects, while `Subscribable` is for `Variables`
and custom objects.

For example you can compose `Variables` in using a class.

```typescript
type MyVariableValue = {
number: number
string: string
}

class MyVariable {
number = Variable(0)
string = Variable("")

get(): MyVariableValue {
return {
number: this.number.get(),
string: this.string.get(),
}
}

subscribe(callback: (v: MyVariableValue) => void) {
const unsub1 = this.number.subscribe((value) => {
callback({ string: value, number: this.number.get() })
})

const unsub2 = this.string.subscribe((value) => {
callback({ number: value, string: this.string.get() })
})

return () => {
unsub1()
unsub2()
}
}
}
```

Then it can be used with `bind`.

```tsx
function MyWidget() {
const myvar = new MyVariableValue()
const label = bind(myvar).as(({ string, number }) => {
return `${string} ${number}`
})

return <label label={label} />
}
```
2 changes: 1 addition & 1 deletion docs/src/content/docs/ags/utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ They are **not** executed in a shell environment,
they do **not** expand env variables like `$HOME`,
and they do **not** handle logical operators like `&&` and `||`.

If you want to run bash, run them with bash.
If you want bash, run them with bash.

```js
exec(["bash", "-c", "command $VAR && command"])
Expand Down
138 changes: 136 additions & 2 deletions docs/src/content/docs/ags/variable.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,141 @@
title: Variable
description: Reference of the builtin Variable type
sidebar:
order: 6
order: 6
---

## TODO:
```js
import { Variable } from "astal"
```

Variable is just a simple `GObject` that holds a value.
And has shortcuts for hooking up subprocesses.

## Variable as state

```typescript
const myvar = Variable<string>("initial-value")

// whenever its value changes, callback will be executed
myvar.subscribe((value: string) => {
console.log(value)
})

// settings its value
myvar.set("new value")

// getting its value
const value = myvar.get()

// binding them to widgets
Widget.Label({
label: bind(myvar).as((value) => `transformed ${value}`),
label: myvar((value) => `transformed ${value}`), // shorthand for the above
})
```

:::caution
Make sure to make the transform functions pure. The `.get()` function can be called
anytime by `astal` especially when `deriving`.
:::

## Composing variables

Using `Variable.derive` we can compose both Variables and Bindings.

```typescript
const v1: Variable<number> = Variable(2)
const v2: Variable<number> = Variable(3)

// first argument is a list of dependencies
// second argument is a transform function,
// where the parameters are the values of the dependencies in the order they were passed
const v3: Variable<number> = Variable.derive([v1, v2], (v1, v2) => {
return v1 * v2
})

const b1: Binding<string> = bind(obj, "prop")
const b2: Binding<string> = bind(obj, "prop")

const b3: Variable<string> = Variable.derive([b1, b2], (b1, b2) => {
return `${b1}-${b2}`
})
```

## Subprocess shorthands

Using `.poll` and `.watch` we can start subprocess and capture their
output in `Variables`. They can poll and watch at the same time, but they
can only poll/watch one subprocess.

:::caution
The command parameter is passed to [execAsync](/astal/ags/utilities/#executing-external-commands-and-scripts)
which means they are **not** executed in a shell environment,
they do **not** expand env variables like `$HOME`,
and they do **not** handle logical operators like `&&` and `||`.

If you want bash, run them with bash.

```js
Variable("").poll(1000, ["bash", "-c", "command $VAR && command"])
```

:::

```typescript
const myVar = Variable<number>(0)
.poll(1000, "command", (out: string, prev: number) => parseInt(out))
.poll(1000, ["bash", "-c", "command"], (out, prev) => parseInt(out))
.poll(1000, (prev) => prev + 1)
```

```typescript
const myVar = Variable<number>(0)
.watch("command", (out: string, prev: number) => parseInt(out))
.watch(["bash", "-c", "command"], (out, prev) => parseInt(out))
```

You can temporarily stop them and restart them whenever.

```js
myvar.stopWatch() // this kills the subprocess
myvar.stopPoll()

myvar.startListen() // launches the subprocess again
myvar.startPoll()

console.log(myvar.isListening())
console.log(myvar.isPolling())
```

## Gobject connection shorthands

Using `.observe` you can connect gobject signals and capture their value.

```typescript
const myvar = Variable("")
.observe(obj1, "signal", () => "")
.observe(obj2, "signal", () => "")
```

## Dispose if no longer needed

This will stop the interval and force exit the subprocess and disconnect gobjects.

```js
myVar.drop()
```

:::caution
Don't forget to drop them when they are defined inside widgets
with either `.poll`, `.watch` or `.observe`

```tsx
function MyWidget() {
const myvar = Variable().poll()

return <box onDestroy={() => myvar.drop()} />
}
```

:::

0 comments on commit 8da6a21

Please sign in to comment.