Skip to content

Commit

Permalink
Merge pull request #183 from Muhammed-Rahif/alpha
Browse files Browse the repository at this point in the history
fix: can’t save or open file on mobile devices
  • Loading branch information
Muhammed-Rahif authored Oct 1, 2024
2 parents 154f24b + 94f80a0 commit 10ad5c1
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 177 deletions.
5 changes: 4 additions & 1 deletion src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,11 @@ body,
::-webkit-scrollbar-thumb {
background: #ff0000;
}

::selection,
::-moz-selection,
::-webkit-selection {
@apply bg-zinc-400 dark:bg-zinc-700;
}
::-moz-selection {
@apply bg-zinc-400 dark:bg-zinc-700;
}
4 changes: 2 additions & 2 deletions src/lib/components/EditorTitle.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { NotpadHelper } from '@/helpers/notpad-helper';
import { Notpad } from '@/helpers/notpad';
import { autoWidth } from 'svelte-input-auto-width';
import { tick } from 'svelte';
import { longpress } from '@/actions/longpress';
Expand All @@ -23,7 +23,7 @@
const isValidFileName = t !== '' && t.length > 0 && t.length <= 24;
if (isValidFileName) {
NotpadHelper.updateFileName(editor.id, t);
Notpad.editors.updateFileName(editor.id, t);
readonly = true;
await tick(); // Ensure the DOM reflects the readonly change
Expand Down
6 changes: 3 additions & 3 deletions src/lib/components/Editors.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import CloseIcon from '@/components/icons/close.svelte';
import Button from '@/components/ui/button/button.svelte';
import { activeTabId, editors } from '@/store/store';
import { NotpadHelper } from '@/helpers/notpad-helper';
import { Notpad } from '@/helpers/notpad';
import type { ButtonEventHandler } from 'bits-ui';
import type { FormTextareaEvent } from './ui/textarea';
import EditorTitle from './EditorTitle.svelte';
Expand All @@ -16,11 +16,11 @@
function onEditorClose(e: ButtonEventHandler<MouseEvent>, id: string) {
e.preventDefault();
e.stopPropagation();
NotpadHelper.remove(id);
Notpad.editors.remove(id);
}
function onTextareaChange(e: FormTextareaEvent<Event>) {
NotpadHelper.updateContent($activeTabId, (e.target as HTMLTextAreaElement).value);
Notpad.editors.updateContent($activeTabId, (e.target as HTMLTextAreaElement).value);
}
// Focus on the textarea when the active tab changes
Expand Down
10 changes: 5 additions & 5 deletions src/lib/components/MenuBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import EditorTitle from './EditorTitle.svelte';
import { editors } from '@/store/store';
import { fade } from 'svelte/transition';
import { NotpadHelper } from '@/helpers/notpad-helper';
import { Notpad } from '@/helpers/notpad';
import { isTauri } from '$lib';
interface MenuItems {
Expand All @@ -26,16 +26,16 @@
{
label: 'New',
shortcut: isTauri ? 'Ctrl+N' : 'Ctrl+Alt+N',
onClick: NotpadHelper.createNew
onClick: Notpad.editors.createNew
},
{ label: 'Open...', shortcut: 'Ctrl+O', onClick: NotpadHelper.openFile },
{ label: 'Open...', shortcut: 'Ctrl+O', onClick: Notpad.file.open },
{
label: 'Save',
shortcut: 'Ctrl+S',
onClick: NotpadHelper.saveFile
onClick: Notpad.file.save
},
{ label: 'Save as...', onClick: () => NotpadHelper.saveFile({ saveAs: true }) },
{ label: 'Save as...', onClick: () => Notpad.file.save({ saveAs: true }) },
{ type: 'separator' },
{ label: 'Print', shortcut: 'Ctrl+P' },
Expand Down
15 changes: 11 additions & 4 deletions src/lib/components/SaveDialog.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
<script context="module">
let open = false;
export function openSaveDialog() {
open = true;
}
</script>

<script>
import * as AlertDialog from '@/components/ui/alert-dialog';
import { NotpadHelper } from '@/helpers/notpad-helper';
import { saveDialog } from '@/store/store';
import { Notpad } from '@/helpers/notpad';
</script>

<AlertDialog.Root open={$saveDialog} onOpenChange={saveDialog.set}>
<AlertDialog.Root {open} onOpenChange={(op) => (open = op)}>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>Are you sure?</AlertDialog.Title>
Expand All @@ -13,7 +20,7 @@
<AlertDialog.Footer>
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
<AlertDialog.Cancel>No</AlertDialog.Cancel>
<AlertDialog.Action autofocus on:click={() => NotpadHelper.createNew()}>
<AlertDialog.Action autofocus on:click={() => Notpad.editors.createNew()}>
Yes
</AlertDialog.Action>
</AlertDialog.Footer>
Expand Down
8 changes: 4 additions & 4 deletions src/lib/components/Shortcuts.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { isTauri } from '$lib';
import { NotpadHelper } from '@/helpers/notpad-helper';
import { Notpad } from '@/helpers/notpad';
import { shortcut, type ShortcutEventDetail } from '@svelte-put/shortcut';
function dispatch(d: ShortcutEventDetail, cb: () => void) {
Expand All @@ -15,20 +15,20 @@
{
key: 'n',
modifier: isTauri ? ['ctrl'] : ['ctrl', 'alt'],
callback: (d) => dispatch(d, NotpadHelper.createNew)
callback: (d) => dispatch(d, Notpad.editors.createNew)
},
{
key: 's',
modifier: ['ctrl'],
callback: (d) => dispatch(d, NotpadHelper.saveFile)
callback: (d) => dispatch(d, Notpad.file.save)
}
]
}}
use:shortcut={{
trigger: {
key: 'o',
modifier: ['ctrl'],
callback: (d) => dispatch(d, NotpadHelper.openFile)
callback: (d) => dispatch(d, Notpad.file.open)
}
}}
/>
139 changes: 139 additions & 0 deletions src/lib/helpers/file-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { activeTabId, editors } from '@/store/store';
import { get } from 'svelte/store';
import { findAsyncSequential } from '@/utils';
import { Notpad } from '@/helpers/notpad';
import { isMobile } from '$lib';

