Skip to content

Commit

Permalink
Merge branch 'release/0.2.0' into released
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack-Works committed Apr 8, 2019
2 parents 3bafe7a + 15fb05e commit c64d6aa
Show file tree
Hide file tree
Showing 10 changed files with 385 additions and 108 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# @holoflows/kit · ![GitHub license](https://img.shields.io/badge/license-AGPL-blue.svg?style=flat-square) [![npm version](https://img.shields.io/npm/v/holoflows/kit.svg?style=flat-square)](https://www.npmjs.com/package/@holoflows/kit) ![Ciecle CI](https://img.shields.io/circleci/project/github/DimensionFoundation/holoflows-kit.svg?style=flat-square&logo=circleci)
# @holoflows/kit · ![GitHub license](https://img.shields.io/badge/license-AGPL-blue.svg?style=flat-square) [![npm version](https://img.shields.io/npm/v/@holoflows/kit.svg?style=flat-square)](https://www.npmjs.com/package/@holoflows/kit) ![Ciecle CI](https://img.shields.io/circleci/project/github/DimensionFoundation/holoflows-kit.svg?style=flat-square&logo=circleci)

Toolkit for modern web browser extension developing.

Expand Down
28 changes: 25 additions & 3 deletions doc/en/DOM.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,14 +401,14 @@ ele.addEventListener('click', watcher.eventListener)

### <a id="doc-valueref">ValueRef</a>

ValueRef is not using `LiveSelector`. It listen to `.value =`. e.g.:
ValueRef is not related to `Watcher`. It listen to `.value =`. e.g.:

```ts
const ref = new ValueRef(0)
ref.value // 0
ref.addEventListener('onChangeFull', () => console.log('onChange'))
ref.addListener((newVal, old) => console.log(newVal, old))
ref.value = 1
// Log: onChange
// Log: 1, 0
ref.value // 1
```

Expand All @@ -420,6 +420,28 @@ ref.value // 1

Current value. If you assign it, then it will emit event.

#### <a id="doc-valueref-addlistener>`.addListener(fn: (newValue: T, oldValue: T) => void): () => void`</a>

Add a listener. If value get changed, it will be called with these parameters.

- newValue: The new value
- oldValue: The old value

`addListener` will return a function. Call that function will remove this listener. Be useful in React hooks.

```ts
const [val, setVal] = React.useState(0)
React.useEffect(() => ref.addListener(setVal))
```

#### <a id="doc-valueref-removelistener>`.removeListener(fn: (newValue: T, oldValue: T) => void): void`</a>

Remove listener.

#### <a id="doc-valueref-removealllistener>`.removeAllListener(): void`</a>

Remove all listeners.

### <a id="doc-watcher-protected">abstract class Watcher (protected)</a>

Here are `protected` fields and methods in the `Watcher` class. If you are not extending it, you don't need it.
Expand Down
103 changes: 102 additions & 1 deletion doc/en/Extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,108 @@ Log to console when receive/send event

# <a id="doc-asynccall">AsyncCall</a>

A simple RPC. [Documentation in the code](../../src/Extension/Async-Call.ts)
Async call between different context.

A High level abstraction of MessageCenter.

> Shared code
- How to stringify/parse parameters/returns should be shared, defaults to NoSerialization.
- `key` should be shared.

> One side
- Should provide some functions then export its type (for example, `BackgroundCalls`)
- `const call = AsyncCall<AllFunctions, ForegroundCalls>(backgroundCalls)`
- Then you can `call` any method on `ForegroundCalls`

> Other side
- Should provide some functions then export its type (for example, `ForegroundCalls`)
- `const call = AsyncCall<AllFunctions, BackgroundCalls>(foregroundCalls)`
- Then you can `call` any method on `BackgroundCalls`

_Note: Two sides can implement the same function_

Example:

```typescript
// Mono repo
// On UI
const UI = {
async dialog(text: string) {
alert(text)
},
}
export type UI = typeof UI
const callsClient = AsyncCall<Server>(UI)
callsClient.sendMail('hello world', 'what')

// On server
const Server = {
async sendMail(text: string, to: string) {
return true
},
}
export type Server = typeof Server
const calls = AsyncCall<UI>(Server)
calls.dialog('hello')
```

## Options

