Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3aa88e6
split keyframe & tutorial tools in nav
Cthomp7 Sep 16, 2025
776e92c
added keyframe name logic
Cthomp7 Sep 19, 2025
ef5e504
removed preset keyframe name
Cthomp7 Sep 19, 2025
f87324a
only show keyframe editing tools when keyframe is selected
Cthomp7 Sep 19, 2025
6b83ee2
added deleting keyframe logic
Cthomp7 Sep 23, 2025
a046d60
configured keyframes and keylogs in zipper.download
Cthomp7 Sep 23, 2025
bd9f04f
in-progress add menu
Cthomp7 Sep 23, 2025
b385faf
Merge branch 'dev' of https://github.com/netizenorg/netnet.studio int…
Cthomp7 Sep 28, 2025
33d4f2a
added html + css for spotlights editing menu
Cthomp7 Sep 28, 2025
8bb2c62
fixed minor typo in demo maker
Cthomp7 Sep 28, 2025
37f8ff2
added spotlight functionality
Cthomp7 Sep 28, 2025
c4e0510
adding handling for updating spotlights when changing keyframe
Cthomp7 Sep 28, 2025
6f40d66
in-progress widget dropdown & widget maker UI
Cthomp7 Sep 29, 2025
38ee38c
in-progress widget maker functionality
Cthomp7 Oct 19, 2025
ed93dca
Merge branch 'dev' of https://github.com/netizenorg/netnet.studio int…
Cthomp7 Oct 19, 2025
944df8e
updated netitor & netnet-standard-library modules
Cthomp7 Oct 19, 2025
4d3ef46
added handling for closing widget maker with unsaved changes
Cthomp7 Oct 19, 2025
3734adb
added WIDGETS.delete method && fixed _updateWidgetState
Cthomp7 Oct 19, 2025
05d3b99
fixed widget updating issues
Cthomp7 Oct 20, 2025
7d02029
Merge branch 'dev' of https://github.com/netizenorg/netnet.studio int…
Cthomp7 Nov 4, 2025
a51ab88
added upload overlay for asset uploading
Cthomp7 Nov 5, 2025
931eefb
configured files to zipper.download
Cthomp7 Nov 5, 2025
e33ec52
added disableFileList logic to FileDrop element
Cthomp7 Nov 9, 2025
b066db8
fixed maxFiles & changed addFile
Cthomp7 Nov 9, 2025
1dfab0f
changed window.open popup name
Cthomp7 Nov 10, 2025
ca4cc13
fixed custom editable divs from triggering shortcuts && fixed Netitor…
Cthomp7 Nov 10, 2025
d63881a
fixed FILES.files download
Cthomp7 Nov 10, 2025
eec3dcd
Merge branch 'dev' of https://github.com/netizenorg/netnet.studio int…
Cthomp7 Nov 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion www/core/netitor
19 changes: 10 additions & 9 deletions www/custom-elements/misc/file-drop/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
/* global HTMLElement CustomEvent FileList */
class FileDrop extends HTMLElement {
static get observedAttributes () { return ['accept', 'max-size', 'max-files', 'multiple'] }
static get observedAttributes () { return ['accept', 'max-size', 'max-files', 'multiple', 'disableFileList'] }

constructor (opts) {
super()
this.config = {
accept: '.pdf, .jpg, .png',
maxSize: '5MB',
maxFiles: '1',
multiple: false
maxFiles: 1,
multiple: false,
disableFileList: false
}
this.files = []
}
Expand Down Expand Up @@ -47,9 +48,9 @@ class FileDrop extends HTMLElement {
applyAttributes () {
this.config.accept = this.getAttribute('accept') ?? this.config.accept
this.config.maxSize = this.getAttribute('max-size') ?? this.config.maxSize
const mf = parseInt(this.getAttribute('max-files'), 10)
this.config.maxFiles = Number.isFinite(mf) ? mf : this.config.maxFiles
this.config.multiple = this.hasAttribute('multiple') || this.config.maxFiles > 1
this.config.maxFiles = this.getAttribute('maxFiles') || this.config.maxFiles
this.config.multiple = this.getAttribute('multiple') || this.config.maxFiles > 1
this.config.disableFileList = this.hasAttribute('disableFileList') || this.config.disableFileList
}

applyListeners () {
Expand Down Expand Up @@ -84,7 +85,7 @@ class FileDrop extends HTMLElement {
})

// return if maxFiles amount is already met
if (this.files.length >= maxFiles) {
if (this.files.length > maxFiles) {
this.displayMsg({
text: `Max amount of files (${maxFiles}) has already been uploaded. Please remove previously uploaded files and re-upload.`,
type: 'error'
Expand All @@ -102,7 +103,7 @@ class FileDrop extends HTMLElement {
const { add, rejected } = this.filterMaxFilesAmount(acceptedFiles)

this.files.push(...add)
this.renderFileItems()
if (!this.config.disableFileList) this.renderFileItems()

this.dispatchEvent(new CustomEvent('files-changed', {
detail: { added: add, rejected, files: this.files.slice() },
Expand All @@ -124,7 +125,7 @@ class FileDrop extends HTMLElement {

filterMaxFilesAmount (files) {
const maxFiles = this.config.maxFiles
const max = Number.isFinite(maxFiles) ? maxFiles : 1
const max = maxFiles ?? 1
const room = Math.max(0, max - this.files.length)
const add = files.slice(0, room)
const rejected = files.slice(room)
Expand Down
2 changes: 1 addition & 1 deletion www/widgets/demo-maker/popups/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ <h2>Demo Maker</h2>
<div id="demo-settings" class="menu" style="opacity: 0.1">
<div style="align-items: flex-start;">
<button id="new" class="pill-btn pill-btn--secondary" title="create a new annotation">+ New Note</button>
<button id="preview" class="pill-btn pill-btn--secondary" title="preview this annotation">💬 prevew Note</button>
<button id="preview" class="pill-btn pill-btn--secondary" title="preview this annotation">💬 Preview Note</button>
<reorderable-list id="note-list"></reorderable-list>
<button id="delete" class="pill-btn pill-btn--secondary" title="delete selected annotation(s)">
<img style="width: 14px;" src="/images/widgets/trash-meta-color.svg"> Delete Note
Expand Down
10 changes: 10 additions & 0 deletions www/widgets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ window.WIDGETS = { // GLOBAL WIDGETS OBJECT
close: (key) => {
if (WIDGETS.instantiated.includes(key)) WIDGETS[key].close()
else console.error(`WIDGETS: ${key} was never instantiated`)
},
delete: (key, cb) => {
if (WIDGETS.instantiated.includes(key) && WIDGETS[key]) {
WIDGETS[key].close()
delete WIDGETS[key]
const i = WIDGETS.instantiated.indexOf(key)
if (i !== -1) WIDGETS.instantiated.splice(i, 1)
} else {
console.error(`Widget (${key}) does not exist.`)
}
}
}

Expand Down
100 changes: 93 additions & 7 deletions www/widgets/tutorial-maker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class TutorialMaker extends Widget {
if (e.origin !== window.location.origin) return // for security
if (type === 'tut-mkr-opened-tutorial') { // opened tutorial from zip
this._loadTutorial(payload)
} else if (type === 'tut-mkr-get-data') { // get tutorial data
const data = this._getData()
this._messagePopup('tut-mkr-data', data)
} else if (type === 'tut-mkr-update-metadata') { // new metadata
this._updateMetadata(payload)
} else if (type === 'tut-mkr-get-metadata') { // asked for metadata
Expand Down Expand Up @@ -58,6 +61,14 @@ class TutorialMaker extends Widget {
this.hvp.toggle()
} else if (type === 'tut-mkr-explain') { // clicked on (?) explainer button
this._openConvo(payload)
} else if (type === 'tut-mkr-sptlght') {
this._addSpotlight(payload.spotlight)
} else if (type === 'tut-mkr-update-widget') {
this._updateWidget(payload.widget, payload.remove)
} else if (type === 'tut-mkr-update-widget-state') {
this._updateWidgetState(payload.widget)
} else if (type === 'tut-mkr-open-widget') {
this._openWidget(payload.widget, payload.open)
}
})

Expand Down Expand Up @@ -126,13 +137,22 @@ class TutorialMaker extends Widget {
}

_updateFrame (type, data) {
const { timecode, name } = data
const { timecode, name, spotlight } = data
let frame = this.hvp.data[type].find(k => k.timecode === timecode)
if (type === 'keyframes') {
if (!frame) { // create keyframe
if (data?.remove) { // delete keyframe
if (frame) {
this.hvp.data[type].splice(this.hvp.data[type].indexOf(frame), 1)
return { frame, remove: true }
} else {
const error = `Failed to remove keyframe. No keyframe found for: ${timecode} seconds`
console.error(error)
return { error }
}
} else if (!frame) { // create keyframe
frame = {
timecode,
name: `keyframe ${(this.hvp.data[type].length + 1).toString()}`,
name: name ?? '',
video: this._getSizeAndPosition(this.hvp),
widgets: this._getCurrentWidgets(),
netitor: this._getNetitorData(),
Expand All @@ -145,7 +165,7 @@ class TutorialMaker extends Widget {
frame.name = name ?? ''
frame.video = this._getSizeAndPosition(this.hvp)
frame.widgets = this._getCurrentWidgets()
frame.netitor = this._getNetitorData()
frame.netitor = this._getNetitorData(spotlight)
frame.netnet = this._getNetNetPos()
}
} else if (type === 'keylogs') {
Expand All @@ -154,11 +174,77 @@ class TutorialMaker extends Widget {
return { frame }
}

_getData () {
const keyframes = this.hvp.data.keyframes
const keylogs = this.hvp.data.keylogs
// TODO: fetch widgets
return { keyframes, keylogs }
}

_addSpotlight (ls) {
if ((Array.isArray(ls) && ls.length <= 0) || !ls) {
NNE.spotlight(null)
} else {
const lines = []
ls.forEach(l => {
if (l.includes('-')) {
const [start, end] = l.split('-').map(Number)
for (let i = start; i <= end; i++) {
lines.push(i)
}
} else lines.push(Number(l))
})
NNE.spotlight(lines)
}
}

_openWidget (widget, open) {
const { key, title, innerHTML } = widget
if (WIDGETS[key] && open) WIDGETS[key].open()
else if (WIDGETS[key] && !open) WIDGETS[key].close()
else if (open) {
WIDGETS.create({ key, title, innerHTML })
WIDGETS[key].open()
}
}

// update widget in HVP
_updateWidget (widget, remove) {
const { key, title, innerHTML, type } = widget
if (remove) { // remove widget
delete this.hvp.data.widgets[key]
WIDGETS.delete(key)
} else if (widget.oldKey) { // save over existent widget
this.hvp.data.widgets[widget.oldKey] = { key, title, innerHTML, type }
} else { // create new widget
this.hvp.data.widgets[key] = { key, title, innerHTML, type }
}
}

// updates widgets state in netnet (WIDGETS)
_updateWidgetState (widget) {
const { key, title, innerHTML } = widget
const k = widget.oldKey || key
if (widget.oldKey && widget.oldKey !== key && WIDGETS[k]) {
// remove old widget and open new widget if key changed
WIDGETS.delete(k)
WIDGETS.create({ key, title, innerHTML })
WIDGETS[key].open()
} else if (WIDGETS[k]) {
WIDGETS[k].key = key
WIDGETS[k].title = title
WIDGETS[k].innerHTML = innerHTML
} else {
WIDGETS.create({ key, title, innerHTML })
WIDGETS[key].open()
}
}

// ............................ helpers ......................................

_openPopup (type, payload) {
const url = 'widgets/tutorial-maker/popups/index.html'
this.popup = window.open(url, 'example-widget', 'width=200,height=200')
this.popup = window.open(url, 'tut-mkr-widget', 'width=200,height=200')
// keep an eye on the pop up to see if it closed
this.popupWatcher = setInterval(() => {
if (this.popup && this.popup.closed) {
Expand Down Expand Up @@ -250,12 +336,12 @@ class TutorialMaker extends Widget {
.map(w => this._getSizeAndPosition(w))
}

_getNetitorData () {
_getNetitorData (ls) {
const s = NNE.cm.getScrollInfo()
return {
code: NNE.code,
scrollTo: { x: s.left, y: s.top },
spotlight: null, // TODO: need to send data from popup
spotlight: ls ?? [],
layout: NNW.layout
}
}
Expand Down
118 changes: 113 additions & 5 deletions www/widgets/tutorial-maker/popups/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
<link rel="stylesheet" href="recorder.css">
<link rel="stylesheet" href="timeline.css">

<div id="modal" class="custom-modal" style="display: none;">
<div class="custom-modal-inner"></div>
</div>

<section id="start" class="overlay">
<button id="new" class="pill-btn pill-btn--secondary">new</button>
<button id="open" class="pill-btn pil-btn--secondary">open</button>
Expand Down Expand Up @@ -121,13 +125,101 @@ <h1>Video Recorder</h1>
</div>
</section>

<!-- style="display: none;" -->
<section id="upload" class="overlay" style="display: none;">
<div style="display: flex; justify-content: space-between; margin-bottom: 15px;">
<div name="upload-goback" class="pill-btn pill-btn--secondary">go back</div>
<button class="pill-btn pill-btn--secondary" style="padding-bottom: 3px;">?</button>
</div>
<div class="file-drop-cont">
<file-drop accept=".png, .jpg, .gif" maxFiles="50" maxSize="1GB" disableFileList="true"></file-drop>
</div>
<p>Uploaded Assets</p>
<div class="uploaded-assets"></div>
</section>


<section id="widget-maker" class="overlay" style="display: none;">
<div>
<div class="widget-maker-inputs">
<div>
<p>key:</p>
<div id="widget-key-input" class="tut-mkr-input-dark" contentEditable="true" data-placeholder="type widget key..." kf-edit-tool></div>
</div>
<div>
<p>title:</p>
<div id="widget-title-input" class="tut-mkr-input-dark" contentEditable="true" data-placeholder="type widget title..." kf-edit-tool></div>
</div>
</div>
<div id="widget-maker-html"></div>
<div class="widget-maker-bottom">
<div name="widget-maker-goback" class="pill-btn pill-btn--secondary">go back</div>
<div name="widget-maker-update" class="pill-btn pill-btn--secondary">create</div>
</div>
</div>
</section>


<nav>
<button id="open-metadata" class="pill-btn pill-btn--secondary">settings</button>
<button id="create-keyframe" class="pill-btn pill-btn--secondary">create keyframe</button>
<button id="update-keyframe" class="pill-btn pill-btn--secondary">update keyframe</button>
<button name="nav-infos" class="pill-btn pill-btn--secondary">?</button>
<button id="download-tutorial" class="pill-btn pill-btn--secondary">download</button>
<div>
<div class="keyframe-tools">
<div style="position: relative;">
<button id="create-marker" class="pill-btn pill-btn--secondary">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 8.13908L1.72182 8.13908" stroke="#C3C3DA" stroke-width="3" stroke-linecap="round"/>
<path d="M7.8609 14.2782L7.8609 1.99999" stroke="#C3C3DA" stroke-width="3" stroke-linecap="round"/>
</svg>
</button>
<div id="create-options" style="display: none;">
<div id="create-keyframe" class="pill-btn pill-btn--secondary icon-btn">
<svg width="11" height="12" viewBox="0 0 11 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1.4393" y="5.97487" width="5.74266" height="5.74266" transform="rotate(-45 1.4393 5.97487)" stroke="#C56DBB" stroke-width="2"/>
</svg>
<p>new keyframe</p>
</div>
<div id="create-keylog" class="pill-btn pill-btn--secondary icon-btn">
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1.41421" y="5.47488" width="5.74266" height="5.74266" transform="rotate(-45 1.41421 5.47488)" stroke="#7DCA95" stroke-width="2"/>
</svg>
<p>new keylog</p>
</div>
</div>
</div>
<div id="kf-name-input" class="tut-mkr-input-dark" contentEditable="true" data-placeholder="type keyframe name..." kf-edit-tool></div>
<button id="update-keyframe" class="pill-btn pill-btn--secondary" kf-edit-tool>update</button>
<button id="delete-keyframe" class="pill-btn pill-btn--secondary" kf-edit-tool>delete</button>
<!-- spotlight edit menu -->
<div id="spotlight-dd" class="dd-edit dd-styles" kf-edit-tool>
<div class="dd-title">
<p>spotlights</p>
<svg width="18" height="9" viewBox="0 0 21 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 1.5L10.5 10.5L19.5 1.5" stroke="#C3C3DB" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div id="sptlght-edit" class="dd-edit-menu dd-styles" style="display: none;">
<div>
<div id="sptlght-line-input" class="tut-mkr-input-light" contenteditable="true" data-placeholder="enter line numbers..."></div>
<button name="sptlght-enter" class="pill-btn pill-btn--secondary">enter</button>
</div>
<div class="hrztl-line"></div>
<div id="spot-tags"></div>
<div class="sptlght-footer">
<div></div>
<div>
<button name="sptlght-show" class="pill-btn pill-btn--secondary red">preview</button>
<button name="sptlght-clear" class="pill-btn pill-btn--secondary red">clear all</button>
<button class="pill-btn pill-btn--secondary">?</button>
</div>
</div>
</div>
</div>
<!-- <button name="nav-infos" class="pill-btn pill-btn--secondary" kf-edit-tool>?</button> -->
</div>
<div class="tutorial-tools">
<button id="open-metadata" class="pill-btn pill-btn--secondary">settings</button>
<button id="download-tutorial" class="pill-btn pill-btn--secondary">download</button>
</div>
</div>
</nav>

<div class="labels">
Expand All @@ -152,7 +244,23 @@ <h1>Video Recorder</h1>
</defs>
</svg>

<footer>
<div id="widget-dd" class="dd-edit dd-styles" kf-edit-tool>
<div class="dd-title">
<p>widgets</p>
<svg width="18" height="9" viewBox="0 0 21 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 1.5L10.5 10.5L19.5 1.5" stroke="#C3C3DB" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<div id="widget-options" class="dd-edit-menu-flip dd-styles" style="display: none;">
<button name="widget-create" class="pill-btn pill-btn--secondary">create new widget</button>
</div>
</div>
</div>
<button name="upload-assets" class="pill-btn pill-btn--secondary">upload assets</button>
</footer>

<script src="/nn.min.js"></script>
<script src="/netitor.min.js"></script>
<script src="/custom-elements/misc/reorderable-list/index.js"></script>
<script src="/custom-elements/misc/file-drop/index.js"></script>
<script src="jszip.min.js"></script>
Expand Down
Loading