forked from cybrespace/pinafore
		
	finish theme engine
This commit is contained in:
		
							parent
							
								
									f69797544d
								
							
						
					
					
						commit
						1f9029f457
					
				
					 10 changed files with 135 additions and 56 deletions
				
			
		|  | @ -10,6 +10,7 @@ const pify = require('pify') | |||
| const writeFile = pify(fs.writeFile.bind(fs)) | ||||
| const readdir = pify(fs.readdir.bind(fs)) | ||||
| const render = pify(sass.render.bind(sass)) | ||||
| const now = require('performance-now') | ||||
| 
 | ||||
| const globalScss = path.join(__dirname, '../scss/global.scss') | ||||
| const defaultThemeScss = path.join(__dirname, '../scss/themes/_default.scss') | ||||
|  | @ -19,9 +20,15 @@ const themesScssDir = path.join(__dirname, '../scss/themes') | |||
| const assetsDir = path.join(__dirname, '../assets') | ||||
| 
 | ||||
| function doWatch() { | ||||
|   var start = now() | ||||
|   chokidar.watch(scssDir).on('change', debounce(() => { | ||||
|     compileGlobalSass() | ||||
|     compileThemesSass() | ||||
|     console.log('Recompiling SCSS...') | ||||
|     Promise.all([ | ||||
|       compileGlobalSass(), | ||||
|       compileThemesSass() | ||||
|     ]).then(() => { | ||||
|       console.log('Recompiled SCSS in ' + (now() - start) + 'ms') | ||||
|     }) | ||||
|   }, 500)) | ||||
|   chokidar.watch() | ||||
| } | ||||
|  | @ -41,7 +48,8 @@ async function compileThemesSass() { | |||
|   let files = (await readdir(themesScssDir)).filter(file => !path.basename(file).startsWith('_')) | ||||
|   await Promise.all(files.map(async file => { | ||||
|     let res = await render({file: path.join(themesScssDir, file)}) | ||||
|     await writeFile(path.join(assetsDir, path.basename(file).replace(/\.scss$/, '.css')), res.css, 'utf8') | ||||
|     let outputFilename = 'theme-' + path.basename(file).replace(/\.scss$/, '.css') | ||||
|     await writeFile(path.join(assetsDir, outputFilename), res.css, 'utf8') | ||||
|   })) | ||||
| } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										10
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -1803,6 +1803,11 @@ | |||
|       "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", | ||||
|       "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=" | ||||
|     }, | ||||
|     "fg-loadcss": { | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/fg-loadcss/-/fg-loadcss-2.0.1.tgz", | ||||
|       "integrity": "sha512-gFtSJjMMt9it0OhXz4wJQT46/LFUrJ2Db6U/fLtwClBEMEEjmVPSWSYrbGCyFwy47Cd4ULOpR+HSWXVkUKciaQ==" | ||||
|     }, | ||||
|     "filename-regex": { | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", | ||||
|  | @ -4806,6 +4811,11 @@ | |||
|         "sha.js": "2.4.9" | ||||
|       } | ||||
|     }, | ||||
|     "performance-now": { | ||||
|       "version": "2.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", | ||||
|       "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" | ||||
|     }, | ||||
|     "pify": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ | |||
|     "css-loader": "^0.28.7", | ||||
|     "express": "^4.16.2", | ||||
|     "extract-text-webpack-plugin": "^3.0.2", | ||||
|     "fg-loadcss": "^2.0.1", | ||||
|     "font-awesome-svg-png": "^1.2.2", | ||||
|     "glob": "^7.1.2", | ||||
|     "idb": "^2.0.4", | ||||
|  | @ -27,6 +28,7 @@ | |||
|     "node-fetch": "^1.7.3", | ||||
|     "node-sass": "^4.7.2", | ||||
|     "npm-run-all": "^4.1.2", | ||||
|     "performance-now": "^2.1.0", | ||||
|     "pify": "^3.0.0", | ||||
|     "sapper": "^0.3.1", | ||||
|     "serve-static": "^1.13.1", | ||||
|  |  | |||
							
								
								
									
										15
									
								
								routes/_utils/themeEngine.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								routes/_utils/themeEngine.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| import { loadCSS } from 'fg-loadcss'; | ||||
| 
 | ||||
| export function switchToTheme(themeName) { | ||||
|   let CL = document.body.classList | ||||
|   for (let i = 0; i < CL.length; i++) { | ||||
|     let clazz = CL.item(i) | ||||
|     if (clazz.startsWith('theme-')) { | ||||
|       CL.remove(clazz) | ||||
|     } | ||||
|   } | ||||
|   if (themeName !== 'default') { | ||||
|     CL.add(`theme-${themeName}`) | ||||
|     loadCSS(`/theme-${themeName}.css`) | ||||
|   } | ||||
| } | ||||
|  | @ -7,23 +7,30 @@ | |||
|     <h1>{{params.instanceName}}</h1> | ||||
| 
 | ||||
