<commit_before>import { a, div, br, label, input, p, button, code, pre, h2, h4, i, h3, h5 } from '@cycle/dom'
import xs from 'xstream'
import isolate from '@cycle/isolate'
import { mergeWith, merge, mergeAll } from 'ramda'
import { clone } from 'ramda';
import sampleCombine from 'xstream/extra/sampleCombine'

// const settings = {
// }

// const initState = {
// 	settings : {
// 		tableCount : 2,
// 		url : 'http://localhost:8090/jobs?context=luciusapi&appName=luciusapi&appName=luciusapi&sync=true&classPath=com.dataintuitive.luciusapi.',
// 	}
// };

export const initSettings = {
		commmon : {
			version: 'v2',
		},
		headTableSettings : {
			title: 'Top Table 123',
			head : 5,
			color: 'rgb(44,123,182)',
			title: 'Top Table',
			version: 'v2'
		},
		tailTableSettings : {
			title: 'Bottom Table 123',
			tail : 5,
			color: 'rgb(215,25,28)',
			title: 'Bottom Table',
			version: 'v2'
		},
		hist : {
			bins : 10
		},
		api : {
		    hostname : 'localhost',
			port : 8080,
			url : 'http://localhost:8090/jobs?context=luciusapi&appName=luciusapi&appName=luciusapi&sync=true&classPath=com.dataintuitive.luciusapi.',
		},
		sourire : {
		    hostname : 'localhost',
			port : 9000
		}
};

export function IsolatedSettings(sources) {
	return isolate(Settings, 'settings')(sources)
}

export function Settings(sources) {

//   const router = sources.DOM.select('a').events('click')
//     .debug(ev => ev.preventDefault())
//     .map(ev => ev.target.pathname)

	const state$ = sources.onion.state$

	const settings$ = sources.onion.state$.debug(state => {
		console.log('== State in settings =================')
		console.log(state)
	})

	const tableHeader = (content) => [ h4( [
			i('.material-icons .tiny', 'settings'),
			content
		])]

	const tableSubHeader = (content) =>
		div([
			h5( [
				content
			]),
			div('.row', []),
		])

	const tableHeaderEl = (content) =>
			div('.row', [
				div('.col .s12 .offset-s1', tableHeader(content))
			])

	const vdom$ = settings$.map(state =>
		div([
			tableHeaderEl('Table Settings'),
			div('.row .container ', [
				div('.col .s6', [
					tableSubHeader('Top Table'),
					div('.input-field ', [
						input('.headTableCount', {style: {fontSize: '20px'} , props: {type: 'text', value: state.headTableSettings.head}}),
						label('.active', '# of entries in top tables'),
					]),
					div('.row', [
						div('.input-field .col .s12', [
							input('.headColor', {style: {fontSize: '20px'} , props: {type: 'text', value: state.headTableSettings.color}}),
							label('.active', 'Color for top tables'),
						]),
						// div('.col .s1', {style: {'background-color': state.headTableSettings.color}}, [p('')])
					])
				]),
				div('.col .s6' , [
					tableSubHeader('Bottom Table'),
					div('.input-field', [
						input('.tailTableCount', {style: {fontSize: '20px'} , props: {type: 'text', value: state.tailTableSettings.tail}}),
						label('.active', '# of entries in bottom tables'),
					]),
					div('.input-field', [
						input('.tailColor', {style: {fontSize: '20px'} , props: {type: 'text', value: state.tailTableSettings.color}}),
						label('.active', 'Color for tail tables'),
					]),
				]),
			]),
			// Histogram Settings
			tableHeaderEl('Histogram Settings'),
			div('.row .container', [
				div('.input-field ', [
					input('.hist-bins', {style: {fontSize: '20px'} , props: {type: 'text', value: state.hist.bins}}),
					label('.active', '# Bins for histogram'),
				]),
			]),
			// API Settings
			tableHeaderEl('API Settings'),
			div('.row .container', [
				div('.input-field', [
					input('.api-hostname', {style: {fontSize: '20px'} , props: {type: 'text', value: state.api.hostname}}),
					label('.active', 'API Hostname'),
				]),
				div('.input-field ', [
					input('.api-port', {style: {fontSize: '20px'} , props: {type: 'text', value: state.api.port}}),
					label('.active', 'API Port'),
				]),
			]),
			div('.row .container', [
				button('.apply .col .s3 .offset-s2 .btn', 'Apply'),
				button('.reset .col .s3 .offset-s2 .btn', 'Reset')
			]),
		])
	)

	// const vdom$ = xs.of(
	// 	div('.row', [
	// 		input('.nrHead .col .s8 .offset-s2', {style: {fontSize: '20px'} , props: {type: 'text', value: 3}, value: 3}) //input('.Query .col s10', {style: {fontSize: '20px'} , props: {type: 'text', value: query}, value: query})
	// 	])
	// )

	const makeConfigStream = (id, key) => {
		return sources.DOM.select(id).events('input').map(event => ({[key] : event.target.value})).startWith({})
	}

	// Table Settings
	const headTableCount$ = makeConfigStream('.headTableCount', 'head')
	const tailTableCount$ = makeConfigStream('.tailTableCount', 'tail')
	const headColor$ = makeConfigStream('.headColor', 'color')
	const tailColor$ = makeConfigStream('.tailColor', 'color')

	const headTableSettings$ = xs.combine(settings$.map(state => state.headTableSettings), headTableCount$, headColor$)
		.map((settings) => ({
				headTableSettings : mergeAll(settings),
		}))//.debug(console.log)

	const tailTableSettings$ = xs.combine(settings$.map(state => state.tailTableSettings), tailTableCount$, tailColor$)
		.map((settings) => ({
				tailTableSettings : mergeAll(settings),
		}))//.debug(console.log)

	// Table Settings
	const apiHostname$ = makeConfigStream('.api-hostname', 'hostname')
	const apiPort$ = makeConfigStream('.api-port', 'port')

	const apiSettings$ = xs.combine(settings$.map(state => state.api), apiHostname$, apiPort$)
		.map((settings) => ({
				api : mergeAll(settings)
		}))//.debug(console.log)

	// Histogram Settings
	const histBins$ = makeConfigStream('.hist-bins', 'bins')//.debug(console.log)

	const histSettings$ = xs.combine(settings$.map(state => state.hist), histBins$)
		.map((settings) => ({
				hist : mergeAll(settings)
		}))//.debug(console.log)

	// const nrHostname$ = sources.DOM.select('.hostname').events('input').map(event => ({hostname : event.target.value})).debug(console.log)
	// const nrSourire$ = sources.DOM.select('.sourire').events('input').map(event => ({sourire : event.target.value})).debug(console.log)

	const all$ = xs.combine(headTableSettings$, tailTableSettings$, apiSettings$, histSettings$)
		.map((settings) => mergeAll(settings))//.debug(console.log)

	const apply$ = sources.DOM.select('.apply').events('click')
	const reset$ = sources.DOM.select('.reset').events('click')

	const defaultReducer$ = xs.of(prevState => {
		console.log("settings -- defaultReducer")
		console.log(prevState)
		if (typeof prevState === 'undefined') {
			return clone(initSettings)
		} else {
			return prevState
		}
	})

	const updateReducer$ = apply$.compose(sampleCombine(all$))
		.map(([click, value]) => prevState => {
			console.log('settings -- updateReducer')
			return merge(prevState, value)
		})

	const resetReducer$ = reset$.compose(sampleCombine(all$))
		.map(([click, value]) => prevState => {
			console.log('settings -- resetReducer')
			return clone(initSettings)
		})

//   const pageReducers$ = page$.map(prop('onion')).flatten()//.debug(console.log)
//   const reducers$ = pageReducers$.startWith(initReducer$)

	return {
        DOM: vdom$,
		onion: xs.merge(
			defaultReducer$,
			updateReducer$,
			resetReducer$
		),
		// router: router
		// router : xs.of('/settings')
	};
}
<commit_msg>Use sliders instead of text input fields<commit_after>import { a, div, br, label, input, p, button, code, pre, h2, h4, i, h3, h5 } from '@cycle/dom'
import xs from 'xstream'
import isolate from '@cycle/isolate'
import { mergeWith, merge, mergeAll } from 'ramda'
import { clone } from 'ramda';
import sampleCombine from 'xstream/extra/sampleCombine'

// const settings = {
// }

// const initState = {
// 	settings : {
// 		tableCount : 2,
// 		url : 'http://localhost:8090/jobs?context=luciusapi&appName=luciusapi&appName=luciusapi&sync=true&classPath=com.dataintuitive.luciusapi.',
// 	}
// };

export const initSettings = {
		commmon : {
			version: 'v2',
		},
		headTableSettings : {
			title: 'Top Table 123',
			head : 5,
			color: 'rgb(44,123,182)',
			title: 'Top Table',
			version: 'v2'
		},
		tailTableSettings : {
			title: 'Bottom Table 123',
			tail : 5,
			color: 'rgb(215,25,28)',
			title: 'Bottom Table',
			version: 'v2'
		},
		hist : {
			bins : 20
		},
		api : {
		    hostname : 'localhost',
			port : 8080,
			url : 'http://localhost:8090/jobs?context=luciusapi&appName=luciusapi&appName=luciusapi&sync=true&classPath=com.dataintuitive.luciusapi.',
		},
		sourire : {
		    hostname : 'localhost',
			port : 9000
		}
};

export function IsolatedSettings(sources) {
	return isolate(Settings, 'settings')(sources)
}

