diff --git a/music-player/__init__.py b/music-player/__init__.py new file mode 100644 index 0000000..511a0ca --- /dev/null +++ b/music-player/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import controllers +from . import models \ No newline at end of file diff --git a/music-player/__manifest__.py b/music-player/__manifest__.py new file mode 100644 index 0000000..27e5dda --- /dev/null +++ b/music-player/__manifest__.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +{ + 'name': "musicPlayer", + + 'summary': """ + Short (1 phrase/line) summary of the module's purpose, used as + subtitle on modules listing or apps.openerp.com""", + + 'description': """ + Long description of module's purpose + """, + + 'author': "My Company", + 'website': "https://www.yourcompany.com", + + # Categories can be used to filter modules in modules listing + # Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml + # for the full list + 'category': 'Uncategorized', + 'version': '0.1', + + # any module necessary for this one to work correctly + 'depends': ['base'], + + # always loaded + 'data': [ + 'security/ir.model.access.csv', + 'views/views.xml', + 'views/templates.xml', + ], + + # only loaded in demonstration mode + 'demo': [ + 'demo/demo.xml', + ], + 'installable':True, + 'application':True, +} diff --git a/music-player/__pycache__/__init__.cpython-38.pyc b/music-player/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..290e732 Binary files /dev/null and b/music-player/__pycache__/__init__.cpython-38.pyc differ diff --git a/music-player/controllers/__init__.py b/music-player/controllers/__init__.py new file mode 100644 index 0000000..457bae2 --- /dev/null +++ b/music-player/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import controllers \ No newline at end of file diff --git a/music-player/controllers/__pycache__/__init__.cpython-38.pyc b/music-player/controllers/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..2933401 Binary files /dev/null and b/music-player/controllers/__pycache__/__init__.cpython-38.pyc differ diff --git a/music-player/controllers/__pycache__/controllers.cpython-38.pyc b/music-player/controllers/__pycache__/controllers.cpython-38.pyc new file mode 100644 index 0000000..0b4fbb5 Binary files /dev/null and b/music-player/controllers/__pycache__/controllers.cpython-38.pyc differ diff --git a/music-player/controllers/controllers.py b/music-player/controllers/controllers.py new file mode 100644 index 0000000..e346b72 --- /dev/null +++ b/music-player/controllers/controllers.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from odoo import http +from odoo.http import Response +import json +from odoo.modules.module import get_module_resource + + + +class MusicPlayer(http.Controller): + @http.route('/music', auth='public') + def index(self, **kw): + return http.request.render('music_player.music_template') + + @http.route('/music/search', auth='public',type="http",methods=["GET"]) + def search(self, **kw): + # Retrieve the song name from search query + song_name = kw.get('song_name') + musics=http.request.env['music_player.music_player'].search_read([('name','ilike',song_name)],fields={"name","url"}) + if not musics: + musics= "Song not found" + + return Response(json.dumps({'result':musics}),content_type='application/json') + + @http.route('/music/',type='http',auth='public') + def object(self, music, **kw): + music_file_path=get_module_resource('music_player','static/songs',music.filename) + file = open(music_file_path,'rb').read() + return file + +# @http.route('/music_player/music_player/objects/', auth='public') +# def object(self, obj, **kw): +# return http.request.render('music_player.object', { +# 'object': obj +# }) diff --git a/music-player/demo/demo.xml b/music-player/demo/demo.xml new file mode 100644 index 0000000..f13bd4c --- /dev/null +++ b/music-player/demo/demo.xml @@ -0,0 +1,36 @@ + + + + Akon-Be-With-You + Akon-Be-With-You.mp3 + + + Akon-Beautiful + Akon-Beautiful.mp3 + + + Akon-Birthmark + Akon-Birthmark.mp3 + + + Akon-Keep-You-Much-Longer + Akon-Keep-You-Much-Longer.mp3 + + + Akon-Sunny-Day + Akon-Sunny-Day.mp3 + + + + Temp-song-1 + Temp-song-1.mp3 + + + Temp-song-2 + Temp-song-2.mp3 + + + Temp-song-3 + Temp-song-3.mp3 + + \ No newline at end of file diff --git a/music-player/models/__init__.py b/music-player/models/__init__.py new file mode 100644 index 0000000..6bce365 --- /dev/null +++ b/music-player/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import player \ No newline at end of file diff --git a/music-player/models/__pycache__/__init__.cpython-38.pyc b/music-player/models/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..02ffdcc Binary files /dev/null and b/music-player/models/__pycache__/__init__.cpython-38.pyc differ diff --git a/music-player/models/__pycache__/models.cpython-38.pyc b/music-player/models/__pycache__/models.cpython-38.pyc new file mode 100644 index 0000000..71c065f Binary files /dev/null and b/music-player/models/__pycache__/models.cpython-38.pyc differ diff --git a/music-player/models/__pycache__/player.cpython-38.pyc b/music-player/models/__pycache__/player.cpython-38.pyc new file mode 100644 index 0000000..83b837d Binary files /dev/null and b/music-player/models/__pycache__/player.cpython-38.pyc differ diff --git a/music-player/models/player.py b/music-player/models/player.py new file mode 100644 index 0000000..ad507af --- /dev/null +++ b/music-player/models/player.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api + + +class music_player(models.Model): + _name = 'music_player.music_player' + _description = 'music_player.music_player' + + name = fields.Char() + filename=fields.Char("File name") + url=fields.Char(compute="_compute_url") + + def _compute_url(self): + for record in self: + record.url = record.get_base_url() + '/music/' + str(record.id) + +# value = fields.Integer() +# value2 = fields.Float(compute="_value_pc", store=True) +# description = fields.Text() +# +# @api.depends('value') +# def _value_pc(self): +# for record in self: +# record.value2 = float(record.value) / 100 diff --git a/music-player/security/ir.model.access.csv b/music-player/security/ir.model.access.csv new file mode 100644 index 0000000..393f8ff --- /dev/null +++ b/music-player/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_music_player_music_player,music_player.music_player,model_music_player_music_player,,1,1,1,1 \ No newline at end of file diff --git a/music-player/static/app.js b/music-player/static/app.js new file mode 100644 index 0000000..0edf94f --- /dev/null +++ b/music-player/static/app.js @@ -0,0 +1,193 @@ + +const { Component, xml, mount, useState,useEffect, setup } = owl; +let audio = ''; + + +class SongItems extends Component { + static template = xml` +
+ + +

Playlist

+
+ +
+ + + + +
+
+

+ + +
+
+
+ `; + + removeSongFromList(ev) { + const selectedSongUrl = ev.target.getAttribute('value'); + const selectedIndex = this.props.songitems.findIndex(song => song.url === selectedSongUrl); + if (selectedIndex !== -1) { + this.props.songitems.splice(selectedIndex, 1); + } + } + + playThisSong(ev) { + const selectedSongUrl = ev.target.getAttribute('value'); + const selectedSong = this.props.songitems.find(song => song.url === selectedSongUrl); + document.getElementById('song-title').textContent = selectedSong.name; + audio = new Audio(selectedSongUrl); + audio.pause(); + audio.currentTime = 0; + audio.play(); + } + + static props = ['songitems'] +} + +class PlayList extends Component { + static template = xml` +
+ +
+ `; + + static props = ['playlist']; + static components = { SongItems }; +} + +class Player extends Component { + static template = xml` +
+

Song Title

+
+ + + +
+
`; + playThisSong() { + if (!audio) { + return; + } + audio.play(); + } + pauseThisSong() { + if (!audio) { + return; + } + audio.pause(); + } + stopThisSong() { + if (!audio) { + return; + } + audio.pause(); + audio.currentTime = 0; + } + } + + +class MusicList extends Component { + static template = xml` +
+ + + +

Song List

+
+ + +

+ + +
+
+
+ +
+
+ `; + + + addSongToPlaylist(ev) { + let musicInfo = this.props.musicdata[0]; + console.log(musicInfo) + const selectedSongUrl = ev.target.getAttribute('value'); + const selectedSong = musicInfo.find(song => song.url === selectedSongUrl); + this.props.updateAddToPlayList(selectedSong); + } + + playSong(ev) { + let musicInfo = this.props.musicdata[0]; + const selectedSongUrl = ev.target.getAttribute('value'); + const selectedSong = musicInfo.find(song => song.url === selectedSongUrl); + document.getElementById('song-title').textContent = selectedSong.name; + audio = new Audio(selectedSongUrl); + audio.play(); + } + + static props = ['musicdata', 'updateAddToPlayList']; + + static components = { Player }; + } + +class Search extends Component { + static template = xml` +
+
+ + +
+ +
+ `; + setup() { + this.searchData = useState([]); + + } + + async getMusic() { + const findSong = document.getElementById('searchSong').value; + const response = await fetch(`/music/search?song_name=${findSong}`); + const {result : newData}= await response.json(); + this.searchData.pop(); + this.searchData.push(newData); + } + + + + static components = { MusicList } + + static props = ['updateAddToPlayList']; +} + +class Root extends Component { + + static template = xml ` + +
+
+ +
+ + +
`; + + setup(){ + this.addToPlaylist = useState([]); + } + + updateAddToPlayList = (newData) => { + this.addToPlaylist.push(newData); + } + + static components = { Search, PlayList }; +} + +window.onload = function() { + mount(Root, document.body); +}; + diff --git a/music-player/static/owl.js b/music-player/static/owl.js new file mode 100644 index 0000000..b07d8c8 --- /dev/null +++ b/music-player/static/owl.js @@ -0,0 +1,5938 @@ +(function (exports) { + 'use strict'; + + function filterOutModifiersFromData(dataList) { + dataList = dataList.slice(); + const modifiers = []; + let elm; + while ((elm = dataList[0]) && typeof elm === "string") { + modifiers.push(dataList.shift()); + } + return { modifiers, data: dataList }; + } + const config = { + // whether or not blockdom should normalize DOM whenever a block is created. + // Normalizing dom mean removing empty text nodes (or containing only spaces) + shouldNormalizeDom: true, + // this is the main event handler. Every event handler registered with blockdom + // will go through this function, giving it the data registered in the block + // and the event + mainEventHandler: (data, ev, currentTarget) => { + if (typeof data === "function") { + data(ev); + } + else if (Array.isArray(data)) { + data = filterOutModifiersFromData(data).data; + data[0](data[1], ev); + } + return false; + }, + }; + + // ----------------------------------------------------------------------------- + // Toggler node + // ----------------------------------------------------------------------------- + class VToggler { + constructor(key, child) { + this.key = key; + this.child = child; + } + mount(parent, afterNode) { + this.parentEl = parent; + this.child.mount(parent, afterNode); + } + moveBeforeDOMNode(node, parent) { + this.child.moveBeforeDOMNode(node, parent); + } + moveBeforeVNode(other, afterNode) { + this.moveBeforeDOMNode((other && other.firstNode()) || afterNode); + } + patch(other, withBeforeRemove) { + if (this === other) { + return; + } + let child1 = this.child; + let child2 = other.child; + if (this.key === other.key) { + child1.patch(child2, withBeforeRemove); + } + else { + child2.mount(this.parentEl, child1.firstNode()); + if (withBeforeRemove) { + child1.beforeRemove(); + } + child1.remove(); + this.child = child2; + this.key = other.key; + } + } + beforeRemove() { + this.child.beforeRemove(); + } + remove() { + this.child.remove(); + } + firstNode() { + return this.child.firstNode(); + } + toString() { + return this.child.toString(); + } + } + function toggler(key, child) { + return new VToggler(key, child); + } + + // Custom error class that wraps error that happen in the owl lifecycle + class OwlError extends Error { + } + // Maps fibers to thrown errors + const fibersInError = new WeakMap(); + const nodeErrorHandlers = new WeakMap(); + function _handleError(node, error) { + if (!node) { + return false; + } + const fiber = node.fiber; + if (fiber) { + fibersInError.set(fiber, error); + } + const errorHandlers = nodeErrorHandlers.get(node); + if (errorHandlers) { + let handled = false; + // execute in the opposite order + for (let i = errorHandlers.length - 1; i >= 0; i--) { + try { + errorHandlers[i](error); + handled = true; + break; + } + catch (e) { + error = e; + } + } + if (handled) { + return true; + } + } + return _handleError(node.parent, error); + } + function handleError(params) { + let { error } = params; + // Wrap error if it wasn't wrapped by wrapError (ie when not in dev mode) + if (!(error instanceof OwlError)) { + error = Object.assign(new OwlError(`An error occured in the owl lifecycle (see this Error's "cause" property)`), { cause: error }); + } + const node = "node" in params ? params.node : params.fiber.node; + const fiber = "fiber" in params ? params.fiber : node.fiber; + // resets the fibers on components if possible. This is important so that + // new renderings can be properly included in the initial one, if any. + let current = fiber; + do { + current.node.fiber = current; + current = current.parent; + } while (current); + fibersInError.set(fiber.root, error); + const handled = _handleError(node, error); + if (!handled) { + console.warn(`[Owl] Unhandled error. Destroying the root component`); + try { + node.app.destroy(); + } + catch (e) { + console.error(e); + } + throw error; + } + } + + const { setAttribute: elemSetAttribute, removeAttribute } = Element.prototype; + const tokenList = DOMTokenList.prototype; + const tokenListAdd = tokenList.add; + const tokenListRemove = tokenList.remove; + const isArray = Array.isArray; + const { split, trim } = String.prototype; + const wordRegexp = /\s+/; + /** + * We regroup here all code related to updating attributes in a very loose sense: + * attributes, properties and classs are all managed by the functions in this + * file. + */ + function setAttribute(key, value) { + switch (value) { + case false: + case undefined: + removeAttribute.call(this, key); + break; + case true: + elemSetAttribute.call(this, key, ""); + break; + default: + elemSetAttribute.call(this, key, value); + } + } + function createAttrUpdater(attr) { + return function (value) { + setAttribute.call(this, attr, value); + }; + } + function attrsSetter(attrs) { + if (isArray(attrs)) { + setAttribute.call(this, attrs[0], attrs[1]); + } + else { + for (let k in attrs) { + setAttribute.call(this, k, attrs[k]); + } + } + } + function attrsUpdater(attrs, oldAttrs) { + if (isArray(attrs)) { + const name = attrs[0]; + const val = attrs[1]; + if (name === oldAttrs[0]) { + if (val === oldAttrs[1]) { + return; + } + setAttribute.call(this, name, val); + } + else { + removeAttribute.call(this, oldAttrs[0]); + setAttribute.call(this, name, val); + } + } + else { + for (let k in oldAttrs) { + if (!(k in attrs)) { + removeAttribute.call(this, k); + } + } + for (let k in attrs) { + const val = attrs[k]; + if (val !== oldAttrs[k]) { + setAttribute.call(this, k, val); + } + } + } + } + function toClassObj(expr) { + const result = {}; + switch (typeof expr) { + case "string": + // we transform here a list of classes into an object: + // 'hey you' becomes {hey: true, you: true} + const str = trim.call(expr); + if (!str) { + return {}; + } + let words = split.call(str, wordRegexp); + for (let i = 0, l = words.length; i < l; i++) { + result[words[i]] = true; + } + return result; + case "object": + // this is already an object but we may need to split keys: + // {'a': true, 'b c': true} should become {a: true, b: true, c: true} + for (let key in expr) { + const value = expr[key]; + if (value) { + key = trim.call(key); + if (!key) { + continue; + } + const words = split.call(key, wordRegexp); + for (let word of words) { + result[word] = value; + } + } + } + return result; + case "undefined": + return {}; + case "number": + return { [expr]: true }; + default: + return { [expr]: true }; + } + } + function setClass(val) { + val = val === "" ? {} : toClassObj(val); + // add classes + const cl = this.classList; + for (let c in val) { + tokenListAdd.call(cl, c); + } + } + function updateClass(val, oldVal) { + oldVal = oldVal === "" ? {} : toClassObj(oldVal); + val = val === "" ? {} : toClassObj(val); + const cl = this.classList; + // remove classes + for (let c in oldVal) { + if (!(c in val)) { + tokenListRemove.call(cl, c); + } + } + // add classes + for (let c in val) { + if (!(c in oldVal)) { + tokenListAdd.call(cl, c); + } + } + } + + function createEventHandler(rawEvent) { + const eventName = rawEvent.split(".")[0]; + const capture = rawEvent.includes(".capture"); + if (rawEvent.includes(".synthetic")) { + return createSyntheticHandler(eventName, capture); + } + else { + return createElementHandler(eventName, capture); + } + } + // Native listener + let nextNativeEventId = 1; + function createElementHandler(evName, capture = false) { + let eventKey = `__event__${evName}_${nextNativeEventId++}`; + if (capture) { + eventKey = `${eventKey}_capture`; + } + function listener(ev) { + const currentTarget = ev.currentTarget; + if (!currentTarget || !currentTarget.ownerDocument.contains(currentTarget)) + return; + const data = currentTarget[eventKey]; + if (!data) + return; + config.mainEventHandler(data, ev, currentTarget); + } + function setup(data) { + this[eventKey] = data; + this.addEventListener(evName, listener, { capture }); + } + function remove() { + delete this[eventKey]; + this.removeEventListener(evName, listener, { capture }); + } + function update(data) { + this[eventKey] = data; + } + return { setup, update, remove }; + } + // Synthetic handler: a form of event delegation that allows placing only one + // listener per event type. + let nextSyntheticEventId = 1; + function createSyntheticHandler(evName, capture = false) { + let eventKey = `__event__synthetic_${evName}`; + if (capture) { + eventKey = `${eventKey}_capture`; + } + setupSyntheticEvent(evName, eventKey, capture); + const currentId = nextSyntheticEventId++; + function setup(data) { + const _data = this[eventKey] || {}; + _data[currentId] = data; + this[eventKey] = _data; + } + function remove() { + delete this[eventKey]; + } + return { setup, update: setup, remove }; + } + function nativeToSyntheticEvent(eventKey, event) { + let dom = event.target; + while (dom !== null) { + const _data = dom[eventKey]; + if (_data) { + for (const data of Object.values(_data)) { + const stopped = config.mainEventHandler(data, event, dom); + if (stopped) + return; + } + } + dom = dom.parentNode; + } + } + const CONFIGURED_SYNTHETIC_EVENTS = {}; + function setupSyntheticEvent(evName, eventKey, capture = false) { + if (CONFIGURED_SYNTHETIC_EVENTS[eventKey]) { + return; + } + document.addEventListener(evName, (event) => nativeToSyntheticEvent(eventKey, event), { + capture, + }); + CONFIGURED_SYNTHETIC_EVENTS[eventKey] = true; + } + + const getDescriptor$3 = (o, p) => Object.getOwnPropertyDescriptor(o, p); + const nodeProto$4 = Node.prototype; + const nodeInsertBefore$3 = nodeProto$4.insertBefore; + const nodeSetTextContent$1 = getDescriptor$3(nodeProto$4, "textContent").set; + const nodeRemoveChild$3 = nodeProto$4.removeChild; + // ----------------------------------------------------------------------------- + // Multi NODE + // ----------------------------------------------------------------------------- + class VMulti { + constructor(children) { + this.children = children; + } + mount(parent, afterNode) { + const children = this.children; + const l = children.length; + const anchors = new Array(l); + for (let i = 0; i < l; i++) { + let child = children[i]; + if (child) { + child.mount(parent, afterNode); + } + else { + const childAnchor = document.createTextNode(""); + anchors[i] = childAnchor; + nodeInsertBefore$3.call(parent, childAnchor, afterNode); + } + } + this.anchors = anchors; + this.parentEl = parent; + } + moveBeforeDOMNode(node, parent = this.parentEl) { + this.parentEl = parent; + const children = this.children; + const anchors = this.anchors; + for (let i = 0, l = children.length; i < l; i++) { + let child = children[i]; + if (child) { + child.moveBeforeDOMNode(node, parent); + } + else { + const anchor = anchors[i]; + nodeInsertBefore$3.call(parent, anchor, node); + } + } + } + moveBeforeVNode(other, afterNode) { + if (other) { + const next = other.children[0]; + afterNode = (next ? next.firstNode() : other.anchors[0]) || null; + } + const children = this.children; + const parent = this.parentEl; + const anchors = this.anchors; + for (let i = 0, l = children.length; i < l; i++) { + let child = children[i]; + if (child) { + child.moveBeforeVNode(null, afterNode); + } + else { + const anchor = anchors[i]; + nodeInsertBefore$3.call(parent, anchor, afterNode); + } + } + } + patch(other, withBeforeRemove) { + if (this === other) { + return; + } + const children1 = this.children; + const children2 = other.children; + const anchors = this.anchors; + const parentEl = this.parentEl; + for (let i = 0, l = children1.length; i < l; i++) { + const vn1 = children1[i]; + const vn2 = children2[i]; + if (vn1) { + if (vn2) { + vn1.patch(vn2, withBeforeRemove); + } + else { + const afterNode = vn1.firstNode(); + const anchor = document.createTextNode(""); + anchors[i] = anchor; + nodeInsertBefore$3.call(parentEl, anchor, afterNode); + if (withBeforeRemove) { + vn1.beforeRemove(); + } + vn1.remove(); + children1[i] = undefined; + } + } + else if (vn2) { + children1[i] = vn2; + const anchor = anchors[i]; + vn2.mount(parentEl, anchor); + nodeRemoveChild$3.call(parentEl, anchor); + } + } + } + beforeRemove() { + const children = this.children; + for (let i = 0, l = children.length; i < l; i++) { + const child = children[i]; + if (child) { + child.beforeRemove(); + } + } + } + remove() { + const parentEl = this.parentEl; + if (this.isOnlyChild) { + nodeSetTextContent$1.call(parentEl, ""); + } + else { + const children = this.children; + const anchors = this.anchors; + for (let i = 0, l = children.length; i < l; i++) { + const child = children[i]; + if (child) { + child.remove(); + } + else { + nodeRemoveChild$3.call(parentEl, anchors[i]); + } + } + } + } + firstNode() { + const child = this.children[0]; + return child ? child.firstNode() : this.anchors[0]; + } + toString() { + return this.children.map((c) => (c ? c.toString() : "")).join(""); + } + } + function multi(children) { + return new VMulti(children); + } + + const getDescriptor$2 = (o, p) => Object.getOwnPropertyDescriptor(o, p); + const nodeProto$3 = Node.prototype; + const characterDataProto$1 = CharacterData.prototype; + const nodeInsertBefore$2 = nodeProto$3.insertBefore; + const characterDataSetData$1 = getDescriptor$2(characterDataProto$1, "data").set; + const nodeRemoveChild$2 = nodeProto$3.removeChild; + class VSimpleNode { + constructor(text) { + this.text = text; + } + mountNode(node, parent, afterNode) { + this.parentEl = parent; + nodeInsertBefore$2.call(parent, node, afterNode); + this.el = node; + } + moveBeforeDOMNode(node, parent = this.parentEl) { + this.parentEl = parent; + nodeInsertBefore$2.call(parent, this.el, node); + } + moveBeforeVNode(other, afterNode) { + nodeInsertBefore$2.call(this.parentEl, this.el, other ? other.el : afterNode); + } + beforeRemove() { } + remove() { + nodeRemoveChild$2.call(this.parentEl, this.el); + } + firstNode() { + return this.el; + } + toString() { + return this.text; + } + } + class VText$1 extends VSimpleNode { + mount(parent, afterNode) { + this.mountNode(document.createTextNode(toText(this.text)), parent, afterNode); + } + patch(other) { + const text2 = other.text; + if (this.text !== text2) { + characterDataSetData$1.call(this.el, toText(text2)); + this.text = text2; + } + } + } + class VComment extends VSimpleNode { + mount(parent, afterNode) { + this.mountNode(document.createComment(toText(this.text)), parent, afterNode); + } + patch() { } + } + function text(str) { + return new VText$1(str); + } + function comment(str) { + return new VComment(str); + } + function toText(value) { + switch (typeof value) { + case "string": + return value; + case "number": + return String(value); + case "boolean": + return value ? "true" : "false"; + default: + return value || ""; + } + } + + const getDescriptor$1 = (o, p) => Object.getOwnPropertyDescriptor(o, p); + const nodeProto$2 = Node.prototype; + const elementProto = Element.prototype; + const characterDataProto = CharacterData.prototype; + const characterDataSetData = getDescriptor$1(characterDataProto, "data").set; + const nodeGetFirstChild = getDescriptor$1(nodeProto$2, "firstChild").get; + const nodeGetNextSibling = getDescriptor$1(nodeProto$2, "nextSibling").get; + const NO_OP = () => { }; + function makePropSetter(name) { + return function setProp(value) { + // support 0, fallback to empty string for other falsy values + this[name] = value === 0 ? 0 : value ? value.valueOf() : ""; + }; + } + const cache$1 = {}; + /** + * Compiling blocks is a multi-step process: + * + * 1. build an IntermediateTree from the HTML element. This intermediate tree + * is a binary tree structure that encode dynamic info sub nodes, and the + * path required to reach them + * 2. process the tree to build a block context, which is an object that aggregate + * all dynamic info in a list, and also, all ref indexes. + * 3. process the context to build appropriate builder/setter functions + * 4. make a dynamic block class, which will efficiently collect references and + * create/update dynamic locations/children + * + * @param str + * @returns a new block type, that can build concrete blocks + */ + function createBlock(str) { + if (str in cache$1) { + return cache$1[str]; + } + // step 0: prepare html base element + const doc = new DOMParser().parseFromString(`${str}`, "text/xml"); + const node = doc.firstChild.firstChild; + if (config.shouldNormalizeDom) { + normalizeNode(node); + } + // step 1: prepare intermediate tree + const tree = buildTree(node); + // step 2: prepare block context + const context = buildContext(tree); + // step 3: build the final block class + const template = tree.el; + const Block = buildBlock(template, context); + cache$1[str] = Block; + return Block; + } + // ----------------------------------------------------------------------------- + // Helper + // ----------------------------------------------------------------------------- + function normalizeNode(node) { + if (node.nodeType === Node.TEXT_NODE) { + if (!/\S/.test(node.textContent)) { + node.remove(); + return; + } + } + if (node.nodeType === Node.ELEMENT_NODE) { + if (node.tagName === "pre") { + return; + } + } + for (let i = node.childNodes.length - 1; i >= 0; --i) { + normalizeNode(node.childNodes.item(i)); + } + } + function buildTree(node, parent = null, domParentTree = null) { + switch (node.nodeType) { + case Node.ELEMENT_NODE: { + // HTMLElement + let currentNS = domParentTree && domParentTree.currentNS; + const tagName = node.tagName; + let el = undefined; + const info = []; + if (tagName.startsWith("block-text-")) { + const index = parseInt(tagName.slice(11), 10); + info.push({ type: "text", idx: index }); + el = document.createTextNode(""); + } + if (tagName.startsWith("block-child-")) { + if (!domParentTree.isRef) { + addRef(domParentTree); + } + const index = parseInt(tagName.slice(12), 10); + info.push({ type: "child", idx: index }); + el = document.createTextNode(""); + } + const attrs = node.attributes; + const ns = attrs.getNamedItem("block-ns"); + if (ns) { + attrs.removeNamedItem("block-ns"); + currentNS = ns.value; + } + if (!el) { + el = currentNS + ? document.createElementNS(currentNS, tagName) + : document.createElement(tagName); + } + if (el instanceof Element) { + if (!domParentTree) { + // some html elements may have side effects when setting their attributes. + // For example, setting the src attribute of an will trigger a + // request to get the corresponding image. This is something that we + // don't want at compile time. We avoid that by putting the content of + // the block in a