|     {{#if currentUser}} | ||||
|     <h2>Logged in as:</h2> | ||||
|     <div class="current-user"> | ||||
|       <img src="{{currentUser.avatar}}" /> | ||||
|       <a rel="noopener" target="_blank" href="{{currentUser.url}}">@{{currentUser.acct}}</a> | ||||
|       <span class="acct-name">{{currentUser.display_name}}</span> | ||||
|     </div> | ||||
|     <h2>Theme:</h2> | ||||
|     <form class="theme-chooser"> | ||||
|       {{#each themes as theme}} | ||||
|         <div class="theme-group"> | ||||
|           <input type="radio" id="choice-theme-{{theme.name}}" | ||||
|                  value="{{theme.name}}" checked="$currentTheme === theme.name" | ||||
|                  bind:group="selectedTheme" on:change="onThemeChange()"> | ||||
|           <label for="choice-theme-{{theme.name}}">{{theme.label}}</label> | ||||
|         </div> | ||||
|       {{/each}} | ||||
|     </form> | ||||
|       <h2>Logged in as:</h2> | ||||
|       <div class="current-user"> | ||||
|         <img src="{{currentUser.avatar}}" /> | ||||
|         <a rel="noopener" target="_blank" href="{{currentUser.url}}">@{{currentUser.acct}}</a> | ||||
|         <span class="acct-name">{{currentUser.display_name}}</span> | ||||
|       </div> | ||||
|       <h2>Theme:</h2> | ||||
|       <form class="theme-chooser"> | ||||
|         {{#each themes as theme}} | ||||
|           <div class="theme-group"> | ||||
|             <input type="radio" id="choice-theme-{{theme.name}}" | ||||
|                    value="{{theme.name}}" checked="$currentTheme === theme.name" | ||||
|                    bind:group="selectedTheme" on:change="onThemeChange()"> | ||||
|             <label for="choice-theme-{{theme.name}}">{{theme.label}}</label> | ||||
|           </div> | ||||
|         {{/each}} | ||||
|       </form> | ||||
| 
 | ||||
|       <form class="instance-actions"> | ||||
|         <button class="primary" disabled="$currentInstance === params.instanceName"> | ||||
|           Switch to this instance | ||||
|         </button> | ||||
|         <button>Log out</button> | ||||
|       </form> | ||||
|     {{/if}} | ||||
|   </SettingsLayout> | ||||
| </Layout> | ||||
|  | @ -54,6 +61,15 @@ | |||
|   .theme-chooser label { | ||||
|     margin: 2px 10px 0; | ||||
|   } | ||||
|   .instance-actions { | ||||
|     width: 100%; | ||||
|     display: flex; | ||||
|     justify-content: right; | ||||
|   } | ||||
|   .instance-actions button { | ||||
|     margin: 0 20px; | ||||
|     flex-basis: 100%; | ||||
|   } | ||||
| </style> | ||||
| <script> | ||||
|   import { store } from '../../_utils/store' | ||||
|  | @ -61,6 +77,7 @@ | |||
|   import SettingsLayout from '../_components/SettingsLayout.html' | ||||
|   import { getCurrentUser } from '../../_utils/mastodon/user' | ||||
|   import { themes } from '../../_static/themes' | ||||
|   import { switchToTheme } from '../../_utils/themeEngine' | ||||
| 
 | ||||
|   export default { | ||||
|     components: { | ||||
|  | @ -87,6 +104,9 @@ | |||
|         instanceThemes[instanceName] = newTheme | ||||
|         this.store.set({instanceThemes: instanceThemes}) | ||||
|         this.store.save() | ||||
|         if (this.get('params').instanceName === this.store.get('currentInstance')) { | ||||
|           switchToTheme(newTheme) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  |  | |||
|  | @ -70,6 +70,21 @@ | |||
|     }, | ||||
|     store: () => store, | ||||
|     methods: { | ||||
|       onSubmit: async function(event) { | ||||
|         event.preventDefault() | ||||
|         let instanceName = this.store.get('instanceNameInSearch') | ||||
|         instanceName = instanceName.replace(/^https?:\/\//, '').replace('/$', '') | ||||
|         // TODO: show toast error if you're already logged into this instance | ||||
|         let instanceData = await (await registerApplication(instanceName)).json() | ||||
|         // TODO: handle error | ||||
|         this.store.set({ | ||||
|           currentRegisteredInstanceName: instanceName, | ||||
|           currentRegisteredInstance: instanceData | ||||
|         }) | ||||
|         this.store.save() | ||||
|         let oauthUrl = generateAuthLink(instanceName, instanceData.client_id) | ||||
|         document.location.href = oauthUrl | ||||
|       }, | ||||
|       onReceivedOauthCode: async function(code) { | ||||
|         let currentRegisteredInstanceName = this.store.get('currentRegisteredInstanceName') | ||||
|         let currentRegisteredInstance = this.store.get('currentRegisteredInstance') | ||||
|  | @ -88,6 +103,8 @@ | |||
|         } | ||||
|         this.store.set({ | ||||
|           instanceNameInSearch: '', | ||||
|           currentRegisteredInstanceName: null, | ||||
|           currentRegisteredInstance: null, | ||||
|           loggedInInstances: loggedInInstances, | ||||
|           currentInstance: currentRegisteredInstanceName, | ||||
|           loggedInInstancesInOrder: loggedInInstancesInOrder | ||||
|  | @ -95,20 +112,6 @@ | |||
|         this.store.save() | ||||
|         goto('/') | ||||
|       }, | ||||
|       onSubmit: async function(event) { | ||||
|         event.preventDefault() | ||||
|         let instanceName = this.store.get('instanceNameInSearch') | ||||
|         instanceName = instanceName.replace(/^https?:\/\//, '').replace('/$', '') | ||||
|         let instanceData = await (await registerApplication(instanceName)).json() | ||||
|         // TODO: handle error | ||||
|         this.store.set({ | ||||
|           currentRegisteredInstanceName: instanceName, | ||||
|           currentRegisteredInstance: instanceData | ||||
|         }) | ||||
|         this.store.save() | ||||
|         let oauthUrl = generateAuthLink(instanceName, instanceData.client_id) | ||||
|         document.location.href = oauthUrl | ||||
|       }, | ||||
|     } | ||||
|   } | ||||
| </script> | ||||
|  | @ -62,29 +62,37 @@ button { | |||
|   border: 1px solid var(--button-border); | ||||
|   cursor: pointer; | ||||
|   color: var(--button-text); | ||||
| 
 | ||||
|   &:hover { | ||||
|     background: var(--button-bg-hover); | ||||
|   } | ||||
| 
 | ||||
|   &:active { | ||||
|     background: var(--button-bg-active); | ||||
|   } | ||||
| 
 | ||||
|   &[disabled] { | ||||
|     opacity: 0.35; | ||||
|     pointer-events: none; | ||||
|     cursor: not-allowed; | ||||
|   } | ||||
| 
 | ||||
|   &.primary { | ||||
|     border: 1px solid var(--button-primary-border); | ||||
|     background: var(--button-primary-bg); | ||||
|     color: var(--button-primary-text); | ||||
| 
 | ||||
|     &:hover { | ||||
|       background: var(--button-primary-bg-hover); | ||||
|     } | ||||
| 
 | ||||
|     &:active { | ||||
|       background: var(--button-primary-bg-active); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| button:hover { | ||||
|   background: var(--button-bg-hover); | ||||
| } | ||||
| 
 | ||||
| button:active { | ||||
|   background: var(--button-bg-active); | ||||
| } | ||||
| 
 | ||||
| button.primary { | ||||
|   border: 1px solid var(--button-primary-border); | ||||
|   background: var(--button-primary-bg); | ||||
|   color: var(--button-primary-text); | ||||
| } | ||||
| 
 | ||||
| button.primary:hover { | ||||
|   background: var(--button-primary-bg-hover); | ||||
| } | ||||
| 
 | ||||
| button.primary:active { | ||||
|   background: var(--button-primary-bg-active); | ||||
| } | ||||
| 
 | ||||
| p, label, input { | ||||
|   font-size: 1.3em; | ||||
|  |  | |||
|  | @ -46,4 +46,4 @@ | |||
|   --settings-list-item-border: $border-color; | ||||
|   --settings-list-item-bg-active: darken($main-bg-color, 10%); | ||||
|   --settings-list-item-bg-hover: darken($main-bg-color, 2%); | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -7,6 +7,6 @@ $secondary-text-color: white; | |||
| 
 | ||||
| @import "_base.scss"; | ||||
| 
 | ||||
| body.theme-crimson { | ||||
| body.theme-scarlet { | ||||
|   @include baseTheme() | ||||
| } | ||||
|  |  | |||
|  | @ -25,6 +25,19 @@ | |||
| 	%sapper.head% | ||||
| </head> | ||||
| <body> | ||||
|   <script> | ||||
| 		<!-- load theme on startup (handled outside of Sapper/Svelte) --> | ||||
| 		if (localStorage.store_currentInstance && localStorage.store_instanceThemes) { | ||||
| 		  let theme = JSON.parse(localStorage.store_instanceThemes)[JSON.parse(localStorage.store_currentInstance)] | ||||
| 			if (theme !== 'default') { | ||||
|         document.body.classList.add(`theme-${theme}`) | ||||
| 				let link = document.createElement('link') | ||||
| 				link.rel = 'stylesheet' | ||||
| 				link.href = `/theme-${theme}.css` | ||||
| 				document.head.appendChild(link) | ||||
|       } | ||||
| 		} | ||||
| 	</script> | ||||
| 	<svg xmlns="http://www.w3.org/2000/svg" style="display:none;"> | ||||
| 
 | ||||
| 		<symbol id="pinafore-logo" viewBox="0 0 100 100"> | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue