+
SpiralDB
+
Deine Zitate als Pixel-Spirale
+
+
+
+
+
+
+ {#if spiralStore.stats}
+
+ Statistiken
+
+
+ {spiralStore.stats.imageSize}x{spiralStore.stats.imageSize}
+ Bildgröße
+
+
+ {spiralStore.stats.activeRecords}
+ Zitate
+
+
+ {spiralStore.stats.usedPixels}
+ Pixel belegt
+
+
+ {spiralStore.stats.compressionRatio}%
+ Kompression vs JSON
+
+
+
+
+
+ {#each Object.entries(COLORS) as [idx, color]}
+
+
+ {color.name}
+ {color.bits.join('')}
+
+ {/each}
+
+
+ {/if}
+
+
+
+
+ Gespeicherte Zitate
+ {#if spiralStore.records.length > 0}
+ {spiralStore.records.length}
+ {/if}
+
+
+ {#if spiralStore.records.length === 0}
+ Noch keine Zitate in der Spirale.
+ {:else}
+
+ {#each spiralStore.records as record}
+ {@const cat = spiralStore.getCategoryName(record.data.category)}
+
+
+
{record.data.text}
+
+
+ {/each}
+
+ {/if}
+
+
+
+
+ Aktionen
+
+ spiralStore.downloadPng()}
+ disabled={!spiralStore.stats || spiralStore.stats.totalRecords === 0}
+ >
+ PNG herunterladen
+
+ PNG importieren
+
+ Favoriten neu importieren ({favoritesStore.favorites.length})
+
+ spiralStore.clear()}
+ disabled={!spiralStore.stats || spiralStore.stats.totalRecords === 0}
+ >
+ Alles löschen
+
+
+
+
+
+ SpiralDB kodiert deine Zitate als farbige Pixel in einem Spiralmuster. Jedes
+ Pixel speichert 3 Bit (8 Farben). Das Bild wächst von der Mitte nach außen, je mehr
+ Zitate du sammelst.
+
+
+
+
+
+
+
+
+
diff --git a/packages/spiral-db/src/index.ts b/packages/spiral-db/src/index.ts
index 0146f8769..df7c2f598 100644
--- a/packages/spiral-db/src/index.ts
+++ b/packages/spiral-db/src/index.ts
@@ -92,6 +92,7 @@ export {
// Schema utilities
export {
createTodoSchema,
+ createQuoteSchema,
encodeSchema,
decodeSchema,
getSchemaPixelCount,
diff --git a/packages/spiral-db/src/schema.test.ts b/packages/spiral-db/src/schema.test.ts
index 18c1288fc..a49bcc819 100644
--- a/packages/spiral-db/src/schema.test.ts
+++ b/packages/spiral-db/src/schema.test.ts
@@ -8,6 +8,7 @@ import {
decodeSchema,
getSchemaPixelCount,
createTodoSchema,
+ createQuoteSchema,
validateRecord,
getFieldNames,
} from './schema.js';
@@ -193,6 +194,52 @@ describe('validateRecord', () => {
});
});
+describe('Quote Schema', () => {
+ it('should create quote schema with correct fields', () => {
+ const schema = createQuoteSchema();
+ expect(schema.name).toBe('quote');
+ expect(schema.version).toBe(1);
+ expect(schema.fields).toHaveLength(8);
+ expect(schema.fields.map((f) => f.name)).toEqual([
+ 'id',
+ 'status',
+ 'category',
+ 'language',
+ 'createdAt',
+ 'quoteId',
+ 'author',
+ 'text',
+ ]);
+ });
+
+ it('should round-trip quote schema encode/decode', () => {
+ const schema = createQuoteSchema();
+ const pixels = encodeSchema(schema);
+ const names = getFieldNames(schema);
+ const decoded = decodeSchema(pixels, names);
+ expect(decoded.fields.length).toBe(schema.fields.length);
+ for (let i = 0; i < schema.fields.length; i++) {
+ expect(decoded.fields[i].type).toBe(schema.fields[i].type);
+ expect(decoded.fields[i].maxLength).toBe(schema.fields[i].maxLength);
+ }
+ });
+
+ it('should validate a valid quote record', () => {
+ const schema = createQuoteSchema();
+ const result = validateRecord(schema, {
+ id: 0,
+ status: 0,
+ category: 3,
+ language: 1,
+ createdAt: new Date(),
+ quoteId: 'q-123',
+ author: 'Goethe',
+ text: 'Ein kluges Wort',
+ });
+ expect(result.valid).toBe(true);
+ });
+});
+
describe('getFieldNames', () => {
it('should return field names in order', () => {
const schema = createTodoSchema();
diff --git a/packages/spiral-db/src/schema.ts b/packages/spiral-db/src/schema.ts
index f59a99bb2..49c9bfcd2 100644
--- a/packages/spiral-db/src/schema.ts
+++ b/packages/spiral-db/src/schema.ts
@@ -110,6 +110,26 @@ export function createTodoSchema(): SchemaDefinition {
};
}
+/**
+ * Create a schema for Quote items (Zitare app)
+ */
+export function createQuoteSchema(): SchemaDefinition {
+ return {
+ version: 1,
+ name: 'quote',
+ fields: [
+ { name: 'id', type: 'int', maxLength: 12 }, // 0-4095
+ { name: 'status', type: 'int', maxLength: 3 }, // 0=active, 2=favorited, 4=removed
+ { name: 'category', type: 'int', maxLength: 4 }, // 10 categories (0-15)
+ { name: 'language', type: 'int', maxLength: 3 }, // 6 languages (0-7)
+ { name: 'createdAt', type: 'timestamp', maxLength: 24 },
+ { name: 'quoteId', type: 'string', maxLength: 100 }, // Reference to content package
+ { name: 'author', type: 'string', maxLength: 100 },
+ { name: 'text', type: 'string', maxLength: 255 },
+ ],
+ };
+}
+
/**
* Validate that a record matches a schema
*/