1
+ <template >
2
+ <div >
3
+ <form ref =" propertiesContainer" >
4
+ <div v-for =" (property, key) in customTheme" :key =" key" class =" property" >
5
+ <label :for =" String(key)" >{{ key }} ({{ property.description }})</label >
6
+ <input type =" color" :name =" String(key)" v-model =" customTheme[key]?.color" class =" customColorInput"
7
+ @input =" updateTheme" />
8
+ </div >
9
+ <a id =" downloadThemeFile" style =" display : none " ></a >
10
+ </form >
11
+
12
+ <button @click =" applyTheme" >Apply Theme</button >
13
+ <button @click =" importTheme" >Import Theme</button >
14
+ <button @click =" exportTheme" >Export Theme</button >
15
+
16
+ <input type =" file" id =" importThemeFile" style =" display : none " @change =" handleFileImport" />
17
+ </div >
18
+ </template >
19
+
20
+ <script lang="ts">
21
+ import { defineComponent , reactive , ref , onMounted } from " vue" ;
22
+ import { dots } from " ../canvasApi" ;
23
+ import themeOptions from " ./themes" ;
24
+ import { updateThemeForStyle } from " ./themer" ;
25
+ import { CreateAbstraction } from " ./customThemeAbstraction" ;
26
+
27
+ export default defineComponent ({
28
+ name: " CustomColorThemes" ,
29
+ setup() {
30
+ // Reactive state for custom theme
31
+ const customTheme = reactive <{ [key : string ]: { color: string ; description: string ; ref: string [] } }>(CreateAbstraction (themeOptions [" Custom Theme" ]) as { [key : string ]: { color: string ; description: string ; ref: string [] } });
32
+
33
+ // Reference to the form container
34
+ const propertiesContainer = ref <HTMLFormElement | null >(null );
35
+
36
+ // Function to update the theme and background
37
+ const updateBG = () => dots (true , false , true );
38
+
39
+ // Update theme when color input changes
40
+ const updateTheme = (event : Event ) => {
41
+ const target = event .target as HTMLInputElement ;
42
+ const key = target .name ;
43
+ if (! customTheme [key ]) {
44
+ console .error (` Theme property ${key } not found ` );
45
+ return ;
46
+ }
47
+ (customTheme [key ] as { color: string ; description: string ; ref: string [] }).color = target .value ;
48
+ try {
49
+ customTheme [key ].ref .forEach ((property : string ) => {
50
+ themeOptions [" Custom Theme" ][property ] = target .value ;
51
+ });
52
+ updateThemeForStyle (" Custom Theme" );
53
+ updateBG ();
54
+ } catch (error ) {
55
+ console .error (' Failed to update theme:' , error );
56
+ }
57
+ };
58
+
59
+ // Apply the custom theme
60
+ const applyTheme = () => {
61
+ try {
62
+ if (typeof localStorage === ' undefined' ) {
63
+ throw new Error (' localStorage is not available' );
64
+ }
65
+ localStorage .setItem (" theme" , " Custom Theme" );
66
+ localStorage .setItem (" Custom Theme" , JSON .stringify (themeOptions [" Custom Theme" ]));
67
+ updateThemeForStyle (" Custom Theme" );
68
+ updateBG ();
69
+ } catch (error ) {
70
+ console .error (' Failed to save theme:' , error );
71
+ // Fallback: Apply theme without saving
72
+ updateThemeForStyle (" Custom Theme" );
73
+ updateBG ();
74
+ }
75
+ };
76
+
77
+ // Import the custom theme from a JSON file
78
+ const importTheme = () => {
79
+ const importThemeFile = document .getElementById (" importThemeFile" ) as HTMLInputElement ;
80
+ importThemeFile .click ();
81
+ };
82
+
83
+ // Export the custom theme as a JSON file
84
+ const exportTheme = () => {
85
+ const dlAnchorElem = document .getElementById (" downloadThemeFile" ) as HTMLAnchorElement ;
86
+ dlAnchorElem .setAttribute (
87
+ " href" ,
88
+ ` data:text/json;charset=utf-8,${encodeURIComponent (
89
+ JSON .stringify (themeOptions [" Custom Theme" ])
90
+ )} `
91
+ );
92
+ dlAnchorElem .setAttribute (" download" , " CV_CustomTheme.json" );
93
+ dlAnchorElem .click ();
94
+ };
95
+
96
+ // Handle file import for custom theme
97
+ const handleFileImport = (event : Event ) => {
98
+ const target = event .target as HTMLInputElement ;
99
+ const file = target .files ?.[0 ];
100
+ if (! file ) {
101
+ alert (" No file selected!" );
102
+ return ;
103
+ }
104
+
105
+ if (! file .name .toLowerCase ().endsWith (' .json' )) {
106
+ alert (" Please select a JSON file!" );
107
+ target .value = " " ;
108
+ return ;
109
+ }
110
+
111
+ if (file .size > 1024 * 1024 ) { // 1MB limit
112
+ alert (" File is too large!" );
113
+ target .value = " " ;
114
+ return ;
115
+ }
116
+
117
+ const reader = new FileReader ();
118
+
119
+ reader .onload = (e ) => {
120
+ try {
121
+ if (! e .target ?.result ) {
122
+ throw new Error (" Failed to read file" );
123
+ }
124
+ const lines = JSON .parse (e .target .result as string );
125
+ if (! lines || typeof lines !== ' object' ) {
126
+ throw new Error (" Invalid theme format" );
127
+ }
128
+ Object .assign (customTheme , CreateAbstraction (lines ));
129
+ themeOptions [" Custom Theme" ] = lines ;
130
+ updateThemeForStyle (" Custom Theme" );
131
+ updateBG ();
132
+ } catch (error ) {
133
+ console .error (' Failed to parse theme:' , error );
134
+ alert (" Invalid theme file!" );
135
+ }
136
+ };
137
+
138
+ reader .onerror = () => {
139
+ console .error (' Failed to read file' );
140
+ alert (" Failed to read file!" );
141
+ };
142
+
143
+ reader .readAsText (file );
144
+ target .value = " " ; // Reset file input
145
+ };
146
+
147
+ // Simulate a delay to update the theme on component mount
148
+ onMounted (() => {
149
+ updateThemeForStyle (" Custom Theme" );
150
+ updateBG ();
151
+ });
152
+
153
+ return {
154
+ customTheme ,
155
+ propertiesContainer ,
156
+ updateTheme ,
157
+ applyTheme ,
158
+ exportTheme ,
159
+ importTheme ,
160
+ handleFileImport ,
161
+ };
162
+ },
163
+ });
164
+ </script >
165
+
166
+ <style scoped>
167
+ .property {
168
+ margin-bottom : 10px ;
169
+ }
170
+
171
+ .customColorInput {
172
+ margin-left : 10px ;
173
+ }
174
+ </style >
0 commit comments