Skip to content

Commit

Permalink
✨ feat: 持久化状态
Browse files Browse the repository at this point in the history
  • Loading branch information
frostime committed Dec 3, 2024
1 parent aae65e2 commit 2405507
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 71 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sy-query-view",
"version": "1.0.0-beta1",
"version": "1.0.0-beta2",
"type": "module",
"description": "This is a sample plugin based on vite and svelte for Siyuan (https://b3log.org/siyuan). Created with siyuan-plugin-cli v2.4.5.",
"repository": "https://github.com/frostime/sy-query-view",
Expand All @@ -12,7 +12,7 @@
"build": "npm run export-types && cross-env NODE_ENV=production vite build && npm run replace-md-file ./dist",
"make-link": "npx make-link-win",
"update-version": "npx update-version",
"make-install": "vite build && npx make-installclear",
"make-install": "vite build && npx make-install",
"auto-i18n": "i18n extract && i18n translate && i18n export",
"export-types": "node scripts/export-types.js",
"replace-md-file": "node scripts/replace-md-file.js"
Expand Down
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "sy-query-view",
"author": "frostime",
"url": "https://github.com/frostime/sy-query-view",
"version": "1.0.0-beta1",
"version": "1.0.0-beta2",
"minAppVersion": "3.0.12",
"backends": [
"windows",
Expand Down
36 changes: 25 additions & 11 deletions public/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ declare module 'siyuan' {

}

// ================== query.d.ts ==================
import { IProtyle } from "siyuan";
declare const Query: {
/**
Expand Down Expand Up @@ -171,6 +172,7 @@ declare const Query: {
}) => Promise<IWrappedList<IWrappedBlock>>;
};

// ================== data-view.d.ts ==================
/**
* List Options
* @interface IListOptions
Expand Down Expand Up @@ -285,7 +287,15 @@ interface IUserCustom {
[key: string]: ICustomView;
}

interface IState<T> {
(): T;
(value: T): T;

value: T;
}


// ================== data-view.d.ts ==================
/**
* DataView class for creating and managing dynamic data visualizations
* Provides various methods for rendering data in different formats including:
Expand All @@ -297,19 +307,21 @@ interface IUserCustom {
* - Mermaid diagrams
*/
export declare class DataView implements IDataView {
/**
* 注册组件 View
* @param method: `(...args: any[]) => HTMLElement`, 一个返回 HTMLElement 的方法
* @param options: 其他配置
* - aliases: 组件的别名
*/
register(method: (...args: any[]) => HTMLElement, options?: {
name?: string;
aliases?: string[];
}): void;
constructor(protyle: IProtyle, embedNode: HTMLElement, top: number | null);
get element(): HTMLElement;
dispose(): void;
/**
* Persist state across renders; it will store the state in the block attributes when disposing, and restore it when creating.
* @param key - The key of the state
* @param initialValue - The initial value of the state
* @returns An IState object -- see {@link IState}
* @example
* const count = dv.useState('count', 0);
* count(); // Access the value
* count.value; // Access the value, same as count()
* count(1); // Set the value
* count.value = 1; // Set the value, same as count(1)
*/
useState<T>(key: string, initialValue?: T): IState<T>;
/**
* Register a disposer function to be called when the DataView is disposed.
* Only when you need to add some extra cleanup logic, you should use this method.
Expand Down Expand Up @@ -575,6 +587,7 @@ export declare class DataView implements IDataView {

export declare const PROHIBIT_METHOD_NAMES: string[];

// ================== proxy.d.ts ==================
/** Wrapped Block interface with extended convenient properties and methods */
export interface IWrappedBlock extends Block {
/** Method to return the original Block object */
Expand Down Expand Up @@ -650,6 +663,7 @@ export interface IWrappedList<T> extends Array<T> {
filter(predicate: (value: T, index: number, array: T[]) => boolean): IWrappedList<T>;
}

// ================== index.d.ts ==================
/*
* Copyright (c) 2024 by frostime. All Rights Reserved.
* @Author : frostime
Expand Down
49 changes: 33 additions & 16 deletions scripts/export-types.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { glob } from 'glob';
import { exec, execSync } from 'child_process';
// import { glob } from 'glob';
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';

import { fileURLToPath } from 'url';
import process from 'process';

//定位当前 js 脚本所在的目录
// First define __filename and __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// chdir 到上层 plugin.json 所在的目录
// Then use process
process.chdir(path.join(__dirname, '..'));
console.log(process.cwd());
const dirname = process.cwd();
Expand All @@ -33,15 +33,8 @@ execSync(tsc);
const fileWriter = (filepath) => {
let fd = fs.openSync(filepath, 'w');

const process = (content) => {
content = content.replaceAll(`import("./proxy").`, '');
content = content.replaceAll(`import("@/core/query").default;`, 'Query');
return content;
}

return {
append: (content) => {
content = process(content);
fs.writeSync(fd, content);
},
close: () => {
Expand All @@ -62,6 +55,11 @@ const removeLine = (content, line) => {
}

const writer = fileWriter(path.join(outputDir, 'types.d.ts'));
const replaceSomething = (content) => {
content = content.replaceAll(`import("./proxy").`, '');
content = content.replaceAll(`import("@/core/query").default;`, 'Query');
return content;
}

writer.append(`
declare module 'siyuan' {
Expand All @@ -72,24 +70,42 @@ declare module 'siyuan' {
`.trimStart());

const query = readFile('./types/core/query.d.ts');
writer.append('// ================== query.d.ts ==================\n');
let query = readFile('./types/core/query.d.ts');
query = replaceSomething(query);
writer.append(query);
writer.append('\n');

const dataviewdts = readFile('./src/types/data-view.d.ts');
writer.append('// ================== data-view.d.ts ==================\n');
let dataviewdts = readFile('./src/types/data-view.d.ts');
dataviewdts = replaceSomething(dataviewdts);
writer.append(dataviewdts);
writer.append('\n');

writer.append('// ================== data-view.d.ts ==================\n');
let dataview = readFile('./types/core/data-view.d.ts');
dataview = removeLine(dataview, 'import { IProtyle } from "siyuan";');
dataview = replaceSomething(dataview);
dataview = dataview.replaceAll('DataView extends UseStateMixin', 'DataView');
writer.append(dataview);
writer.append('\n');

const proxy = readFile('./types/core/proxy.d.ts');
// writer.append('// ================== use-state.d.ts ==================\n');
// let useState = readFile('./types/core/use-state.d.ts');
// useState = removeLine(useState, 'import { IProtyle } from "siyuan";');
// useState = replaceSomething(useState);
// writer.append(useState);
// writer.append('\n');

writer.append('// ================== proxy.d.ts ==================\n');
let proxy = readFile('./types/core/proxy.d.ts');
proxy = replaceSomething(proxy);
writer.append(proxy);
writer.append('\n');

const indexdts = readFile('./src/types/index.d.ts');
writer.append('// ================== index.d.ts ==================\n');
let indexdts = readFile('./src/types/index.d.ts');
indexdts = replaceSomething(indexdts);
writer.append(indexdts);
writer.append('\n');

Expand All @@ -108,6 +124,7 @@ let content = fs.readFileSync(path.join(outputDir, 'types.d.ts'), 'utf8');
const ERASED_LINES = [
'import { DataView } from "./data-view";',
'export default Query;',
'import UseStateMixin from "./use-state";',
];

for (const line of ERASED_LINES) {
Expand Down
115 changes: 74 additions & 41 deletions src/core/data-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @Author : frostime
* @Date : 2024-12-02 10:15:04
* @FilePath : /src/core/data-view.ts
* @LastEditTime : 2024-12-03 15:19:13
* @LastEditTime : 2024-12-03 20:41:00
* @Description :
*/
import {
Expand All @@ -16,6 +16,7 @@ import { BlockList, BlockTable, MermaidRelation, EmbedNodes, Echarts, MermaidBas
import { registerProtyleGC } from "./gc";
import { openBlock } from "@/utils";
import { getCustomView } from "./custom-view";
import UseStateMixin from "./use-state";

const getCSSVar = (name: string) => getComputedStyle(document.documentElement).getPropertyValue(name);

Expand Down Expand Up @@ -64,7 +65,7 @@ const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
* - Block embeddings
* - Mermaid diagrams
*/
export class DataView implements IDataView {
export class DataView extends UseStateMixin implements IDataView {
/** @internal */
private protyle: IProtyle;

Expand Down Expand Up @@ -99,12 +100,13 @@ export class DataView implements IDataView {
private disposed = false;

/**
* @internal
* 注册组件 View
* @param method: `(...args: any[]) => HTMLElement`, 一个返回 HTMLElement 的方法
* @param options: 其他配置
* - aliases: 组件的别名
*/
register(
private register(
method: (...args: any[]) => HTMLElement,
options: {
name?: string,
Expand Down Expand Up @@ -156,40 +158,8 @@ export class DataView implements IDataView {
});
}

constructor(protyle: IProtyle, embedNode: HTMLElement, top: number | null) {
this.protyle = protyle;
this.thisEmbedNode = embedNode;
this.top = top;
this._element = document.createElement("div");

this._element.classList.add('data-query-embed');
Object.assign(this._element.style, {
'cursor': 'default',
});
this.thisEmbedNode.lastElementChild.insertAdjacentElement("beforebegin", this._element);
this.lute = getLute();

this.ROOT_ID = this.protyle.block.rootID;
this.EMBED_BLOCK_ID = embedNode.dataset.nodeId;

this.register(this.markdown, { aliases: ['md'] });
this.register(this.details, { aliases: ['Details', 'Detail'] });
this.register(this.list, { aliases: ['BlockList'] });
this.register(this.table, { aliases: ['BlockTable'] });
// this.register(this.blockTable);
this.register(this.columns, { aliases: ['Cols'] });
this.register(this.rows);
this.register(this.mermaid, { aliases: ['Mermaid'] });
this.register(this.mermaidRelation);
this.register(this.flowchart, { aliases: ['Flowchart'] });
this.register(this.mindmap, { aliases: ['Mindmap'] });
this.register(this.embed);
this.register(this.echarts);
this.register(this.echartsLine, { aliases: ['Line'] });
this.register(this.echartsBar, { aliases: ['Bar'] });
this.register(this.echartsTree, { aliases: ['Tree'] });
this.register(this.echartsGraph, { aliases: ['Graph'] });

/** @internal */
private registerCustomViews() {
const customView = getCustomView();
if (!customView) return;

Expand All @@ -203,9 +173,9 @@ export class DataView implements IDataView {
} else {
const ans = render(container, ...args);
if (ans) {
//原则上不支持 Promise,但为了兼容性,还是做了处理
//原则上不支持 Promise 返回,但为了兼容性,还是做了处理
if (ans instanceof Promise) {
ans.then(ele => container.append(ele)).catch(err => {
ans.then(ele => ele && container.append(ele)).catch(err => {
const span = document.createElement('span');
errorMessage(span, err.message);
container.append(span);
Expand All @@ -221,6 +191,7 @@ export class DataView implements IDataView {
return container;
}
}

Object.entries(customView).forEach(([key, value]) => {
const name = key;
const { use, alias } = value;
Expand All @@ -233,8 +204,46 @@ export class DataView implements IDataView {
});
}

get element() {
return this._element;
constructor(protyle: IProtyle, embedNode: HTMLElement, top: number | null) {
super(embedNode);

this.protyle = protyle;
this.thisEmbedNode = embedNode;
this.top = top;
this._element = document.createElement("div");

this._element.classList.add('data-query-embed');
Object.assign(this._element.style, {
'cursor': 'default',
});
this.thisEmbedNode.lastElementChild.insertAdjacentElement("beforebegin", this._element);
this.lute = getLute();

this.ROOT_ID = this.protyle.block.rootID;
this.EMBED_BLOCK_ID = embedNode.dataset.nodeId;

this.register(this.markdown, { aliases: ['md'] });
this.register(this.details, { aliases: ['Details', 'Detail'] });
this.register(this.list, { aliases: ['BlockList'] });
this.register(this.table, { aliases: ['BlockTable'] });
// this.register(this.blockTable);
this.register(this.columns, { aliases: ['Cols'] });
this.register(this.rows);
this.register(this.mermaid, { aliases: ['Mermaid'] });
this.register(this.mermaidRelation);
this.register(this.flowchart, { aliases: ['Flowchart'] });
this.register(this.mindmap, { aliases: ['Mindmap'] });
this.register(this.embed);
this.register(this.echarts);
this.register(this.echartsLine, { aliases: ['Line'] });
this.register(this.echartsBar, { aliases: ['Bar'] });
this.register(this.echartsTree, { aliases: ['Tree'] });
this.register(this.echartsGraph, { aliases: ['Graph'] });

this.registerCustomViews();

this.restoreState(); // 从块属性中恢复 state
this.disposers.push(() => this.storeState()); // 在 DataView 销毁时,将 state 同步到块属性中
}

dispose() {
Expand All @@ -251,6 +260,30 @@ export class DataView implements IDataView {
}
}

repaint() {
const button = this.thisEmbedNode.querySelector('div.protyle-icons > span.protyle-action__reload');
if (button) {
this.dispose();
(button as HTMLButtonElement).click();
}
}

/**
* Persist state across renders; it will store the state in the block attributes when disposing, and restore it when creating.
* @param key - The key of the state
* @param initialValue - The initial value of the state
* @returns An IState object -- see {@link IState}
* @example
* const count = dv.useState('count', 0);
* count(); // Access the value
* count.value; // Access the value, same as count()
* count(1); // Set the value
* count.value = 1; // Set the value, same as count(1)
*/
useState<T>(key: string, initialValue?: T): IState<T> {
return super.useState(key, initialValue);
}

/** @internal */
private cleanup() {
// 清理所有引用
Expand Down
Loading

0 comments on commit 2405507

Please sign in to comment.