Recent sheets per user
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { selectedSheet, currentStep, columnMapping } from '$lib/stores';
|
import {
|
||||||
|
selectedSheet,
|
||||||
|
currentStep,
|
||||||
|
columnMapping,
|
||||||
|
} from '$lib/stores';
|
||||||
|
import { userEmail } from '$lib/google';
|
||||||
|
import { hashString } from '$lib/utils';
|
||||||
import type { ColumnMappingType, SheetInfoType } from '$lib/stores';
|
import type { ColumnMappingType, SheetInfoType } from '$lib/stores';
|
||||||
import { getSheetNames, getSheetData, ensureToken } from '$lib/google';
|
import { getSheetNames, getSheetData, ensureToken } from '$lib/google';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
@@ -33,7 +39,13 @@
|
|||||||
{ key: 'alreadyPrinted', label: 'Already Printed', required: false }
|
{ key: 'alreadyPrinted', label: 'Already Printed', required: false }
|
||||||
];
|
];
|
||||||
|
|
||||||
const RECENT_SHEETS_KEY = 'recentSheets';
|
async function getRecentSheetsKey() {
|
||||||
|
const email = $userEmail;
|
||||||
|
if (email) {
|
||||||
|
return `recentSheets_${await hashString(email)}`;
|
||||||
|
}
|
||||||
|
return 'recentSheets_anonymous';
|
||||||
|
}
|
||||||
|
|
||||||
// Load available sheets when component mounts
|
// Load available sheets when component mounts
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
@@ -42,7 +54,8 @@
|
|||||||
console.log('Selected sheet on mount:', $selectedSheet);
|
console.log('Selected sheet on mount:', $selectedSheet);
|
||||||
|
|
||||||
// Check if we already have saved mapping data
|
// Check if we already have saved mapping data
|
||||||
const recentSheetsData = localStorage.getItem(RECENT_SHEETS_KEY);
|
const key = await getRecentSheetsKey();
|
||||||
|
const recentSheetsData = localStorage.getItem(key);
|
||||||
|
|
||||||
if (recentSheetsData) {
|
if (recentSheetsData) {
|
||||||
try {
|
try {
|
||||||
@@ -161,7 +174,7 @@
|
|||||||
autoMapColumns();
|
autoMapColumns();
|
||||||
|
|
||||||
// Check if we have saved column mapping for this sheet
|
// Check if we have saved column mapping for this sheet
|
||||||
loadSavedColumnMapping();
|
await loadSavedColumnMapping();
|
||||||
} else {
|
} else {
|
||||||
error = 'The selected sheet appears to be empty.';
|
error = 'The selected sheet appears to be empty.';
|
||||||
console.warn('Sheet is empty');
|
console.warn('Sheet is empty');
|
||||||
@@ -235,14 +248,15 @@
|
|||||||
updateMappingStatus();
|
updateMappingStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSavedColumnMapping() {
|
async function loadSavedColumnMapping() {
|
||||||
if (!$selectedSheet || !selectedSheetName) {
|
if (!$selectedSheet || !selectedSheetName) {
|
||||||
console.log('Cannot load saved column mapping: missing selectedSheet or selectedSheetName');
|
console.log('Cannot load saved column mapping: missing selectedSheet or selectedSheetName');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const existingData = localStorage.getItem(RECENT_SHEETS_KEY);
|
const key = await getRecentSheetsKey();
|
||||||
|
const existingData = localStorage.getItem(key);
|
||||||
|
|
||||||
if (existingData) {
|
if (existingData) {
|
||||||
const recentSheets = JSON.parse(existingData);
|
const recentSheets = JSON.parse(existingData);
|
||||||
@@ -256,7 +270,7 @@
|
|||||||
// Override auto-mapping with saved mapping
|
// Override auto-mapping with saved mapping
|
||||||
mappedIndices = {
|
mappedIndices = {
|
||||||
name: savedSheet.columnMapping.name ?? -1,
|
name: savedSheet.columnMapping.name ?? -1,
|
||||||
nationality: savedSheet.columnMappin.nationality ?? -1,
|
nationality: savedSheet.columnMapping.nationality ?? -1,
|
||||||
birthday: savedSheet.columnMapping.birthday ?? -1,
|
birthday: savedSheet.columnMapping.birthday ?? -1,
|
||||||
pictureUrl: savedSheet.columnMapping.pictureUrl ?? -1,
|
pictureUrl: savedSheet.columnMapping.pictureUrl ?? -1,
|
||||||
alreadyPrinted: savedSheet.columnMapping.alreadyPrinted ?? -1,
|
alreadyPrinted: savedSheet.columnMapping.alreadyPrinted ?? -1,
|
||||||
@@ -318,12 +332,13 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleContinue() {
|
async function handleContinue() {
|
||||||
if (!mappingComplete || !$selectedSheet || !selectedSheetName) return;
|
if (!mappingComplete || !$selectedSheet || !selectedSheetName) return;
|
||||||
|
|
||||||
// Save column mapping to localStorage for the selected sheet
|
// Save column mapping to localStorage for the selected sheet
|
||||||
try {
|
try {
|
||||||
const existingData = localStorage.getItem(RECENT_SHEETS_KEY);
|
const key = await getRecentSheetsKey();
|
||||||
|
const existingData = localStorage.getItem(key);
|
||||||
let recentSheets = existingData ? JSON.parse(existingData) : [];
|
let recentSheets = existingData ? JSON.parse(existingData) : [];
|
||||||
|
|
||||||
// Find the current sheet in recent sheets and update its column mapping
|
// Find the current sheet in recent sheets and update its column mapping
|
||||||
@@ -344,9 +359,6 @@
|
|||||||
// Update existing entry
|
// Update existing entry
|
||||||
recentSheets[sheetIndex].columnMapping = columnMappingData;
|
recentSheets[sheetIndex].columnMapping = columnMappingData;
|
||||||
recentSheets[sheetIndex].lastUsed = new Date().toISOString();
|
recentSheets[sheetIndex].lastUsed = new Date().toISOString();
|
||||||
|
|
||||||
// Ensure we have consistent property names
|
|
||||||
recentSheets[sheetIndex].id = recentSheets[sheetIndex].id || recentSheets[sheetIndex].id;
|
|
||||||
} else {
|
} else {
|
||||||
// Add new entry
|
// Add new entry
|
||||||
const newEntry = {
|
const newEntry = {
|
||||||
@@ -364,7 +376,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem(RECENT_SHEETS_KEY, JSON.stringify(recentSheets));
|
localStorage.setItem(key, JSON.stringify(recentSheets));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to save column mapping to localStorage:', err);
|
console.error('Failed to save column mapping to localStorage:', err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { selectedSheet, currentStep } from '$lib/stores';
|
import { selectedSheet, currentStep } from '$lib/stores';
|
||||||
import type { SheetInfoType } from '$lib/stores';
|
import type { SheetInfoType } from '$lib/stores';
|
||||||
import { searchSheets, ensureToken } from '$lib/google';
|
import { searchSheets, ensureToken, userEmail } from '$lib/google';
|
||||||
|
import { hashString } from '$lib/utils';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Navigator from './subcomponents/Navigator.svelte';
|
import Navigator from './subcomponents/Navigator.svelte';
|
||||||
|
|
||||||
@@ -12,7 +13,13 @@
|
|||||||
let hasSearched = $state(false);
|
let hasSearched = $state(false);
|
||||||
let recentSheets = $state<any[]>([]);
|
let recentSheets = $state<any[]>([]);
|
||||||
|
|
||||||
const RECENT_SHEETS_KEY = 'recentSheets';
|
async function getRecentSheetsKey() {
|
||||||
|
const email = $userEmail;
|
||||||
|
if (email) {
|
||||||
|
return `recentSheets_${await hashString(email)}`;
|
||||||
|
}
|
||||||
|
return 'recentSheets_anonymous';
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
ensureToken();
|
ensureToken();
|
||||||
@@ -37,16 +44,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadRecentSheets() {
|
async function loadRecentSheets() {
|
||||||
try {
|
try {
|
||||||
const saved = localStorage.getItem(RECENT_SHEETS_KEY);
|
const key = await getRecentSheetsKey();
|
||||||
|
const saved = localStorage.getItem(key);
|
||||||
if (saved) {
|
if (saved) {
|
||||||
recentSheets = JSON.parse(saved);
|
recentSheets = JSON.parse(saved);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error loading recent sheets:', err);
|
console.error('Error loading recent sheets:', err);
|
||||||
// If there's an error, clear the stored value
|
// If there's an error, clear the stored value
|
||||||
localStorage.removeItem(RECENT_SHEETS_KEY);
|
const key = await getRecentSheetsKey();
|
||||||
|
localStorage.removeItem(key);
|
||||||
recentSheets = [];
|
recentSheets = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { env } from '$env/dynamic/public';
|
|||||||
export const accessToken = writable<string | null | undefined>(undefined);
|
export const accessToken = writable<string | null | undefined>(undefined);
|
||||||
export const isSignedIn = writable(false);
|
export const isSignedIn = writable(false);
|
||||||
export const isGoogleApiReady = writable(false); // To track GAPI client readiness
|
export const isGoogleApiReady = writable(false); // To track GAPI client readiness
|
||||||
|
export const userEmail = writable<string | null>(null);
|
||||||
|
|
||||||
let tokenClient: google.accounts.oauth2.TokenClient;
|
let tokenClient: google.accounts.oauth2.TokenClient;
|
||||||
let gapiInited = false;
|
let gapiInited = false;
|
||||||
@@ -48,6 +49,27 @@ export function initGoogleClients(callback: () => void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches user's email and stores it.
|
||||||
|
*/
|
||||||
|
async function fetchUserInfo(token: string) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch user info');
|
||||||
|
}
|
||||||
|
const profile = await response.json();
|
||||||
|
userEmail.set(profile.email);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user info:', error);
|
||||||
|
userEmail.set(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Load GSI script for Auth. This should only be called after GAPI is ready.
|
// 2. Load GSI script for Auth. This should only be called after GAPI is ready.
|
||||||
function initGsiClient(callback: () => void) {
|
function initGsiClient(callback: () => void) {
|
||||||
if (gsiInited) {
|
if (gsiInited) {
|
||||||
@@ -64,7 +86,7 @@ function initGsiClient(callback: () => void) {
|
|||||||
tokenClient = google.accounts.oauth2.initTokenClient({
|
tokenClient = google.accounts.oauth2.initTokenClient({
|
||||||
client_id: env.PUBLIC_GOOGLE_CLIENT_ID,
|
client_id: env.PUBLIC_GOOGLE_CLIENT_ID,
|
||||||
scope:
|
scope:
|
||||||
'https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/spreadsheets.readonly',
|
'https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/spreadsheets.readonly https://www.googleapis.com/auth/userinfo.email',
|
||||||
callback: (tokenResponse) => {
|
callback: (tokenResponse) => {
|
||||||
// This callback handles responses from all token requests.
|
// This callback handles responses from all token requests.
|
||||||
if (tokenResponse.error) {
|
if (tokenResponse.error) {
|
||||||
@@ -78,6 +100,7 @@ function initGsiClient(callback: () => void) {
|
|||||||
isSignedIn.set(true);
|
isSignedIn.set(true);
|
||||||
// Also set the token for the GAPI client
|
// Also set the token for the GAPI client
|
||||||
if (gapiInited) gapi.client.setToken({ access_token: token });
|
if (gapiInited) gapi.client.setToken({ access_token: token });
|
||||||
|
fetchUserInfo(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -152,6 +175,7 @@ export function handleSignOut() {
|
|||||||
}
|
}
|
||||||
accessToken.set(null);
|
accessToken.set(null);
|
||||||
isSignedIn.set(false);
|
isSignedIn.set(false);
|
||||||
|
userEmail.set(null);
|
||||||
|
|
||||||
console.log('User signed out.');
|
console.log('User signed out.');
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/lib/utils.ts
Normal file
13
src/lib/utils.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Hashes a string using the SHA-256 algorithm.
|
||||||
|
* @param input The string to hash.
|
||||||
|
* @returns A promise that resolves to the hex-encoded hash string.
|
||||||
|
*/
|
||||||
|
export async function hashString(input: string): Promise<string> {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const data = encoder.encode(input);
|
||||||
|
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
||||||
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||||
|
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
|
||||||
|
return hashHex;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user