export function Settings(sources) {

//   const router = sources.DOM.select('a').events('click')
//     .debug(ev => ev.preventDefault())
//     .map(ev => ev.target.pathname)

	const state$ = sources.onion.state$

	const settings$ = sources.onion.state$.debug(state => {
		console.log('== State in settings =================')
		console.log(state)
	})

	const tableHeader = (content) => [ h4( [
			i('.material-icons .tiny', 'settings'),
			content
		])]

	const tableSubHeader = (content) =>
		div([
			h5( [
				content
			]),
			div('.row', []),
		])

	const tableHeaderEl = (content) =>
			div('.row', [
				div('.col .s12 .offset-s1', tableHeader(content))
			])

	const vdom$ = settings$.map(state =>
		div([
			tableHeaderEl('Table Settings'),
			div('.row .container ', [
				div('.col .s6', [
					tableSubHeader('Top Table'),
					div('.range-field ', [
						label('.active', '# of entries in top tables'),
						input('.headTableCount', {style: {fontSize: '20px'} , props: {type: 'range', min:0, max:100, value: state.headTableSettings.head}}),
					]),
					div('.row', [
						div('.input-field .col .s12', [
							input('.headColor', {style: {fontSize: '20px'} , props: {type: 'text', value: state.headTableSettings.color}}),
							label('.active', 'Color for top tables'),
						]),
						// div('.col .s1', {style: {'background-color': state.headTableSettings.color}}, [p('')])
					])
				]),
				div('.col .s6' , [
					tableSubHeader('Bottom Table'),
					div('.range-field', [
						label('.active', '# of entries in bottom tables'),
						input('.tailTableCount', {style: {fontSize: '20px'} , props: {type: 'range', min:0, max:100, value: state.tailTableSettings.tail}}),
					]),
					div('.input-field', [
						input('.tailColor', {style: {fontSize: '20px'} , props: {type: 'text', value: state.tailTableSettings.color}}),
						label('.active', 'Color for tail tables'),
					]),
				]),
			]),
			// Histogram Settings
			tableHeaderEl('Histogram Settings'),
			div('.row .container', [
				div('.range-field ', [
					label('.active', '# Bins for histogram'),
					input('.hist-bins', {style: {fontSize: '20px'} , props: {type: 'range', min:10, max: 100, value: state.hist.bins}}),
				]),
			]),
			// API Settings
			tableHeaderEl('API Settings'),
			div('.row .container', [
				div('.input-field', [
					input('.api-hostname', {style: {fontSize: '20px'} , props: {type: 'text', value: state.api.hostname}}),
					label('.active', 'API Hostname'),
				]),
				div('.input-field ', [
					input('.api-port', {style: {fontSize: '20px'} , props: {type: 'text', value: state.api.port}}),
					label('.active', 'API Port'),
				]),
			]),
			div('.row .container', [
				button('.apply .col .s3 .offset-s2 .btn', 'Apply'),
				button('.reset .col .s3 .offset-s2 .btn', 'Reset')
			]),
		])
	)

	// const vdom$ = xs.of(
	// 	div('.row', [
	// 		input('.nrHead .col .s8 .offset-s2', {style: {fontSize: '20px'} , props: {type: 'text', value: 3}, value: 3}) //input('.Query .col s10', {style: {fontSize: '20px'} , props: {type: 'text', value: query}, value: query})
	// 	])
	// )

	const makeConfigStream = (id, key) => {
		return sources.DOM.select(id).events('input').map(event => ({[key] : event.target.value})).startWith({})
	}

	// Table Settings
	const headTableCount$ = makeConfigStream('.headTableCount', 'head')
	const tailTableCount$ = makeConfigStream('.tailTableCount', 'tail')
	const headColor$ = makeConfigStream('.headColor', 'color')
	const tailColor$ = makeConfigStream('.tailColor', 'color')

	const headTableSettings$ = xs.combine(settings$.map(state => state.headTableSettings), headTableCount$, headColor$)
		.map((settings) => ({
				headTableSettings : mergeAll(settings),
		}))//.debug(console.log)

	const tailTableSettings$ = xs.combine(settings$.map(state => state.tailTableSettings), tailTableCount$, tailColor$)
		.map((settings) => ({
				tailTableSettings : mergeAll(settings),
		}))//.debug(console.log)

	// Table Settings
	const apiHostname$ = makeConfigStream('.api-hostname', 'hostname')
	const apiPort$ = makeConfigStream('.api-port', 'port')

	const apiSettings$ = xs.combine(settings$.map(state => state.api), apiHostname$, apiPort$)
		.map((settings) => ({
				api : mergeAll(settings)
		}))//.debug(console.log)

	// Histogram Settings
	const histBins$ = makeConfigStream('.hist-bins', 'bins')//.debug(console.log)

	const histSettings$ = xs.combine(settings$.map(state => state.hist), histBins$)
		.map((settings) => ({
				hist : mergeAll(settings)
		}))//.debug(console.log)

	// const nrHostname$ = sources.DOM.select('.hostname').events('input').map(event => ({hostname : event.target.value})).debug(console.log)
	// const nrSourire$ = sources.DOM.select('.sourire').events('input').map(event => ({sourire : event.target.value})).debug(console.log)

	const all$ = xs.combine(headTableSettings$, tailTableSettings$, apiSettings$, histSettings$)
		.map((settings) => mergeAll(settings))//.debug(console.log)

	const apply$ = sources.DOM.select('.apply').events('click')
	const reset$ = sources.DOM.select('.reset').events('click')

	const defaultReducer$ = xs.of(prevState => {
		console.log("settings -- defaultReducer")
		console.log(prevState)
		if (typeof prevState === 'undefined') {
			return clone(initSettings)
		} else {
			return prevState
		}
	})

	const updateReducer$ = apply$.compose(sampleCombine(all$))
		.map(([click, value]) => prevState => {
			console.log('settings -- updateReducer')
			return merge(prevState, value)
		})

	const resetReducer$ = reset$.compose(sampleCombine(all$))
		.map(([click, value]) => prevState => {
			console.log('settings -- resetReducer')
			return clone(initSettings)
		})

//   const pageReducers$ = page$.map(prop('onion')).flatten()//.debug(console.log)
//   const reducers$ = pageReducers$.startWith(initReducer$)

	return {
        DOM: vdom$,
		onion: xs.merge(
			defaultReducer$,
			updateReducer$,
			resetReducer$
		),
		// router: router
		// router : xs.of('/settings')
	};
}
<|endoftext|>


<commit_before>(function() {

angular.module('queryInterface', ['angucomplete-alt'])
	.factory('queryInterfaceState', function() { // persist state of page

		return {
			classData: {}, // classData from Classes service
			classIndex: [], // list of class indices for iteration

			selectedClass: false,
			searchOrderBy: 'ai', // (see /src/data/format.md for available options)
			offspring: 'i',

			sparqlQuery: '', // sparql query as a string
			queryError: '', // error message string returned from query.wikidata.org if something went wrong

			pagination: undefined // persist pagination (unnecessary statement for the sake of verbosity)
		};
	})

	.controller('QueryController', ['$scope','Classes', 'i18n', 'sparql', 'wikidataapi', 'queryInterfaceState',  function($scope, Classes, i18n, sparql, wikidataapi, qis) {

		sparqewl = sparql; // expose as global in dev console TODO remove in production

		$scope.qui = qis; // just point to the persisted state object
		$scope.pagination = {
			autoBoot: true,
			onPageChange: processEntities,
			init: function(pgnt) {
				if(qis.pagination === undefined) { qis.pagination = jQuery.extend({}, pgnt); }
				return qis.pagination;
		}	};

		$scope.selectedClassHandler = function(selected) {
			if(selected) { qis.selectedClass = selected; }
			else { qis.selectedClass = false; }

			$scope.buildSparql();
		};

		// searching classes in lokal data by id or labels (case insensitive)
		$scope.classSearch = function(str) {
			var i = qis.classIndex.length, matches = [],
				classId, classLabel, newMatch;
			str = str.toString().toLowerCase();
			while(i--) {
				classId = qis.classIndex[i];
				classLabel = qis.classData[classId].l;
				if( (classLabel !== undefined && classLabel !== null && classLabel.toLowerCase().indexOf(str) > -1)
				||( ('q'+classId).indexOf(str) > -1) ) {
					newmatch = qis.classData[qis.classIndex[i]];
					newmatch.qid = 'Q' + qis.classIndex[i];
					newmatch.title = newmatch.l + ' [' + newmatch.qid + ']';
					matches.push(newmatch);
				}
			}
			matches.sort(function(a,b) { // sort by number of all instances descending
				return (a[qis.searchOrderBy] > b[qis.searchOrderBy]) ? -1 : ( (a[qis.searchOrderBy] === b[qis.searchOrderBy]) ? 0 : 1);
			});
			return matches.slice(0,9);
		};

		// build classIndex array when we have the class data
		Classes.then(function(data){
			//console.log('classes loaded');
			qis.classData = data.getClasses();
			qis.classIndex = [];
			for(var p in qis.classData) {
				if(qis.classData.hasOwnProperty(p)) {
					qis.classIndex.push(p);
				}
			}

		});

		// Translate form state into sparql
		$scope.buildSparql = function() {
			if(!qis.selectedClass) { qis.sparqlQuery = null; } else {

				var obj = "wd:" + qis.selectedClass.originalObject.qid,
					ins = "?instance",
					tab = "    ";

				var header = sparql.getStandardPrefixes() +
					"SELECT " + ins + " \n" +
					"WHERE {\n" + tab;

				var body;
				switch(qis.offspring) {
					case 'i': 	body = ins + " wdt:P31 " + obj + " .";
								break;
					case 'ai': 	body = "?class wdt:P279* " + obj + " .\n" +
									tab + ins + " wdt:P31 ?class .";
								break;
					case 's': 	body = ins + " wdt:P279 " + obj + " .";
								break;
					case 'as': 	body = ins + " wdt:P279* " + obj + " .";
								break;
				}

				var footer = "\n" +
					"}";

				qis.sparqlQuery = header + body + footer;
			}
		};

		$scope.runSparql = function() {
			sparql.getQueryRequest(qis.sparqlQuery).then(function(data) { // success
				qis.pagination.setIndex(data.results.bindings);
			}, function(response) { // error
				console.log(response);
				qis.queryError = response;
			});

		};

		// process entities on the current page
		function processEntities (entities) {
			// grab Q/P ids from the full entity uri
			var entityIds = [], i = entities.length, eid;
			while(i--) {
				if(entities[i].qid === undefined) { // no need to process more than once
					eid = entities[i].instance.value.split('/entity/')[1];
					entities[i].qid = eid;
					entities[i].url = i18n.getEntityUrl(eid);
					entityIds.push(eid);
				}
			}
			// pull labels and descriptions
			if(entityIds.length > 0) {
				wikidataapi.getEntityTerms(entityIds, i18n.getLanguage()).then(function(data) {
					//console.log(data);
					var i = entities.length, entity;
					while(i--) {
						entity = entities[i];
						if(data[entity.qid] !== undefined) {
							entity.label = data[entity.qid].label;
							entity.description = data[entity.qid].description;
						}
					}
				});
			}
		}
	}]); // controller
})(); // module
<commit_msg>return to page 1 on new query<commit_after>(function() {

angular.module('queryInterface', ['angucomplete-alt'])
	.factory('queryInterfaceState', function() { // persist state of page

		return {
			classData: {}, // classData from Classes service
			classIndex: [], // list of class indices for iteration

			selectedClass: false,
			searchOrderBy: 'ai', // (see /src/data/format.md for available options)
			offspring: 'i',

			sparqlQuery: '', // sparql query as a string
			queryError: '', // error message string returned from query.wikidata.org if something went wrong

			pagination: undefined // persist pagination (unnecessary statement for the sake of verbosity)
		};
	})

	.controller('QueryController', ['$scope','Classes', 'i18n', 'sparql', 'wikidataapi', 'queryInterfaceState',  function($scope, Classes, i18n, sparql, wikidataapi, qis) {

		sparqewl = sparql; // expose as global in dev console TODO remove in production

		$scope.qui = qis; // just point to the persisted state object
		$scope.pagination = {
			autoBoot: true,
			onPageChange: processEntities,
			init: function(pgnt) {
				if(qis.pagination === undefined) { qis.pagination = jQuery.extend({}, pgnt); }
				return qis.pagination;
		}	};

		$scope.selectedClassHandler = function(selected) {
			if(selected) { qis.selectedClass = selected; }
			else { qis.selectedClass = false; }

			$scope.buildSparql();
		};

		// searching classes in lokal data by id or labels (case insensitive)
		$scope.classSearch = function(str) {
			var i = qis.classIndex.length, matches = [],
				classId, classLabel, newMatch;
			str = str.toString().toLowerCase();
			while(i--) {
				classId = qis.classIndex[i];
				classLabel = qis.classData[classId].l;
				if( (classLabel !== undefined && classLabel !== null && classLabel.toLowerCase().indexOf(str) > -1)
				||( ('q'+classId).indexOf(str) > -1) ) {
					newmatch = qis.classData[qis.classIndex[i]];
					newmatch.qid = 'Q' + qis.classIndex[i];
					newmatch.title = newmatch.l + ' [' + newmatch.qid + ']';
					matches.push(newmatch);
				}
			}
			matches.sort(function(a,b) { // sort by number of all instances descending
				return (a[qis.searchOrderBy] > b[qis.searchOrderBy]) ? -1 : ( (a[qis.searchOrderBy] === b[qis.searchOrderBy]) ? 0 : 1);
			});
			return matches.slice(0,9);
		};

		// build classIndex array when we have the class data
		Classes.then(function(data){
			//console.log('classes loaded');
			qis.classData = data.getClasses();
			qis.classIndex = [];
			for(var p in qis.classData) {
				if(qis.classData.hasOwnProperty(p)) {
					qis.classIndex.push(p);
				}
			}

		});

		// Translate form state into sparql
		$scope.buildSparql = function() {
			if(!qis.selectedClass) { qis.sparqlQuery = null; } else {

				var obj = "wd:" + qis.selectedClass.originalObject.qid,
					ins = "?instance",
					tab = "    ";

				var header = sparql.getStandardPrefixes() +
					"SELECT " + ins + " \n" +
					"WHERE {\n" + tab;

				var body;
				switch(qis.offspring) {
					case 'i': 	body = ins + " wdt:P31 " + obj + " .";
								break;
					case 'ai': 	body = "?class wdt:P279* " + obj + " .\n" +
									tab + ins + " wdt:P31 ?class .";
								break;
					case 's': 	body = ins + " wdt:P279 " + obj + " .";
								break;
					case 'as': 	body = ins + " wdt:P279* " + obj + " .";
								break;
				}

				var footer = "\n" +
					"}";

				qis.sparqlQuery = header + body + footer;
			}
		};

		$scope.runSparql = function() {
			sparql.getQueryRequest(qis.sparqlQuery).then(function(data) { // success
				qis.pagination.setIndex(data.results.bindings);
				qis.pagination.setPage(1);
			}, function(response) { // error
				console.log(response);
				qis.queryError = response;
			});

		};

		// process entities on the current page
		function processEntities (entities) {
			// grab Q/P ids from the full entity uri
			var entityIds = [], i = entities.length, eid;
			while(i--) {
				if(entities[i].qid === undefined) { // no need to process more than once
					eid = entities[i].instance.value.split('/entity/')[1];
					entities[i].qid = eid;
					entities[i].url = i18n.getEntityUrl(eid);
					entityIds.push(eid);
				}
			}
			// pull labels and descriptions
			if(entityIds.length > 0) {
				wikidataapi.getEntityTerms(entityIds, i18n.getLanguage()).then(function(data) {
					//console.log(data);
					var i = entities.length, entity;
					while(i--) {
						entity = entities[i];
						if(data[entity.qid] !== undefined) {
							entity.label = data[entity.qid].label;
							entity.description = data[entity.qid].description;
						}
					}
				});
			}
		}
	}]); // controller
})(); // module
<|endoftext|>


