finish theme engine

This commit is contained in:
Nolan Lawson 2018-01-13 18:59:49 -08:00
parent f69797544d
commit 1f9029f457
10 changed files with 135 additions and 56 deletions

View file

@ -10,6 +10,7 @@ const pify = require('pify')
const writeFile = pify(fs.writeFile.bind(fs)) const writeFile = pify(fs.writeFile.bind(fs))
const readdir = pify(fs.readdir.bind(fs)) const readdir = pify(fs.readdir.bind(fs))
const render = pify(sass.render.bind(sass)) const render = pify(sass.render.bind(sass))
const now = require('performance-now')
const globalScss = path.join(__dirname, '../scss/global.scss') const globalScss = path.join(__dirname, '../scss/global.scss')
const defaultThemeScss = path.join(__dirname, '../scss/themes/_default.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') const assetsDir = path.join(__dirname, '../assets')
function doWatch() { function doWatch() {
var start = now()
chokidar.watch(scssDir).on('change', debounce(() => { chokidar.watch(scssDir).on('change', debounce(() => {
compileGlobalSass() console.log('Recompiling SCSS...')
compileThemesSass() Promise.all([
compileGlobalSass(),
compileThemesSass()
]).then(() => {
console.log('Recompiled SCSS in ' + (now() - start) + 'ms')
})
}, 500)) }, 500))
chokidar.watch() chokidar.watch()
} }
@ -41,7 +48,8 @@ async function compileThemesSass() {
let files = (await readdir(themesScssDir)).filter(file => !path.basename(file).startsWith('_')) let files = (await readdir(themesScssDir)).filter(file => !path.basename(file).startsWith('_'))
await Promise.all(files.map(async file => { await Promise.all(files.map(async file => {
let res = await render({file: path.join(themesScssDir, 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
View file

@ -1803,6 +1803,11 @@
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz",
"integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=" "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": { "filename-regex": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
@ -4806,6 +4811,11 @@
"sha.js": "2.4.9" "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": { "pify": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",

View file

@ -20,6 +20,7 @@
"css-loader": "^0.28.7", "css-loader": "^0.28.7",
"express": "^4.16.2", "express": "^4.16.2",
"extract-text-webpack-plugin": "^3.0.2", "extract-text-webpack-plugin": "^3.0.2",
"fg-loadcss": "^2.0.1",
"font-awesome-svg-png": "^1.2.2", "font-awesome-svg-png": "^1.2.2",
"glob": "^7.1.2", "glob": "^7.1.2",
"idb": "^2.0.4", "idb": "^2.0.4",
@ -27,6 +28,7 @@
"node-fetch": "^1.7.3", "node-fetch": "^1.7.3",
"node-sass": "^4.7.2", "node-sass": "^4.7.2",
"npm-run-all": "^4.1.2", "npm-run-all": "^4.1.2",
"performance-now": "^2.1.0",
"pify": "^3.0.0", "pify": "^3.0.0",
"sapper": "^0.3.1", "sapper": "^0.3.1",
"serve-static": "^1.13.1", "serve-static": "^1.13.1",

View 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`)
}
}

View file

@ -7,23 +7,30 @@
<h1>{{params.instanceName}}</h1> <h1>{{params.instanceName}}</h1>
{{#if currentUser}} {{#if currentUser}}
<h2>Logged in as:</h2> <h2>Logged in as:</h2>
<div class="current-user"> <div class="current-user">
<img src="{{currentUser.avatar}}" /> <img src="{{currentUser.avatar}}" />
<a rel="noopener" target="_blank" href="{{currentUser.url}}">@{{currentUser.acct}}</a> <a rel="noopener" target="_blank" href="{{currentUser.url}}">@{{currentUser.acct}}</a>
<span class="acct-name">{{currentUser.display_name}}</span> <span class="acct-name">{{currentUser.display_name}}</span>
</div> </div>
<h2>Theme:</h2> <h2>Theme:</h2>
<form class="theme-chooser"> <form class="theme-chooser">
{{#each themes as theme}} {{#each themes as theme}}
<div class="theme-group"> <div class="theme-group">
<input type="radio" id="choice-theme-{{theme.name}}" <input type="radio" id="choice-theme-{{theme.name}}"
value="{{theme.name}}" checked="$currentTheme === theme.name" value="{{theme.name}}" checked="$currentTheme === theme.name"
bind:group="selectedTheme" on:change="onThemeChange()"> bind:group="selectedTheme" on:change="onThemeChange()">
<label for="choice-theme-{{theme.name}}">{{theme.label}}</label> <label for="choice-theme-{{theme.name}}">{{theme.label}}</label>
</div> </div>
{{/each}} {{/each}}
</form> </form>
<form class="instance-actions">
<button class="primary" disabled="$currentInstance === params.instanceName">
Switch to this instance
</button>
<button>Log out</button>
</form>
{{/if}} {{/if}}
</SettingsLayout> </SettingsLayout>
</Layout> </Layout>
@ -54,6 +61,15 @@
.theme-chooser label { .theme-chooser label {
margin: 2px 10px 0; margin: 2px 10px 0;
} }
.instance-actions {
width: 100%;
display: flex;
justify-content: right;
}
.instance-actions button {
margin: 0 20px;
flex-basis: 100%;
}
</style> </style>
<script> <script>
import { store } from '../../_utils/store' import { store } from '../../_utils/store'
@ -61,6 +77,7 @@
import SettingsLayout from '../_components/SettingsLayout.html' import SettingsLayout from '../_components/SettingsLayout.html'
import { getCurrentUser } from '../../_utils/mastodon/user' import { getCurrentUser } from '../../_utils/mastodon/user'
import { themes } from '../../_static/themes' import { themes } from '../../_static/themes'
import { switchToTheme } from '../../_utils/themeEngine'
export default { export default {
components: { components: {
@ -87,6 +104,9 @@
instanceThemes[instanceName] = newTheme instanceThemes[instanceName] = newTheme
this.store.set({instanceThemes: instanceThemes}) this.store.set({instanceThemes: instanceThemes})
this.store.save() this.store.save()
if (this.get('params').instanceName === this.store.get('currentInstance')) {
switchToTheme(newTheme)
}
} }
} }
} }

View file

@ -70,6 +70,21 @@
}, },
store: () => store, store: () => store,
methods: { 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) { onReceivedOauthCode: async function(code) {
let currentRegisteredInstanceName = this.store.get('currentRegisteredInstanceName') let currentRegisteredInstanceName = this.store.get('currentRegisteredInstanceName')
let currentRegisteredInstance = this.store.get('currentRegisteredInstance') let currentRegisteredInstance = this.store.get('currentRegisteredInstance')
@ -88,6 +103,8 @@
} }
this.store.set({ this.store.set({
instanceNameInSearch: '', instanceNameInSearch: '',
currentRegisteredInstanceName: null,
currentRegisteredInstance: null,
loggedInInstances: loggedInInstances, loggedInInstances: loggedInInstances,
currentInstance: currentRegisteredInstanceName, currentInstance: currentRegisteredInstanceName,
loggedInInstancesInOrder: loggedInInstancesInOrder loggedInInstancesInOrder: loggedInInstancesInOrder
@ -95,20 +112,6 @@
this.store.save() this.store.save()
goto('/') 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> </script>

View file

@ -62,29 +62,37 @@ button {
border: 1px solid var(--button-border); border: 1px solid var(--button-border);
cursor: pointer; cursor: pointer;
color: var(--button-text); 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 { p, label, input {
font-size: 1.3em; font-size: 1.3em;

View file

@ -46,4 +46,4 @@
--settings-list-item-border: $border-color; --settings-list-item-border: $border-color;
--settings-list-item-bg-active: darken($main-bg-color, 10%); --settings-list-item-bg-active: darken($main-bg-color, 10%);
--settings-list-item-bg-hover: darken($main-bg-color, 2%); --settings-list-item-bg-hover: darken($main-bg-color, 2%);
} }

View file

@ -7,6 +7,6 @@ $secondary-text-color: white;
@import "_base.scss"; @import "_base.scss";
body.theme-crimson { body.theme-scarlet {
@include baseTheme() @include baseTheme()
} }

View file

@ -25,6 +25,19 @@
%sapper.head% %sapper.head%
</head> </head>
<body> <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;"> <svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
<symbol id="pinafore-logo" viewBox="0 0 100 100"> <symbol id="pinafore-logo" viewBox="0 0 100 100">