@blocksuite/store
Advanced tools
Comparing version 0.4.0-20230113184550-93ac0f4 to 0.4.0-20230114095726-16babb2
@@ -16,3 +16,3 @@ import type { Workspace } from '../workspace/workspace.js'; | ||
export declare function runOnce(): Promise<void>; | ||
export declare function assertExists<T>(val: T | null | undefined): asserts val is T; | ||
export { assertExists } from '@blocksuite/global/utils'; | ||
export declare function nextFrame(): Promise<unknown>; | ||
@@ -19,0 +19,0 @@ export declare function loadTestImageBlob(name: string): Promise<Blob>; |
@@ -39,7 +39,3 @@ const testResult = { | ||
} | ||
export function assertExists(val) { | ||
if (val === null || val === undefined) { | ||
throw new Error('val does not exist'); | ||
} | ||
} | ||
export { assertExists } from '@blocksuite/global/utils'; | ||
export async function nextFrame() { | ||
@@ -46,0 +42,0 @@ return new Promise(resolve => requestAnimationFrame(resolve)); |
@@ -16,2 +16,11 @@ /// <reference types="@blocksuite/global" /> | ||
} | ||
type Request<Flags extends Record<string, unknown> = BlockSuiteFlags, Key extends keyof Flags = keyof Flags> = { | ||
id: string; | ||
clientId: number; | ||
field: Key; | ||
value: Flags[Key]; | ||
}; | ||
type Response = { | ||
id: string; | ||
}; | ||
interface AwarenessState<Flags extends Record<string, unknown> = BlockSuiteFlags> { | ||
@@ -21,2 +30,4 @@ cursor?: SelectionRange; | ||
flags: Flags; | ||
request?: Request<Flags>[]; | ||
response?: Response[]; | ||
} | ||
@@ -41,9 +52,11 @@ interface AwarenessMessage<Flags extends Record<string, unknown> = BlockSuiteFlags> { | ||
setFlag<Key extends keyof Flags>(field: Key, value: Flags[Key]): void; | ||
getFlag<Key extends keyof Flags>(field: Key): any; | ||
getFlag<Key extends keyof Flags>(field: Key): Flags[Key] | undefined; | ||
setReadonly(value: boolean): void; | ||
isReadonly(): boolean; | ||
setRemoteFlag<Key extends keyof Flags>(clientId: number, field: Key, value: Flags[Key]): void; | ||
getLocalCursor(): SelectionRange | undefined; | ||
getStates(): Map<number, AwarenessState>; | ||
getStates(): Map<number, AwarenessState<Flags>>; | ||
private _onAwarenessChange; | ||
private _onAwarenessMessage; | ||
private _handleRemoteFlags; | ||
private _resetRemoteCursor; | ||
@@ -50,0 +63,0 @@ updateLocalCursor(): void; |
import * as Y from 'yjs'; | ||
import { Signal } from './utils/signal.js'; | ||
import { assertExists } from './utils/utils.js'; | ||
import { merge } from 'merge'; | ||
import { uuidv4 } from './utils/id-generator.js'; | ||
export class AwarenessAdapter { | ||
@@ -43,2 +43,5 @@ constructor( | ||
} | ||
if (this.getFlag('enable_set_remote_flag') === true) { | ||
this._handleRemoteFlags(); | ||
} | ||
}; | ||
@@ -65,13 +68,37 @@ this.space = space; | ||
getFlag(field) { | ||
const flags = this.awareness.getLocalState()?.flags; | ||
assertExists(flags); | ||
const flags = this.awareness.getLocalState()?.flags ?? {}; | ||
return flags[field]; | ||
} | ||
setReadonly(value) { | ||
const flags = this.getFlag('readonly'); | ||
this.setFlag('readonly', { ...flags, [this.space.prefixedId]: value }); | ||
const flags = this.getFlag('readonly') ?? {}; | ||
this.setFlag('readonly', { | ||
...flags, | ||
[this.space.prefixedId]: value, | ||
}); | ||
} | ||
isReadonly() { | ||
return this.getFlag('readonly')[this.space.prefixedId] ?? false; | ||
const rd = this.getFlag('readonly'); | ||
if (rd && typeof rd === 'object') { | ||
return Boolean(rd[this.space.prefixedId]); | ||
} | ||
else { | ||
return false; | ||
} | ||
} | ||
setRemoteFlag(clientId, field, value) { | ||
if (!this.getFlag('enable_set_remote_flag')) { | ||
console.error('set remote flag feature disabled'); | ||
return; | ||
} | ||
const oldRequest = this.awareness.getLocalState()?.request ?? []; | ||
this.awareness.setLocalStateField('request', [ | ||
...oldRequest, | ||
{ | ||
id: uuidv4(), | ||
clientId, | ||
field, | ||
value, | ||
}, | ||
]); | ||
} | ||
getLocalCursor() { | ||
@@ -85,2 +112,58 @@ const states = this.awareness.getStates(); | ||
} | ||
_handleRemoteFlags() { | ||
const nextTick = []; | ||
const localState = this.awareness.getLocalState(); | ||
const request = (localState?.request ?? []); | ||
const selfResponse = []; | ||
const fakeDirtyResponse = []; | ||
if (localState && Array.isArray(localState.response)) { | ||
selfResponse.push(...localState.response); | ||
fakeDirtyResponse.push(...localState.response); | ||
} | ||
const response = []; | ||
for (const [clientId, state] of this.awareness.getStates()) { | ||
if (clientId === this.awareness.clientID) { | ||
continue; | ||
} | ||
if (Array.isArray(state.response)) { | ||
response.push(...state.response); | ||
} | ||
if (Array.isArray(state.request)) { | ||
const remoteRequest = state.request; | ||
selfResponse.forEach((response, idx) => { | ||
if (response === null) { | ||
return; | ||
} | ||
const index = remoteRequest.findIndex(request => request.id === response.id); | ||
if (index === -1) { | ||
fakeDirtyResponse[idx].id = 'remove'; | ||
} | ||
}); | ||
remoteRequest.forEach(request => { | ||
if (request.clientId === this.awareness.clientID) { | ||
// handle request | ||
nextTick.push(() => { | ||
this.setFlag(request.field, request.value); | ||
}); | ||
selfResponse.push({ | ||
id: request.id, | ||
}); | ||
} | ||
}); | ||
} | ||
} | ||
response.forEach(response => { | ||
const idx = request.findIndex(request => request.id === response.id); | ||
if (idx !== -1) { | ||
request.splice(idx, 1); | ||
} | ||
}); | ||
nextTick.push(() => { | ||
this.awareness.setLocalStateField('request', request); | ||
this.awareness.setLocalStateField('response', selfResponse.filter((response, idx) => fakeDirtyResponse[idx] ? fakeDirtyResponse[idx].id !== 'remove' : true)); | ||
}); | ||
setTimeout(() => { | ||
nextTick.forEach(fn => fn()); | ||
}, 100); | ||
} | ||
_resetRemoteCursor() { | ||
@@ -87,0 +170,0 @@ this.space.richTextAdapters.forEach(textAdapter => textAdapter.quillCursors.clearCursors()); |
@@ -1,9 +0,4 @@ | ||
/// <reference types="@blocksuite/global" /> | ||
import type { BaseBlockModel } from '../base.js'; | ||
import type { BlockProps, PrefixedBlockProps, YBlock, YBlocks } from '../workspace/page.js'; | ||
import { PrelimText, Text, TextType } from '../text-adapter.js'; | ||
import type { Workspace } from '../workspace/index.js'; | ||
export declare function assertExists<T>(val: T | null | undefined): asserts val is T; | ||
export declare function assertFlavours(model: BaseBlockModel, allowed: string[]): void; | ||
export declare function matchFlavours<Key extends keyof BlockSuiteInternal.BlockModels & string = keyof BlockSuiteInternal.BlockModels & string>(model: BaseBlockModel, expected: Key[]): boolean; | ||
export declare function assertValidChildren(yBlocks: YBlocks, props: Partial<BlockProps>): void; | ||
@@ -10,0 +5,0 @@ export declare function initSysProps(yBlock: YBlock, props: Partial<BlockProps>): void; |
import * as Y from 'yjs'; | ||
import { PrelimText, Text } from '../text-adapter.js'; | ||
import { fromBase64, toBase64 } from 'lib0/buffer.js'; | ||
const SYS_KEYS = new Set(['id', 'flavour', 'children']); | ||
// https://stackoverflow.com/questions/31538010/test-if-a-variable-is-a-primitive-rather-than-an-object | ||
function isPrimitive(a) { | ||
return a !== Object(a); | ||
} | ||
export function assertExists(val) { | ||
if (val === null || val === undefined) { | ||
throw new Error('val does not exist'); | ||
} | ||
} | ||
export function assertFlavours(model, allowed) { | ||
if (!allowed.includes(model.flavour)) { | ||
throw new Error(`model flavour ${model.flavour} is not allowed`); | ||
} | ||
} | ||
export function matchFlavours(model, expected) { | ||
return expected.includes(model.flavour); | ||
} | ||
import { isPrimitive, SYS_KEYS } from '@blocksuite/global/utils'; | ||
export function assertValidChildren(yBlocks, props) { | ||
@@ -23,0 +6,0 @@ if (!Array.isArray(props.children)) |
// Test page entry located in playground/examples/workspace/index.html | ||
import { Workspace } from '../workspace.js'; | ||
import { testSerial, runOnce, nextFrame, } from '../../__tests__/test-utils-dom.js'; | ||
import { assertExists } from '../../utils/utils.js'; | ||
import { assertExists } from '@blocksuite/global/utils'; | ||
import './test-app'; | ||
@@ -6,0 +6,0 @@ let i = 0; |
@@ -7,4 +7,5 @@ import * as Y from 'yjs'; | ||
import { Signal } from '../utils/signal.js'; | ||
import { assertValidChildren, initSysProps, syncBlockProps, trySyncTextProp, toBlockProps, matchFlavours, } from '../utils/utils.js'; | ||
import { assertValidChildren, initSysProps, syncBlockProps, trySyncTextProp, toBlockProps, } from '../utils/utils.js'; | ||
import { tryMigrate } from './migrations.js'; | ||
import { matchFlavours } from '@blocksuite/global/utils'; | ||
const isWeb = typeof window !== 'undefined'; | ||
@@ -11,0 +12,0 @@ function createChildMap(yChildIds) { |
@@ -8,2 +8,3 @@ import * as Y from 'yjs'; | ||
import { getBlobStorage } from '../blob/index.js'; | ||
import { merge } from 'merge'; | ||
class WorkspaceMeta extends Space { | ||
@@ -159,2 +160,3 @@ constructor(id, doc, awareness, defaultFlags) { | ||
const flagsPreset = { | ||
enable_set_remote_flag: true, | ||
enable_drag_handle: true, | ||
@@ -176,6 +178,3 @@ readonly: {}, | ||
this.room = options.room; | ||
this.meta = new WorkspaceMeta('space:meta', this.doc, this._store.awareness, { | ||
...flagsPreset, | ||
...options.defaultFlags, | ||
}); | ||
this.meta = new WorkspaceMeta('space:meta', this.doc, this._store.awareness, merge(flagsPreset, options.defaultFlags)); | ||
this.signals = { | ||
@@ -182,0 +181,0 @@ pagesUpdated: this.meta.pagesUpdated, |
{ | ||
"name": "@blocksuite/store", | ||
"version": "0.4.0-20230113184550-93ac0f4", | ||
"version": "0.4.0-20230114095726-16babb2", | ||
"description": "BlockSuite data store built for general purpose state management.", | ||
@@ -11,5 +11,4 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"@blocksuite/global": "0.4.0-20230113184550-93ac0f4", | ||
"@blocksuite/global": "0.4.0-20230114095726-16babb2", | ||
"@types/flexsearch": "^0.7.3", | ||
"@types/quill": "^1.3.7", | ||
"buffer": "^6.0.3", | ||
@@ -16,0 +15,0 @@ "flexsearch": "0.7.21", |
@@ -71,7 +71,3 @@ import type { Workspace } from '../workspace/workspace.js'; | ||
export function assertExists<T>(val: T | null | undefined): asserts val is T { | ||
if (val === null || val === undefined) { | ||
throw new Error('val does not exist'); | ||
} | ||
} | ||
export { assertExists } from '@blocksuite/global/utils'; | ||
@@ -78,0 +74,0 @@ export async function nextFrame() { |
@@ -20,3 +20,3 @@ /* eslint-disable @typescript-eslint/no-restricted-imports */ | ||
import type { PageMeta } from '../workspace/index.js'; | ||
import { assertExists } from '../utils/utils.js'; | ||
import { assertExists } from './test-utils-dom.js'; | ||
@@ -23,0 +23,0 @@ function createTestOptions() { |
@@ -6,4 +6,4 @@ import * as Y from 'yjs'; | ||
import { Signal } from './utils/signal.js'; | ||
import { assertExists } from './utils/utils.js'; | ||
import { merge } from 'merge'; | ||
import { uuidv4 } from './utils/id-generator.js'; | ||
@@ -22,2 +22,16 @@ export interface SelectionRange { | ||
type Request< | ||
Flags extends Record<string, unknown> = BlockSuiteFlags, | ||
Key extends keyof Flags = keyof Flags | ||
> = { | ||
id: string; | ||
clientId: number; | ||
field: Key; | ||
value: Flags[Key]; | ||
}; | ||
type Response = { | ||
id: string; | ||
}; | ||
interface AwarenessState< | ||
@@ -29,2 +43,4 @@ Flags extends Record<string, unknown> = BlockSuiteFlags | ||
flags: Flags; | ||
request?: Request<Flags>[]; | ||
response?: Response[]; | ||
} | ||
@@ -89,5 +105,4 @@ | ||
public getFlag<Key extends keyof Flags>(field: Key) { | ||
const flags = this.awareness.getLocalState()?.flags; | ||
assertExists(flags); | ||
public getFlag<Key extends keyof Flags>(field: Key): Flags[Key] | undefined { | ||
const flags = this.awareness.getLocalState()?.flags ?? {}; | ||
return flags[field]; | ||
@@ -97,10 +112,39 @@ } | ||
public setReadonly(value: boolean): void { | ||
const flags = this.getFlag('readonly'); | ||
this.setFlag('readonly', { ...flags, [this.space.prefixedId]: value }); | ||
const flags = this.getFlag('readonly') ?? {}; | ||
this.setFlag('readonly', { | ||
...flags, | ||
[this.space.prefixedId]: value, | ||
} as Flags['readonly']); | ||
} | ||
public isReadonly(): boolean { | ||
return this.getFlag('readonly')[this.space.prefixedId] ?? false; | ||
const rd = this.getFlag('readonly'); | ||
if (rd && typeof rd === 'object') { | ||
return Boolean((rd as Record<string, boolean>)[this.space.prefixedId]); | ||
} else { | ||
return false; | ||
} | ||
} | ||
setRemoteFlag<Key extends keyof Flags>( | ||
clientId: number, | ||
field: Key, | ||
value: Flags[Key] | ||
) { | ||
if (!this.getFlag('enable_set_remote_flag')) { | ||
console.error('set remote flag feature disabled'); | ||
return; | ||
} | ||
const oldRequest = this.awareness.getLocalState()?.request ?? []; | ||
this.awareness.setLocalStateField('request', [ | ||
...oldRequest, | ||
{ | ||
id: uuidv4(), | ||
clientId, | ||
field, | ||
value, | ||
}, | ||
] satisfies Request<Flags>[]); | ||
} | ||
public getLocalCursor(): SelectionRange | undefined { | ||
@@ -112,4 +156,4 @@ const states = this.awareness.getStates(); | ||
public getStates(): Map<number, AwarenessState> { | ||
return this.awareness.getStates() as Map<number, AwarenessState>; | ||
public getStates(): Map<number, AwarenessState<Flags>> { | ||
return this.awareness.getStates() as Map<number, AwarenessState<Flags>>; | ||
} | ||
@@ -153,4 +197,72 @@ | ||
} | ||
if (this.getFlag('enable_set_remote_flag') === true) { | ||
this._handleRemoteFlags(); | ||
} | ||
}; | ||
private _handleRemoteFlags() { | ||
const nextTick: (() => void)[] = []; | ||
const localState = this.awareness.getLocalState() as AwarenessState<Flags>; | ||
const request = (localState?.request ?? []) as Request<Flags>[]; | ||
const selfResponse = [] as Response[]; | ||
const fakeDirtyResponse = [] as Response[]; | ||
if (localState && Array.isArray(localState.response)) { | ||
selfResponse.push(...localState.response); | ||
fakeDirtyResponse.push(...localState.response); | ||
} | ||
const response = [] as Response[]; | ||
for (const [clientId, state] of this.awareness.getStates()) { | ||
if (clientId === this.awareness.clientID) { | ||
continue; | ||
} | ||
if (Array.isArray(state.response)) { | ||
response.push(...state.response); | ||
} | ||
if (Array.isArray(state.request)) { | ||
const remoteRequest = state.request as Request<Flags>[]; | ||
selfResponse.forEach((response, idx) => { | ||
if (response === null) { | ||
return; | ||
} | ||
const index = remoteRequest.findIndex( | ||
request => request.id === response.id | ||
); | ||
if (index === -1) { | ||
fakeDirtyResponse[idx].id = 'remove'; | ||
} | ||
}); | ||
remoteRequest.forEach(request => { | ||
if (request.clientId === this.awareness.clientID) { | ||
// handle request | ||
nextTick.push(() => { | ||
this.setFlag(request.field, request.value); | ||
}); | ||
selfResponse.push({ | ||
id: request.id, | ||
}); | ||
} | ||
}); | ||
} | ||
} | ||
response.forEach(response => { | ||
const idx = request.findIndex(request => request.id === response.id); | ||
if (idx !== -1) { | ||
request.splice(idx, 1); | ||
} | ||
}); | ||
nextTick.push(() => { | ||
this.awareness.setLocalStateField('request', request); | ||
this.awareness.setLocalStateField( | ||
'response', | ||
selfResponse.filter((response, idx) => | ||
fakeDirtyResponse[idx] ? fakeDirtyResponse[idx].id !== 'remove' : true | ||
) | ||
); | ||
}); | ||
setTimeout(() => { | ||
nextTick.forEach(fn => fn()); | ||
}, 100); | ||
} | ||
private _resetRemoteCursor() { | ||
@@ -157,0 +269,0 @@ this.space.richTextAdapters.forEach(textAdapter => |
// Test page entry located in playground/examples/blob/index.html | ||
import { getBlobStorage } from '..'; | ||
import { getBlobStorage } from '../index.js'; | ||
import { | ||
@@ -11,3 +11,3 @@ testSerial, | ||
disableButtonsAfterClick, | ||
} from '../../__tests__/test-utils-dom'; | ||
} from '../../__tests__/test-utils-dom.js'; | ||
@@ -14,0 +14,0 @@ async function testBasic() { |
import * as Y from 'yjs'; | ||
import type { BaseBlockModel } from '../base.js'; | ||
import type { | ||
@@ -12,34 +11,4 @@ BlockProps, | ||
import { fromBase64, toBase64 } from 'lib0/buffer.js'; | ||
import { isPrimitive, SYS_KEYS } from '@blocksuite/global/utils'; | ||
const SYS_KEYS = new Set(['id', 'flavour', 'children']); | ||
// https://stackoverflow.com/questions/31538010/test-if-a-variable-is-a-primitive-rather-than-an-object | ||
function isPrimitive( | ||
a: unknown | ||
): a is null | undefined | boolean | number | string { | ||
return a !== Object(a); | ||
} | ||
export function assertExists<T>(val: T | null | undefined): asserts val is T { | ||
if (val === null || val === undefined) { | ||
throw new Error('val does not exist'); | ||
} | ||
} | ||
export function assertFlavours(model: BaseBlockModel, allowed: string[]) { | ||
if (!allowed.includes(model.flavour)) { | ||
throw new Error(`model flavour ${model.flavour} is not allowed`); | ||
} | ||
} | ||
export function matchFlavours< | ||
Key extends keyof BlockSuiteInternal.BlockModels & | ||
string = keyof BlockSuiteInternal.BlockModels & string | ||
>( | ||
model: BaseBlockModel, | ||
expected: Key[] | ||
): boolean /* model is BlockModels[Key] */ { | ||
return expected.includes(model.flavour as Key); | ||
} | ||
export function assertValidChildren( | ||
@@ -46,0 +15,0 @@ yBlocks: YBlocks, |
@@ -9,3 +9,3 @@ // Test page entry located in playground/examples/workspace/index.html | ||
} from '../../__tests__/test-utils-dom.js'; | ||
import { assertExists } from '../../utils/utils.js'; | ||
import { assertExists } from '@blocksuite/global/utils'; | ||
import './test-app'; | ||
@@ -12,0 +12,0 @@ |
@@ -21,3 +21,2 @@ import * as Y from 'yjs'; | ||
toBlockProps, | ||
matchFlavours, | ||
} from '../utils/utils.js'; | ||
@@ -27,3 +26,3 @@ import type { PageMeta, Workspace } from './workspace.js'; | ||
import { tryMigrate } from './migrations.js'; | ||
import { matchFlavours } from '@blocksuite/global/utils'; | ||
export type YBlock = Y.Map<unknown>; | ||
@@ -30,0 +29,0 @@ export type YBlocks = Y.Map<YBlock>; |
@@ -11,2 +11,3 @@ import * as Y from 'yjs'; | ||
import type { BlockSuiteDoc } from '../yjs/index.js'; | ||
import { merge } from 'merge'; | ||
@@ -220,2 +221,3 @@ export interface PageMeta { | ||
const flagsPreset = { | ||
enable_set_remote_flag: true, | ||
enable_drag_handle: true, | ||
@@ -258,6 +260,3 @@ readonly: {}, | ||
this._store.awareness, | ||
{ | ||
...flagsPreset, | ||
...options.defaultFlags, | ||
} | ||
merge(flagsPreset, options.defaultFlags) | ||
); | ||
@@ -264,0 +263,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
482231
11
7960
+ Added@blocksuite/global@0.4.0-20230114095726-16babb2(transitive)
- Removed@types/quill@^1.3.7
- Removed@blocksuite/global@0.4.0-20230113184550-93ac0f4(transitive)
- Removed@types/quill@1.3.10(transitive)
- Removedparchment@1.1.4(transitive)