<commit_before>/**
 * Implements XEP-0313: Message Archive Management.
 *
 * @namespace jsxc.xmpp.mam
 * @see {@link https://xmpp.org/extensions/xep-0313.html}
 */
jsxc.xmpp.mam = {
   conn: null
};

jsxc.xmpp.mam.init = function() {
   var self = jsxc.xmpp.mam;

   self.conn = jsxc.xmpp.conn;
};

jsxc.xmpp.mam.isEnabled = function() {
   var self = jsxc.xmpp.mam;
   var mamOptions = jsxc.options.get('mam') || {};

   // prosody does not announce mam:2, while it is supported
   var hasFeatureMam0 = jsxc.xmpp.hasFeatureByJid(self.conn.domain, 'urn:xmpp:mam:0');
   var hasFeatureMam2 = jsxc.xmpp.hasFeatureByJid(self.conn.domain, Strophe.NS.MAM);

   return (hasFeatureMam0 || hasFeatureMam2) && mamOptions.enable;
};

jsxc.xmpp.mam.nextMessages = function(bid) {
   var self = jsxc.xmpp.mam;
   var buddyData = jsxc.storage.getUserItem('buddy', bid) || {};
   var lastArchiveUid = buddyData.lastArchiveUid;
   var queryId = self.conn.getUniqueId();
   var mamOptions = jsxc.options.get('mam') || {};
   var history = jsxc.storage.getUserItem('history', bid) || [];

   if (buddyData.archiveExhausted) {
      jsxc.debug('No more archived messages.');
      return;
   }

   var queryOptions = {
      queryid: queryId,
      before: lastArchiveUid || '',
      with: bid,
      onMessage: function() {
         var args = Array.from(arguments);
         args.unshift(bid);
         self.onMessage.apply(this, args);
         return true;
      },
      onComplete: function() {
         var args = Array.from(arguments);
         args.unshift(bid);
         self.onComplete.apply(this, args);
         return true;
      }
   };

   var oldestMessageId = history[history.length - 1];

   if (oldestMessageId && !lastArchiveUid) {
      var oldestMessage = new jsxc.Message(oldestMessageId);
      queryOptions.end = (new Date(oldestMessage.stamp)).toISOString();
   }

   if (mamOptions.max) {
      queryOptions.max = mamOptions.max;
   }

   self.conn.mam.query(undefined, queryOptions);
};

jsxc.xmpp.mam.onMessage = function(bid, stanza) {
   stanza = $(stanza);
   var result = stanza.find('result[xmlns="' + Strophe.NS.MAM + '"]');
   var queryId = result.attr('queryid');

   if (result.length !== 1) {
      return;
   }

   var forwarded = result.find('forwarded[xmlns="' + jsxc.CONST.NS.FORWARD + '"]');
   var message = forwarded.find('message');
   var messageId = $(message).attr('id');

   if (message.length !== 1) {
      return;
   }

   var from = message.attr('from');
   var to = message.attr('to');

   if (jsxc.jidToBid(from) !== bid && jsxc.jidToBid(to) !== bid) {
      return;
   }

   var delay = forwarded.find('delay[xmlns="urn:xmpp:delay"]');
   var stamp = (delay.length > 0) ? new Date(delay.attr('stamp')) : new Date();
   stamp = stamp.getTime();

   var body = $(message).find('body:first').text();

   if (!body || body.match(/\?OTR/i)) {
      return true;
   }

   var direction = (jsxc.jidToBid(to) === bid) ? jsxc.Message.OUT : jsxc.Message.IN;

   var win = jsxc.gui.window.get(bid);
   var textarea = win.find('.jsxc_textarea');
   if (textarea.find('[id="' + messageId + '"]').length === 0) {
      var pseudoChatElement = $('<div>');
      pseudoChatElement.attr('id', messageId.replace(/:/g, '-'));
      pseudoChatElement.attr('data-queryId', queryId);

      var lastMessage = textarea.find('[data-queryId="' + queryId + '"]').last();
      var history = jsxc.storage.getUserItem('history', bid) || [];

      if (history.indexOf(messageId) < 0) {
         if (lastMessage.length === 0) {
            textarea.prepend(pseudoChatElement);
            history.push(messageId);
         } else {
            lastMessage.after(pseudoChatElement);
            history.splice(history.indexOf(lastMessage.attr('id').replace(/-/g, ':')), 0, messageId);
         }
      }

      jsxc.storage.setUserItem('history', bid, history);
   }

   jsxc.gui.window.postMessage({
      _uid: messageId,
      bid: bid,
      direction: direction,
      msg: body,
      encrypted: false,
      forwarded: true,
      stamp: stamp
   });
};

jsxc.xmpp.mam.onComplete = function(bid, stanza) {
   stanza = $(stanza);
   var fin = stanza.find('fin[xmlns="' + Strophe.NS.MAM + '"]');
   var buddyData = jsxc.storage.getUserItem('buddy', bid) || {};

   buddyData.archiveExhausted = fin.attr('complete') === 'true';
   buddyData.lastArchiveUid = fin.find('first').text();

   jsxc.storage.setUserItem('buddy', bid, buddyData);
};

jsxc.xmpp.mam.initWindow = function(ev, win) {
   var self = jsxc.xmpp.mam;

   if (!jsxc.xmpp.conn && jsxc.master) {
      $(document).one('attached.jsxc', function() {
         self.initWindow(null, win);
      });
      return;
   }

   if (!jsxc.xmpp.mam.isEnabled()) {
      return;
   }

   var bid = win.attr('data-bid');
   var buddyData = jsxc.storage.getUserItem('buddy', bid) || {};

   var element = $('<div>');
   element.addClass('jsxc_mam-load-more');
   element.appendTo(win.find('.slimScrollDiv'));
   element.click(function() {
      jsxc.xmpp.mam.nextMessages(bid);
   });
   element.text($.t('Load_older_messages'));

   win.find('.jsxc_textarea').scroll(function() {
      if (this.scrollTop < 42 && !buddyData.archiveExhausted) {
         element.addClass('jsxc_show');
      } else {
         element.removeClass('jsxc_show');
      }
   });

   win.find('.jsxc_textarea').scroll();
};

$(document).on('attached.jsxc', jsxc.xmpp.mam.init);
$(document).on('init.window.jsxc', jsxc.xmpp.mam.initWindow);
<commit_msg>fix mam button in empty windows<commit_after>/**
 * Implements XEP-0313: Message Archive Management.
 *
 * @namespace jsxc.xmpp.mam
 * @see {@link https://xmpp.org/extensions/xep-0313.html}
 */
jsxc.xmpp.mam = {
   conn: null
};

jsxc.xmpp.mam.init = function() {
   var self = jsxc.xmpp.mam;

   self.conn = jsxc.xmpp.conn;
};

jsxc.xmpp.mam.isEnabled = function() {
   var self = jsxc.xmpp.mam;
   var mamOptions = jsxc.options.get('mam') || {};

   // prosody does not announce mam:2, while it is supported
   var hasFeatureMam0 = jsxc.xmpp.hasFeatureByJid(self.conn.domain, 'urn:xmpp:mam:0');
   var hasFeatureMam2 = jsxc.xmpp.hasFeatureByJid(self.conn.domain, Strophe.NS.MAM);

   return (hasFeatureMam0 || hasFeatureMam2) && mamOptions.enable;
};

jsxc.xmpp.mam.nextMessages = function(bid) {
   var self = jsxc.xmpp.mam;
   var buddyData = jsxc.storage.getUserItem('buddy', bid) || {};
   var lastArchiveUid = buddyData.lastArchiveUid;
   var queryId = self.conn.getUniqueId();
   var mamOptions = jsxc.options.get('mam') || {};
   var history = jsxc.storage.getUserItem('history', bid) || [];

   if (buddyData.archiveExhausted) {
      jsxc.debug('No more archived messages.');
      return;
   }

   var queryOptions = {
      queryid: queryId,
      before: lastArchiveUid || '',
      with: bid,
      onMessage: function() {
         var args = Array.from(arguments);
         args.unshift(bid);
         self.onMessage.apply(this, args);
         return true;
      },
      onComplete: function() {
         var args = Array.from(arguments);
         args.unshift(bid);
         self.onComplete.apply(this, args);
         return true;
      }
   };

   var oldestMessageId = history[history.length - 1];

   if (oldestMessageId && !lastArchiveUid) {
      var oldestMessage = new jsxc.Message(oldestMessageId);
      queryOptions.end = (new Date(oldestMessage.stamp)).toISOString();
   }

   if (mamOptions.max) {
      queryOptions.max = mamOptions.max;
   }

   self.conn.mam.query(undefined, queryOptions);
};

jsxc.xmpp.mam.onMessage = function(bid, stanza) {
   stanza = $(stanza);
   var result = stanza.find('result[xmlns="' + Strophe.NS.MAM + '"]');
   var queryId = result.attr('queryid');

   if (result.length !== 1) {
      return;
   }

   var forwarded = result.find('forwarded[xmlns="' + jsxc.CONST.NS.FORWARD + '"]');
   var message = forwarded.find('message');
   var messageId = $(message).attr('id');

   if (message.length !== 1) {
      return;
   }

   var from = message.attr('from');
   var to = message.attr('to');

   if (jsxc.jidToBid(from) !== bid && jsxc.jidToBid(to) !== bid) {
      return;
   }

   var delay = forwarded.find('delay[xmlns="urn:xmpp:delay"]');
   var stamp = (delay.length > 0) ? new Date(delay.attr('stamp')) : new Date();
   stamp = stamp.getTime();

   var body = $(message).find('body:first').text();

   if (!body || body.match(/\?OTR/i)) {
      return true;
   }

   var direction = (jsxc.jidToBid(to) === bid) ? jsxc.Message.OUT : jsxc.Message.IN;

   var win = jsxc.gui.window.get(bid);
   var textarea = win.find('.jsxc_textarea');
   if (textarea.find('[id="' + messageId + '"]').length === 0) {
      var pseudoChatElement = $('<div>');
      pseudoChatElement.attr('id', messageId.replace(/:/g, '-'));
      pseudoChatElement.attr('data-queryId', queryId);

      var lastMessage = textarea.find('[data-queryId="' + queryId + '"]').last();
      var history = jsxc.storage.getUserItem('history', bid) || [];

      if (history.indexOf(messageId) < 0) {
         if (lastMessage.length === 0) {
            textarea.prepend(pseudoChatElement);
            history.push(messageId);
         } else {
            lastMessage.after(pseudoChatElement);
            history.splice(history.indexOf(lastMessage.attr('id').replace(/-/g, ':')), 0, messageId);
         }
      }

      jsxc.storage.setUserItem('history', bid, history);
   }

   jsxc.gui.window.postMessage({
      _uid: messageId,
      bid: bid,
      direction: direction,
      msg: body,
      encrypted: false,
      forwarded: true,
      stamp: stamp
   });
};

jsxc.xmpp.mam.onComplete = function(bid, stanza) {
   stanza = $(stanza);
   var fin = stanza.find('fin[xmlns="' + Strophe.NS.MAM + '"]');
   var buddyData = jsxc.storage.getUserItem('buddy', bid) || {};

   buddyData.archiveExhausted = fin.attr('complete') === 'true';
   buddyData.lastArchiveUid = fin.find('first').text();

   jsxc.storage.setUserItem('buddy', bid, buddyData);
};

