style: enable rxjs/finnish (#6276)

chore(infra): use finnish notation for observables

do rename
This commit is contained in:
EYHN committed 2024-03-24 17:04:51 +00:00
1 parent c6676fd074
commit 2b42a75e5a
104 files changed
+795 -589

No files matched your search

+16
View File
@@ -98,6 +98,7 @@ const config = {
'i', 'i',
'unused-imports', 'unused-imports',
'unicorn', 'unicorn',
'rxjs',
], ],
rules: { rules: {
'array-callback-return': 'error', 'array-callback-return': 'error',
@@ -202,6 +203,21 @@ const config = {
'sonarjs/no-collection-size-mischeck': 'error', 'sonarjs/no-collection-size-mischeck': 'error',
'sonarjs/no-useless-catch': 'error', 'sonarjs/no-useless-catch': 'error',
'sonarjs/no-identical-functions': 'error', 'sonarjs/no-identical-functions': 'error',
'rxjs/finnish': [
'error',
{
functions: false,
methods: false,
strict: true,
types: {
'^LiveData$': true,
// some yjs classes are Observables, but they don't need to be in Finnish notation
'^Doc$': false, // yjs Doc
'^Awareness$': false, // yjs Awareness
'^UndoManager$': false, // yjs UndoManager
},
},
],
}, },
overrides: [ overrides: [
{ {
+1
View File
@@ -82,6 +82,7 @@
"eslint-plugin-i": "^2.29.1", "eslint-plugin-i": "^2.29.1",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-rxjs": "^5.0.3",
"eslint-plugin-simple-import-sort": "^12.0.0", "eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-sonarjs": "^0.24.0", "eslint-plugin-sonarjs": "^0.24.0",
"eslint-plugin-unicorn": "^51.0.1", "eslint-plugin-unicorn": "^51.0.1",
@@ -43,9 +43,9 @@ const metricCreators: MetricCreators = {
gauge(meter: Meter, name: string, opts?: MetricOptions) { gauge(meter: Meter, name: string, opts?: MetricOptions) {
let value: any; let value: any;
let attrs: Attributes | undefined; let attrs: Attributes | undefined;
const ob = meter.createObservableGauge(name, opts); const ob$ = meter.createObservableGauge(name, opts);
ob.addCallback(result => { ob$.addCallback(result => {
result.observe(value, attrs); result.observe(value, attrs);
}); });
@@ -113,8 +113,8 @@ export async function buildShowcaseWorkspace(
// perhaps put them into middleware? // perhaps put them into middleware?
{ {
// the "Write, Draw, Plan all at Once." page should be set to edgeless mode // the "Write, Draw, Plan all at Once." page should be set to edgeless mode
const edgelessPage1 = pageRecordList.records.value.find( const edgelessPage1 = pageRecordList.records$.value.find(
p => p.title.value === 'Write, Draw, Plan all at Once.' p => p.title$.value === 'Write, Draw, Plan all at Once.'
); );
if (edgelessPage1) { if (edgelessPage1) {
@@ -122,8 +122,8 @@ export async function buildShowcaseWorkspace(
} }
// should jump to "Write, Draw, Plan all at Once." by default // should jump to "Write, Draw, Plan all at Once." by default
const defaultPage = pageRecordList.records.value.find(p => const defaultPage = pageRecordList.records$.value.find(p =>
p.title.value.startsWith('Write, Draw, Plan all at Once.') p.title$.value.startsWith('Write, Draw, Plan all at Once.')
); );
if (defaultPage) { if (defaultPage) {
@@ -6,33 +6,33 @@ import { LiveData, PoisonedError } from '..';
describe('livedata', () => { describe('livedata', () => {
test('LiveData', async () => { test('LiveData', async () => {
const livedata = new LiveData(0); const livedata$ = new LiveData(0);
expect(livedata.value).toBe(0); expect(livedata$.value).toBe(0);
livedata.next(1); livedata$.next(1);
expect(livedata.value).toBe(1); expect(livedata$.value).toBe(1);
let subscribed = 0; let subscribed = 0;
livedata.subscribe(v => { livedata$.subscribe(v => {
subscribed = v; subscribed = v;
}); });
livedata.next(2); livedata$.next(2);
expect(livedata.value).toBe(2); expect(livedata$.value).toBe(2);
await vitest.waitFor(() => subscribed === 2); await vitest.waitFor(() => subscribed === 2);
}); });
test('from', async () => { test('from', async () => {
{ {
const livedata = LiveData.from(of(1, 2, 3, 4), 0); const livedata$ = LiveData.from(of(1, 2, 3, 4), 0);
expect(livedata.value).toBe(4); expect(livedata$.value).toBe(4);
} }
{ {
let subscriber: Subscriber<number> = null!; let subscriber: Subscriber<number> = null!;
const observable = new Observable<number>(s => { const observable$ = new Observable<number>(s => {
subscriber = s; subscriber = s;
}); });
const livedata = LiveData.from(observable, 0); const livedata$ = LiveData.from(observable$, 0);
let value = 0; let value = 0;
livedata.subscribe(v => { livedata$.subscribe(v => {
value = v; value = v;
}); });
@@ -46,16 +46,16 @@ describe('livedata', () => {
{ {
let observableSubscribed = false; let observableSubscribed = false;
let observableClosed = false; let observableClosed = false;
const observable = new Observable(subscriber => { const observable$ = new Observable(subscriber => {
observableSubscribed = true; observableSubscribed = true;
subscriber.next(1); subscriber.next(1);
return () => { return () => {
observableClosed = true; observableClosed = true;
}; };
}); });
const livedata = LiveData.from(observable, 0); const livedata$ = LiveData.from(observable$, 0);
expect(observableSubscribed).toBe(false); expect(observableSubscribed).toBe(false);
const subscription = livedata.subscribe(_ => {}); const subscription = livedata$.subscribe(_ => {});
expect(observableSubscribed).toBe(true); expect(observableSubscribed).toBe(true);
expect(observableClosed).toBe(false); expect(observableClosed).toBe(false);
subscription.unsubscribe(); subscription.unsubscribe();
@@ -64,17 +64,17 @@ describe('livedata', () => {
{ {
let subscriber: Subscriber<number> = null!; let subscriber: Subscriber<number> = null!;
const observable = new Observable<number>(s => { const observable$ = new Observable<number>(s => {
subscriber = s; subscriber = s;
}); });
const livedata = LiveData.from(observable, 0); const livedata$ = LiveData.from(observable$, 0);
let value1 = 0; let value1 = 0;
livedata.subscribe(v => { livedata$.subscribe(v => {
value1 = v; value1 = v;
}); });
let value2 = 0; let value2 = 0;
livedata.subscribe(v => { livedata$.subscribe(v => {
value2 = v; value2 = v;
}); });
@@ -91,17 +91,17 @@ describe('livedata', () => {
{ {
let observableSubscribed = false; let observableSubscribed = false;
let observableClosed = false; let observableClosed = false;
const observable = new Observable(subscriber => { const observable$ = new Observable(subscriber => {
observableSubscribed = true; observableSubscribed = true;
subscriber.next(1); subscriber.next(1);
return () => { return () => {
observableClosed = true; observableClosed = true;
}; };
}); });
const livedata = LiveData.from(observable, 0); const livedata$ = LiveData.from(observable$, 0);
expect(observableSubscribed).toBe(false); expect(observableSubscribed).toBe(false);
const subscription1 = livedata.subscribe(_ => {}); const subscription1 = livedata$.subscribe(_ => {});
const subscription2 = livedata.subscribe(_ => {}); const subscription2 = livedata$.subscribe(_ => {});
expect(observableSubscribed).toBe(true); expect(observableSubscribed).toBe(true);
expect(observableClosed).toBe(false); expect(observableClosed).toBe(false);
subscription1.unsubscribe(); subscription1.unsubscribe();
@@ -112,31 +112,31 @@ describe('livedata', () => {
{ {
let observerCount = 0; let observerCount = 0;
const observable = new Observable(_ => { const observable$ = new Observable(_ => {
observerCount++; observerCount++;
}); });
const livedata = LiveData.from(observable, 0); const livedata$ = LiveData.from(observable$, 0);
livedata.subscribe(_ => {}); livedata$.subscribe(_ => {});
livedata.subscribe(_ => {}); livedata$.subscribe(_ => {});
expect(observerCount).toBe(1); expect(observerCount).toBe(1);
} }
{ {
let value = 0; let value = 0;
const observable = new Observable<number>(subscriber => { const observable$ = new Observable<number>(subscriber => {
subscriber.next(value); subscriber.next(value);
}); });
const livedata = LiveData.from(observable, 0); const livedata$ = LiveData.from(observable$, 0);
expect(livedata.value).toBe(0); expect(livedata$.value).toBe(0);
value = 1; value = 1;
expect(livedata.value).toBe(1); expect(livedata$.value).toBe(1);
} }
}); });
test('poisoned', () => { test('poisoned', () => {
{ {
let subscriber: Subscriber<number> = null!; let subscriber: Subscriber<number> = null!;
const livedata = LiveData.from<number>( const livedata$ = LiveData.from<number>(
new Observable(sub => { new Observable(sub => {
subscriber = sub; subscriber = sub;
}), }),
@@ -145,7 +145,7 @@ describe('livedata', () => {
let value: number = 0; let value: number = 0;
let error: any = null; let error: any = null;
livedata.subscribe({ livedata$.subscribe({
next: v => { next: v => {
value = v; value = v;
}, },
@@ -161,11 +161,11 @@ describe('livedata', () => {
subscriber.error('error'); subscriber.error('error');
expect(error).toBeInstanceOf(PoisonedError); expect(error).toBeInstanceOf(PoisonedError);
expect(() => livedata.next(3)).toThrowError(PoisonedError); expect(() => livedata$.next(3)).toThrowError(PoisonedError);
expect(() => livedata.value).toThrowError(PoisonedError); expect(() => livedata$.value).toThrowError(PoisonedError);
let error2: any = null; let error2: any = null;
livedata.subscribe({ livedata$.subscribe({
error: e => { error: e => {
error2 = e; error2 = e;
}, },
@@ -176,29 +176,29 @@ describe('livedata', () => {
test('map', () => { test('map', () => {
{ {
const livedata = new LiveData(0); const livedata$ = new LiveData(0);
const mapped = livedata.map(v => v + 1); const mapped$ = livedata$.map(v => v + 1);
expect(mapped.value).toBe(1); expect(mapped$.value).toBe(1);
livedata.next(1); livedata$.next(1);
expect(mapped.value).toBe(2); expect(mapped$.value).toBe(2);
} }
{ {
const livedata = new LiveData(0); const livedata$ = new LiveData(0);
const mapped = livedata.map(v => v + 1); const mapped$ = livedata$.map(v => v + 1);
let value = 0; let value = 0;
mapped.subscribe(v => { mapped$.subscribe(v => {
value = v; value = v;
}); });
expect(value).toBe(1); expect(value).toBe(1);
livedata.next(1); livedata$.next(1);
expect(value).toBe(2); expect(value).toBe(2);
} }
{ {
let observableSubscribed = false; let observableSubscribed = false;
let observableClosed = false; let observableClosed = false;
const observable = new Observable<number>(subscriber => { const observable$ = new Observable<number>(subscriber => {
observableSubscribed = true; observableSubscribed = true;
subscriber.next(1); subscriber.next(1);
return () => { return () => {
@@ -206,11 +206,11 @@ describe('livedata', () => {
}; };
}); });
const livedata = LiveData.from(observable, 0); const livedata$ = LiveData.from(observable$, 0);
const mapped = livedata.map(v => v + 1); const mapped$ = livedata$.map(v => v + 1);
expect(observableSubscribed).toBe(false); expect(observableSubscribed).toBe(false);
const subscription = mapped.subscribe(_ => {}); const subscription = mapped$.subscribe(_ => {});
expect(observableSubscribed).toBe(true); expect(observableSubscribed).toBe(true);
expect(observableClosed).toBe(false); expect(observableClosed).toBe(false);
subscription.unsubscribe(); subscription.unsubscribe();
@@ -219,9 +219,9 @@ describe('livedata', () => {
}); });
test('interop with rxjs', () => { test('interop with rxjs', () => {
const ob = combineLatest([new LiveData(1)]); const ob$ = combineLatest([new LiveData(1)]);
let value = 0; let value = 0;
ob.subscribe(v => { ob$.subscribe(v => {
value = v[0]; value = v[0];
}); });
expect(value).toBe(1); expect(value).toBe(1);
@@ -229,69 +229,69 @@ describe('livedata', () => {
test('flat', () => { test('flat', () => {
{ {
const wrapped = new LiveData(new LiveData(0)); const wrapped$ = new LiveData(new LiveData(0));
const flatten = wrapped.flat(); const flatten$ = wrapped$.flat();
expect(flatten.value).toBe(0); expect(flatten$.value).toBe(0);
wrapped.next(new LiveData(1)); wrapped$.next(new LiveData(1));
expect(flatten.value).toBe(1); expect(flatten$.value).toBe(1);
wrapped.next(LiveData.from(of(2, 3), 0)); wrapped$.next(LiveData.from(of(2, 3), 0));
expect(flatten.value).toBe(3); expect(flatten$.value).toBe(3);
} }
{ {
const wrapped = new LiveData( const wrapped$ = new LiveData(
new LiveData([ new LiveData([
new LiveData(new LiveData(1)), new LiveData(new LiveData(1)),
new LiveData(new LiveData(2)), new LiveData(new LiveData(2)),
]) ])
); );
const flatten = wrapped.flat(); const flatten$ = wrapped$.flat();
expect(flatten.value).toStrictEqual([1, 2]); expect(flatten$.value).toStrictEqual([1, 2]);
} }
{ {
const wrapped = new LiveData([new LiveData(0), new LiveData(1)]); const wrapped$ = new LiveData([new LiveData(0), new LiveData(1)]);
const flatten = wrapped.flat(); const flatten$ = wrapped$.flat();
expect(flatten.value).toEqual([0, 1]); expect(flatten$.value).toEqual([0, 1]);
const inner = new LiveData(2); const inner$ = new LiveData(2);
wrapped.next([inner, new LiveData(3)]); wrapped$.next([inner$, new LiveData(3)]);
expect(flatten.value).toEqual([2, 3]); expect(flatten$.value).toEqual([2, 3]);
inner.next(4); inner$.next(4);
expect(flatten.value).toEqual([4, 3]); expect(flatten$.value).toEqual([4, 3]);
} }
}); });
test('computed', () => { test('computed', () => {
{ {
const a = new LiveData(1); const a$ = new LiveData(1);
const b = LiveData.computed(get => get(a) + 1); const b$ = LiveData.computed(get => get(a$) + 1);
expect(b.value).toBe(2); expect(b$.value).toBe(2);
} }
{ {
const a = new LiveData('v1'); const a$ = new LiveData('v1');
const v1 = new LiveData(100); const v1$ = new LiveData(100);
const v2 = new LiveData(200); const v2$ = new LiveData(200);
const v = LiveData.computed(get => { const v$ = LiveData.computed(get => {
return get(a) === 'v1' ? get(v1) : get(v2); return get(a$) === 'v1' ? get(v1$) : get(v2$);
}); });
expect(v.value).toBe(100); expect(v$.value).toBe(100);
a.next('v2'); a$.next('v2');
expect(v.value).toBe(200); expect(v$.value).toBe(200);
} }
{ {
let watched = false; let watched = false;
let count = 0; let count = 0;
let subscriber: Subscriber<number> = null!; let subscriber: Subscriber<number> = null!;
const a = LiveData.from<number>( const a$ = LiveData.from<number>(
new Observable(sub => { new Observable(sub => {
count++; count++;
watched = true; watched = true;
@@ -303,16 +303,16 @@ describe('livedata', () => {
}), }),
0 0
); );
const b = LiveData.computed(get => get(a) + 1); const b$ = LiveData.computed(get => get(a$) + 1);
expect(watched).toBe(false); expect(watched).toBe(false);
expect(count).toBe(0); expect(count).toBe(0);
const subscription = b.subscribe(_ => {}); const subscription = b$.subscribe(_ => {});
expect(watched).toBe(true); expect(watched).toBe(true);
expect(count).toBe(1); expect(count).toBe(1);
subscriber.next(2); subscriber.next(2);
expect(b.value).toBe(3); expect(b$.value).toBe(3);
subscription.unsubscribe(); subscription.unsubscribe();
expect(watched).toBe(false); expect(watched).toBe(false);
@@ -320,11 +320,11 @@ describe('livedata', () => {
} }
{ {
let c = null! as LiveData<number>; let c$ = null! as LiveData<number>;
const b = LiveData.computed(get => get(c) + 1); const b$ = LiveData.computed(get => get(c$) + 1);
c = LiveData.computed(get => get(b) + 1); c$ = LiveData.computed(get => get(b$) + 1);
expect(() => b.value).toThrowError(PoisonedError); expect(() => b$.value).toThrowError(PoisonedError);
} }
}); });
}); });
@@ -10,11 +10,11 @@ import { LiveData, useLiveData } from '..';
describe('livedata', () => { describe('livedata', () => {
test('react', () => { test('react', () => {
const livedata = new LiveData(0); const livedata$ = new LiveData(0);
const Component = () => { const Component = () => {
const renderCount = useRef(0); const renderCount = useRef(0);
renderCount.current++; renderCount.current++;
const value = useLiveData(livedata); const value = useLiveData(livedata$);
return ( return (
<main> <main>
{renderCount.current}:{value} {renderCount.current}:{value}
@@ -23,7 +23,7 @@ describe('livedata', () => {
}; };
const { rerender } = render(<Component />); const { rerender } = render(<Component />);
expect(screen.getByRole('main').innerText).toBe('1:0'); expect(screen.getByRole('main').innerText).toBe('1:0');
livedata.next(1); livedata$.next(1);
rerender(<Component />); rerender(<Component />);
expect(screen.getByRole('main').innerText).toBe('3:1'); expect(screen.getByRole('main').innerText).toBe('3:1');
}); });
@@ -31,7 +31,7 @@ describe('livedata', () => {
test('lifecycle', async () => { test('lifecycle', async () => {
let observableSubscribed = false; let observableSubscribed = false;
let observableClosed = false; let observableClosed = false;
const observable = new Observable<number>(subscriber => { const observable$ = new Observable<number>(subscriber => {
observableSubscribed = true; observableSubscribed = true;
subscriber.next(1); subscriber.next(1);
console.log(1); console.log(1);
@@ -40,9 +40,9 @@ describe('livedata', () => {
}; };
}); });
const livedata = LiveData.from(observable, 0); const livedata$ = LiveData.from(observable$, 0);
const Component1 = () => { const Component1 = () => {
const value = useLiveData(livedata); const value = useLiveData(livedata$);
return <main>{value}</main>; return <main>{value}</main>;
}; };
+75 -51
View File
@@ -14,6 +14,7 @@ import {
skip, skip,
type Subscription, type Subscription,
switchMap, switchMap,
type TeardownLogic,
} from 'rxjs'; } from 'rxjs';
import { BehaviorSubject, Subject } from 'rxjs'; import { BehaviorSubject, Subject } from 'rxjs';
@@ -78,20 +79,23 @@ const logger = new DebugLogger('livedata');
* @see {@link https://rxjs.dev/api/index/class/BehaviorSubject} * @see {@link https://rxjs.dev/api/index/class/BehaviorSubject}
* @see {@link https://developer.android.com/topic/libraries/architecture/livedata} * @see {@link https://developer.android.com/topic/libraries/architecture/livedata}
*/ */
export class LiveData<T = unknown> implements InteropObservable<T> { export class LiveData<T = unknown>
extends Observable<T>
implements InteropObservable<T>
{
static from<T>( static from<T>(
upstream: upstream$:
| Observable<T> | Observable<T>
| InteropObservable<T> | InteropObservable<T>
| ((stream: Observable<LiveDataOperation>) => Observable<T>), | ((stream: Observable<LiveDataOperation>) => Observable<T>),
initialValue: T initialValue: T
): LiveData<T> { ): LiveData<T> {
const data = new LiveData( const data$ = new LiveData(
initialValue, initialValue,
typeof upstream === 'function' typeof upstream$ === 'function'
? upstream ? upstream$
: stream => : stream$ =>
stream.pipe( stream$.pipe(
filter( filter(
(op): op is Exclude<LiveDataOperation, 'set'> => op !== 'set' (op): op is Exclude<LiveDataOperation, 'set'> => op !== 'set'
), ),
@@ -121,7 +125,7 @@ export class LiveData<T = unknown> implements InteropObservable<T> {
distinctUntilChanged(), distinctUntilChanged(),
switchMap(op => { switchMap(op => {
if (op === 'watch') { if (op === 'watch') {
return upstream; return upstream$;
} else { } else {
return EMPTY; return EMPTY;
} }
@@ -129,7 +133,7 @@ export class LiveData<T = unknown> implements InteropObservable<T> {
) )
); );
return data; return data$;
} }
private static GLOBAL_COMPUTED_RECURSIVE_COUNT = 0; private static GLOBAL_COMPUTED_RECURSIVE_COUNT = 0;
@@ -155,11 +159,11 @@ export class LiveData<T = unknown> implements InteropObservable<T> {
new Observable(subscribe => { new Observable(subscribe => {
const execute = (next: () => void) => { const execute = (next: () => void) => {
const subscriptions: Subscription[] = []; const subscriptions: Subscription[] = [];
const getfn = <L>(data: LiveData<L>) => { const getfn = <L>(data$: LiveData<L>) => {
let value = null as L; let value = null as L;
let first = true; let first = true;
subscriptions.push( subscriptions.push(
data.subscribe({ data$.subscribe({
error(err) { error(err) {
subscribe.error(err); subscribe.error(err);
}, },
@@ -212,8 +216,8 @@ export class LiveData<T = unknown> implements InteropObservable<T> {
); );
} }
private readonly raw: BehaviorSubject<T>; private readonly raw$: BehaviorSubject<T>;
private readonly ops = new Subject<LiveDataOperation>(); private readonly ops$ = new Subject<LiveDataOperation>();
private readonly upstreamSubscription: Subscription | undefined; private readonly upstreamSubscription: Subscription | undefined;
/** /**
@@ -232,14 +236,15 @@ export class LiveData<T = unknown> implements InteropObservable<T> {
| ((upstream: Observable<LiveDataOperation>) => Observable<T>) | ((upstream: Observable<LiveDataOperation>) => Observable<T>)
| undefined = undefined | undefined = undefined
) { ) {
this.raw = new BehaviorSubject(initialValue); super();
this.raw$ = new BehaviorSubject(initialValue);
if (upstream) { if (upstream) {
this.upstreamSubscription = upstream(this.ops).subscribe({ this.upstreamSubscription = upstream(this.ops$).subscribe({
next: v => { next: v => {
this.raw.next(v); this.raw$.next(v);
}, },
complete: () => { complete: () => {
if (!this.raw.closed) { if (!this.raw$.closed) {
logger.error('livedata upstream unexpected complete'); logger.error('livedata upstream unexpected complete');
} }
}, },
@@ -247,7 +252,7 @@ export class LiveData<T = unknown> implements InteropObservable<T> {
logger.error('uncatched error in livedata', err); logger.error('uncatched error in livedata', err);
this.isPoisoned = true; this.isPoisoned = true;
this.poisonedError = new PoisonedError(err); this.poisonedError = new PoisonedError(err);
this.raw.error(this.poisonedError); this.raw$.error(this.poisonedError);
}, },
}); });
} }
@@ -257,16 +262,16 @@ export class LiveData<T = unknown> implements InteropObservable<T> {
if (this.isPoisoned) { if (this.isPoisoned) {
throw this.poisonedError; throw this.poisonedError;
} }
this.ops.next('get'); this.ops$.next('get');
return this.raw.value; return this.raw$.value;
} }
setValue(v: T) { setValue(v: T) {
if (this.isPoisoned) { if (this.isPoisoned) {
throw this.poisonedError; throw this.poisonedError;
} }
this.raw.next(v); this.raw$.next(v);
this.ops.next('set'); this.ops$.next('set');
} }
get value(): T { get value(): T {
@@ -284,66 +289,81 @@ export class LiveData<T = unknown> implements InteropObservable<T> {
this.setValue(v); this.setValue(v);
} }
subscribe( override subscribe(
observer?: Partial<Observer<T>> | ((value: T) => void) | undefined observerOrNext?: Partial<Observer<T>> | ((value: T) => void)
): Subscription;
override subscribe(
next?: ((value: T) => void) | null,
error?: ((error: any) => void) | null,
complete?: (() => void) | null
): Subscription;
override subscribe(
observerOrNext?: Partial<Observer<T>> | ((value: T) => void) | null,
error?: ((error: any) => void) | null,
complete?: (() => void) | null
): Subscription { ): Subscription {
this.ops.next('watch'); this.ops$.next('watch');
const subscription = this.raw.subscribe(observer); const subscription = this.raw$.subscribe(
observerOrNext as any,
error,
complete
);
subscription.add(() => { subscription.add(() => {
this.ops.next('unwatch'); this.ops$.next('unwatch');
}); });
return subscription; return subscription;
} }
map<R>(mapper: (v: T) => R) { map<R>(mapper: (v: T) => R): LiveData<R> {
const sub = LiveData.from( const sub$ = LiveData.from(
new Observable<R>(subscriber => new Observable<R>(subscriber =>
this.subscribe({ this.subscribe({
next: v => { next: v => {
subscriber.next(mapper(v)); subscriber.next(mapper(v));
}, },
complete: () => { complete: () => {
sub.complete(); sub$.complete();
}, },
}) })
), ),
undefined as R // is safe undefined as R // is safe
); );
return sub; return sub$;
} }
// eslint-disable-next-line rxjs/finnish
asObservable(): Observable<T> { asObservable(): Observable<T> {
return new Observable<T>(subscriber => { return new Observable<T>(subscriber => {
return this.subscribe(subscriber); return this.subscribe(subscriber);
}); });
} }
pipe(): Observable<T>; override pipe(): Observable<T>;
pipe<A>(op1: OperatorFunction<T, A>): Observable<A>; override pipe<A>(op1: OperatorFunction<T, A>): Observable<A>;
pipe<A, B>( override pipe<A, B>(
op1: OperatorFunction<T, A>, op1: OperatorFunction<T, A>,
op2: OperatorFunction<A, B> op2: OperatorFunction<A, B>
): Observable<B>; ): Observable<B>;
pipe<A, B, C>( override pipe<A, B, C>(
op1: OperatorFunction<T, A>, op1: OperatorFunction<T, A>,
op2: OperatorFunction<A, B>, op2: OperatorFunction<A, B>,
op3: OperatorFunction<B, C> op3: OperatorFunction<B, C>
): Observable<C>; ): Observable<C>;
pipe<A, B, C, D>( override pipe<A, B, C, D>(
op1: OperatorFunction<T, A>, op1: OperatorFunction<T, A>,
op2: OperatorFunction<A, B>, op2: OperatorFunction<A, B>,
op3: OperatorFunction<B, C>, op3: OperatorFunction<B, C>,
op4: OperatorFunction<C, D> op4: OperatorFunction<C, D>
): Observable<D>; ): Observable<D>;
pipe<A, B, C, D, E>( override pipe<A, B, C, D, E>(
op1: OperatorFunction<T, A>, op1: OperatorFunction<T, A>,
op2: OperatorFunction<A, B>, op2: OperatorFunction<A, B>,
op3: OperatorFunction<B, C>, op3: OperatorFunction<B, C>,
op4: OperatorFunction<C, D>, op4: OperatorFunction<C, D>,
op5: OperatorFunction<D, E> op5: OperatorFunction<D, E>
): Observable<E>; ): Observable<E>;
pipe<A, B, C, D, E, F>( override pipe<A, B, C, D, E, F>(
op1: OperatorFunction<T, A>, op1: OperatorFunction<T, A>,
op2: OperatorFunction<A, B>, op2: OperatorFunction<A, B>,
op3: OperatorFunction<B, C>, op3: OperatorFunction<B, C>,
@@ -351,23 +371,23 @@ export class LiveData<T = unknown> implements InteropObservable<T> {
op5: OperatorFunction<D, E>, op5: OperatorFunction<D, E>,
op6: OperatorFunction<E, F> op6: OperatorFunction<E, F>
): Observable<F>; ): Observable<F>;
pipe(...args: any[]) { override pipe(...args: any[]) {
return new Observable(subscriber => { return new Observable(subscriber => {
this.ops.next('watch'); this.ops$.next('watch');
// eslint-disable-next-line prefer-spread // eslint-disable-next-line prefer-spread
const subscription = this.raw.pipe const subscription = this.raw$.pipe
.apply(this.raw, args as any) .apply(this.raw$, args as any)
.subscribe(subscriber); .subscribe(subscriber);
subscription.add(() => { subscription.add(() => {
this.ops.next('unwatch'); this.ops$.next('unwatch');
}); });
return subscription; return subscription;
}); });
} }
complete() { complete() {
this.ops.complete(); this.ops$.complete();
this.raw.complete(); this.raw$.complete();
this.upstreamSubscription?.unsubscribe(); this.upstreamSubscription?.unsubscribe();
} }
@@ -411,12 +431,12 @@ export class LiveData<T = unknown> implements InteropObservable<T> {
if (this.isPoisoned) { if (this.isPoisoned) {
throw this.poisonedError; throw this.poisonedError;
} }
this.ops.next('watch'); this.ops$.next('watch');
const subscription = this.raw const subscription = this.raw$
.pipe(distinctUntilChanged(), skip(1)) .pipe(distinctUntilChanged(), skip(1))
.subscribe(cb); .subscribe(cb);
subscription.add(() => { subscription.add(() => {
this.ops.next('unwatch'); this.ops$.next('unwatch');
}); });
return () => subscription.unsubscribe(); return () => subscription.unsubscribe();
}; };
@@ -425,13 +445,17 @@ export class LiveData<T = unknown> implements InteropObservable<T> {
if (this.isPoisoned) { if (this.isPoisoned) {
throw this.poisonedError; throw this.poisonedError;
} }
this.ops.next('watch'); this.ops$.next('watch');
setImmediate(() => { setImmediate(() => {
this.ops.next('unwatch'); this.ops$.next('unwatch');
}); });
return this.raw.value; return this.raw$.value;
}; };
protected _subscribe(): TeardownLogic {
throw new Error('Method not implemented.');
}
[Symbol.observable || '@@observable']() { [Symbol.observable || '@@observable']() {
return this; return this;
} }
+3 -3
View File
@@ -40,13 +40,13 @@ export function useLiveData<Input extends LiveData<any> | null | undefined>(
/** /**
* subscribe LiveData and return the value. If the value is nullish, will suspends until the value is not nullish. * subscribe LiveData and return the value. If the value is nullish, will suspends until the value is not nullish.
*/ */
export function useEnsureLiveData<T>(liveData: LiveData<T>): NonNullable<T> { export function useEnsureLiveData<T>(liveData$: LiveData<T>): NonNullable<T> {
const data = useLiveData(liveData); const data = useLiveData(liveData$);
if (data === null || data === undefined) { if (data === null || data === undefined) {
return use( return use(
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const subscription = liveData.subscribe({ const subscription = liveData$.subscribe({
next(value) { next(value) {
if (value === null || value === undefined) { if (value === null || value === undefined) {
resolve(value); resolve(value);
+1 -1
View File
@@ -16,7 +16,7 @@ export class PageManager {
) {} ) {}
open(pageId: string) { open(pageId: string) {
const pageRecord = this.pageRecordList.record(pageId).value; const pageRecord = this.pageRecordList.record$(pageId).value;
if (!pageRecord) { if (!pageRecord) {
throw new Error('Page record not found'); throw new Error('Page record not found');
} }
+3 -3
View File
@@ -14,9 +14,9 @@ export class Doc {
return this.record.id; return this.record.id;
} }
readonly mete = this.record.meta; readonly mete$ = this.record.meta$;
readonly mode = this.record.mode; readonly mode$ = this.record.mode$;
readonly title = this.record.title; readonly title$ = this.record.title$;
setMode(mode: PageMode) { setMode(mode: PageMode) {
this.record.setMode(mode); this.record.setMode(mode);
@@ -11,7 +11,7 @@ export class PageRecordList {
private readonly localState: WorkspaceLocalState private readonly localState: WorkspaceLocalState
) {} ) {}
public readonly records = LiveData.from<PageRecord[]>( public readonly records$ = LiveData.from<PageRecord[]>(
new Observable<string[]>(subscriber => { new Observable<string[]>(subscriber => {
const emit = () => { const emit = () => {
subscriber.next( subscriber.next(
@@ -35,11 +35,11 @@ export class PageRecordList {
[] []
); );
public readonly isReady = this.workspace.engine.rootDocState.map( public readonly isReady$ = this.workspace.engine.rootDocState$.map(
state => !state.syncing state => !state.syncing
); );
public record(id: string) { public record$(id: string) {
return this.records.map(record => record.find(record => record.id === id)); return this.records$.map(record => record.find(record => record.id === id));
} }
} }
+5 -5
View File
@@ -13,7 +13,7 @@ export class PageRecord {
private readonly localState: WorkspaceLocalState private readonly localState: WorkspaceLocalState
) {} ) {}
meta = LiveData.from<DocMeta>( meta$ = LiveData.from<DocMeta>(
new Observable(subscriber => { new Observable(subscriber => {
const emit = () => { const emit = () => {
const meta = this.workspace.docCollection.meta.docMetas.find( const meta = this.workspace.docCollection.meta.docMetas.find(
@@ -45,7 +45,7 @@ export class PageRecord {
this.workspace.docCollection.setDocMeta(this.id, meta); this.workspace.docCollection.setDocMeta(this.id, meta);
} }
mode: LiveData<PageMode> = LiveData.from( mode$: LiveData<PageMode> = LiveData.from(
this.localState.watch<PageMode>(`page:${this.id}:mode`), this.localState.watch<PageMode>(`page:${this.id}:mode`),
'page' 'page'
).map(mode => (mode === 'edgeless' ? 'edgeless' : 'page')); ).map(mode => (mode === 'edgeless' ? 'edgeless' : 'page'));
@@ -55,9 +55,9 @@ export class PageRecord {
} }
toggleMode() { toggleMode() {
this.setMode(this.mode.value === 'edgeless' ? 'page' : 'edgeless'); this.setMode(this.mode$.value === 'edgeless' ? 'page' : 'edgeless');
return this.mode.value; return this.mode$.value;
} }
title = this.meta.map(meta => meta.title); title$ = this.meta$.map(meta => meta.title);
} }
+5 -5
View File
@@ -40,12 +40,12 @@ export class MemoryMemento implements Memento {
private readonly data = new Map<string, LiveData<any>>(); private readonly data = new Map<string, LiveData<any>>();
private getLiveData(key: string): LiveData<any> { private getLiveData(key: string): LiveData<any> {
let data = this.data.get(key); let data$ = this.data.get(key);
if (!data) { if (!data$) {
data = new LiveData<any>(null); data$ = new LiveData<any>(null);
this.data.set(key, data); this.data.set(key, data$);
} }
return data; return data$;
} }
get<T>(key: string): T | null { get<T>(key: string): T | null {
@@ -14,13 +14,13 @@ describe('Workspace System', () => {
const provider = services.provider(); const provider = services.provider();
const workspaceManager = provider.get(WorkspaceManager); const workspaceManager = provider.get(WorkspaceManager);
const workspaceListService = provider.get(WorkspaceListService); const workspaceListService = provider.get(WorkspaceListService);
expect(workspaceListService.workspaceList.value.length).toBe(0); expect(workspaceListService.workspaceList$.value.length).toBe(0);
const { workspace } = workspaceManager.open( const { workspace } = workspaceManager.open(
await workspaceManager.createWorkspace(WorkspaceFlavour.LOCAL) await workspaceManager.createWorkspace(WorkspaceFlavour.LOCAL)
); );
expect(workspaceListService.workspaceList.value.length).toBe(1); expect(workspaceListService.workspaceList$.value.length).toBe(1);
const page = workspace.docCollection.createDoc({ const page = workspace.docCollection.createDoc({
id: 'page0', id: 'page0',
@@ -32,10 +32,10 @@ export class DocEngine {
storage: DocStorageInner; storage: DocStorageInner;
engineState = LiveData.computed(get => { engineState$ = LiveData.computed(get => {
const localState = get(this.localPart.engineState); const localState = get(this.localPart.engineState$);
if (this.remotePart) { if (this.remotePart) {
const remoteState = get(this.remotePart?.engineState); const remoteState = get(this.remotePart?.engineState$);
return { return {
total: remoteState.total, total: remoteState.total,
syncing: remoteState.syncing, syncing: remoteState.syncing,
@@ -53,12 +53,12 @@ export class DocEngine {
}; };
}); });
docState(docId: string) { docState$(docId: string) {
const localState = this.localPart.docState(docId); const localState$ = this.localPart.docState$(docId);
const remoteState = this.remotePart?.docState(docId); const remoteState$ = this.remotePart?.docState$(docId);
return LiveData.computed(get => { return LiveData.computed(get => {
const local = get(localState); const local = get(localState$);
const remote = remoteState ? get(remoteState) : null; const remote = remoteState$ ? get(remoteState$) : null;
return { return {
ready: local.ready, ready: local.ready,
saving: local.syncing, saving: local.syncing,
@@ -134,7 +134,7 @@ export class DocEngine {
*/ */
waitForSaved() { waitForSaved() {
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
this.engineState this.engineState$
.pipe(map(state => state.saving === 0)) .pipe(map(state => state.saving === 0))
.subscribe(saved => { .subscribe(saved => {
if (saved) { if (saved) {
@@ -150,7 +150,7 @@ export class DocEngine {
*/ */
waitForSynced() { waitForSynced() {
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
this.engineState this.engineState$
.pipe(map(state => state.syncing === 0 && state.saving === 0)) .pipe(map(state => state.syncing === 0 && state.saving === 0))
.subscribe(synced => { .subscribe(synced => {
if (synced) { if (synced) {
@@ -175,7 +175,7 @@ export class DocEngine {
*/ */
waitForReady(docId: string) { waitForReady(docId: string) {
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
this.docState(docId) this.docState$(docId)
.pipe(map(state => state.ready)) .pipe(map(state => state.ready))
.subscribe(ready => { .subscribe(ready => {
if (ready) { if (ready) {
@@ -48,7 +48,7 @@ export interface LocalDocState {
*/ */
export class DocEngineLocalPart { export class DocEngineLocalPart {
private readonly prioritySettings = new Map<string, number>(); private readonly prioritySettings = new Map<string, number>();
private readonly statusUpdatedSubject = new Subject<string>(); private readonly statusUpdatedSubject$ = new Subject<string>();
private readonly status = { private readonly status = {
docs: new Map<string, YDoc>(), docs: new Map<string, YDoc>(),
@@ -59,7 +59,7 @@ export class DocEngineLocalPart {
currentJob: null as { docId: string; jobs: Job[] } | null, currentJob: null as { docId: string; jobs: Job[] } | null,
}; };
engineState = LiveData.from<LocalEngineState>( engineState$ = LiveData.from<LocalEngineState>(
new Observable(subscribe => { new Observable(subscribe => {
const next = () => { const next = () => {
subscribe.next({ subscribe.next({
@@ -68,14 +68,14 @@ export class DocEngineLocalPart {
}); });
}; };
next(); next();
return this.statusUpdatedSubject.subscribe(() => { return this.statusUpdatedSubject$.subscribe(() => {
next(); next();
}); });
}), }),
{ syncing: 0, total: 0 } { syncing: 0, total: 0 }
); );
docState(docId: string) { docState$(docId: string) {
return LiveData.from<LocalDocState>( return LiveData.from<LocalDocState>(
new Observable(subscribe => { new Observable(subscribe => {
const next = () => { const next = () => {
@@ -87,7 +87,7 @@ export class DocEngineLocalPart {
}); });
}; };
next(); next();
return this.statusUpdatedSubject.subscribe(updatedId => { return this.statusUpdatedSubject$.subscribe(updatedId => {
if (updatedId === docId) next(); if (updatedId === docId) next();
}); });
}), }),
@@ -120,7 +120,7 @@ export class DocEngineLocalPart {
} }
this.status.currentJob = { docId, jobs }; this.status.currentJob = { docId, jobs };
this.statusUpdatedSubject.next(docId); this.statusUpdatedSubject$.next(docId);
const { apply, load, save } = groupBy(jobs, job => job.type) as { const { apply, load, save } = groupBy(jobs, job => job.type) as {
[key in Job['type']]?: Job[]; [key in Job['type']]?: Job[];
@@ -139,7 +139,7 @@ export class DocEngineLocalPart {
} }
this.status.currentJob = null; this.status.currentJob = null;
this.statusUpdatedSubject.next(docId); this.statusUpdatedSubject$.next(docId);
} }
} finally { } finally {
dispose(); dispose();
@@ -161,7 +161,7 @@ export class DocEngineLocalPart {
}); });
this.status.docs.set(doc.guid, doc); this.status.docs.set(doc.guid, doc);
this.statusUpdatedSubject.next(doc.guid); this.statusUpdatedSubject$.next(doc.guid);
}, },
}; };
@@ -186,7 +186,7 @@ export class DocEngineLocalPart {
doc.on('update', this.handleDocUpdate); doc.on('update', this.handleDocUpdate);
this.status.connectedDocs.add(job.docId); this.status.connectedDocs.add(job.docId);
this.statusUpdatedSubject.next(job.docId); this.statusUpdatedSubject$.next(job.docId);
const docData = await this.storage.loadDocFromLocal(job.docId, signal); const docData = await this.storage.loadDocFromLocal(job.docId, signal);
@@ -196,7 +196,7 @@ export class DocEngineLocalPart {
this.applyUpdate(job.docId, docData); this.applyUpdate(job.docId, docData);
this.status.readyDocs.add(job.docId); this.status.readyDocs.add(job.docId);
this.statusUpdatedSubject.next(job.docId); this.statusUpdatedSubject$.next(job.docId);
}, },
save: async ( save: async (
docId: string, docId: string,
@@ -292,7 +292,7 @@ export class DocEngineLocalPart {
const existingJobs = this.status.jobMap.get(job.docId) ?? []; const existingJobs = this.status.jobMap.get(job.docId) ?? [];
existingJobs.push(job); existingJobs.push(job);
this.status.jobMap.set(job.docId, existingJobs); this.status.jobMap.set(job.docId, existingJobs);
this.statusUpdatedSubject.next(job.docId); this.statusUpdatedSubject$.next(job.docId);
} }
setPriority(docId: string, priority: number) { setPriority(docId: string, priority: number) {
@@ -81,9 +81,9 @@ export class DocEngineRemotePart {
retrying: false, retrying: false,
errorMessage: null, errorMessage: null,
}; };
private readonly statusUpdatedSubject = new Subject<string | true>(); private readonly statusUpdatedSubject$ = new Subject<string | true>();
engineState = LiveData.from<RemoteEngineState>( engineState$ = LiveData.from<RemoteEngineState>(
new Observable(subscribe => { new Observable(subscribe => {
const next = () => { const next = () => {
if (!this.status.syncing) { if (!this.status.syncing) {
@@ -103,7 +103,7 @@ export class DocEngineRemotePart {
}); });
}; };
next(); next();
return this.statusUpdatedSubject.subscribe(() => { return this.statusUpdatedSubject$.subscribe(() => {
next(); next();
}); });
}), }),
@@ -115,7 +115,7 @@ export class DocEngineRemotePart {
} }
); );
docState(docId: string) { docState$(docId: string) {
return LiveData.from<RemoteDocState>( return LiveData.from<RemoteDocState>(
new Observable(subscribe => { new Observable(subscribe => {
const next = () => { const next = () => {
@@ -126,7 +126,7 @@ export class DocEngineRemotePart {
}); });
}; };
next(); next();
return this.statusUpdatedSubject.subscribe(updatedId => { return this.statusUpdatedSubject$.subscribe(updatedId => {
if (updatedId === true || updatedId === docId) next(); if (updatedId === true || updatedId === docId) next();
}); });
}), }),
@@ -152,7 +152,7 @@ export class DocEngineRemotePart {
} }
this.status.connectedDocs.add(docId); this.status.connectedDocs.add(docId);
this.statusUpdatedSubject.next(docId); this.statusUpdatedSubject$.next(docId);
}, },
push: async ( push: async (
docId: string, docId: string,
@@ -321,7 +321,7 @@ export class DocEngineRemotePart {
addDoc: (docId: string) => { addDoc: (docId: string) => {
if (!this.status.docs.has(docId)) { if (!this.status.docs.has(docId)) {
this.status.docs.add(docId); this.status.docs.add(docId);
this.statusUpdatedSubject.next(docId); this.statusUpdatedSubject$.next(docId);
this.schedule({ this.schedule({
type: 'connect', type: 'connect',
docId, docId,
@@ -359,7 +359,7 @@ export class DocEngineRemotePart {
logger.error('Remote sync error, retry in 5s', err); logger.error('Remote sync error, retry in 5s', err);
this.status.errorMessage = this.status.errorMessage =
err instanceof Error ? err.message : `${err}`; err instanceof Error ? err.message : `${err}`;
this.statusUpdatedSubject.next(true); this.statusUpdatedSubject$.next(true);
} finally { } finally {
this.status = { this.status = {
docs: this.status.docs, docs: this.status.docs,
@@ -371,7 +371,7 @@ export class DocEngineRemotePart {
retrying: true, retrying: true,
errorMessage: this.status.errorMessage, errorMessage: this.status.errorMessage,
}; };
this.statusUpdatedSubject.next(true); this.statusUpdatedSubject$.next(true);
} }
await Promise.race([ await Promise.race([
new Promise<void>(resolve => { new Promise<void>(resolve => {
@@ -420,7 +420,7 @@ export class DocEngineRemotePart {
logger.info('Remote sync started'); logger.info('Remote sync started');
this.status.syncing = true; this.status.syncing = true;
this.statusUpdatedSubject.next(true); this.statusUpdatedSubject$.next(true);
this.server.onInterrupted(reason => { this.server.onInterrupted(reason => {
abort.abort(reason); abort.abort(reason);
@@ -471,7 +471,7 @@ export class DocEngineRemotePart {
const jobs = this.status.jobMap.get(docId); const jobs = this.status.jobMap.get(docId);
if (!jobs || jobs.length === 0) { if (!jobs || jobs.length === 0) {
this.status.jobMap.delete(docId); this.status.jobMap.delete(docId);
this.statusUpdatedSubject.next(docId); this.statusUpdatedSubject$.next(docId);
break; break;
} }
@@ -535,7 +535,7 @@ export class DocEngineRemotePart {
const existingJobs = this.status.jobMap.get(job.docId) ?? []; const existingJobs = this.status.jobMap.get(job.docId) ?? [];
existingJobs.push(job); existingJobs.push(job);
this.status.jobMap.set(job.docId, existingJobs); this.status.jobMap.set(job.docId, existingJobs);
this.statusUpdatedSubject.next(job.docId); this.statusUpdatedSubject$.next(job.docId);
} }
setPriority(docId: string, priority: number) { setPriority(docId: string, priority: number) {
@@ -52,7 +52,7 @@ export class WorkspaceEngine {
} }
canGracefulStop() { canGracefulStop() {
return this.doc.engineState.value.saving === 0; return this.doc.engineState$.value.saving === 0;
} }
async waitForGracefulStop(abort?: AbortSignal) { async waitForGracefulStop(abort?: AbortSignal) {
@@ -67,9 +67,9 @@ export class WorkspaceEngine {
this.blob.stop(); this.blob.stop();
} }
docEngineState = this.doc.engineState; docEngineState$ = this.doc.engineState$;
rootDocState = this.doc.docState(this.yDoc.guid); rootDocState$ = this.doc.docState$(this.yDoc.guid);
waitForSynced() { waitForSynced() {
return this.doc.waitForSynced(); return this.doc.waitForSynced();
@@ -86,18 +86,18 @@ export class WorkspaceListService {
WorkspaceInformation WorkspaceInformation
>(); >();
status = new LiveData<WorkspaceListStatus>({ status$ = new LiveData<WorkspaceListStatus>({
loading: true, loading: true,
workspaceList: [], workspaceList: [],
}); });
setStatus(status: WorkspaceListStatus) { setStatus(status: WorkspaceListStatus) {
this.status.next(status); this.status$.next(status);
// update cache // update cache
writeWorkspaceListCache(this.cache, status.workspaceList); writeWorkspaceListCache(this.cache, status.workspaceList);
} }
workspaceList = this.status.map(x => x.workspaceList); workspaceList$ = this.status$.map(x => x.workspaceList);
constructor( constructor(
private readonly providers: WorkspaceListProvider[], private readonly providers: WorkspaceListProvider[],
@@ -106,8 +106,8 @@ export class WorkspaceListService {
// initialize workspace list from cache // initialize workspace list from cache
const cached = readWorkspaceListCache(cache); const cached = readWorkspaceListCache(cache);
const workspaceList = cached; const workspaceList = cached;
this.status.next({ this.status$.next({
...this.status.value, ...this.status$.value,
workspaceList, workspaceList,
}); });
@@ -134,7 +134,7 @@ export class WorkspaceListService {
} }
const metadata = await provider.create(initial); const metadata = await provider.create(initial);
// update workspace list // update workspace list
this.setStatus(this.addWorkspace(this.status.value, metadata)); this.setStatus(this.addWorkspace(this.status$.value, metadata));
return metadata; return metadata;
} }
@@ -157,7 +157,7 @@ export class WorkspaceListService {
await provider.delete(workspaceMetadata.id); await provider.delete(workspaceMetadata.id);
// delete workspace from list // delete workspace from list
this.setStatus(this.deleteWorkspace(this.status.value, workspaceMetadata)); this.setStatus(this.deleteWorkspace(this.status$.value, workspaceMetadata));
} }
/** /**
@@ -201,7 +201,7 @@ export class WorkspaceListService {
added?: WorkspaceMetadata[]; added?: WorkspaceMetadata[];
deleted?: WorkspaceMetadata[]; deleted?: WorkspaceMetadata[];
}) { }) {
let status = this.status.value; let status = this.status$.value;
for (const added of changed.added ?? []) { for (const added of changed.added ?? []) {
status = this.addWorkspace(status, added); status = this.addWorkspace(status, added);
@@ -239,7 +239,7 @@ export class WorkspaceListService {
}) })
.finally(() => { .finally(() => {
this.setStatus({ this.setStatus({
...this.status.value, ...this.status$.value,
loading: false, loading: false,
}); });
}); });
@@ -250,7 +250,7 @@ export class WorkspaceListService {
this.providers.map(async provider => { this.providers.map(async provider => {
try { try {
const list = await provider.getList(); const list = await provider.getList();
const oldList = this.workspaceList.value.filter( const oldList = this.workspaceList$.value.filter(
w => w.flavour === provider.name w => w.flavour === provider.name
); );
this.handleWorkspaceChange({ this.handleWorkspaceChange({
@@ -14,7 +14,7 @@ export const DumpInfo = (_props: DumpInfoProps) => {
const location = useLocation(); const location = useLocation();
const workspaceList = useService(WorkspaceListService); const workspaceList = useService(WorkspaceListService);
const currentWorkspace = useLiveData( const currentWorkspace = useLiveData(
useService(CurrentWorkspaceService).currentWorkspace useService(CurrentWorkspaceService).currentWorkspace$
); );
const path = location.pathname; const path = location.pathname;
const query = useParams(); const query = useParams();
@@ -9,7 +9,7 @@ import { CurrentWorkspaceService } from '../../../modules/workspace/current-work
const SyncAwarenessInnerLoggedIn = () => { const SyncAwarenessInnerLoggedIn = () => {
const { user } = useSession(); const { user } = useSession();
const currentWorkspace = useLiveData( const currentWorkspace = useLiveData(
useService(CurrentWorkspaceService).currentWorkspace useService(CurrentWorkspaceService).currentWorkspace$
); );
useEffect(() => { useEffect(() => {
@@ -479,7 +479,7 @@ const PageHistoryManager = ({
); );
const page = useService(Doc); const page = useService(Doc);
const [mode, setMode] = useState<PageMode>(page.mode.value); const [mode, setMode] = useState<PageMode>(page.mode$.value);
const title = useDocCollectionPageTitle(docCollection, pageId); const title = useDocCollectionPageTitle(docCollection, pageId);
@@ -42,8 +42,8 @@ const InlineTagsList = ({
children, children,
}: PropsWithChildren<InlineTagsListProps>) => { }: PropsWithChildren<InlineTagsListProps>) => {
const tagService = useService(TagService); const tagService = useService(TagService);
const tags = useLiveData(tagService.tags); const tags = useLiveData(tagService.tags$);
const tagIds = useLiveData(tagService.tagIdsByPageId(pageId)); const tagIds = useLiveData(tagService.tagIdsByPageId$(pageId));
return ( return (
<div className={styles.inlineTagsContainer} data-testid="inline-tags-list"> <div className={styles.inlineTagsContainer} data-testid="inline-tags-list">
@@ -83,9 +83,9 @@ export const EditTagMenu = ({
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const legacyProperties = useService(WorkspaceLegacyProperties); const legacyProperties = useService(WorkspaceLegacyProperties);
const tagService = useService(TagService); const tagService = useService(TagService);
const tag = useLiveData(tagService.tagByTagId(tagId)); const tag = useLiveData(tagService.tagByTagId$(tagId));
const tagColor = useLiveData(tag?.color); const tagColor = useLiveData(tag?.color$);
const tagValue = useLiveData(tag?.value); const tagValue = useLiveData(tag?.value$);
const navigate = useNavigateHelper(); const navigate = useNavigateHelper();
const menuProps = useMemo(() => { const menuProps = useMemo(() => {
@@ -135,11 +135,11 @@ export const EditTagMenu = ({
options.push('-'); options.push('-');
options.push( options.push(
tagColors.map(([name, color]) => { tagColors.map(([name, color], i) => {
return { return {
text: name, text: name,
icon: ( icon: (
<div className={styles.tagColorIconWrapper}> <div key={i} className={styles.tagColorIconWrapper}>
<div <div
className={styles.tagColorIcon} className={styles.tagColorIcon}
style={{ style={{
@@ -181,8 +181,8 @@ export const EditTagMenu = ({
export const TagsEditor = ({ pageId, readonly }: TagsEditorProps) => { export const TagsEditor = ({ pageId, readonly }: TagsEditorProps) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const tagService = useService(TagService); const tagService = useService(TagService);
const tags = useLiveData(tagService.tags); const tags = useLiveData(tagService.tags$);
const tagIds = useLiveData(tagService.tagIdsByPageId(pageId)); const tagIds = useLiveData(tagService.tagIdsByPageId$(pageId));
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [selectedTagIds, setSelectedTagIds] = useState<string[]>([]); const [selectedTagIds, setSelectedTagIds] = useState<string[]>([]);
@@ -203,15 +203,11 @@ export const TagsEditor = ({ pageId, readonly }: TagsEditorProps) => {
[setOpen, setSelectedTagIds] [setOpen, setSelectedTagIds]
); );
const exactMatch = useLiveData(tagService.tagByTagValue(inputValue)); const exactMatch = useLiveData(tagService.tagByTagValue$(inputValue));
const filteredLiveData = useMemo(() => { const filteredTags = useLiveData(
if (inputValue) { inputValue ? tagService.filterTagsByName$(inputValue) : tagService.tags$
return tagService.filterTagsByName(inputValue); );
}
return tagService.tags;
}, [inputValue, tagService]);
const filteredTags = useLiveData(filteredLiveData);
const onInputChange = useCallback( const onInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -295,7 +291,7 @@ export const TagsEditor = ({ pageId, readonly }: TagsEditorProps) => {
className={styles.tagSelectorItem} className={styles.tagSelectorItem}
data-testid="tag-selector-item" data-testid="tag-selector-item"
data-tag-id={tag.id} data-tag-id={tag.id}
data-tag-value={tag.value} data-tag-value={tag.value$}
onClick={() => { onClick={() => {
onAddTag(tag.id); onAddTag(tag.id);
}} }}
@@ -351,7 +347,7 @@ export const TagsInlineEditor = ({
className, className,
}: TagsInlineEditorProps) => { }: TagsInlineEditorProps) => {
const tagService = useService(TagService); const tagService = useService(TagService);
const tagIds = useLiveData(tagService.tagIdsByPageId(pageId)); const tagIds = useLiveData(tagService.tagIdsByPageId$(pageId));
const empty = !tagIds || tagIds.length === 0; const empty = !tagIds || tagIds.length === 0;
return ( return (
<Menu <Menu
@@ -195,7 +195,7 @@ export const WorkspaceList = ({
activeSubTab: WorkspaceSubTab; activeSubTab: WorkspaceSubTab;
}) => { }) => {
const workspaces = useLiveData( const workspaces = useLiveData(
useService(WorkspaceManager).list.workspaceList useService(WorkspaceManager).list.workspaceList$
); );
return ( return (
<> <>
@@ -34,7 +34,7 @@ export const DeleteLeaveWorkspace = ({
const setSettingModal = useSetAtom(openSettingModalAtom); const setSettingModal = useSetAtom(openSettingModalAtom);
const workspaceManager = useService(WorkspaceManager); const workspaceManager = useService(WorkspaceManager);
const workspaceList = useLiveData(workspaceManager.list.workspaceList); const workspaceList = useLiveData(workspaceManager.list.workspaceList$);
const currentWorkspace = useService(Workspace); const currentWorkspace = useService(Workspace);
const pushNotification = useSetAtom(pushNotificationAtom); const pushNotification = useSetAtom(pushNotificationAtom);
@@ -31,7 +31,7 @@ export const ProfilePanel = ({ isOwner, workspace }: ProfilePanelProps) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const pushNotification = useSetAtom(pushNotificationAtom); const pushNotification = useSetAtom(pushNotificationAtom);
const workspaceIsReady = useLiveData(workspace?.engine.rootDocState)?.ready; const workspaceIsReady = useLiveData(workspace?.engine.rootDocState$)?.ready;
const [avatarBlob, setAvatarBlob] = useState<string | null>(null); const [avatarBlob, setAvatarBlob] = useState<string | null>(null);
const [name, setName] = useState(''); const [name, setName] = useState('');
@@ -26,7 +26,7 @@ export const ShareExport = ({
urlType: 'workspace', urlType: 'workspace',
}); });
const exportHandler = useExportPage(currentPage); const exportHandler = useExportPage(currentPage);
const currentMode = useLiveData(page.mode); const currentMode = useLiveData(page.mode$);
return ( return (
<> <>
@@ -66,7 +66,7 @@ export const AffineSharePage = (props: ShareMenuProps) => {
disableShare, disableShare,
} = useIsSharedPage(workspaceId, currentPage.id); } = useIsSharedPage(workspaceId, currentPage.id);
const currentPageMode = useLiveData(page.mode); const currentPageMode = useLiveData(page.mode$);
const defaultMode = useMemo(() => { const defaultMode = useMemo(() => {
if (isSharedPage) { if (isSharedPage) {
@@ -55,7 +55,7 @@ export const PageHeaderMenuButton = ({
meta => meta.id === pageId meta => meta.id === pageId
); );
const page = useService(Doc); const page = useService(Doc);
const currentMode = useLiveData(page.mode); const currentMode = useLiveData(page.mode$);
const { favorite, toggleFavorite } = useFavorite(pageId); const { favorite, toggleFavorite } = useFavorite(pageId);
@@ -48,7 +48,7 @@ export const EditorModeSwitch = ({
const trash = pageMeta?.trash ?? false; const trash = pageMeta?.trash ?? false;
const page = useService(Doc); const page = useService(Doc);
const currentMode = useLiveData(page.mode); const currentMode = useLiveData(page.mode$);
useEffect(() => { useEffect(() => {
if (trash || isPublic) { if (trash || isPublic) {
@@ -19,7 +19,7 @@ export const usePageHelper = (docCollection: DocCollection) => {
const isPreferredEdgeless = useCallback( const isPreferredEdgeless = useCallback(
(pageId: string) => (pageId: string) =>
pageRecordList.record(pageId).value?.mode.value === 'edgeless', pageRecordList.record$(pageId).value?.mode$.value === 'edgeless',
[pageRecordList] [pageRecordList]
); );
@@ -27,7 +27,7 @@ export const usePageHelper = (docCollection: DocCollection) => {
(mode?: 'page' | 'edgeless') => { (mode?: 'page' | 'edgeless') => {
const page = createDoc(); const page = createDoc();
initEmptyPage(page); initEmptyPage(page);
pageRecordList.record(page.id).value?.setMode(mode || 'page'); pageRecordList.record$(page.id).value?.setMode(mode || 'page');
openPage(docCollection.id, page.id); openPage(docCollection.id, page.id);
return page; return page;
}, },
@@ -49,7 +49,7 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({
isPublic, isPublic,
publishMode, publishMode,
}: PageDetailEditorProps & { page: BlockSuiteDoc }) { }: PageDetailEditorProps & { page: BlockSuiteDoc }) {
const currentMode = useLiveData(useService(Doc).mode); const currentMode = useLiveData(useService(Doc).mode$);
const mode = useMemo(() => { const mode = useMemo(() => {
const shareMode = publishMode || currentMode; const shareMode = publishMode || currentMode;
@@ -94,8 +94,8 @@ export const TagPageListHeader = ({
tag: Tag; tag: Tag;
workspaceId: string; workspaceId: string;
}) => { }) => {
const tagColor = useLiveData(tag.color); const tagColor = useLiveData(tag.color$);
const tagTitle = useLiveData(tag.value); const tagTitle = useLiveData(tag.value$);
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const { jumpToTags, jumpToCollection } = useNavigateHelper(); const { jumpToTags, jumpToCollection } = useNavigateHelper();
@@ -183,13 +183,9 @@ export const SwitchTag = ({ onClick }: SwitchTagProps) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const tagService = useService(TagService); const tagService = useService(TagService);
const filteredLiveData = useMemo(() => { const filteredTags = useLiveData(
if (inputValue) { inputValue ? tagService.filterTagsByName$(inputValue) : tagService.tags$
return tagService.filterTagsByName(inputValue); );
}
return tagService.tags;
}, [inputValue, tagService]);
const filteredTags = useLiveData(filteredLiveData);
const onInputChange = useCallback( const onInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -238,8 +234,8 @@ export const SwitchTag = ({ onClick }: SwitchTagProps) => {
}; };
const TagLink = ({ tag, onClick }: { tag: Tag; onClick: () => void }) => { const TagLink = ({ tag, onClick }: { tag: Tag; onClick: () => void }) => {
const tagColor = useLiveData(tag.color); const tagColor = useLiveData(tag.color$);
const tagTitle = useLiveData(tag.value); const tagTitle = useLiveData(tag.value$);
return ( return (
<Link <Link
key={tag.id} key={tag.id}
@@ -69,8 +69,7 @@ const PageSelectionCell = ({
export const PageTagsCell = ({ pageId }: Pick<PageListItemProps, 'pageId'>) => { export const PageTagsCell = ({ pageId }: Pick<PageListItemProps, 'pageId'>) => {
const tagsService = useService(TagService); const tagsService = useService(TagService);
const tagsLiveData = tagsService.tagsByPageId(pageId); const tags = useLiveData(tagsService.tagsByPageId$(pageId));
const tags = useLiveData(tagsLiveData);
return ( return (
<div data-testid="page-list-item-tags" className={styles.tagsCell}> <div data-testid="page-list-item-tags" className={styles.tagsCell}>
@@ -57,8 +57,8 @@ export const TagItem = ({
style, style,
maxWidth, maxWidth,
}: TagItemProps) => { }: TagItemProps) => {
const value = useLiveData(tag?.value); const value = useLiveData(tag?.value$);
const color = useLiveData(tag?.color); const color = useLiveData(tag?.color$);
const handleRemove: MouseEventHandler = useCallback( const handleRemove: MouseEventHandler = useCallback(
e => { e => {
e.stopPropagation(); e.stopPropagation();
@@ -112,13 +112,13 @@ const TagItemNormal = ({
return maxItems ? tags.slice(0, maxItems) : tags; return maxItems ? tags.slice(0, maxItems) : tags;
}, [maxItems, tags]); }, [maxItems, tags]);
const tagsOrderedLiveData = useMemo(() => { const tagsOrdered = useLiveData(
useMemo(() => {
return LiveData.computed(get => return LiveData.computed(get =>
[...nTags].sort((a, b) => get(a.value).length - get(b.value).length) [...nTags].sort((a, b) => get(a.value$).length - get(b.value$).length)
);
}, [nTags])
); );
}, [nTags]);
const tagsOrdered = useLiveData(tagsOrderedLiveData);
return useMemo( return useMemo(
() => () =>
@@ -275,11 +275,11 @@ function tagIdToTagOption(
const PageTitle = ({ id }: { id: string }) => { const PageTitle = ({ id }: { id: string }) => {
const page = useLiveData( const page = useLiveData(
useService(PageRecordList).records.map(record => { useService(PageRecordList).records$.map(record => {
return record.find(p => p.id === id); return record.find(p => p.id === id);
}) })
); );
const title = useLiveData(page?.title); const title = useLiveData(page?.title$);
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
return title || t['Untitled'](); return title || t['Untitled']();
}; };
@@ -33,8 +33,8 @@ export const CreateOrEditTag = ({
tagMeta?: TagMeta; tagMeta?: TagMeta;
}) => { }) => {
const tagService = useService(TagService); const tagService = useService(TagService);
const tagOptions = useLiveData(tagService.tagMetas); const tagOptions = useLiveData(tagService.tagMetas$);
const tag = useLiveData(tagService.tagByTagId(tagMeta?.id)); const tag = useLiveData(tagService.tagByTagId$(tagMeta?.id));
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
@@ -107,8 +107,8 @@ export const pageToCommand = (
subTitle?: string, subTitle?: string,
blockId?: string blockId?: string
): CMDKCommand => { ): CMDKCommand => {
const pageMode = workspace.services.get(PageRecordList).record(page.id).value const pageMode = workspace.services.get(PageRecordList).record$(page.id).value
?.mode.value; ?.mode$.value;
const title = getPageTitle(page.id) || t['Untitled'](); const title = getPageTitle(page.id) || t['Untitled']();
const commandLabel = { const commandLabel = {
@@ -320,7 +320,7 @@ export const collectionToCommand = (
export const useCollectionsCommands = () => { export const useCollectionsCommands = () => {
// todo: considering collections for searching pages // todo: considering collections for searching pages
const collectionService = useService(CollectionService); const collectionService = useService(CollectionService);
const collections = useLiveData(collectionService.collections); const collections = useLiveData(collectionService.collections$);
const query = useAtomValue(cmdkQueryAtom); const query = useAtomValue(cmdkQueryAtom);
const navigationHelper = useNavigateHelper(); const navigationHelper = useNavigateHelper();
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
@@ -356,7 +356,7 @@ export const useCMDKCommandGroups = () => {
const collectionCommands = useCollectionsCommands(); const collectionCommands = useCollectionsCommands();
const currentPage = useServiceOptional(Doc); const currentPage = useServiceOptional(Doc);
const currentPageMode = useLiveData(currentPage?.mode); const currentPageMode = useLiveData(currentPage?.mode$);
const affineCommands = useMemo(() => { const affineCommands = useMemo(() => {
return getAllCommand({ return getAllCommand({
pageMode: currentPageMode, pageMode: currentPageMode,
@@ -30,7 +30,7 @@ const showList = environment.isDesktop ? DESKTOP_SHOW_LIST : DEFAULT_SHOW_LIST;
export const HelpIsland = () => { export const HelpIsland = () => {
const page = useServiceOptional(Doc); const page = useServiceOptional(Doc);
const pageId = page?.id; const pageId = page?.id;
const mode = useLiveData(page?.mode); const mode = useLiveData(page?.mode$);
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom); const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
const [spread, setShowSpread] = useState(false); const [spread, setShowSpread] = useState(false);
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
@@ -19,7 +19,7 @@ import * as styles from './styles.css';
export const TrashPageFooter = ({ pageId }: { pageId: string }) => { export const TrashPageFooter = ({ pageId }: { pageId: string }) => {
const workspace = useLiveData( const workspace = useLiveData(
useService(CurrentWorkspaceService).currentWorkspace useService(CurrentWorkspaceService).currentWorkspace$
); );
assertExists(workspace); assertExists(workspace);
const docCollection = workspace.docCollection; const docCollection = workspace.docCollection;
@@ -89,7 +89,7 @@ const CollectionRenderer = ({
}; };
return filterPage(collection, pageData); return filterPage(collection, pageData);
}); });
const location = useLiveData(useService(Workbench).location); const location = useLiveData(useService(Workbench).location$);
const currentPath = location.pathname; const currentPath = location.pathname;
const path = `/collection/${collection.id}`; const path = `/collection/${collection.id}`;
@@ -174,7 +174,7 @@ export const CollectionsList = ({
onCreate, onCreate,
}: CollectionsListProps) => { }: CollectionsListProps) => {
const metas = useBlockSuiteDocMeta(workspace); const metas = useBlockSuiteDocMeta(workspace);
const collections = useLiveData(useService(CollectionService).collections); const collections = useLiveData(useService(CollectionService).collections$);
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
if (collections.length === 0) { if (collections.length === 0) {
return ( return (
@@ -36,8 +36,8 @@ export const Page = ({
const pageId = page.id; const pageId = page.id;
const active = params.pageId === pageId; const active = params.pageId === pageId;
const pageRecord = useLiveData(useService(PageRecordList).record(pageId)); const pageRecord = useLiveData(useService(PageRecordList).record$(pageId));
const pageMode = useLiveData(pageRecord?.mode); const pageMode = useLiveData(pageRecord?.mode$);
const dragItemId = getDragItemId('collectionPage', pageId); const dragItemId = getDragItemId('collectionPage', pageId);
const icon = useMemo(() => { const icon = useMemo(() => {
@@ -27,8 +27,8 @@ export const ReferencePage = ({
const params = useParams(); const params = useParams();
const active = params.pageId === pageId; const active = params.pageId === pageId;
const pageRecord = useLiveData(useService(PageRecordList).record(pageId)); const pageRecord = useLiveData(useService(PageRecordList).record$(pageId));
const pageMode = useLiveData(pageRecord?.mode); const pageMode = useLiveData(pageRecord?.mode$);
const icon = useMemo(() => { const icon = useMemo(() => {
return pageMode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />; return pageMode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
}, [pageMode]); }, [pageMode]);
@@ -29,8 +29,8 @@ export const FavouritePage = ({
const params = useParams(); const params = useParams();
const active = params.pageId === pageId; const active = params.pageId === pageId;
const dragItemId = getDragItemId('favouritePage', pageId); const dragItemId = getDragItemId('favouritePage', pageId);
const pageRecord = useLiveData(useService(PageRecordList).record(pageId)); const pageRecord = useLiveData(useService(PageRecordList).record$(pageId));
const pageMode = useLiveData(pageRecord?.mode); const pageMode = useLiveData(pageRecord?.mode$);
const icon = useMemo(() => { const icon = useMemo(() => {
return pageMode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />; return pageMode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
@@ -121,7 +121,7 @@ const UserWithWorkspaceListInner = ({
}, [onEventEnd, setOpenCreateWorkspaceModal]); }, [onEventEnd, setOpenCreateWorkspaceModal]);
const workspaceManager = useService(WorkspaceManager); const workspaceManager = useService(WorkspaceManager);
const workspaces = useLiveData(workspaceManager.list.workspaceList); const workspaces = useLiveData(workspaceManager.list.workspaceList$);
// revalidate workspace list when mounted // revalidate workspace list when mounted
useEffect(() => { useEffect(() => {
@@ -105,7 +105,7 @@ export const AFFiNEWorkspaceList = ({
onEventEnd?: () => void; onEventEnd?: () => void;
}) => { }) => {
const workspaces = useLiveData( const workspaces = useLiveData(
useService(WorkspaceManager).list.workspaceList useService(WorkspaceManager).list.workspaceList$
); );
const setOpenCreateWorkspaceModal = useSetAtom(openCreateWorkspaceModalAtom); const setOpenCreateWorkspaceModal = useSetAtom(openCreateWorkspaceModalAtom);
@@ -113,7 +113,7 @@ export const AFFiNEWorkspaceList = ({
const { jumpToSubPath } = useNavigateHelper(); const { jumpToSubPath } = useNavigateHelper();
const currentWorkspace = useLiveData( const currentWorkspace = useLiveData(
useService(CurrentWorkspaceService).currentWorkspace useService(CurrentWorkspaceService).currentWorkspace$
); );
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom); const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
@@ -99,7 +99,7 @@ export const RootAppSidebar = ({
const { appSettings } = useAppSettingHelper(); const { appSettings } = useAppSettingHelper();
const docCollection = currentWorkspace.docCollection; const docCollection = currentWorkspace.docCollection;
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const currentPath = useLiveData(useService(Workbench).location).pathname; const currentPath = useLiveData(useService(Workbench).location$).pathname;
const onClickNewPage = useAsyncCallback(async () => { const onClickNewPage = useAsyncCallback(async () => {
const page = createPage(); const page = createPage();
@@ -111,7 +111,7 @@ export function useBlockSuiteMetaHelper(docCollection: DocCollection) {
const duplicate = useAsyncCallback( const duplicate = useAsyncCallback(
async (pageId: string, openPageAfterDuplication: boolean = true) => { async (pageId: string, openPageAfterDuplication: boolean = true) => {
const currentPageMode = pageRecordList.record(pageId).value?.mode.value; const currentPageMode = pageRecordList.record$(pageId).value?.mode$.value;
const currentPageMeta = getDocMeta(pageId); const currentPageMeta = getDocMeta(pageId);
const newPage = createDoc(); const newPage = createDoc();
const currentPage = docCollection.getDoc(pageId); const currentPage = docCollection.getDoc(pageId);
@@ -137,7 +137,7 @@ export function useBlockSuiteMetaHelper(docCollection: DocCollection) {
currentPageMeta.title.replace(lastDigitRegex, '') + `(${newNumber})`; currentPageMeta.title.replace(lastDigitRegex, '') + `(${newNumber})`;
pageRecordList pageRecordList
.record(newPage.id) .record$(newPage.id)
.value?.setMode(currentPageMode || 'page'); .value?.setMode(currentPageMode || 'page');
setDocTitle(newPage.id, newPageTitle); setDocTitle(newPage.id, newPageTitle);
openPageAfterDuplication && openPage(docCollection.id, newPage.id); openPageAfterDuplication && openPage(docCollection.id, newPage.id);
@@ -4,7 +4,7 @@ import { useMemo } from 'react';
export function useDocEngineStatus() { export function useDocEngineStatus() {
const workspace = useService(Workspace); const workspace = useService(Workspace);
const engineState = useLiveData(workspace.engine.docEngineState); const engineState = useLiveData(workspace.engine.docEngineState$);
const progress = const progress =
(engineState.total - engineState.syncing) / engineState.total; (engineState.total - engineState.syncing) / engineState.total;
@@ -21,7 +21,7 @@ import { useTrashModalHelper } from './use-trash-modal-helper';
export function useRegisterBlocksuiteEditorCommands() { export function useRegisterBlocksuiteEditorCommands() {
const page = useService(Doc); const page = useService(Doc);
const pageId = page.id; const pageId = page.id;
const mode = useLiveData(page.mode); const mode = useLiveData(page.mode$);
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const workspace = useService(Workspace); const workspace = useService(Workspace);
const docCollection = workspace.docCollection; const docCollection = workspace.docCollection;
@@ -56,14 +56,14 @@ export const QuickSearch = () => {
); );
const workbench = useService(Workbench); const workbench = useService(Workbench);
const currentPath = useLiveData(workbench.location.map(l => l.pathname)); const currentPath = useLiveData(workbench.location$.map(l => l.pathname));
const pageRecordList = useService(PageRecordList); const pageRecordList = useService(PageRecordList);
const currentPathId = matchPath('/:pageId', currentPath)?.params.pageId; const currentPathId = matchPath('/:pageId', currentPath)?.params.pageId;
// TODO: getting pageid from route is fragile, get current page from context // TODO: getting pageid from route is fragile, get current page from context
const currentPage = useLiveData( const currentPage = useLiveData(
currentPathId ? pageRecordList.record(currentPathId) : null currentPathId ? pageRecordList.record$(currentPathId) : null
); );
const pageMeta = useLiveData(currentPage?.meta); const pageMeta = useLiveData(currentPage?.meta$);
return ( return (
<CMDKQuickSearchModal <CMDKQuickSearchModal
@@ -32,7 +32,7 @@ export class CollectionService {
return this.setting.get(COLLECTIONS_TRASH_KEY) as YArray<DeletedCollection>; return this.setting.get(COLLECTIONS_TRASH_KEY) as YArray<DeletedCollection>;
} }
readonly collections = LiveData.from( readonly collections$ = LiveData.from(
new Observable<Collection[]>(subscriber => { new Observable<Collection[]>(subscriber => {
subscriber.next(this.collectionsYArray?.toArray() ?? []); subscriber.next(this.collectionsYArray?.toArray() ?? []);
const fn = () => { const fn = () => {
@@ -46,7 +46,7 @@ export class CollectionService {
[] []
); );
readonly collectionsTrash = LiveData.from( readonly collectionsTrash$ = LiveData.from(
new Observable<DeletedCollection[]>(subscriber => { new Observable<DeletedCollection[]>(subscriber => {
subscriber.next(this.collectionsTrashYArray?.toArray() ?? []); subscriber.next(this.collectionsTrashYArray?.toArray() ?? []);
const fn = () => { const fn = () => {
@@ -148,7 +148,7 @@ export class CollectionService {
deletePagesFromCollections(ids: string[]) { deletePagesFromCollections(ids: string[]) {
const idSet = new Set(ids); const idSet = new Set(ids);
this.doc.transact(() => { this.doc.transact(() => {
this.collections.value.forEach(collection => { this.collections$.value.forEach(collection => {
this.deletePagesFromCollection(collection, idSet); this.deletePagesFromCollection(collection, idSet);
}); });
}); });
@@ -16,7 +16,7 @@ export const GlobalScopeProvider: React.FC<
}); });
const workspaceProvider = useLiveData( const workspaceProvider = useLiveData(
currentWorkspaceService.currentWorkspace currentWorkspaceService.currentWorkspace$
)?.services; )?.services;
return ( return (
@@ -57,8 +57,8 @@ const PageItem = ({
className, className,
...attrs ...attrs
}: PageItemProps) => { }: PageItemProps) => {
const title = useLiveData(pageRecord.title); const title = useLiveData(pageRecord.title$);
const mode = useLiveData(pageRecord.mode); const mode = useLiveData(pageRecord.mode$);
const workspace = useService(Workspace); const workspace = useService(Workspace);
const { isJournal } = useJournalInfoHelper( const { isJournal } = useJournalInfoHelper(
workspace.docCollection, workspace.docCollection,
@@ -171,7 +171,7 @@ const sortPagesByDate = (
return [...pages].sort((a, b) => { return [...pages].sort((a, b) => {
return ( return (
(order === 'asc' ? 1 : -1) * (order === 'asc' ? 1 : -1) *
dayjs(b.meta.value[field]).diff(dayjs(a.meta.value[field])) dayjs(b.meta$.value[field]).diff(dayjs(a.meta$.value[field]))
); );
}); });
}; };
@@ -193,7 +193,7 @@ const JournalDailyCountBlock = ({ date }: JournalBlockProps) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const [activeItem, setActiveItem] = useState<NavItemName>('createdToday'); const [activeItem, setActiveItem] = useState<NavItemName>('createdToday');
const pageRecordList = useService(PageRecordList); const pageRecordList = useService(PageRecordList);
const pageRecords = useLiveData(pageRecordList.records); const pageRecords = useLiveData(pageRecordList.records$);
const navigateHelper = useNavigateHelper(); const navigateHelper = useNavigateHelper();
@@ -201,10 +201,10 @@ const JournalDailyCountBlock = ({ date }: JournalBlockProps) => {
(field: 'createDate' | 'updatedDate') => { (field: 'createDate' | 'updatedDate') => {
return sortPagesByDate( return sortPagesByDate(
pageRecords.filter(pageRecord => { pageRecords.filter(pageRecord => {
if (pageRecord.meta.value.trash) return false; if (pageRecord.meta$.value.trash) return false;
return ( return (
pageRecord.meta.value[field] && pageRecord.meta$.value[field] &&
dayjs(pageRecord.meta.value[field]).isSame(date, 'day') dayjs(pageRecord.meta$.value[field]).isSame(date, 'day')
); );
}), }),
field field
@@ -321,7 +321,7 @@ const ConflictList = ({
setTrashModal({ setTrashModal({
open: true, open: true,
pageIds: [pageRecord.id], pageIds: [pageRecord.id],
pageTitles: [pageRecord.meta.value.title], pageTitles: [pageRecord.meta$.value.title],
}); });
}, },
[setTrashModal] [setTrashModal]
@@ -363,7 +363,7 @@ const JournalConflictBlock = ({ date }: JournalBlockProps) => {
const pageRecordList = useService(PageRecordList); const pageRecordList = useService(PageRecordList);
const journalHelper = useJournalHelper(workspace.docCollection); const journalHelper = useJournalHelper(workspace.docCollection);
const docs = journalHelper.getJournalsByDate(date.format('YYYY-MM-DD')); const docs = journalHelper.getJournalsByDate(date.format('YYYY-MM-DD'));
const pageRecords = useLiveData(pageRecordList.records).filter(v => { const pageRecords = useLiveData(pageRecordList.records$).filter(v => {
return docs.some(doc => doc.id === v.id); return docs.some(doc => doc.id === v.id);
}); });
@@ -7,12 +7,12 @@ import type { Workbench } from '../../workbench';
export class Navigator { export class Navigator {
constructor(private readonly workbench: Workbench) {} constructor(private readonly workbench: Workbench) {}
private readonly history = this.workbench.activeView.map( private readonly history$ = this.workbench.activeView$.map(
view => view.history view => view.history
); );
private readonly location = LiveData.from( private readonly location$ = LiveData.from(
this.history.pipe( this.history$.pipe(
switchMap( switchMap(
history => history =>
new Observable<{ index: number; entries: Location[] }>(subscriber => { new Observable<{ index: number; entries: Location[] }>(subscriber => {
@@ -29,11 +29,11 @@ export class Navigator {
{ index: 0, entries: [] } { index: 0, entries: [] }
); );
readonly backable = this.location.map( readonly backable$ = this.location$.map(
({ index, entries }) => index > 0 && entries.length > 1 ({ index, entries }) => index > 0 && entries.length > 1
); );
readonly forwardable = this.location.map( readonly forwardable$ = this.location$.map(
({ index, entries }) => index < entries.length - 1 ({ index, entries }) => index < entries.length - 1
); );
@@ -41,7 +41,7 @@ export class Navigator {
if (!environment.isDesktop) { if (!environment.isDesktop) {
window.history.back(); window.history.back();
} else { } else {
this.history.value.back(); this.history$.value.back();
} }
} }
@@ -49,7 +49,7 @@ export class Navigator {
if (!environment.isDesktop) { if (!environment.isDesktop) {
window.history.forward(); window.history.forward();
} else { } else {
this.history.value.forward(); this.history$.value.forward();
} }
} }
} }
@@ -33,8 +33,8 @@ export const NavigationButtons = () => {
const navigator = useService(Navigator); const navigator = useService(Navigator);
const backable = useLiveData(navigator.backable); const backable = useLiveData(navigator.backable$);
const forwardable = useLiveData(navigator.forwardable); const forwardable = useLiveData(navigator.forwardable$);
const handleBack = useCallback(() => { const handleBack = useCallback(() => {
navigator.back(); navigator.back();
@@ -7,22 +7,22 @@ const RIGHT_SIDEBAR_KEY = 'app:settings:rightsidebar';
export class RightSidebar { export class RightSidebar {
constructor(private readonly globalState: GlobalState) {} constructor(private readonly globalState: GlobalState) {}
readonly isOpen = LiveData.from( readonly isOpen$ = LiveData.from(
this.globalState.watch<boolean>(RIGHT_SIDEBAR_KEY), this.globalState.watch<boolean>(RIGHT_SIDEBAR_KEY),
false false
).map(Boolean); ).map(Boolean);
readonly views = new LiveData<RightSidebarView[]>([]); readonly views$ = new LiveData<RightSidebarView[]>([]);
readonly front = this.views.map( readonly front$ = this.views$.map(
stack => stack[0] as RightSidebarView | undefined stack => stack[0] as RightSidebarView | undefined
); );
readonly hasViews = this.views.map(stack => stack.length > 0); readonly hasViews$ = this.views$.map(stack => stack.length > 0);
open() { open() {
this._set(true); this._set(true);
} }
toggle() { toggle() {
this._set(!this.isOpen.value); this._set(!this.isOpen$.value);
} }
close() { close() {
@@ -37,15 +37,15 @@ export class RightSidebar {
* @private use `RightSidebarViewIsland` instead * @private use `RightSidebarViewIsland` instead
*/ */
_append(view: RightSidebarView) { _append(view: RightSidebarView) {
this.views.next([...this.views.value, view]); this.views$.next([...this.views$.value, view]);
} }
/** /**
* @private use `RightSidebarViewIsland` instead * @private use `RightSidebarViewIsland` instead
*/ */
_moveToFront(view: RightSidebarView) { _moveToFront(view: RightSidebarView) {
if (this.views.value.includes(view)) { if (this.views$.value.includes(view)) {
this.views.next([view, ...this.views.value.filter(v => v !== view)]); this.views$.next([view, ...this.views$.value.filter(v => v !== view)]);
} }
} }
@@ -53,6 +53,6 @@ export class RightSidebar {
* @private use `RightSidebarViewIsland` instead * @private use `RightSidebarViewIsland` instead
*/ */
_remove(view: RightSidebarView) { _remove(view: RightSidebarView) {
this.views.next(this.views.value.filter(v => v !== view)); this.views$.next(this.views$.value.filter(v => v !== view));
} }
} }
@@ -19,8 +19,8 @@ export const RightSidebarContainer = () => {
const [resizing, setResizing] = useState(false); const [resizing, setResizing] = useState(false);
const rightSidebar = useService(RightSidebar); const rightSidebar = useService(RightSidebar);
const frontView = useLiveData(rightSidebar.front); const frontView = useLiveData(rightSidebar.front$);
const open = useLiveData(rightSidebar.isOpen) && frontView !== undefined; const open = useLiveData(rightSidebar.isOpen$) && frontView !== undefined;
const [floating, setFloating] = useState(false); const [floating, setFloating] = useState(false);
const appSidebarOpened = useAtomValue(appSidebarOpenAtom); const appSidebarOpened = useAtomValue(appSidebarOpenAtom);
@@ -10,24 +10,24 @@ export class Tag {
private readonly pageRecordList: PageRecordList private readonly pageRecordList: PageRecordList
) {} ) {}
private readonly tagOption = this.properties.tagOptions$.map( private readonly tagOption$ = this.properties.tagOptions$.map(
tags => tags.find(tag => tag.id === this.id) as TagSchema tags => tags.find(tag => tag.id === this.id) as TagSchema
); );
value = this.tagOption.map(tag => tag?.value || ''); value$ = this.tagOption$.map(tag => tag?.value || '');
color = this.tagOption.map(tag => tag?.color || ''); color$ = this.tagOption$.map(tag => tag?.color || '');
createDate = this.tagOption.map(tag => tag?.createDate || Date.now()); createDate$ = this.tagOption$.map(tag => tag?.createDate || Date.now());
updateDate = this.tagOption.map(tag => tag?.updateDate || Date.now()); updateDate$ = this.tagOption$.map(tag => tag?.updateDate || Date.now());
rename(value: string) { rename(value: string) {
this.properties.updateTagOption(this.id, { this.properties.updateTagOption(this.id, {
id: this.id, id: this.id,
value, value,
color: this.color.value, color: this.color$.value,
createDate: this.createDate.value, createDate: this.createDate$.value,
updateDate: Date.now(), updateDate: Date.now(),
}); });
} }
@@ -35,37 +35,37 @@ export class Tag {
changeColor(color: string) { changeColor(color: string) {
this.properties.updateTagOption(this.id, { this.properties.updateTagOption(this.id, {
id: this.id, id: this.id,
value: this.value.value, value: this.value$.value,
color, color,
createDate: this.createDate.value, createDate: this.createDate$.value,
updateDate: Date.now(), updateDate: Date.now(),
}); });
} }
tag(pageId: string) { tag(pageId: string) {
const pageRecord = this.pageRecordList.record(pageId).value; const pageRecord = this.pageRecordList.record$(pageId).value;
if (!pageRecord) { if (!pageRecord) {
return; return;
} }
pageRecord?.setMeta({ pageRecord?.setMeta({
tags: [...pageRecord.meta.value.tags, this.id], tags: [...pageRecord.meta$.value.tags, this.id],
}); });
} }
untag(pageId: string) { untag(pageId: string) {
const pageRecord = this.pageRecordList.record(pageId).value; const pageRecord = this.pageRecordList.record$(pageId).value;
if (!pageRecord) { if (!pageRecord) {
return; return;
} }
pageRecord?.setMeta({ pageRecord?.setMeta({
tags: pageRecord.meta.value.tags.filter(tagId => tagId !== this.id), tags: pageRecord.meta$.value.tags.filter(tagId => tagId !== this.id),
}); });
} }
readonly pageIds = LiveData.computed(get => { readonly pageIds$ = LiveData.computed(get => {
const pages = get(this.pageRecordList.records); const pages = get(this.pageRecordList.records$);
return pages return pages
.filter(page => get(page.meta).tags.includes(this.id)) .filter(page => get(page.meta$).tags.includes(this.id))
.map(page => page.id); .map(page => page.id);
}); });
} }
@@ -10,7 +10,7 @@ export class TagService {
private readonly pageRecordList: PageRecordList private readonly pageRecordList: PageRecordList
) {} ) {}
readonly tags = this.properties.tagOptions$.map(tags => readonly tags$ = this.properties.tagOptions$.map(tags =>
tags.map(tag => new Tag(tag.id, this.properties, this.pageRecordList)) tags.map(tag => new Tag(tag.id, this.properties, this.pageRecordList))
); );
@@ -34,33 +34,33 @@ export class TagService {
this.properties.removeTagOption(tagId); this.properties.removeTagOption(tagId);
} }
tagsByPageId(pageId: string) { tagsByPageId$(pageId: string) {
return LiveData.computed(get => { return LiveData.computed(get => {
const pageRecord = get(this.pageRecordList.record(pageId)); const pageRecord = get(this.pageRecordList.record$(pageId));
if (!pageRecord) return []; if (!pageRecord) return [];
const tagIds = get(pageRecord.meta).tags; const tagIds = get(pageRecord.meta$).tags;
return get(this.tags).filter(tag => tagIds.includes(tag.id)); return get(this.tags$).filter(tag => tagIds.includes(tag.id));
}); });
} }
tagIdsByPageId(pageId: string) { tagIdsByPageId$(pageId: string) {
return this.tagsByPageId(pageId).map(tags => tags.map(tag => tag.id)); return this.tagsByPageId$(pageId).map(tags => tags.map(tag => tag.id));
} }
tagByTagId(tagId?: string) { tagByTagId$(tagId?: string) {
return this.tags.map(tags => tags.find(tag => tag.id === tagId)); return this.tags$.map(tags => tags.find(tag => tag.id === tagId));
} }
tagMetas = LiveData.computed(get => { tagMetas$ = LiveData.computed(get => {
return get(this.tags).map(tag => { return get(this.tags$).map(tag => {
return { return {
id: tag.id, id: tag.id,
title: get(tag.value), title: get(tag.value$),
color: get(tag.color), color: get(tag.color$),
pageCount: get(tag.pageIds).length, pageCount: get(tag.pageIds$).length,
createDate: get(tag.createDate), createDate: get(tag.createDate$),
updatedDate: get(tag.updateDate), updatedDate: get(tag.updateDate$),
}; };
}); });
}); });
@@ -71,15 +71,17 @@ export class TagService {
return trimmedValue.includes(trimmedQuery); return trimmedValue.includes(trimmedQuery);
} }
filterTagsByName(name: string) { filterTagsByName$(name: string) {
return LiveData.computed(get => { return LiveData.computed(get => {
return get(this.tags).filter(tag => this.filterFn(get(tag.value), name)); return get(this.tags$).filter(tag =>
this.filterFn(get(tag.value$), name)
);
}); });
} }
tagByTagValue(value: string) { tagByTagValue$(value: string) {
return LiveData.computed(get => { return LiveData.computed(get => {
return get(this.tags).find(tag => this.filterFn(get(tag.value), value)); return get(this.tags$).find(tag => this.filterFn(get(tag.value$), value));
}); });
} }
} }
@@ -17,11 +17,11 @@ export const DeleteTagConfirmModal = ({
}) => { }) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const tagService = useService(TagService); const tagService = useService(TagService);
const tags = useLiveData(tagService.tags); const tags = useLiveData(tagService.tags$);
const selectedTags = useMemo(() => { const selectedTags = useMemo(() => {
return tags.filter(tag => selectedTagIds.includes(tag.id)); return tags.filter(tag => selectedTagIds.includes(tag.id));
}, [selectedTagIds, tags]); }, [selectedTagIds, tags]);
const tagName = useLiveData(selectedTags[0]?.value || ''); const tagName = useLiveData(selectedTags[0]?.value$ || '');
const handleDelete = useCallback(() => { const handleDelete = useCallback(() => {
selectedTagIds.forEach(tagId => { selectedTagIds.forEach(tagId => {
@@ -21,7 +21,7 @@ export class View {
initialIndex: 0, initialIndex: 0,
}); });
location = LiveData.from<Location>( location$ = LiveData.from<Location>(
new Observable(subscriber => { new Observable(subscriber => {
subscriber.next(this.history.location); subscriber.next(this.history.location);
return this.history.listen(update => { return this.history.listen(update => {
@@ -31,7 +31,7 @@ export class View {
this.history.location this.history.location
); );
entries = LiveData.from<Location[]>( entries$ = LiveData.from<Location[]>(
new Observable(subscriber => { new Observable(subscriber => {
subscriber.next(this.history.entries); subscriber.next(this.history.entries);
return this.history.listen(() => { return this.history.listen(() => {
@@ -41,7 +41,7 @@ export class View {
this.history.entries this.history.entries
); );
size = new LiveData(100); size$ = new LiveData(100);
header = createIsland(); header = createIsland();
body = createIsland(); body = createIsland();
@@ -59,6 +59,6 @@ export class View {
} }
setSize(size?: number) { setSize(size?: number) {
this.size.next(size ?? 100); this.size$.next(size ?? 100);
} }
} }
@@ -13,32 +13,32 @@ interface WorkbenchOpenOptions {
} }
export class Workbench { export class Workbench {
readonly views = new LiveData([new View()]); readonly views$ = new LiveData([new View()]);
activeViewIndex = new LiveData(0); activeViewIndex$ = new LiveData(0);
activeView = LiveData.from( activeView$ = LiveData.from(
combineLatest([this.views, this.activeViewIndex]).pipe( combineLatest([this.views$, this.activeViewIndex$]).pipe(
map(([views, index]) => views[index]) map(([views, index]) => views[index])
), ),
this.views.value[this.activeViewIndex.value] this.views$.value[this.activeViewIndex$.value]
); );
basename = new LiveData('/'); basename$ = new LiveData('/');
location = LiveData.from( location$ = LiveData.from(
this.activeView.pipe(switchMap(view => view.location)), this.activeView$.pipe(switchMap(view => view.location$)),
this.views.value[this.activeViewIndex.value].history.location this.views$.value[this.activeViewIndex$.value].history.location
); );
active(index: number) { active(index: number) {
this.activeViewIndex.next(index); this.activeViewIndex$.next(index);
} }
createView(at: WorkbenchPosition = 'beside', defaultLocation: To) { createView(at: WorkbenchPosition = 'beside', defaultLocation: To) {
const view = new View(defaultLocation); const view = new View(defaultLocation);
const newViews = [...this.views.value]; const newViews = [...this.views$.value];
newViews.splice(this.indexAt(at), 0, view); newViews.splice(this.indexAt(at), 0, view);
this.views.next(newViews); this.views$.next(newViews);
return newViews.indexOf(view); return newViews.indexOf(view);
} }
@@ -91,33 +91,33 @@ export class Workbench {
} }
viewAt(positionIndex: WorkbenchPosition): View | undefined { viewAt(positionIndex: WorkbenchPosition): View | undefined {
return this.views.value[this.indexAt(positionIndex)]; return this.views$.value[this.indexAt(positionIndex)];
} }
close(view: View) { close(view: View) {
if (this.views.value.length === 1) return; if (this.views$.value.length === 1) return;
const index = this.views.value.indexOf(view); const index = this.views$.value.indexOf(view);
if (index === -1) return; if (index === -1) return;
const newViews = [...this.views.value]; const newViews = [...this.views$.value];
newViews.splice(index, 1); newViews.splice(index, 1);
const activeViewIndex = this.activeViewIndex.value; const activeViewIndex = this.activeViewIndex$.value;
if (activeViewIndex !== 0 && activeViewIndex >= index) { if (activeViewIndex !== 0 && activeViewIndex >= index) {
this.active(activeViewIndex - 1); this.active(activeViewIndex - 1);
} }
this.views.next(newViews); this.views$.next(newViews);
} }
closeOthers(view: View) { closeOthers(view: View) {
view.size.next(100); view.size$.next(100);
this.views.next([view]); this.views$.next([view]);
this.active(0); this.active(0);
} }
moveView(from: number, to: number) { moveView(from: number, to: number) {
const views = [...this.views.value]; const views = [...this.views$.value];
const [removed] = views.splice(from, 1); const [removed] = views.splice(from, 1);
views.splice(to, 0, removed); views.splice(to, 0, removed);
this.views.next(views); this.views$.next(views);
this.active(to); this.active(to);
} }
@@ -128,18 +128,18 @@ export class Workbench {
* @returns * @returns
*/ */
resize(index: number, percent: number) { resize(index: number, percent: number) {
const view = this.views.value[index]; const view = this.views$.value[index];
const nextView = this.views.value[index + 1]; const nextView = this.views$.value[index + 1];
if (!nextView) return; if (!nextView) return;
const totalViewSize = this.views.value.reduce( const totalViewSize = this.views$.value.reduce(
(sum, v) => sum + v.size.value, (sum, v) => sum + v.size$.value,
0 0
); );
const percentOfTotal = totalViewSize * percent; const percentOfTotal = totalViewSize * percent;
const newSize = Number((view.size.value + percentOfTotal).toFixed(4)); const newSize = Number((view.size$.value + percentOfTotal).toFixed(4));
const newNextSize = Number( const newNextSize = Number(
(nextView.size.value - percentOfTotal).toFixed(4) (nextView.size$.value - percentOfTotal).toFixed(4)
); );
// TODO: better strategy to limit size // TODO: better strategy to limit size
if (newSize / totalViewSize < 0.2 || newNextSize / totalViewSize < 0.2) if (newSize / totalViewSize < 0.2 || newNextSize / totalViewSize < 0.2)
@@ -150,16 +150,16 @@ export class Workbench {
private indexAt(positionIndex: WorkbenchPosition): number { private indexAt(positionIndex: WorkbenchPosition): number {
if (positionIndex === 'active') { if (positionIndex === 'active') {
return this.activeViewIndex.value; return this.activeViewIndex$.value;
} }
if (positionIndex === 'beside') { if (positionIndex === 'beside') {
return this.activeViewIndex.value + 1; return this.activeViewIndex$.value + 1;
} }
if (positionIndex === 'head') { if (positionIndex === 'head') {
return 0; return 0;
} }
if (positionIndex === 'tail') { if (positionIndex === 'tail') {
return this.views.value.length; return this.views$.value.length;
} }
return positionIndex; return positionIndex;
} }
@@ -29,7 +29,7 @@ export function useBindWorkbenchToBrowserRouter(
const navigate = useNavigate(); const navigate = useNavigate();
const browserLocation = useLocation(); const browserLocation = useLocation();
const view = useLiveData(workbench.activeView); const view = useLiveData(workbench.activeView$);
useEffect(() => { useEffect(() => {
return view.history.listen(update => { return view.history.listen(update => {
@@ -31,9 +31,9 @@ export function useBindWorkbenchToDesktopRouter(
return; return;
} }
if ( if (
workbench.location.value.pathname === newLocation.pathname && workbench.location$.value.pathname === newLocation.pathname &&
workbench.location.value.search === newLocation.search && workbench.location$.value.search === newLocation.search &&
workbench.location.value.hash === newLocation.hash workbench.location$.value.hash === newLocation.hash
) { ) {
return; return;
} }
@@ -48,8 +48,8 @@ export const RouteContainer = ({ route }: Props) => {
const viewPosition = useViewPosition(); const viewPosition = useViewPosition();
const leftSidebarOpen = useAtomValue(appSidebarOpenAtom); const leftSidebarOpen = useAtomValue(appSidebarOpenAtom);
const rightSidebar = useService(RightSidebar); const rightSidebar = useService(RightSidebar);
const rightSidebarOpen = useLiveData(rightSidebar.isOpen); const rightSidebarOpen = useLiveData(rightSidebar.isOpen$);
const rightSidebarHasViews = useLiveData(rightSidebar.hasViews); const rightSidebarHasViews = useLiveData(rightSidebar.hasViews$);
const handleToggleRightSidebar = useCallback(() => { const handleToggleRightSidebar = useCallback(() => {
rightSidebar.toggle(); rightSidebar.toggle();
}, [rightSidebar]); }, [rightSidebar]);
@@ -45,10 +45,10 @@ export const SplitViewPanel = memo(function SplitViewPanel({
}: SplitViewPanelProps) { }: SplitViewPanelProps) {
const [indicatorPressed, setIndicatorPressed] = useState(false); const [indicatorPressed, setIndicatorPressed] = useState(false);
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const size = useLiveData(view.size); const size = useLiveData(view.size$);
const workbench = useService(Workbench); const workbench = useService(Workbench);
const activeView = useLiveData(workbench.activeView); const activeView = useLiveData(workbench.activeView$);
const views = useLiveData(workbench.views); const views = useLiveData(workbench.views$);
const isLast = views[views.length - 1] === view; const isLast = views[views.length - 1] === view;
const { const {
@@ -116,7 +116,7 @@ export const SplitViewPanel = memo(function SplitViewPanel({
const SplitViewMenu = ({ view }: { view: View }) => { const SplitViewMenu = ({ view }: { view: View }) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const workbench = useService(Workbench); const workbench = useService(Workbench);
const views = useLiveData(workbench.views); const views = useLiveData(workbench.views$);
const viewIndex = views.findIndex(v => v === view); const viewIndex = views.findIndex(v => v === view);
@@ -7,6 +7,6 @@ import { useView } from './use-view';
export function useIsActiveView() { export function useIsActiveView() {
const workbench = useService(Workbench); const workbench = useService(Workbench);
const currentView = useView(); const currentView = useView();
const activeView = useLiveData(workbench.activeView); const activeView = useLiveData(workbench.activeView$);
return currentView === activeView; return currentView === activeView;
} }
@@ -10,11 +10,11 @@ export const useViewPosition = () => {
const view = useView(); const view = useView();
const [position, setPosition] = useState(() => const [position, setPosition] = useState(() =>
calcPosition(view, workbench.views.value) calcPosition(view, workbench.views$.value)
); );
useEffect(() => { useEffect(() => {
const subscription = workbench.views.subscribe(views => { const subscription = workbench.views$.subscribe(views => {
setPosition(calcPosition(view, views)); setPosition(calcPosition(view, views));
}); });
return () => { return () => {
@@ -33,7 +33,7 @@ const warpedRoutes = viewRoutes.map(({ path, lazy }) => {
export const ViewRoot = ({ view }: { view: View }) => { export const ViewRoot = ({ view }: { view: View }) => {
const viewRouter = useMemo(() => createMemoryRouter(warpedRoutes), []); const viewRouter = useMemo(() => createMemoryRouter(warpedRoutes), []);
const location = useLiveData(view.location); const location = useLiveData(view.location$);
useEffect(() => { useEffect(() => {
viewRouter.navigate(location).catch(err => { viewRouter.navigate(location).catch(err => {
@@ -16,7 +16,7 @@ export const WorkbenchLink = ({
>) => { >) => {
const workbench = useService(Workbench); const workbench = useService(Workbench);
const { appSettings } = useAppSettingHelper(); const { appSettings } = useAppSettingHelper();
const basename = useLiveData(workbench.basename); const basename = useLiveData(workbench.basename$);
const handleClick = useCallback( const handleClick = useCallback(
(event: React.MouseEvent<HTMLAnchorElement>) => { (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault(); event.preventDefault();
@@ -21,7 +21,7 @@ export const WorkbenchRoot = () => {
// for debugging // for debugging
(window as any).workbench = workbench; (window as any).workbench = workbench;
const views = useLiveData(workbench.views); const views = useLiveData(workbench.views$);
const location = useLocation(); const location = useLocation();
const basename = location.pathname.match(/\/workspace\/[^/]+/g)?.[0] ?? '/'; const basename = location.pathname.match(/\/workspace\/[^/]+/g)?.[0] ?? '/';
@@ -40,8 +40,8 @@ export const WorkbenchRoot = () => {
); );
useEffect(() => { useEffect(() => {
workbench.basename.next(basename); workbench.basename$.next(basename);
}, [basename, workbench.basename]); }, [basename, workbench.basename$]);
return ( return (
<SplitView <SplitView
@@ -5,20 +5,20 @@ import { LiveData } from '@toeverything/infra/livedata';
* service to manage current workspace * service to manage current workspace
*/ */
export class CurrentWorkspaceService { export class CurrentWorkspaceService {
currentWorkspace = new LiveData<Workspace | null>(null); currentWorkspace$ = new LiveData<Workspace | null>(null);
/** /**
* open workspace, current workspace will be set to the workspace * open workspace, current workspace will be set to the workspace
* @param workspace * @param workspace
*/ */
openWorkspace(workspace: Workspace) { openWorkspace(workspace: Workspace) {
this.currentWorkspace.next(workspace); this.currentWorkspace$.next(workspace);
} }
/** /**
* close current workspace, current workspace will be null * close current workspace, current workspace will be null
*/ */
closeWorkspace() { closeWorkspace() {
this.currentWorkspace.next(null); this.currentWorkspace$.next(null);
} }
} }
+1 -1
View File
@@ -27,7 +27,7 @@ export const Component = () => {
const [navigating, setNavigating] = useState(false); const [navigating, setNavigating] = useState(false);
const [creating, setCreating] = useState(false); const [creating, setCreating] = useState(false);
const list = useLiveData(useService(WorkspaceListService).workspaceList); const list = useLiveData(useService(WorkspaceListService).workspaceList$);
const { openPage } = useNavigateHelper(); const { openPage } = useNavigateHelper();
@@ -186,7 +186,7 @@ export const Component = () => {
workspaceManager, workspaceManager,
]); ]);
const pageTitle = useLiveData(page?.title); const pageTitle = useLiveData(page?.title$);
usePageDocumentTitle(pageTitle); usePageDocumentTitle(pageTitle);
const loginStatus = useCurrentLoginStatus(); const loginStatus = useCurrentLoginStatus();
@@ -26,7 +26,7 @@ export const AllCollection = () => {
const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true); const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true);
const collectionService = useService(CollectionService); const collectionService = useService(CollectionService);
const collections = useLiveData(collectionService.collections); const collections = useLiveData(collectionService.collections$);
const config = useAllPageListConfig(); const config = useAllPageListConfig();
const collectionMetas = useMemo(() => { const collectionMetas = useMemo(() => {
@@ -32,11 +32,11 @@ const EmptyTagListHeader = () => {
export const AllTag = () => { export const AllTag = () => {
const tagService = useService(TagService); const tagService = useService(TagService);
const tags = useLiveData(tagService.tags); const tags = useLiveData(tagService.tags$);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [selectedTagIds, setSelectedTagIds] = useState<string[]>([]); const [selectedTagIds, setSelectedTagIds] = useState<string[]>([]);
const tagMetas: TagMeta[] = useLiveData(tagService.tagMetas); const tagMetas: TagMeta[] = useLiveData(tagService.tagMetas$);
const handleCloseModal = useCallback( const handleCloseModal = useCallback(
(open: boolean) => { (open: boolean) => {
@@ -67,7 +67,7 @@ export const CollectionDetail = ({
export const Component = function CollectionPage() { export const Component = function CollectionPage() {
const collectionService = useService(CollectionService); const collectionService = useService(CollectionService);
const collections = useLiveData(collectionService.collections); const collections = useLiveData(collectionService.collections$);
const navigate = useNavigateHelper(); const navigate = useNavigateHelper();
const params = useParams(); const params = useParams();
const workspace = useService(Workspace); const workspace = useService(Workspace);
@@ -76,7 +76,7 @@ export const Component = function CollectionPage() {
useEffect(() => { useEffect(() => {
if (!collection) { if (!collection) {
navigate.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL); navigate.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
const collection = collectionService.collectionsTrash.value.find( const collection = collectionService.collectionsTrash$.value.find(
v => v.collection.id === params.collectionId v => v.collection.id === params.collectionId
); );
let text = 'Collection does not exist'; let text = 'Collection does not exist';
@@ -94,7 +94,7 @@ export const Component = function CollectionPage() {
} }
}, [ }, [
collection, collection,
collectionService.collectionsTrash.value, collectionService.collectionsTrash$.value,
navigate, navigate,
params.collectionId, params.collectionId,
pushNotification, pushNotification,
@@ -108,9 +108,9 @@ const DetailPageImpl = memo(function DetailPageImpl() {
const isInTrash = pageMeta?.trash; const isInTrash = pageMeta?.trash;
const mode = useLiveData(page.mode); const mode = useLiveData(page.mode$);
useRegisterBlocksuiteEditorCommands(); useRegisterBlocksuiteEditorCommands();
const title = useLiveData(page.title); const title = useLiveData(page.title$);
usePageDocumentTitle(title); usePageDocumentTitle(title);
const onLoad = useCallback( const onLoad = useCallback(
@@ -156,13 +156,13 @@ const DetailPageImpl = memo(function DetailPageImpl() {
const disposable = new DisposableGroup(); const disposable = new DisposableGroup();
pageService.getEditorMode = (pageId: string) => pageService.getEditorMode = (pageId: string) =>
pageRecordList.record(pageId).value?.mode.value ?? 'page'; pageRecordList.record$(pageId).value?.mode$.value ?? 'page';
pageService.getDocUpdatedAt = (pageId: string) => { pageService.getDocUpdatedAt = (pageId: string) => {
const linkedPage = pageRecordList.record(pageId).value; const linkedPage = pageRecordList.record$(pageId).value;
if (!linkedPage) return new Date(); if (!linkedPage) return new Date();
const updatedDate = linkedPage.meta.value.updatedDate; const updatedDate = linkedPage.meta$.value.updatedDate;
const createDate = linkedPage.meta.value.createDate; const createDate = linkedPage.meta$.value.createDate;
return updatedDate ? new Date(updatedDate) : new Date(createDate); return updatedDate ? new Date(updatedDate) : new Date(createDate);
}; };
@@ -278,9 +278,9 @@ export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
const currentWorkspace = useService(Workspace); const currentWorkspace = useService(Workspace);
const pageRecordList = useService(PageRecordList); const pageRecordList = useService(PageRecordList);
const pageListReady = useLiveData(pageRecordList.isReady); const pageListReady = useLiveData(pageRecordList.isReady$);
const pageRecords = useLiveData(pageRecordList.records); const pageRecords = useLiveData(pageRecordList.records$);
const pageRecord = useMemo( const pageRecord = useMemo(
() => pageRecords.find(page => page.id === pageId), () => pageRecords.find(page => page.id === pageId),
@@ -310,7 +310,7 @@ export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
}; };
}, [currentWorkspace, pageId]); }, [currentWorkspace, pageId]);
const jumpOnce = useLiveData(pageRecord?.meta.map(meta => meta.jumpOnce)); const jumpOnce = useLiveData(pageRecord?.meta$.map(meta => meta.jumpOnce));
useEffect(() => { useEffect(() => {
if (jumpOnce) { if (jumpOnce) {
@@ -39,7 +39,7 @@ export const Component = (): ReactElement => {
const params = useParams(); const params = useParams();
const { workspaceList, loading: listLoading } = useLiveData( const { workspaceList, loading: listLoading } = useLiveData(
useService(WorkspaceListService).status useService(WorkspaceListService).status$
); );
const workspaceManager = useService(WorkspaceManager); const workspaceManager = useService(WorkspaceManager);
@@ -71,7 +71,7 @@ export const Component = (): ReactElement => {
// avoid doing operation, before workspace is loaded // avoid doing operation, before workspace is loaded
const isRootDocReady = const isRootDocReady =
useLiveData(workspace?.engine.rootDocState)?.ready ?? false; useLiveData(workspace?.engine.rootDocState$)?.ready ?? false;
// if listLoading is false, we can show 404 page, otherwise we should show loading page. // if listLoading is false, we can show 404 page, otherwise we should show loading page.
if (listLoading === false && meta === undefined) { if (listLoading === false && meta === undefined) {
@@ -8,7 +8,7 @@ import {
ViewBodyIsland, ViewBodyIsland,
ViewHeaderIsland, ViewHeaderIsland,
} from '@affine/core/modules/workbench'; } from '@affine/core/modules/workbench';
import { LiveData, useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { Workspace } from '@toeverything/infra'; import { Workspace } from '@toeverything/infra';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
@@ -23,21 +23,9 @@ export const TagDetail = ({ tagId }: { tagId?: string }) => {
const pageMetas = useBlockSuiteDocMeta(currentWorkspace.docCollection); const pageMetas = useBlockSuiteDocMeta(currentWorkspace.docCollection);
const tagService = useService(TagService); const tagService = useService(TagService);
const currentTagLiveData = tagService.tagByTagId(tagId); const currentTag = useLiveData(tagService.tagByTagId$(tagId));
const currentTag = useLiveData(currentTagLiveData);
const pageIdsLiveData = useMemo( const pageIds = useLiveData(currentTag?.pageIds$);
() =>
LiveData.computed(get => {
const liveTag = get(currentTagLiveData);
if (liveTag?.pageIds) {
return get(liveTag.pageIds);
}
return [];
}),
[currentTagLiveData]
);
const pageIds = useLiveData(pageIdsLiveData);
const filteredPageMetas = useMemo(() => { const filteredPageMetas = useMemo(() => {
const pageIdsSet = new Set(pageIds); const pageIdsSet = new Set(pageIds);
@@ -192,7 +192,7 @@ export const AuthModal = (): ReactElement => {
export function CurrentWorkspaceModals() { export function CurrentWorkspaceModals() {
const currentWorkspace = useLiveData( const currentWorkspace = useLiveData(
useService(CurrentWorkspaceService).currentWorkspace useService(CurrentWorkspaceService).currentWorkspace$
); );
const [openDisableCloudAlertModal, setOpenDisableCloudAlertModal] = useAtom( const [openDisableCloudAlertModal, setOpenDisableCloudAlertModal] = useAtom(
openDisableCloudAlertModalAtom openDisableCloudAlertModalAtom
@@ -227,10 +227,10 @@ export const SignOutConfirmModal = () => {
const { openPage } = useNavigateHelper(); const { openPage } = useNavigateHelper();
const [open, setOpen] = useAtom(openSignOutModalAtom); const [open, setOpen] = useAtom(openSignOutModalAtom);
const currentWorkspace = useLiveData( const currentWorkspace = useLiveData(
useService(CurrentWorkspaceService).currentWorkspace useService(CurrentWorkspaceService).currentWorkspace$
); );
const workspaces = useLiveData( const workspaces = useLiveData(
useService(WorkspaceManager).list.workspaceList useService(WorkspaceManager).list.workspaceList$
); );
const onConfirm = useAsyncCallback(async () => { const onConfirm = useAsyncCallback(async () => {
+4 -4
View File
@@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
export const createIsland = () => { export const createIsland = () => {
const targetLiveData = new LiveData<HTMLDivElement | null>(null); const targetLiveData$ = new LiveData<HTMLDivElement | null>(null);
let mounted = false; let mounted = false;
let provided = false; let provided = false;
return { return {
@@ -14,16 +14,16 @@ export const createIsland = () => {
throw new Error('Island should not be mounted more than once'); throw new Error('Island should not be mounted more than once');
} }
mounted = true; mounted = true;
targetLiveData.next(target.current); targetLiveData$.next(target.current);
return () => { return () => {
mounted = false; mounted = false;
targetLiveData.next(null); targetLiveData$.next(null);
}; };
}, []); }, []);
return <div {...other} ref={target}></div>; return <div {...other} ref={target}></div>;
}, },
Provider: ({ children }: React.PropsWithChildren) => { Provider: ({ children }: React.PropsWithChildren) => {
const target = useLiveData(targetLiveData); const target = useLiveData(targetLiveData$);
useEffect(() => { useEffect(() => {
if (provided === true && process.env.NODE_ENV !== 'production') { if (provided === true && process.env.NODE_ENV !== 'production') {
throw new Error('Island should not be provided more than once'); throw new Error('Island should not be provided more than once');
@@ -38,9 +38,9 @@ export const db$Map = new Map<string, Observable<WorkspaceSQLiteDB>>();
const beforeQuit$ = defer(() => fromEvent(process, 'beforeExit')); const beforeQuit$ = defer(() => fromEvent(process, 'beforeExit'));
// return a stream that emit a single event when the subject completes // return a stream that emit a single event when the subject completes
function completed<T>(subject: Subject<T>) { function completed<T>(subject$: Subject<T>) {
return new Observable(subscriber => { return new Observable(subscriber => {
const sub = subject.subscribe({ const sub = subject$.subscribe({
complete: () => { complete: () => {
subscriber.next(); subscriber.next();
subscriber.complete(); subscriber.complete();
@@ -50,7 +50,7 @@ function completed<T>(subject: Subject<T>) {
}); });
} }
function getWorkspaceDB$(id: string) { function getWorkspaceDB(id: string) {
if (!db$Map.has(id)) { if (!db$Map.has(id)) {
db$Map.set( db$Map.set(
id, id,
@@ -103,7 +103,7 @@ function getWorkspaceDB$(id: string) {
function startPollingSecondaryDB(db: WorkspaceSQLiteDB) { function startPollingSecondaryDB(db: WorkspaceSQLiteDB) {
return merge( return merge(
getWorkspaceMeta(db.workspaceId), getWorkspaceMeta(db.workspaceId),
workspaceSubjects.meta.pipe( workspaceSubjects.meta$.pipe(
map(({ meta }) => meta), map(({ meta }) => meta),
filter(meta => meta.id === db.workspaceId) filter(meta => meta.id === db.workspaceId)
) )
@@ -141,5 +141,5 @@ function startPollingSecondaryDB(db: WorkspaceSQLiteDB) {
} }
export function ensureSQLiteDB(id: string) { export function ensureSQLiteDB(id: string) {
return lastValueFrom(getWorkspaceDB$(id).pipe(take(1))); return lastValueFrom(getWorkspaceDB(id).pipe(take(1)));
} }
@@ -48,7 +48,7 @@ export const dbEvents = {
docId?: string; docId?: string;
}) => void }) => void
) => { ) => {
const sub = dbSubjects.externalUpdate.subscribe(fn); const sub = dbSubjects.externalUpdate$.subscribe(fn);
return () => { return () => {
sub.unsubscribe(); sub.unsubscribe();
}; };
@@ -1,7 +1,7 @@
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
export const dbSubjects = { export const dbSubjects = {
externalUpdate: new Subject<{ externalUpdate$: new Subject<{
workspaceId: string; workspaceId: string;
update: Uint8Array; update: Uint8Array;
docId?: string; docId?: string;
@@ -66,7 +66,7 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
if (origin === 'renderer') { if (origin === 'renderer') {
await this.addUpdateToSQLite(insertRows); await this.addUpdateToSQLite(insertRows);
} else if (origin === 'external') { } else if (origin === 'external') {
dbSubjects.externalUpdate.next({ dbSubjects.externalUpdate$.next({
workspaceId: this.workspaceId, workspaceId: this.workspaceId,
update, update,
docId, docId,
@@ -97,7 +97,7 @@ export async function storeWorkspaceMeta(
...meta, ...meta,
}; };
await fs.writeJSON(metaPath, newMeta); await fs.writeJSON(metaPath, newMeta);
workspaceSubjects.meta.next({ workspaceSubjects.meta$.next({
workspaceId, workspaceId,
meta: newMeta, meta: newMeta,
}); });
@@ -10,7 +10,7 @@ export const workspaceEvents = {
onMetaChange: ( onMetaChange: (
fn: (meta: { workspaceId: string; meta: WorkspaceMeta }) => void fn: (meta: { workspaceId: string; meta: WorkspaceMeta }) => void
) => { ) => {
const sub = workspaceSubjects.meta.subscribe(fn); const sub = workspaceSubjects.meta$.subscribe(fn);
return () => { return () => {
sub.unsubscribe(); sub.unsubscribe();
}; };
@@ -3,5 +3,5 @@ import { Subject } from 'rxjs';
import type { WorkspaceMeta } from '../type'; import type { WorkspaceMeta } from '../type';
export const workspaceSubjects = { export const workspaceSubjects = {
meta: new Subject<{ workspaceId: string; meta: WorkspaceMeta }>(), meta$: new Subject<{ workspaceId: string; meta: WorkspaceMeta }>(),
}; };
@@ -26,7 +26,7 @@ export function createApplicationMenu() {
label: `About ${app.getName()}`, label: `About ${app.getName()}`,
click: async () => { click: async () => {
await showMainWindow(); await showMainWindow();
applicationMenuSubjects.openAboutPageInSettingModal.next(); applicationMenuSubjects.openAboutPageInSettingModal$.next();
}, },
}, },
{ type: 'separator' }, { type: 'separator' },
@@ -52,7 +52,7 @@ export function createApplicationMenu() {
click: async () => { click: async () => {
await initAndShowMainWindow(); await initAndShowMainWindow();
// fixme: if the window is just created, the new page action will not be triggered // fixme: if the window is just created, the new page action will not be triggered
applicationMenuSubjects.newPageAction.next(); applicationMenuSubjects.newPageAction$.next();
}, },
}, },
{ type: 'separator' }, { type: 'separator' },
@@ -12,14 +12,14 @@ export const applicationMenuEvents = {
* File -> New Doc * File -> New Doc
*/ */
onNewPageAction: (fn: () => void) => { onNewPageAction: (fn: () => void) => {
const sub = applicationMenuSubjects.newPageAction.subscribe(fn); const sub = applicationMenuSubjects.newPageAction$.subscribe(fn);
return () => { return () => {
sub.unsubscribe(); sub.unsubscribe();
}; };
}, },
openAboutPageInSettingModal: (fn: () => void) => { openAboutPageInSettingModal: (fn: () => void) => {
const sub = const sub =
applicationMenuSubjects.openAboutPageInSettingModal.subscribe(fn); applicationMenuSubjects.openAboutPageInSettingModal$.subscribe(fn);
return () => { return () => {
sub.unsubscribe(); sub.unsubscribe();
}; };
@@ -1,6 +1,6 @@
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
export const applicationMenuSubjects = { export const applicationMenuSubjects = {
newPageAction: new Subject<void>(), newPageAction$: new Subject<void>(),
openAboutPageInSettingModal: new Subject<void>(), openAboutPageInSettingModal$: new Subject<void>(),
}; };
@@ -104,7 +104,7 @@ async function createWindow(additionalArguments: string[]) {
logger.info('main window is ready to show'); logger.info('main window is ready to show');
if (browserWindow.isMaximized() || browserWindow.isFullScreen()) { if (browserWindow.isMaximized() || browserWindow.isFullScreen()) {
uiSubjects.onMaximized.next(true); uiSubjects.onMaximized$.next(true);
} }
handleWebContentsResize().catch(logger.error); handleWebContentsResize().catch(logger.error);
@@ -141,20 +141,20 @@ async function createWindow(additionalArguments: string[]) {
browserWindow.setSize(size[0] + 1, size[1] + 1); browserWindow.setSize(size[0] + 1, size[1] + 1);
browserWindow.setSize(size[0], size[1]); browserWindow.setSize(size[0], size[1]);
}); });
uiSubjects.onMaximized.next(false); uiSubjects.onMaximized$.next(false);
}); });
browserWindow.on('maximize', () => { browserWindow.on('maximize', () => {
uiSubjects.onMaximized.next(true); uiSubjects.onMaximized$.next(true);
}); });
// full-screen == maximized in UI on windows // full-screen == maximized in UI on windows
browserWindow.on('enter-full-screen', () => { browserWindow.on('enter-full-screen', () => {
uiSubjects.onMaximized.next(true); uiSubjects.onMaximized$.next(true);
}); });
browserWindow.on('unmaximize', () => { browserWindow.on('unmaximize', () => {
uiSubjects.onMaximized.next(false); uiSubjects.onMaximized$.next(false);
}); });
/** /**
@@ -172,7 +172,7 @@ async function createWindow(additionalArguments: string[]) {
} }
// singleton // singleton
let browserWindow$: Promise<BrowserWindow> | undefined; let browserWindow: Promise<BrowserWindow> | undefined;
// a hidden window that prevents the app from quitting on MacOS // a hidden window that prevents the app from quitting on MacOS
let hiddenMacWindow: BrowserWindow | undefined; let hiddenMacWindow: BrowserWindow | undefined;
@@ -181,11 +181,11 @@ let hiddenMacWindow: BrowserWindow | undefined;
* Init main BrowserWindow. Will create a new window if it's not created yet. * Init main BrowserWindow. Will create a new window if it's not created yet.
*/ */
export async function initAndShowMainWindow() { export async function initAndShowMainWindow() {
if (!browserWindow$ || (await browserWindow$.then(w => w.isDestroyed()))) { if (!browserWindow || (await browserWindow.then(w => w.isDestroyed()))) {
const additionalArguments = await getWindowAdditionalArguments(); const additionalArguments = await getWindowAdditionalArguments();
browserWindow$ = createWindow(additionalArguments); browserWindow = createWindow(additionalArguments);
} }
const mainWindow = await browserWindow$; const mainWindow = await browserWindow;
if (IS_DEV) { if (IS_DEV) {
// do not gain focus in dev mode // do not gain focus in dev mode
@@ -209,8 +209,8 @@ export async function initAndShowMainWindow() {
} }
export async function getMainWindow() { export async function getMainWindow() {
if (!browserWindow$) return; if (!browserWindow) return;
const window = await browserWindow$; const window = await browserWindow;
if (window.isDestroyed()) return; if (window.isDestroyed()) return;
return window; return window;
} }
@@ -251,7 +251,7 @@ export async function setCookie(
arg0: CookiesSetDetails | string, arg0: CookiesSetDetails | string,
arg1?: string arg1?: string
) { ) {
const window = await browserWindow$; const window = await browserWindow;
if (!window) { if (!window) {
// do nothing if window is not ready // do nothing if window is not ready
return; return;
@@ -271,7 +271,7 @@ export async function setCookie(
} }
export async function removeCookie(url: string, name: string): Promise<void> { export async function removeCookie(url: string, name: string): Promise<void> {
const window = await browserWindow$; const window = await browserWindow;
if (!window) { if (!window) {
// do nothing if window is not ready // do nothing if window is not ready
return; return;
@@ -280,7 +280,7 @@ export async function removeCookie(url: string, name: string): Promise<void> {
} }
export async function getCookie(url?: string, name?: string) { export async function getCookie(url?: string, name?: string) {
const window = await browserWindow$; const window = await browserWindow;
if (!window) { if (!window) {
// do nothing if window is not ready // do nothing if window is not ready
return; return;
@@ -96,23 +96,23 @@ async function createOnboardingWindow(additionalArguments: string[]) {
return browserWindow; return browserWindow;
} }
let onBoardingWindow$: Promise<BrowserWindow> | undefined; let onBoardingWindow: Promise<BrowserWindow> | undefined;
export async function getOrCreateOnboardingWindow() { export async function getOrCreateOnboardingWindow() {
const additionalArguments = await getWindowAdditionalArguments(); const additionalArguments = await getWindowAdditionalArguments();
if ( if (
!onBoardingWindow$ || !onBoardingWindow ||
(await onBoardingWindow$.then(w => w.isDestroyed())) (await onBoardingWindow.then(w => w.isDestroyed()))
) { ) {
onBoardingWindow$ = createOnboardingWindow(additionalArguments); onBoardingWindow = createOnboardingWindow(additionalArguments);
} }
return onBoardingWindow$; return onBoardingWindow;
} }
export async function getOnboardingWindow() { export async function getOnboardingWindow() {
if (!onBoardingWindow$) return; if (!onBoardingWindow) return;
const window = await onBoardingWindow$; const window = await onBoardingWindow;
if (window.isDestroyed()) return; if (window.isDestroyed()) return;
return window; return window;
} }
@@ -6,7 +6,7 @@ import { uiSubjects } from './subject';
*/ */
export const uiEvents = { export const uiEvents = {
onMaximized: (fn: (maximized: boolean) => void) => { onMaximized: (fn: (maximized: boolean) => void) => {
const sub = uiSubjects.onMaximized.subscribe(fn); const sub = uiSubjects.onMaximized$.subscribe(fn);
return () => { return () => {
sub.unsubscribe(); sub.unsubscribe();
}; };
@@ -1,5 +1,5 @@
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
export const uiSubjects = { export const uiSubjects = {
onMaximized: new Subject<boolean>(), onMaximized$: new Subject<boolean>(),
}; };
@@ -67,7 +67,7 @@ export const downloadUpdate = async () => {
return; return;
} }
downloading = true; downloading = true;
updaterSubjects.downloadProgress.next(0); updaterSubjects.downloadProgress$.next(0);
autoUpdater.downloadUpdate().catch(e => { autoUpdater.downloadUpdate().catch(e => {
downloading = false; downloading = false;
logger.error('Failed to download update', e); logger.error('Failed to download update', e);
@@ -115,7 +115,7 @@ export const registerUpdater = async () => {
console.error(err); console.error(err);
}); });
} }
updaterSubjects.updateAvailable.next({ updaterSubjects.updateAvailable$.next({
version: info.version, version: info.version,
allowAutoUpdate, allowAutoUpdate,
}); });
@@ -125,11 +125,11 @@ export const registerUpdater = async () => {
}); });
autoUpdater.on('download-progress', e => { autoUpdater.on('download-progress', e => {
logger.info(`Download progress: ${e.percent}`); logger.info(`Download progress: ${e.percent}`);
updaterSubjects.downloadProgress.next(e.percent); updaterSubjects.downloadProgress$.next(e.percent);
}); });
autoUpdater.on('update-downloaded', e => { autoUpdater.on('update-downloaded', e => {
downloading = false; downloading = false;
updaterSubjects.updateReady.next({ updaterSubjects.updateReady$.next({
version: e.version, version: e.version,
allowAutoUpdate, allowAutoUpdate,
}); });
@@ -9,26 +9,26 @@ export interface UpdateMeta {
export const updaterSubjects = { export const updaterSubjects = {
// means it is ready for restart and install the new version // means it is ready for restart and install the new version
updateAvailable: new Subject<UpdateMeta>(), updateAvailable$: new Subject<UpdateMeta>(),
updateReady: new Subject<UpdateMeta>(), updateReady$: new Subject<UpdateMeta>(),
downloadProgress: new BehaviorSubject<number>(0), downloadProgress$: new BehaviorSubject<number>(0),
}; };
export const updaterEvents = { export const updaterEvents = {
onUpdateAvailable: (fn: (versionMeta: UpdateMeta) => void) => { onUpdateAvailable: (fn: (versionMeta: UpdateMeta) => void) => {
const sub = updaterSubjects.updateAvailable.subscribe(fn); const sub = updaterSubjects.updateAvailable$.subscribe(fn);
return () => { return () => {
sub.unsubscribe(); sub.unsubscribe();
}; };
}, },
onUpdateReady: (fn: (versionMeta: UpdateMeta) => void) => { onUpdateReady: (fn: (versionMeta: UpdateMeta) => void) => {
const sub = updaterSubjects.updateReady.subscribe(fn); const sub = updaterSubjects.updateReady$.subscribe(fn);
return () => { return () => {
sub.unsubscribe(); sub.unsubscribe();
}; };
}, },
onDownloadProgress: (fn: (progress: number) => void) => { onDownloadProgress: (fn: (progress: number) => void) => {
const sub = updaterSubjects.downloadProgress.subscribe(fn); const sub = updaterSubjects.downloadProgress$.subscribe(fn);
return () => { return () => {
sub.unsubscribe(); sub.unsubscribe();
}; };
@@ -1,9 +0,0 @@
import type { BrowserWindow } from 'electron';
import type { LaunchStage } from './types';
export const windows$: Record<LaunchStage, Promise<BrowserWindow> | undefined> =
{
main: undefined,
onboarding: undefined,
};
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff. Show more