From b69ca2c48f91c2dff33904dd2917bbb86e861c26 Mon Sep 17 00:00:00 2001 From: "h.u.g.u.rp" Date: Wed, 18 Oct 2023 15:23:33 +0200 Subject: [PATCH] refactor appearence to `account.{theme,color_scheme}` --- {styles => assets}/theme-default.css | 150 ++++++++++++++++++++++-- {styles => assets}/theme-jellybeans.css | 0 assets/theme-r4.css | 121 ++++++++++++++++++- src/components/r4-app.js | 47 ++++++-- src/components/r4-user-account.js | 47 ++++++-- src/index.js | 2 - src/libs/db-listeners.js | 2 - styles/components/r4-layout.css | 136 --------------------- styles/components/r4-map.css | 3 - styles/index.css | 5 - styles/theme-r4.css | 122 ------------------- 11 files changed, 340 insertions(+), 295 deletions(-) rename {styles => assets}/theme-default.css (69%) rename {styles => assets}/theme-jellybeans.css (100%) delete mode 100644 styles/components/r4-layout.css delete mode 100644 styles/components/r4-map.css delete mode 100644 styles/index.css delete mode 100644 styles/theme-r4.css diff --git a/styles/theme-default.css b/assets/theme-default.css similarity index 69% rename from styles/theme-default.css rename to assets/theme-default.css index c5af9869..92ecfd53 100644 --- a/styles/theme-default.css +++ b/assets/theme-default.css @@ -3,10 +3,6 @@ - required defaults to make the work (though it should without CSS) */ -/* these imports are to make the player layout & map work */ -@import url('components/r4-layout.css'); -@import url('components/r4-map.css'); - /* these changes are "user personalisation" for color themes */ r4-app { /* sizes */ @@ -155,14 +151,151 @@ r4-app r4-layout::part(controls-button) { cursor: pointer; } +/* + the layout +*/ +r4-layout { + flex: 1; + display: flex; + flex-direction: column; + position: relative; +} +@media (min-width: 70rem) { + r4-layout { + flex-direction: row; + } +} + +/* can make the layout "panel" & its "main" part the full viewport height, + so on page change, all interface does not jump */ +r4-layout::part(panel), +r4-layout::part(main) { + display: flex; + flex-direction: column; + flex-grow: 1; +} + +r4-router > * { + display: flex; + flex-direction: column; + flex-grow: 1; +} +r4-route { + display: none; +} + +r4-layout::part(playback) { + display: flex; + flex-direction: column; + justify-content: flex-end; +} +/* basic styles for details and summary of the playback layout part */ +r4-layout::part(playback-details) { + background-color: var(--c-bg); + flex-grow: 1; + display: flex; + flex-direction: column; + height: 100%; + align-items: flex-end; +} +r4-layout::part(playback-summary) { + cursor: pointer; +} + +/* hide player slot content if not playing, and in 'close' state */ +r4-layout:not([is-playing]) slot[name='player']::slotted(*), +r4-layout[ui-state='close'] [slot='player']::slotted(*) { + display: none; +} +/* make the player summary not display its icon */ +r4-layout::part(playback-summary) { + display: flex; + justify-content: flex-end; +} + +/* the player */ +r4-layout r4-player, +r4-layout radio4000-player { + flex-grow: 1; +} + +/* hide the player summary when fullscreen */ +r4-layout[ui-state='fullscreen']::part(playback-summary) { + display: none; +} +/* and make the player full height */ +r4-layout[ui-state='fullscreen'] radio4000-player { + height: 100vh; +} + +/* hide the bullets of the playback menu (should all menu?), make it inline by default */ +r4-layout::part(playback-menu) { + list-style: none; + margin: 0; + padding: 0; + display: flex; +} + +/* by default, make the playback menu inline (mobile first) */ +r4-layout::part(playback-menu) { +} + +/* + ui-state = minimize +*/ + +/* keep the player visible, but small */ +r4-layout[ui-state='minimize'] radio4000-player { + min-height: auto; + max-height: var(--s-youtube); /* youtube guidelines for video, @to-keep */ +} +r4-layout r4-player radio4000-player .Layout { + border-color: var(--c-link); +} +/* overwrite some of radio4000-player library styles */ +r4-layout[ui-state='minimize'] radio4000-player .Layout-header, +r4-layout[ui-state='minimize'] radio4000-player .Layout-main { + display: none; +} + +/* + ui-state = minimal & dock + - make the playback sticky at top of viewport, + so "r4-app" router does not scroll to the top on page change + - also keep the "panel" over the "playback" +*/ + +/* make the "playback" sticky at the top of viewport */ +r4-layout[ui-state='minimize']::part(playback), +r4-layout[ui-state='dock']::part(playback) { + position: sticky; + bottom: 0; + z-index: 2; +} +@media (min-width: 70rem) { + r4-layout[ui-state='minimize']::part(playback), + r4-layout[ui-state='dock']::part(playback) { + top: 0; + bottom: auto; + max-height: 100vh; + } + r4-layout[is-playing]::part(playback) { + min-width: 30vw; + } +} + +/* keep panel over the playback */ +r4-layout[ui-state='minimize']::part(panel), +r4-layout[ui-state='dock']::part(panel) { + z-index: 1; + /* background-color: var(--c-bg); */ +} + /* can start targetting "more specific customization" */ /* make the panel min height, the viewport, so "route change" don't make the UI jump */ -r4-app r4-layout::part(panel) { - /* min-height: 100vh; */ -} r4-layout [slot='menu'] { } r4-layout [slot='main'], @@ -200,6 +333,9 @@ r4-map .ol-viewport { flex-direction: column; flex-grow: 1; } +r4-map .ol-viewport { + min-height: 30vh; +} r4-page-header, r4-page-main { diff --git a/styles/theme-jellybeans.css b/assets/theme-jellybeans.css similarity index 100% rename from styles/theme-jellybeans.css rename to assets/theme-jellybeans.css diff --git a/assets/theme-r4.css b/assets/theme-r4.css index e291236e..e0ca349e 100644 --- a/assets/theme-r4.css +++ b/assets/theme-r4.css @@ -1,3 +1,122 @@ +/* The default theme for R4 and components */ + +/* Shared between light and dark color schemes */ r4-app { - backround-color: red; + --font: Maison Neue, system-ui, sans-serif; + --s-font: 15px; + --s-line-height: 1.3125; + --s: 0.5rem; + + --c-light: hsl(0deg 0% 98%); + --c-lightgray: hsl(0deg 0% 80%); + /* --c-darkgray: hsl(0deg 0% 15%); */ + --c-dark: hsl(0deg 0% 10%); + /* --c-link: hotpink; */ + --c-purple: #5d1ae6; + --c-light--accent: var(--c-purple); + --c-dark--accent: var(--c-purple); + /* --c-link: var(--c-purple); */ + /* background-color: hsl(40, 6%, 90%); */ + /* color: hsl(0, 0%, 10%); */ + /* font-size: 15px; */ + --c-border: var(--c-dark); +} + +[color-scheme='light'] r4-app { + /* --c-card-background: var(--c-lightgray); */ + /* --c-input-background: hsl(0 0% 93%); */ + /* --c-button-background: hsl(0 0% 93%); */ + /* --c-text: hsl(0 0% 10%); */ + /* --c-border: hsla(0 10% 50% / 0.5); */ +} + +[color-scheme='dark'] r4-app { + /* --c-border: hsl(0 0% 5%); */ + /* --c-text: hsl(0 0% 90%); */ + /* --c-link: hsl(260 80% 90%); */ +} + +r4-layout { + padding: 1rem; +} + +r4-app [slot='main'] header:first-of-type { + margin-bottom: var(--s); +} + +r4-app [slot='main'] header:first-of-type h1 { + margin-top: var(--s); +} + +r4-app h1, +r4-app h2 { + line-height: 1.1; +} + +r4-app h1 { + font-size: 2rem; +} + +r4-app h2 { + font-size: 1.5rem; +} + +r4-app button { + display: inline-block; + font-size: 0.86667rem; + font-weight: bold; + border: 1px solid rgba(115, 115, 115, 0.4); + border-radius: 3px; + box-shadow: 0 3px 0 -1px rgba(204, 204, 204, 0.9); + box-sizing: border-box; + cursor: pointer; + line-height: 1; + min-height: 2.1em; + padding: 0.6em; + background-color: hsl(0, 0%, 98%); + color: var(--c-fg); + text-align: center; + text-decoration: none; +} + +r4-app[color-scheme='dark'] button { + color: var(--c-bg); +} + +r4-channel-card { + display: flex; + flex-flow: column nowrap; + /* background-color: var(--c-lightgray); */ + max-width: 21rem; + margin-bottom: var(--s); +} + +/* Maison Neue regular/italic + bold/italic from Cloudinary */ +@font-face { + font-family: 'Maison Neue'; + src: url('https://res.cloudinary.com/radio4000/raw/upload/v1492541388/maisonneueweb-book_ee98sm.woff2') + format('woff2'); + font-weight: normal; + font-style: normal; +} +@font-face { + font-family: 'Maison Neue'; + src: url('https://res.cloudinary.com/radio4000/raw/upload/v1492541388/maisonneueweb-bookitalic_szjrvl.woff2') + format('woff2'); + font-weight: normal; + font-style: italic; +} +@font-face { + font-family: 'Maison Neue'; + src: url('https://res.cloudinary.com/radio4000/raw/upload/v1492541388/maisonneueweb-bold_rvmbzr.woff2') + format('woff2'); + font-weight: bold; + font-style: normal; +} +@font-face { + font-family: 'Maison Neue'; + src: url('https://res.cloudinary.com/radio4000/raw/upload/v1492541388/maisonneueweb-bolditalic_awvcg5.woff2') + format('woff2'); + font-weight: bold; + font-style: italic; } diff --git a/src/components/r4-app.js b/src/components/r4-app.js index f55d463b..7d55a099 100644 --- a/src/components/r4-app.js +++ b/src/components/r4-app.js @@ -38,6 +38,10 @@ export default class R4App extends LitElement { playingChannel: {type: Object}, playingTrack: {type: Object}, + theme: {type: String, reflect: true}, + colorScheme: {type: String, attribute: 'color-scheme', reflect: true}, + themeStyles: {type: String, state: true}, + /* state for global usage */ store: {type: Object, state: true}, config: {type: Object, state: true}, @@ -159,17 +163,32 @@ export default class R4App extends LitElement { // From local storage // From database if (this.store.userAccount?.theme) { - this.setAttribute('color-scheme', this.store.userAccount.theme) - // - could fetch theme from static assets file by name - // - or could fetch it from github repo (see i4k.ntwrk) - // - coud apply dark/light version for each theme + this.theme = this.store.userAccount.theme + this.themeStyles = await this.fetchTheme(this.store.userAccount.theme) + } else { + this.theme = 'default' + } + if (this.store.userAccount?.color_scheme) { + this.colorScheme = this.store.userAccount.color_scheme } else { // From OS settings const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches - const theme = prefersDark ? 'dark' : 'light' - this.setAttribute('color-scheme', theme) + this.colorScheme = prefersDark ? 'dark' : 'light' } } + async fetchTheme(name) { + const url = `/theme-${name}.css` + const themeUrl = new URL(url, import.meta.url).href + const themeData = await fetch(themeUrl).then((res) => { + const text = res.text() + if (res.status === 404) { + return null + } else { + return text + } + }) + return themeData + } render() { if (!this.didLoad) return html` ` @@ -183,14 +202,15 @@ export default class R4App extends LitElement { ?is-playing=${this.isPlaying} @trackchanged=${this.onTrackChange} > + ${this.themeStyles ? this.renderThemeStyles() : null} ` } /* the default routers: - - one for the channel in CMS mode (all channels are accessible) - - one for when only one channel should be displayed in the UI - */ + - one for the channel in CMS mode (all channels are accessible) + - one for when only one channel should be displayed in the UI + */ renderRouter() { if (this.singleChannel) { return html` @@ -210,6 +230,15 @@ export default class R4App extends LitElement { ` } + renderThemeStyles() { + return html` + + ` + } onChannelSelect({detail}) { if (detail.channel) { diff --git a/src/components/r4-user-account.js b/src/components/r4-user-account.js index 0b4875ba..65f1a933 100644 --- a/src/components/r4-user-account.js +++ b/src/components/r4-user-account.js @@ -1,21 +1,34 @@ import {LitElement, html} from 'lit' import {sdk} from '@radio4000/sdk' -const THEMES = ['os', 'dark', 'light'] +const THEMES = ['default', 'jellybeans', 'r4'] +const COLOR_SCHEMES = ['dark', 'light'] export default class R4UserAccount extends LitElement { static properties = { + /* the account ID is the user ID (relationship) */ account: {type: Object, state: true}, } + get currentTheme() { + return this.account?.theme || THEMES[0] + } + + get prefersDark() { + return window.matchMedia('(prefers-color-scheme: dark)').matches + } + + /* save functions */ async saveTheme(value) { if (!value) return if (this.account) { - /* the account ID is the user ID (relationship) */ await sdk.supabase.from('accounts').update({theme: value}).eq('id', this.account.id) } } - get currentTheme() { - return this.account?.theme || THEMES[0] + async saveColorScheme(value) { + if (!value) return + if (this.account) { + await sdk.supabase.from('accounts').update({color_scheme: value}).eq('id', this.account.id) + } } render() { @@ -24,25 +37,43 @@ export default class R4UserAccount extends LitElement {

Theme

${this.account ? this.renderThemes() : 'Sign-in to use themes'} +
+

Color Scheme

+ ${this.account ? this.renderColorScheme() : 'Sign-in to use color schemes'} +
` } renderThemes() { return html` - ${THEMES.map(this.renderThemeOption.bind(this))} ` } renderThemeOption(theme) { - return html` ` + return html` ` } - onUpdateTheme({target}) { - this.saveTheme(target.value) + renderColorScheme() { + return COLOR_SCHEMES.map((scheme) => { + const disabled = this.account.color_scheme === scheme + return html` ` + }) } // Disable shadow DOM createRenderRoot() { return this } + + /* events handle */ + onTheme({target}) { + this.saveTheme(target.value) + } + onColorScheme({target}) { + this.saveColorScheme(target.value) + } + onSize({target}) { + this.saveSize(target.value) + } } diff --git a/src/index.js b/src/index.js index 3df93307..f45acffb 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,6 @@ import lib from './libs/index.js' import components from './components/index.js' -import '../styles/index.css' - export default { lib, ...components, diff --git a/src/libs/db-listeners.js b/src/libs/db-listeners.js index 64bbab17..53d07bf8 100644 --- a/src/libs/db-listeners.js +++ b/src/libs/db-listeners.js @@ -36,7 +36,6 @@ export default class DatabaseListeners extends EventTarget { ) .subscribe() - console.log('add accountds db event', user.id) sdk.supabase .channel('yser-account') .on( @@ -48,7 +47,6 @@ export default class DatabaseListeners extends EventTarget { filter: `id=eq.${user.id}`, }, (payload) => { - console.log('on accountds db event', payload) this.dispatchEvent(new CustomEvent('user-account', {detail: payload})) } ) diff --git a/styles/components/r4-layout.css b/styles/components/r4-layout.css deleted file mode 100644 index 18a660b4..00000000 --- a/styles/components/r4-layout.css +++ /dev/null @@ -1,136 +0,0 @@ -r4-layout { - flex: 1; - display: flex; - flex-direction: column; - position: relative; -} -@media (min-width: 70rem) { - r4-layout { - flex-direction: row; - } -} - -/* can make the layout "panel" & its "main" part the full viewport height, - so on page change, all interface does not jump */ -r4-layout::part(panel), -r4-layout::part(main) { - display: flex; - flex-direction: column; - flex-grow: 1; -} - -r4-router > * { - display: flex; - flex-direction: column; - flex-grow: 1; -} -r4-route { - display: none; -} - -r4-layout::part(playback) { - display: flex; - flex-direction: column; - justify-content: flex-end; -} -/* basic styles for details and summary of the playback layout part */ -r4-layout::part(playback-details) { - background-color: var(--c-bg); - flex-grow: 1; - display: flex; - flex-direction: column; - height: 100%; - align-items: flex-end; -} -r4-layout::part(playback-summary) { - cursor: pointer; -} - -/* hide player slot content if not playing, and in 'close' state */ -r4-layout:not([is-playing]) slot[name='player']::slotted(*), -r4-layout[ui-state='close'] [slot='player']::slotted(*) { - display: none; -} -/* make the player summary not display its icon */ -r4-layout::part(playback-summary) { - display: flex; - justify-content: flex-end; -} - -/* the player */ -r4-layout r4-player, -r4-layout radio4000-player { - flex-grow: 1; -} - -/* hide the player summary when fullscreen */ -r4-layout[ui-state='fullscreen']::part(playback-summary) { - display: none; -} -/* and make the player full height */ -r4-layout[ui-state='fullscreen'] radio4000-player { - height: 100vh; -} - -/* hide the bullets of the playback menu (should all menu?), make it inline by default */ -r4-layout::part(playback-menu) { - list-style: none; - margin: 0; - padding: 0; - display: flex; -} - -/* by default, make the playback menu inline (mobile first) */ -r4-layout::part(playback-menu) { -} - -/* - ui-state = minimize -*/ - -/* keep the player visible, but small */ -r4-layout[ui-state='minimize'] radio4000-player { - min-height: auto; - max-height: var(--s-youtube); /* youtube guidelines for video, @to-keep */ -} -r4-layout r4-player radio4000-player .Layout { - border-color: var(--c-link); -} -/* overwrite some of radio4000-player library styles */ -r4-layout[ui-state='minimize'] radio4000-player .Layout-header, -r4-layout[ui-state='minimize'] radio4000-player .Layout-main { - display: none; -} - -/* - ui-state = minimal & dock - - make the playback sticky at top of viewport, - so "r4-app" router does not scroll to the top on page change - - also keep the "panel" over the "playback" -*/ - -/* make the "playback" sticky at the top of viewport */ -r4-layout[ui-state='minimize']::part(playback), -r4-layout[ui-state='dock']::part(playback) { - position: sticky; - bottom: 0; - z-index: 2; -} -@media (min-width: 70rem) { - r4-layout[ui-state='minimize']::part(playback), - r4-layout[ui-state='dock']::part(playback) { - top: 0; - bottom: auto; - max-height: 100vh; - } - r4-layout[is-playing]::part(playback) { - min-width: 30vw; - } -} - -/* keep panel over the playback */ -r4-layout[ui-state='minimize']::part(panel), -r4-layout[ui-state='dock']::part(panel) { - z-index: 1; - /* background-color: var(--c-bg); */ -} diff --git a/styles/components/r4-map.css b/styles/components/r4-map.css deleted file mode 100644 index 2272eaa3..00000000 --- a/styles/components/r4-map.css +++ /dev/null @@ -1,3 +0,0 @@ -r4-map .ol-viewport { - min-height: 30vh; -} diff --git a/styles/index.css b/styles/index.css deleted file mode 100644 index ed361c39..00000000 --- a/styles/index.css +++ /dev/null @@ -1,5 +0,0 @@ -/* Root or base styles, not scoped to any component */ - -@import url('theme-default.css'); -/* @import url('theme-r4.css'); */ -/* @import url("theme-jellybeans.css"); */ diff --git a/styles/theme-r4.css b/styles/theme-r4.css deleted file mode 100644 index e0ca349e..00000000 --- a/styles/theme-r4.css +++ /dev/null @@ -1,122 +0,0 @@ -/* The default theme for R4 and components */ - -/* Shared between light and dark color schemes */ -r4-app { - --font: Maison Neue, system-ui, sans-serif; - --s-font: 15px; - --s-line-height: 1.3125; - --s: 0.5rem; - - --c-light: hsl(0deg 0% 98%); - --c-lightgray: hsl(0deg 0% 80%); - /* --c-darkgray: hsl(0deg 0% 15%); */ - --c-dark: hsl(0deg 0% 10%); - /* --c-link: hotpink; */ - --c-purple: #5d1ae6; - --c-light--accent: var(--c-purple); - --c-dark--accent: var(--c-purple); - /* --c-link: var(--c-purple); */ - /* background-color: hsl(40, 6%, 90%); */ - /* color: hsl(0, 0%, 10%); */ - /* font-size: 15px; */ - --c-border: var(--c-dark); -} - -[color-scheme='light'] r4-app { - /* --c-card-background: var(--c-lightgray); */ - /* --c-input-background: hsl(0 0% 93%); */ - /* --c-button-background: hsl(0 0% 93%); */ - /* --c-text: hsl(0 0% 10%); */ - /* --c-border: hsla(0 10% 50% / 0.5); */ -} - -[color-scheme='dark'] r4-app { - /* --c-border: hsl(0 0% 5%); */ - /* --c-text: hsl(0 0% 90%); */ - /* --c-link: hsl(260 80% 90%); */ -} - -r4-layout { - padding: 1rem; -} - -r4-app [slot='main'] header:first-of-type { - margin-bottom: var(--s); -} - -r4-app [slot='main'] header:first-of-type h1 { - margin-top: var(--s); -} - -r4-app h1, -r4-app h2 { - line-height: 1.1; -} - -r4-app h1 { - font-size: 2rem; -} - -r4-app h2 { - font-size: 1.5rem; -} - -r4-app button { - display: inline-block; - font-size: 0.86667rem; - font-weight: bold; - border: 1px solid rgba(115, 115, 115, 0.4); - border-radius: 3px; - box-shadow: 0 3px 0 -1px rgba(204, 204, 204, 0.9); - box-sizing: border-box; - cursor: pointer; - line-height: 1; - min-height: 2.1em; - padding: 0.6em; - background-color: hsl(0, 0%, 98%); - color: var(--c-fg); - text-align: center; - text-decoration: none; -} - -r4-app[color-scheme='dark'] button { - color: var(--c-bg); -} - -r4-channel-card { - display: flex; - flex-flow: column nowrap; - /* background-color: var(--c-lightgray); */ - max-width: 21rem; - margin-bottom: var(--s); -} - -/* Maison Neue regular/italic + bold/italic from Cloudinary */ -@font-face { - font-family: 'Maison Neue'; - src: url('https://res.cloudinary.com/radio4000/raw/upload/v1492541388/maisonneueweb-book_ee98sm.woff2') - format('woff2'); - font-weight: normal; - font-style: normal; -} -@font-face { - font-family: 'Maison Neue'; - src: url('https://res.cloudinary.com/radio4000/raw/upload/v1492541388/maisonneueweb-bookitalic_szjrvl.woff2') - format('woff2'); - font-weight: normal; - font-style: italic; -} -@font-face { - font-family: 'Maison Neue'; - src: url('https://res.cloudinary.com/radio4000/raw/upload/v1492541388/maisonneueweb-bold_rvmbzr.woff2') - format('woff2'); - font-weight: bold; - font-style: normal; -} -@font-face { - font-family: 'Maison Neue'; - src: url('https://res.cloudinary.com/radio4000/raw/upload/v1492541388/maisonneueweb-bolditalic_awvcg5.woff2') - format('woff2'); - font-weight: bold; - font-style: italic; -}