jsxc.xmpp.mam.initWindow = function(ev, win) {
   var self = jsxc.xmpp.mam;

   if (!jsxc.xmpp.conn && jsxc.master) {
      $(document).one('attached.jsxc', function() {
         self.initWindow(null, win);
      });
      return;
   }

   if (!jsxc.xmpp.mam.isEnabled()) {
      return;
   }

   var bid = win.attr('data-bid');

   var element = $('<div>');
   element.addClass('jsxc_mam-load-more');
   element.appendTo(win.find('.slimScrollDiv'));
   element.click(function() {
      jsxc.xmpp.mam.nextMessages(bid);
   });
   element.text($.t('Load_older_messages'));

   win.find('.jsxc_textarea').scroll(function() {
      var buddyData = jsxc.storage.getUserItem('buddy', bid) || {};

      if (this.scrollTop < 42 && !buddyData.archiveExhausted) {
         element.addClass('jsxc_show');
      } else {
         element.removeClass('jsxc_show');
      }
   });

   win.find('.jsxc_textarea').scroll();
};

$(document).on('attached.jsxc', jsxc.xmpp.mam.init);
$(document).on('init.window.jsxc', jsxc.xmpp.mam.initWindow);
<|endoftext|>


<commit_before>/**
  Adrian Feld <adrian@feldorado.de>
*/
export default {
	// Top Menu
	dashboard: "Dashboard",
	sysCleaner: "System Cleaner",
	startupApps: "Startup Apps",
	services: "Services",
	process: "Processes",
	uninstaller: "Uninstaller",
	resources: "Resources",
	// Dashboard Page
	cpu: "CPU",
	memory: "SPEICHER",
	disk: "DISK",
	download: "DOWNLOAD",
	upload: "UPLOAD",
	systemInfo: "SYSTEM INFO",
	hostname: "Hostname",
	platform: "Platform",
	distribution: "Distribution",
	kernelRel: "Kernel Release",
	cpuModel: "CPU Model",
	cpuSpeed: "CPU Speed",
	cpuCores: "CPU Cores",
	// System Cleaner Page
	packageCache: "Package Cache",
	crashReports: "Crash Reports",
	systemLogs: "System Logs",
	appCache: "App Cache",
	trash: "Trash",
	systemScan: "System Scan",
	clean: "Säubern",
	selectAll: "Alles auswählen",
	// Startup Apps Page
	startupTitle: "System Startup Applikationen",
	addStartup: "Hinzufügen Autostart",
	noStartup: "Keine Autostart Programme gefunden.",
	app: "Applikation",
	appName: "App Name",
	appComment: "App Comment",
	appCommand: "Command",
	add: "Add",
	cancel: "Cancel",
	// Services Page
	serviceTitle: "System Services",
	noService: "Kein Service gefunden",
	// Process Page
	endProcess: "End Process",
	refresh: "Refresh",
	// Uninstaller Page
	uninstallerTitle: "System Installed Packages",
	noPackage: "No package found.",
	uninstallSelecteds: "Uninstall Selecteds",
	// Resources Page
	cpuHistory: "CPU History",
	memHistory: "Memory History",
	networkHistory: "Network History",
	swap: "Swap",
	// Settings
	settings: "Settings",
	appSettings: "Application Settings",
	appLanguage: "App Language",
	appTheme: "App Theme",
	// Global
	search: "Search",
	downloadUpdate: "Download Update",
	updateMsg: "There are updates currently available.",
	// Messages
	opFail: "Operation failed.",
	sysCleanFail: "System reingung fehlgeschlagen.",
	sysCleanSuc: "System gereinigt.",
	appCacheWarning: "Lösche Applikationen \' caches can cause problems. Please be careful.",
	uninstallFail: "Uninstall failed.",
	packageUninstalled: " packages uninstalled.",
	anotherProc: "Another process continues.",
	changedLang: "Restart the program for the changes to take effect.",
	permitted: 'Operation not permitted.',
	update: 'Update'
}
<commit_msg>Update lang.de.js<commit_after>/**
  Adrian Feld <adrian@feldorado.de>
*/
export default {
	// Top Menu
	dashboard: "Dashboard",
	sysCleaner: "System Cleaner",
	startupApps: "Startup Apps",
	services: "Dienste",
	process: "Prozesse",
	uninstaller: "Uninstaller",
	resources: "Ressourcen",
	// Dashboard Page
	cpu: "CPU",
	memory: "SPEICHER",
	disk: "DISK",
	download: "DOWNLOAD",
	upload: "UPLOAD",
	systemInfo: "SYSTEM INFO",
	hostname: "Hostname",
	platform: "Platform",
	distribution: "Distribution",
	kernelRel: "Kernel Release",
	cpuModel: "CPU Model",
	cpuSpeed: "CPU Speed",
	cpuCores: "CPU Cores",
	// System Cleaner Page
	packageCache: "Package Cache",
	crashReports: "Crash Reports",
	systemLogs: "System Logs",
	appCache: "App Cache",
	trash: "Trash",
	systemScan: "System Scan",
	clean: "Säubern",
	selectAll: "Alles auswählen",
	// Startup Apps Page
	startupTitle: "System Startup Applikationen",
	addStartup: "Hinzufügen Autostart",
	noStartup: "Keine Autostart Programme gefunden.",
	app: "Applikation",
	appName: "App Name",
	appComment: "App Kommentar",
	appCommand: "Befehl",
	add: "Hinzufügen",
	cancel: "Abbruch",
	// Services Page
	serviceTitle: "System Services",
	noService: "Kein Service gefunden",
	// Process Page
	endProcess: "End Process",
	refresh: "aktualisieren",
	// Uninstaller Page
	uninstallerTitle: "System Installed Packages",
	noPackage: "Kein Paket gefunden.",
	uninstallSelecteds: "Uninstall Gewählte",
	// Resources Page
	cpuHistory: "CPU Verlauf",
	memHistory: "Speicher Verlauf",
	networkHistory: "Netzwerk Verlauf",
	swap: "Swap",
	// Settings
	settings: "Einstellungen",
	appSettings: "Programm Einstellungen",
	appLanguage: "App Sprache",
	appTheme: "App Theme",
	// Global
	search: "Suche",
	downloadUpdate: "Download Update",
	updateMsg: "Es sind aktuelle Updates verfügbar.",
	// Messages
	opFail: "Operation failed.",
	sysCleanFail: "System reingung fehlgeschlagen.",
	sysCleanSuc: "System gereinigt.",
	appCacheWarning: "Lösche Applikationen \' caches can cause problems. Please be careful.",
	uninstallFail: "Deinstallation fehlgeschlagen.",
	packageUninstalled: " Pakete deinstalliert.",
	anotherProc: "Ein weiterer Prozess läuft.",
	changedLang: "Restart the program for the changes to take effect.",
	permitted: 'Operation unzulässig.',
	update: 'Update'
}
<|endoftext|>


<commit_before>import ol from 'openlayers'
import {ProvideMapMixin} from './ProvideMapMixin'
import {mixin} from '../utilities'

/**
 * This Class is a Wrap around {ol.layer.Group} providing some extra functionality. This class is normally used for a
 * category of layers containing them.
 */
export class GroupLayer extends mixin(ol.layer.Group, ProvideMapMixin) {
  /**
   * @param {object} [options={}]
   */
  constructor (options = {}) {
    super(options)

    let listenerKeys = new WeakMap()

    this.getLayers().on('add', /** ol.CollectionEvent */ e => {
      let layer = e.element
      if (layer.provideMap) {
        layer.provideMap(this.getProvidedMap())
      }
      listenerKeys.set(layer, layer.on('change:visible', () => {
        this.dispatchEvent({
          type: 'change:childVisible',
          child: layer
        })
      }))
    })

    this.getLayers().on('remove', /** ol.CollectionEvent */ e => {
      let layer = e.element
      if (layer.provideMap) {
        layer.provideMap(null)
      }
      ol.Observable.unByKey(listenerKeys.get(layer))
      listenerKeys.delete(layer)
    })
  }

  /**
   * The provideMap methods of all contained children are called recursively
   * @param {G4UMap} map
   */
  provideMap (map) {
    super.provideMap(map)

    this.getLayers().forEach(layer => {
      if (layer.provideMap) { layer.provideMap(map) }
    })
  }

  /**
   * calls callback for every terminal, non-group Layer
   * @param {Function} callback
   */
  recursiveForEach (callback) {
    this.getLayers().forEach(layer => {
      callback(layer, this)
      if (layer.recursiveForEach) {
        layer.recursiveForEach(callback)
      }
    })
  }

  /**
   * Attachs a listener to each layer and to each newly added listener and removes the listener if the layer gets
   * removed.
   * @param eventType
   * @param listener
   */
  forEachOn (eventType, listener) {
    this.recursiveForEach(layer => {
      layer.on(eventType, listener)
      if (layer instanceof GroupLayer) {
        this.getLayers().on('add', (e) => {
          e.element.on(eventType, listener)
        })
        this.getLayers().on('remove', (e) => {
          e.element.un(eventType, listener)
        })
      }
    })
  }

  /**
   * Checks how many children are visible. Doesn't check visibility of the group layer
   * @returns {number}
   */
  countChildrenVisible () {
    let array = this.getLayersArray()
    let count = 0

    for (let i = 0, ii = array.length; i < ii; i++) {
      if (array[i] instanceof GroupLayer) {
        count += array[i].countChildrenVisible()
      } else if (array[i].getVisible()) {
        count += 1
      }
    }
    return count
  }

  countChildren () {
    let array = this.getLayersArray()
    let count = 0

    for (let i = 0, ii = array.length; i < ii; i++) {
      if (array[i] instanceof GroupLayer) {
        count += array[i].countChildren()
      } else {
        count += 1
      }
    }
    return count
  }

  /**
   * Returns the layers inside as an array.
   * @returns {ol.layer.Base[]}
   */
  getLayersArray () {
    return this.getLayers().getArray()
  }

  /**
   * Returns the number of direct child layers
   * @returns {number}
   */
  getLength () {
    return this.getLayers().getLength()
  }

  /**
   * checks if the given layer is a child of the grouplayer
   * @param {ol.layer.Base} layer
   * @returns {boolean}
   */
  isParentOf (layer) {
    let found = false
    this.recursiveForEach(function (childLayer) {
      if (layer === childLayer) {
        found = true
      }
    })
    return found
  }

  /**
   * Returns all ids of all contained layers and its own.
   * @returns {number[]}
   */
  getIds () {
    let ids = [this.get('id')]

    let array = this.getLayersArray()

    for (let i = 0, ii = array.length; i < ii; i++) {
      if (array[i].getIds) {
        ids = ids.concat(array[i].getIds())
      } else {
        ids.push(array[i].get('id'))
      }
    }

    return ids
  }

  /**
   * Returns all attributions of all visible layers.
   * @returns {number[]}
   */
  getAttributions () {
    let attributions = []

    this.recursiveForEach(function (layer) {
      if (layer.getVisible()) {
        if (layer.getSource) {
          let atts = layer.getSource().getAttributions()
          if (atts) {
            attributions = attributions.concat(atts)
          }
        }
      }
    })

    return attributions
  }

  /**
   * Remove layer by id
   * @param {string|number} id
   */
  removeLayerById (id) {
    let layers = this.getLayers()
    for (let i = 0; i < layers.getLength(); i++) {
      if (layers.item(i).get('id') === id) {
        return layers.removeAt(i)
      } else if (layers.item(i).removeLayerById) {
        let res = layers.item(i).removeLayerById(id)
        if (res) {
          return res
        }
      }
    }
  }

  /**
   * Find layer with callback
   * @param {Function} cb
   */
  findLayer (cb) {
    for (let layer of this.getLayersArray()) {
      if (cb(layer)) {
        return layer
      } else if (layer.findLayer) {
        let res = layer.findLayer(cb)
        if (res) {
          return res
        }
      }
    }
  }

  /**
   * Get layer by id
   * @param {string|number} id
   */
  getLayerById (id) {
    for (let layer of this.getLayersArray()) {
      if (layer.get('id') === id) {
        return layer
      } else if (layer.getLayerById) {
        let res = layer.getLayerById(id)
        if (res) {
          return res
        }
      }
    }
  }

  /**
   * removes layer
   * @param {ol.layer.Base} layer
   */
  removeLayer (layer) {
    let found = this.getLayers().remove(layer)
    if (!found) {
      this.getLayers().forEach(subLayer => {
        if (!found && subLayer.removeLayer) {
          found = subLayer.removeLayer(layer)
        }
      })
    }
    return found
  }