- key: A key to prevent collision with other AsyncCalls. Can be anything, but need to be the same on the both side. (Defaults to `default`)
- serializer: How to serialization and deserialization parameters and return values (Defaults to `NoSerialization`)
- MessageCenter: A class that can let you transfer messages between two sides (Defaults to `MessageCenter` of @holoflows/kit)
- dontThrowOnNotImplemented: If this side receive messages that we didn't implemented, throw an error (Defaults to `true`)
- writeToConsole: Write all calls to console. (Defaults to `true`)

## Serializer:

We offer some built-in serializer:

- `NoSerialization` (Do not do any serialization)
- `JSONSerialization` (Use JSON.parse/stringify) (You can provide a `replacer`! See [JSON.stringify](https://mdn.io/JSON.stringify))

You can also build your own serializer by implement interface `Serialization`

## Return:

`typeof` the type parameter. (`<OtherSideImplementedFunctions>`)

# <a id="doc-automatedtabtask">AutomatedTabTask</a>

Based on AsyncCall. Open a new page in the background, execute some task, then close it automatically.

Usage:

> In content script: (You must run this in the page you wanted to run task in!)
```ts
export const task = AutomatedTabTask({
async taskA() {
return 'Done!'
},
})
```

> In background script:
```ts
import { task } from '...'
task('https://example.com/').taskA()
// Open https://example.com/ then run taskA() on that page, which will return 'Done!'
```

## Parameters:

- taskImplements: All tasks that background page can call.
- AsyncCallKey: A unique key, defaults to a extension specific url.

## Return:

- `null` on content script
- `typeof taskImplements` on background page

# <a id="doc-contexts">Contexts</a>

Expand Down
28 changes: 25 additions & 3 deletions doc/zh-CN/DOM.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,14 +399,14 @@ ele.addEventListener('click', watcher.eventListener)

### <a id="doc-valueref">ValueRef</a>

ValueRef 与 `LiveSelector` 无关,它监听 `.value =`。例如:
ValueRef 与 `Watcher` 无关,它监听 `.value =`。例如:

```ts
const ref = new ValueRef(0)
ref.value // 0
ref.addEventListener('onChangeFull', () => console.log('onChange'))
ref.addListener((newVal, old) => console.log(newVal, old))
ref.value = 1
// Log: onChange
// Log: 1, 0
ref.value // 1
```

Expand All @@ -418,6 +418,28 @@ ref.value // 1

当前值。写入的话会触发事件。

#### <a id="doc-valueref-addlistener>`.addListener(fn: (newValue: T, oldValue: T) => void): () => void`</a>

添加监听器。当值改变的时候会被触发。

- newValue: 新的值
- oldValue: 旧的值

返回一个函数,调用该函数会取消该监听器。可以用在 React 的 hooks 中。

```ts
const [val, setVal] = React.useState(0)
React.useEffect(() => ref.addListener(setVal))
```

#### <a id="doc-valueref-removelistener>`.removeListener(fn: (newValue: T, oldValue: T) => void): void`</a>

取消监听器。

#### <a id="doc-valueref-removealllistener>`.removeAllListener(): void`</a>

取消所有监听器。

### <a id="doc-watcher-protected">abstract class Watcher (protected)</a>

这里 Watcher 的 protected 属性与方法。如果你不是在自己继承 `Watcher`,那么你用不到这里提到的属性和方法。
Expand Down
103 changes: 101 additions & 2 deletions doc/zh-CN/Extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,108 @@ mc.send // 只允许发送 keyof AllEvents, 且 data 需要是 AllEvents[key]

# <a id="doc-asynccall">AsyncCall</a>

一个简易 RPC
在不同环境之间进行远程过程调用

[文档见此](../../src/Extension/Async-Call.ts)
一个 MessageCenter 的高阶抽象。

> 两端共享的代码
- 如何对参数和返回值进行序列化、反序列化的代码应该在两端共享,默认值为不进行序列化。
- `key` should be shared.

> 甲端
- 应该提供一些函数供另一端调用。(例如, `BackgroundCalls`
- `const call = AsyncCall<ForegroundCalls>(backgroundCalls)`
- 然后你就能把 `call` 当作 `ForegroundCalls` 类型的对象来调用另一端的代码了。

> 乙端
- 应该提供一些函数供另一端调用。(例如, `ForegroundCalls`
- `const call = AsyncCall<BackgroundCalls>(foregroundCalls)`
- 然后你就能把 `call` 当作 `BackgroundCalls` 类型的对象来调用另一端的代码了。

_提示: 两端可以定义同一个函数_

例子:

```typescript
// Mono repo
// UI 一端
const UI = {
async dialog(text: string) {
alert(text)
},
}
export type UI = typeof UI
const callsClient = AsyncCall<Server>(UI)
callsClient.sendMail('hello world', 'what')

// 服务器一端
const Server = {
async sendMail(text: string, to: string) {
return true
},
}
export type Server = typeof Server
const calls = AsyncCall<UI>(Server)
calls.dialog('hello')
```

## 选项

- key: 一个 Key,以防与其他同一信道上通信的 AsyncCall 冲突,可以是任何内容,但需要在两端一致。(默认为 `default`
- serializer: 如何序列化、反序列化参数和返回值。(默认为 `NoSerialization`
- MessageCenter: 一个消息中心,作为通信信道,只要实现了对应接口即可使用任何信道通信,比如 `WebSocket` `chrome.runtime` 等。(默认为 `@holoflows/kit` 自带的 `MessageCenter`
- dontThrowOnNotImplemented: 如果本端收到了远程调用,但本端没有实现该函数时,是否忽略错误。(默认为 `true`
- writeToConsole: 是否把所有调用输出到控制台以便调试。(默认为 `true`

## 序列化

有一些内置的序列化办法:

- `NoSerialization` (不进行任何序列化)
- `JSONSerialization` (使用 JSON.parse/stringify 进行序列化) (你可以提供一个 `replacer`!见 [JSON.stringify](https://mdn.io/JSON.stringify))

你也可以实现自己的序列化,只需要实现 `Serialization` 接口即可。

## 返回值

返回一个类型为 `OtherSideImplementedFunctions` 的对象。

# <a id="doc-automatedtabtask">AutomatedTabTask</a>

基于 AsyncCall。打开一个新标签页,执行一些任务,然后自动关闭标签页。

例子:

> 在 content script 中(一定要在你需要执行任务的页面里注入!):
```ts
export const task = AutomatedTabTask({
async taskA() {
return 'Done!'
},
})
```

> 在背景页中:
```ts
import { task } from '...'
task('https://example.com/').taskA()
// 打开 https://example.com/,在上面运行 taskA(),等待返回结果('Done!')然后自动关闭页面
```

## 参数

- taskImplements: Content script 能执行的任务。
- AsyncCallKey: 一个 Key,默认对每个插件不同。

## 返回值

- 在 content script 上为 `null`
- 在背景页上为 `typeof taskImplements`

# <a id="doc-contexts">Contexts</a>

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@holoflows/kit",
"version": "0.1.0",
"version": "0.2.0",
"module": "./es/index.js",
"main": "./dist/out.js",
"typings": "./es/",
Expand Down
49 changes: 34 additions & 15 deletions src/DOM/Watchers/ValueRef.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
import { Watcher } from '../Watcher'
import { LiveSelector } from '../LiveSelector'

export class ValueRef<T> extends Watcher<T> {
type Fn<T> = (newVal: T, oldVal: T) => void
export class ValueRef<T> {
/** Get current value of a ValueRef */
get value() {
return this._value
}
set value(v: T) {
this._value = v
this.watcherCallback()
/** Set current value of a ValueRef */
set value(newVal: T) {
const oldVal = this._value
this._value = newVal
for (const fn of this.watcher.keys()) {
try {
fn(newVal, oldVal)
} catch (e) {
console.error(e)
}
}
}
constructor(private _value: T) {
super(new LiveSelector().replace(() => [this._value]))
this.startWatch()
/** All watchers */
private watcher = new Map<Fn<T>, boolean>()
constructor(private _value: T) {}
/**
* Add a listener. This will return a remover.
* Use it like: useEffect(() => ref.addListener(() => {...}))
*/
addListener(fn: Fn<T>) {
this.watcher.set(fn, true)
return () => this.removeListener(fn)
}
startWatch() {
this.watching = true
return this
/**
* Remove a listener
*/
removeListener(fn: Fn<T>) {
this.watcher.delete(fn)
}
stopWatch() {
this.watching = false
/**
* Remove all listeners
*/
removeAllListener() {
this.watcher = new Map()
}
}
Loading

0 comments on commit c64d6aa

Please sign in to comment.