Skip to content

Commit

Permalink
[MS] Added file viewers
Browse files Browse the repository at this point in the history
  • Loading branch information
Max-7 committed Nov 18, 2024
1 parent 0586410 commit ea4a463
Show file tree
Hide file tree
Showing 50 changed files with 4,370 additions and 76 deletions.
2 changes: 2 additions & 0 deletions .cspell/cspell.config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ ignorePaths:
- "**/*.icns"
- "**/.git"
- "**/client/.env.*"
- "**/client/tests/unit/data"
- "**/client/tests/e2e/data"
import:
- "@cspell/dict-fr-fr/cspell-ext.json"
- "@cspell/dict-fr-reforme/cspell-ext.json"
Expand Down
2 changes: 2 additions & 0 deletions .cspell/custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ blockstores
Booyakasha
boto
botocore
bottombar
broadcastable
browserslistrc
Bsas
Expand Down Expand Up @@ -736,6 +737,7 @@ winify
winres
wizz
wksp
wordprocessingml
workdir
workspacefs
wpath
Expand Down
1 change: 1 addition & 0 deletions client/.eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ electron/node_modules
src/views/testing/TestPage.vue
bindings
playwright.config.ts
src/parsec/mock_files
1 change: 1 addition & 0 deletions client/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ package-lock.json
# Auto-generated
src/plugins/libparsec/definitions.ts
src/components.d.ts
src/parsec/mock_files
502 changes: 485 additions & 17 deletions client/package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@
"file-type": "^19.6.0",
"luxon": "^3.4.4",
"megashark-lib": "git+https://github.com/Scille/megashark-lib.git#f65a38f9255bb7b4e1fafe1be3b833b6a1a8f1d4",
"mammoth": "^1.8.0",
"monaco-editor": "^0.52.0",
"pdfjs-dist": "^4.8.69",
"qrcode-vue3": "^1.6.8",
"uuid": "^9.0.1",
"vue": "^3.3.8",
"vue-i18n": "^9.6.5",
"vue-router": "^4.2.5"
"vue-router": "^4.2.5",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
},
"devDependencies": {
"@capacitor/cli": "^5.6.0",
Expand Down
5 changes: 2 additions & 3 deletions client/scripts/file_to_uint8array.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@
parser.add_argument("-o", "--output", default=sys.stdout, help="Where to put the result")
args = parser.parse_args()
content = args.input.read_bytes()
array = ",".join([str(c) for c in content])
result = f"""
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS
array = ", ".join([str(c) for c in content])
result = f"""// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS
/*
Generated automatically with {sys.argv[0]}
Expand Down
95 changes: 93 additions & 2 deletions client/src/common/fileTypes.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS

import {
closeFile,
closeHistoryFile,
FileDescriptor,
FsPath,
openFile,
openFileAt,
Path,
readFile,
readHistoryFile,
WorkspaceHandle,
} from '@/parsec';
import { fileTypeFromBuffer } from 'file-type';
import { DateTime } from 'luxon';

enum FileContentType {
Image = 'image',
Video = 'video',
Audio = 'audio',
Spreadsheet = 'spreadsheet',
Document = 'document',
Text = 'text',
PdfDocument = 'pdf-document',
Unknown = 'unknown',
}

Expand All @@ -19,8 +34,25 @@ interface DetectedFileType {

const IMAGES = ['image/png', 'image/webp', 'image/jpeg', 'image/svg+xml', 'image/bmp', 'image/gif'];
const SPREADSHEETS = ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
const DOCUMENTS = ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
const PDF_DOCUMENTS = ['application/pdf'];
const AUDIOS = ['audio/x-wav', 'audio/mpeg'];
const VIDEOS = ['video/mp4', 'video/mpeg'];

async function detectFileContentType(buffer: Uint8Array): Promise<DetectedFileType> {
const TEXTS = new Map<string, string>([
['xml', 'application/xml'],
['json', 'application/json'],
['js', 'text/javascript'],
['html', 'text/html'],
['htm', 'text/html'],
['txt', 'text/plain'],
['sh', 'application/x-sh'],
['csv', 'text/csv'],
['css', 'text/css'],
['py', 'text/x-python'],
]);

async function detectFileContentTypeFromBuffer(buffer: Uint8Array): Promise<DetectedFileType> {
const result = await fileTypeFromBuffer(buffer);

if (!result) {
Expand All @@ -33,9 +65,68 @@ async function detectFileContentType(buffer: Uint8Array): Promise<DetectedFileTy
if (SPREADSHEETS.includes(result.mime)) {
return { type: FileContentType.Spreadsheet, extension: result.ext, mimeType: result.mime };
}
if (DOCUMENTS.includes(result.mime)) {
return { type: FileContentType.Document, extension: result.ext, mimeType: result.mime };
}
if (PDF_DOCUMENTS.includes(result.mime)) {
return { type: FileContentType.PdfDocument, extension: result.ext, mimeType: result.mime };
}
if (AUDIOS.includes(result.mime)) {
return { type: FileContentType.Audio, extension: result.ext, mimeType: result.mime };
}
if (VIDEOS.includes(result.mime)) {
return { type: FileContentType.Video, extension: result.ext, mimeType: result.mime };
}
console.log(`Unhandled mimetype ${result.mime}`);

return { type: FileContentType.Unknown, extension: result.ext, mimeType: result.mime };
}

export { DetectedFileType, FileContentType, detectFileContentType };
async function detectFileContentType(workspaceHandle: WorkspaceHandle, path: FsPath, at?: DateTime): Promise<DetectedFileType | undefined> {
const fileName = await Path.filename(path);

if (!fileName) {
return;
}
const ext = Path.getFileExtension(fileName).toLocaleLowerCase();

if (TEXTS.has(ext)) {
return { type: FileContentType.Text, extension: ext, mimeType: TEXTS.get(ext) as string };
}

const READ_CHUNK_SIZE = 512;
let fd: FileDescriptor | null = null;
try {
let openResult;
if (at) {
openResult = await openFileAt(workspaceHandle, path, at);
} else {
openResult = await openFile(workspaceHandle, path, { read: true });
}
if (!openResult.ok) {
return;
}
fd = openResult.value;
let readResult;
if (at) {
readResult = await readHistoryFile(workspaceHandle, fd, 0, READ_CHUNK_SIZE);
} else {
readResult = await readFile(workspaceHandle, fd, 0, READ_CHUNK_SIZE);
}
if (!readResult.ok) {
return;
}
const buffer = new Uint8Array(readResult.value);
return await detectFileContentTypeFromBuffer(buffer);
} finally {
if (fd) {
if (at) {
closeHistoryFile(workspaceHandle, fd);
} else {
closeFile(workspaceHandle, fd);
}
}
}
}

export { DetectedFileType, detectFileContentType, detectFileContentTypeFromBuffer, FileContentType };
6 changes: 5 additions & 1 deletion client/src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@
},
"myProfile": "My profile",
"recoveryExport": "Recovery file",
"history": "History"
"history": "History",
"viewer": "File viewer"
},
"previous": "Go back",
"invitations": {
Expand Down Expand Up @@ -1671,5 +1672,8 @@
"loading": "Loading...",
"workspace": "Workspace:",
"date": "Preview date:"
},
"fileViewers": {
"openWithDefault": "Open with default application"
}
}
6 changes: 5 additions & 1 deletion client/src/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@
},
"myProfile": "Mon profil",
"recoveryExport": "Fichier de récupération",
"history": "Historique"
"history": "Historique",
"viewer": "Aperçu du fichier"
},
"previous": "Précédent",
"invitations": {
Expand Down Expand Up @@ -1671,5 +1672,8 @@
"loading": "Chargement...",
"workspace": "Espace de travail :",
"date": "Date de prévisualisation :"
},
"fileViewers": {
"openWithDefault": "Ouvrir avec l'application par défaut"
}
}
15 changes: 15 additions & 0 deletions client/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import { Information, InformationDataType, InformationLevel, InformationManager,
import { InjectionProvider, InjectionProviderKey } from '@/services/injectionProvider';
import { Sentry } from '@/services/sentry';
import { Answer, Base64, I18n, Locale, MegaSharkPlugin, ThemeManager, Validity, askQuestion } from 'megashark-lib';
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import * as pdfjs from 'pdfjs-dist';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker?worker&url';

enum AppState {
Ready = 'ready',
Expand All @@ -51,6 +54,16 @@ function preventRightClick(): void {
});
}

async function initViewers(): Promise<void> {
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;

self.MonacoEnvironment = {
getWorker: function (_workerId, _label): Worker {
return new editorWorker();
},
};
}

async function setupApp(): Promise<void> {
await storageManagerInstance.init();
const storageManager = storageManagerInstance.get();
Expand Down Expand Up @@ -94,6 +107,8 @@ async function setupApp(): Promise<void> {
app.provide(InjectionProviderKey, injectionProvider);
app.provide(HotkeyManagerKey, hotkeyManager);

await initViewers();

// We get the app element
const appElem = document.getElementById('app');
if (!appElem) {
Expand Down
22 changes: 20 additions & 2 deletions client/src/parsec/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ export async function entryStat(workspaceHandle: WorkspaceHandle, path: FsPath):
let entry: MockEntry;

if (path !== '/' && fileName.startsWith('File_')) {
entry = await generateFile(path, `${MOCK_FILE_ID}`);
entry = await generateFile(path, { parentId: `${MOCK_FILE_ID}`, fileName: fileName });
} else {
entry = await generateFolder(path, `${MOCK_FILE_ID}`);
entry = await generateFolder(path, { parentId: `${MOCK_FILE_ID}`, fileName: fileName });
}
(entry as any as EntryStat).baseVersion = entry.version;
(entry as any as EntryStat).confinementPoint = null;
Expand Down Expand Up @@ -330,6 +330,24 @@ export async function readFile(
case 'png':
console.log('Using PNG content');
return { ok: true, value: MockFiles.PNG };
case 'docx':
console.log('Using DOCX content');
return { ok: true, value: MockFiles.DOCX };
case 'txt':
console.log('Using TXT content');
return { ok: true, value: MockFiles.TXT };
case 'py':
console.log('Using PY content');
return { ok: true, value: MockFiles.PY };
case 'pdf':
console.log('Using PDF content');
return { ok: true, value: MockFiles.PDF };
case 'mp3':
console.log('Using MP3 content');
return { ok: true, value: MockFiles.MP3 };
case 'mp4':
console.log('Using MP4 content');
return { ok: true, value: MockFiles.MP4 };
}
console.log('Using default file content');
return {
Expand Down
Loading

0 comments on commit ea4a463

Please sign in to comment.