  /**
   * gives an object with ids as keys and visibility as value
   * @returns {Object.<string|number,boolean>}
   */
  getIdsVisibilities () {
    let visibilities = {}
    this.recursiveForEach(layer => {
      if (layer.get('id')) {
        visibilities[layer.get('id')] = layer.getVisible()
      }
    })
    return visibilities
  }

  /**
   * Sets the visibility of the layers to the given visibility
   * @param {Object.<string|number,boolean>} visibilities
   */
  setIdsVisibilities (visibilities) {
    this.recursiveForEach(layer => {
      if (layer.get('id')) {
        layer.setVisible(visibilities[layer.get('id')])
      }
    })
  }

  /**
   * Checks if the group contains the layer
   * @param {ol.layer.Layer} layer
   * @returns {boolean}
   */
  containsLayer (layer) {
    return this.getLayers().getArray().some(subLayer => {
      if (subLayer === layer) {
        return true
      }
      if (subLayer.containsLayer) {
        return subLayer.containsLayer(layer)
      }
    })
  }
}
<commit_msg>corrected getIds<commit_after>import ol from 'openlayers'
import {ProvideMapMixin} from './ProvideMapMixin'
import {mixin} from '../utilities'

/**
 * This Class is a Wrap around {ol.layer.Group} providing some extra functionality. This class is normally used for a
 * category of layers containing them.
 */
export class GroupLayer extends mixin(ol.layer.Group, ProvideMapMixin) {
  /**
   * @param {object} [options={}]
   */
  constructor (options = {}) {
    super(options)

    let listenerKeys = new WeakMap()

    this.getLayers().on('add', /** ol.CollectionEvent */ e => {
      let layer = e.element
      if (layer.provideMap) {
        layer.provideMap(this.getProvidedMap())
      }
      listenerKeys.set(layer, layer.on('change:visible', () => {
        this.dispatchEvent({
          type: 'change:childVisible',
          child: layer
        })
      }))
    })

    this.getLayers().on('remove', /** ol.CollectionEvent */ e => {
      let layer = e.element
      if (layer.provideMap) {
        layer.provideMap(null)
      }
      ol.Observable.unByKey(listenerKeys.get(layer))
      listenerKeys.delete(layer)
    })
  }

  /**
   * The provideMap methods of all contained children are called recursively
   * @param {G4UMap} map
   */
  provideMap (map) {
    super.provideMap(map)

    this.getLayers().forEach(layer => {
      if (layer.provideMap) { layer.provideMap(map) }
    })
  }

  /**
   * calls callback for every terminal, non-group Layer
   * @param {Function} callback
   */
  recursiveForEach (callback) {
    this.getLayers().forEach(layer => {
      callback(layer, this)
      if (layer.recursiveForEach) {
        layer.recursiveForEach(callback)
      }
    })
  }

  /**
   * Attachs a listener to each layer and to each newly added listener and removes the listener if the layer gets
   * removed.
   * @param eventType
   * @param listener
   */
  forEachOn (eventType, listener) {
    this.recursiveForEach(layer => {
      layer.on(eventType, listener)
      if (layer instanceof GroupLayer) {
        this.getLayers().on('add', (e) => {
          e.element.on(eventType, listener)
        })
        this.getLayers().on('remove', (e) => {
          e.element.un(eventType, listener)
        })
      }
    })
  }

  /**
   * Checks how many children are visible. Doesn't check visibility of the group layer
   * @returns {number}
   */
  countChildrenVisible () {
    let array = this.getLayersArray()
    let count = 0

    for (let i = 0, ii = array.length; i < ii; i++) {
      if (array[i] instanceof GroupLayer) {
        count += array[i].countChildrenVisible()
      } else if (array[i].getVisible()) {
        count += 1
      }
    }
    return count
  }

  countChildren () {
    let array = this.getLayersArray()
    let count = 0

    for (let i = 0, ii = array.length; i < ii; i++) {
      if (array[i] instanceof GroupLayer) {
        count += array[i].countChildren()
      } else {
        count += 1
      }
    }
    return count
  }

  /**
   * Returns the layers inside as an array.
   * @returns {ol.layer.Base[]}
   */
  getLayersArray () {
    return this.getLayers().getArray()
  }

  /**
   * Returns the number of direct child layers
   * @returns {number}
   */
  getLength () {
    return this.getLayers().getLength()
  }

  /**
   * checks if the given layer is a child of the grouplayer
   * @param {ol.layer.Base} layer
   * @returns {boolean}
   */
  isParentOf (layer) {
    let found = false
    this.recursiveForEach(function (childLayer) {
      if (layer === childLayer) {
        found = true
      }
    })
    return found
  }

  /**
   * Returns all ids of all contained layers and its own.
   * @returns {number[]}
   */
  getIds () {
    let ids = this.get('id') !== undefined ? [this.get('id')] : []

    let array = this.getLayersArray()

    for (let i = 0, ii = array.length; i < ii; i++) {
      if (array[i].getIds) {
        ids = ids.concat(array[i].getIds())
      } else {
        ids.push(array[i].get('id'))
      }
    }

    return ids
  }

  /**
   * Returns all attributions of all visible layers.
   * @returns {number[]}
   */
  getAttributions () {
    let attributions = []

    this.recursiveForEach(function (layer) {
      if (layer.getVisible()) {
        if (layer.getSource) {
          let atts = layer.getSource().getAttributions()
          if (atts) {
            attributions = attributions.concat(atts)
          }
        }
      }
    })

    return attributions
  }

  /**
   * Remove layer by id
   * @param {string|number} id
   */
  removeLayerById (id) {
    let layers = this.getLayers()
    for (let i = 0; i < layers.getLength(); i++) {
      if (layers.item(i).get('id') === id) {
        return layers.removeAt(i)
      } else if (layers.item(i).removeLayerById) {
        let res = layers.item(i).removeLayerById(id)
        if (res) {
          return res
        }
      }
    }
  }

  /**
   * Find layer with callback
   * @param {Function} cb
   */
  findLayer (cb) {
    for (let layer of this.getLayersArray()) {
      if (cb(layer)) {
        return layer
      } else if (layer.findLayer) {
        let res = layer.findLayer(cb)
        if (res) {
          return res
        }
      }
    }
  }

  /**
   * Get layer by id
   * @param {string|number} id
   */
  getLayerById (id) {
    for (let layer of this.getLayersArray()) {
      if (layer.get('id') === id) {
        return layer
      } else if (layer.getLayerById) {
        let res = layer.getLayerById(id)
        if (res) {
          return res
        }
      }
    }
  }

  /**
   * removes layer
   * @param {ol.layer.Base} layer
   */
  removeLayer (layer) {
    let found = this.getLayers().remove(layer)
    if (!found) {
      this.getLayers().forEach(subLayer => {
        if (!found && subLayer.removeLayer) {
          found = subLayer.removeLayer(layer)
        }
      })
    }
    return found
  }

  /**
   * gives an object with ids as keys and visibility as value
   * @returns {Object.<string|number,boolean>}
   */
  getIdsVisibilities () {
    let visibilities = {}
    this.recursiveForEach(layer => {
      if (layer.get('id')) {
        visibilities[layer.get('id')] = layer.getVisible()
      }
    })
    return visibilities
  }

  /**
   * Sets the visibility of the layers to the given visibility
   * @param {Object.<string|number,boolean>} visibilities
   */
  setIdsVisibilities (visibilities) {
    this.recursiveForEach(layer => {
      if (layer.get('id')) {
        layer.setVisible(visibilities[layer.get('id')])
      }
    })
  }

  /**
   * Checks if the group contains the layer
   * @param {ol.layer.Layer} layer
   * @returns {boolean}
   */
  containsLayer (layer) {
    return this.getLayers().getArray().some(subLayer => {
      if (subLayer === layer) {
        return true
      }
      if (subLayer.containsLayer) {
        return subLayer.containsLayer(layer)
      }
    })
  }
}
<|endoftext|>


<commit_before>/**
 * Options
 * =======
 *
 * Pseudoklasse um die Optionen des Userscript zu handeln. Braucht zum instanziieren keine weiteren
 * Parameter, ruft direkt die _init() Methode auf um die Settings auszulesen.
 */

