Skip to content

Commit

Permalink
Add support for data-turbo-confirm (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
hopsoft committed Jul 21, 2023
1 parent ca1719a commit cb692d0
Show file tree
Hide file tree
Showing 16 changed files with 316 additions and 509 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ RUN gem update --system && gem install bundler

RUN mkdir -p /mnt/external/node_modules /mnt/external/bundle

COPY . /opt/turbo_boost-commands
WORKDIR /opt/turbo_boost-commands
COPY . /app
WORKDIR /app
CMD bin/docker/run/remote
4 changes: 2 additions & 2 deletions app/assets/builds/@turbo-boost/commands.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions app/assets/builds/@turbo-boost/commands.js.map

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions app/assets/builds/@turbo-boost/commands.metafile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"inputs":{"node_modules/@turbo-boost/streams/app/assets/builds/@turbo-boost/streams.js":{"bytes":47867,"imports":[],"format":"esm"},"app/javascript/meta.js":{"bytes":341,"imports":[],"format":"esm"},"app/javascript/events.js":{"bytes":784,"imports":[{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/state/observable.js":{"bytes":929,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"../meta"},{"path":"app/javascript/events.js","kind":"import-statement","original":"../events"}],"format":"esm"},"app/javascript/state/index.js":{"bytes":1840,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"../meta"},{"path":"app/javascript/state/observable.js","kind":"import-statement","original":"./observable"},{"path":"app/javascript/events.js","kind":"import-statement","original":"../events"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/renderer.js":{"bytes":477,"imports":[],"format":"esm"},"app/javascript/activity.js":{"bytes":283,"imports":[],"format":"esm"},"app/javascript/lifecycle.js":{"bytes":637,"imports":[{"path":"app/javascript/activity.js","kind":"import-statement","original":"./activity"},{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"}],"format":"esm"},"app/javascript/turbo.js":{"bytes":2040,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"./meta"},{"path":"app/javascript/state/index.js","kind":"import-statement","original":"./state"},{"path":"app/javascript/renderer.js","kind":"import-statement","original":"./renderer"},{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"},{"path":"app/javascript/lifecycle.js","kind":"import-statement","original":"./lifecycle"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/schema.js":{"bytes":210,"imports":[{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/confirmation.js":{"bytes":919,"imports":[{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"},{"path":"app/javascript/schema.js","kind":"import-statement","original":"./schema"}],"format":"esm"},"app/javascript/delegates.js":{"bytes":874,"imports":[{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/elements.js":{"bytes":1463,"imports":[{"path":"app/javascript/schema.js","kind":"import-statement","original":"./schema"},{"path":"app/javascript/lifecycle.js","kind":"import-statement","original":"./lifecycle"}],"format":"esm"},"app/javascript/drivers/form.js":{"bytes":314,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"../meta"}],"format":"esm"},"app/javascript/urls.js":{"bytes":241,"imports":[],"format":"esm"},"app/javascript/drivers/frame.js":{"bytes":219,"imports":[{"path":"app/javascript/urls.js","kind":"import-statement","original":"../urls"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/drivers/method.js":{"bytes":266,"imports":[{"path":"app/javascript/urls.js","kind":"import-statement","original":"../urls"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/drivers/window.js":{"bytes":1930,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"../meta"},{"path":"app/javascript/state/index.js","kind":"import-statement","original":"../state"},{"path":"app/javascript/events.js","kind":"import-statement","original":"../events"},{"path":"app/javascript/lifecycle.js","kind":"import-statement","original":"../lifecycle"},{"path":"app/javascript/urls.js","kind":"import-statement","original":"../urls"},{"path":"app/javascript/renderer.js","kind":"import-statement","original":"../renderer"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/drivers/index.js":{"bytes":2064,"imports":[{"path":"app/javascript/elements.js","kind":"import-statement","original":"../elements"},{"path":"app/javascript/drivers/form.js","kind":"import-statement","original":"./form"},{"path":"app/javascript/drivers/frame.js","kind":"import-statement","original":"./frame"},{"path":"app/javascript/drivers/method.js","kind":"import-statement","original":"./method"},{"path":"app/javascript/drivers/window.js","kind":"import-statement","original":"./window"}],"format":"esm"},"app/javascript/logger.js":{"bytes":731,"imports":[{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"}],"format":"esm"},"app/javascript/uuids.js":{"bytes":221,"imports":[],"format":"esm"},"app/javascript/index.js":{"bytes":3513,"imports":[{"path":"node_modules/@turbo-boost/streams/app/assets/builds/@turbo-boost/streams.js","kind":"import-statement","original":"@turbo-boost/streams"},{"path":"app/javascript/turbo.js","kind":"import-statement","original":"./turbo"},{"path":"app/javascript/schema.js","kind":"import-statement","original":"./schema"},{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"},{"path":"app/javascript/activity.js","kind":"import-statement","original":"./activity"},{"path":"app/javascript/confirmation.js","kind":"import-statement","original":"./confirmation"},{"path":"app/javascript/delegates.js","kind":"import-statement","original":"./delegates"},{"path":"app/javascript/drivers/index.js","kind":"import-statement","original":"./drivers"},{"path":"app/javascript/meta.js","kind":"import-statement","original":"./meta"},{"path":"app/javascript/elements.js","kind":"import-statement","original":"./elements"},{"path":"app/javascript/lifecycle.js","kind":"import-statement","original":"./lifecycle"},{"path":"app/javascript/logger.js","kind":"import-statement","original":"./logger"},{"path":"app/javascript/state/index.js","kind":"import-statement","original":"./state"},{"path":"app/javascript/urls.js","kind":"import-statement","original":"./urls"},{"path":"app/javascript/uuids.js","kind":"import-statement","original":"./uuids"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"app/assets/builds/@turbo-boost/commands.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":238243},"app/assets/builds/@turbo-boost/commands.js":{"imports":[],"exports":["default"],"entryPoint":"app/javascript/index.js","inputs":{"node_modules/@turbo-boost/streams/app/assets/builds/@turbo-boost/streams.js":{"bytesInOutput":47846},"app/javascript/meta.js":{"bytesInOutput":254},"app/javascript/events.js":{"bytesInOutput":500},"app/javascript/state/observable.js":{"bytesInOutput":415},"app/javascript/state/index.js":{"bytesInOutput":794},"app/javascript/renderer.js":{"bytesInOutput":264},"app/javascript/activity.js":{"bytesInOutput":179},"app/javascript/lifecycle.js":{"bytesInOutput":299},"app/javascript/turbo.js":{"bytesInOutput":1031},"app/javascript/schema.js":{"bytesInOutput":166},"app/javascript/confirmation.js":{"bytesInOutput":419},"app/javascript/delegates.js":{"bytesInOutput":440},"app/javascript/elements.js":{"bytesInOutput":783},"app/javascript/drivers/form.js":{"bytesInOutput":188},"app/javascript/urls.js":{"bytesInOutput":167},"app/javascript/drivers/frame.js":{"bytesInOutput":98},"app/javascript/drivers/method.js":{"bytesInOutput":132},"app/javascript/drivers/window.js":{"bytesInOutput":1188},"app/javascript/drivers/index.js":{"bytesInOutput":1002},"app/javascript/logger.js":{"bytesInOutput":408},"app/javascript/uuids.js":{"bytesInOutput":155},"app/javascript/index.js":{"bytesInOutput":1676}},"bytes":58909}}}
31 changes: 31 additions & 0 deletions app/javascript/confirmation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { commandEvents } from './events'
import schema from './schema'

const confirmation = {
method: message => Promise.resolve(confirm(message))
}

const isTurboMethod = event => event.detail.driver === 'method'

const isTurboForm = event => {
if (event.detail.driver !== 'form') return false

const element = event.target
const frame = element.closest('turbo-frame')
const target = element.closest(`[${schema.frameAttribute}]`)
return !!(frame || target)
}

const shouldDelegate = event => isTurboMethod(event) || isTurboForm(event)

document.addEventListener(commandEvents.start, async event => {
const message = event.target.getAttribute(schema.confirmAttribute)
if (!message) return

event.detail.confirmation = true

if (shouldDelegate(event)) return // delegate confirmation handling to Turbo
if (await confirmation.method(message)) event.preventDefault()
})

export default confirmation
17 changes: 0 additions & 17 deletions app/javascript/drivers/turbo_form.js

This file was deleted.

14 changes: 8 additions & 6 deletions app/javascript/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ export const stateEvents = {
export const allEvents = { ...commandEvents, ...stateEvents }

export function dispatch (name, target, options = {}) {
options = options || {}
options.detail = options.detail || {}
target = target || document
const evt = new CustomEvent(name, { ...options, bubbles: true })
target.dispatchEvent(evt)
return evt
return new Promise(resolve => {
options = options || {}
options.detail = options.detail || {}
target = target || document
const evt = new CustomEvent(name, { ...options, bubbles: true })
target.dispatchEvent(evt)
resolve(evt)
})
}
12 changes: 7 additions & 5 deletions app/javascript/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import './turbo'
import schema from './schema'
import { dispatch, commandEvents, stateEvents } from './events'
import activity from './activity'
import confirmation from './confirmation'
import delegates from './delegates'
import drivers from './drivers'
import meta from './meta'
Expand All @@ -23,7 +24,7 @@ function buildCommandPayload (id, element) {
}
}

function invokeCommand (event) {
async function invokeCommand (event) {
let element
let payload = {}

Expand All @@ -38,15 +39,15 @@ function invokeCommand (event) {
...buildCommandPayload(commandId, element),
driver: driver.name,
frameId: driver.frame ? driver.frame.id : null,
src: driver.src
src: driver.src,
}

const startEvent = dispatch(commandEvents.start, element, {
const startEvent = await dispatch(commandEvents.start, element, {
cancelable: true,
detail: payload
})

if (startEvent.defaultPrevented)
if (startEvent.defaultPrevented || startEvent.detail.confirmation && event.defaultPrevented)
return dispatch(commandEvents.abort, element, {
detail: {
message: `An event handler for '${commandEvents.start}' prevented default behavior and blocked command invocation!`,
Expand All @@ -60,7 +61,7 @@ function invokeCommand (event) {
...buildCommandPayload(commandId, element),
driver: driver.name,
frameId: driver.frame ? driver.frame.id : null,
src: driver.src
src: driver.src,
}

activity.add(payload)
Expand Down Expand Up @@ -114,6 +115,7 @@ self.TurboBoost = {
}

self.TurboBoost.Commands = {
confirmation,
logger,
schema,
events: commandEvents,
Expand Down
4 changes: 2 additions & 2 deletions app/javascript/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ const logLevels = {
Object.values(events).forEach(name => {
addEventListener(name, event => {
if (logLevels[currentLevel].includes(event.type)) {
const level = currentLevel === 'debug' ? 'log' : currentLevel
console[level](event.type, { target: event.target, detail: event.detail })
const { target, detail } = event
console[currentLevel](event.type, { target, detail })
}
})
})
Expand Down
3 changes: 2 additions & 1 deletion app/javascript/schema.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const schema = {
frameAttribute: 'data-turbo-frame',
methodAttribute: 'data-turbo-method',
commandAttribute: 'data-turbo-command'
commandAttribute: 'data-turbo-command',
confirmAttribute: 'data-turbo-confirm'
}

export default { ...schema }
40 changes: 40 additions & 0 deletions bin/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as esbuild from 'esbuild'
import fs from 'fs'

const context = await esbuild.context({
entryPoints: ['app/javascript/index.js'],
external: ['@hotwired/turbo'],
bundle: true,
format: 'esm',
logLevel: 'debug',
metafile: true,
minify: true,
outfile: 'app/assets/builds/@turbo-boost/commands.js',
sourcemap: true,
target: ['chrome79', 'edge44', 'es2020', 'firefox71', 'opera65', 'safari13']
})

const watch = process.argv.includes('--watch')

if (watch) {
context.logLevel = 'verbose'
await context.watch()
} else {
const result = await context.rebuild()
const metafile = 'app/assets/builds/@turbo-boost/commands.metafile.json'

fs.writeFile(metafile, JSON.stringify(result.metafile), ex => {
if (ex) {
console.error('Build failed!❗️')
conosle.error(ex)
} else {
const message = [
'Build succeeded! 🚀',
`|- Metafile saved to ... → ${metafile}`,
'|- Analyze the bundle at → https://esbuild.github.io/analyze/'
]
console.log(message.join('\n'))
}
context.dispose()
})
}
2 changes: 0 additions & 2 deletions bin/docker/run/local
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ fi
# ============================================================================================================
# Dependencies
# ============================================================================================================
rm -rf /opt/turbo_boost-commands/node_modules
ln -sf /mnt/external/node_modules /opt/turbo_boost-commands/node_modules
yarn
bundle

Expand Down
2 changes: 1 addition & 1 deletion bin/standardize
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

bundle exec magic_frozen_string_literal
bundle exec standardrb --fix
yarn run prettier-standard package.json app/javascript/**/*.js
yarn run prettier-standard package.json bin/build.mjs app/javascript/**/*.js
yarn run rustywind --write test/dummy/app
yarn run rustywind --write --custom-regex "(:\s[\"'])(.+)[\"']" test/dummy/app/views/_tailwind.yml.erb
9 changes: 4 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
version: "3.9"

x-default-env: &default_env
GEM_HOME: /mnt/external/bundle
PLAYWRIGHT_BROWSERS_PATH: 0
RAILS_ENV: development
RAILS_LOG_TO_STDOUT: true
Expand All @@ -17,9 +16,9 @@ x-default-app: &default_app
networks:
- primary
volumes:
- .:/opt/turbo_boost-commands
- bundle:/mnt/external/bundle
- node_modules:/mnt/external/node_modules
- .:/app
- bundle:/usr/local/bundle
- node_modules:/app/node_modules

networks:
primary:
Expand Down Expand Up @@ -61,7 +60,7 @@ services:
tailwind:
<<: *default_app
container_name: turbo_boost-commands-tailwind
working_dir: /opt/turbo_boost-commands/test/dummy
working_dir: /app/test/dummy
command: bin/rails tailwindcss:watch
depends_on:
web:
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
],
"main": "app/assets/builds/@turbo-boost/commands.js",
"files": [
"app/assets/builds"
"app/assets/builds/commands.js",
"app/assets/builds/commands.js.map"
],
"repository": "https://github.com/hopsoft/turbo_boost-commands",
"author": "Nate Hopkins (hopsoft) <[email protected]>",
Expand All @@ -23,15 +24,14 @@
"@hotwired/turbo-rails": ">= 7.2.0"
},
"devDependencies": {
"esbuild": "^0.17.3",
"eslint": "^8.19.0",
"esbuild": "^0.18.14",
"flowbite": "1.5.3",
"playwright": "^1.29.2",
"prettier-standard": "^16.4.1",
"rustywind": "^0.16.0"
},
"scripts": {
"build": "esbuild app/javascript/index.js --bundle --minify --sourcemap --format=esm --target=es2020,chrome79,edge44,firefox71,opera65,safari13 --analyze --outfile=app/assets/builds/@turbo-boost/commands.js",
"build:watch": "yarn build -- --watch"
"build": "yarn run prettier-standard package.json bin/build.mjs app/javascript/**/*.js && node bin/build.mjs",
"build:watch": "yarn build --watch"
}
}
Loading

0 comments on commit cb692d0

Please sign in to comment.