feat: support profile theming

This commit is contained in:
Yarmo Mackenbach 2023-10-05 13:06:35 +02:00
parent df48b92732
commit 99217f6a3d
No known key found for this signature in database
GPG key ID: 3C57D093219103A3
8 changed files with 109 additions and 16 deletions

View file

@ -8,7 +8,8 @@
"ajv": "^8.6.3", "ajv": "^8.6.3",
"bent": "^7.3.12", "bent": "^7.3.12",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"doipjs": "^1.2.4", "colorjs.io": "^0.4.5",
"doipjs": "^1.2.5",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"express": "^4.17.1", "express": "^4.17.1",
"express-http-context2": "^1.0.0", "express-http-context2": "^1.0.0",

View file

@ -32,7 +32,7 @@ import bodyParserImport from 'body-parser'
import { rateLimit } from 'express-rate-limit' import { rateLimit } from 'express-rate-limit'
import { generateSignatureProfile, utils, generateWKDProfile, generateHKPProfile, generateAutoProfile, generateKeybaseProfile } from '../server/index.js' import { generateSignatureProfile, utils, generateWKDProfile, generateHKPProfile, generateAutoProfile, generateKeybaseProfile } from '../server/index.js'
import { Profile } from 'doipjs' import { Profile } from 'doipjs'
import { getMetaFromReq } from '../server/utils.js' import { generateProfileTheme, getMetaFromReq } from '../server/utils.js'
import logger from '../log.js' import logger from '../log.js'
const router = express.Router() const router = express.Router()
@ -133,6 +133,7 @@ router.get('/keybase/:username/:fingerprint', profileRateLimiter, async (req, re
router.get('/:id', profileRateLimiter, async (req, res) => { router.get('/:id', profileRateLimiter, async (req, res) => {
const data = await generateAutoProfile(req.params.id) const data = await generateAutoProfile(req.params.id)
const theme = generateProfileTheme(data)
const title = utils.generatePageTitle('profile', data) const title = utils.generatePageTitle('profile', data)
res.set('ariadne-identity-proof', data.identifier) res.set('ariadne-identity-proof', data.identifier)
res.render('profile', { res.render('profile', {
@ -140,6 +141,7 @@ router.get('/:id', profileRateLimiter, async (req, res) => {
data: data instanceof Profile ? data.toJSON() : data, data: data instanceof Profile ? data.toJSON() : data,
enable_message_encryption: false, enable_message_encryption: false,
enable_signature_verification: false, enable_signature_verification: false,
theme,
meta: getMetaFromReq(req) meta: getMetaFromReq(req)
}) })
}) })

View file

@ -28,6 +28,8 @@ if any, to sign a "copyright disclaimer" for the program, if necessary. For
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>. more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
*/ */
import { webcrypto as crypto } from 'crypto' import { webcrypto as crypto } from 'crypto'
import { Profile } from 'doipjs'
import Color from 'colorjs.io'
export async function computeWKDLocalPart (localPart) { export async function computeWKDLocalPart (localPart) {
const localPartEncoded = new TextEncoder().encode(localPart.toLowerCase()) const localPartEncoded = new TextEncoder().encode(localPart.toLowerCase())
@ -87,3 +89,51 @@ export function getMetaFromReq (req) {
} }
} }
} }
export function generateProfileTheme (/** @type {Profile} */ profile) {
if (!(profile && profile instanceof Profile)) return null
if (!profile.personas[profile.primaryPersonaIndex].themeColor) return null
let base
try {
base = new Color(profile.personas[profile.primaryPersonaIndex].themeColor)
} catch (_) {
return null
}
if (base.to('hsl').hsl[0].isNaN) return null
if (base.to('hsl').hsl[2] === 0) return null
const primaryLight = base.to('hsl')
primaryLight.hsl[2] = 40
const primaryDark = base.to('hsl')
primaryDark.hsl[2] = 80
const primarySubtleLight = base.to('hsl')
primarySubtleLight.hsl[2] = 50
const primarySubtleDark = base.to('hsl')
primarySubtleDark.hsl[2] = 70
const backgroundLight = base.to('hsl')
backgroundLight.hsl[2] = 98
const backgroundDark = base.to('hsl')
backgroundDark.hsl[1] = 20
backgroundDark.hsl[2] = 5
return {
base: base.toString({ format: 'hex' }),
primary: {
light: primaryLight.toString(),
dark: primaryDark.toString()
},
primarySubtle: {
light: primarySubtleLight.toString(),
dark: primarySubtleDark.toString()
},
background: {
light: backgroundLight.toString(),
dark: backgroundDark.toString()
}
}
}

View file

@ -58,8 +58,9 @@ kx-claim {
} }
hr { hr {
margin: 8px 0;
border: none; border: none;
border-top: 2px solid var(--background-color); border-top: 2px solid var(--header-background-color);
} }
.content { .content {

View file

@ -108,6 +108,18 @@ a.button {
border-color: var(--button-border-color-hover); border-color: var(--button-border-color-hover);
color: var(--button-text-color-hover); color: var(--button-text-color-hover);
} }
&.themed {
padding: 8px 12px;
color: var(--text-color-inverse);
background-color: var(--primary-color);
border: 0;
&:hover {
color: var(--text-color-inverse);
background-color: var(--primary-color-subtle);
}
}
} }
button { button {

View file

@ -64,14 +64,25 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
} }
:root { :root {
--primary-color: var(--purple-700); --primary-color-light: var(--purple-700);
--primary-color-subtle: var(--purple-400); --primary-color-dark: var(--purple-300);
--body-background-color: #fafafa; --primary-color-subtle-light: var(--purple-400);
--primary-color-subtle-dark: var(--purple-500);
--background-color-light: var(#fafafa);
--background-color-dark: var(#0a0a0a);
}
:root {
--primary-color: var(--primary-color-light);
--primary-color-subtle: var(--primary-color-subtle-light);
--body-background-color: var(--background-color-light);
--section-background-color: var(--white); --section-background-color: var(--white);
--text-color: var(--grey-800); --text-color: var(--grey-800);
--text-color-subtle: var(--grey-500); --text-color-subtle: var(--grey-500);
--text-color-inverse: var(--white);
--h1-color: var(--text-color); --h1-color: var(--text-color);
--h2-color: var(--text-color); --h2-color: var(--text-color);
--h2-small-color: var(--primary-color-subtle); --h2-small-color: var(--primary-color-subtle);
@ -82,7 +93,7 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
--link-color: var(--blue-700); --link-color: var(--blue-700);
--link-color-subtle: var(--text-color); --link-color-subtle: var(--text-color);
--link-color-hover: var(--purple-700); --link-color-hover: var(--primary-color);
--line-color-subtle: var(--grey-200); --line-color-subtle: var(--grey-200);
@ -105,14 +116,15 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
color-scheme: dark; color-scheme: dark;
--primary-color: var(--purple-300); --primary-color: var(--primary-color-dark);
--primary-color-subtle: var(--purple-500); --primary-color-subtle: var(--primary-color-subtle-dark);
--body-background-color: #0a0a0a; --body-background-color: var(--background-color-dark);
--section-background-color: var(--grey-900); --section-background-color: var(--grey-900);
--text-color: var(--grey-50); --text-color: var(--grey-50);
--text-color-subtle: var(--grey-300); --text-color-subtle: var(--grey-300);
--text-color-inverse: var(--grey-800);
--h1-color: var(--text-color); --h1-color: var(--text-color);
--h2-color: var(--text-color); --h2-color: var(--text-color);
--h2-small-color: var(--primary-color-subtle); --h2-small-color: var(--primary-color-subtle);

View file

@ -111,6 +111,16 @@ block content
input(type='submit', name='submit', value='Generate profile') input(type='submit', name='submit', value='Generate profile')
unless (isSignature && !signature) unless (isSignature && !signature)
if (theme)
style
| :root {
| --primary-color-light: #{theme.primary.light};
| --primary-color-dark: #{theme.primary.dark};
| --primary-color-subtle-light: #{theme.primarySubtle.light};
| --primary-color-subtle-dark: #{theme.primarySubtle.dark};
| --background-color-light: #{theme.background.light};
| --background-color-dark: #{theme.background.dark};
| }
.profile__header .profile__header
img.profile__avatar.u-logo(src=data.personas[data.primaryPersonaIndex].avatarUrl alt="avatar") img.profile__avatar.u-logo(src=data.personas[data.primaryPersonaIndex].avatarUrl alt="avatar")
@ -132,8 +142,8 @@ block content
+generatePersona(persona, false) +generatePersona(persona, false)
h2 Profile h2 Profile
if data.verifiers.length > 0 if data.verifiers.length > 0
button(onClick=`showQR('${data.verifiers[0].url}', 'profile_verifier_url')` aria-label='Show profile ID QR') Keyoxide profile QR button.themed(onClick=`showQR('${data.verifiers[0].url}', 'profile_verifier_url')` aria-label='Show profile ID QR') Keyoxide profile QR
button(onClick=`showQR('${data.identifier}', 'profile_identifier')` aria-label='Show profile ID QR') Profile ID QR button.themed(onClick=`showQR('${data.identifier}', 'profile_identifier')` aria-label='Show profile ID QR') Profile ID QR
section section
h2 Profile information h2 Profile information

View file

@ -1579,6 +1579,11 @@ colorette@^2.0.14:
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
colorjs.io@^0.4.5:
version "0.4.5"
resolved "https://registry.yarnpkg.com/colorjs.io/-/colorjs.io-0.4.5.tgz#7775f787ff90aca7a38f6edb7b7c0f8cce1e6418"
integrity sha512-yCtUNCmge7llyfd/Wou19PMAcf5yC3XXhgFoAh6zsO2pGswhUPBaaUh8jzgHnXtXuZyFKzXZNAnyF5i+apICow==
colorspace@1.1.x: colorspace@1.1.x:
version "1.1.4" version "1.1.4"
resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243"
@ -1837,10 +1842,10 @@ doctypes@^1.1.0:
resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9"
integrity sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ== integrity sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==
doipjs@^1.2.4: doipjs@^1.2.5:
version "1.2.4" version "1.2.5"
resolved "https://registry.yarnpkg.com/doipjs/-/doipjs-1.2.4.tgz#f980ac4a3ca71b4496530a1e877230b7b6a4cec0" resolved "https://registry.yarnpkg.com/doipjs/-/doipjs-1.2.5.tgz#98d67dad67ec0d0afafcda4e4740c93a964eecd3"
integrity sha512-zs/kL/Yc3H4X8SrWW0zf6w6RLWDw2LQ6w/tgx8Kirqf7eMtOO0rur9/0ASCeHvoqRr7O6W7dXnW7nD/I5ZgxDg== integrity sha512-366rS4LpzrGUgf1wfxjLpS46T4whB15m4dW8DfVYLLFYqNhwDVirRhysaexCQ8B4Ns+eCSa7qfpVszSxwGmgkA==
dependencies: dependencies:
"@openpgp/hkp-client" "^0.0.3" "@openpgp/hkp-client" "^0.0.3"
"@openpgp/wkd-client" "^0.0.4" "@openpgp/wkd-client" "^0.0.4"