function Options() {
    /**
     * Name der benutzt wird um die Optionen im Localstorage zu speichern.
     * @type {string}
     */
    var LOCALSTORAGE_NAME = 'userscriptOptions';

    /**
     * Name der benutzt wird um das Backup der Optionen im Localstorage zu speichern.
     * @type {string}
     */
    var LOCALSTORAGE_NAME_BACKUP = 'userscriptOptionsBackup';

    /**
     * Um das Object aus privaten Methoden heraus ansprechen zu können.
     * @type {Options}
     * @private
     */
    var _self = this;

    /**
     * Version des Scriptes, wird durch GrundJS im Buildprozess ersetzt.
     * @type {string}
     * @private
     */
    var _version = '{{version}}';

    /**
     * Property für die Optionen. Werden über entsprechende Methoden abgefragt, sind daher
     * nicht mehr public.
     * @type {{}}
     * @private
     */
    var _options = {};

    /**
     * Missbrauche diese Funktion einfach mal als Konstuktor-Ersatz. Finde es einfach schöner
     * dafür eine separate Methode zu haben. Wird als letzte Zeile ausgerufen.     *
     * @private
     */
    var _init = function() {
        // Optionen aus dem Localstorage auslesen
        _readOptionsFromLocalstorage();
    };

    /**
     * Funktion um den Wert einer bestimmten Option zurückzugeben.
     * @param {String} what
     */
    this.getOption = function(what) {
        return _options[what];
    };

    /**
     * Gibt die aktuelle Version zurück.
     * @returns {string}
     */
    this.getVersion = function() {
        return _version;
    };

    /**
     * Fügt zuerst den Quellcode der Optionen in die Seite ein, danach den Link / das Icon zum öffnen
     * und zuletzt die diversen Eventhandler.
     */
    this.insertOptions = function() {
        // Optionen einfügen
        $('body').append('{{optionshtml}}');

        // Link einfügen
        $('div#header li.socials').prepend('<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAABuklEQVR42n2Sv09TURTHvyCgQkKEgcWFhNHVEA1LYwQHEweSxjBgYFBjjLUlLZba99iMrDq5+S8wMTlJ0xAWSXTAxMGoiRJCE42WQvsOn3v7bEqbcJPzzrvnfL/n1z3S/5OxiwrsnsLog0I7RH/kfl2r1qesjemZXVHeRiTr0amzYBf03LKQKoANHUFc07Jd1YrN4nuDzOuRI7uzagNKENURA0uRac8TPTn6piB6T5Ad7jWv83ZXSThESWJ4rKLdB7TO/78WsVOcr2gvKX1IgLeIvI/xB/oPgAb/To6QWvwfeaLDFu0GGc+pK4MDFe0LlaTRd7hvenLTV8Z+rTmg0L52EOt+KK4cFzm020g19v1EXkFeFFFvAnyN41dMPsb2Qg9skMi9DGOmu6rotwD0M+4pX3/rGaJdyA9VsFvY3vkqTg+qJD2xYUAJLuUWsTmQGvdq3F/DZw2pKrAD8E+lnE34nkL7e+YzBNEGvaWQJeSylLZLftUCv2LuCT6hS8j3tp72fJYka+laa64dn4yNAlzA+ZZoc8hkXEU9zlih3xxtnVfXcUa3h36dCFawacifkW0CrjDd8fYFPwEUiX/TL0OkfAAAAABJRU5ErkJggg==" style="width: 14px;"/> | ');

        // Eventhandler
        _addEventHandler();
    };

    /**
     * Liest die Optionen aus dem HTML aus und speichert sie anschließend als JSON-String den Localstorage
     * des Browsers. Gibt im Fehlerfall eine einfache Meldung zurück.
     * @return {boolean}
     */
    this.saveOptions = function() {
        _readOptionsFromHTML();

        try {
            localStorage.setItem(LOCALSTORAGE_NAME, JSON.stringify(_options));
        } catch (e) {
            alert('Es ist ein Fehler beim Speichern aufgetreten: ' + e);
            return false;
        }

        return true;
    };

    /**
     * Funktion um die Optionen als JSON-String auszulesen und zurück zu geben. Wird für den
     * Export der Settings benutzt.
     * @returns {String}
     */
    this.getOptionsRaw = function() {
        return localStorage.getItem(LOCALSTORAGE_NAME);
    };

    /**
     * Funktion um die Optionen als JSON-String zu übergeben und in den Localstorage zu speichern. Wird für
     * den Import benutzt.
     * @param options {String}
     */
    this.setOptionsRaw = function(options) {
        localStorage.setItem(LOCALSTORAGE_NAME, options + "");
    };

    /**
     * Sichert die aktuelle Konfiguration für Backupzwecke. Falls ein Import schief geht kann so der alte
     * Stand wieder hergestellt werdne.
     */
    this.backupOptions = function() {
        localStorage.setItem(LOCALSTORAGE_NAME_BACKUP, localStorage.getItem(LOCALSTORAGE_NAME));
    };

    /**
     * Blendet das Fenster mit den Optionen ein.
     */
    this.showOptions = function() {
        _writeOptionsToHTML();

        $('div#userscriptOptionsOverlay').css('height', $(document).height()).fadeIn(200, function() {
            // Reset scroll
            $('div#userscriptOptions div.rmus-options-content').animate({
                scrollTop: 0
            }, 50);

            // Im-/Export ausblenden
            $('div#rmus-options-imexport').hide();
            $('div#userscriptOptions').fadeIn(250);
        });
    };

    /**
     * Schließt das Fenster mit den Optionen.
     */
    this.hideOptions = function() {
        $('div#userscriptOptions').fadeOut(250, function() {
            $('div#userscriptOptionsOverlay').fadeOut(200);
        });

        _readOptionsFromLocalstorage();
    };

    /**
     * Private Methode um die aktuellen Einstellung aus den Input-Feldern der Optionen auszulesen
     * und in das entsprechende Property zu schreiben.     *
     * @private
     */
    var _readOptionsFromHTML = function() {
        var userscriptOptions = {};

        // Geht alle Checkboxen durch, prüft ob die Box gechecked ist und setzt den passenden
        // Wert in den Optionen.
        $('input[type=checkbox].userscriptOptions').each(function() {
            var attr = $(this).prop('checked');
            if (attr === true) {
                userscriptOptions[$(this).attr('name')] = 'checked';
            } else {
                userscriptOptions[$(this).attr('name')] = false;
            }
        });

        // Liest den Wert der Textfelder aus.
        $('input.userscriptOptions[type!=checkbox]').each(function() {
            userscriptOptions[$(this).attr('name')] = $(this).val();
        });

        // Liest den Wert der Selects aus.
        $('select.userscriptOptions').each(function() {
            userscriptOptions[$(this).attr('name')] = $(this).val();
        });

        // Nachdem alle Daten eingelesen wurden, werden die Settings in dem Attribut gesichert.
        _options = userscriptOptions;
    };

    /**
     * Liest den JSON-String aus dem Localstorage aus und setzt das passende
     * Attribut der Pseudoklasse. Wird im Konstruktor aufgerunden, sollte also stets verfügbar sein.     *
     * @private
     */
    var _readOptionsFromLocalstorage = function() {
        // JSON-String aus dem Localstorage auslesen und wieder in ein Objekt umwandeln
        _options = JSON.parse(localStorage.getItem(LOCALSTORAGE_NAME));

        if (_options == null) {
            _options = {};
        }
    };

    /**
     * Private Methode um die Inputboxen im Menu zu setzen. Die Optionen werden aus dem
     * Attribut entnommen.
     * @private
     */
    var _writeOptionsToHTML = function() {
        var type = '';

        if (!$.isEmptyObject(_options)) {
            $.each(_options, function(index, value) {
                // Typ der Option bestimmen
                type = $('[name=' + index + ']').attr('type');

                // Checkboxen auslesen und den Wert zuweisen
                if (type == 'checkbox') {
                    // Checkboxen setzen
                    if (value == 'checked') {
                        $('[name=' + index + ']').attr('checked', true);
                    } else {
                        $('[name=' + index + ']').attr('checked', false);
                    }
                    return true;
                }

                // Textfelder auslesen und den Wert zuweisen
                if (type == 'text' || type == null) {
                    $('[name=' + index + ']').val(value);
                    return true;
                }

                // Selectboxen auslesen und den Wert zuweisen
                if (index.match('rightColumn_forum_hideForum_') != null) {
                    $('[name=' + index + ']').val(value);
                    return true;
                }
            });
        }
    };

    /**
     * Fügt die Eventhandler hinzu
     * @private
     */
    var _addEventHandler = function() {

        // Eventhandler für die Optionen setzen
        $('#saveUserscriptOptions').click(function() {
            if (_self.saveOptions()) {
                _self.hideOptions();
            }
        });

        $('#openUserscriptOptions').click(function(e) {
            e.preventDefault();
            _self.showOptions();
        });

        $('#closeUserscriptOptions,#userscriptOptionsOverlay').click(function(e) {
            e.preventDefault();
            _self.hideOptions();
        });

        $('div#userscriptOptions input.imexp').click(function() {
            var $this = $(this),
                imexportContainer = $('div#rmus-options-imexport'),
                importBtn = $('div#rmus-options-imexport input#importUserscriptOptionsBtn'),
                helpContainer = $('div.rmus-options-imexport-help', imexportContainer),
                textarea = $('textarea', imexportContainer);

            imexportContainer.hide();

            if ($this.prop('id') === 'importUserscriptOptions') {
                importBtn.show();
                helpContainer.text('Füge den exportierten JSON-String in das Textfeld ein:');
                textarea.val('');
                textarea.focus();
            } else {
                var opts = _self.getOptionsRaw();

                if (opts === 'null') {
                    alert('Es sind keine gespeicherten Optionen zum Exportieren vorhanden. Bitte speichere deine Optionen zuerst ab.');
                    return;
                }

                importBtn.hide();
                helpContainer.text('Kopiere diesen JSON-String für den späteren Import:');
                textarea.val(opts);
            }

            imexportContainer.slideToggle(250, function() {
                textarea.select();
            });
        });
        $('#imexportUserscriptOptionsCloseBtn').click(function() {
            $('div#rmus-options-imexport').hide();
        });
        $('#importUserscriptOptionsBtn').click(function() {
            var opts = $('div#rmus-options-imexport textarea').val(),
                validJson = true;

            try {
                JSON.parse(opts);
            } catch (e) {
                validJson = false;
            }

            if (validJson) {
                _self.backupOptions();
                _self.setOptionsRaw(opts);

                // Optionen schließen, damit sie neu geladen werden beim nächsten öffnen.
                _self.hideOptions();

                alert('Die Optionen wurden erfolgreich importiert! Du musst die Seite neu laden, damit die Optionen vollständig übernommen werden.');
            } else {
                alert('Die Optionen konnten nicht importiert werden! Der eingegebene Text ist kein valider JSON-String.');
            }
        });

        var plusLink = "http://readmore.thextor.de/userscript/img/plus_alt_16x16.png",
            minusLink = "http://readmore.thextor.de/userscript/img/minus_alt_16x16.png";

        // Eventhandler für die +/- Buttons
        $('[id*=toggle_sub]').click(function() {
            var img = $(this).attr('src').trim();

            if (img == plusLink) {
                $(this).attr('src', minusLink);
            } else {
                $(this).attr('src', plusLink);
            }
        });

        // Auf- und zuklappen der Unterkategorien
        $('#toggle_sub_middleColumn_forum_reloadPosts_readNewPosts').click(function() {
            $('.sub_middleColumn_forum_reloadPosts_readNewPosts').toggle();
            $('.sub_middleColumn_forum_reloadPosts_markNewPosts').css('display', 'none');
            $('.sub_middleColumn_forum_reloadPosts_endlessPage').css('display', 'none');
            $("#toggle_sub_middleColumn_forum_reloadPosts_endlessPage").attr("src", plusLink);
            $("#toggle_sub_middleColumn_forum_reloadPosts_markNewPosts").attr("src", plusLink);
        });
        $('#toggle_sub_middleColumn_forum_reloadPosts_endlessPage').click(function() {
            $('.sub_middleColumn_forum_reloadPosts_endlessPage').toggle();
        });
        $('#toggle_sub_middleColumn_forum_reloadPosts_markNewPosts').click(function() {
            $('.sub_middleColumn_forum_reloadPosts_markNewPosts').toggle();
        });
        $('#toggle_sub_rightColumn_headlines_hideHeadlines').click(function() {
            $('.sub_rightColumn_headlines_hideHeadlines').toggle();
        });
        $('#toggle_sub_rightColumn_forum_hideForum').click(function() {
            $('.sub_rightColumn_forum_hideForum').toggle();
            $('.sub_rightColumn_forum_sections').css('display', 'none');
        });
        $('#toggle_sub_rightColumn_forum_sections').click(function() {
            $('.sub_rightColumn_forum_sections').toggle();
        });
        $('#toggle_sub_miscellaneous_reloadMessages').click(function() {
            $('.sub_miscellaneous_reloadMessages').toggle();
        });
        $('#toggle_sub_miscellaneous_ignoreUser').click(function() {
            $('.sub_miscellaneous_ignoreUser').toggle();
        });
        $('#toggle_sub_middleColumn_forum_scrollForNewPage').click(function() {
            $('.sub_middleColumn_forum_scrollForNewPage').toggle();
        });
    };

    /**
     * Init Methode aufrufen!
     */
    _init();
}
<commit_msg>BUGFIX: Options icon got blocked by adblock (antisocial list)<commit_after>/**
 * Options
 * =======
 *
 * Pseudoklasse um die Optionen des Userscript zu handeln. Braucht zum instanziieren keine weiteren
 * Parameter, ruft direkt die _init() Methode auf um die Settings auszulesen.
 */

