kakidash 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -92,13 +92,12 @@ pnpm add kakidash
92
92
  pnpm add kakidash
93
93
  ```
94
94
 
95
- ````typescript
95
+ ```typescript
96
96
  import { Kakidash } from 'kakidash';
97
97
 
98
98
  // コンテナ取得
99
99
  const container = document.getElementById('mindmap-container');
100
100
 
101
- // インスタンス化
102
101
  // インスタンス化 (オプション指定可能)
103
102
  const kakidash = new Kakidash(container, {
104
103
  locale: 'ja', // オプション: 'en' | 'ja' (デフォルト: 'en')
@@ -106,11 +105,13 @@ const kakidash = new Kakidash(container, {
106
105
  customStyles: { // オプション: 初期のカスタムスタイル
107
106
  rootNode: { border: '2px solid red' }
108
107
  },
109
- disabledCommandPaletteFeatures: ['import'] // オプション: 特定の機能を無効化
108
+ disabledCommandPaletteFeatures: ['import'], // オプション: 特定の機能を無効化
109
+ //getImage: (ref) => myImageMap[ref] // オプション: 拡大表示用にオリジナル画像を取得するコールバック
110
110
  });
111
111
 
112
112
  // 必要に応じて初期データをロードしたり、ノードを追加したりします
113
113
  kakidash.addNode(kakidash.getRootId(), 'Hello World');
114
+ ```
114
115
 
115
116
  ### 3. テーマの切り替え
116
117
 
@@ -118,9 +119,7 @@ kakidash.addNode(kakidash.getRootId(), 'Hello World');
118
119
 
119
120
  ```typescript
120
121
  kakidash.setTheme('dark'); // 'default', 'simple', 'colorful', 'dark'
121
- ````
122
-
123
- ````
122
+ ```
124
123
 
125
124
  #### B. ブラウザ直接読み込み (Script Tag / CDN)
126
125
 
@@ -209,7 +208,9 @@ kakidash.setTheme('custom');
209
208
  - `options.disabledCommandPaletteFeatures`: コマンドパレットの特定の機能を無効化 ('search' | 'icon' | 'import' | 'export')。
210
209
  - **`kakidash.addNode(parentId, topic)`**: 指定した親ノードに新しい子ノードを追加します。
211
210
  - **`kakidash.getData()`**: 現在のマインドマップデータをJSONオブジェクトとして取得します。
212
- - **`kakidash.loadData(data)`**: JSONデータを読み込み、マインドマップを描画します。
211
+ - **`kakidash.loadData(data)`**: JSONデータを読み込み、マインドマップを描画します。画像埋め込み済みの旧形式データも自動移行して読み込めます。
212
+ - **`kakidash.getImages()`**: 現在メモリ(ImageStore)に保持されているすべてのオリジナル画像を取得します。
213
+ - **`kakidash.gcImages()`**: 使用されていない画像をメモリから削除します。
213
214
  - **`kakidash.updateGlobalStyles(styles)`**: グローバルスタイルを更新します ('custom' テーマ選択時のみ有効)。
214
215
  - **`kakidash.updateLayout(mode)`**: レイアウトモードを変更します ('Standard', 'Left', 'Right')。
215
216
  - **`kakidash.setReadOnly(boolean)`**: 読み取り専用モードを切り替えます。
@@ -391,6 +392,28 @@ board.registerCommand({
391
392
 
392
393
  登録されたコマンドはコマンドパレット(デフォルト:`m` キー)の一覧に表示されます。項目をクリックするか Enter キーを押すことで実行されます。
393
394
 
395
+ ## 画像の管理 (ハイブリッド形式)
396
+
397
+ Kakidashは、大量の画像を効率的に扱うために「ハイブリッド保存方式」を採用しています。
398
+ - **サムネイル**: 軽量な画像データ。マインドマップのJSON内に直接保存されます。
399
+ - **オリジナル画像**: 高解像度の画像データ。JSONからは切り離され、メモリ上(`ImageStore`)で管理されます。これらはホストアプリケーション(VSCode拡張機能など)側でサイドカーフォルダ等に保存する必要があります。
400
+
401
+ ### 実装例: VSCode拡張機能との連携
402
+
403
+ ```typescript
404
+ // 1. 保存時
405
+ const data = kakidash.getData(); // 巨大な画像を含まない軽量なJSON
406
+ const images = kakidash.getImages(); // { 参照ID: Base64データ } のマップ
407
+
408
+ // 'data' を .kaki ファイルに、'images' を 'filename_images/' フォルダ等に保存します
409
+ saveToDisk(data, images);
410
+
411
+ // 2. 読み込み時
412
+ const { data, images } = loadFromDisk();
413
+ kakidash.loadData(data); // 旧形式(画像埋め込み)の場合も自動でマイグレーションされます
414
+ // コンストラクタの getImage コールバックを通じて、読み込んだ画像をライブラリに提供します
415
+ ```
416
+
394
417
  ## アーキテクチャ
395
418
 
396
419
  ソフトウェアアーキテクチャの詳細や内部モジュールの依存関係については、以下を参照してください:
package/README.md CHANGED
@@ -90,13 +90,12 @@ Prepare a container element (e.g., `div`) to display `kakidash`.
90
90
  pnpm add kakidash
91
91
  ```
92
92
 
93
- ````typescript
93
+ ```typescript
94
94
  import { Kakidash } from 'kakidash';
95
95
 
96
96
  // Get container
97
97
  const container = document.getElementById('mindmap-container');
98
98
 
99
- // Instantiate
100
99
  // Instantiate with optional configuration
101
100
  const kakidash = new Kakidash(container, {
102
101
  locale: 'en', // Optional: 'en' | 'ja' (Default: 'en')
@@ -104,11 +103,13 @@ const kakidash = new Kakidash(container, {
104
103
  customStyles: { // Optional: Initial custom styles
105
104
  rootNode: { border: '2px solid red' }
106
105
  },
107
- disabledCommandPaletteFeatures: ['import'] // Optional: Disable specific features
106
+ disabledCommandPaletteFeatures: ['import'], // Optional: Disable specific features
107
+ //getImage: (ref) => myImageMap[ref] // Optional: Callback to fetch original images for zooming
108
108
  });
109
109
 
110
110
  // Add initial data or nodes if needed
111
111
  kakidash.addNode(kakidash.getRootId(), 'Hello World');
112
+ ```
112
113
 
113
114
  ### 3. Theme Selection
114
115
 
@@ -116,9 +117,7 @@ You can switch themes dynamically:
116
117
 
117
118
  ```typescript
118
119
  kakidash.setTheme('dark'); // 'default', 'simple', 'colorful', 'dark'
119
- ````
120
-
121
- ````
120
+ ```
122
121
 
123
122
  #### B. Browser Direct Import (Script Tag / CDN)
124
123
 
@@ -204,7 +203,9 @@ All values accept standard CSS strings.
204
203
  - `options.disabledCommandPaletteFeatures`: Features to disable in the command palette ('search' | 'icon' | 'import' | 'export').
205
204
  - **`kakidash.addNode(parentId, topic)`**: Adds a new child node to the specified parent node.
206
205
  - **`kakidash.getData()`**: Retrieves current mindmap data as a JSON object.
207
- - **`kakidash.loadData(data)`**: Loads JSON data and renders the mindmap.
206
+ - **`kakidash.loadData(data)`**: Loads JSON data and renders the mindmap. Supports legacy format with embedded images.
207
+ - **`kakidash.getImages()`**: Retrieves all original high-resolution images currently in memory.
208
+ - **`kakidash.gcImages()`**: Cleans up unused images from memory.
208
209
  - **`kakidash.updateGlobalStyles(styles)`**: Updates global styles (only active when theme is 'custom').
209
210
  - **`kakidash.updateLayout(mode)`**: Changes layout mode ('Standard', 'Left', 'Right').
210
211
  - **`kakidash.setReadOnly(boolean)`**: Toggles read-only mode.
@@ -386,6 +387,28 @@ board.registerCommand({
386
387
 
387
388
  Registered commands will appear in the Command Palette (default key: `m`). Clicking or pressing Enter on the item will execute the handler.
388
389
 
390
+ ## Image Management (Hybrid Storage)
391
+
392
+ Kakidash uses a hybrid storage approach to handle large images efficiently.
393
+ - **Thumbnails**: Small, low-resolution versions are stored directly in the JSON data.
394
+ - **Originals**: High-resolution versions are kept in a separate memory store (`ImageStore`) and should be persisted by the host application (e.g., in a sidecar folder).
395
+
396
+ ### Example: VSCode Extension Integration
397
+
398
+ ```typescript
399
+ // 1. Saving
400
+ const data = kakidash.getData(); // JSON without large images
401
+ const images = kakidash.getImages(); // Map of { imageRef: base64Data }
402
+
403
+ // Save 'data' to .kaki file and 'images' to .kaki_images/ folder
404
+ saveToDisk(data, images);
405
+
406
+ // 2. Loading
407
+ const { data, images } = loadFromDisk();
408
+ kakidash.loadData(data); // Library handles migration if old format
409
+ // Images should be provided via the getImage callback in constructor
410
+ ```
411
+
389
412
  ## Architecture
390
413
 
391
414
  For details on the software architecture and internal module dependencies, please refer to:
package/dist/index.d.ts CHANGED
@@ -137,6 +137,8 @@ declare interface ControllerDependencies {
137
137
  fileIOService: FileIOService;
138
138
  themeService: ThemeService;
139
139
  commandBus: CommandBus;
140
+ imageStore: ImageStore;
141
+ imageProcessingService: ImageProcessingService;
140
142
  locale?: 'en' | 'ja';
141
143
  commandPaletteFeatures?: ('search' | 'icon' | 'import' | 'export')[];
142
144
  }
@@ -204,6 +206,54 @@ declare interface IdGenerator {
204
206
  generate(): string;
205
207
  }
206
208
 
209
+ declare class ImageProcessingService {
210
+ /**
211
+ * Generates a lightweight thumbnail from a base64 image string.
212
+ * Uses HTML Canvas API.
213
+ * @param base64Data The original base64 image data
214
+ * @param maxWidth The maximum width for the thumbnail (default 200)
215
+ * @param quality Image quality for lossy formats (0 to 1, default 0.7)
216
+ */
217
+ generateThumbnail(base64Data: string, maxWidth?: number, quality?: number): Promise<ThumbnailResult>;
218
+ /**
219
+ * Calculates proportional dimensions preserving aspect ratio, constrained by maxWidth.
220
+ */
221
+ calculateDimensions(originalWidth: number, originalHeight: number, maxWidth: number): {
222
+ width: number;
223
+ height: number;
224
+ };
225
+ }
226
+
227
+ declare class ImageStore {
228
+ private images;
229
+ /**
230
+ * Adds an image to the store.
231
+ * @param ref The unique reference or filename of the image
232
+ * @param base64Data The original base64 image data
233
+ */
234
+ addImage(ref: string, base64Data: string): void;
235
+ /**
236
+ * Retrieves an image from the store.
237
+ * @param ref The unique reference or filename of the image
238
+ * @returns The base64 image data, or undefined if not found
239
+ */
240
+ getImage(ref: string): string | undefined;
241
+ /**
242
+ * Removes an image from the store.
243
+ * @param ref The unique reference or filename of the image
244
+ */
245
+ removeImage(ref: string): void;
246
+ /**
247
+ * Retrieves all image references currently in the store.
248
+ * @returns Array of references
249
+ */
250
+ getAllRefs(): string[];
251
+ /**
252
+ * Clears all images from the store.
253
+ */
254
+ clear(): void;
255
+ }
256
+
207
257
  declare interface IMindMapEventBus {
208
258
  emit<K extends keyof KakidashEventMap>(event: K, payload: KakidashEventMap[K]): void;
209
259
  on<K extends keyof KakidashEventMap>(event: K, handler: (payload: KakidashEventMap[K]) => void): void;
@@ -349,6 +399,7 @@ export declare class Kakidash extends TypedEventEmitter<KakidashEventMap> {
349
399
  panBoard(dx: number, dy: number): void;
350
400
  zoomBoard(delta: number, clientX: number, clientY: number): void;
351
401
  resetZoom(): void;
402
+ zoomNode(nodeId: string): void;
352
403
  copyNode(nodeId: string): void;
353
404
  pasteNode(parentId: string): void;
354
405
  pasteImage(parentId: string, imageData: string, width?: number, height?: number): void;
@@ -358,6 +409,8 @@ export declare class Kakidash extends TypedEventEmitter<KakidashEventMap> {
358
409
  getLayoutMode(): LayoutMode;
359
410
  navigateNode(nodeId: string, direction: Direction): void;
360
411
  getData(): MindMapData;
412
+ getImages(): Record<string, string>;
413
+ gcImages(): void;
361
414
  loadData(data: MindMapData): void;
362
415
  getRootId(): string;
363
416
  }
@@ -526,6 +579,8 @@ declare class MindMapController {
526
579
  private searchService;
527
580
  private viewportService;
528
581
  private navigationService;
582
+ private imageStore;
583
+ private imageProcessingService;
529
584
  private anchorNodeId;
530
585
  private selectedNodeId;
531
586
  private selectedNodeIds;
@@ -541,6 +596,9 @@ declare class MindMapController {
541
596
  init(containerWidth: number, containerHeight: number): void;
542
597
  destroy(): void;
543
598
  getData(): MindMapData;
599
+ getImages(): Record<string, string>;
600
+ getImage(ref: string): string | undefined;
601
+ gcImages(): void;
544
602
  loadData(data: MindMapData): void;
545
603
  batch(callback: () => void): void;
546
604
  getSelectedNodeId(): string | null;
@@ -579,6 +637,7 @@ declare class MindMapController {
579
637
  * Fast path: update selection styles without full DOM rebuild.
580
638
  */
581
639
  private renderSelection;
640
+ zoomNode(nodeId: string): void;
582
641
  setLayoutMode(mode: LayoutMode): void;
583
642
  getLayoutMode(): LayoutMode;
584
643
  setMaxNodeWidth(width: number): void;
@@ -637,6 +696,8 @@ export declare interface MindMapNodeData {
637
696
  root?: boolean;
638
697
  isFolded?: boolean;
639
698
  parentId?: string;
699
+ thumbnail?: string;
700
+ imageRef?: string;
640
701
  image?: string;
641
702
  imageSize?: {
642
703
  width: number;
@@ -652,7 +713,7 @@ declare class MindMapService {
652
713
  private idGenerator;
653
714
  constructor(mindMap: MindMap, idGenerator: IdGenerator);
654
715
  addNode(parentId: string, topic?: string, layoutSide?: 'left' | 'right'): Node_2 | null;
655
- addImageNode(parentId: string, imageData: string, width?: number, height?: number): Node_2 | null;
716
+ addImageNode(parentId: string, thumbnail: string, imageRef: string, width?: number, height?: number): Node_2 | null;
656
717
  removeNode(id: string): boolean;
657
718
  removeNodes(ids: string[]): boolean;
658
719
  updateNodeTopic(id: string, topic: string): boolean;
@@ -726,6 +787,8 @@ declare class Node_2 {
726
787
  style: NodeStyle;
727
788
  parentId: string | null;
728
789
  isRoot: boolean;
790
+ thumbnail?: string;
791
+ imageRef?: string;
729
792
  image?: string;
730
793
  imageSize?: {
731
794
  width: number;
@@ -733,10 +796,11 @@ declare class Node_2 {
733
796
  };
734
797
  icon?: string;
735
798
  presentation: NodePresentationData;
736
- constructor(id: string, topic: string, parentId?: string | null, isRoot?: boolean, image?: string, layoutSide?: 'left' | 'right', isFolded?: boolean, icon?: string, imageSize?: {
799
+ constructor(id: string, topic: string, parentId?: string | null, isRoot?: boolean, image?: string, // @deprecated
800
+ layoutSide?: 'left' | 'right', isFolded?: boolean, icon?: string, imageSize?: {
737
801
  width: number;
738
802
  height: number;
739
- }, customWidth?: number);
803
+ }, customWidth?: number, thumbnail?: string, imageRef?: string);
740
804
  addChild(node: Node_2): void;
741
805
  insertChild(node: Node_2, index: number): void;
742
806
  removeChild(nodeId: string): void;
@@ -780,6 +844,7 @@ declare interface Renderer {
780
844
  };
781
845
  updateSelection(selectedNodeIds: Set<string>): void;
782
846
  getNodeElement(nodeId: string): HTMLElement | undefined;
847
+ zoomNode(nodeId: string): void;
783
848
  }
784
849
 
785
850
  declare class SearchService {
@@ -927,6 +992,12 @@ declare interface ThemeServiceDependencies {
927
992
  eventBus: IMindMapEventBus;
928
993
  }
929
994
 
995
+ declare interface ThumbnailResult {
996
+ thumbnailBase64: string;
997
+ width: number;
998
+ height: number;
999
+ }
1000
+
930
1001
  export declare class TypedEventEmitter<EventMap extends Record<string, any>> {
931
1002
  private listeners;
932
1003
  on<K extends keyof EventMap>(event: K, listener: (payload: EventMap[K]) => void): void;