Skip to content

Commit

Permalink
Allow grouping plugins visually into sections
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiask committed Sep 10, 2024
1 parent 709767d commit ef38db3
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 4 deletions.
2 changes: 2 additions & 0 deletions content_editor/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class ContentEditorInline(StackedInline):
button = ""
icon = ""
color = ""
sections = 0

def formfield_for_dbfield(self, db_field, *args, **kwargs):
"""Ensure ``region`` and ``ordering`` use a HiddenInput widget"""
Expand Down Expand Up @@ -171,6 +172,7 @@ def _content_editor_context(self, request, context):
"prefix": iaf.formset.prefix,
"button": button,
"color": iaf.opts.color,
"sections": iaf.opts.sections,
}
)
regions = [
Expand Down
9 changes: 8 additions & 1 deletion content_editor/static/content_editor/content_editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ html {

.order-machine-wrapper {
clear: both;
position: relative;
}

.order-machine {
Expand All @@ -73,10 +74,16 @@ html {
overflow: hidden;
}

.order-machine-section {
position: absolute;
background: rgba(0 0 0 / 0.1);
pointer-events: none;
}

.order-machine .inline-related {
border: 1px solid var(--hairline-color, #e8e8e8);
border-bottom: 0;
margin-top: 10px;
margin-top: 15px;
}

.order-machine .inline-related > h3 {
Expand Down
113 changes: 110 additions & 3 deletions content_editor/static/content_editor/content_editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ django.jQuery(($) => {
ContentEditor.regions.forEach((region) => {
ContentEditor.regionsByKey[region.key] = region
})
ContentEditor.hasSections = ContentEditor.plugins.some(
(plugin) => plugin.sections,
)

// Add basic structure. There is always at least one inline group if
// we even have any plugins.
Expand Down Expand Up @@ -262,13 +265,110 @@ django.jQuery(($) => {
row[0].classList.remove("selected")
})
window.__fs_dragging = null

updateSections()
}
})

arg.find(">h3, .card-title").attr("draggable", true) // Default admin, Jazzmin
arg.addClass("fs-draggable")
}

function findInlinesInOrder(context) {
const inlines = (context || orderMachine).find(
`.inline-related:not(.empty-form)[data-region="${ContentEditor.currentRegion}`,
)
inlines.sort((a, b) => a.style.order - b.style.order)
return inlines
}

let sectionsMap = new Map()

function updateSections(context) {
/* Bail out early if we wouldn't do nothing anyway */
if (!ContentEditor.hasSections) return

const inlines = findInlinesInOrder(context)

let indent = 0
let nextIndent
const stack = []
const wrapper = orderMachineWrapper[0]
const wrapperRect = wrapper.getBoundingClientRect()

const newSectionsMap = new Map()

function closeSection(atInline) {
const fromInline = stack.pop()
const from = fromInline.getBoundingClientRect()
const until = atInline.getBoundingClientRect()

let div = sectionsMap.get(fromInline)
if (div) {
sectionsMap.delete(fromInline)
} else {
div = document.createElement("div")
div.classList.add("order-machine-section")
wrapper.prepend(div)
}

newSectionsMap.set(fromInline, div)
div.style.top = `${from.top - wrapperRect.top - 5}px`
div.style.left = `${from.left - wrapperRect.left - 5}px`
div.style.right = "5px"
div.style.height = `${until.top - from.top + until.height + 10}px`
}

for (const inline of inlines) {
const prefix = inline.id.replace(/-[0-9]+$/, "")
inline.style.marginInlineStart = `${30 * indent}px`
nextIndent = Math.max(
0,
indent + ContentEditor.pluginsByPrefix[prefix].sections,
)

while (indent < nextIndent) {
stack.push(inline)
++indent
}

while (indent > nextIndent) {
closeSection(inline)
--indent
}

indent = nextIndent
}

while (stack.length) {
closeSection(inlines[inlines.length - 1])
}

for (const section of sectionsMap.values()) {
section.remove()
}
sectionsMap = newSectionsMap
}

if (ContentEditor.hasSections) {
/* From https://www.freecodecamp.org/news/javascript-debounce-example/ */
function debounce(func, timeout = 300) {
let timer
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, timeout)
}
}
const debouncedIndentInlines = debounce(updateSections, 10)

const resizeObserver = new ResizeObserver((entries) => {
debouncedIndentInlines()
})
resizeObserver.observe(orderMachineWrapper[0])
}

function reorderInlines(context) {
const inlines = (context || orderMachine).find(".inline-related")
inlines.not(".empty-form").each(function () {
Expand Down Expand Up @@ -512,12 +612,13 @@ django.jQuery(($) => {
e.target.classList.add("selected")

const pos = e.target.getBoundingClientRect()
const wrapperRect = orderMachineWrapper[0].getBoundingClientRect()
const buttons = qs(".plugin-buttons")
buttons.style.left = `${pos.left + window.scrollX + 30}px`
buttons.style.left = `${pos.left - wrapperRect.left + 30}px`

const y =
pos.top +
window.scrollY +
pos.top -
wrapperRect.top +
(e.target.classList.contains("last")
? 30 - buttons.getBoundingClientRect().height
: 0)
Expand Down Expand Up @@ -554,6 +655,8 @@ django.jQuery(($) => {
$(document).trigger("content-editor:activate", [$row])

$row.find("input, select, textarea").first().focus()

updateSections()
}

function handleFormsetRemoved(prefix) {
Expand All @@ -580,6 +683,8 @@ django.jQuery(($) => {
.each(function () {
$(document).trigger("content-editor:activate", [$(this)])
})

updateSections()
}, 0)
}

Expand Down Expand Up @@ -623,6 +728,8 @@ django.jQuery(($) => {

// Make sure only allowed plugins are in select
hideNotAllowedPluginButtons()

updateSections()
})

const collapseAllInput = $(".collapse-items input")
Expand Down

0 comments on commit ef38db3

Please sign in to comment.