function Options() {
    /**
     * Name der benutzt wird um die Optionen im Localstorage zu speichern.
     * @type {string}
     */
    var LOCALSTORAGE_NAME = 'userscriptOptions';

    /**
     * Name der benutzt wird um das Backup der Optionen im Localstorage zu speichern.
     * @type {string}
     */
    var LOCALSTORAGE_NAME_BACKUP = 'userscriptOptionsBackup';

    /**
     * Um das Object aus privaten Methoden heraus ansprechen zu können.
     * @type {Options}
     * @private
     */
    var _self = this;

    /**
     * Version des Scriptes, wird durch GrundJS im Buildprozess ersetzt.
     * @type {string}
     * @private
     */
    var _version = '{{version}}';

    /**
     * Property für die Optionen. Werden über entsprechende Methoden abgefragt, sind daher
     * nicht mehr public.
     * @type {{}}
     * @private
     */
    var _options = {};

    /**
     * Missbrauche diese Funktion einfach mal als Konstuktor-Ersatz. Finde es einfach schöner
     * dafür eine separate Methode zu haben. Wird als letzte Zeile ausgerufen.     *
     * @private
     */
    var _init = function() {
        // Optionen aus dem Localstorage auslesen
        _readOptionsFromLocalstorage();
    };

    /**
     * Funktion um den Wert einer bestimmten Option zurückzugeben.
     * @param {String} what
     */
    this.getOption = function(what) {
        return _options[what];
    };

    /**
     * Gibt die aktuelle Version zurück.
     * @returns {string}
     */
    this.getVersion = function() {
        return _version;
    };

    /**
     * Fügt zuerst den Quellcode der Optionen in die Seite ein, danach den Link / das Icon zum öffnen
     * und zuletzt die diversen Eventhandler.
     */
    this.insertOptions = function() {
        // Optionen einfügen
        $('body').append('{{optionshtml}}');

        // Link einfügen
        $('div#header li.ucp').after('
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAABuklEQVR42n2Sv09TURTHvyCgQkKEgcWFhNHVEA1LYwQHEweSxjBgYFBjjLUlLZba99iMrDq5+S8wMTlJ0xAWSXTAxMGoiRJCE42WQvsOn3v7bEqbcJPzzrvnfL/n1z3S/5OxiwrsnsLog0I7RH/kfl2r1qesjemZXVHeRiTr0amzYBf03LKQKoANHUFc07Jd1YrN4nuDzOuRI7uzagNKENURA0uRac8TPTn6piB6T5Ad7jWv83ZXSThESWJ4rKLdB7TO/78WsVOcr2gvKX1IgLeIvI/xB/oPgAb/To6QWvwfeaLDFu0GGc+pK4MDFe0LlaTRd7hvenLTV8Z+rTmg0L52EOt+KK4cFzm020g19v1EXkFeFFFvAnyN41dMPsb2Qg9skMi9DGOmu6rotwD0M+4pX3/rGaJdyA9VsFvY3vkqTg+qJD2xYUAJLuUWsTmQGvdq3F/DZw2pKrAD8E+lnE34nkL7e+YzBNEGvaWQJeSylLZLftUCv2LuCT6hS8j3tp72fJYka+laa64dn4yNAlzA+ZZoc8hkXEU9zlih3xxtnVfXcUa3h36dCFawacifkW0CrjDd8fYFPwEUiX/TL0OkfAAAAABJRU5ErkJggg==" style="width: 15px; margin-top: 4px;"/> |
');
        $('div#header li.socials').css('margin-left', '0px');

        // Eventhandler
        _addEventHandler();
    };

    /**
     * Liest die Optionen aus dem HTML aus und speichert sie anschließend als JSON-String den Localstorage
     * des Browsers. Gibt im Fehlerfall eine einfache Meldung zurück.
     * @return {boolean}
     */
    this.saveOptions = function() {
        _readOptionsFromHTML();

        try {
            localStorage.setItem(LOCALSTORAGE_NAME, JSON.stringify(_options));
        } catch (e) {
            alert('Es ist ein Fehler beim Speichern aufgetreten: ' + e);
            return false;
        }

        return true;
    };

    /**
     * Funktion um die Optionen als JSON-String auszulesen und zurück zu geben. Wird für den
     * Export der Settings benutzt.
     * @returns {String}
     */
    this.getOptionsRaw = function() {
        return localStorage.getItem(LOCALSTORAGE_NAME);
    };

    /**
     * Funktion um die Optionen als JSON-String zu übergeben und in den Localstorage zu speichern. Wird für
     * den Import benutzt.
     * @param options {String}
     */
    this.setOptionsRaw = function(options) {
        localStorage.setItem(LOCALSTORAGE_NAME, options + "");
    };

    /**
     * Sichert die aktuelle Konfiguration für Backupzwecke. Falls ein Import schief geht kann so der alte
     * Stand wieder hergestellt werdne.
     */
    this.backupOptions = function() {
        localStorage.setItem(LOCALSTORAGE_NAME_BACKUP, localStorage.getItem(LOCALSTORAGE_NAME));
    };

    /**
     * Blendet das Fenster mit den Optionen ein.
     */
    this.showOptions = function() {
        _writeOptionsToHTML();

        $('div#userscriptOptionsOverlay').css('height', $(document).height()).fadeIn(200, function() {
            // Reset scroll
            $('div#userscriptOptions div.rmus-options-content').animate({
                scrollTop: 0
            }, 50);

            // Im-/Export ausblenden
            $('div#rmus-options-imexport').hide();
            $('div#userscriptOptions').fadeIn(250);
        });
    };

    /**
     * Schließt das Fenster mit den Optionen.
     */
    this.hideOptions = function() {
        $('div#userscriptOptions').fadeOut(250, function() {
            $('div#userscriptOptionsOverlay').fadeOut(200);
        });

        _readOptionsFromLocalstorage();
    };

    /**
     * Private Methode um die aktuellen Einstellung aus den Input-Feldern der Optionen auszulesen
     * und in das entsprechende Property zu schreiben.     *
     * @private
     */
    var _readOptionsFromHTML = function() {
        var userscriptOptions = {};

        // Geht alle Checkboxen durch, prüft ob die Box gechecked ist und setzt den passenden
        // Wert in den Optionen.
        $('input[type=checkbox].userscriptOptions').each(function() {
            var attr = $(this).prop('checked');
            if (attr === true) {
                userscriptOptions[$(this).attr('name')] = 'checked';
            } else {
                userscriptOptions[$(this).attr('name')] = false;
            }
        });

        // Liest den Wert der Textfelder aus.
        $('input.userscriptOptions[type!=checkbox]').each(function() {
            userscriptOptions[$(this).attr('name')] = $(this).val();
        });

        // Liest den Wert der Selects aus.
        $('select.userscriptOptions').each(function() {
            userscriptOptions[$(this).attr('name')] = $(this).val();
        });

        // Nachdem alle Daten eingelesen wurden, werden die Settings in dem Attribut gesichert.
        _options = userscriptOptions;
    };

    /**
     * Liest den JSON-String aus dem Localstorage aus und setzt das passende
     * Attribut der Pseudoklasse. Wird im Konstruktor aufgerunden, sollte also stets verfügbar sein.     *
     * @private
     */
    var _readOptionsFromLocalstorage = function() {
        // JSON-String aus dem Localstorage auslesen und wieder in ein Objekt umwandeln
        _options = JSON.parse(localStorage.getItem(LOCALSTORAGE_NAME));

        if (_options == null) {
            _options = {};
        }
    };

    /**
     * Private Methode um die Inputboxen im Menu zu setzen. Die Optionen werden aus dem
     * Attribut entnommen.
     * @private
     */
    var _writeOptionsToHTML = function() {
        var type = '';

        if (!$.isEmptyObject(_options)) {
            $.each(_options, function(index, value) {
                // Typ der Option bestimmen
                type = $('[name=' + index + ']').attr('type');

                // Checkboxen auslesen und den Wert zuweisen
                if (type == 'checkbox') {
                    // Checkboxen setzen
                    if (value == 'checked') {
                        $('[name=' + index + ']').attr('checked', true);
                    } else {
                        $('[name=' + index + ']').attr('checked', false);
                    }
                    return true;
                }

                // Textfelder auslesen und den Wert zuweisen
                if (type == 'text' || type == null) {
                    $('[name=' + index + ']').val(value);
                    return true;
                }

                // Selectboxen auslesen und den Wert zuweisen
                if (index.match('rightColumn_forum_hideForum_') != null) {
                    $('[name=' + index + ']').val(value);
                    return true;
                }
            });
        }
    };

    /**
     * Fügt die Eventhandler hinzu
     * @private
     */
    var _addEventHandler = function() {

        // Eventhandler für die Optionen setzen
        $('#saveUserscriptOptions').click(function() {
            if (_self.saveOptions()) {
                _self.hideOptions();
            }
        });

        $('#openUserscriptOptions').click(function(e) {
            e.preventDefault();
            _self.showOptions();
        });

        $('#closeUserscriptOptions,#userscriptOptionsOverlay').click(function(e) {
            e.preventDefault();
            _self.hideOptions();
        });

        $('div#userscriptOptions input.imexp').click(function() {
            var $this = $(this),
                imexportContainer = $('div#rmus-options-imexport'),
                importBtn = $('div#rmus-options-imexport input#importUserscriptOptionsBtn'),
                helpContainer = $('div.rmus-options-imexport-help', imexportContainer),
                textarea = $('textarea', imexportContainer);

            imexportContainer.hide();

            if ($this.prop('id') === 'importUserscriptOptions') {
                importBtn.show();
                helpContainer.text('Füge den exportierten JSON-String in das Textfeld ein:');
                textarea.val('');
                textarea.focus();
            } else {
                var opts = _self.getOptionsRaw();

                if (opts === 'null') {
                    alert('Es sind keine gespeicherten Optionen zum Exportieren vorhanden. Bitte speichere deine Optionen zuerst ab.');
                    return;
                }

                importBtn.hide();
                helpContainer.text('Kopiere diesen JSON-String für den späteren Import:');
                textarea.val(opts);
            }

            imexportContainer.slideToggle(250, function() {
                textarea.select();
            });
        });
        $('#imexportUserscriptOptionsCloseBtn').click(function() {
            $('div#rmus-options-imexport').hide();
        });
        $('#importUserscriptOptionsBtn').click(function() {
            var opts = $('div#rmus-options-imexport textarea').val(),
                validJson = true;

            try {
                JSON.parse(opts);
            } catch (e) {
                validJson = false;
            }

            if (validJson) {
                _self.backupOptions();
                _self.setOptionsRaw(opts);

                // Optionen schließen, damit sie neu geladen werden beim nächsten öffnen.
                _self.hideOptions();

                alert('Die Optionen wurden erfolgreich importiert! Du musst die Seite neu laden, damit die Optionen vollständig übernommen werden.');
            } else {
                alert('Die Optionen konnten nicht importiert werden! Der eingegebene Text ist kein valider JSON-String.');
            }
        });

        var plusLink = "http://readmore.thextor.de/userscript/img/plus_alt_16x16.png",
            minusLink = "http://readmore.thextor.de/userscript/img/minus_alt_16x16.png";

        // Eventhandler für die +/- Buttons
        $('[id*=toggle_sub]').click(function() {
            var img = $(this).attr('src').trim();

            if (img == plusLink) {
                $(this).attr('src', minusLink);
            } else {
                $(this).attr('src', plusLink);
            }
        });

        // Auf- und zuklappen der Unterkategorien
        $('#toggle_sub_middleColumn_forum_reloadPosts_readNewPosts').click(function() {
            $('.sub_middleColumn_forum_reloadPosts_readNewPosts').toggle();
            $('.sub_middleColumn_forum_reloadPosts_markNewPosts').css('display', 'none');
            $('.sub_middleColumn_forum_reloadPosts_endlessPage').css('display', 'none');
            $("#toggle_sub_middleColumn_forum_reloadPosts_endlessPage").attr("src", plusLink);
            $("#toggle_sub_middleColumn_forum_reloadPosts_markNewPosts").attr("src", plusLink);
        });
        $('#toggle_sub_middleColumn_forum_reloadPosts_endlessPage').click(function() {
            $('.sub_middleColumn_forum_reloadPosts_endlessPage').toggle();
        });
        $('#toggle_sub_middleColumn_forum_reloadPosts_markNewPosts').click(function() {
            $('.sub_middleColumn_forum_reloadPosts_markNewPosts').toggle();
        });
        $('#toggle_sub_rightColumn_headlines_hideHeadlines').click(function() {
            $('.sub_rightColumn_headlines_hideHeadlines').toggle();
        });
        $('#toggle_sub_rightColumn_forum_hideForum').click(function() {
            $('.sub_rightColumn_forum_hideForum').toggle();
            $('.sub_rightColumn_forum_sections').css('display', 'none');
        });
        $('#toggle_sub_rightColumn_forum_sections').click(function() {
            $('.sub_rightColumn_forum_sections').toggle();
        });
        $('#toggle_sub_miscellaneous_reloadMessages').click(function() {
            $('.sub_miscellaneous_reloadMessages').toggle();
        });
        $('#toggle_sub_miscellaneous_ignoreUser').click(function() {
            $('.sub_miscellaneous_ignoreUser').toggle();
        });
        $('#toggle_sub_middleColumn_forum_scrollForNewPage').click(function() {
            $('.sub_middleColumn_forum_scrollForNewPage').toggle();
        });
    };

    /**
     * Init Methode aufrufen!
     */
    _init();
}
<|endoftext|>


<commit_before>import {PHPTypeUnion, PHPSimpleType} from "./phptype"
import * as PHPError from "./php-error"
import {FileContext} from "./file-context"
import * as ParserStateOption from "./parser-state-option"

/**
 * Defines content in a specific class
 */
class ClassContext {
    /**
     * Builds the object
     * @param {string} name Fully qualified only
     * @param {?ClassContext} [superclass]
     * @param {?FileContext} [file_context]
     */
    constructor(name, superclass = null, file_context = null) {
        this.name = name
        this.fileContext = file_context
        this.staticIdentifiers = {}
        this.instanceIdentifiers = {}
        this.superclass = superclass
    }

    /**
     * Adds a known identifier
     * @param {string} name
     * @param {string} scope "public", "private" or "protected"
     * @param {PHPTypeUnion} types
     * @param {boolean} is_static
     */
    addIdentifier(name, scope, is_static, types) {
        if(is_static) {
            this.staticIdentifiers[name] = {
                scope: scope,
                types: types,
            }
        } else {
            this.instanceIdentifiers[name.replace(/^[$]/, "")] = {
                scope: scope,
                types: types,
            }
        }
    }
    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @param {Set<ParserStateOption.Base>} [parser_state]
     * @returns {?PHPTypeUnion}
     */
    findInstanceIdentifier(name, from_class_context, parser_state = new Set()) {
        let m = this.instanceIdentifiers[name]
        let wrong_case
        if(m) {
            if(
                m.scope == "public" ||
                (
                    m.scope == "protected" &&
                    from_class_context.isSubclassOf(this)
                ) ||
                from_class_context === this
            ) {
                return m.types
            } else {
                throw new PHPError.ScopeMiss(
                    `Scope miss for name ${name} with scope ${m.scope} ($this instanceof ${from_class_context.name})`
                )
            }
            // TODO inheritance
        } else if(wrong_case = Object.keys(this.instanceIdentifiers).find(n => n.toLowerCase() == name.toLowerCase())) {
            console.log(`Wrong case for identifier, ${name} != ${wrong_case}`)
            this.instanceIdentifiers[name] = this.instanceIdentifiers[wrong_case]
            return this.findInstanceIdentifier(wrong_case, from_class_context)
        } else if(this.superclass) {
            let superclass_types = this.superclass.findInstanceIdentifier(name, from_class_context)
            if(superclass_types) {
                return superclass_types
            }
        }
        if(parser_state.has(ParserStateOption.InCall) && name != "__call") {
            return this.findInstanceIdentifier("__call", from_class_context, new Set([ParserStateOption.InCall]))
        } else if(!parser_state.has(ParserStateOption.InCall) && name != "__get") {
            if(this.findInstanceIdentifier("__get", from_class_context, new Set([ParserStateOption.InCall]))) {
                return PHPSimpleType.coreTypes.mixed
            }
        }
        return null
    }
    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findStaticIdentifier(name, from_class_context) {
        let m = this.staticIdentifiers[name]
        let wrong_case
        if(m) {
            if(from_class_context === this || m.scope == "public") {
                return m.types
            }
        } else if(wrong_case = Object.keys(this.staticIdentifiers).find(n => n.toLowerCase() == name.toLowerCase())) {
            console.log(`Wrong case for identifier, ${name} != ${wrong_case}`)
            this.staticIdentifiers[name] = this.staticIdentifiers[wrong_case]
            return this.findStaticIdentifier(wrong_case, from_class_context)
        } else if(this.superclass) {
            let superclass_types = this.superclass.findStaticIdentifier(name, from_class_context)
            if(superclass_types) {
                return superclass_types
            }
        }
        return null
    }

    /**
     * Imports a trait into the current context
     *
     * @param {ClassContext} trait
     */
    importTrait(trait) {
        for(var k in trait.staticIdentifiers) {
            this.staticIdentifiers[k] = trait.staticIdentifiers[k]
        }
        for(var k in trait.instanceIdentifiers) {
            this.instanceIdentifiers[k] = trait.instanceIdentifiers[k]
        }
    }

    /**
     * Returns true if this is a subclass of that class.
     * @param {ClassContext} other_class
     * @returns {boolean}
     */
    isSubclassOf(other_class) {
        if(this.superclass) {
            return(
                this.superclass === other_class ||
                this.superclass.isSubclassOf(other_class)
            )
        } else {
            return false
        }
    }

    /**
     * The fully resolved name. This just handles "parent", "self" and "static";
     * everything else returns null.
     *
     * @param {string} name
     * @throws {PHPError.Error} when using "parent" with no superclass
     * @returns {?string}
     */
    resolveName(name) {
        if(name == "parent") {
            if(this.superclass) {
                return this.superclass.name
            } else {
                throw new PHPError.NoSuperclassParent()
            }
        } else if(name == "self") {
            return this.name
        } else if(name == "static") {
            return this.name
        } else {
            return null
        }
    }
}

/**
 * This handles interfaces
 */
class InterfaceContext extends ClassContext {
    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findInstanceIdentifier(name, from_class_context) {
        return super.findInstanceIdentifier(name, from_class_context) || PHPSimpleType.coreTypes.mixed
    }

    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findStaticIdentifier(name, from_class_context) {
        return super.findStaticIdentifier(name, from_class_context) || PHPSimpleType.coreTypes.mixed
    }
}

/**
 * This handles traits
 */
class TraitContext extends ClassContext {
    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findInstanceIdentifier(name, from_class_context) {
        return super.findInstanceIdentifier(name, from_class_context) || PHPSimpleType.coreTypes.mixed
    }

    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findStaticIdentifier(name, from_class_context) {
        return super.findStaticIdentifier(name, from_class_context) || PHPSimpleType.coreTypes.mixed
    }
}

/**
 * This handles functions when accessed as objects.
 */
class AnonymousFunctionContext extends ClassContext {
    static get inst() {
        if(!this._inst) {
            this._inst = new AnonymousFunctionContext()
        }
        return this._inst
    }
    /**
     * Builds the object
     */
    constructor() {
        super("() -> ()") // Just something that looks functiony.
    }

    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findInstanceIdentifier(name, from_class_context) {
        // TODO: Limit to the actual methods.
        return PHPSimpleType.coreTypes.mixed
    }

    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findStaticIdentifier(name, from_class_context) {
        return null
    }
}

/**
 * This handles unknown classes
 */
class UnknownClassContext extends ClassContext {
    /**
     * Builds the object
     * @param {string} name Fully qualified only
     */
    constructor(name, superclass = null) {
        super(name)
    }

    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findInstanceIdentifier(name, from_class_context) {
        return PHPSimpleType.coreTypes.mixed
    }

    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findStaticIdentifier(name, from_class_context) {
        return PHPSimpleType.coreTypes.mixed
    }
}

export {AnonymousFunctionContext, ClassContext, InterfaceContext, TraitContext, UnknownClassContext}<commit_msg>Clearer control flow<commit_after>import {PHPTypeUnion, PHPSimpleType} from "./phptype"
import * as PHPError from "./php-error"
import {FileContext} from "./file-context"
import * as ParserStateOption from "./parser-state-option"

/**
 * Defines content in a specific class
 */
class ClassContext {
    /**
     * Builds the object
     * @param {string} name Fully qualified only
     * @param {?ClassContext} [superclass]
     * @param {?FileContext} [file_context]
     */
    constructor(name, superclass = null, file_context = null) {
        this.name = name
        this.fileContext = file_context
        this.staticIdentifiers = {}
        this.instanceIdentifiers = {}
        this.superclass = superclass
    }

    /**
     * Adds a known identifier
     * @param {string} name
     * @param {string} scope "public", "private" or "protected"
     * @param {PHPTypeUnion} types
     * @param {boolean} is_static
     */
    addIdentifier(name, scope, is_static, types) {
        if(is_static) {
            this.staticIdentifiers[name] = {
                scope: scope,
                types: types,
            }
        } else {
            this.instanceIdentifiers[name.replace(/^[$]/, "")] = {
                scope: scope,
                types: types,
            }
        }
    }
    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @param {Set<ParserStateOption.Base>} [parser_state]
     * @returns {?PHPTypeUnion}
     */
    findInstanceIdentifier(name, from_class_context, parser_state = new Set()) {
        let m = this.instanceIdentifiers[name]
        let wrong_case
        if(m) {
            if(
                m.scope == "public" ||
                (
                    m.scope == "protected" &&
                    from_class_context.isSubclassOf(this)
                ) ||
                from_class_context === this
            ) {
                return m.types
            } else {
                throw new PHPError.ScopeMiss(
                    `Scope miss for name ${name} with scope ${m.scope} ($this instanceof ${from_class_context.name})`
                )
            }
            // TODO inheritance
        } else if(wrong_case = Object.keys(this.instanceIdentifiers).find(n => n.toLowerCase() == name.toLowerCase())) {
            console.log(`Wrong case for identifier, ${name} != ${wrong_case}`)
            this.instanceIdentifiers[name] = this.instanceIdentifiers[wrong_case]
            return this.findInstanceIdentifier(wrong_case, from_class_context)
        } else if(this.superclass) {
            let superclass_types = this.superclass.findInstanceIdentifier(name, from_class_context)
            if(superclass_types) {
                return superclass_types
            }
        }
        if(parser_state.has(ParserStateOption.InCall) && name != "__call") {
            return this.findInstanceIdentifier("__call", from_class_context, new Set([ParserStateOption.InCall]))
        } else if(!parser_state.has(ParserStateOption.InCall) && name != "__get") {
            if(this.findInstanceIdentifier("__get", from_class_context, new Set([ParserStateOption.InCall]))) {
                return PHPSimpleType.coreTypes.mixed
            }
        }
        return null
    }
    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findStaticIdentifier(name, from_class_context) {
        let m = this.staticIdentifiers[name]
        let wrong_case
        if(m) {
            if(from_class_context === this || m.scope == "public") {
                return m.types
            }
        } else if(wrong_case = Object.keys(this.staticIdentifiers).find(n => n.toLowerCase() == name.toLowerCase())) {
            console.log(`Wrong case for identifier, ${name} != ${wrong_case}`)
            this.staticIdentifiers[name] = this.staticIdentifiers[wrong_case]
            return this.findStaticIdentifier(wrong_case, from_class_context)
        } else if(this.superclass) {
            let superclass_types = this.superclass.findStaticIdentifier(name, from_class_context)
            if(superclass_types) {
                return superclass_types
            }
        } else {
            return null
        }
    }

    /**
     * Imports a trait into the current context
     *
     * @param {ClassContext} trait
     */
    importTrait(trait) {
        for(var k in trait.staticIdentifiers) {
            this.staticIdentifiers[k] = trait.staticIdentifiers[k]
        }
        for(var k in trait.instanceIdentifiers) {
            this.instanceIdentifiers[k] = trait.instanceIdentifiers[k]
        }
    }

    /**
     * Returns true if this is a subclass of that class.
     * @param {ClassContext} other_class
     * @returns {boolean}
     */
    isSubclassOf(other_class) {
        if(this.superclass) {
            return(
                this.superclass === other_class ||
                this.superclass.isSubclassOf(other_class)
            )
        } else {
            return false
        }
    }

    /**
     * The fully resolved name. This just handles "parent", "self" and "static";
     * everything else returns null.
     *
     * @param {string} name
     * @throws {PHPError.Error} when using "parent" with no superclass
     * @returns {?string}
     */
    resolveName(name) {
        if(name == "parent") {
            if(this.superclass) {
                return this.superclass.name
            } else {
                throw new PHPError.NoSuperclassParent()
            }
        } else if(name == "self") {
            return this.name
        } else if(name == "static") {
            return this.name
        } else {
            return null
        }
    }
}

/**
 * This handles interfaces
 */
class InterfaceContext extends ClassContext {
    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findInstanceIdentifier(name, from_class_context) {
        return super.findInstanceIdentifier(name, from_class_context) || PHPSimpleType.coreTypes.mixed
    }

    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findStaticIdentifier(name, from_class_context) {
        return super.findStaticIdentifier(name, from_class_context) || PHPSimpleType.coreTypes.mixed
    }
}

/**
 * This handles traits
 */
class TraitContext extends ClassContext {
    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findInstanceIdentifier(name, from_class_context) {
        return super.findInstanceIdentifier(name, from_class_context) || PHPSimpleType.coreTypes.mixed
    }

    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findStaticIdentifier(name, from_class_context) {
        return super.findStaticIdentifier(name, from_class_context) || PHPSimpleType.coreTypes.mixed
    }
}

/**
 * This handles functions when accessed as objects.
 */
class AnonymousFunctionContext extends ClassContext {
    static get inst() {
        if(!this._inst) {
            this._inst = new AnonymousFunctionContext()
        }
        return this._inst
    }
    /**
     * Builds the object
     */
    constructor() {
        super("() -> ()") // Just something that looks functiony.
    }

    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findInstanceIdentifier(name, from_class_context) {
        // TODO: Limit to the actual methods.
        return PHPSimpleType.coreTypes.mixed
    }

    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findStaticIdentifier(name, from_class_context) {
        return null
    }
}

/**
 * This handles unknown classes
 */
class UnknownClassContext extends ClassContext {
    /**
     * Builds the object
     * @param {string} name Fully qualified only
     */
    constructor(name, superclass = null) {
        super(name)
    }

    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findInstanceIdentifier(name, from_class_context) {
        return PHPSimpleType.coreTypes.mixed
    }

    /**
     * Finds the named identifier
     * @param {string} name
     * @param {?ClassContext} from_class_context
     * @returns {?PHPTypeUnion}
     */
    findStaticIdentifier(name, from_class_context) {
        return PHPSimpleType.coreTypes.mixed
    }
}

export {AnonymousFunctionContext, ClassContext, InterfaceContext, TraitContext, UnknownClassContext}<|endoftext|>