mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-11-04 05:10:07 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			456 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			456 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**********************************************************************
 | 
						|
* 
 | 
						|
*
 | 
						|
*
 | 
						|
**********************************************************************/
 | 
						|
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
 | 
						|
(function(require){ var module={} // make module AMD/node compatible...
 | 
						|
/*********************************************************************/
 | 
						|
 | 
						|
var actions = require('lib/actions')
 | 
						|
var features = require('lib/features')
 | 
						|
 | 
						|
var core = require('features/core')
 | 
						|
var browse = require('lib/widget/browse')
 | 
						|
 | 
						|
var widgets = require('features/ui-widgets')
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/*********************************************************************/
 | 
						|
//
 | 
						|
//
 | 
						|
// XXX should these be sortable and how???
 | 
						|
// 		...relative placement (i.e. "before <GID>")???
 | 
						|
// 		.....might be a good idea to add option to "pair" images as a 
 | 
						|
// 		generic sort thing -- is this grouping???
 | 
						|
// XXX should the export mechanism be extensible???
 | 
						|
// XXX add undo...
 | 
						|
//
 | 
						|
// 
 | 
						|
var VirtualBlocksActions = actions.Actions({
 | 
						|
 | 
						|
	// XXX do we need to pre-cache this???
 | 
						|
	get virtual(){
 | 
						|
		var that = this
 | 
						|
		return this.data.order
 | 
						|
			.filter(function(gid){ 
 | 
						|
				var img = that.images[gid] || {}
 | 
						|
				return img.type == 'virtual' }) },
 | 
						|
 | 
						|
	// construction of new "virtual images"...
 | 
						|
	//
 | 
						|
	// XXX add undo...
 | 
						|
	// XXX do better arg processing -- handle data correctly...
 | 
						|
	makeVirtualBlock: ['Virtual block/Add blank $after',
 | 
						|
		core.doc`Add virtual block...
 | 
						|
 | 
						|
				Add virtual block after current image...
 | 
						|
				.makeVirtualBlock()
 | 
						|
				.makeVirtualBlock('current')
 | 
						|
				.makeVirtualBlock('current', 'after')
 | 
						|
					-> this
 | 
						|
 | 
						|
				Add virtual block before current image...
 | 
						|
				.makeVirtualBlock('current')
 | 
						|
				.makeVirtualBlock('current', 'after')
 | 
						|
 | 
						|
				Add virtual block at offset relative to reference image,
 | 
						|
				optionally containing data...
 | 
						|
				.makeVirtualBlock(reference, offset)
 | 
						|
				.makeVirtualBlock(reference, offset, data)
 | 
						|
					-> this
 | 
						|
 | 
						|
			NOTE: reference if not given defaults to 'current'
 | 
						|
			NOTE: data if not given will default to:
 | 
						|
				{
 | 
						|
					type: 'virtual',
 | 
						|
					path: null,
 | 
						|
				}
 | 
						|
 | 
						|
 | 
						|
			Virtual Block Format (Image):
 | 
						|
				{
 | 
						|
					// block type...
 | 
						|
					type: 'virtual',
 | 
						|
 | 
						|
					// Block name (optional)...
 | 
						|
					//
 | 
						|
					// NOTE: this is a standard Image attribute used to generate 
 | 
						|
					//		exported image filename...
 | 
						|
					// NOTE: if it is required to change exported file extension 
 | 
						|
					//		from '.txt' add the extension to the name...
 | 
						|
					//			Example:
 | 
						|
					//				name: 'virtual-image.tex'
 | 
						|
					name: <String>,
 | 
						|
 | 
						|
					// Block text (optional)...
 | 
						|
					text: <String>,
 | 
						|
 | 
						|
					// export constructor action...
 | 
						|
					//
 | 
						|
					// XXX not implemented yet...
 | 
						|
					//export: <action>,
 | 
						|
 | 
						|
					// optional image attributes...
 | 
						|
					// for more info see: imagegrid/images.js
 | 
						|
					...
 | 
						|
				}
 | 
						|
			`,
 | 
						|
		{ mode: function(){ return !this.collection && 'disabled' }, },
 | 
						|
		function(ref, offset, img){
 | 
						|
			ref = ref || 'current'
 | 
						|
			offset = offset || 'after'	
 | 
						|
			offset = offset == 'after' ? 
 | 
						|
					1 
 | 
						|
				: offset == 'before' ? 
 | 
						|
					0
 | 
						|
				: typeof(offset) == typeof(123) ?
 | 
						|
					offset
 | 
						|
				: 0
 | 
						|
			// XXX revise...
 | 
						|
			img = arguments[arguments.length-1] instanceof Object ?
 | 
						|
				arguments[arguments.length-1]
 | 
						|
				: undefined
 | 
						|
			img = img === null ? 
 | 
						|
				img 
 | 
						|
				: Object.assign({
 | 
						|
						type: 'virtual', 
 | 
						|
						path: null,
 | 
						|
					},
 | 
						|
					img || {})
 | 
						|
 | 
						|
			var data = this.data
 | 
						|
 | 
						|
			ref = data.getImage(ref)
 | 
						|
			var r = data.getRibbon(ref)
 | 
						|
 | 
						|
			var gid = data.newGID()
 | 
						|
 | 
						|
			// place image into data...
 | 
						|
			var order = data.order
 | 
						|
			order.splice(order.indexOf(ref)+offset,0, gid)
 | 
						|
			var ribbon = data.ribbons[r]
 | 
						|
			ribbon.splice(ribbon.indexOf(ref)+offset,0, gid)
 | 
						|
 | 
						|
			// update data...
 | 
						|
			data.updateImagePositions()
 | 
						|
 | 
						|
			// update data...
 | 
						|
			img
 | 
						|
				&& (this.images[gid] = img)
 | 
						|
			this.markChanged
 | 
						|
				&& this
 | 
						|
					.markChanged('data')
 | 
						|
					.markChanged('images', [gid])
 | 
						|
 | 
						|
			// focus new image...
 | 
						|
			// NOTE: this should update the view too...
 | 
						|
			this.focusImage(gid)
 | 
						|
		}],
 | 
						|
	makeVirtualBlockBefore: ['Virtual block/Add blank $before',
 | 
						|
		core.doc`Add block before...
 | 
						|
		
 | 
						|
			This is the same as .makeVirtualBlock(.., 'before', ..)`,
 | 
						|
		{ mode: 'makeVirtualBlock', },
 | 
						|
		'makeVirtualBlock: $0 "before" ...'],
 | 
						|
 | 
						|
	cloneVirtualBlock: ['Virtual block/$Clone block',
 | 
						|
		{ mode: function(){ 
 | 
						|
			return (this.image || {}).type != 'virtual' && 'disabled' }, },
 | 
						|
		function(ref, offset, img){
 | 
						|
			var img = Object.assign({}, 
 | 
						|
				this.images[this.data.getImage(ref)] || {}, 
 | 
						|
				img || {})
 | 
						|
			delete img.gid
 | 
						|
			this.makeVirtualBlock(ref, offset, img) }],
 | 
						|
 | 
						|
	// crop...
 | 
						|
	// XXX would be nice to avoid these and just register a list and context...
 | 
						|
	cropVirtualBlocks: ['Virtual block|Crop/$Crop $virtual $blocks',
 | 
						|
		{ mode: 'makeVirtualBlock' },
 | 
						|
		'crop: "virtual" ...'],
 | 
						|
	cropVirtualBlocksOut: ['Virtual block|Crop/Crop virtual blocks out',
 | 
						|
		{ mode: 'cropVirtualBlocks' },
 | 
						|
		'crop: "!virtual" ...'],
 | 
						|
 | 
						|
	// marks...
 | 
						|
	toggleMarkVirtualBlocks: ['Virtual block|Mark/Toggle $mark on $virtual blocks',
 | 
						|
		{ mode: 'makeVirtualBlock' },
 | 
						|
		'toggleMark: "virtual"'],
 | 
						|
 | 
						|
	// remove...
 | 
						|
	// XXX alias to "remove from crop/collection" ???
 | 
						|
})
 | 
						|
 | 
						|
var VirtualBlocks = 
 | 
						|
module.VirtualBlocks = core.ImageGridFeatures.Feature({
 | 
						|
	title: '',
 | 
						|
	doc: '',
 | 
						|
 | 
						|
	tag: 'virtual-blocks',
 | 
						|
	depends: [
 | 
						|
		'edit',
 | 
						|
	],
 | 
						|
	suggested: [
 | 
						|
		'ui-virtual-blocks',
 | 
						|
		'ui-virtual-blocks-edit',
 | 
						|
	],
 | 
						|
 | 
						|
	actions: VirtualBlocksActions, 
 | 
						|
 | 
						|
})
 | 
						|
 | 
						|
 | 
						|
 | 
						|
//---------------------------------------------------------------------
 | 
						|
 | 
						|
var VirtualBlocksUIActions = actions.Actions({
 | 
						|
	config: {
 | 
						|
		// Threshold text length at which 
 | 
						|
		'virtual-text-fit-area-threshold': 100,
 | 
						|
 | 
						|
		// Editable virtual block fields...
 | 
						|
		//
 | 
						|
		// XXX if we need to add types try and re-use existing editor 
 | 
						|
		// 		constructors in features/ui-widgets.js....
 | 
						|
		'virtual-text-fields': {
 | 
						|
			'Tit$le': 'name',
 | 
						|
			'Te$xt': 'text',
 | 
						|
		},
 | 
						|
	},
 | 
						|
 | 
						|
	__virtual_block_processors__: {
 | 
						|
		// Text handler is designed to process plain or lightly 
 | 
						|
		// formatted text.
 | 
						|
		//
 | 
						|
		// This will:
 | 
						|
		// 	- replace '\n' with '<br>'
 | 
						|
		// 	- adjust font size to fit the image block
 | 
						|
		//
 | 
						|
		// NOTE: the processor is allowed to only modify image block 
 | 
						|
		// 		content, anything else would require cleanup...
 | 
						|
		//
 | 
						|
		// XXX might be a good idea to add action param to enable 
 | 
						|
		// 		handlers to do things like 'setup', 'cleanup', ...	
 | 
						|
		text: function(image, dom){
 | 
						|
			if(!image.text){
 | 
						|
				return dom }
 | 
						|
 | 
						|
			// threshold after which we try to fill the volume...
 | 
						|
			var C = this.config['virtual-text-fit-area-threshold'] || 100
 | 
						|
 | 
						|
			// construct a basic text element...
 | 
						|
			var text = document.createElement('div')
 | 
						|
			text.innerHTML = image.text
 | 
						|
				.replace(/\n/g, '<br>\n')
 | 
						|
			dom[0].innerHTML = ''
 | 
						|
			dom[0].appendChild(text)
 | 
						|
 | 
						|
			// scale the text if it is small...
 | 
						|
			var R = dom[0].offsetHeight * 0.8
 | 
						|
			var r = image.text.length > C ?
 | 
						|
				Math.max(
 | 
						|
					text.offsetWidth, 
 | 
						|
					text.offsetHeight, 
 | 
						|
					// keep large text blocks roughly square-ish...
 | 
						|
					Math.sqrt(text.scrollHeight * text.scrollWidth))
 | 
						|
				: Math.max(
 | 
						|
					text.offsetWidth, 
 | 
						|
					text.offsetHeight, 
 | 
						|
					text.scrollHeight,
 | 
						|
					text.scrollWidth)
 | 
						|
			var s = R/r
 | 
						|
			text.style.fontSize = `${ 100*s }%`
 | 
						|
 | 
						|
			// prioritize width... 
 | 
						|
			text.style.width = '95%'
 | 
						|
 | 
						|
			// justify larger blocks...
 | 
						|
			image.text.length > C
 | 
						|
				&& (text.style.textAlign = 'justify')
 | 
						|
 | 
						|
			return dom
 | 
						|
		},
 | 
						|
 | 
						|
		/* XXX
 | 
						|
		html: function(image, dom){},
 | 
						|
		svg: function(image, dom){},
 | 
						|
		markdown: function(image, dom){},
 | 
						|
		pwiki: function(image, dom){},
 | 
						|
		//*/
 | 
						|
	},
 | 
						|
	// XXX add format auto-detection -- first line announce (a-la pWiki)...
 | 
						|
	updateVirtualBlock: ['- Virtual block/',
 | 
						|
		function(gid, dom, image){
 | 
						|
			image = image || this.images[gid] || {}
 | 
						|
 | 
						|
			if(image.type != 'virtual'){
 | 
						|
				return actions.UNDEFINED }
 | 
						|
 | 
						|
			var p = (this.__virtual_block_processors__ 
 | 
						|
				|| VirtualBlocksUIActions.__virtual_block_processors__
 | 
						|
				|| {})
 | 
						|
			p = p[image.format] || p['text']
 | 
						|
			return p instanceof Function ?
 | 
						|
				p.call(this, image, dom) 
 | 
						|
				: dom }],
 | 
						|
 | 
						|
	metadataSection: [
 | 
						|
		{ sortedActionPriority: 80 },
 | 
						|
		function(make, gid, image){
 | 
						|
			var that = this
 | 
						|
			if(!image || image.type != 'virtual'){
 | 
						|
				return }
 | 
						|
 | 
						|
			make.Separator()
 | 
						|
			this.editVirtualBlock ?
 | 
						|
				// editable... 
 | 
						|
				this.editVirtualBlock(make, gid, image)
 | 
						|
				// view only...
 | 
						|
				: Object.entries(this.config['virtual-text-fields'] || {})
 | 
						|
					.forEach(function([title, attr]){
 | 
						|
						make([title +':', image[attr]]) })
 | 
						|
		}],
 | 
						|
})
 | 
						|
 | 
						|
// NOTE: this is independent of 'virtual-blocks'...
 | 
						|
var VirtualBlocksUI = 
 | 
						|
module.VirtualBlocksUI = core.ImageGridFeatures.Feature({
 | 
						|
	title: '',
 | 
						|
	doc: '',
 | 
						|
 | 
						|
	tag: 'ui-virtual-blocks',
 | 
						|
	depends: [
 | 
						|
		'ui',
 | 
						|
	],
 | 
						|
	suggested: [
 | 
						|
		'virtual-blocks',
 | 
						|
		'ui-virtual-blocks-edit',
 | 
						|
	],
 | 
						|
 | 
						|
	actions: VirtualBlocksUIActions, 
 | 
						|
 | 
						|
	handlers: [
 | 
						|
		['updateImage',
 | 
						|
			function(_, gid, dom){
 | 
						|
				var image = this.images[gid] || {}
 | 
						|
 | 
						|
				// set image content...
 | 
						|
				if(image.type == 'virtual' && image.text){
 | 
						|
					this.updateVirtualBlock(gid, dom, image)
 | 
						|
 | 
						|
				// clear reused image content...
 | 
						|
				} else if(dom[0].innerHTML != ''){
 | 
						|
					dom[0].innerHTML = ''
 | 
						|
				}
 | 
						|
			}],
 | 
						|
	],
 | 
						|
})
 | 
						|
 | 
						|
 | 
						|
 | 
						|
//---------------------------------------------------------------------
 | 
						|
 | 
						|
var VirtualBlocksEditUIActions = actions.Actions({
 | 
						|
	// XXX this is a good candidate for inlineing (browse2)
 | 
						|
	// XXX should we also add a preview (preview constructor from features/metadata.js)???
 | 
						|
	// XXX should we do a sanity check for image type???
 | 
						|
	editVirtualBlock: ['Virtual block/$Edit...',
 | 
						|
		{ mode: 'cloneVirtualBlock' },
 | 
						|
		widgets.makeUIDialog(function(gid){
 | 
						|
			var that = this
 | 
						|
 | 
						|
			var _make = function(make, gid, image){
 | 
						|
				var Editable = function(title, attr){
 | 
						|
					make.Editable([title +':', image[attr] || ''], {
 | 
						|
						start_on: 'open',
 | 
						|
						edit_text: 'last',
 | 
						|
						multiline: true,
 | 
						|
						reset_on_commit: false,
 | 
						|
						editdone: function(evt, value){
 | 
						|
							if(value == ''){
 | 
						|
								delete image[attr]
 | 
						|
							} else {
 | 
						|
								image[attr] = value 
 | 
						|
							}
 | 
						|
 | 
						|
							// mark image as changed...
 | 
						|
							that.markChanged 
 | 
						|
								&& that.markChanged('images', [gid])
 | 
						|
							// refresh views...
 | 
						|
							make.dialog.updatePreview
 | 
						|
								&& make.dialog.updatePreview()
 | 
						|
							that.refresh(gid)
 | 
						|
						}, }) }
 | 
						|
				// build the list...
 | 
						|
				Object.entries(that.config['virtual-text-fields'] || {})
 | 
						|
					.forEach(function([title, attr]){
 | 
						|
						Editable(title, attr) }) }
 | 
						|
 | 
						|
			// XXX should this be a more specific test???
 | 
						|
			return arguments[0] instanceof Function?
 | 
						|
				// inline...
 | 
						|
				_make.call(this, ...arguments)
 | 
						|
				// dialog...
 | 
						|
				: browse.makeLister(null, 
 | 
						|
					function(p, make){
 | 
						|
						var gid = gid || that.current
 | 
						|
						var data = data || that.images[gid]
 | 
						|
 | 
						|
						_make(make, gid, data) 
 | 
						|
					}, {
 | 
						|
						cls: 'table-view',
 | 
						|
					})
 | 
						|
					.close(function(){
 | 
						|
						that.refresh(gid) }) })],
 | 
						|
 | 
						|
	// virtual block templates...
 | 
						|
	//
 | 
						|
	// XXX implementation vs. UI
 | 
						|
	// 		implementation:
 | 
						|
	// 			- this looks like a special collection with local .images
 | 
						|
	// 				- hidden?
 | 
						|
	// 				- can this be global or shared between indexes?
 | 
						|
	// 			- this can be just a list in .config or .data (global/local)
 | 
						|
	// 		UI:
 | 
						|
	// 			- this can be loaded and/or edited as a collection
 | 
						|
	// 			- this can be a dialog...
 | 
						|
	cloneVirtualBlockFromTemplate: ['- Virtual block/Clone from template...',
 | 
						|
		function(){ 
 | 
						|
		}],
 | 
						|
	saveVirtualBlockAsTemplate: ['- Virtual block/Save as template',
 | 
						|
		function(gid){
 | 
						|
		}],
 | 
						|
 | 
						|
	// XXX list existing non-blank v-blocks...
 | 
						|
	cloneVirtualBlockFrom: ['- Virtual block/Clone...',
 | 
						|
		function(position){ 
 | 
						|
		}],
 | 
						|
 | 
						|
	// XXX add alias to remove...
 | 
						|
})
 | 
						|
 | 
						|
// NOTE: this is independent of 'virtual-blocks'...
 | 
						|
var VirtualBlocksEditUI = 
 | 
						|
module.VirtualBlocksEditUI = core.ImageGridFeatures.Feature({
 | 
						|
	title: '',
 | 
						|
	doc: '',
 | 
						|
 | 
						|
	tag: 'ui-virtual-blocks-edit',
 | 
						|
	depends: [
 | 
						|
		'ui',
 | 
						|
		'ui-virtual-blocks',
 | 
						|
		'virtual-blocks',
 | 
						|
	],
 | 
						|
 | 
						|
	actions: VirtualBlocksEditUIActions, 
 | 
						|
})
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/**********************************************************************
 | 
						|
* vim:set ts=4 sw=4 :                               */ return module })
 |