forked from jan/psslides
refactor css + add dark mode + maybe some other stuff idk dont remember what else i did
This commit is contained in:
parent
22e17a92d4
commit
e309455edc
5 changed files with 368 additions and 53 deletions
|
|
@ -11,7 +11,13 @@
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="menubar"><b>p/slides</b></div>
|
<div class="menubar"><b>p/slides</b></div>
|
||||||
<div class="slidelist"></div>
|
<div class="slidelist">
|
||||||
|
<div class="slide-list-header">
|
||||||
|
<span>Slides</span>
|
||||||
|
<button class="add-slide" type="button" aria-label="Add slide">+</button>
|
||||||
|
</div>
|
||||||
|
<div class="slide-list-items"></div>
|
||||||
|
</div>
|
||||||
<div class="code"></div>
|
<div class="code"></div>
|
||||||
<div class="preview">
|
<div class="preview">
|
||||||
<div class="slide"></div>
|
<div class="slide"></div>
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,11 @@ export function codeToElement(
|
||||||
options: CodeToElementOptions = {}
|
options: CodeToElementOptions = {}
|
||||||
): HTMLIFrameElement {
|
): HTMLIFrameElement {
|
||||||
const iframe = document.createElement('iframe')
|
const iframe = document.createElement('iframe')
|
||||||
const sandbox = new Set<string>(['allow-same-origin'])
|
const sandbox = new Set<string>()
|
||||||
|
|
||||||
if (options.allowScripts) {
|
if (!options.allowScripts) {
|
||||||
|
sandbox.add('allow-same-origin')
|
||||||
|
} else {
|
||||||
sandbox.add('allow-scripts')
|
sandbox.add('allow-scripts')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,7 +30,7 @@ export async function createCodeThumbnail(
|
||||||
code: string,
|
code: string,
|
||||||
options: CodeToElementOptions = {}
|
options: CodeToElementOptions = {}
|
||||||
): Promise<HTMLImageElement> {
|
): Promise<HTMLImageElement> {
|
||||||
const iframe = codeToElement(code, { title: 'Code thumbnail', ...options })
|
const iframe = codeToElement(code, { title: 'Code thumbnail', ...options, allowScripts: false })
|
||||||
iframe.style.width = '640px'
|
iframe.style.width = '640px'
|
||||||
iframe.style.height = '360px'
|
iframe.style.height = '360px'
|
||||||
iframe.style.position = 'fixed'
|
iframe.style.position = 'fixed'
|
||||||
|
|
@ -36,13 +38,19 @@ export async function createCodeThumbnail(
|
||||||
iframe.style.border = '0'
|
iframe.style.border = '0'
|
||||||
document.body.appendChild(iframe)
|
document.body.appendChild(iframe)
|
||||||
|
|
||||||
await new Promise<void>((resolve) => iframe.addEventListener('load', () => resolve(), { once: true }))
|
try {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
iframe.addEventListener('load', () => resolve(), { once: true })
|
||||||
|
iframe.addEventListener('error', () => reject(new Error('Unable to render code thumbnail')), { once: true })
|
||||||
|
})
|
||||||
|
|
||||||
const content = iframe.contentDocument?.body
|
const content = iframe.contentDocument?.body
|
||||||
const thumbnail = document.createElement('img')
|
const thumbnail = document.createElement('img')
|
||||||
if (content) {
|
if (content) {
|
||||||
thumbnail.src = domToSvgDataUrl(content, { width: 640, height: 360 })
|
thumbnail.src = domToSvgDataUrl(content, { width: 640, height: 360 })
|
||||||
|
}
|
||||||
|
return thumbnail
|
||||||
|
} finally {
|
||||||
|
iframe.remove()
|
||||||
}
|
}
|
||||||
iframe.remove()
|
|
||||||
return thumbnail
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export function createEditor() {
|
||||||
{
|
{
|
||||||
value: 'test',
|
value: 'test',
|
||||||
language: 'html',
|
language: 'html',
|
||||||
theme: 'vs-light',
|
theme: 'vs-dark',
|
||||||
automaticLayout: true
|
automaticLayout: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
229
src/main.ts
229
src/main.ts
|
|
@ -15,8 +15,21 @@ const slides: Slide[] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
const editor = createEditor()
|
const editor = createEditor()
|
||||||
const slideList = document.querySelector<HTMLElement>('.slidelist')!
|
const slideList = document.querySelector<HTMLElement>('.slide-list-items')!
|
||||||
|
const addSlideButton = document.querySelector<HTMLButtonElement>('.add-slide')!
|
||||||
let currentSlide = 0
|
let currentSlide = 0
|
||||||
|
let draggedSlide: number | null = null
|
||||||
|
let globalSlideElement: HTMLElement | null = null
|
||||||
|
const slideElements = new Map<Slide, HTMLElement>()
|
||||||
|
const thumbnailRequests = new Map<number, number>()
|
||||||
|
let thumbnailDebounceTimer: number | undefined
|
||||||
|
|
||||||
|
function updateSelection() {
|
||||||
|
slideList.querySelectorAll<HTMLElement>('.slide-list-item').forEach((element) => {
|
||||||
|
const index = Number(element.dataset.slideId)
|
||||||
|
element.classList.toggle('selected', index === currentSlide)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function setSlide(index: number) {
|
function setSlide(index: number) {
|
||||||
const slide = index !== -1 ? slides[index] : { code: globalSlide }
|
const slide = index !== -1 ? slides[index] : { code: globalSlide }
|
||||||
|
|
@ -26,59 +39,215 @@ function setSlide(index: number) {
|
||||||
|
|
||||||
currentSlide = index
|
currentSlide = index
|
||||||
editor.getModel()?.setValue(slide.code)
|
editor.getModel()?.setValue(slide.code)
|
||||||
|
updateSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateSlideThumbnail(slide: any, i: number) {
|
async function generateSlideThumbnail(slide: Slide, i: number) {
|
||||||
|
const slideElement = document.createElement('div')
|
||||||
|
slideElement.classList.add('slide-list-item')
|
||||||
|
slideElement.dataset.slideId = i.toString()
|
||||||
|
if (i === -1) {
|
||||||
|
slideElement.classList.add('global-slide')
|
||||||
|
} else {
|
||||||
|
slideElement.draggable = true
|
||||||
|
slideElements.set(slide, slideElement)
|
||||||
|
}
|
||||||
|
|
||||||
const slideInList = document.createElement('button')
|
const slideInList = document.createElement('button')
|
||||||
slideInList.type = 'button'
|
slideInList.type = 'button'
|
||||||
slideInList.classList.add('slide-in-list')
|
slideInList.classList.add('slide-in-list')
|
||||||
|
slideInList.setAttribute('aria-label', i === -1 ? 'Global slide' : `Slide ${i + 1}`)
|
||||||
slideInList.addEventListener('click', () => {
|
slideInList.addEventListener('click', () => {
|
||||||
setSlide(i)
|
setSlide(Number(slideElement.dataset.slideId))
|
||||||
})
|
})
|
||||||
slideInList.dataset.slideId = i.toString()
|
|
||||||
|
|
||||||
|
const slideNumber = document.createElement('span')
|
||||||
|
slideNumber.classList.add('slide-number')
|
||||||
|
slideNumber.textContent = i === -1 ? 'G' : `${i + 1}`
|
||||||
|
|
||||||
const thumbnail = await createCodeThumbnail(slide.code)
|
const thumbnail = await createCodeThumbnail(slide.code)
|
||||||
slideInList.appendChild(thumbnail)
|
slideInList.append(slideNumber, thumbnail)
|
||||||
if (i === -1) {
|
slideElement.appendChild(slideInList)
|
||||||
const p = document.createElement("p")
|
|
||||||
p.innerText = "(global)"
|
if (i !== -1) {
|
||||||
slideInList.appendChild(p)
|
const deleteButton = document.createElement('button')
|
||||||
|
deleteButton.type = 'button'
|
||||||
|
deleteButton.classList.add('delete-slide')
|
||||||
|
deleteButton.setAttribute('aria-label', `Delete slide ${i + 1}`)
|
||||||
|
deleteButton.textContent = '×'
|
||||||
|
deleteButton.addEventListener('click', () => {
|
||||||
|
deleteSlide(Number(slideElement.dataset.slideId))
|
||||||
|
})
|
||||||
|
slideElement.appendChild(deleteButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
return slideInList
|
if (i === -1) {
|
||||||
|
globalSlideElement = slideElement
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === currentSlide) {
|
||||||
|
slideElement.classList.add('selected')
|
||||||
|
}
|
||||||
|
|
||||||
|
return slideElement
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshThumbnail(index: number, slide: Slide) {
|
||||||
|
const request = (thumbnailRequests.get(index) ?? 0) + 1
|
||||||
|
thumbnailRequests.set(index, request)
|
||||||
|
const oldSlideElement = index === -1 ? globalSlideElement : slideElements.get(slide)
|
||||||
|
const slideElement = await generateSlideThumbnail(slide, index)
|
||||||
|
if (thumbnailRequests.get(index) !== request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oldSlideElement?.replaceWith(slideElement)
|
||||||
|
if (index === -1) {
|
||||||
|
globalSlideElement = slideElement
|
||||||
|
} else {
|
||||||
|
slideElements.set(slide, slideElement)
|
||||||
|
}
|
||||||
|
updateSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setSlideList() {
|
async function setSlideList() {
|
||||||
const slideListSlidesList = [{ code: globalSlide }, ...slides]
|
const slideListSlidesList = [{ code: globalSlide }, ...slides]
|
||||||
|
slideList.replaceChildren()
|
||||||
|
slideElements.clear()
|
||||||
|
globalSlideElement = null
|
||||||
|
|
||||||
for (let i = -1; i < slides.length; i++) {
|
for (let i = -1; i < slides.length; i++) {
|
||||||
const slideInList = await generateSlideThumbnail(slideListSlidesList[i + 1], i)
|
slideList.appendChild(await generateSlideThumbnail(slideListSlidesList[i + 1], i))
|
||||||
slideList.appendChild(slideInList)
|
|
||||||
}
|
}
|
||||||
|
updateSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.onDidChangeModelContent(async () => {
|
function updateSlideOrder() {
|
||||||
const code = editor.getValue()
|
if (globalSlideElement) {
|
||||||
|
globalSlideElement.dataset.slideId = '-1'
|
||||||
let slide = null
|
slideList.appendChild(globalSlideElement)
|
||||||
|
|
||||||
if (currentSlide === -1) {
|
|
||||||
globalSlide = code
|
|
||||||
slide = { code }
|
|
||||||
} else {
|
|
||||||
slides[currentSlide].code = code
|
|
||||||
slide = slides[currentSlide]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const slideEl = document.querySelector(`[data-slide-id="${currentSlide}"]`)
|
for (const [index, slide] of slides.entries()) {
|
||||||
const slthumb = await generateSlideThumbnail(slide, currentSlide)
|
const slideElement = slideElements.get(slide)
|
||||||
slideEl?.replaceWith(slthumb)
|
if (!slideElement) {
|
||||||
})
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
function render() {
|
slideElement.dataset.slideId = index.toString()
|
||||||
setSlideList()
|
const slideButton = slideElement.querySelector<HTMLButtonElement>('.slide-in-list')
|
||||||
|
slideButton?.setAttribute('aria-label', `Slide ${index + 1}`)
|
||||||
|
const slideNumber = slideElement.querySelector<HTMLElement>('.slide-number')
|
||||||
|
if (slideNumber) {
|
||||||
|
slideNumber.textContent = `${index + 1}`
|
||||||
|
}
|
||||||
|
slideElement.querySelector<HTMLButtonElement>('.delete-slide')?.setAttribute('aria-label', `Delete slide ${index + 1}`)
|
||||||
|
slideList.appendChild(slideElement)
|
||||||
|
}
|
||||||
|
updateSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveSlide(from: number, to: number) {
|
||||||
|
if (from === to || from < 0 || to < 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const [movedSlide] = slides.splice(from, 1)
|
||||||
|
slides.splice(to, 0, movedSlide)
|
||||||
|
|
||||||
|
if (currentSlide === from) {
|
||||||
|
currentSlide = to
|
||||||
|
} else if (from < currentSlide && to >= currentSlide) {
|
||||||
|
currentSlide--
|
||||||
|
} else if (from > currentSlide && to <= currentSlide) {
|
||||||
|
currentSlide++
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSlideOrder()
|
||||||
|
editor.getModel()?.setValue(slides[currentSlide]?.code ?? globalSlide)
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSlide(index: number) {
|
||||||
|
if (index < 0 || slides.length <= 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const [deletedSlide] = slides.splice(index, 1)
|
||||||
|
slideElements.get(deletedSlide)?.remove()
|
||||||
|
slideElements.delete(deletedSlide)
|
||||||
|
|
||||||
|
if (index < currentSlide) {
|
||||||
|
currentSlide--
|
||||||
|
} else if (index === currentSlide) {
|
||||||
|
currentSlide = Math.min(currentSlide, slides.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSlideOrder()
|
||||||
setSlide(currentSlide)
|
setSlide(currentSlide)
|
||||||
}
|
}
|
||||||
|
|
||||||
render()
|
addSlideButton.addEventListener('click', async () => {
|
||||||
|
const slide = { code: '' }
|
||||||
|
slides.push(slide)
|
||||||
|
const slideElement = await generateSlideThumbnail(slide, slides.length - 1)
|
||||||
|
slideList.appendChild(slideElement)
|
||||||
|
updateSlideOrder()
|
||||||
|
setSlide(slides.length - 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
slideList.addEventListener('dragstart', (event) => {
|
||||||
|
const slideElement = (event.target as HTMLElement).closest<HTMLElement>('.slide-list-item')
|
||||||
|
const index = slideElement ? Number(slideElement.dataset.slideId) : -1
|
||||||
|
if (index < 0) {
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
draggedSlide = index
|
||||||
|
event.dataTransfer?.setData('text/plain', index.toString())
|
||||||
|
})
|
||||||
|
|
||||||
|
slideList.addEventListener('dragover', (event) => {
|
||||||
|
if (draggedSlide !== null) {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
slideList.addEventListener('drop', (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
const target = (event.target as HTMLElement).closest<HTMLElement>('.slide-list-item')
|
||||||
|
const targetIndex = target ? Number(target.dataset.slideId) : -1
|
||||||
|
if (draggedSlide !== null && targetIndex >= 0) {
|
||||||
|
moveSlide(draggedSlide, targetIndex)
|
||||||
|
}
|
||||||
|
draggedSlide = null
|
||||||
|
})
|
||||||
|
|
||||||
|
slideList.addEventListener('dragend', () => {
|
||||||
|
draggedSlide = null
|
||||||
|
})
|
||||||
|
|
||||||
|
editor.onDidChangeModelContent(() => {
|
||||||
|
const code = editor.getValue()
|
||||||
|
const slideIndex = currentSlide
|
||||||
|
const slide = slideIndex === -1 ? { code } : slides[slideIndex]
|
||||||
|
if (slideIndex === -1) {
|
||||||
|
globalSlide = code
|
||||||
|
} else {
|
||||||
|
slides[slideIndex].code = code
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbnailDebounceTimer !== undefined) {
|
||||||
|
window.clearTimeout(thumbnailDebounceTimer)
|
||||||
|
}
|
||||||
|
thumbnailDebounceTimer = window.setTimeout(() => {
|
||||||
|
thumbnailDebounceTimer = undefined
|
||||||
|
void refreshThumbnail(slideIndex, slide)
|
||||||
|
}, 250)
|
||||||
|
})
|
||||||
|
|
||||||
|
async function render() {
|
||||||
|
await setSlideList()
|
||||||
|
setSlide(currentSlide)
|
||||||
|
}
|
||||||
|
|
||||||
|
render()
|
||||||
|
|
|
||||||
154
src/style.css
154
src/style.css
|
|
@ -1,17 +1,34 @@
|
||||||
:root {
|
:root {
|
||||||
--border-color: #3f3f3f
|
color-scheme: dark;
|
||||||
|
|
||||||
|
--bg: #181818;
|
||||||
|
--bg-raised: #202020;
|
||||||
|
--bg-sunken: #101010;
|
||||||
|
|
||||||
|
--text: #e8e8e8;
|
||||||
|
--text-muted: #a0a0a0;
|
||||||
|
|
||||||
|
--border-color: #3f3f3f;
|
||||||
|
|
||||||
|
--accent: #008800;
|
||||||
|
--accent-text: #ffffff;
|
||||||
|
|
||||||
|
--slide-bg: #ffffff;
|
||||||
|
--slide-border: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 0.6fr) minmax(0, 40%) minmax(0, 1fr);
|
grid-template-columns: minmax(0, 0.6fr) minmax(0, 40%) minmax(0, 1fr);
|
||||||
grid-template-rows: 0.03fr 1fr;
|
grid-template-rows: 0.03fr 1fr;
|
||||||
gap: 0px 0px;
|
gap: 0;
|
||||||
grid-auto-flow: row;
|
grid-auto-flow: row;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"menubar menubar menubar"
|
"menubar menubar menubar"
|
||||||
|
|
@ -22,8 +39,8 @@ body {
|
||||||
|
|
||||||
.menubar {
|
.menubar {
|
||||||
grid-area: menubar;
|
grid-area: menubar;
|
||||||
background-color: #008800;
|
background-color: var(--accent);
|
||||||
color: white;
|
color: var(--accent-text);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
|
|
@ -34,13 +51,55 @@ body {
|
||||||
.slidelist {
|
.slidelist {
|
||||||
grid-area: slidelist;
|
grid-area: slidelist;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
background-color: var(--bg);
|
||||||
border-right: 1px solid var(--border-color);
|
border-right: 1px solid var(--border-color);
|
||||||
padding: 2rem;
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-list-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-height: 3rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-slide {
|
||||||
|
width: 1.75rem;
|
||||||
|
height: 1.75rem;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-raised);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-slide:hover,
|
||||||
|
.add-slide:focus-visible,
|
||||||
|
.delete-slide:hover,
|
||||||
|
.delete-slide:focus-visible {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent-text);
|
||||||
|
background: var(--accent);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-list-items {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code {
|
.code {
|
||||||
grid-area: code;
|
grid-area: code;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
background-color: var(--bg-sunken);
|
||||||
border-right: 1px solid var(--border-color);
|
border-right: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,27 +110,100 @@ body {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
background-color: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide {
|
.slide {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 16 / 9;
|
aspect-ratio: 16 / 9;
|
||||||
border: #000 solid 1px;
|
background: var(--slide-bg);
|
||||||
|
border: 1px solid var(--slide-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-list-item {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.25rem minmax(0, 1fr);
|
||||||
|
gap: 0.4rem;
|
||||||
|
align-items: start;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide-in-list {
|
.slide-in-list {
|
||||||
display: block;
|
grid-column: 1 / -1;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.25rem minmax(0, 1fr);
|
||||||
|
gap: 0.4rem;
|
||||||
|
align-items: start;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 1px solid #000;
|
min-width: 0;
|
||||||
padding: 0;
|
padding: 0.35rem;
|
||||||
margin-bottom: 1rem;
|
border: 1px solid var(--slide-border);
|
||||||
background: transparent;
|
background: var(--bg-raised);
|
||||||
|
color: var(--text);
|
||||||
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.slide-in-list:hover,
|
||||||
|
.slide-in-list:focus-visible {
|
||||||
|
border-color: var(--accent);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-list-item.selected .slide-in-list {
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: inset 2px 0 0 var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-number {
|
||||||
|
display: block;
|
||||||
|
min-height: 1rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-slide {
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
border-left: 2px solid var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-slide .slide-number {
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.slide-in-list img {
|
.slide-in-list img {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
object-fit: contain;
|
||||||
|
background: var(--slide-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-slide {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.25rem;
|
||||||
|
right: 0.25rem;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 1.4rem;
|
||||||
|
height: 1.4rem;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-raised);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-list-item:hover .delete-slide,
|
||||||
|
.delete-slide:focus-visible {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
Loading…
Add table
Reference in a new issue