Skip to content

Commit 85b56e2

Browse files
committed
feat(frontend:files): add ability to edit basic text files
1 parent b0c22ee commit 85b56e2

File tree

2 files changed

+75
-13
lines changed

2 files changed

+75
-13
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!--
2+
~ Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>
3+
~ This file is part of Sync-in | The open source file sync and share solution
4+
~ See the LICENSE file for licensing details
5+
-->
6+
7+
<div [style.height.px]="currentHeight()">
8+
<div class="app-top-menu">
9+
<button
10+
(click)="toggleReadonly()"
11+
[tooltip]="'Read-only' | translate:locale.language"
12+
container="body"
13+
class="d-none d-sm-block btn btn-sm btn-secondary me-1"
14+
type="button"
15+
[disabled]="!isReadable()">
16+
<fa-icon [icon]="isReadonly() ? icons.faLock : icons.faLockOpen"></fa-icon>
17+
</button>
18+
<button
19+
(click)="save()"
20+
[tooltip]="'Save' | translate:locale.language"
21+
container="body"
22+
class="d-none d-sm-block btn btn-sm btn-secondary me-1"
23+
type="button"
24+
[disabled]="isReadonly() || !isModified()">
25+
<fa-icon [icon]="icons.faFloppyDisk"></fa-icon>
26+
</button>
27+
<button
28+
(click)="openSearchPanel(editor.view)"
29+
[tooltip]="'Search' | translate:locale.language"
30+
container="body"
31+
class="d-none d-sm-block btn btn-sm btn-secondary me-1"
32+
type="button"
33+
[disabled]="!isReadable()">
34+
<fa-icon [icon]="icons.faMagnifyingGlass"></fa-icon>
35+
</button>
36+
</div>
37+
<code-editor
38+
#editor
39+
[languages]="languages"
40+
[language]="currentLanguage"
41+
[(ngModel)]="content"
42+
(change)="isModified.set(true)"
43+
[theme]="currentTheme"
44+
[readonly]="isReadonly()"
45+
[lineWrapping]="true" />
46+
</div>

frontend/src/app/applications/files/components/viewers/files-viewer-text.component.ts

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,55 +6,71 @@
66

77
import { CodeEditor } from '@acrodata/code-editor'
88
import { HttpClient } from '@angular/common/http'
9-
import { Component, inject, input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'
9+
import { Component, inject, input, OnDestroy, OnInit, signal, ViewEncapsulation } from '@angular/core'
1010
import { FormsModule } from '@angular/forms'
1111
import { LanguageDescription } from '@codemirror/language'
1212
import { languages } from '@codemirror/language-data'
13+
import { openSearchPanel } from '@codemirror/search'
14+
import { FaIconComponent } from '@fortawesome/angular-fontawesome'
15+
import { faFloppyDisk, faLock, faLockOpen, faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'
16+
import { L10N_LOCALE, L10nLocale, L10nTranslatePipe } from 'angular-l10n'
17+
import { TooltipModule } from 'ngx-bootstrap/tooltip'
1318
import { themeDark } from '../../../../layout/layout.interfaces'
1419
import { LayoutService } from '../../../../layout/layout.service'
1520
import { FileModel } from '../../models/file.model'
21+
import { FilesUploadService } from '../../services/files-upload.service'
1622

1723
@Component({
1824
selector: 'app-files-viewer-text',
1925
encapsulation: ViewEncapsulation.None,
20-
imports: [CodeEditor, FormsModule],
26+
imports: [CodeEditor, TooltipModule, FormsModule, FaIconComponent, L10nTranslatePipe],
2127
styles: [
2228
`
2329
.code-editor {
24-
height: 100%;
30+
height: calc(100% - 40px);
2531
font-size: 0.75rem;
2632
}
2733
`
2834
],
29-
template: ` <div [style.height.px]="currentHeight()">
30-
<code-editor
31-
[languages]="languages"
32-
[language]="currentLanguage"
33-
[ngModel]="content"
34-
[theme]="currentTheme"
35-
[readonly]="true"
36-
[lineWrapping]="true"
37-
></code-editor>
38-
</div>`
35+
templateUrl: 'files-viewer-text.component.html'
3936
})
4037
export class FilesViewerTextComponent implements OnInit, OnDestroy {
4138
currentHeight = input<number>()
4239
file = input<FileModel>()
40+
isReadonly = signal(true)
41+
isReadable = signal(false)
42+
isModified = signal(false)
43+
protected openSearchPanel = openSearchPanel
4344
protected content: string
4445
protected currentLanguage = undefined
4546
protected readonly languages: LanguageDescription[] = languages
4647
protected currentTheme: any = 'light'
48+
protected readonly icons = { faFloppyDisk, faLock, faLockOpen, faMagnifyingGlass }
49+
protected readonly locale = inject<L10nLocale>(L10N_LOCALE)
4750
private readonly layout = inject(LayoutService)
4851
private readonly http = inject(HttpClient)
52+
private readonly filesUpload = inject(FilesUploadService)
4953
private subscription = this.layout.switchTheme.subscribe((layout: string) => (this.currentTheme = layout === themeDark ? 'dark' : 'light'))
5054
private readonly maxSize = 5242880 // 5MB
5155

56+
toggleReadonly() {
57+
this.isReadonly.update((value) => !value)
58+
}
59+
60+
async save() {
61+
const file = new File([new Blob([this.content])], this.file().name, { type: 'text/plain' })
62+
await this.filesUpload.addFiles([file], true)
63+
this.isModified.set(false)
64+
}
65+
5266
ngOnInit() {
5367
const language: LanguageDescription = LanguageDescription.matchFilename(languages, this.file().name)
5468
if (language?.name || this.file().size <= this.maxSize) {
5569
this.currentLanguage = language?.name
70+
this.isReadable.set(true)
5671
this.http.get(this.file().dataUrl, { responseType: 'text' }).subscribe((data: string) => (this.content = data))
5772
} else {
73+
this.isReadable.set(false)
5874
this.content = this.layout.translateString('This file contains binary data that can not be read')
5975
}
6076
}

0 commit comments

Comments
 (0)