feat: export/import functionality (backup/restore/share with friends) + a new input component (#433)
* feat: export schedule function to be added to handler * feat: use UserScheduleStore and return json * feat: download functionality * feat: oh wow we already have a blob download util that is very very nice * feat: return empty json if none found * feat: import function completion * feat: file uploading done * feat: new input component-stories made-settings input replaced with component * feat: attempt 1 to hook settings.tsx to importSchedule * feat: it works horray aka using right Course constructor it works * chore: fix jsdoc * chore: comments and debug style * docs: extra comment * feat: name of schedule more user friendly * feat: reworked how schedule is passed and check for file being schedule * feat: color is kept on import * fix: add sendResponse to exportSchedule --------- Co-authored-by: doprz <52579214+doprz@users.noreply.github.com>
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
// import addCourse from '@pages/background/lib/addCourse';
|
||||
import { addCourseByURL } from '@pages/background/lib/addCourseByURL';
|
||||
import { deleteAllSchedules } from '@pages/background/lib/deleteSchedule';
|
||||
import exportSchedule from '@pages/background/lib/exportSchedule';
|
||||
import importSchedule from '@pages/background/lib/importSchedule';
|
||||
import { background } from '@shared/messages';
|
||||
import { initSettings, OptionsStore } from '@shared/storage/OptionsStore';
|
||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||
import { downloadBlob } from '@shared/util/downloadBlob';
|
||||
// import { addCourseByUrl } from '@shared/util/courseUtils';
|
||||
// import { getCourseColors } from '@shared/util/colors';
|
||||
// import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell';
|
||||
@@ -26,6 +31,7 @@ import IconoirGitFork from '~icons/iconoir/git-fork';
|
||||
// import { ExampleCourse } from 'src/stories/components/ConflictsWithWarning.stories';
|
||||
import DeleteForeverIcon from '~icons/material-symbols/delete-forever';
|
||||
|
||||
import InputButton from '../common/InputButton';
|
||||
import { useMigrationDialog } from '../common/MigrationDialog';
|
||||
// import RefreshIcon from '~icons/material-symbols/refresh';
|
||||
import DevMode from './DevMode';
|
||||
@@ -204,6 +210,35 @@ export default function Settings(): JSX.Element {
|
||||
});
|
||||
};
|
||||
|
||||
const handleExportClick = async (id: string) => {
|
||||
const jsonString = await exportSchedule(id);
|
||||
if (jsonString) {
|
||||
const schedules = await UserScheduleStore.get('schedules');
|
||||
const schedule = schedules.find(s => s.id === id);
|
||||
const fileName = `${schedule?.name ?? `schedule_${id}`}_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
|
||||
await downloadBlob(jsonString, 'JSON', fileName);
|
||||
} else {
|
||||
console.error('Error exporting schedule: jsonString is undefined');
|
||||
}
|
||||
};
|
||||
|
||||
const handleImportClick = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async e => {
|
||||
try {
|
||||
const result = e.target?.result as string;
|
||||
const jsonObject = JSON.parse(result);
|
||||
await importSchedule(jsonObject);
|
||||
} catch (error) {
|
||||
console.error('Invalid import file!');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
|
||||
// const handleAddCourseByLink = async () => {
|
||||
// // todo: Use a proper modal instead of a prompt
|
||||
// const link: string | null = prompt('Enter course link');
|
||||
@@ -323,6 +358,40 @@ export default function Settings(): JSX.Element {
|
||||
|
||||
<Divider size='auto' orientation='horizontal' /> */}
|
||||
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='max-w-xs'>
|
||||
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
||||
Export Current Schedule
|
||||
</Text>
|
||||
<p className='text-sm text-gray-600'>
|
||||
Backup your active schedule to a portable file
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='ut-burntorange'
|
||||
onClick={() => handleExportClick(activeSchedule.id)}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Divider size='auto' orientation='horizontal' />
|
||||
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='max-w-xs'>
|
||||
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
||||
Import Schedule
|
||||
</Text>
|
||||
<p className='text-sm text-gray-600'>Import from a schedule file</p>
|
||||
</div>
|
||||
<InputButton variant='filled' color='ut-burntorange' onChange={handleImportClick}>
|
||||
Import Schedule
|
||||
</InputButton>
|
||||
</div>
|
||||
|
||||
<Divider size='auto' orientation='horizontal' />
|
||||
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='max-w-xs'>
|
||||
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
||||
|
||||
Reference in New Issue
Block a user