/**
* A helper class for handling file operations such as opening, saving, and saving as.
* This class is used in the Menubar's file options to manage file interactions.
*/
export class FileOptions {
/**
* Opens a file picker dialog for the user to select a text file, reads the file content,
* and either activates an already opened tab or creates a new tab with the file content.
*
* @throws Will show an error message if the file picker dialog fails or file reading encounters an error.
*/
async open() {
if (isMobile) {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.txt';
input.onchange = (event: Event) => {
const target = event.target as HTMLInputElement;
if (target.files && target.files.length > 0) {
const file = target.files[0];
if (file.type !== 'text/plain') {
Notpad.showError(new Error('Please select a text file.'));
input.remove();
return;
}

const reader = new FileReader();

reader.onload = (e) => {
const content = e.target?.result as string;
Notpad.editors.createNew({
content,
fileName: file.name
});
input.remove();
};

reader.readAsText(file);
}
};
input.click();
} else
try {
const [fileHandle] = await showOpenFilePicker({
multiple: false,
excludeAcceptAllOption: true,
types: [
{
description: 'Text Documents (*.txt)',
accept: {
'text/plain': ['.txt']
}
}
]
});

const file = await fileHandle.getFile();
const content = await file.text();

const alreadyOpened = await findAsyncSequential(get(editors), async (edtr) => {
if (!edtr.fileHandle) return false;
return await fileHandle.isSameEntry(edtr.fileHandle);
});

if (alreadyOpened) {
activeTabId.set(alreadyOpened.id);
} else {
Notpad.editors.createNew({
fileName: file.name,
content,
fileHandle: fileHandle
});
}
} catch (err) {
Notpad.showError(err);
}
}

/**
* Saves the current content of the active editor to a file.
* If `saveAs` is true or no file handle exists, prompts the user to select a file location.
*
* @param {Object} options - Options for saving the file.
* @param {boolean} [options.saveAs] - If true, prompts the user to select a file location.
*
* @throws Will throw an error if the save operation fails.
*/
async save({ saveAs }: { saveAs?: boolean } = {}) {
const activeEditor = Notpad.editors.getActive();
if (!activeEditor) return;

if (isMobile) {
const blob = new Blob([activeEditor.content], { type: 'text/plain' });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const nav = window.navigator as any;

if (nav.msSaveOrOpenBlob) {
nav.msSaveBlob(blob, activeEditor.fileName);
} else {
const elem = window.document.createElement('a');
elem.href = window.URL.createObjectURL(blob);
elem.download = activeEditor.fileName;
document.body.appendChild(elem);
elem.click();
document.body.removeChild(elem);
}
} else
try {
let fileHandle: FileSystemFileHandle | undefined = activeEditor.fileHandle;
if (saveAs || !fileHandle)
fileHandle = await showSaveFilePicker({
id: activeEditor.id,
suggestedName: activeEditor.fileName,
excludeAcceptAllOption: true,
types: [
{
description: 'Text Documents (*.txt)',
accept: {
'text/plain': ['.txt']
}
}
]
});

const writable = await fileHandle.createWritable();
await writable.write(activeEditor.content);
await writable.close();
Notpad.editors.updateFileHandle(activeEditor.id, fileHandle);
} catch (err) {
Notpad.showError(err);
}
}
}
84 changes: 84 additions & 0 deletions src/lib/helpers/notpad-editors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { activeTabId, editors, type EditorData } from '@/store/store';
import { get } from 'svelte/store';
import { generate as genId } from 'short-uuid';
import { toast } from 'svelte-sonner';

/**
* A helper class for performing various editor-related tasks such as opening
* a new editor, removing an editor, etc.
*/
export class NotpadEditors {
createNew({ content, fileName, fileHandle }: Partial<EditorData> = {}) {
const newId = genId();
editors.update((value) => {
value.push({
fileName: fileName ?? 'Untitled.txt',
content: content ?? '',
id: newId,
fileHandle
});

return value;
});
activeTabId.update(() => newId);
}

remove(editorId: string) {
let i = 0;
let removeIndex = -1;
editors.update((value) => {
return value.filter((editor) => {
if (editor.id == editorId) removeIndex = i;
i++;
return editor.id !== editorId;
});
});

activeTabId.update((value) => {
if (value === editorId && removeIndex > 0) {
return get(editors)[removeIndex - 1]?.id ?? null;
}
return value;
});
}

updateContent(id: string, content: string) {
editors.update((value) => {
return value.map((editor) => {
if (editor.id === id) {
editor.content = content;
}
return editor;
});
});
}

updateFileName(editorId: string, fileName: string) {
editors.update((value) => {
return value.map((editor) => {
if (editor.id === editorId) {
editor.fileName = fileName;
}
return editor;
});
});
toast.success(`Title updated to "${fileName}"`);
}

getActive(): EditorData | undefined {
const activeId = get(activeTabId);
const editorsList = get(editors);
return editorsList.find((editor) => editor.id === activeId);
}

updateFileHandle(editorId: string, fileHandle: FileSystemFileHandle) {
editors.update((value) => {
return value.map((e) => {
if (e.id === editorId) {
return { ...e, fileHandle, fileName: fileHandle.name };
}
return e;
});
});
}
}
Loading

0 comments on commit 10ad5c1

Please sign in to comment.