forked from trent_larson/crowd-funder-for-time-pwa
Merge remote-tracking branch 'refs/remotes/origin/sql-absurd-sql' into sql-absurd-sql
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
description:
|
description:
|
||||||
globs:
|
globs:
|
||||||
alwaysApply: true
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
# Camera Implementation Documentation
|
# Camera Implementation Documentation
|
||||||
|
|
||||||
|
|||||||
@@ -241,7 +241,9 @@ docker run -d \
|
|||||||
1. Build the electron app in production mode:
|
1. Build the electron app in production mode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build:electron-prod
|
npm run build:web
|
||||||
|
npm run electron:build
|
||||||
|
npm run build:electron-mac
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Package the Electron app for macOS:
|
2. Package the Electron app for macOS:
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export default class DataExportSection extends Vue {
|
|||||||
transform: (table, value, key) => {
|
transform: (table, value, key) => {
|
||||||
if (table === "contacts") {
|
if (table === "contacts") {
|
||||||
// Dexie inserts a number 0 when some are undefined, so we need to totally remove them.
|
// Dexie inserts a number 0 when some are undefined, so we need to totally remove them.
|
||||||
Object.keys(value).forEach(prop => {
|
Object.keys(value).forEach((prop) => {
|
||||||
if (value[prop] === undefined) {
|
if (value[prop] === undefined) {
|
||||||
delete value[prop];
|
delete value[prop];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import migrationService from '../services/migrationService';
|
import migrationService from "../services/migrationService";
|
||||||
import type { QueryExecResult } from '../services/migrationService';
|
import type { QueryExecResult } from "../services/migrationService";
|
||||||
|
|
||||||
// Each migration can include multiple SQL statements (with semicolons)
|
// Each migration can include multiple SQL statements (with semicolons)
|
||||||
const MIGRATIONS = [
|
const MIGRATIONS = [
|
||||||
{
|
{
|
||||||
name: '001_initial',
|
name: "001_initial",
|
||||||
// see ../db/tables files for explanations of the fields
|
// see ../db/tables files for explanations of the fields
|
||||||
sql: `
|
sql: `
|
||||||
CREATE TABLE IF NOT EXISTS accounts (
|
CREATE TABLE IF NOT EXISTS accounts (
|
||||||
@@ -84,8 +84,8 @@ const MIGRATIONS = [
|
|||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
blobB64 TEXT
|
blobB64 TEXT
|
||||||
);
|
);
|
||||||
`
|
`,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function registerMigrations(): Promise<void> {
|
export async function registerMigrations(): Promise<void> {
|
||||||
@@ -96,8 +96,8 @@ export async function registerMigrations(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function runMigrations(
|
export async function runMigrations(
|
||||||
sqlExec: (sql: string, params?: any[]) => Promise<Array<QueryExecResult>>
|
sqlExec: (sql: string, params?: any[]) => Promise<Array<QueryExecResult>>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await registerMigrations();
|
await registerMigrations();
|
||||||
await migrationService.runMigrations(sqlExec);
|
await migrationService.runMigrations(sqlExec);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,10 @@ db.on("populate", async () => {
|
|||||||
try {
|
try {
|
||||||
await db.settings.add(DEFAULT_SETTINGS);
|
await db.settings.add(DEFAULT_SETTINGS);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error populating the database with default settings:", error);
|
console.error(
|
||||||
|
"Error populating the database with default settings:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -105,7 +108,7 @@ async function safeOpenDatabase(retries = 1, delay = 500): Promise<void> {
|
|||||||
|
|
||||||
// Create a promise that rejects after 5 seconds
|
// Create a promise that rejects after 5 seconds
|
||||||
const timeoutPromise = new Promise((_, reject) => {
|
const timeoutPromise = new Promise((_, reject) => {
|
||||||
setTimeout(() => reject(new Error('Database open timed out')), 500);
|
setTimeout(() => reject(new Error("Database open timed out")), 500);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Race between the open operation and the timeout
|
// Race between the open operation and the timeout
|
||||||
@@ -123,7 +126,7 @@ async function safeOpenDatabase(retries = 1, delay = 500): Promise<void> {
|
|||||||
console.error(`Attempt ${i + 1}: Database open failed:`, error);
|
console.error(`Attempt ${i + 1}: Database open failed:`, error);
|
||||||
if (i < retries - 1) {
|
if (i < retries - 1) {
|
||||||
console.log(`Attempt ${i + 1}: Waiting ${delay}ms before retry...`);
|
console.log(`Attempt ${i + 1}: Waiting ${delay}ms before retry...`);
|
||||||
await new Promise(resolve => setTimeout(resolve, delay));
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -144,18 +147,24 @@ export async function updateDefaultSettings(
|
|||||||
// console.log("Database version:", db.verno);
|
// console.log("Database version:", db.verno);
|
||||||
await safeOpenDatabase();
|
await safeOpenDatabase();
|
||||||
} catch (openError: unknown) {
|
} catch (openError: unknown) {
|
||||||
console.error("Failed to open database:", openError);
|
console.error("Failed to open database:", openError, String(openError));
|
||||||
const errorMessage = openError instanceof Error ? openError.message : String(openError);
|
throw new Error(
|
||||||
throw new Error(`Database connection failed: ${errorMessage}. Please try again or restart the app.`);
|
`The database connection failed. We recommend you try again or restart the app.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const result = await db.settings.update(MASTER_SETTINGS_KEY, settingsChanges);
|
const result = await db.settings.update(
|
||||||
|
MASTER_SETTINGS_KEY,
|
||||||
|
settingsChanges,
|
||||||
|
);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating default settings:", error);
|
console.error("Error updating default settings:", error);
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
throw error; // Re-throw if it's already an Error with a message
|
throw error; // Re-throw if it's already an Error with a message
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Failed to update settings: ${error}`);
|
throw new Error(
|
||||||
|
`Failed to update settings. We recommend you try again or restart the app.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ export interface QueryExecResult {
|
|||||||
export interface DatabaseService {
|
export interface DatabaseService {
|
||||||
initialize(): Promise<void>;
|
initialize(): Promise<void>;
|
||||||
query(sql: string, params?: any[]): Promise<QueryExecResult[]>;
|
query(sql: string, params?: any[]): Promise<QueryExecResult[]>;
|
||||||
run(sql: string, params?: any[]): Promise<{ changes: number; lastId?: number }>;
|
run(
|
||||||
|
sql: string,
|
||||||
|
params?: any[],
|
||||||
|
): Promise<{ changes: number; lastId?: number }>;
|
||||||
getOneRow(sql: string, params?: any[]): Promise<any[] | undefined>;
|
getOneRow(sql: string, params?: any[]): Promise<any[] | undefined>;
|
||||||
getAll(sql: string, params?: any[]): Promise<any[][]>;
|
getAll(sql: string, params?: any[]): Promise<any[][]>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -561,14 +561,16 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
|
|||||||
newId.did,
|
newId.did,
|
||||||
identity,
|
identity,
|
||||||
mnemonic,
|
mnemonic,
|
||||||
newId.keys[0].publicKeyHex
|
newId.keys[0].publicKeyHex,
|
||||||
]
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
await updateDefaultSettings({ activeDid: newId.did });
|
await updateDefaultSettings({ activeDid: newId.did });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update default settings:", error);
|
console.error("Failed to update default settings:", error);
|
||||||
throw new Error("Failed to set default settings. Please try again or restart the app.");
|
throw new Error(
|
||||||
|
"Failed to set default settings. Please try again or restart the app.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await updateAccountSettings(newId.did, { isRegistered: false });
|
await updateAccountSettings(newId.did, { isRegistered: false });
|
||||||
return newId.did;
|
return newId.did;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { initBackend } from 'absurd-sql/dist/indexeddb-main-thread';
|
import { initBackend } from "absurd-sql/dist/indexeddb-main-thread";
|
||||||
import { initializeApp } from "./main.common";
|
import { initializeApp } from "./main.common";
|
||||||
import "./registerServiceWorker"; // Web PWA support
|
import "./registerServiceWorker"; // Web PWA support
|
||||||
|
|
||||||
@@ -6,9 +6,12 @@ const app = initializeApp();
|
|||||||
|
|
||||||
function sqlInit() {
|
function sqlInit() {
|
||||||
// see https://github.com/jlongster/absurd-sql
|
// see https://github.com/jlongster/absurd-sql
|
||||||
let worker = new Worker(new URL('./registerSQLWorker.js', import.meta.url), {
|
const worker = new Worker(
|
||||||
type: 'module'
|
new URL("./registerSQLWorker.js", import.meta.url),
|
||||||
});
|
{
|
||||||
|
type: "module",
|
||||||
|
},
|
||||||
|
);
|
||||||
// This is only required because Safari doesn't support nested
|
// This is only required because Safari doesn't support nested
|
||||||
// workers. This installs a handler that will proxy creating web
|
// workers. This installs a handler that will proxy creating web
|
||||||
// workers through the main thread
|
// workers through the main thread
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import databaseService from './services/database';
|
import databaseService from "./services/database";
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
await databaseService.initialize();
|
await databaseService.initialize();
|
||||||
|
|||||||
12
src/services/database.d.ts
vendored
12
src/services/database.d.ts
vendored
@@ -1,23 +1,25 @@
|
|||||||
import { DatabaseService } from '../interfaces/database';
|
import { DatabaseService } from "../interfaces/database";
|
||||||
|
|
||||||
declare module '@jlongster/sql.js' {
|
declare module "@jlongster/sql.js" {
|
||||||
interface SQL {
|
interface SQL {
|
||||||
Database: any;
|
Database: any;
|
||||||
FS: any;
|
FS: any;
|
||||||
register_for_idb: (fs: any) => void;
|
register_for_idb: (fs: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initSqlJs(config: { locateFile: (file: string) => string }): Promise<SQL>;
|
function initSqlJs(config: {
|
||||||
|
locateFile: (file: string) => string;
|
||||||
|
}): Promise<SQL>;
|
||||||
export default initSqlJs;
|
export default initSqlJs;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'absurd-sql' {
|
declare module "absurd-sql" {
|
||||||
export class SQLiteFS {
|
export class SQLiteFS {
|
||||||
constructor(fs: any, backend: any);
|
constructor(fs: any, backend: any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'absurd-sql/dist/indexeddb-backend' {
|
declare module "absurd-sql/dist/indexeddb-backend" {
|
||||||
export default class IndexedDBBackend {
|
export default class IndexedDBBackend {
|
||||||
constructor();
|
constructor();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
// Add type declarations for external modules
|
// Add type declarations for external modules
|
||||||
declare module '@jlongster/sql.js';
|
declare module "@jlongster/sql.js";
|
||||||
declare module 'absurd-sql';
|
declare module "absurd-sql";
|
||||||
declare module 'absurd-sql/dist/indexeddb-backend';
|
declare module "absurd-sql/dist/indexeddb-backend";
|
||||||
|
|
||||||
import initSqlJs from '@jlongster/sql.js';
|
import initSqlJs from "@jlongster/sql.js";
|
||||||
import { SQLiteFS } from 'absurd-sql';
|
import { SQLiteFS } from "absurd-sql";
|
||||||
import IndexedDBBackend from 'absurd-sql/dist/indexeddb-backend';
|
import IndexedDBBackend from "absurd-sql/dist/indexeddb-backend";
|
||||||
|
|
||||||
import { runMigrations } from '../db-sql/migration';
|
import { runMigrations } from "../db-sql/migration";
|
||||||
import type { QueryExecResult } from '../interfaces/database';
|
import type { QueryExecResult } from "../interfaces/database";
|
||||||
|
|
||||||
interface SQLDatabase {
|
interface SQLDatabase {
|
||||||
exec: (sql: string, params?: any[]) => Promise<QueryExecResult[]>;
|
exec: (sql: string, params?: any[]) => Promise<QueryExecResult[]>;
|
||||||
run: (sql: string, params?: any[]) => Promise<{ changes: number; lastId?: number }>;
|
run: (
|
||||||
|
sql: string,
|
||||||
|
params?: any[],
|
||||||
|
) => Promise<{ changes: number; lastId?: number }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DatabaseService {
|
class DatabaseService {
|
||||||
@@ -62,34 +65,39 @@ class DatabaseService {
|
|||||||
|
|
||||||
const SQL = await initSqlJs({
|
const SQL = await initSqlJs({
|
||||||
locateFile: (file: string) => {
|
locateFile: (file: string) => {
|
||||||
return new URL(`/node_modules/@jlongster/sql.js/dist/${file}`, import.meta.url).href;
|
return new URL(
|
||||||
}
|
`/node_modules/@jlongster/sql.js/dist/${file}`,
|
||||||
|
import.meta.url,
|
||||||
|
).href;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let sqlFS = new SQLiteFS(SQL.FS, new IndexedDBBackend());
|
const sqlFS = new SQLiteFS(SQL.FS, new IndexedDBBackend());
|
||||||
SQL.register_for_idb(sqlFS);
|
SQL.register_for_idb(sqlFS);
|
||||||
|
|
||||||
SQL.FS.mkdir('/sql');
|
SQL.FS.mkdir("/sql");
|
||||||
SQL.FS.mount(sqlFS, {}, '/sql');
|
SQL.FS.mount(sqlFS, {}, "/sql");
|
||||||
|
|
||||||
const path = '/sql/db.sqlite';
|
const path = "/sql/db.sqlite";
|
||||||
if (typeof SharedArrayBuffer === 'undefined') {
|
if (typeof SharedArrayBuffer === "undefined") {
|
||||||
let stream = SQL.FS.open(path, 'a+');
|
const stream = SQL.FS.open(path, "a+");
|
||||||
await stream.node.contents.readIfFallback();
|
await stream.node.contents.readIfFallback();
|
||||||
SQL.FS.close(stream);
|
SQL.FS.close(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.db = new SQL.Database(path, { filename: true });
|
this.db = new SQL.Database(path, { filename: true });
|
||||||
if (!this.db) {
|
if (!this.db) {
|
||||||
throw new Error('The database initialization failed. We recommend you restart or reinstall.');
|
throw new Error(
|
||||||
|
"The database initialization failed. We recommend you restart or reinstall.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.db.exec(`PRAGMA journal_mode=MEMORY;`);
|
await this.db.exec(`PRAGMA journal_mode=MEMORY;`);
|
||||||
const sqlExec = this.db.exec.bind(this.db);
|
const sqlExec = this.db.exec.bind(this.db);
|
||||||
|
|
||||||
// Run migrations
|
// Run migrations
|
||||||
await runMigrations(sqlExec);
|
await runMigrations(sqlExec);
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,13 +116,20 @@ class DatabaseService {
|
|||||||
|
|
||||||
// If initialized but no db, something went wrong
|
// If initialized but no db, something went wrong
|
||||||
if (!this.db) {
|
if (!this.db) {
|
||||||
console.error(`Database not properly initialized after await waitForInitialization() - initialized flag is true but db is null`);
|
console.error(
|
||||||
throw new Error(`The database could not be initialized. We recommend you restart or reinstall.`);
|
`Database not properly initialized after await waitForInitialization() - initialized flag is true but db is null`,
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
`The database could not be initialized. We recommend you restart or reinstall.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used for inserts, updates, and deletes
|
// Used for inserts, updates, and deletes
|
||||||
async run(sql: string, params: any[] = []): Promise<{ changes: number; lastId?: number }> {
|
async run(
|
||||||
|
sql: string,
|
||||||
|
params: any[] = [],
|
||||||
|
): Promise<{ changes: number; lastId?: number }> {
|
||||||
await this.waitForInitialization();
|
await this.waitForInitialization();
|
||||||
return this.db!.run(sql, params);
|
return this.db!.run(sql, params);
|
||||||
}
|
}
|
||||||
@@ -141,4 +156,4 @@ class DatabaseService {
|
|||||||
// Create a singleton instance
|
// Create a singleton instance
|
||||||
const databaseService = DatabaseService.getInstance();
|
const databaseService = DatabaseService.getInstance();
|
||||||
|
|
||||||
export default databaseService;
|
export default databaseService;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { QueryExecResult } from '../interfaces/database';
|
import { QueryExecResult } from "../interfaces/database";
|
||||||
|
|
||||||
interface Migration {
|
interface Migration {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -23,7 +23,7 @@ export class MigrationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async runMigrations(
|
async runMigrations(
|
||||||
sqlExec: (sql: string, params?: any[]) => Promise<Array<QueryExecResult>>
|
sqlExec: (sql: string, params?: any[]) => Promise<Array<QueryExecResult>>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Create migrations table if it doesn't exist
|
// Create migrations table if it doesn't exist
|
||||||
await sqlExec(`
|
await sqlExec(`
|
||||||
@@ -35,12 +35,16 @@ export class MigrationService {
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
// Get list of executed migrations
|
// Get list of executed migrations
|
||||||
const result: QueryExecResult[] = await sqlExec('SELECT name FROM migrations;');
|
const result: QueryExecResult[] = await sqlExec(
|
||||||
|
"SELECT name FROM migrations;",
|
||||||
|
);
|
||||||
let executedMigrations: Set<string> = new Set();
|
let executedMigrations: Set<string> = new Set();
|
||||||
// Even with that query, the QueryExecResult may be [] (which doesn't make sense to me).
|
// Even with that query, the QueryExecResult may be [] (which doesn't make sense to me).
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
const singleResult = result[0];
|
const singleResult = result[0];
|
||||||
executedMigrations = new Set(singleResult.values.map((row: any[]) => row[0]));
|
executedMigrations = new Set(
|
||||||
|
singleResult.values.map((row: any[]) => row[0]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run pending migrations in order
|
// Run pending migrations in order
|
||||||
@@ -48,7 +52,9 @@ export class MigrationService {
|
|||||||
if (!executedMigrations.has(migration.name)) {
|
if (!executedMigrations.has(migration.name)) {
|
||||||
try {
|
try {
|
||||||
await sqlExec(migration.sql);
|
await sqlExec(migration.sql);
|
||||||
await sqlExec('INSERT INTO migrations (name) VALUES (?)', [migration.name]);
|
await sqlExec("INSERT INTO migrations (name) VALUES (?)", [
|
||||||
|
migration.name,
|
||||||
|
]);
|
||||||
console.log(`Migration ${migration.name} executed successfully`);
|
console.log(`Migration ${migration.name} executed successfully`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error executing migration ${migration.name}:`, error);
|
console.error(`Error executing migration ${migration.name}:`, error);
|
||||||
@@ -59,4 +65,4 @@ export class MigrationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MigrationService.getInstance();
|
export default MigrationService.getInstance();
|
||||||
|
|||||||
@@ -76,7 +76,8 @@
|
|||||||
Set Your Name
|
Set Your Name
|
||||||
</button>
|
</button>
|
||||||
<p class="text-xs text-slate-500 mt-1">
|
<p class="text-xs text-slate-500 mt-1">
|
||||||
(Don't worry: this is not visible to anyone until you share it with them. It's not sent to any servers.)
|
(Don't worry: this is not visible to anyone until you share it with
|
||||||
|
them. It's not sent to any servers.)
|
||||||
</p>
|
</p>
|
||||||
<UserNameDialog ref="userNameDialog" />
|
<UserNameDialog ref="userNameDialog" />
|
||||||
</span>
|
</span>
|
||||||
@@ -964,7 +965,7 @@ import { AxiosError } from "axios";
|
|||||||
import { Buffer } from "buffer/";
|
import { Buffer } from "buffer/";
|
||||||
import Dexie from "dexie";
|
import Dexie from "dexie";
|
||||||
import "dexie-export-import";
|
import "dexie-export-import";
|
||||||
// @ts-ignore - they aren't exporting it but it's there
|
// @ts-expect-error - they aren't exporting it but it's there
|
||||||
import { ImportProgress } from "dexie-export-import";
|
import { ImportProgress } from "dexie-export-import";
|
||||||
import { LeafletMouseEvent } from "leaflet";
|
import { LeafletMouseEvent } from "leaflet";
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
@@ -1610,12 +1611,13 @@ export default class AccountViewView extends Vue {
|
|||||||
*/
|
*/
|
||||||
async submitImportFile() {
|
async submitImportFile() {
|
||||||
if (inputImportFileNameRef.value != null) {
|
if (inputImportFileNameRef.value != null) {
|
||||||
await db.delete()
|
await db
|
||||||
|
.delete()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
// BulkError: settings.bulkAdd(): 1 of 21 operations failed. Errors: ConstraintError: Key already exists in the object store.
|
// BulkError: settings.bulkAdd(): 1 of 21 operations failed. Errors: ConstraintError: Key already exists in the object store.
|
||||||
await Dexie.import(inputImportFileNameRef.value as Blob, {
|
await Dexie.import(inputImportFileNameRef.value as Blob, {
|
||||||
progressCallback: this.progressCallback,
|
progressCallback: this.progressCallback,
|
||||||
})
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error("Error importing file:", error);
|
logger.error("Error importing file:", error);
|
||||||
|
|||||||
@@ -726,9 +726,11 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
|
|
||||||
// Apply mirroring after a short delay to ensure video element is ready
|
// Apply mirroring after a short delay to ensure video element is ready
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const videoElement = document.querySelector('.qr-scanner video') as HTMLVideoElement;
|
const videoElement = document.querySelector(
|
||||||
|
".qr-scanner video",
|
||||||
|
) as HTMLVideoElement;
|
||||||
if (videoElement) {
|
if (videoElement) {
|
||||||
videoElement.style.transform = 'scaleX(-1)';
|
videoElement.style.transform = "scaleX(-1)";
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
@@ -943,7 +945,9 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
// Add method to detect desktop browser
|
// Add method to detect desktop browser
|
||||||
private detectDesktopBrowser(): boolean {
|
private detectDesktopBrowser(): boolean {
|
||||||
const userAgent = navigator.userAgent.toLowerCase();
|
const userAgent = navigator.userAgent.toLowerCase();
|
||||||
return !/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent);
|
return !/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
|
||||||
|
userAgent,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the computed property for camera mirroring
|
// Update the computed property for camera mirroring
|
||||||
|
|||||||
@@ -34,9 +34,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else-if="hitError">
|
<div v-else-if="hitError">
|
||||||
<span class="text-xl">Error Creating Identity</span>
|
<span class="text-xl">Error Creating Identity</span>
|
||||||
<font-awesome icon="exclamation-triangle" class="fa-fw text-red-500 ml-2"></font-awesome>
|
<font-awesome
|
||||||
|
icon="exclamation-triangle"
|
||||||
|
class="fa-fw text-red-500 ml-2"
|
||||||
|
></font-awesome>
|
||||||
<p class="text-sm text-gray-500">
|
<p class="text-sm text-gray-500">
|
||||||
Try fully restarting the app. If that doesn't work, back up all data (identities and other data) and reinstall the app.
|
Try fully restarting the app. If that doesn't work, back up all data
|
||||||
|
(identities and other data) and reinstall the app.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
@@ -85,7 +89,7 @@ export default class NewIdentifierView extends Vue {
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.hitError = true;
|
this.hitError = true;
|
||||||
console.error('Failed to generate identity:', error);
|
console.error("Failed to generate identity:", error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,6 +161,42 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<h2 class="text-xl font-bold mb-4">SQL Operations</h2>
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="flex gap-2 mb-2">
|
||||||
|
<button
|
||||||
|
class="text-sm text-blue-600 hover:text-blue-800 underline"
|
||||||
|
@click="
|
||||||
|
sqlQuery = 'SELECT * FROM sqlite_master WHERE type=\'table\';'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
All Tables
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
v-model="sqlQuery"
|
||||||
|
class="w-full h-32 p-2 border border-gray-300 rounded-md font-mono"
|
||||||
|
placeholder="Enter your SQL query here..."
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<button
|
||||||
|
class="font-bold capitalize bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
@click="executeSql"
|
||||||
|
>
|
||||||
|
Execute
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-if="sqlResult" class="mt-4">
|
||||||
|
<h3 class="text-lg font-semibold mb-2">Result:</h3>
|
||||||
|
<pre class="bg-gray-100 p-4 rounded-md overflow-x-auto">{{
|
||||||
|
JSON.stringify(sqlResult, null, 2)
|
||||||
|
}}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<h2 class="text-xl font-bold mb-4">Image Sharing</h2>
|
<h2 class="text-xl font-bold mb-4">Image Sharing</h2>
|
||||||
Populates the "shared-photo" view as if they used "share_target".
|
Populates the "shared-photo" view as if they used "share_target".
|
||||||
@@ -271,6 +307,7 @@ import { AppString, NotificationIface } from "../constants/app";
|
|||||||
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
import * as vcLib from "../libs/crypto/vc";
|
import * as vcLib from "../libs/crypto/vc";
|
||||||
import * as cryptoLib from "../libs/crypto";
|
import * as cryptoLib from "../libs/crypto";
|
||||||
|
import databaseService from "../services/database";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PeerSetup,
|
PeerSetup,
|
||||||
@@ -316,6 +353,10 @@ export default class Help extends Vue {
|
|||||||
peerSetup?: PeerSetup;
|
peerSetup?: PeerSetup;
|
||||||
userName?: string;
|
userName?: string;
|
||||||
|
|
||||||
|
// for SQL operations
|
||||||
|
sqlQuery = "";
|
||||||
|
sqlResult: any = null;
|
||||||
|
|
||||||
cryptoLib = cryptoLib;
|
cryptoLib = cryptoLib;
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@@ -492,5 +533,28 @@ export default class Help extends Vue {
|
|||||||
);
|
);
|
||||||
logger.log("decoded", decoded);
|
logger.log("decoded", decoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async executeSql() {
|
||||||
|
try {
|
||||||
|
const isSelect = this.sqlQuery.trim().toLowerCase().startsWith("select");
|
||||||
|
if (isSelect) {
|
||||||
|
this.sqlResult = await databaseService.query(this.sqlQuery);
|
||||||
|
} else {
|
||||||
|
this.sqlResult = await databaseService.run(this.sqlQuery);
|
||||||
|
}
|
||||||
|
console.log("SQL Result:", this.sqlResult);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("SQL Error:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "SQL Error",
|
||||||
|
text: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user