mirror of
https://github.com/woutdp/live_svelte
synced 2026-05-24 09:28:21 +00:00
Run formatter
This commit is contained in:
parent
45a7f0fe78
commit
9b837bcd28
29 changed files with 513 additions and 533 deletions
55
README.md
55
README.md
|
|
@ -16,19 +16,19 @@ Render Svelte directly into Phoenix LiveView with E2E reactivity.
|
|||
|
||||
## Resources
|
||||
|
||||
- [HexDocs](https://hexdocs.pm/live_svelte)
|
||||
- [HexPackage](https://hex.pm/packages/live_svelte)
|
||||
- [Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view)
|
||||
- [Blog Post](https://wout.space/notes/live-svelte)
|
||||
- [HexDocs](https://hexdocs.pm/live_svelte)
|
||||
- [HexPackage](https://hex.pm/packages/live_svelte)
|
||||
- [Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view)
|
||||
- [Blog Post](https://wout.space/notes/live-svelte)
|
||||
|
||||
## Features
|
||||
|
||||
- ⚡ **End-To-End Reactivity** with LiveView
|
||||
- 🔋 **Server-Side Rendered** (SSR) Svelte
|
||||
- ⭐ **Svelte Preprocessing** Support with [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess)
|
||||
- 🦄 **Tailwind** Support
|
||||
- 💀 **Dead View** Support
|
||||
- 🦥 **Slot Interoperability** *(Experimental)*
|
||||
- ⚡ **End-To-End Reactivity** with LiveView
|
||||
- 🔋 **Server-Side Rendered** (SSR) Svelte
|
||||
- ⭐ **Svelte Preprocessing** Support with [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess)
|
||||
- 🦄 **Tailwind** Support
|
||||
- 💀 **Dead View** Support
|
||||
- 🦥 **Slot Interoperability** _(Experimental)_
|
||||
|
||||
## Demo
|
||||
|
||||
|
|
@ -58,11 +58,11 @@ LiveSvelte builds on top of Phoenix LiveView to allow for easy client side state
|
|||
|
||||
### Reasons why you'd use LiveSvelte
|
||||
|
||||
- You have (complex) local state
|
||||
- You want to use an NPM package
|
||||
- You want to take advantage of Svelte's animations
|
||||
- You want scoped CSS
|
||||
- You like Svelte and its DX :)
|
||||
- You have (complex) local state
|
||||
- You want to use an NPM package
|
||||
- You want to take advantage of Svelte's animations
|
||||
- You want scoped CSS
|
||||
- You like Svelte and its DX :)
|
||||
|
||||
## Requirements
|
||||
|
||||
|
|
@ -99,6 +99,7 @@ end
|
|||
```
|
||||
|
||||
3. Run the following in your terminal
|
||||
|
||||
```bash
|
||||
mix deps.get
|
||||
mix live_svelte.setup
|
||||
|
|
@ -127,10 +128,10 @@ In addition we commented out some things such as the `esbuild` watcher configure
|
|||
|
||||
Svelte components need to go into the `assets/svelte` directory
|
||||
|
||||
- Set the `name` of the Svelte component.
|
||||
- _Optional:_ Provide the `props` you want to use that should be reactive as a map to the props field
|
||||
- _Optional:_ Provide `class` to set the class attribute on the root svelte element
|
||||
- _Optional:_ Set `ssr` to false to disable server-side rendering
|
||||
- Set the `name` of the Svelte component.
|
||||
- _Optional:_ Provide the `props` you want to use that should be reactive as a map to the props field
|
||||
- _Optional:_ Provide `class` to set the class attribute on the root svelte element
|
||||
- _Optional:_ Set `ssr` to false to disable server-side rendering
|
||||
|
||||
e.g. If your component is named `assets/svelte/Example.svelte`:
|
||||
|
||||
|
|
@ -164,7 +165,7 @@ An example project can be found in the `/example_project` directory.
|
|||
// This pushes the event over the websocket
|
||||
// The last parameter is optional. It's a callback for when the event is finished.
|
||||
// You could for example set a loading state until the event is finished if it takes a longer time.
|
||||
pushEvent('set_number', { number: number + 1 }, () => {})
|
||||
pushEvent("set_number", {number: number + 1}, () => {})
|
||||
|
||||
// Note that we actually never set the number in the frontend!
|
||||
// We ONLY push the event to the server.
|
||||
|
|
@ -173,7 +174,7 @@ An example project can be found in the `/example_project` directory.
|
|||
}
|
||||
|
||||
function decrease() {
|
||||
pushEvent('set_number', { number: number - 1 }, () => {})
|
||||
pushEvent("set_number", {number: number - 1}, () => {})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -246,6 +247,7 @@ LiveView allows for a bunch of interoperability which you can read more about he
|
|||
To use the preprocessor, install the desired preprocessor.
|
||||
|
||||
e.g. Typescript
|
||||
|
||||
```
|
||||
cd assets && npm install --save-dev typescript
|
||||
```
|
||||
|
|
@ -293,9 +295,9 @@ mix assets.build
|
|||
|
||||
### Releasing
|
||||
|
||||
- Update the version in `README.md`
|
||||
- Update the version in `package.json`
|
||||
- Update the version in `mix.exs`
|
||||
- Update the version in `README.md`
|
||||
- Update the version in `package.json`
|
||||
- Update the version in `mix.exs`
|
||||
|
||||
Run:
|
||||
|
||||
|
|
@ -304,5 +306,6 @@ mix hex.publish
|
|||
```
|
||||
|
||||
## Credits
|
||||
- [Ryan Cooke](https://dev.to/debussyman) - [E2E Reactivity using Svelte with Phoenix LiveView](https://dev.to/debussyman/e2e-reactivity-using-svelte-with-phoenix-liveview-38mf)
|
||||
- [Svonix](https://github.com/nikokozak/svonix)
|
||||
|
||||
- [Ryan Cooke](https://dev.to/debussyman) - [E2E Reactivity using Svelte with Phoenix LiveView](https://dev.to/debussyman/e2e-reactivity-using-svelte-with-phoenix-liveview-38mf)
|
||||
- [Svonix](https://github.com/nikokozak/svonix)
|
||||
|
|
|
|||
|
|
@ -1,71 +1,71 @@
|
|||
const esbuild = require('esbuild')
|
||||
const sveltePlugin = require('esbuild-svelte')
|
||||
const importGlobPlugin = require('esbuild-plugin-import-glob').default
|
||||
const sveltePreprocess = require('svelte-preprocess')
|
||||
const esbuild = require("esbuild")
|
||||
const sveltePlugin = require("esbuild-svelte")
|
||||
const importGlobPlugin = require("esbuild-plugin-import-glob").default
|
||||
const sveltePreprocess = require("svelte-preprocess")
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const watch = args.includes('--watch')
|
||||
const deploy = args.includes('--deploy')
|
||||
const watch = args.includes("--watch")
|
||||
const deploy = args.includes("--deploy")
|
||||
|
||||
let optsClient = {
|
||||
entryPoints: ['js/app.js'],
|
||||
mainFields: ['svelte', 'browser', 'module', 'main'],
|
||||
entryPoints: ["js/app.js"],
|
||||
mainFields: ["svelte", "browser", "module", "main"],
|
||||
bundle: true,
|
||||
minify: false,
|
||||
target: 'es2017',
|
||||
outdir: '../priv/static/assets',
|
||||
logLevel: 'info',
|
||||
target: "es2017",
|
||||
outdir: "../priv/static/assets",
|
||||
logLevel: "info",
|
||||
plugins: [
|
||||
importGlobPlugin(),
|
||||
sveltePlugin({
|
||||
preprocess: sveltePreprocess(),
|
||||
compilerOptions: {hydratable: true, css: true},
|
||||
})
|
||||
]
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
let optsServer = {
|
||||
entryPoints: ['js/server.js'],
|
||||
mainFields: ['svelte', 'module', 'main'],
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
entryPoints: ["js/server.js"],
|
||||
mainFields: ["svelte", "module", "main"],
|
||||
platform: "node",
|
||||
format: "cjs",
|
||||
bundle: true,
|
||||
minify: false,
|
||||
target: "node19.6.1",
|
||||
outdir: '../priv/static/assets/server',
|
||||
logLevel: 'info',
|
||||
outdir: "../priv/static/assets/server",
|
||||
logLevel: "info",
|
||||
plugins: [
|
||||
importGlobPlugin(),
|
||||
sveltePlugin({
|
||||
preprocess: sveltePreprocess(),
|
||||
compilerOptions: {hydratable: true, generate: 'ssr', format: 'cjs'},
|
||||
})
|
||||
]
|
||||
compilerOptions: {hydratable: true, generate: "ssr", format: "cjs"},
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
if (watch) {
|
||||
optsClient = {
|
||||
...optsClient,
|
||||
watch,
|
||||
sourcemap: 'inline'
|
||||
sourcemap: "inline",
|
||||
}
|
||||
|
||||
optsServer = {
|
||||
...optsServer,
|
||||
watch,
|
||||
sourcemap: 'inline'
|
||||
sourcemap: "inline",
|
||||
}
|
||||
}
|
||||
|
||||
if (deploy) {
|
||||
optsClient = {
|
||||
...optsClient,
|
||||
minify: true
|
||||
minify: true,
|
||||
}
|
||||
|
||||
optsServer = {
|
||||
...optsServer,
|
||||
minify: true
|
||||
minify: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ const server = esbuild.build(optsServer)
|
|||
|
||||
if (watch) {
|
||||
client.then(_result => {
|
||||
process.stdin.on('close', () => {
|
||||
process.stdin.on("close", () => {
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ if (watch) {
|
|||
})
|
||||
|
||||
server.then(_result => {
|
||||
process.stdin.on('close', () => {
|
||||
process.stdin.on("close", () => {
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import "phoenix_html"
|
|||
import {Socket} from "phoenix"
|
||||
import {LiveSocket} from "phoenix_live_view"
|
||||
import topbar from "../vendor/topbar"
|
||||
import {getHooks} from 'live_svelte'
|
||||
import * as SvelteComponents from '../svelte/**/*'
|
||||
import {getHooks} from "live_svelte"
|
||||
import * as SvelteComponents from "../svelte/**/*"
|
||||
|
||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
||||
let liveSocket = new LiveSocket("/live", Socket, {hooks: getHooks(SvelteComponents), params: {_csrf_token: csrfToken}})
|
||||
|
|
@ -40,4 +40,3 @@ liveSocket.connect()
|
|||
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
|
||||
// >> liveSocket.disableLatencySim()
|
||||
window.liveSocket = liveSocket
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import * as Components from '../svelte/**/*'
|
||||
import {exportSvelteComponents} from 'live_svelte'
|
||||
import * as Components from "../svelte/**/*"
|
||||
import {exportSvelteComponents} from "live_svelte"
|
||||
|
||||
module.exports = exportSvelteComponents(Components)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import {detach, insert, noop} from 'svelte/internal'
|
||||
import {exportSvelteComponents} from './utils'
|
||||
import {detach, insert, noop} from "svelte/internal"
|
||||
import {exportSvelteComponents} from "./utils"
|
||||
|
||||
function base64ToElement(base64) {
|
||||
let template = document.createElement('div')
|
||||
let template = document.createElement("div")
|
||||
template.innerHTML = atob(base64).trim()
|
||||
return template
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ function createSlots(slots, ref) {
|
|||
return () => {
|
||||
return {
|
||||
getElement() {
|
||||
return base64ToElement(dataAttributeToJson('data-slots', ref.el)[slotName])
|
||||
return base64ToElement(dataAttributeToJson("data-slots", ref.el)[slotName])
|
||||
},
|
||||
update() {
|
||||
const element = this.getElement()
|
||||
|
|
@ -53,10 +53,10 @@ function createSlots(slots, ref) {
|
|||
|
||||
function getProps(ref) {
|
||||
return {
|
||||
...dataAttributeToJson('data-props', ref.el),
|
||||
...dataAttributeToJson("data-props", ref.el),
|
||||
pushEvent: (event, data, callback) => ref.pushEvent(event, data, callback),
|
||||
$$slots: createSlots(dataAttributeToJson('data-slots', ref.el), ref),
|
||||
$$scope: {}
|
||||
$$slots: createSlots(dataAttributeToJson("data-slots", ref.el), ref),
|
||||
$$scope: {},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,9 +71,9 @@ export function getHooks(Components) {
|
|||
|
||||
const SvelteHook = {
|
||||
mounted() {
|
||||
const componentName = this.el.getAttribute('data-name')
|
||||
const componentName = this.el.getAttribute("data-name")
|
||||
if (!componentName) {
|
||||
throw new Error('Component name must be provided')
|
||||
throw new Error("Component name must be provided")
|
||||
}
|
||||
|
||||
const Component = components[componentName]
|
||||
|
|
@ -84,7 +84,7 @@ export function getHooks(Components) {
|
|||
this._instance = new Component({
|
||||
target: this.el,
|
||||
props: getProps(this),
|
||||
hydrate: true
|
||||
hydrate: true,
|
||||
})
|
||||
},
|
||||
|
||||
|
|
@ -104,10 +104,10 @@ export function getHooks(Components) {
|
|||
// If we do a page navigation, this would remove the component in the DOM,
|
||||
// and then it would to the transition, causing a flicker of unrendered content
|
||||
// Since we're doing a page transition anyway, the component will be remove automatically
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
SvelteHook
|
||||
SvelteHook,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export {getRender} from "./render"
|
||||
export {getHooks} from "./hooks"
|
||||
export {exportSvelteComponents} from './utils'
|
||||
export {exportSvelteComponents} from "./utils"
|
||||
|
|
|
|||
|
|
@ -5,17 +5,14 @@ export function getRender(componentPath) {
|
|||
function render(name, props = {}, slots = null) {
|
||||
// remove from cache in non-production environments
|
||||
// so that we can see changes
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
require.resolve(componentPath) in require.cache
|
||||
) {
|
||||
if (process.env.NODE_ENV !== "production" && require.resolve(componentPath) in require.cache) {
|
||||
delete require.cache[require.resolve(componentPath)]
|
||||
}
|
||||
|
||||
const component = require(componentPath)[name].default
|
||||
const $$slots = Object.fromEntries(Object.entries(slots).map(([k, v]) => [k, () => v])) || {}
|
||||
|
||||
return component.render(props, { $$slots, context: new Map() })
|
||||
return component.render(props, {$$slots, context: new Map()})
|
||||
}
|
||||
|
||||
return render
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
export function exportSvelteComponents(components) {
|
||||
let { default: modules, filenames } = components
|
||||
let {default: modules, filenames} = components
|
||||
|
||||
filenames = filenames
|
||||
.map(name => name.replace('../svelte/', ''))
|
||||
.map(name => name.replace('.svelte', ''))
|
||||
filenames = filenames.map(name => name.replace("../svelte/", "")).map(name => name.replace(".svelte", ""))
|
||||
|
||||
return Object.assign({}, ...modules.map((m, index) => ({[filenames[index]]: m.default})))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
To start your Phoenix server:
|
||||
|
||||
* Run `mix setup` to install and setup dependencies
|
||||
* Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
|
||||
- Run `mix setup` to install and setup dependencies
|
||||
- Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
|
||||
|
||||
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
|
||||
|
||||
|
|
@ -11,8 +11,8 @@ Ready to run in production? Please [check our deployment guides](https://hexdocs
|
|||
|
||||
## Learn more
|
||||
|
||||
* Official website: https://www.phoenixframework.org/
|
||||
* Guides: https://hexdocs.pm/phoenix/overview.html
|
||||
* Docs: https://hexdocs.pm/phoenix
|
||||
* Forum: https://elixirforum.com/c/phoenix-forum
|
||||
* Source: https://github.com/phoenixframework/phoenix
|
||||
- Official website: https://www.phoenixframework.org/
|
||||
- Guides: https://hexdocs.pm/phoenix/overview.html
|
||||
- Docs: https://hexdocs.pm/phoenix
|
||||
- Forum: https://elixirforum.com/c/phoenix-forum
|
||||
- Source: https://github.com/phoenixframework/phoenix
|
||||
|
|
|
|||
|
|
@ -1,71 +1,71 @@
|
|||
const esbuild = require('esbuild')
|
||||
const sveltePlugin = require('esbuild-svelte')
|
||||
const importGlobPlugin = require('esbuild-plugin-import-glob').default
|
||||
const sveltePreprocess = require('svelte-preprocess')
|
||||
const esbuild = require("esbuild")
|
||||
const sveltePlugin = require("esbuild-svelte")
|
||||
const importGlobPlugin = require("esbuild-plugin-import-glob").default
|
||||
const sveltePreprocess = require("svelte-preprocess")
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const watch = args.includes('--watch')
|
||||
const deploy = args.includes('--deploy')
|
||||
const watch = args.includes("--watch")
|
||||
const deploy = args.includes("--deploy")
|
||||
|
||||
let optsClient = {
|
||||
entryPoints: ['js/app.js'],
|
||||
mainFields: ['svelte', 'browser', 'module', 'main'],
|
||||
entryPoints: ["js/app.js"],
|
||||
mainFields: ["svelte", "browser", "module", "main"],
|
||||
bundle: true,
|
||||
minify: false,
|
||||
target: 'es2017',
|
||||
outdir: '../priv/static/assets',
|
||||
logLevel: 'info',
|
||||
target: "es2017",
|
||||
outdir: "../priv/static/assets",
|
||||
logLevel: "info",
|
||||
plugins: [
|
||||
importGlobPlugin(),
|
||||
sveltePlugin({
|
||||
preprocess: sveltePreprocess(),
|
||||
compilerOptions: {hydratable: true, css: true},
|
||||
})
|
||||
]
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
let optsServer = {
|
||||
entryPoints: ['js/server.js'],
|
||||
mainFields: ['svelte', 'module', 'main'],
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
entryPoints: ["js/server.js"],
|
||||
mainFields: ["svelte", "module", "main"],
|
||||
platform: "node",
|
||||
format: "cjs",
|
||||
bundle: true,
|
||||
minify: false,
|
||||
target: "node19.6.1",
|
||||
outdir: '../priv/static/assets/server',
|
||||
logLevel: 'info',
|
||||
outdir: "../priv/static/assets/server",
|
||||
logLevel: "info",
|
||||
plugins: [
|
||||
importGlobPlugin(),
|
||||
sveltePlugin({
|
||||
preprocess: sveltePreprocess(),
|
||||
compilerOptions: {hydratable: true, generate: 'ssr', format: 'cjs'},
|
||||
})
|
||||
]
|
||||
compilerOptions: {hydratable: true, generate: "ssr", format: "cjs"},
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
if (watch) {
|
||||
optsClient = {
|
||||
...optsClient,
|
||||
watch,
|
||||
sourcemap: 'inline'
|
||||
sourcemap: "inline",
|
||||
}
|
||||
|
||||
optsServer = {
|
||||
...optsServer,
|
||||
watch,
|
||||
sourcemap: 'inline'
|
||||
sourcemap: "inline",
|
||||
}
|
||||
}
|
||||
|
||||
if (deploy) {
|
||||
optsClient = {
|
||||
...optsClient,
|
||||
minify: true
|
||||
minify: true,
|
||||
}
|
||||
|
||||
optsServer = {
|
||||
...optsServer,
|
||||
minify: true
|
||||
minify: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ const server = esbuild.build(optsServer)
|
|||
|
||||
if (watch) {
|
||||
client.then(_result => {
|
||||
process.stdin.on('close', () => {
|
||||
process.stdin.on("close", () => {
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ if (watch) {
|
|||
})
|
||||
|
||||
server.then(_result => {
|
||||
process.stdin.on('close', () => {
|
||||
process.stdin.on("close", () => {
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import "phoenix_html"
|
|||
import {Socket} from "phoenix"
|
||||
import {LiveSocket} from "phoenix_live_view"
|
||||
import topbar from "../vendor/topbar"
|
||||
import {getHooks} from 'live_svelte'
|
||||
import * as SvelteComponents from '../svelte/**/*'
|
||||
import {getHooks} from "live_svelte"
|
||||
import * as SvelteComponents from "../svelte/**/*"
|
||||
|
||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
||||
let liveSocket = new LiveSocket("/live", Socket, {hooks: getHooks(SvelteComponents), params: {_csrf_token: csrfToken}})
|
||||
|
|
@ -40,4 +40,3 @@ liveSocket.connect()
|
|||
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
|
||||
// >> liveSocket.disableLatencySim()
|
||||
window.liveSocket = liveSocket
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import * as Components from '../svelte/**/*'
|
||||
import {exportSvelteComponents} from 'live_svelte'
|
||||
import * as Components from "../svelte/**/*"
|
||||
import {exportSvelteComponents} from "live_svelte"
|
||||
|
||||
module.exports = exportSvelteComponents(Components)
|
||||
|
|
|
|||
|
|
@ -1,41 +1,41 @@
|
|||
<script>
|
||||
import { slide, fly } from 'svelte/transition'
|
||||
import { Marquee } from 'dynamic-marquee'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
import {slide, fly} from "svelte/transition"
|
||||
import {Marquee} from "dynamic-marquee"
|
||||
import {onMount} from "svelte"
|
||||
|
||||
export let news = []
|
||||
export let pushEvent
|
||||
|
||||
|
||||
let newItem = ""
|
||||
let marquee
|
||||
let marqueeEl
|
||||
let index = 0
|
||||
let speed = -150
|
||||
|
||||
|
||||
// Function to add a new item to the news feed
|
||||
function addItem() {
|
||||
if (newItem === "") return
|
||||
if (news.length === 0) marquee.appendItem(createItem(newItem))
|
||||
pushEvent("add_news_item", { body: newItem })
|
||||
pushEvent("add_news_item", {body: newItem})
|
||||
newItem = ""
|
||||
}
|
||||
|
||||
|
||||
// Function to remove an item from the news feed
|
||||
function removeItem(id) {
|
||||
pushEvent("remove_news_item", { id: id })
|
||||
pushEvent("remove_news_item", {id: id})
|
||||
}
|
||||
|
||||
|
||||
// Run this code when the component is mounted to the DOM
|
||||
onMount(async () => {
|
||||
// Create a new Marquee instance
|
||||
marquee = new Marquee(marqueeEl, {
|
||||
rate: speed,
|
||||
startOnScreen: false,
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
// Add the first item to the Marquee, if there is one
|
||||
if (news.length > 0) marquee.appendItem(createItem(news[0].body))
|
||||
|
||||
|
||||
// Add an item to the Marquee whenever it is required
|
||||
marquee.onItemRequired(() => {
|
||||
if (news.length === 0) return
|
||||
|
|
@ -44,16 +44,16 @@
|
|||
return createItem(news[index].body)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
// Function to create a new Marquee item
|
||||
function createItem(text) {
|
||||
const item = document.createElement('span');
|
||||
item.classList.add("mx-8");
|
||||
item.classList.add("hover:text-black");
|
||||
const item = document.createElement("span")
|
||||
item.classList.add("mx-8")
|
||||
item.classList.add("hover:text-black")
|
||||
item.textContent = text
|
||||
return item
|
||||
}
|
||||
|
||||
|
||||
// Set the Marquee speed whenever it is changed
|
||||
$: marquee && marquee.setRate(speed)
|
||||
</script>
|
||||
|
|
@ -62,12 +62,12 @@
|
|||
<div>
|
||||
<div class="flex items-center">
|
||||
<form>
|
||||
<input class="rounded" type="text" bind:value={newItem}/>
|
||||
<input class="rounded" type="text" bind:value={newItem} />
|
||||
<button class="bg-black text-white rounded px-2 py-1" on:click|preventDefault={addItem} type="submit">Add Item</button>
|
||||
</form>
|
||||
<div class="ml-4">
|
||||
<button class="bg-black text-white rounded px-2 py-1 active:opacity-95" on:click={() => speed -= 20}>← Faster</button>
|
||||
<button class="bg-black text-white rounded px-2 py-1 active:opacity-95" on:click={() => speed += 20}>Slower →</button>
|
||||
<button class="bg-black text-white rounded px-2 py-1 active:opacity-95" on:click={() => (speed -= 20)}>← Faster</button>
|
||||
<button class="bg-black text-white rounded px-2 py-1 active:opacity-95" on:click={() => (speed += 20)}>Slower →</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -76,19 +76,16 @@
|
|||
<div in:fly={{y: 20}} out:slide={{y: -20}} class="mb-1">
|
||||
<button class="bg-[#F00] px-2 py-1 rounded" on:click={() => removeItem(item.id)}>Remove</button>
|
||||
{item.body}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="fixed bottom-0 left-0 text-white font-bold text-4xl z-20 p-4 rounded-r-lg bg-gradient-to-b from-[#f00] via-[#f77] to-[#f00]"
|
||||
>
|
||||
|
||||
<div class="fixed bottom-0 left-0 text-white font-bold text-4xl z-20 p-4 rounded-r-lg bg-gradient-to-b from-[#f00] via-[#f77] to-[#f00]">
|
||||
BREAKING NEWS
|
||||
</div>
|
||||
<div
|
||||
bind:this={marqueeEl}
|
||||
<div
|
||||
bind:this={marqueeEl}
|
||||
class="fixed bottom-0 w-screen text-white font-bold text-xl py-2 bg-gradient-to-b from-[#f00] via-[#f77] to-[#f00]"
|
||||
>
|
||||
</div>
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,59 +1,59 @@
|
|||
<script>
|
||||
import {fly} from "svelte/transition"
|
||||
import {elasticOut} from "svelte/easing"
|
||||
import {afterUpdate} from "svelte"
|
||||
import {fly} from "svelte/transition"
|
||||
import {elasticOut} from "svelte/easing"
|
||||
import {afterUpdate} from "svelte"
|
||||
|
||||
export let messages
|
||||
export let name
|
||||
export let pushEvent
|
||||
export let messages
|
||||
export let name
|
||||
export let pushEvent
|
||||
|
||||
let body = ""
|
||||
let messagesElement
|
||||
let body = ""
|
||||
let messagesElement
|
||||
|
||||
$: charCount = body.length
|
||||
$: charCount = body.length
|
||||
|
||||
afterUpdate(() => messagesElement.scroll({ top: messagesElement.scrollHeight, behavior: "smooth" }))
|
||||
afterUpdate(() => messagesElement.scroll({top: messagesElement.scrollHeight, behavior: "smooth"}))
|
||||
|
||||
function submitMessage() {
|
||||
if (body === "") return
|
||||
pushEvent("send_message", {body})
|
||||
body = ""
|
||||
}
|
||||
function submitMessage() {
|
||||
if (body === "") return
|
||||
pushEvent("send_message", {body})
|
||||
body = ""
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col justify-between items-between sm:border sm:rounded-lg w-full h-full sm:w-[360px] sm:h-[600px]">
|
||||
<ul bind:this={messagesElement} class="flex flex-col gap-2 h-full overflow-x-clip overflow-y-auto p-2">
|
||||
{#each messages as message (message.id)}
|
||||
{@const me = message.name === name}
|
||||
<li
|
||||
in:fly={{x: 100 * (me ? 1 : -1), y: -20, duration: 1000, easing: elasticOut}}
|
||||
class="
|
||||
<ul bind:this={messagesElement} class="flex flex-col gap-2 h-full overflow-x-clip overflow-y-auto p-2">
|
||||
{#each messages as message (message.id)}
|
||||
{@const me = message.name === name}
|
||||
<li
|
||||
in:fly={{x: 100 * (me ? 1 : -1), y: -20, duration: 1000, easing: elasticOut}}
|
||||
class="
|
||||
rounded-[1em] px-4 py-2 flex flex-col
|
||||
{me ? 'rounded-tr-none ml-10 bg-[#0A80FE] text-white' : 'rounded-tl-none mr-10 bg-[#E9E8EB] text-black'}
|
||||
"
|
||||
>
|
||||
<span in:fly={{y: 10}} class="text-xs font-bold">{message.name}</span>
|
||||
<span>{message.body}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
>
|
||||
<span in:fly={{y: 10}} class="text-xs font-bold">{message.name}</span>
|
||||
<span>{message.body}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<form on:submit|preventDefault={submitMessage} class="bg-gray-100 p-2 flex gap-2 justify-center items-center">
|
||||
<div class="relative flex justify-center items-center">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
type="text"
|
||||
name="message"
|
||||
bind:value={body}
|
||||
placeholder="Message..."
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
class="flex-grow rounded-full bg-transparent text-black pr-10"
|
||||
/>
|
||||
<span class="absolute right-0 px-4 text-gray-500 text-xs">
|
||||
{charCount}
|
||||
</span>
|
||||
</div>
|
||||
<button class="bg-black text-white rounded px-4 py-2">Send</button>
|
||||
</form>
|
||||
<form on:submit|preventDefault={submitMessage} class="bg-gray-100 p-2 flex gap-2 justify-center items-center">
|
||||
<div class="relative flex justify-center items-center">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
type="text"
|
||||
name="message"
|
||||
bind:value={body}
|
||||
placeholder="Message..."
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
class="flex-grow rounded-full bg-transparent text-black pr-10"
|
||||
/>
|
||||
<span class="absolute right-0 px-4 text-gray-500 text-xs">
|
||||
{charCount}
|
||||
</span>
|
||||
</div>
|
||||
<button class="bg-black text-white rounded px-4 py-2">Send</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import {slide, fly} from 'svelte/transition'
|
||||
import {slide, fly} from "svelte/transition"
|
||||
|
||||
export let pushEvent
|
||||
export let items = []
|
||||
|
|
@ -9,26 +9,27 @@
|
|||
|
||||
function addItem() {
|
||||
if (!newItemName) return
|
||||
pushEvent('add_item', {name: newItemName})
|
||||
newItemName = ''
|
||||
pushEvent("add_item", {name: newItemName})
|
||||
newItemName = ""
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={showItems}>
|
||||
<input type="checkbox" bind:checked={showItems} />
|
||||
show list
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="range" bind:value={i} max={items.length}>
|
||||
<input type="range" bind:value={i} max={items.length} />
|
||||
{i}
|
||||
</label>
|
||||
|
||||
<div class="mb-2">
|
||||
<form>
|
||||
<input type="test" bind:value={newItemName} class="border rounded px-2 py-1"/>
|
||||
<button type="submit" on:click|preventDefault={addItem} class="bg-black rounded text-white px-2 py-1 font-bold">Add item</button>
|
||||
<input type="test" bind:value={newItemName} class="border rounded px-2 py-1" />
|
||||
<button type="submit" on:click|preventDefault={addItem} class="bg-black rounded text-white px-2 py-1 font-bold">Add item</button
|
||||
>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
<script>
|
||||
export let number
|
||||
let other = 1
|
||||
export let number
|
||||
let other = 1
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Simple Counter</title>
|
||||
<title>Simple Counter</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex flex-col justify-center items-center gap-4 p-4">
|
||||
<div class="flex flex-row items-center justify-center gap-10">
|
||||
<div class="flex flex-col justify-center items-center">
|
||||
Server
|
||||
<span class="text-xl">{number}</span>
|
||||
<button class="plus" phx-click="increment">+1</button>
|
||||
</div>
|
||||
<div class="flex flex-row items-center justify-center gap-10">
|
||||
<div class="flex flex-col justify-center items-center">
|
||||
Server
|
||||
<span class="text-xl">{number}</span>
|
||||
<button class="plus" phx-click="increment">+1</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center items-center">
|
||||
Client
|
||||
<span class="text-xl">{other}</span>
|
||||
<button class="plus" on:click={() => other += 1}>+1</button>
|
||||
<div class="flex flex-col justify-center items-center">
|
||||
Client
|
||||
<span class="text-xl">{other}</span>
|
||||
<button class="plus" on:click={() => (other += 1)}>+1</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
<div class="flex flex-col justify-center items-center gap-4">
|
||||
<div class="flex flex-row items-center justify-center gap-10">
|
||||
<button class="minus" on:click={() => number -= amount}>-{amount}</button>
|
||||
<button class="minus" on:click={() => (number -= amount)}>-{amount}</button>
|
||||
<span class="text-xl">{number}</span>
|
||||
<button class="plus" on:click={() => number += amount}>+{amount}</button>
|
||||
<button class="plus" on:click={() => (number += amount)}>+{amount}</button>
|
||||
</div>
|
||||
<label>
|
||||
Amount:
|
||||
|
|
|
|||
|
|
@ -6,63 +6,64 @@ const fs = require("fs")
|
|||
const path = require("path")
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
"./js/**/*.js",
|
||||
"../lib/*_web.ex",
|
||||
"../lib/*_web/**/*.*ex",
|
||||
"./svelte/**/*.svelte"
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
brand: "#FD4F00",
|
||||
}
|
||||
content: ["./js/**/*.js", "../lib/*_web.ex", "../lib/*_web/**/*.*ex", "./svelte/**/*.svelte"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
brand: "#FD4F00",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require("@tailwindcss/forms"),
|
||||
// Allows prefixing tailwind classes with LiveView classes to add rules
|
||||
// only when LiveView classes are applied, for example:
|
||||
//
|
||||
// <div class="phx-click-loading:animate-ping">
|
||||
//
|
||||
plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])),
|
||||
plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])),
|
||||
plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])),
|
||||
plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])),
|
||||
plugins: [
|
||||
require("@tailwindcss/forms"),
|
||||
// Allows prefixing tailwind classes with LiveView classes to add rules
|
||||
// only when LiveView classes are applied, for example:
|
||||
//
|
||||
// <div class="phx-click-loading:animate-ping">
|
||||
//
|
||||
plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])),
|
||||
plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])),
|
||||
plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])),
|
||||
plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])),
|
||||
|
||||
// Embeds Hero Icons (https://heroicons.com) into your app.css bundle
|
||||
// See your `CoreComponents.icon/1` for more information.
|
||||
//
|
||||
plugin(function({matchComponents, theme}) {
|
||||
let iconsDir = path.join(__dirname, "../priv/hero_icons/optimized")
|
||||
let values = {}
|
||||
let icons = [
|
||||
["", "/24/outline"],
|
||||
["-solid", "/24/solid"],
|
||||
["-mini", "/20/solid"]
|
||||
]
|
||||
icons.forEach(([suffix, dir]) => {
|
||||
fs.readdirSync(path.join(iconsDir, dir)).map(file => {
|
||||
let name = path.basename(file, ".svg") + suffix
|
||||
values[name] = {name, fullPath: path.join(iconsDir, dir, file)}
|
||||
})
|
||||
})
|
||||
matchComponents({
|
||||
"hero": ({name, fullPath}) => {
|
||||
let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "")
|
||||
return {
|
||||
[`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
|
||||
"-webkit-mask": `var(--hero-${name})`,
|
||||
"mask": `var(--hero-${name})`,
|
||||
"background-color": "currentColor",
|
||||
"vertical-align": "middle",
|
||||
"display": "inline-block",
|
||||
"width": theme("spacing.5"),
|
||||
"height": theme("spacing.5")
|
||||
}
|
||||
}
|
||||
}, {values})
|
||||
})
|
||||
]
|
||||
// Embeds Hero Icons (https://heroicons.com) into your app.css bundle
|
||||
// See your `CoreComponents.icon/1` for more information.
|
||||
//
|
||||
plugin(function ({matchComponents, theme}) {
|
||||
let iconsDir = path.join(__dirname, "../priv/hero_icons/optimized")
|
||||
let values = {}
|
||||
let icons = [
|
||||
["", "/24/outline"],
|
||||
["-solid", "/24/solid"],
|
||||
["-mini", "/20/solid"],
|
||||
]
|
||||
icons.forEach(([suffix, dir]) => {
|
||||
fs.readdirSync(path.join(iconsDir, dir)).map(file => {
|
||||
let name = path.basename(file, ".svg") + suffix
|
||||
values[name] = {name, fullPath: path.join(iconsDir, dir, file)}
|
||||
})
|
||||
})
|
||||
matchComponents(
|
||||
{
|
||||
hero: ({name, fullPath}) => {
|
||||
let content = fs
|
||||
.readFileSync(fullPath)
|
||||
.toString()
|
||||
.replace(/\r?\n|\r/g, "")
|
||||
return {
|
||||
[`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
|
||||
"-webkit-mask": `var(--hero-${name})`,
|
||||
mask: `var(--hero-${name})`,
|
||||
"background-color": "currentColor",
|
||||
"vertical-align": "middle",
|
||||
display: "inline-block",
|
||||
width: theme("spacing.5"),
|
||||
height: theme("spacing.5"),
|
||||
}
|
||||
},
|
||||
},
|
||||
{values}
|
||||
)
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
|
|
|||
297
example_project/assets/vendor/topbar.js
vendored
297
example_project/assets/vendor/topbar.js
vendored
|
|
@ -4,162 +4,149 @@
|
|||
* https://buunguyen.github.io/topbar
|
||||
* Copyright (c) 2021 Buu Nguyen
|
||||
*/
|
||||
(function (window, document) {
|
||||
"use strict";
|
||||
;(function (window, document) {
|
||||
"use strict"
|
||||
|
||||
// https://gist.github.com/paulirish/1579671
|
||||
(function () {
|
||||
var lastTime = 0;
|
||||
var vendors = ["ms", "moz", "webkit", "o"];
|
||||
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
|
||||
window.requestAnimationFrame =
|
||||
window[vendors[x] + "RequestAnimationFrame"];
|
||||
window.cancelAnimationFrame =
|
||||
window[vendors[x] + "CancelAnimationFrame"] ||
|
||||
window[vendors[x] + "CancelRequestAnimationFrame"];
|
||||
}
|
||||
if (!window.requestAnimationFrame)
|
||||
window.requestAnimationFrame = function (callback, element) {
|
||||
var currTime = new Date().getTime();
|
||||
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
||||
var id = window.setTimeout(function () {
|
||||
callback(currTime + timeToCall);
|
||||
}, timeToCall);
|
||||
lastTime = currTime + timeToCall;
|
||||
return id;
|
||||
};
|
||||
if (!window.cancelAnimationFrame)
|
||||
window.cancelAnimationFrame = function (id) {
|
||||
clearTimeout(id);
|
||||
};
|
||||
})();
|
||||
|
||||
var canvas,
|
||||
currentProgress,
|
||||
showing,
|
||||
progressTimerId = null,
|
||||
fadeTimerId = null,
|
||||
delayTimerId = null,
|
||||
addEvent = function (elem, type, handler) {
|
||||
if (elem.addEventListener) elem.addEventListener(type, handler, false);
|
||||
else if (elem.attachEvent) elem.attachEvent("on" + type, handler);
|
||||
else elem["on" + type] = handler;
|
||||
},
|
||||
options = {
|
||||
autoRun: true,
|
||||
barThickness: 3,
|
||||
barColors: {
|
||||
0: "rgba(26, 188, 156, .9)",
|
||||
".25": "rgba(52, 152, 219, .9)",
|
||||
".50": "rgba(241, 196, 15, .9)",
|
||||
".75": "rgba(230, 126, 34, .9)",
|
||||
"1.0": "rgba(211, 84, 0, .9)",
|
||||
},
|
||||
shadowBlur: 10,
|
||||
shadowColor: "rgba(0, 0, 0, .6)",
|
||||
className: null,
|
||||
},
|
||||
repaint = function () {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = options.barThickness * 5; // need space for shadow
|
||||
|
||||
var ctx = canvas.getContext("2d");
|
||||
ctx.shadowBlur = options.shadowBlur;
|
||||
ctx.shadowColor = options.shadowColor;
|
||||
|
||||
var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
|
||||
for (var stop in options.barColors)
|
||||
lineGradient.addColorStop(stop, options.barColors[stop]);
|
||||
ctx.lineWidth = options.barThickness;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, options.barThickness / 2);
|
||||
ctx.lineTo(
|
||||
Math.ceil(currentProgress * canvas.width),
|
||||
options.barThickness / 2
|
||||
);
|
||||
ctx.strokeStyle = lineGradient;
|
||||
ctx.stroke();
|
||||
},
|
||||
createCanvas = function () {
|
||||
canvas = document.createElement("canvas");
|
||||
var style = canvas.style;
|
||||
style.position = "fixed";
|
||||
style.top = style.left = style.right = style.margin = style.padding = 0;
|
||||
style.zIndex = 100001;
|
||||
style.display = "none";
|
||||
if (options.className) canvas.classList.add(options.className);
|
||||
document.body.appendChild(canvas);
|
||||
addEvent(window, "resize", repaint);
|
||||
},
|
||||
topbar = {
|
||||
config: function (opts) {
|
||||
for (var key in opts)
|
||||
if (options.hasOwnProperty(key)) options[key] = opts[key];
|
||||
},
|
||||
show: function (delay) {
|
||||
if (showing) return;
|
||||
if (delay) {
|
||||
if (delayTimerId) return;
|
||||
delayTimerId = setTimeout(() => topbar.show(), delay);
|
||||
} else {
|
||||
showing = true;
|
||||
if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);
|
||||
if (!canvas) createCanvas();
|
||||
canvas.style.opacity = 1;
|
||||
canvas.style.display = "block";
|
||||
topbar.progress(0);
|
||||
if (options.autoRun) {
|
||||
(function loop() {
|
||||
progressTimerId = window.requestAnimationFrame(loop);
|
||||
topbar.progress(
|
||||
"+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)
|
||||
);
|
||||
})();
|
||||
}
|
||||
// https://gist.github.com/paulirish/1579671
|
||||
;(function () {
|
||||
var lastTime = 0
|
||||
var vendors = ["ms", "moz", "webkit", "o"]
|
||||
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
|
||||
window.requestAnimationFrame = window[vendors[x] + "RequestAnimationFrame"]
|
||||
window.cancelAnimationFrame = window[vendors[x] + "CancelAnimationFrame"] || window[vendors[x] + "CancelRequestAnimationFrame"]
|
||||
}
|
||||
},
|
||||
progress: function (to) {
|
||||
if (typeof to === "undefined") return currentProgress;
|
||||
if (typeof to === "string") {
|
||||
to =
|
||||
(to.indexOf("+") >= 0 || to.indexOf("-") >= 0
|
||||
? currentProgress
|
||||
: 0) + parseFloat(to);
|
||||
}
|
||||
currentProgress = to > 1 ? 1 : to;
|
||||
repaint();
|
||||
return currentProgress;
|
||||
},
|
||||
hide: function () {
|
||||
clearTimeout(delayTimerId);
|
||||
delayTimerId = null;
|
||||
if (!showing) return;
|
||||
showing = false;
|
||||
if (progressTimerId != null) {
|
||||
window.cancelAnimationFrame(progressTimerId);
|
||||
progressTimerId = null;
|
||||
}
|
||||
(function loop() {
|
||||
if (topbar.progress("+.1") >= 1) {
|
||||
canvas.style.opacity -= 0.05;
|
||||
if (canvas.style.opacity <= 0.05) {
|
||||
canvas.style.display = "none";
|
||||
fadeTimerId = null;
|
||||
return;
|
||||
if (!window.requestAnimationFrame)
|
||||
window.requestAnimationFrame = function (callback, element) {
|
||||
var currTime = new Date().getTime()
|
||||
var timeToCall = Math.max(0, 16 - (currTime - lastTime))
|
||||
var id = window.setTimeout(function () {
|
||||
callback(currTime + timeToCall)
|
||||
}, timeToCall)
|
||||
lastTime = currTime + timeToCall
|
||||
return id
|
||||
}
|
||||
}
|
||||
fadeTimerId = window.requestAnimationFrame(loop);
|
||||
})();
|
||||
},
|
||||
};
|
||||
if (!window.cancelAnimationFrame)
|
||||
window.cancelAnimationFrame = function (id) {
|
||||
clearTimeout(id)
|
||||
}
|
||||
})()
|
||||
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
module.exports = topbar;
|
||||
} else if (typeof define === "function" && define.amd) {
|
||||
define(function () {
|
||||
return topbar;
|
||||
});
|
||||
} else {
|
||||
this.topbar = topbar;
|
||||
}
|
||||
}.call(this, window, document));
|
||||
var canvas,
|
||||
currentProgress,
|
||||
showing,
|
||||
progressTimerId = null,
|
||||
fadeTimerId = null,
|
||||
delayTimerId = null,
|
||||
addEvent = function (elem, type, handler) {
|
||||
if (elem.addEventListener) elem.addEventListener(type, handler, false)
|
||||
else if (elem.attachEvent) elem.attachEvent("on" + type, handler)
|
||||
else elem["on" + type] = handler
|
||||
},
|
||||
options = {
|
||||
autoRun: true,
|
||||
barThickness: 3,
|
||||
barColors: {
|
||||
0: "rgba(26, 188, 156, .9)",
|
||||
".25": "rgba(52, 152, 219, .9)",
|
||||
".50": "rgba(241, 196, 15, .9)",
|
||||
".75": "rgba(230, 126, 34, .9)",
|
||||
"1.0": "rgba(211, 84, 0, .9)",
|
||||
},
|
||||
shadowBlur: 10,
|
||||
shadowColor: "rgba(0, 0, 0, .6)",
|
||||
className: null,
|
||||
},
|
||||
repaint = function () {
|
||||
canvas.width = window.innerWidth
|
||||
canvas.height = options.barThickness * 5 // need space for shadow
|
||||
|
||||
var ctx = canvas.getContext("2d")
|
||||
ctx.shadowBlur = options.shadowBlur
|
||||
ctx.shadowColor = options.shadowColor
|
||||
|
||||
var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0)
|
||||
for (var stop in options.barColors) lineGradient.addColorStop(stop, options.barColors[stop])
|
||||
ctx.lineWidth = options.barThickness
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(0, options.barThickness / 2)
|
||||
ctx.lineTo(Math.ceil(currentProgress * canvas.width), options.barThickness / 2)
|
||||
ctx.strokeStyle = lineGradient
|
||||
ctx.stroke()
|
||||
},
|
||||
createCanvas = function () {
|
||||
canvas = document.createElement("canvas")
|
||||
var style = canvas.style
|
||||
style.position = "fixed"
|
||||
style.top = style.left = style.right = style.margin = style.padding = 0
|
||||
style.zIndex = 100001
|
||||
style.display = "none"
|
||||
if (options.className) canvas.classList.add(options.className)
|
||||
document.body.appendChild(canvas)
|
||||
addEvent(window, "resize", repaint)
|
||||
},
|
||||
topbar = {
|
||||
config: function (opts) {
|
||||
for (var key in opts) if (options.hasOwnProperty(key)) options[key] = opts[key]
|
||||
},
|
||||
show: function (delay) {
|
||||
if (showing) return
|
||||
if (delay) {
|
||||
if (delayTimerId) return
|
||||
delayTimerId = setTimeout(() => topbar.show(), delay)
|
||||
} else {
|
||||
showing = true
|
||||
if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId)
|
||||
if (!canvas) createCanvas()
|
||||
canvas.style.opacity = 1
|
||||
canvas.style.display = "block"
|
||||
topbar.progress(0)
|
||||
if (options.autoRun) {
|
||||
;(function loop() {
|
||||
progressTimerId = window.requestAnimationFrame(loop)
|
||||
topbar.progress("+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2))
|
||||
})()
|
||||
}
|
||||
}
|
||||
},
|
||||
progress: function (to) {
|
||||
if (typeof to === "undefined") return currentProgress
|
||||
if (typeof to === "string") {
|
||||
to = (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 ? currentProgress : 0) + parseFloat(to)
|
||||
}
|
||||
currentProgress = to > 1 ? 1 : to
|
||||
repaint()
|
||||
return currentProgress
|
||||
},
|
||||
hide: function () {
|
||||
clearTimeout(delayTimerId)
|
||||
delayTimerId = null
|
||||
if (!showing) return
|
||||
showing = false
|
||||
if (progressTimerId != null) {
|
||||
window.cancelAnimationFrame(progressTimerId)
|
||||
progressTimerId = null
|
||||
}
|
||||
;(function loop() {
|
||||
if (topbar.progress("+.1") >= 1) {
|
||||
canvas.style.opacity -= 0.05
|
||||
if (canvas.style.opacity <= 0.05) {
|
||||
canvas.style.display = "none"
|
||||
fadeTimerId = null
|
||||
return
|
||||
}
|
||||
}
|
||||
fadeTimerId = window.requestAnimationFrame(loop)
|
||||
})()
|
||||
},
|
||||
}
|
||||
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
module.exports = topbar
|
||||
} else if (typeof define === "function" && define.amd) {
|
||||
define(function () {
|
||||
return topbar
|
||||
})
|
||||
} else {
|
||||
this.topbar = topbar
|
||||
}
|
||||
}).call(this, window, document)
|
||||
|
|
|
|||
|
|
@ -1,56 +1,58 @@
|
|||
<script>
|
||||
import {slide, fly, fade} from "svelte/transition"
|
||||
import {elasticOut} from "svelte/easing"
|
||||
import {afterUpdate} from "svelte"
|
||||
import {slide, fly, fade} from "svelte/transition"
|
||||
import {elasticOut} from "svelte/easing"
|
||||
import {afterUpdate} from "svelte"
|
||||
|
||||
export let messages
|
||||
export let name
|
||||
export let pushEvent
|
||||
export let messages
|
||||
export let name
|
||||
export let pushEvent
|
||||
|
||||
let body = ""
|
||||
let messagesElement
|
||||
let body = ""
|
||||
let messagesElement
|
||||
|
||||
afterUpdate(() => { scrollToBottom(messagesElement) })
|
||||
afterUpdate(() => {
|
||||
scrollToBottom(messagesElement)
|
||||
})
|
||||
|
||||
const scrollToBottom = async (node) => {
|
||||
node.scroll({ top: node.scrollHeight, behavior: 'smooth' })
|
||||
}
|
||||
const scrollToBottom = async node => {
|
||||
node.scroll({top: node.scrollHeight, behavior: "smooth"})
|
||||
}
|
||||
|
||||
function submitMessage() {
|
||||
if (body === "") return
|
||||
pushEvent("send_message", {body})
|
||||
body = ""
|
||||
}
|
||||
function submitMessage() {
|
||||
if (body === "") return
|
||||
pushEvent("send_message", {body})
|
||||
body = ""
|
||||
}
|
||||
</script>
|
||||
|
||||
<div in:fade class="flex flex-col justify-between items-between sm:border sm:rounded-lg w-full h-full sm:w-[360px] sm:h-[600px]">
|
||||
<ul bind:this={messagesElement} class="flex flex-col gap-2 h-full sm:h-[400px] overflow-x-clip overflow-y-auto p-2">
|
||||
{#each messages as message (message.id)}
|
||||
{@const me = message.name === name}
|
||||
<li
|
||||
in:fly={{x: 100 * (me ? 1 : -1), y: -20, duration: 1000, easing: elasticOut}}
|
||||
class="
|
||||
<ul bind:this={messagesElement} class="flex flex-col gap-2 h-full sm:h-[400px] overflow-x-clip overflow-y-auto p-2">
|
||||
{#each messages as message (message.id)}
|
||||
{@const me = message.name === name}
|
||||
<li
|
||||
in:fly={{x: 100 * (me ? 1 : -1), y: -20, duration: 1000, easing: elasticOut}}
|
||||
class="
|
||||
rounded-[1em] px-4 py-2 flex flex-col
|
||||
{me ? 'rounded-tr-none ml-10 bg-[#0A80FE] text-white' : 'rounded-tl-none mr-10 bg-[#E9E8EB] text-black'}
|
||||
"
|
||||
>
|
||||
<span in:fly={{y: 10}} class="text-xs font-bold">{message.name}</span>
|
||||
<span in:fade>{message.body}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
>
|
||||
<span in:fly={{y: 10}} class="text-xs font-bold">{message.name}</span>
|
||||
<span in:fade>{message.body}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<form on:submit|preventDefault={submitMessage} class="bg-gray-100 p-2 flex gap-2">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
type="text"
|
||||
name="message"
|
||||
class="flex-grow rounded-full bg-transparent text-black"
|
||||
bind:value={body}
|
||||
placeholder="Message..."
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button class="bg-black text-white rounded px-4 py-2">Send</button>
|
||||
</form>
|
||||
<form on:submit|preventDefault={submitMessage} class="bg-gray-100 p-2 flex gap-2">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
type="text"
|
||||
name="message"
|
||||
class="flex-grow rounded-full bg-transparent text-black"
|
||||
bind:value={body}
|
||||
placeholder="Message..."
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button class="bg-black text-white rounded px-4 py-2">Send</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
<script>
|
||||
import {fly} from 'svelte/transition'
|
||||
import {fly} from "svelte/transition"
|
||||
|
||||
export let number = 1
|
||||
export let pushEvent
|
||||
|
||||
function increase() {
|
||||
pushEvent('set_number', { number: number + 1 })
|
||||
pushEvent("set_number", {number: number + 1})
|
||||
}
|
||||
|
||||
function decrease() {
|
||||
pushEvent('set_number', { number: number - 1 })
|
||||
pushEvent("set_number", {number: number - 1})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -21,12 +21,8 @@
|
|||
{#key number}
|
||||
<p class="mt-1">
|
||||
The number is:
|
||||
<br/>
|
||||
<span
|
||||
in:fly={{y: -40}}
|
||||
class="absolute px-2 mt-2"
|
||||
style="font-size: {number/2}rem;"
|
||||
>
|
||||
<br />
|
||||
<span in:fly={{y: -40}} class="absolute px-2 mt-2" style="font-size: {number / 2}rem;">
|
||||
{number}
|
||||
</span>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script>
|
||||
// https://github.com/tjenkinson/dynamic-marquee
|
||||
import {slide, fly} from 'svelte/transition'
|
||||
import {Marquee} from 'dynamic-marquee'
|
||||
import {onMount} from 'svelte'
|
||||
|
||||
import {slide, fly} from "svelte/transition"
|
||||
import {Marquee} from "dynamic-marquee"
|
||||
import {onMount} from "svelte"
|
||||
|
||||
export let news = []
|
||||
export let pushEvent
|
||||
|
||||
|
|
@ -12,23 +12,23 @@
|
|||
let marqueeEl
|
||||
let index = 0
|
||||
let speed = -150
|
||||
|
||||
|
||||
function addItem() {
|
||||
if (newItem === "") return
|
||||
if (news.length === 0) marquee.appendItem(createItem(newItem))
|
||||
pushEvent("add_news_item", { body: newItem })
|
||||
pushEvent("add_news_item", {body: newItem})
|
||||
newItem = ""
|
||||
}
|
||||
|
||||
function removeItem(id) {
|
||||
pushEvent("remove_news_item", { id: id })
|
||||
pushEvent("remove_news_item", {id: id})
|
||||
}
|
||||
|
||||
|
||||
onMount(async () => {
|
||||
marquee = new Marquee(marqueeEl, {
|
||||
rate: speed,
|
||||
startOnScreen: false,
|
||||
});
|
||||
})
|
||||
|
||||
if (news.length > 0) marquee.appendItem(createItem(news[0].body))
|
||||
|
||||
|
|
@ -39,15 +39,15 @@
|
|||
return createItem(news[index].body)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
function createItem(text) {
|
||||
const item = document.createElement('span');
|
||||
item.classList.add("mx-8");
|
||||
item.classList.add("hover:text-black");
|
||||
const item = document.createElement("span")
|
||||
item.classList.add("mx-8")
|
||||
item.classList.add("hover:text-black")
|
||||
item.textContent = text
|
||||
return item
|
||||
}
|
||||
|
||||
|
||||
$: marquee && marquee.setRate(speed)
|
||||
</script>
|
||||
|
||||
|
|
@ -55,12 +55,12 @@
|
|||
<div>
|
||||
<div class="flex items-center">
|
||||
<form>
|
||||
<input class="rounded" type="text" bind:value={newItem}/>
|
||||
<input class="rounded" type="text" bind:value={newItem} />
|
||||
<button class="bg-black text-white rounded px-2 py-1" on:click|preventDefault={addItem} type="submit">Add Item</button>
|
||||
</form>
|
||||
<div class="ml-4">
|
||||
<button class="bg-black text-white rounded px-2 py-1 active:opacity-95" on:click={() => speed -= 20}>← Faster</button>
|
||||
<button class="bg-black text-white rounded px-2 py-1 active:opacity-95" on:click={() => speed += 20}>Slower →</button>
|
||||
<button class="bg-black text-white rounded px-2 py-1 active:opacity-95" on:click={() => (speed -= 20)}>← Faster</button>
|
||||
<button class="bg-black text-white rounded px-2 py-1 active:opacity-95" on:click={() => (speed += 20)}>Slower →</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -69,19 +69,16 @@
|
|||
<div in:fly={{y: 20}} out:slide={{y: -20}} class="mb-1">
|
||||
<button class="bg-[#F00] px-2 py-1 rounded" on:click={() => removeItem(item.id)}>Remove</button>
|
||||
{item.body}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="fixed bottom-0 left-0 text-white font-bold text-4xl z-20 p-4 rounded-r-lg bg-gradient-to-b from-[#f00] via-[#f77] to-[#f00]"
|
||||
>
|
||||
|
||||
<div class="fixed bottom-0 left-0 text-white font-bold text-4xl z-20 p-4 rounded-r-lg bg-gradient-to-b from-[#f00] via-[#f77] to-[#f00]">
|
||||
BREAKING NEWS
|
||||
</div>
|
||||
<div
|
||||
bind:this={marqueeEl}
|
||||
<div
|
||||
bind:this={marqueeEl}
|
||||
class="fixed bottom-0 w-screen text-white font-bold text-xl py-2 bg-gradient-to-b from-[#f00] via-[#f77] to-[#f00]"
|
||||
>
|
||||
</div>
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import {slide, fly} from 'svelte/transition'
|
||||
import {slide, fly} from "svelte/transition"
|
||||
|
||||
export let pushEvent
|
||||
export let items = []
|
||||
|
|
@ -10,26 +10,27 @@
|
|||
|
||||
function addItem() {
|
||||
if (!newItemName) return
|
||||
pushEvent('add_item', {name: newItemName})
|
||||
newItemName = ''
|
||||
pushEvent("add_item", {name: newItemName})
|
||||
newItemName = ""
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={showItems}>
|
||||
<input type="checkbox" bind:checked={showItems} />
|
||||
show list
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="range" bind:value={i} max={items.length}>
|
||||
<input type="range" bind:value={i} max={items.length} />
|
||||
{i}
|
||||
</label>
|
||||
|
||||
<div class="mb-2">
|
||||
<form>
|
||||
<input type="test" bind:value={newItemName} class="border rounded px-2 py-1"/>
|
||||
<button type="submit" on:click|preventDefault={addItem} class="bg-black rounded text-white px-2 py-1 font-bold">Add item</button>
|
||||
<input type="test" bind:value={newItemName} class="border rounded px-2 py-1" />
|
||||
<button type="submit" on:click|preventDefault={addItem} class="bg-black rounded text-white px-2 py-1 font-bold">Add item</button
|
||||
>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -45,4 +46,3 @@
|
|||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
export let pushEvent
|
||||
|
||||
function increase() {
|
||||
pushEvent('set_number', { number: number + 1 })
|
||||
pushEvent("set_number", {number: number + 1})
|
||||
}
|
||||
|
||||
function decrease() {
|
||||
pushEvent('set_number', { number: number - 1 })
|
||||
pushEvent("set_number", {number: number - 1})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,14 +18,15 @@
|
|||
<ul class="flex flex-col gap-2">
|
||||
{#each messages as message (message.id)}
|
||||
<li in:slide class="bg-[#eee] rounded-full px-4 py-2 rounded-bl-none">
|
||||
<i>{message.name}:</i> {message.body}
|
||||
<i>{message.name}:</i>
|
||||
{message.body}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<form on:submit|preventDefault={submitMessage}>
|
||||
<input type="text" name="name" class="rounded" bind:value={name} placeholder="Your Name" />
|
||||
<input type="text" name="message" class="rounded" bind:value={message} placeholder="Message..."/>
|
||||
<input type="text" name="message" class="rounded" bind:value={message} placeholder="Message..." />
|
||||
<button class="bg-black text-white rounded px-4 py-2">Send</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
<script>
|
||||
import {fly} from 'svelte/transition'
|
||||
import {fly} from "svelte/transition"
|
||||
|
||||
export let show = true
|
||||
</script>
|
||||
|
||||
<button class="bg-black text-white p-2 rounded" on:click={() => show = !show}>Show/Hide</button>
|
||||
<button class="bg-black text-white p-2 rounded" on:click={() => (show = !show)}>Show/Hide</button>
|
||||
|
||||
{#if show}
|
||||
<div transition:fly|local={{y: 20}} class="flex flex-col gap-4">
|
||||
<div class="flex gap-2">
|
||||
<p>Inner Block: </p>
|
||||
<p>Inner Block:</p>
|
||||
<slot>Default</slot>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<p>Updating named slot: </p>
|
||||
<p>Updating named slot:</p>
|
||||
<slot name="the-slot-name">Another default</slot>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<script>
|
||||
import store from './store'
|
||||
import store from "./store"
|
||||
</script>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={$store}/> {$store}
|
||||
</label>
|
||||
<input type="checkbox" bind:checked={$store} />
|
||||
{$store}
|
||||
</label>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<script>
|
||||
import store from './store'
|
||||
import store from "./store"
|
||||
</script>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={$store}/> {$store}
|
||||
<input type="checkbox" bind:checked={$store} />
|
||||
{$store}
|
||||
</label>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import {writable} from 'svelte/store'
|
||||
import {writable} from "svelte/store"
|
||||
|
||||
function createStore() {
|
||||
return writable(true)
|
||||
}
|
||||
|
||||
function getStore() {
|
||||
if (typeof window === 'undefined') return createStore()
|
||||
if (typeof window === "undefined") return createStore()
|
||||
window.store = window.store || createStore()
|
||||
return window.store
|
||||
}
|
||||
|
||||
export default store = getStore()
|
||||
export default store = getStore()
|
||||
|
|
|
|||
Loading…
Reference in a new issue