Skip to content

Commit e4720dc

Browse files
kerbilgpedrolamas
andauthored
feat(ui): custom stylesheet and background image (#795)
Signed-off-by: Kerim Bilgic <bastelklug@pfusch.eu> Co-authored-by: Pedro Lamas <pedrolamas@gmail.com>
1 parent 9424f7f commit e4720dc

10 files changed

Lines changed: 154 additions & 30 deletions

File tree

src/App.vue

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
<v-icon>$estop</v-icon>
3535
</v-btn>
3636

37-
<v-main>
37+
<v-main :style="customBackgroundImageStyle">
3838
<!-- <pre>authenticated {{ authenticated }}, socketConnected {{ socketConnected }}, apiConnected {{ apiConnected }}</pre> -->
3939
<v-container
4040
fluid
@@ -79,10 +79,11 @@
7979
</template>
8080

8181
<script lang="ts">
82-
import { Component, Mixins } from 'vue-property-decorator'
82+
import { Component, Mixins, Watch } from 'vue-property-decorator'
8383
import { EventBus, FlashMessage } from '@/eventBus'
84-
import StateMixin from './mixins/state'
85-
import { Waits } from './globals'
84+
import StateMixin from '@/mixins/state'
85+
import FilesMixin from '@/mixins/files'
86+
import { Waits } from '@/globals'
8687
import { LinkPropertyHref } from 'vue-meta'
8788
8889
@Component<App>({
@@ -99,10 +100,11 @@ import { LinkPropertyHref } from 'vue-meta'
99100
}
100101
}
101102
})
102-
export default class App extends Mixins(StateMixin) {
103+
export default class App extends Mixins(StateMixin, FilesMixin) {
103104
toolsdrawer: boolean | null = null
104105
navdrawer: boolean | null = null
105106
showUpdateUI = false
107+
customBackgroundImageStyle: Record<string, string> = {}
106108
107109
flashMessage: FlashMessage = {
108110
open: false,
@@ -213,6 +215,55 @@ export default class App extends Mixins(StateMixin) {
213215
return theme.currentTheme.primary
214216
}
215217
218+
get customStyleSheet () {
219+
return this.$store.getters['config/getCustomThemeFile']('custom', ['.css'])
220+
}
221+
222+
@Watch('customStyleSheet')
223+
async onCustomStyleSheet (value: string) {
224+
if (!value) {
225+
return
226+
}
227+
228+
const url = await this.createFileUrl(value, 'config')
229+
230+
const oldCustomStylesheet = document.getElementById('customStylesheet')
231+
232+
if (oldCustomStylesheet) {
233+
oldCustomStylesheet.setAttribute('href', url)
234+
return
235+
}
236+
237+
const linkElement = document.createElement('link')
238+
239+
linkElement.rel = 'stylesheet'
240+
linkElement.type = 'text/css'
241+
linkElement.id = 'customStylesheet'
242+
linkElement.href = url
243+
244+
document.head.appendChild(linkElement)
245+
}
246+
247+
get customBackgroundImage () {
248+
return this.$store.getters['config/getCustomThemeFile']('background', ['.png', '.jpg', '.jpeg', '.gif'])
249+
}
250+
251+
@Watch('customBackgroundImage')
252+
async onCustomBackgroundImage (value: string) {
253+
if (!value) {
254+
return
255+
}
256+
257+
const url = await this.createFileUrl(value, 'config')
258+
259+
this.customBackgroundImageStyle = {
260+
backgroundImage: `url(${url})`,
261+
backgroundSize: 'cover',
262+
backgroundAttachment: 'fixed',
263+
backgroundRepeat: 'no-repeat'
264+
}
265+
}
266+
216267
mounted () {
217268
// this.onLoadLocale(this.$i18n.locale)
218269
EventBus.bus.$on('flashMessage', (payload: FlashMessage) => {

src/api/socketActions.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,18 @@ export const SocketActions = {
474474
)
475475
},
476476

477+
async serverFilesListRoot (root: string) {
478+
const wait = `${Waits.onFileSystem}${root}`
479+
baseEmit(
480+
'server.files.list',
481+
{
482+
dispatch: 'files/onServerFilesListRoot',
483+
wait,
484+
params: { root }
485+
}
486+
)
487+
},
488+
477489
async serverFilesMove (source: string, dest: string) {
478490
const wait = Waits.onFileSystem
479491
baseEmit(

src/mixins/files.ts

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -119,35 +119,40 @@ export default class FilesMixin extends Vue {
119119

120120
/**
121121
* Will download a file by filepath via a standard browser link.
122-
* Implements a oneshot.
123122
* @param filename The filename to retrieve.
124123
* @param path The path to the file.
125124
*/
126-
downloadFile (filename: string, path: string) {
125+
async downloadFile (filename: string, path: string) {
127126
// Grab a oneshot.
128-
authApi.getOneShot()
129-
.then(response => response.data.result)
130-
.then((token) => {
131-
// Sort out the filepath and url.
132-
const filepath = (path) ? `${path}/${filename}` : `${filename}`
133-
const url = encodeURI(
134-
this.apiUrl +
135-
'/server/files/' + filepath +
136-
'?token=' + token +
137-
'&date=' + new Date().getTime())
127+
try {
128+
const url = encodeURI(await this.createFileUrl(filename, path))
138129

139-
// Create a link, handle its click - and finally remove it again.
140-
const link = document.createElement('a')
141-
link.href = url
142-
link.setAttribute('download', filename)
143-
link.setAttribute('target', '_blank')
144-
document.body.appendChild(link)
145-
link.click()
146-
document.body.removeChild(link)
147-
})
148-
.catch(() => {
149-
// Likely a 401.
150-
})
130+
// Create a link, handle its click - and finally remove it again.
131+
const link = document.createElement('a')
132+
link.href = url
133+
link.setAttribute('download', filename)
134+
link.setAttribute('target', '_blank')
135+
document.body.appendChild(link)
136+
link.click()
137+
document.body.removeChild(link)
138+
} catch {
139+
// Likely a 401.
140+
}
141+
}
142+
143+
/**
144+
* Creates a url for a file by filepath.
145+
* Implements a oneshot.
146+
* @param filename The filename.
147+
* @param path The path to the file.
148+
* @returns The url for the requested file
149+
*/
150+
async createFileUrl (filename: string, path: string) {
151+
const token = (await authApi.getOneShot()).data.result
152+
153+
const filepath = (path) ? `${path}/${filename}` : `${filename}`
154+
155+
return `${this.apiUrl}/server/files/${filepath}?token=${token}&date=${Date.now()}`
151156
}
152157

153158
/**

src/store/config/getters.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Heater, Fan } from '../printer/types'
66
import tinycolor from '@ctrl/tinycolor'
77
import { AppTableHeader } from '@/types'
88
import { AppTablePartialHeader } from '@/types/tableheaders'
9+
import { RootFile } from '../files/types'
910

1011
export const getters: GetterTree<ConfigState, RootState> = {
1112
getCurrentInstance: (state) => {
@@ -93,6 +94,20 @@ export const getters: GetterTree<ConfigState, RootState> = {
9394
return r
9495
},
9596

97+
getCustomThemeFile: (state, getters, rootState, rootGetters) => (filename: string, extensions: string[]) => {
98+
const files = rootGetters['files/getRootFiles']('config') as RootFile[]
99+
100+
if (files) {
101+
for (const extension of extensions) {
102+
const path = `.fluidd-theme/${filename}${extension}`
103+
104+
if (files.some(f => f.path === path)) {
105+
return path
106+
}
107+
}
108+
}
109+
},
110+
96111
/**
97112
* Returns a default theme preset for first init / when reset
98113
*/

src/store/files/actions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ export const actions: ActionTree<FilesState, RootState> = {
7474
commit('setServerFilesGetDirectory', { root, directory: { path, items } })
7575
},
7676

77+
async onServerFilesListRoot ({ commit }, payload) {
78+
const root = payload.__request__.params.root
79+
80+
commit('setServerFilesListRoot', { root, files: payload })
81+
},
82+
7783
/**
7884
* If we request the metadata (a file..) then we load and update here.
7985
*/

src/store/files/getters.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ export const getters: GetterTree<FilesState, RootState> = {
1616
}
1717
},
1818

19+
getRootFiles: (state) => (root: FileRoot) => {
20+
return state.rootFiles[root]
21+
},
22+
1923
/**
2024
* Indicates if a root is available.
2125
*/

src/store/files/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ export const defaultState = (): FilesState => {
1515
used: 0,
1616
free: 0
1717
},
18+
rootFiles: {
19+
gcodes: [],
20+
config: [],
21+
config_examples: [],
22+
docs: [],
23+
logs: [],
24+
timelapse: []
25+
},
1826
gcodes: [],
1927
config: [],
2028
config_examples: [],

src/store/files/mutations.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ export const mutations: MutationTree<FilesState> = {
3131
}
3232
},
3333

34+
setServerFilesListRoot (state, payload) {
35+
const root = payload.root as FileRoot
36+
37+
state.rootFiles[root] = payload.files
38+
},
39+
3440
setFileUpdate (state, payload: FileUpdate) {
3541
const root = payload.root as FileRoot
3642
const paths = payload.paths

src/store/files/types.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { HistoryItem } from '@/store/history/types'
22

33
export interface FilesState {
4-
[key: string]: Files[] | FilesUpload[] | FileDownload | string[] | CurrentPaths | DiskUsage | null;
54
uploads: FilesUpload[];
65
download: FileDownload | null;
76
currentPaths: CurrentPaths;
87
disk_usage: DiskUsage;
8+
rootFiles: RootFiles;
99

1010
gcodes: Files[];
1111
config: Files[];
@@ -143,3 +143,19 @@ export interface FilePreviewState {
143143
type: string;
144144
appFile?: AppFile;
145145
}
146+
147+
export interface RootFiles {
148+
gcodes: RootFile[];
149+
config: RootFile[];
150+
config_examples: RootFile[];
151+
docs: RootFile[];
152+
logs: RootFile[];
153+
timelapse: RootFile[];
154+
}
155+
156+
export interface RootFile {
157+
path: string;
158+
modified: number;
159+
size: number;
160+
permissions: string;
161+
}

src/store/socket/actions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const actions: ActionTree<SocketState, RootState> = {
3232
if (payload === true) {
3333
SocketActions.serverInfo()
3434
SocketActions.identify()
35+
SocketActions.serverFilesListRoot('config')
3536
}
3637
},
3738

0 commit comments

Comments
 (0)