How to use ColumnToken method in Playwright Internal

Best JavaScript code snippet using playwright-internal

filtereditor.js

Source:filtereditor.js Github

copy

Full Screen

1"use strict";2// The filter editor and the whole filtering subsystem of the SuperTable. Contains two filtering3// interfaces: a mouse-driven "traditional" one, and a more advanced system that uses a simple4// scripting language. Only one can be active at any given moment, but both are compiled down to a5// simple RPN "program" that is executed for every row on the table to see if it's visible or not.6// The table itself does not know anything about this, it only sees the final program.7// --------------------------------------------------------------------------------------------------8// --------------------------------------------------------------------------------------------------9// COLUMN NAME LOOKUPS10/*11Each column can have zero or more (unique) alias names that all point to the actual name.12It's just a simple text replacement system. Some column names used in the raw JSON data13are very short and cryptic, so it's nice to have easy-to-remember aliases for them.14*/15class ColumnDefinitions {16 constructor(rawDefinitions)17 {18 this.columns = new Map();19 this.aliases = new Map();20 for (const key of Object.keys(rawDefinitions)) {21 const def = rawDefinitions[key];22 this.columns.set(key, {23 type: def.type,24 flags: def.flags || 0,25 });26 if ("alias" in def) {27 for (const a of def.alias) {28 // Each alias name must be unique. Explode loudly if they aren't.29 if (this.aliases.has(a))30 throw new Error(`Alias "${a}" used by columns "${this.aliases.get(a)}" and "${key}"`);31 this.aliases.set(a, key);32 }33 }34 }35 }36 isColumn(s)37 {38 return this.columns.has(s) || this.aliases.has(s);39 }40 // Expands an alias name. If the string isn't an alias, nothing happens.41 expandAlias(s)42 {43 if (!this.aliases.has(s))44 return s;45 return this.aliases.get(s);46 }47 // Retrieves a column definition by name. Bad things will happen if the name isn't valid.48 // (Use isColumn() to validate it first, and expandAlias() to get the full name.)49 get(s)50 {51 return this.columns.get(s);52 }53 getAliases(s)54 {55 let out = new Set();56 for (const a of this.aliases)57 if (a[1] == s)58 out.add(a[0]);59 return out;60 }61};62// A custom logger that also keeps track of row and column numbers and some other metadata63class MessageLogger {64 constructor()65 {66 this.messages = [];67 }68 empty()69 {70 return this.messages.length == 0;71 }72 haveErrors()73 {74 for (const m of this.messages)75 if (m.type == "error")76 return true;77 return false;78 }79 clear()80 {81 this.messages = [];82 }83 warn(message, row=-1, col=-1, pos=-1, len=-1, extra=null)84 {85 this.messages.push({86 type: "warning",87 message: message,88 row: row,89 col: col,90 pos: pos,91 len: len,92 extra: extra,93 });94 }95 warnToken(message, token, extra=null)96 {97 this.messages.push({98 type: "warning",99 message: message,100 row: token.row,101 col: token.col,102 pos: token.pos,103 len: token.len,104 extra: extra,105 });106 }107 error(message, row=-1, col=-1, pos=-1, len=-1, extra=null)108 {109 this.messages.push({110 type: "error",111 message: message,112 row: row,113 col: col,114 pos: pos,115 len: len,116 extra: extra,117 });118 }119 errorToken(message, token, extra=null)120 {121 this.messages.push({122 type: "error",123 message: message,124 row: token.row,125 col: token.col,126 pos: token.pos,127 len: token.len,128 extra: extra,129 });130 }131};132// --------------------------------------------------------------------------------------------------133// --------------------------------------------------------------------------------------------------134// TOKENIZATION135// Raw token types136const TokenType = {137 COLUMN: "col", // target column name138 OPERATOR: "opr", // comparison operator139 VALUE: "val", // comparison value (whether it's a string or number is irrelevant here)140 OPEN_PAREN: "(",141 CLOSE_PAREN: ")",142 BOOL_AND: "and",143 BOOL_OR: "or",144 BOOL_NOT: "not",145 // Used during parsing to initialize the state machine to a known state.146 // Using this anywhere else will hurt you badly.147 START: "start",148};149// Raw token flags, set by the tokenizer150const TokenFlags = {151 REGEXP: 0x01, // This token contains a regexp string152 MULTILINE: 0x02, // This string is multiline (can be used with regexps)153};154class Tokenizer {155 constructor()156 {157 this.columnDefinitions = null;158 this.logger = null;159 this.source = null;160 this.pos = 0; // character position in the input stream161 this.row = 1;162 this.col = 1;163 this.startPos = 0;164 this.startRow = 1;165 this.startCol = 1;166 this.tokens = [];167 }168 addToken(type, token, flags=0)169 {170 this.tokens.push({171 type: type,172 str: token,173 flags: flags,174 // These are used in error reporting. They're not needed for anything else.175 row: this.startRow,176 col: this.startCol,177 pos: this.startPos,178 len: this.pos - this.startPos,179 });180 }181 done()182 {183 return this.pos >= this.source.length;184 }185 peek()186 {187 if (this.done())188 return null;189 return this.source[this.pos];190 }191 read()192 {193 const c = this.source[this.pos++];194 if (c == "\n") {195 this.row++;196 this.col = 0;197 }198 if (c != "\r") {199 // Transparently treat \n\r (or \r\n) newlines as just \n200 this.col++;201 }202 return c;203 }204 match(expected)205 {206 if (this.done())207 return false;208 if (this.source[this.pos] != expected)209 return false;210 this.pos++;211 this.col++; // don't call match("\r") if you want the column number to be correct212 return true;213 }214 comment()215 {216 while (true) {217 if (this.peek() == "\n" || this.done())218 break;219 this.read();220 }221 }222 content(initial)223 {224 let quoted = false,225 escape = false,226 token = "",227 flags = 0;228 if (`"'/`.includes(initial)) {229 // We have three types of values/strings here: unquoted, quoted and regexp.230 // The only difference between quoted and regexp strings is the terminating231 // character. For unquoted strings, any unescaped whitespace or characters232 // that are actually part of the language syntax end the string.233 quoted = true;234 if (initial == "/")235 flags |= TokenFlags.REGEXP;236 } else {237 // Start the token with a non-quote character238 token += initial;239 }240 while (true) {241 if (this.done()) {242 if (quoted) {243 this.logger.error("unterminated_string",244 this.startRow, this.startCol, this.startPos, this.col - this.startCol);245 return true;246 }247 break;248 }249 // Can't use read() here, otherwise we can end up reading one character too far250 const c = this.peek();251 if (c == "\\") {252 escape = true;253 this.read();254 continue;255 }256 if (escape) {257 // Process escape codes258 switch (c) {259 case "n":260 token += "\n";261 flags |= TokenFlags.MULTILINE;262 break;263 case "r":264 token += "\r";265 break;266 case "t":267 token += "\t";268 break;269 default:270 // Unknown escape, pass it through as-is271 token += `\\${c}`;272 break;273 }274 } else {275 if (quoted) {276 // For quoted strings, the initial character also ends the string277 if (c == initial) {278 this.read();279 break;280 }281 if (c == "\n")282 flags |= TokenFlags.MULTILINE;283 } else if (" \n\r\t()&|<>=!#".includes(c)) {284 // Only these can end an unquoted string as they're part of the language itself285 break;286 }287 token += c;288 }289 this.read();290 escape = false;291 }292 if (escape) {293 // Unfinished escape sequence (+1 to skip the escape character)294 this.logger.error("unexpected_end",295 this.startRow, this.startCol, this.startPos, this.col - this.startCol + 1);296 return true;297 }298 // Quoted strings are hardcoded to always be values, but unquoted299 // strings are either values or column names300 if (!quoted && this.columnDefinitions.isColumn(token))301 this.addToken(TokenType.COLUMN, this.columnDefinitions.expandAlias(token), flags);302 else this.addToken(TokenType.VALUE, token, flags);303 return true;304 }305 token()306 {307 // Remember the starting position, so we'll know exact token locations and lengths308 this.startPos = this.pos;309 this.startRow = this.row;310 this.startCol = this.col;311 const c = this.read();312 switch (c) {313 case " ":314 case "\t":315 case "\n":316 case "\r":317 // Either already handled in read(), or we can skip these318 break;319 case "#":320 this.comment();321 break;322 case "=":323 // equality, permit "=" and "=="324 if (this.match("="))325 this.addToken(TokenType.OPERATOR, "=");326 else this.addToken(TokenType.OPERATOR, "=");327 break;328 case "!":329 // ! (unary negation), != (inequality) or !! (database field presence check)330 if (this.match("="))331 this.addToken(TokenType.OPERATOR, "!=");332 else if (this.match("!"))333 this.addToken(TokenType.OPERATOR, "!!");334 else this.addToken(TokenType.BOOL_NOT, "!");335 break;336 case "<":337 // < or <=338 if (this.match("="))339 this.addToken(TokenType.OPERATOR, "<=");340 else this.addToken(TokenType.OPERATOR, "<");341 break;342 case ">":343 // > or >=344 if (this.match("="))345 this.addToken(TokenType.OPERATOR, ">=");346 else this.addToken(TokenType.OPERATOR, ">");347 break;348 case "&":349 // && (and)350 if (this.match("&"))351 this.addToken(TokenType.BOOL_AND, "&&");352 else this.logger.error("unknown_operator", this.startRow, this.startCol, this.startPos, 2);353 break;354 case "|":355 // || (or)356 if (this.match("|"))357 this.addToken(TokenType.BOOL_OR, "||");358 else this.logger.error("unknown_operator", this.startRow, this.startCol, this.startPos, 2);359 break;360 case "(":361 this.addToken(TokenType.OPEN_PAREN, "(");362 break;363 case ")":364 this.addToken(TokenType.CLOSE_PAREN, ")");365 break;366 default:367 // Could be a column name or a string, or a number, etc.368 if (this.content(c))369 break;370 // I don't currently know any syntactical structure that could end up here371 this.logger.error("syntax_error", this.startRow, this.startCol, this.startPos, 1);372 break;373 }374 }375 tokenize(logger, columns, source)376 {377 this.columnDefinitions = columns;378 this.logger = logger;379 this.pos = 0;380 this.source = source;381 this.row = 1;382 this.col = 1;383 this.tokens = [];384 while (!this.done())385 this.token();386 }387};388// --------------------------------------------------------------------------------------------------389// --------------------------------------------------------------------------------------------------390// SYNTAX ANALYZER391/*392This isn't a recursive-descent parser. It just looks at the current token and the token before393it and makes all the decisions based on those. This parser is only used to validate the filter394string and move all comparisons into their own array. No AST is build. The remaining tokens395are stored in a new array that is then fed to the Shunting Yard algorithm.396*/397class Parser {398 constructor()399 {400 this.columnDefinitions = null;401 this.logger = null;402 this.tokens = [];403 this.previous = TokenType.START;404 this.pos = 0;405 this.comparisons = [];406 this.output = [];407 }408 prev()409 {410 for (let i = 0; i < arguments.length; i++)411 if (this.previous == arguments[i])412 return true;413 return false;414 }415 peek()416 {417 if (this.pos + 1 >= this.tokens.length)418 return false;419 const type = this.tokens[this.pos + 1].type;420 for (let i = 0; i < arguments.length; i++)421 if (type == arguments[i])422 return true;423 return false;424 }425 // Adds a new comparison or finds the existing one. Always returns an index426 // to a comparison you can use to look up whatever you passed to this.427 comparison(column, operator, value)428 {429 for (let i = 0; i < this.comparisons.length; i++) {430 const c = this.comparisons[i];431 if (c.column.str == column.str &&432 c.operator.str == operator.str &&433 c.value.str == value.str &&434 c.value.flags == value.flags)435 return i;436 }437 this.comparisons.push({438 column: column,439 operator: operator,440 value: value,441 });442 return this.comparisons.length - 1;443 }444 parse(logger, columns, tokens, lastRow, lastCol)445 {446 this.columnDefinitions = columns;447 this.logger = logger;448 this.tokens = [...tokens];449 this.previous = TokenType.START;450 this.pos = 0;451 this.output = [];452 this.comparisons = [];453 let errors = false,454 nesting = 0,455 columnIndex = -1,456 operatorIndex = -1;457 while (this.pos < this.tokens.length) {458 const t = this.tokens[this.pos];459 switch (t.type) {460 case TokenType.START:461 break;462 case TokenType.COLUMN:463 if (this.prev(TokenType.START, TokenType.OPEN_PAREN,464 TokenType.BOOL_AND, TokenType.BOOL_OR, TokenType.BOOL_NOT)) {465 // If there is no operator after this, then this is a shorthand "!!"466 // syntax. Expand the shorthand by splicing two extra tokens into467 // the stream after the column name. This is really ugly, but it works468 // and it really does make writing filter expressions nicer.469 if (this.pos + 1 >= this.tokens.length ||470 this.peek(TokenType.BOOL_AND, TokenType.BOOL_OR, TokenType.CLOSE_PAREN)) {471 let opr = {...t},472 val = {...t};473 opr.type = TokenType.OPERATOR;474 opr.flags = 0;475 opr.str = "!!";476 opr.len = 2;477 // Exists or not?478 val.type = TokenType.VALUE;479 val.flags = 0;480 val.str = this.prev(TokenType.BOOL_NOT) ? "0" : "1";481 val.len = 1;482 if (this.prev(TokenType.BOOL_NOT)) {483 // In this case, the negation before the colum name is part484 // of the coercion. We don't want to negate the result of485 // this test, so remove the negation from the opcodes.486 // Ugly ugly ugly.487 this.output.pop();488 }489 this.tokens.splice(this.pos + 1, 0, opr);490 this.tokens.splice(this.pos + 2, 0, val);491 // Then continue like nothing happened...492 }493 columnIndex = this.pos;494 operatorIndex = -1;495 break;496 }497 this.logger.errorToken("unexpected_column_name", t);498 errors = true;499 break;500 case TokenType.OPERATOR:501 if (this.previous == TokenType.COLUMN) {502 operatorIndex = this.pos;503 break;504 }505 this.logger.errorToken("expected_column_name", t);506 errors = true;507 columnIndex = -1;508 operatorIndex = -1;509 break;510 case TokenType.VALUE:511 if (this.previous == TokenType.OPERATOR) {512 if (columnIndex == -1) {513 this.logger.errorToken("expected_operator", t);514 errors = true;515 break;516 }517 const index = this.comparison(this.tokens[columnIndex],518 this.tokens[operatorIndex],519 this.tokens[this.pos]);520 this.output.push([index, -1]);521 columnIndex = -1;522 operatorIndex = -1;523 break;524 }525 if (this.previous == TokenType.COLUMN) {526 this.logger.errorToken("expected_operator", t);527 errors = true;528 } else {529 this.logger.errorToken("expected_column_name", t);530 errors = true;531 }532 columnIndex = -1;533 operatorIndex = -1;534 break;535 case TokenType.BOOL_AND:536 if (!this.prev(TokenType.VALUE, TokenType.CLOSE_PAREN)) {537 this.logger.errorToken("expected_value_rpar", t);538 errors = true;539 break;540 }541 this.output.push(["&", 1]);542 break;543 case TokenType.BOOL_OR:544 if (this.previous == TokenType.COLUMN) {545 this.logger.errorToken("expected_operator", t);546 errors = true;547 break;548 }549 if (!this.prev(TokenType.VALUE, TokenType.CLOSE_PAREN)) {550 this.logger.errorToken("expected_value_rpar", t);551 errors = true;552 break;553 }554 this.output.push(["|", 2]);555 break;556 case TokenType.BOOL_NOT:557 if (!this.prev(TokenType.START, TokenType.BOOL_AND, TokenType.BOOL_OR,558 TokenType.BOOL_NOT, TokenType.OPEN_PAREN)) {559 this.logger.errorToken("unexpected_negation", t);560 errors = true;561 break;562 }563 this.output.push(["!", 0]);564 break;565 case TokenType.OPEN_PAREN:566 if (!this.prev(TokenType.START, TokenType.VALUE,567 TokenType.BOOL_AND, TokenType.BOOL_OR, TokenType.BOOL_NOT,568 TokenType.OPEN_PAREN)) {569 this.logger.errorToken("unexpected_lpar", t);570 errors = true;571 break;572 }573 nesting++;574 this.output.push(["(", -1]);575 break;576 case TokenType.CLOSE_PAREN:577 if (!this.prev(TokenType.VALUE, TokenType.CLOSE_PAREN)) {578 this.logger.errorToken("unexpected_rpar", t);579 errors = true;580 break;581 }582 if (nesting < 1) {583 this.logger.errorToken("unbalanced_nesting", t);584 errors = true;585 return false;586 }587 nesting--;588 this.output.push([")", -1]);589 break;590 default:591 this.logger.errorToken("syntax_error", t);592 errors = true;593 return false;594 }595 this.previous = t.type;596 this.pos++;597 }598 if (!this.prev(TokenType.START, TokenType.VALUE, TokenType.CLOSE_PAREN)) {599 this.logger.error("unexpected_end", lastRow, lastCol, this.pos);600 return false;601 }602 if (nesting != 0) {603 this.logger.error("unbalanced_nesting", lastRow, lastCol, this.pos);604 return false;605 }606 return !errors;607 }608};609// --------------------------------------------------------------------------------------------------610// --------------------------------------------------------------------------------------------------611// RPN CODE GENERATION612/*613This is a modified Shunting Yard algorithm. It has been changed to only accept logical AND, OR614and NOT operators instead of addition and such. If you think about it, a "||" is "plus" because615either side being non-zero is enough, and "&&" is multiplication because both sides must be616non-zero. "!" is multiplying by -1. And for numbers, you only have 0 and 1 because a comparison617either succeeds (1) or fails (0). An unmodified Shunting Yard algorithm can therefore handle618logical expressions if you replace the operators and numbers.619*/620// Operators, their precedences and associativities621const Associativity = {622 LEFT: 0,623 RIGHT: 1624};625// If this table is reordered, you must change the parser above to match the new order626const SHUNTING_OPERATORS = [627 { op: '!', precedence: 4, assoc: Associativity.LEFT },628 { op: '&', precedence: 3, assoc: Associativity.LEFT },629 { op: '|', precedence: 2, assoc: Associativity.LEFT },630];631class CodeGenerator {632 compile(input)633 {634 let output = [];635 let ops = [], // operator stack636 pos = 0;637 // This was converted almost verbatim from the pseudocode given on the Wikipedia's638 // page about the Shunting Yard algorithm. If you want comments, read the page.639 while (pos < input.length) {640 const tok = input[pos];641 // Load a comparison result. All numbers are indexes to the results table.642 if (typeof(tok[0]) == "number") {643 output.push(tok);644 pos++;645 continue;646 }647 // Process an operator648 // tok[0] = operator token, tok[1] = index to OPERATORS[]649 switch (tok[0]) {650 case "!":651 case "&":652 case "|":653 {654 const opIndex = tok[1],655 thisOp = SHUNTING_OPERATORS[opIndex];656 while (ops.length > 0) {657 const top = ops[ops.length - 1];658 if (top[0] != "(" &&659 (SHUNTING_OPERATORS[top[1]].precedence > thisOp.precedence ||660 (SHUNTING_OPERATORS[top[1]].precedence == thisOp.precedence &&661 thisOp.assoc == Associativity.LEFT))) {662 output.push(top);663 ops.pop();664 } else break;665 }666 ops.push(tok);667 break;668 }669 case "(":670 ops.push(tok);671 break;672 case ")": {673 while (ops.length > 0) {674 const top = ops[ops.length - 1];675 if (top[0] != "(") {676 output.push(top);677 ops.pop();678 } else break;679 }680 if (ops.length == 0)681 throw new Error(`Mismatched parenthesis in Shunting Yard (1)`);682 ops.pop();683 break;684 }685 default:686 throw new Error(`Unknown token "${tok[0]}"`);687 }688 pos++;689 }690 while (ops.length > 0) {691 const top = ops[ops.length - 1];692 if (top[0] == "(")693 throw new Error(`Mismatched parenthesis in Shunting Yard (2)`);694 output.push(top);695 ops.pop();696 }697 return output;698 }699};700// --------------------------------------------------------------------------------------------------701// --------------------------------------------------------------------------------------------------702// COMPARISON EVALUATORS703/*704The RPN program contains only binary logic. These evaluators actually figure out if the logic705values being tested are true (1) or false (0). They're "compiled" only once, but they must be706re-evaluated again for every row that is being checked.707*/708// All known operators709const KNOWN_OPERATORS = new Set(["=", "!=", "<", "<=", ">", ">=", "!!"]);710// Which operators can be used with different column types? For example, strings cannot711// be compared with < or > (actually they can be, but it won't result in what you expect).712const ALLOWED_OPERATORS = {713 [ColumnType.BOOL]: new Set(["=", "!=", "!!"]),714 [ColumnType.NUMERIC]: new Set(["=", "!=", "<", "<=", ">", ">=", "!!"]),715 [ColumnType.UNIXTIME]: new Set(["=", "!=", "<", "<=", ">", ">=", "!!"]),716 [ColumnType.STRING]: new Set(["=", "!=", "!!"]),717};718// Absolute ("YYYY-MM-DD HH:MM:SS") and relative ("-7d") time matchers719const ABSOLUTE_TIME = /^(?<year>\d{4})-?(?<month>\d{2})?-?(?<day>\d{2})? ?(?<hour>\d{2})?:?(?<minute>\d{2})?:?(?<second>\d{2})?$/,720 RELATIVE_TIME = /^(?<sign>(\+|-))?(?<value>(\d*))(?<unit>(s|h|d|w|m|y))?$/;721// Storage size matcher ("10M", "1.3G", "50%10G" and so on).722// XY or Z%XY, where X=value, Y=optional unit, Z=percentage. Permit floats, written with723// either commas or dots.724const STORAGE_PARSER = /^((?<percent>(([0-9]*[.,])?[0-9]+))%)?(?<value>(([0-9]*[.,])?[0-9]+))(?<unit>([a-zA-Z]))?$/;725// String->float, understands dots and commas (so locales that use dots or commas for726// thousands separating will work correctly)727function floatize(str)728{729 return parseFloat(str.replace(",", "."));730}731// Converts a "YYYY-MM-DD HH:MM:SS" into a Date object, but the catch is that you can omit732// the parts you don't need, ie. the more you specify, the more accurate it gets. Giving733// "2021" to this function returns 2021-01-01 00:00:00, "2021-05" returns 2021-05-01 00:00:00,734// "2021-05-27 19:37" returns 2021-05-27 19:37:00 and so on. The other format this function735// understands are relative times: if the input value is an integer, then it is added to the736// CURRENT time and returned. Negative values point to the past, positive point to the future.737function parseAbsoluteOrRelativeDate(str)738{739 let match = ABSOLUTE_TIME.exec(str);740 if (match !== null) {741 // Parse an absolute datetime742 // This should cut off after the first missing element (ie. if you omit the day,743 // then hours, minutes and seconds should not be set), but the regexp won't match744 // it then, so no harm done.745 const year = parseInt(match.groups.year, 10),746 month = parseInt(match.groups.month || "1", 10) - 1,747 day = parseInt(match.groups.day || "1", 10),748 hour = parseInt(match.groups.hour || "0", 10),749 minute = parseInt(match.groups.minute || "0", 10),750 second = parseInt(match.groups.second || "0", 10);751 let d = null;752 try {753 d = new Date();754 d.setFullYear(year);755 d.setMonth(month);756 d.setDate(day);757 d.setHours(hour);758 d.setMinutes(minute);759 d.setSeconds(second);760 d.setMilliseconds(0); // the database values have only 1-second granularity761 } catch (e) {762 console.error(`parseAbsoluteOrRelativeDate(): can't construct an absolute Date object from "${str}":`);763 console.error(e);764 return null;765 }766 return d;767 }768 match = RELATIVE_TIME.exec(str);769 if (match === null) {770 // Don't know what this string means771 console.error(`parseAbsoluteOrRelativeDate(): "${str}" is neither absolute nor relative date`);772 return null;773 }774 // Parse a relative datetime775 let value = parseInt(match.groups.value, 10);776 // Scale777 switch (match.groups.unit) {778 default:779 case "s":780 // Seconds are the default, do nothing781 break;782 case "h":783 value *= 60 * 60; // 1 hour in seconds784 break;785 case "d":786 value *= 60 * 60 * 24; // 1 day in seconds787 break;788 case "w":789 value *= 60 * 60 * 24 * 7; // 1 week in seconds790 break;791 case "m":792 value *= 60 * 60 * 24 * 30; // 1 (30-day) month in seconds793 break;794 case "y":795 value *= 60 * 60 * 24 * 365; // 1 year with 365 days (no leap year checks here)796 break;797 }798 // Sign799 if (match.groups.sign !== undefined) {800 if (match.groups.sign == "-")801 value *= -1;802 // Treat all other signs are +, even unknown ones (there shouldn't be any, since the803 // regexp rejects them)804 }805 let d = new Date();806 try {807 d.setSeconds(d.getSeconds() + value);808 d.setMilliseconds(0); // the database values have only 1-second granularity809 } catch (e) {810 console.error(`parseAbsoluteOrRelativeDate(): can't construct a relative Date object from "${str}":`);811 console.error(e);812 return null;813 }814 return d;815}816class ComparisonCompiler {817 constructor()818 {819 this.logger = null;820 }821 // Parses and expands a value with unit, like "10M" or "10G" to a full number. Optionally822 // calculates a percentage value, like "50%10M" is equivalent to writing "5M".823 __parseStorage(valueToken)824 {825 const storage = STORAGE_PARSER.exec(valueToken.str.trim());826 if (storage === null) {827 // Just a number, no units or percentages828 try {829 const v = floatize(valueToken.str);830 if (isNaN(v)) {831 console.error(`__parseStorage(): "${valueToken.str}" is not a valid number`);832 this.logger.errorToken("not_a_number", valueToken);833 return null;834 }835 return v;836 } catch (e) {837 console.error(`__parseStorage(): "${valueToken.str}" cannot be parsed as a float`);838 this.logger.errorToken("not_a_number", valueToken);839 return null;840 }841 }842 // The base value. It's easier if we treat everything here as a float.843 let value = 0;844 try {845 value = floatize(storage.groups.value);846 } catch (e) {847 console.error(`__parseStorage(): "${storage.groups.value}" cannot be parsed as a float`);848 this.logger.errorToken("not_a_number", valueToken);849 return null;850 }851 // Scale unit852 let unit = storage.groups.unit;853 if (unit === undefined || unit === null)854 unit = "B";855 switch (unit) {856 case "B":857 // bytes are the default, do nothing858 break;859 case "K":860 value *= 1024;861 break;862 case "M":863 value *= 1024 * 1024;864 break;865 case "G":866 value *= 1024 * 1024 * 1024;867 break;868 case "T":869 value *= 1024 * 1024 * 1024 * 1024;870 break;871 default:872 console.error(`__parseStorage(): invalid storage unit "${unit}"`);873 this.logger.errorToken("invalid_storage_unit", valueToken, unit);874 return null;875 }876 // Percentage877 let percent = storage.groups.percent;878 if (percent) {879 percent = Math.min(Math.max(floatize(percent), 0.0), 100.0);880 value *= percent / 100.0;881 }882 return value;883 }884 __compileBoolean(columnToken, operatorToken, valueToken)885 {886 return {887 column: columnToken.str,888 operator: operatorToken.str,889 value: ["1", "t", "y", "true", "yes", "on"].includes(valueToken.str.toLowerCase()),890 regexp: false891 };892 }893 __compileNumeric(columnToken, operatorToken, valueToken)894 {895 const colDef = this.columnDefs.get(this.columnDefs.expandAlias(columnToken.str));896 let value = undefined;897 if (colDef.flags & ColumnFlag.F_STORAGE) {898 // Parse a storage specifier, like "5M" or "10G"899 value = this.__parseStorage(valueToken);900 if (value === null)901 return null;902 } else {903 try {904 if (valueToken.str.indexOf(".") == -1 && valueToken.str.indexOf(",") == -1) {905 // Integer906 value = parseInt(valueToken.str, 10);907 } else {908 // Float909 value = floatize(valueToken.str);910 }911 if (isNaN(value))912 throw new Error("not an integer");913 } catch (e) {914 console.error(`ComparisonCompiler::compile(): can't parse a number: ${e.message}`);915 console.error(e);916 this.logger.errorToken("not_a_number", valueToken);917 return null;918 }919 }920 return {921 column: columnToken.str,922 operator: operatorToken.str,923 value: value,924 regexp: false925 };926 }927 __compileUnixtime(columnToken, operatorToken, valueToken)928 {929 const out = parseAbsoluteOrRelativeDate(valueToken.str);930 if (out === null) {931 this.logger.errorToken("unparseable_time", valueToken);932 return false;933 }934 return {935 column: columnToken.str,936 operator: operatorToken.str,937 value: out.getTime() / 1000, // convert to seconds938 regexp: false939 }940 }941 __compileString(columnToken, operatorToken, valueToken)942 {943 let regexp = false,944 value = undefined;945 if (valueToken.flags & TokenFlags.REGEXP) {946 // Compile a regexp947 try {948 value = new RegExp(valueToken.str.trim(),949 valueToken.flags & TokenFlags.MULTILINE ? "miu" : "iu"),950 regexp = true;951 } catch (e) {952 console.error(`ComparisonCompiler::compile(): regexp compilation failed: ${e.message}`);953 this.logger.errorToken("invalid_regexp", valueToken, e.message);954 return null;955 }956 } else {957 // A plain string, use as-is958 value = valueToken.str;959 }960 return {961 column: columnToken.str,962 operator: operatorToken.str,963 value: value,964 regexp: regexp965 };966 }967 // Takes a raw comparison (made of three tokens) and "compiles" it (ie. verifies the data968 // types, the comparison operator and the value, and converts the stringly-typed value into969 // "native" JavaScript type). Returns null if it failed.970 compile(logger, columns, columnToken, operatorToken, valueToken)971 {972 this.logger = logger;973 this.columnDefs = columns;974 // Validate the column and the operator975 if (!this.columnDefs.isColumn(columnToken.str)) {976 console.error(`ComparisonCompiler::compile(): unknown column "${columnToken.str}"`);977 this.logger.errorToken("unknown_column", columnToken);978 return null;979 }980 if (!KNOWN_OPERATORS.has(operatorToken.str)) {981 console.error(`ComparisonCompiler::compile(): invalid operator "${operatorToken.str}"`);982 this.logger.errorToken("invalid_operator", operatorToken);983 return null;984 }985 const colDef = this.columnDefs.get(this.columnDefs.expandAlias(columnToken.str));986 if (!ALLOWED_OPERATORS[colDef.type].has(operatorToken.str)) {987 console.error(`ComparisonCompiler::compile(): operator "${operatorToken.str}" cannot be used with column type "${colDef.type}"`);988 this.logger.errorToken("incompatible_operator", operatorToken);989 return null;990 }991 if (typeof(valueToken.str) != "string") {992 console.error(`ComparisonCompiler::compile(): value "${valueToken.str}" is not a string`);993 this.logger.errorToken("invalid_value", valueToken);994 return null;995 }996 // Interpret the comparison value and convert it into a "native" type997 if (operatorToken.str == "!!") {998 // Special case: always treat the value as boolean, regardless of what the column is999 return this.__compileBoolean(columnToken, operatorToken, valueToken);1000 }1001 switch (colDef.type) {1002 case ColumnType.BOOL:1003 return this.__compileBoolean(columnToken, operatorToken, valueToken);1004 case ColumnType.NUMERIC:1005 return this.__compileNumeric(columnToken, operatorToken, valueToken);1006 case ColumnType.UNIXTIME:1007 return this.__compileUnixtime(columnToken, operatorToken, valueToken);1008 case ColumnType.STRING:1009 return this.__compileString(columnToken, operatorToken, valueToken);1010 default:1011 console.error(`ComparisonCompiler::compile(): unhandled column type "${colDef.type}"`);1012 this.logger.errorToken("unknown_error", columnToken);1013 return null;1014 }1015 }1016};1017// Executes a comparison. Returns true if the comparison matches the tested value.1018// Can (sorta) deal with NULL and undefined values.1019function __compareSingleValue(cmp, value)1020{1021 if (cmp.operator != "!!") {1022 if (value === undefined || value === null) {1023 // Treat missing values as false. Actually comparing them with something1024 // is nonsensical. Use the "!!" operator to test if those values actually1025 // are present in the data.1026 return false;1027 }1028 }1029 switch (cmp.operator) {1030 case "=":1031 return cmp.regexp ? cmp.value.test(value) : cmp.value === value;1032 case "!=":1033 return !(cmp.regexp ? cmp.value.test(value) : cmp.value === value);1034 case "<":1035 return value < cmp.value;1036 case "<=":1037 return value <= cmp.value;1038 case ">":1039 return value > cmp.value;1040 case ">=":1041 return value >= cmp.value;1042 case "!!":1043 return cmp.value != (value === null || value === undefined);1044 default:1045 throw new Error(`compare(): unknown operator "${cmp.operator}"`);1046 }1047}1048// Executes a single comparison against a row value. Deals with arrays and NULL/undefined1049// data. Returns true if the comparison matched.1050function compareRowValue(value, cmp)1051{1052 if (value !== undefined && value !== null && Array.isArray(value)) {1053 // Loop over multiple values. Currently only string arrays are supported,1054 // because no other types of arrays exists in the database.1055 if (cmp.operator == "=") {1056 for (const v of value)1057 if (__compareSingleValue(cmp, v))1058 return true;1059 return false;1060 }1061 // Assume "!=" because there are only two usable operators with strings1062 for (const v of value)1063 if (!__compareSingleValue(cmp, v))1064 return false;1065 return true;1066 }1067 // Just one value1068 return __compareSingleValue(cmp, value);1069}1070// Runs the filter program and returns true if the row matches1071function evaluateFilter(program, comparisonResults)1072{1073 let stack = [],1074 a, b;1075 // a simple RPN evaluator1076 for (const instr of program) {1077 switch (instr[0]) {1078 case "!":1079 if (stack.length < 1)1080 throw new Error("stack underflow while evaluating a logical NOT");1081 a = stack.pop();1082 stack.push(!a);1083 break;1084 case "&":1085 if (stack.length < 2)1086 throw new Error("stack underflow while evaluating a logical AND");1087 a = stack.pop();1088 b = stack.pop();1089 stack.push(a & b);1090 break;1091 case "|":1092 if (stack.length < 2)1093 throw new Error("stack underflow while evaluating a logical OR");1094 a = stack.pop();1095 b = stack.pop();1096 stack.push(a | b);1097 break;1098 default:1099 // load comparison result1100 stack.push(comparisonResults[instr[0]]);1101 break;1102 }1103 }1104 return stack[0];1105}1106// --------------------------------------------------------------------------------------------------1107// --------------------------------------------------------------------------------------------------1108// THE FILTER EDITOR USER INTERFACE1109// All known operators and which column types they can be used with1110const OPERATORS = {1111 "=": {1112 allowed: new Set([ColumnType.BOOL, ColumnType.NUMERIC, ColumnType.UNIXTIME, ColumnType.STRING]),1113 multiple: true,1114 },1115 "!=": {1116 allowed: new Set([ColumnType.BOOL, ColumnType.NUMERIC, ColumnType.UNIXTIME, ColumnType.STRING]),1117 multiple: true,1118 },1119 "<": {1120 allowed: new Set([ColumnType.NUMERIC, ColumnType.UNIXTIME]),1121 multiple: false,1122 },1123 "<=": {1124 allowed: new Set([ColumnType.NUMERIC, ColumnType.UNIXTIME]),1125 multiple: false,1126 },1127 ">": {1128 allowed: new Set([ColumnType.NUMERIC, ColumnType.UNIXTIME]),1129 multiple: false,1130 },1131 ">=": {1132 allowed: new Set([ColumnType.NUMERIC, ColumnType.UNIXTIME]),1133 multiple: false,1134 },1135 // interval (closed)1136 "[]": {1137 allowed: new Set([ColumnType.NUMERIC, ColumnType.UNIXTIME]),1138 multiple: true,1139 },1140 // reverse interval (closed)1141 "![]": {1142 allowed: new Set([ColumnType.NUMERIC, ColumnType.UNIXTIME]),1143 multiple: true,1144 },1145};1146const ColumnTypeStrings = {1147 [ColumnType.BOOL]: "boolean",1148 [ColumnType.NUMERIC]: "numeric",1149 [ColumnType.UNIXTIME]: "unixtime",1150 [ColumnType.STRING]: "string",1151};1152function makeRandomID()1153{1154 const CHARS = "abcdefghijklmnopqrstuvwxyz";1155 let out = "";1156 for (let i = 0; i < 20; i++)1157 out += CHARS.charAt(Math.floor(Math.random() * CHARS.length));1158 return out;1159}1160let filterID_ = 1;1161function nextFilterID()1162{1163 return filterID_++;1164}1165function hideElements()1166{1167 for (let i = 0; i < arguments.length; i++)1168 arguments[i].classList.add("hidden");1169}1170function showElements()1171{1172 for (let i = 0; i < arguments.length; i++)1173 arguments[i].classList.remove("hidden");1174}1175function humanOperatorName(operator)1176{1177 switch (operator) {1178 case "=": return "=";1179 case "!=": return "≠";1180 case "<": return "<";1181 case "<=": return "≤";1182 case ">": return ">";1183 case ">=": return "≥";1184 case "[]": return _tr("tabs.filtering.pretty.interval");1185 case "![]": return _tr("tabs.filtering.pretty.not_interval");1186 default:1187 throw new Error(`humanOperatorName(): invalid operator "${operator}"`);1188 }1189}1190function getDefaultValue(definition)1191{1192 switch (definition.type) {1193 case ColumnType.BOOL:1194 return true;1195 case ColumnType.NUMERIC:1196 if (definition.flags & ColumnFlag.F_STORAGE)1197 return "0M";1198 return 0;1199 case ColumnType.STRING:1200 return "";1201 case ColumnType.UNIXTIME:1202 return 0;1203 default:1204 throw new Error("getDefaultValue(): invalid column type");1205 }1206}1207// Single editable filter1208class EditableFilter {1209constructor()1210{1211 this.active = false;1212 this.column = null;1213 this.operator = null;1214 this.values = [];1215 // Current data being edited (the original data is not overwritten until "Save" is pressed)1216 this.editColumn = null;1217 this.editOperator = null;1218 this.editValues = null;1219 // True if this is a brand new filter that hasn't been saved yet. Changes how some1220 // operations work (or don't work).1221 this.isNew = false;1222 // Editor child class (see below)1223 this.editor = null;1224}1225beginEditing()1226{1227 this.editColumn = this.column;1228 this.editOperator = this.operator;1229 this.editValues = [...this.values];1230 // The editor is created elsewhere1231}1232finishEditing()1233{1234 // Overwrite old values1235 this.column = this.editColumn;1236 this.operator = this.editOperator;1237 this.values = this.editor.getData();1238}1239cancelEditing()1240{1241 this.editColumn = null;1242 this.editOperator = null;1243 this.editValues = null;1244 // The editor is destroyed elsewhere1245}1246// Parses a "raw" filter stored as [active?, column, operator, value1, value2, ... valueN].1247// Returns true if OK.1248load(raw, columnDefinitions)1249{1250 if (!Array.isArray(raw) || raw.length < 4) {1251 console.error(`EditableFilter::fromRaw(): invalid/incomplete raw filter:`);1252 console.error(raw);1253 return false;1254 }1255 // The column must be valid. We can tolerate/fix almost everything else, but not this.1256 if (!(raw[1] in columnDefinitions)) {1257 console.warn(`EditableFilter::fromRaw(): column "${raw[1]}" is not valid`);1258 return false;1259 }1260 this.active = (raw[0] === true || raw[0] === 1) ? 1 : 0;1261 this.column = raw[1];1262 this.operator = raw[2];1263 this.values = raw.slice(3);1264 // Reset invalid operators to "=" because it's the least destructive of them all,1265 // and I'd wager that most filters are simple equality checks1266 if (!(this.operator in OPERATORS)) {1267 console.warn(`EditableFilter::fromRaw(): operator "${this.operator}" is not valid, resetting it to "="`);1268 this.operator = "=";1269 }1270 // Is the operator usable with this column type?1271 const opDef = OPERATORS[this.operator],1272 colDef = columnDefinitions[this.column];1273 if (!opDef.allowed.has(colDef.type)) {1274 console.warn(`EditableFilter::fromRaw(): operator "${this.operator}" cannot be used with ` +1275 `column type "${ColumnTypeStrings[colDef.type]}" (column "${this.column}"), ` +1276 `resetting to "="`);1277 this.operator = "=";1278 }1279 // Handle storage units. Remove invalid values.1280 if (colDef.flags & ColumnFlag.F_STORAGE) {1281 let proper = [];1282 // Remove invalid entries1283 for (const v of this.values) {1284 try {1285 const m = STORAGE_PARSER.exec(v.toString().trim());1286 if (m !== null) {1287 const unit = (m.groups.unit === undefined || m.groups.unit === null) ? "B" : m.groups.unit;1288 proper.push(`${m.groups.value}${unit}`);1289 }1290 } catch (e) {1291 console.error(e);1292 continue;1293 }1294 }1295 this.values = proper;1296 }1297 // Check time strings1298 if (colDef.type == ColumnType.UNIXTIME) {1299 let proper = [];1300 for (const v of this.values) {1301 try {1302 if (parseAbsoluteOrRelativeDate(v) !== null)1303 proper.push(v);1304 } catch (e) {1305 console.error(e);1306 continue;1307 }1308 }1309 this.values = proper;1310 }1311 if (this.values.length == 0) {1312 // Need to do this check again, because we might have altered the values1313 console.error(`EditableFilter::fromRaw(): filter has no values at all`);1314 return false;1315 }1316 // Ensure there's the required number of values for this operator1317 if (this.operator == "[]" || this.operator == "![]") {1318 if (this.values.length == 1) {1319 console.warn(`EditableFilter::fromRaw(): need more than one value, duplicating the single value`);1320 this.values.push(this.values[0]);1321 } else if (this.values.length > 2) {1322 console.warn(`EditableFilter::fromRaw(): intervals can use only two values, removing extras`);1323 this.values = [this.values[0], this.values[1]];1324 }1325 }1326 if (this.values.length > 1 && opDef.multiple !== true) {1327 console.warn(`EditableFilter::fromRaw(): operator "${this.operator}" cannot handle multiple values, extra values removed`);1328 this.values = [this.values[0]];1329 }1330 return true;1331}1332save()1333{1334 return [this.active, this.column, this.operator].concat(this.values);1335}1336}; // class EditableFilter1337class FilterEditorBase {1338constructor(container, filter, definition)1339{1340 // Target filter1341 this.filter = filter;1342 // Does this filter target RAM/HD sizes?1343 this.isStorage = (definition.flags & ColumnFlag.F_STORAGE) ? true : false;1344 // Where to put the editor interface1345 this.container = container;1346 // Unique UI element prefix1347 this.id = makeRandomID();1348 // UI properties1349 this.defaultValue = "";1350 this.fieldSize = 50;1351 this.maxLength = "";1352}1353buildUI()1354{1355}1356operatorHasChanged(operator)1357{1358 this.buildUI();1359}1360getData()1361{1362 throw new Error("you did not override getData()");1363}1364// Return [state, message], if state is true then the data is valid, otherwise the1365// message is displayed and the filter is NOT saved (and the editor does not close).1366validate()1367{1368 return [true, null];1369}1370// Accessing this.container.query... is so frequent that here's two helpers for it1371$(query) { return this.container.querySelector(query); }1372$all(query) { return this.container.querySelectorAll(query); }1373createValueRow(value, showButtons=true, title=null)1374{1375 let row = document.createElement("tr"),1376 html = "";1377 if (title !== null)1378 html += `<td>${title}</td>`;1379 html += `<td><div class="flex flex-cols flex-gap-5px">`;1380 value = value.toString();1381 if (this.isStorage) {1382 // Make a unit selector combo box and strip the unit from the value1383 const unit = value.length > 1 ? value.toString().slice(value.length - 1) : null;1384 html += `<input type="text" size="${this.fieldSize}" maxlen="" value="${value.length > 1 ? value.slice(0, value.length - 1) : value}">`;1385 html += "<select>";1386 for (const u of [["B", "B"], ["KiB", "K"], ["MiB", "M"], ["GiB", "G"], ["TiB", "T"]])1387 html += `<option data-unit="${u[1]}" ${u[1] == unit ? "selected" : ""}>${u[0]}</option>`;1388 html += "</select>";1389 } else html += `<input type="text" size="${this.fieldSize}" maxlength="${this.maxLength}">`;1390 if (showButtons)1391 html += `<button>+</button><button>-</button>`;1392 html += "</div></td>";1393 row.innerHTML = html;1394 if (!this.isStorage)1395 row.querySelector(`input[type="text"]`).value = value;1396 if (showButtons)1397 this.addEventHandlers(row);1398 return row;1399}1400addEventHandlers(row)1401{1402 // +/- button click handlers. Their positions change if the unit combo box is on the row.1403 const add = this.isStorage ? 2 : 1,1404 del = this.isStorage ? 3 : 2;1405 row.children[0].children[0].children[add].addEventListener("click", (e) => this.duplicateRow(e));1406 row.children[0].children[0].children[del].addEventListener("click", (e) => this.removeRow(e));1407}1408duplicateRow(e)1409{1410 let thisRow = e.target.parentNode.parentNode.parentNode,1411 newRow = thisRow.cloneNode(true);1412 if (this.isStorage) {1413 // Turns out that selectedIndex is not part of the DOM. Thank you, JavaScript.1414 // This is so ugly.1415 newRow.children[0].children[0].children[1].selectedIndex =1416 thisRow.children[0].children[0].children[1].selectedIndex;1417 }1418 this.addEventHandlers(newRow);1419 thisRow.parentNode.insertBefore(newRow, thisRow.nextSibling);1420}1421removeRow(e)1422{1423 let thisRow = e.target.parentNode.parentNode.parentNode;1424 thisRow.parentNode.removeChild(thisRow);1425 // There must be at least one value at all times, even if it's empty1426 if (this.$all(`table#values tr`).length == 0) {1427 console.log("Creating a new empty value row");1428 this.$("table#values").appendChild(this.createValueRow(this.defaultValue));1429 }1430}1431}; // class FilterEditorBase1432class FilterEditorBoolean extends FilterEditorBase {1433buildUI()1434{1435 this.container.innerHTML =1436`<div class="flex flex-rows flex-gap-5px">1437<span><input type="radio" name="${this.id}-value" id="${this.id}-true" ${this.filter.editValues[0] === 1 ? "checked" : ""}><label for="${this.id}-true">${_tr('tabs.filtering.ed.bool.t')}</label></span>1438<span><input type="radio" name="${this.id}-value" id="${this.id}-false" ${this.filter.editValues[0] !== 1 ? "checked" : ""}><label for="${this.id}-false">${_tr('tabs.filtering.ed.bool.f')}</label></span>1439</div>`;1440 this.$(`#${this.id}-true`).addEventListener("click", () => { this.filter.editValues = [1]; });1441 this.$(`#${this.id}-false`).addEventListener("click", () => { this.filter.editValues = [0]; });1442}1443getData()1444{1445 return [this.filter.editValues[0] === 1 ? 1 : 0];1446}1447}; // class FilterEditorBoolean1448class FilterEditorString extends FilterEditorBase {1449buildUI()1450{1451 this.container.innerHTML = `<p>${this.getExplanation()}</p><table id="values"></table>`;1452 let table = this.$("table#values");1453 for (const v of this.filter.editValues)1454 table.appendChild(this.createValueRow(v));1455}1456getData()1457{1458 let values = [];1459 for (const i of this.$all(`table#values tr input[type="text"]`))1460 values.push(i.value.trim());1461 return values;1462}1463operatorHasChanged(operator)1464{1465 this.$("p").innerHTML = this.getExplanation();1466}1467getExplanation()1468{1469 let out = "";1470 out += _tr("tabs.filtering.ed.multiple");1471 out += " ";1472 out += _tr((this.filter.editOperator == "=") ? "tabs.filtering.ed.one_hit_is_enough" : "tabs.filtering.ed.no_hits_allowed");1473 out += " ";1474 out += _tr("tabs.filtering.ed.regexp");1475 return out;1476}1477}; // class FilterEditorString1478class FilterEditorNumeric extends FilterEditorBase {1479constructor(container, filter, definition)1480{1481 super(container, filter, definition);1482 this.defaultValue = this.isStorage ? "0M" : "0";1483 this.fieldSize = 10;1484 this.maxLength = "32";1485}1486buildUI()1487{1488 const id = this.id;1489 const opr = this.filter.editOperator;1490 if (opr == "[]" || opr == "![]") {1491 let help = "";1492 if (opr == "[]")1493 help += _tr("tabs.filtering.ed.closed");1494 else help += _tr("tabs.filtering.ed.open");1495 this.container.innerHTML = `<p>${help}${this.getExtraHelp()}</p><table id="values"></table>`;1496 let table = this.$("table#values");1497 table.appendChild(this.createValueRow(this.filter.editValues[0], false, "Min:"));1498 table.appendChild(this.createValueRow(this.filter.editValues[1], false, "Max:"));1499 } else if (opr == "=" || opr == "!=") {1500 let html = `<p>${_tr("tabs.filtering.ed.multiple")}`;1501 html += " ";1502 html += _tr((opr == "=") ? "tabs.filtering.ed.one_hit_is_enough" : "tabs.filtering.ed.no_hits_allowed");1503 html += " ";1504 html += `${this.getExtraHelp()}</p><table id="values"></table>`;1505 this.container.innerHTML = html;1506 let table = this.$("table#values");1507 for (const v of this.filter.editValues)1508 table.appendChild(this.createValueRow(v));1509 } else {1510 this.container.innerHTML = `<p>${_tr("tabs.filtering.ed.single")}${this.getExtraHelp()}</p><table id="values"></table>`;1511 this.$("table#values").appendChild(this.createValueRow(this.filter.editValues[0], false));1512 }1513}1514getData()1515{1516 const interval = (this.filter.editOperator == "[]" || this.filter.editOperator == "![]");1517 let values = [];1518 // This assumes validate() has been called first and the data is actually valid1519 for (const i of this.$all(`table#values tr input[type="text"]`)) {1520 let n = i.value.trim();1521 if (n.length == 0)1522 continue;1523 try {1524 n = floatize(n);1525 } catch (e) {1526 continue;1527 }1528 if (isNaN(n))1529 continue;1530 if (this.isStorage) {1531 const s = i.parentNode.children[1];1532 values.push(`${n}${s.options[s.selectedIndex].dataset.unit}`); // put the unit back1533 } else values.push(n);1534 }1535 // min > max, swap1536 // TODO: Make this work with storage1537 if (!this.isStorage && interval && values[0] > values[1])1538 values = [values[1], values[0]];1539 return values;1540}1541validate()1542{1543 const interval = (this.filter.editOperator == "[]" || this.filter.editOperator == "![]");1544 let valid = 0;1545 for (const i of this.$all(`table#values tr input[type="text"]`)) {1546 let n = i.value.trim();1547 if (n.length == 0)1548 continue;1549 try {1550 n = floatize(n);1551 } catch (e) {1552 return [false, `"${i.value.trim()}" ` + _tr("tabs.filtering.ed.numeric.nan")];1553 }1554 if (isNaN(n))1555 return [false, `"${i.value.trim()}" `+ _tr("tabs.filtering.ed.numeric.nan")];1556 if (this.isStorage && n < 0)1557 return [false, _tr("tabs.filtering.ed.numeric.negative_storage")];1558 valid++;1559 }1560 if (interval && valid < 2)1561 return [false, _tr("tabs.filtering.ed.invalid_interval")];1562 if (!interval && valid == 0)1563 return [false, _tr("tabs.filtering.ed.no_values")];1564 return [true, null];1565}1566getExtraHelp()1567{1568 return "";1569}1570}; // class FilterEditorNumeric1571class FilterEditorUnixtime extends FilterEditorNumeric {1572constructor(container, filter, definition)1573{1574 super(container, filter, definition);1575 // Use today's date as the default value1576 const d = new Date();1577 this.defaultValue = `${d.getFullYear()}-` +1578 `${String(d.getMonth() + 1).padStart(2, "0")}-` +1579 `${String(d.getDate()).padStart(2, "0")}`;1580 this.fieldSize = 20;1581 this.maxLength = "20";1582}1583buildUI()1584{1585 super.buildUI();1586 this.$(`a#${this.id}-help`).addEventListener("click", (e) => this.showHelp(e));1587}1588getData()1589{1590 let values = [];1591 // Unlike numbers, attempt no string->int conversions here. The filter compiler1592 // engine will deal with interpreting absolute and relative time values.1593 for (const i of this.$all(`table#values tr input[type="text"]`)) {1594 const v = i.value.trim();1595 if (v.length == 0)1596 continue;1597 values.push(v);1598 }1599 return values;1600}1601validate()1602{1603 const interval = (this.filter.editOperator == "[]" || this.filter.editOperator == "![]");1604 let valid = 0;1605 for (const i of this.$all(`table#values tr input[type="text"]`)) {1606 const v = i.value.trim();1607 if (v.length == 0)1608 continue;1609 if (parseAbsoluteOrRelativeDate(v) === null)1610 return [false, `"${v}"` + _tr("tabs.filtering.ed.time.invalid")];1611 valid++;1612 }1613 if (interval && valid < 2)1614 return [false, _tr("tabs.filtering.ed.invalid_interval")];1615 if (!interval && valid == 0)1616 return [false, _tr("tabs.filtering.ed.no_values")];1617 return [true, null];1618}1619getExtraHelp()1620{1621 return ` <a href="#" id="${this.id}-help"> ${_tr("tabs.filtering.ed.time.help_link")}</a>.`;1622}1623showHelp(e)1624{1625 e.preventDefault();1626 window.alert(_tr("tabs.filtering.ed.time.help"));1627}1628}; // class FilterEditorUnixtime1629const RowElem = {1630 BTN_DELETE: 1,1631 BTN_DUPLICATE: 2,1632 CB_ACTIVE: 3,1633 DIV_PRETTY: 4,1634 DIV_EDITOR: 5,1635};1636class FilterEditor {1637constructor(parentClass, container, columnDefinitions, columnTitles, filterPresets, filterDefaults, isAdvanced)1638{1639 // Who do we tell about filter changes?1640 this.parentClass = parentClass;1641 // This container is our playground. Everything we put on the screen, it's1642 // inside this HTML element.1643 this.container = container;1644 // Column definitions1645 this.plainColumnDefinitions = columnDefinitions;1646 this.columnDefinitions = new ColumnDefinitions(columnDefinitions);1647 this.columnTitles = columnTitles;1648 this.updateColumnHelp = true;1649 this.haveHelp = false;1650 this.changed = false;1651 this.isAdvanced = isAdvanced;1652 this.filterPresets = filterPresets;1653 this.filters = {}; // the traditional filters1654 this.showJSON = false;1655 this.defaultFilter = filterDefaults[0];1656 // The current filter programs. One for the old-style filters, one for the advanced filter.1657 this.comparisons = [];1658 this.program = [];1659 this.comparisonsAdvanced = [];1660 this.programAdvanced = [];1661 // JS event handling shenanigans1662 this.onDeleteFilter = this.onDeleteFilter.bind(this);1663 this.onDuplicateFilter = this.onDuplicateFilter.bind(this);1664 this.onActiveFilter = this.onActiveFilter.bind(this);1665 this.onClickColumnName = this.onClickColumnName.bind(this);1666 this.buildUI();1667 this.enableOrDisable(false);1668}1669buildUI()1670{1671 const havePresets = Object.keys(this.filterPresets[0]).length > 0,1672 haveAdvancedPresets = Object.keys(this.filterPresets[1]).length > 0;1673 let html = "";1674 html += `<div id="traditional" class="filterEditorWrapper">`;1675 html +=1676`<div class="flex flex-rows flex-gap-5px"><div class="flex flex-cols flex-gap-5px">1677<button id="deleteAll" class="danger" title="${_tr("tabs.filtering.delete_all_title")}">${_tr("tabs.filtering.delete_all")}</button>1678<button id="toggleJSON" title="${_tr("tabs.filtering.toggle_json_title")}">${_tr("tabs.filtering.show_json")}</button>1679<button id="saveJSON" class="hidden" title="${_tr("tabs.filtering.save_json_title")}">${_tr("tabs.filtering.save_json")}</button>1680</div><textarea id="json" rows="5" class="width-100p jsonEditor hidden" title="${_tr("tabs.filtering.json_title")}"></textarea>1681<table class="filtersTable"></table>1682<div><button id="new">${_tr("tabs.filtering.new_filter")}</button></div>`;1683 if (havePresets) {1684 const presets = this.filterPresets[0];1685 html += `<div><details><summary>${_tr('tabs.filtering.presets.title')}</summary>`;1686 html += `<div class="flex flex-rows flex-gap-5px margin-top-10px">`;1687 html += `<p class="margin-0 padding-0">${_tr("tabs.filtering.presets.click_to_add")}</p>`;1688 html += `<span><input type="checkbox" id="append-at-end" checked><label for="append-at-end">${_tr('tabs.filtering.presets.append')}</label></span>`;1689 html += `<ul class="margin-0 padding-0 no-list-bullets" id="presets">`;1690 for (const key of Object.keys(presets))1691 html += `<li><a href="#" data-id="${key}">${presets[key].title}</a></li>`;1692 html += `</ul></details></div></div>`;1693 }1694 html += `</div>`;1695 html += `</div>`;1696 html += `<div id="advanced">`;1697 html +=1698`<div class="flex flex-columns flex-gap-10px width-100p">1699<fieldset class="width-66p">1700<legend>${_tr('tabs.filtering.expression_title')}</legend>1701<textarea id="filter" placeholder="${_tr('tabs.filtering.expression_placeholder')}" rows="5"></textarea>1702<div class="flex flex-columns flex-gap-5px margin-top-5px">1703<button id="save" disabled>${_tr('tabs.filtering.save')}</button>1704<button id="clear" disabled>${_tr('tabs.filtering.clear')}</button>1705<button id="convert" disabled>${_tr('tabs.filtering.convert')}</button>1706</div>1707</fieldset>1708<fieldset class="width-33p">1709<legend>${_tr('tabs.filtering.messages_title')}</legend>1710<div id="messages"></div>1711</fieldset>1712</div>`;1713 html += `<div class="flex flex-rows flex-gap-10px margin-top-10px">`;1714 if (haveAdvancedPresets) {1715 html +=1716`<details>1717<summary>${_tr('tabs.filtering.presets.title')}</summary>1718<div class="padding-10px">1719<p class="line-height-150p margin-0 padding-0">${_tr('tabs.filtering.presets.instructions')}</p>1720<div class="padding-top-10px padding-bottom-10px flex flex-vcenter flex-columns flex-gap-10px">1721<span><input type="checkbox" id="append-at-end-advanced" checked><label for="append-at-end-advanced">${_tr('tabs.filtering.presets.append')}</label></span>1722<span><input type="checkbox" id="add-parenthesis" checked><label for="add-parenthesis">${_tr('tabs.filtering.presets.add_parenthesis')}</label></span>1723</div>1724<table class="commonTable presetsTable"><thead>1725<tr><th class="padding-5px">${_tr('tabs.filtering.presets.name')}</th><th class="padding-5px">${_tr('tabs.filtering.presets.expression')}</th></tr>1726</thead><tbody>`;1727 for (const key of Object.keys(this.filterPresets[1])) {1728 const preset = this.filterPresets[1][key];1729 html +=1730`<tr data-id="${key}">1731<td class="padding-5px"><a href="#" data-id="${key}">${preset.title}</a></td>1732<td class="padding-5px"><code>${escapeHTML(preset.filter)}</code></td>1733</tr>`;1734 }1735 html += `</tbody></table>`;1736 html += "</div>";1737 html += `</details>`;1738 }1739 html +=1740`<details>1741<summary>${_tr('tabs.filtering.column_list.title')}</summary>1742<div class="padding-10px">1743<p class="line-height-150p margin-0 padding-0">${_tr('tabs.filtering.column_list.hidden_warning')}</p>1744<div id="columnList" class="margin-top-10px"></div>1745</details>`;1746 html += `</div>`;1747 html += `</div>`;1748 this.container.innerHTML = html;1749 // Initial mode selection1750 if (this.isAdvanced)1751 this.$("div#traditional").classList.add("hidden");1752 else this.$("div#advanced").classList.add("hidden");1753 this.$("button#deleteAll").addEventListener("click", () => this.onDeleteAllFilters());1754 this.$("button#toggleJSON").addEventListener("click", () => this.onToggleJSON());1755 this.$("button#saveJSON").addEventListener("click", () => this.onSaveJSON());1756 this.$("button#new").addEventListener("click", () => this.onNewFilter());1757 this.$("textarea#json").addEventListener("input", () => this.validateJSON());1758 this.$("button#save").addEventListener("click", () => this.onSave());1759 this.$("button#clear").addEventListener("click", () => this.onClear());1760 this.$("button#convert").addEventListener("click", () => this.onConvertTraditionalFilter());1761 this.$("textarea#filter").addEventListener("input", () => this.onAdvancedInput());1762 // Make the presets clickable1763 if (havePresets) {1764 for (let i of this.$all("div#traditional ul#presets a"))1765 i.addEventListener("click", (e) => this.onLoadPreset(e));1766 }1767 if (haveAdvancedPresets) {1768 for (let i of this.$all("div#advanced .presetsTable a"))1769 i.addEventListener("click", (e) => this.onLoadPreset(e));1770 }1771 this.generateColumnHelp();1772}1773$(selector) { return this.container.querySelector(selector); }1774$all(selector) { return this.container.querySelectorAll(selector); }1775// Called from the parent class1776enableOrDisable(isEnabled)1777{1778 this.disabled = !isEnabled;1779 this.$("textarea#filter").disabled = this.disabled;1780 this.$("button#save").disabled = this.disabled;1781 this.$("button#clear").disabled = this.disabled;1782 this.$("button#convert").disabled = this.disabled;1783 this.$("button#deleteAll").disabled = this.disabled;1784 this.$("button#toggleJSON").disabled = this.disabled;1785 this.$("button#saveJSON").disabled = this.disabled;1786 this.$("button#new").disabled = this.disabled;1787 // Filter editor row elements1788 for (let row of this.$all("table.filtersTable tr.row")) {1789 const f = this.filters[row.dataset.id];1790 if (f.isNew)1791 continue;1792 row.children[0].children[0].children[0].disabled = this.disabled; // delete1793 row.children[0].children[0].children[1].disabled = this.disabled; // duplicate1794 row.children[1].children[0].disabled = this.disabled; // active1795 }1796}1797// Switch between traditional and advanced filtering modes1798toggleMode(advanced)1799{1800 if (this.isAdvanced == advanced)1801 return;1802 this.isAdvanced = advanced;1803 if (this.isAdvanced) {1804 this.$("div#traditional").classList.add("hidden");1805 this.$("div#advanced").classList.remove("hidden");1806 } else {1807 this.$("div#traditional").classList.remove("hidden");1808 this.$("div#advanced").classList.add("hidden");1809 }1810 this.parentClass.updateFiltering();1811}1812// Load a filter preset1813onLoadPreset(e)1814{1815 e.preventDefault();1816 const id = e.target.dataset.id;1817 const preset = this.filterPresets[this.isAdvanced ? 1 : 0][id];1818 if (!preset) {1819 window.alert(`Invalid preset ID "${id}". Please contact Opinsys support.`);1820 return;1821 }1822 if (this.isAdvanced) {1823 let f = preset.filter;1824 if (this.$("input#add-parenthesis").checked)1825 f = `(${f})`;1826 let box = this.$("textarea#filter");1827 if (this.$("input#append-at-end-advanced").checked == false)1828 box.value = f;1829 else {1830 // Append or replace?1831 if (box.value.trim().length == 0)1832 box.value = f;1833 else {1834 box.value += "\n";1835 box.value += f;1836 }1837 }1838 this.clearMessages();1839 this.changed = true;1840 this.updateUnsavedWarning();1841 } else {1842 // Append or replace?1843 if (this.$("input#append-at-end").checked == false)1844 this.setFilters(preset.filters);1845 else this.setFilters(this.getFilters().concat(preset.filters));1846 this.parentClass.saveFilters();1847 this.parentClass.updateFiltering();1848 }1849}1850// Compiles a filter expression and returns the compiled comparisons and RPN code in an array.1851// This does not actually USE the filter for anything, it only compiles the given string.1852compileFilterExpression(input)1853{1854 console.log("----- Compiling filter string -----");1855 console.log("Input:");1856 console.log(input);1857 const t0 = performance.now();1858 this.clearMessages();1859 if (input.trim() == "") {1860 // Do nothing if there's nothing to compile1861 console.log("(Doing nothing to an empty string)");1862 return [[], []];1863 }1864 let logger = new MessageLogger();1865 // ----------------------------------------------------------------------------------------------1866 // Tokenization1867 let t = new Tokenizer();1868 console.log("----- Tokenization -----");1869 t.tokenize(logger, this.columnDefinitions, input);1870 if (!logger.empty()) {1871 for (const m of logger.messages) {1872 if (m.message == "unexpected_end") {1873 // Don't report the same error multiple times1874 this.listMessages(logger);1875 return null;1876 }1877 }1878 }1879 console.log("Raw tokens:");1880 if (t.tokens.length == 0)1881 console.log(" (NONE)");1882 else console.log(t.tokens);1883 // ----------------------------------------------------------------------------------------------1884 // Syntax analysis and comparison extraction1885 let p = new Parser();1886 console.log("----- Syntax analysis/parsing -----");1887 // TODO: Should we abort the compilation if this fails? Now we just cram ahead at full speed1888 // and hope for the best.1889 p.parse(logger, this.columnDefinitions, t.tokens, t.lastRow, t.lastCol);1890 console.log("Raw comparisons:");1891 if (p.comparisons.length == 0)1892 console.log(" (NONE)");1893 else console.log(p.comparisons);1894 console.log("Raw parser output:");1895 if (p.output.length == 0)1896 console.log(" (NONE)");1897 else console.log(p.output);1898 // ----------------------------------------------------------------------------------------------1899 // Compile the actual comparisons1900 let comparisons = [];1901 console.log("----- Compiling the comparisons -----");1902 let cc = new ComparisonCompiler();1903 for (const raw of p.comparisons) {1904 const c = cc.compile(logger, this.columnDefinitions, raw.column, raw.operator, raw.value);1905 if (c === null) {1906 // null == the comparison was so invalid it could not even be parsed1907 // log it for debugging1908 console.error(raw);1909 continue;1910 }1911 if (c === false) {1912 // false == the comparison was syntactically okay, but it wasn't actually correct1913 console.warn("Could not compile comparison");1914 console.warn(raw);1915 continue;1916 }1917 comparisons.push(c);1918 }1919 if (!logger.empty()) {1920 this.listMessages(logger);1921 if (logger.haveErrors()) {1922 // Warnings won't stop the filter string from saved or used1923 console.error("Comparison compilation failed, no filter program produced");1924 return null;1925 }1926 }1927 console.log("Compiled comparisons:");1928 console.log(comparisons);1929 let program = [];1930 console.log("----- Shunting Yard -----");1931 // Generate code1932 let cg = new CodeGenerator();1933 program = cg.compile(p.output);1934 console.log("Final filter program:");1935 if (program.length == 0)1936 console.log(" (Empty)");1937 for (let i = 0; i < program.length; i++) {1938 const o = program[i];1939 switch (o[0]) {1940 case "!":1941 console.log(`(${i}) NEG`);1942 break;1943 case "&":1944 console.log(`(${i}) AND`);1945 break;1946 case "|":1947 console.log(`(${i}) OR`);1948 break;1949 default: {1950 const cmp = comparisons[o[0]];1951 console.log(`(${i}) CMP [${cmp.column} ${cmp.operator} ${cmp.value.toString()}]`);1952 break;1953 }1954 }1955 }1956 const t1 = performance.now();1957 console.log(`Filter expression compiled to ${program.length} opcode(s), ${comparisons.length} comparison evaluator(s)`);1958 console.log(`Filter expression compilation: ${t1 - t0} ms`);1959 return [comparisons, program];1960}1961getFilterProgram()1962{1963 return {1964 comparisons: [...(this.isAdvanced ? this.comparisonsAdvanced : this.comparisons)],1965 program: [...(this.isAdvanced ? this.programAdvanced : this.program)]1966 };1967}1968// --------------------------------------------------------------------------------------------------1969// --------------------------------------------------------------------------------------------------1970// "TRADITIONAL" FILTERS1971// Loads filters from an array, updates the table and builds the filter program1972setFilters(raw)1973{1974 this.filters = {};1975 let table = this.$("table.filtersTable");1976 table.innerHTML = "";1977 for (const r of (Array.isArray(raw) ? raw : [])) {1978 let e = new EditableFilter();1979 if (!e.load(r, this.plainColumnDefinitions))1980 continue;1981 const id = nextFilterID();1982 this.filters[id] = e;1983 let row = this.buildFilterRow(id, e);1984 this.setFilterRowEvents(row);1985 table.appendChild(row);1986 }1987 this.updateJSON();1988 this.convertAndCompileFilters();1989}1990getFilters()1991{1992 let out = [];1993 for (const row of this.$all("table.filtersTable tr.row")) {1994 const f = this.filters[row.dataset.id];1995 if (!f.isNew)1996 out.push(f.save());1997 }1998 return out;1999}2000onDeleteAllFilters()2001{2002 if (!window.confirm(_tr("tabs.filtering.delete_all_confirm")))2003 return;2004 this.filters = {};2005 this.$("table.filtersTable").innerHTML = "";2006 this.updateJSON();2007 this.convertAndCompileFilters();2008 this.parentClass.saveFilters();2009 this.parentClass.updateFiltering();2010}2011onToggleJSON()2012{2013 let box = this.$("textarea#json"),2014 button = this.$("button#saveJSON");2015 this.showJSON = !this.showJSON;2016 if (this.showJSON)2017 showElements(box, button);2018 else hideElements(box, button);2019 this.$("button#toggleJSON").innerText =2020 _tr("tabs.filtering." + (this.showJSON ? "hide_json" : "show_json"));2021}2022onSaveJSON()2023{2024 if (!window.confirm(_tr("tabs.filtering.save_json_confirm")))2025 return;2026 try {2027 this.setFilters(JSON.parse(this.$("textarea#json").value));2028 this.convertAndCompileFilters();2029 this.parentClass.saveFilters();2030 this.parentClass.updateFiltering();2031 } catch (e) {2032 window.alert(e);2033 }2034}2035updateJSON()2036{2037 this.$("textarea#json").value = JSON.stringify(this.getFilters());2038 this.$("textarea#json").classList.remove("invalidJSON");2039 this.$("button#saveJSON").disabled = false;2040}2041validateJSON()2042{2043 let box = this.$("textarea#json");2044 // Is the JSON parseable?2045 try {2046 JSON.parse(box.value);2047 box.classList.remove("invalidJSON");2048 this.$("button#saveJSON").disabled = false;2049 } catch (e) {2050 box.classList.add("invalidJSON");2051 this.$("button#saveJSON").disabled = true;2052 }2053}2054// Pretty-prints a filter row2055prettyPrintFilter(filter)2056{2057 const colDef = this.plainColumnDefinitions[filter.column],2058 operator = OPERATORS[filter.operator];2059 function formatValue(v)2060 {2061 if (colDef.type == ColumnType.UNIXTIME) {2062 const d = parseAbsoluteOrRelativeDate(v);2063 if (d === null)2064 return "?";2065 return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +2066 `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;2067 }2068 if (colDef.flags & ColumnFlag.F_STORAGE) {2069 if (v.length == 0)2070 return "";2071 let unit = v.slice(v.length - 1);2072 if (!"BKMGT".includes(unit))2073 return `${v} B`;2074 switch (unit) {2075 case "B": unit = "B"; break;2076 case "K": unit = "KiB"; break;2077 case "M": unit = "MiB"; break;2078 case "G": unit = "GiB"; break;2079 case "T": unit = "TiB"; break;2080 }2081 return `${v.slice(0, v.length - 1)} ${unit}`2082 }2083 return v;2084 }2085 const prettyTrue = _tr("tabs.filtering.ed.bool.t"),2086 prettyFalse = _tr("tabs.filtering.ed.bool.f"),2087 prettyEmpty = _tr("tabs.filtering.pretty.empty"),2088 prettyOr = _tr("tabs.filtering.pretty.or"),2089 prettyNor = _tr("tabs.filtering.pretty.nor");2090 let html = "";2091 html += `<span class="column">${this.columnTitles[filter.column]}</span>`;2092 html += `<span class="operator">${humanOperatorName(filter.operator)}</span>`;2093 html += `<span class="values">`2094 if (filter.operator == "[]" || filter.operator == "![]") {2095 html += `<span class="value">`;2096 html += formatValue(filter.values[0]);2097 html += `</span><span class="sep"> − </span><span class="value">`;2098 html += formatValue(filter.values[1]);2099 html += `</span>`;2100 } else {2101 if (colDef.type == ColumnType.BOOL)2102 html += `<span class="value">${filter.values[0] === 1 ? prettyTrue : prettyFalse}</span>`;2103 else {2104 for (let i = 0, j = filter.values.length; i < j; i++) {2105 if (filter.values[i].length == 0 && colDef.type == ColumnType.STRING)2106 html += `<span class="value empty">${prettyEmpty}</span>`;2107 else {2108 html += `<span class="value">`;2109 html += formatValue(filter.values[i]);2110 html += "</span>";2111 }2112 if (i + 1 < j - 1)2113 html += `<span class="sep">, </span>`;2114 else if (i + 1 < j) {2115 if (filter.operator == "!=")2116 html += `<span class="sep"> ${prettyNor} </span>`;2117 else html += `<span class="sep"> ${prettyOr} </span>`;2118 }2119 }2120 }2121 }2122 html += "</span>";2123 return html;2124}2125buildFilterRow(id, filter)2126{2127 let tr = document.createElement("tr");2128 tr.id = `row-${id}`;2129 tr.className = "row";2130 tr.dataset.id = id;2131 tr.innerHTML =2132`<td class="minimize-width"><div class="buttons">2133<button class="danger" title="${_tr("tabs.filtering.remove_title")}" ${filter.isNew ? "disabled" : ""}>${_tr("tabs.filtering.remove")}</button>2134<button title="${_tr("tabs.filtering.duplicate_title")}" ${filter.isNew ? "disabled" : ""}>${_tr("tabs.filtering.duplicate")}</button></div></td>2135<td class="minimize-width" title="${_tr("tabs.filtering.active_title")}">2136<input type="checkbox" class="active" ${filter.active == 1 ? "checked" : ""} ${filter.isNew ? "disabled" : ""}>2137</td><td><div class="flex flex-rows"><div class="pretty" title="${_tr("tabs.filtering.click_to_edit_title")}">2138${this.prettyPrintFilter(filter)}</div><div></div></td>`;2139 return tr;2140}2141findTableRow(element)2142{2143 return element.closest(`tr[id^="row-"]`);2144}2145getRowElem(row, what)2146{2147 switch (what) {2148 case RowElem.BTN_DELETE:2149 return row.children[0].children[0].children[0];2150 case RowElem.BTN_DUPLICATE:2151 return row.children[0].children[0].children[1];2152 case RowElem.CB_ACTIVE:2153 return row.children[1].children[0];2154 case RowElem.DIV_PRETTY:2155 return row.children[2].children[0].children[0];2156 case RowElem.DIV_EDITOR:2157 return row.children[2].children[0].children[1];2158 default:2159 return null;2160 }2161}2162setFilterRowEvents(row)2163{2164 this.getRowElem(row, RowElem.BTN_DELETE).addEventListener("click", e => this.onDeleteFilter(e));2165 this.getRowElem(row, RowElem.BTN_DUPLICATE).addEventListener("click", e => this.onDuplicateFilter(e));2166 this.getRowElem(row, RowElem.CB_ACTIVE).addEventListener("click", e => this.onActiveFilter(e));2167 this.getRowElem(row, RowElem.DIV_PRETTY).addEventListener("click", e => {2168 let row = this.findTableRow(e.target);2169 this.filters[row.dataset.id].beginEditing();2170 this.openFilterEditor(row);2171 });2172}2173onDeleteFilter(e)2174{2175 let tr = this.findTableRow(e.target);2176 const id = tr.dataset.id;2177 const wasActive = this.filters[id].active;2178 delete this.filters[id];2179 tr.parentNode.removeChild(tr);2180 this.updateJSON();2181 this.parentClass.saveFilters();2182 // Deleting disabled filters don't trigger table rebuilds2183 if (wasActive) {2184 this.convertAndCompileFilters();2185 this.parentClass.updateFiltering();2186 }2187}2188onDuplicateFilter(e)2189{2190 let tr = this.findTableRow(e.target);2191 const id = tr.dataset.id;2192 let dupe = new EditableFilter();2193 if (!dupe.load(this.filters[id].save(), this.plainColumnDefinitions)) {2194 window.alert("Filter duplication failed");2195 return;2196 }2197 dupe.active = 0; // prevent double filtering2198 const newID = nextFilterID();2199 this.filters[newID] = dupe;2200 let newRow = this.buildFilterRow(newID, dupe);2201 tr.parentNode.insertBefore(newRow, tr.nextSibling);2202 this.setFilterRowEvents(newRow);2203 this.updateJSON();2204 this.parentClass.saveFilters();2205 // The duplicated row is disabled, don't update the table2206}2207onActiveFilter(e)2208{2209 this.filters[this.findTableRow(e.target).dataset.id].active ^= 1;2210 this.updateJSON();2211 this.convertAndCompileFilters();2212 this.parentClass.saveFilters();2213 this.parentClass.updateFiltering();2214}2215openFilterEditor(row)2216{2217 const id = row.dataset.id;2218 let filter = this.filters[id];2219 hideElements(this.getRowElem(row, RowElem.DIV_PRETTY));2220 // Construct an editor interface2221 let wrapper = this.getRowElem(row, RowElem.DIV_EDITOR);2222 wrapper.innerHTML =2223`<div class="flex flex-rows">2224<div class="openFilter flex flex-columns flex-gap-5px">2225<select id="column" title="${_tr("tabs.filtering.edit_column_title")}"></select>2226<select id="operator" title="${_tr("tabs.filtering.edit_operator_title")}"></select>2227<button id="save" class="margin-left-20px"><i class="icon-ok"></i>${_tr("tabs.filtering.save")}</button>2228<button id="cancel"><i class="icon-cancel"></i>${_tr("tabs.filtering.cancel")}</button>2229</div><div id="editor" class="editor"></div></div>`;2230 wrapper.classList.add("editorWrapper");2231 // Sort the columns in alphabetical order2232 let select = wrapper.querySelector("select#column"),2233 columns = [];2234 for (const column of Object.keys(this.plainColumnDefinitions))2235 columns.push([column, this.columnTitles[column]]);2236 columns.sort((a, b) => { return a[1].localeCompare(b[1]) });2237 for (const [column, title] of columns) {2238 let o = document.createElement("option");2239 o.innerText = title;2240 o.dataset.column = column;2241 o.selected = (filter.editColumn == column);2242 select.appendChild(o);2243 }2244 const colDef = this.plainColumnDefinitions[filter.editColumn];2245 this.fillOperatorSelector(wrapper.querySelector("select#operator"),2246 colDef.type, filter.editOperator);2247 // Initial type-specific editor child UI2248 this.buildValueEditor(filter, wrapper.querySelector("div#editor"), colDef);2249 wrapper.querySelector("select#column").addEventListener("change", (e) => this.onColumnChanged(e));2250 wrapper.querySelector("select#operator").addEventListener("change", (e) => this.onOperatorChanged(e));2251 wrapper.querySelector("button#save").addEventListener("click", (e) => {2252 let row = this.findTableRow(e.target);2253 const id = row.dataset.id;2254 // Don't save the filter if the value (or values) is incorrect2255 const valid = this.filters[id].editor.validate();2256 if (!valid[0]) {2257 window.alert(valid[1]);2258 return;2259 }2260 this.filters[id].finishEditing();2261 this.filters[id].isNew = false; // enable normal functionality2262 this.closeFilterEditor(row);2263 this.getRowElem(row, RowElem.BTN_DELETE).disabled = false;2264 this.getRowElem(row, RowElem.BTN_DUPLICATE).disabled = false;2265 this.getRowElem(row, RowElem.CB_ACTIVE).disabled = false;2266 this.getRowElem(row, RowElem.DIV_PRETTY).innerHTML = this.prettyPrintFilter(this.filters[id]);2267 this.updateJSON();2268 this.parentClass.saveFilters();2269 if (this.filters[id].active) {2270 this.convertAndCompileFilters();2271 this.parentClass.updateFiltering();2272 }2273 });2274 wrapper.querySelector("button#cancel").addEventListener("click", (e) => {2275 let row = this.findTableRow(e.target);2276 let filter = this.filters[row.dataset.id];2277 const wasNew = filter.isNew;2278 filter.cancelEditing();2279 if (wasNew)2280 row.parentNode.removeChild(row);2281 else this.closeFilterEditor(row);2282 });2283}2284closeFilterEditor(row)2285{2286 this.filters[row.dataset.id].editor = null;2287 let editor = this.getRowElem(row, RowElem.DIV_EDITOR);2288 editor.innerHTML = "";2289 editor.classList.remove("editorWrapper");2290 showElements(this.getRowElem(row, RowElem.DIV_PRETTY));2291}2292buildValueEditor(filter, container, colDef)2293{2294 const editors = {2295 [ColumnType.BOOL]: FilterEditorBoolean,2296 [ColumnType.NUMERIC]: FilterEditorNumeric,2297 [ColumnType.STRING]: FilterEditorString,2298 [ColumnType.UNIXTIME]: FilterEditorUnixtime,2299 };2300 if (colDef.type in editors) {2301 filter.editor = new editors[colDef.type](container, filter, colDef);2302 filter.editor.buildUI();2303 } else throw new Error(`Unknown column type ${colDef.type}`);2304}2305// Attempts to preserve the current filter values between operator/column changes and applies2306// fixes to the data to ensure the current operator has enough data to work with2307preserveFilterData(filter)2308{2309 if (!filter.editor) {2310 console.warn("preserveFilterData(): no editor?");2311 return;2312 }2313 // Grab the values from the form first2314 filter.editValues = filter.editor.getData();2315 // Then ensure there are enough values2316 if (filter.editValues.length == 0)2317 filter.editValues.push(getDefaultValue(this.plainColumnDefinitions[filter.editColumn]));2318 if ((filter.editOperator == "[]" || filter.editOperator == "![]") && filter.editValues.length < 2)2319 filter.editValues.push(filter.editValues[0]);2320}2321onColumnChanged(e)2322{2323 let row = this.findTableRow(e.target);2324 const id = row.dataset.id;2325 let filter = this.filters[id];2326 let wrapper = this.getRowElem(row, RowElem.DIV_EDITOR);2327 filter.editColumn = e.target[e.target.selectedIndex].dataset.column;2328 // Is the previous operator still valid for this type? If not, reset it to "=",2329 // it's the default (and the safest) operator.2330 const newDef = this.plainColumnDefinitions[filter.editColumn];2331 if (!OPERATORS[filter.editOperator].allowed.has(newDef.type))2332 filter.editOperator = "=";2333 // Refill the operator selector2334 // TODO: Don't do this if the new column has the same operators available2335 // as the previous column did.2336 this.fillOperatorSelector(wrapper.querySelector("select#operator"),2337 newDef.type, filter.editOperator);2338 this.preserveFilterData(filter);2339 // Recreate the editor UI2340 let editor = wrapper.querySelector("div#editor");2341 editor.innerHTML = "";2342 filter.editor = null;2343 this.buildValueEditor(filter, editor, newDef);2344}2345onOperatorChanged(e)2346{2347 const row = this.findTableRow(e.target);2348 const operator = e.target[e.target.selectedIndex].dataset.operator;2349 let filter = this.filters[row.dataset.id];2350 filter.editOperator = operator;2351 this.preserveFilterData(filter);2352 filter.editor.operatorHasChanged(operator);2353}2354fillOperatorSelector(target, type, initial)2355{2356 target.innerHTML = "";2357 for (const opId of ["=", "!=", "<", "<=", ">", ">=", "[]", "![]"]) {2358 if (OPERATORS[opId].allowed.has(type)) {2359 let o = document.createElement("option");2360 o.innerText = humanOperatorName(opId);2361 o.dataset.operator = opId;2362 o.selected = (opId == initial);2363 target.appendChild(o);2364 }2365 }2366}2367onNewFilter()2368{2369 let f = new EditableFilter();2370 let initial = null;2371 if (this.defaultFilter === undefined || this.defaultFilter === null || this.defaultFilter.length < 4) {2372 // Use the first available column. Probably not the best, but at least the filter will be valid.2373 initial = [0, Object.keys(this.plainColumnDefinitions)[0], "=", ""];2374 } else initial = [...this.defaultFilter];2375 initial[3] = getDefaultValue(this.plainColumnDefinitions[initial[1]]);2376 if (!f.load(initial, this.plainColumnDefinitions)) {2377 window.alert("Filter creation failed. See the console for details.");2378 return;2379 }2380 f.isNew = true; // disables certain UI elements and makes the Cancel button remove the filter2381 const newID = nextFilterID();2382 this.filters[newID] = f;2383 let newRow = this.buildFilterRow(newID, f);2384 this.setFilterRowEvents(newRow);2385 this.$("table.filtersTable").appendChild(newRow);2386 // Open the newly-created filter for editing2387 this.filters[newID].beginEditing();2388 this.openFilterEditor(newRow);2389}2390convertTraditionalFilter(filters)2391{2392 let parts = [];2393 for (const f of filters) {2394 if (!f[0]) // inactive filter2395 continue;2396 if (f.length < 4) // incomplete filter2397 continue;2398 let col = f[1],2399 op = f[2],2400 val = [];2401 const colDef = this.plainColumnDefinitions[col];2402 // Convert the value2403 for (let v of f.slice(3)) {2404 switch (colDef.type) {2405 case ColumnType.BOOL:2406 if (v.length == 0)2407 continue;2408 val.push(v === 1 ? '1' : '0');2409 break;2410 case ColumnType.NUMERIC:2411 // All possible values should work fine, even storage units, without quotes2412 if (v.length == 0)2413 continue;2414 val.push(v);2415 break;2416 case ColumnType.UNIXTIME:2417 if (v.length == 0)2418 continue;2419 // Absolute times must be quoted, relative times should work as-is2420 val.push(ABSOLUTE_TIME.exec(v) !== null ? `"${v}"` : v);2421 break;2422 case ColumnType.STRING:2423 default:2424 // Convert strings to regexps2425 if (v == "")2426 val.push(`/^$/`);2427 else val.push(`/${v}/`);2428 break;2429 }2430 }2431 // Output a comparison with the converted value2432 if (op == "[]") {2433 // include (closed)2434 if (val.length < 2)2435 continue;2436 parts.push(`(${col} >= ${val[0]} && ${col} <= ${val[1]})`);2437 } else if (op == "![]") {2438 // exclude (open)2439 if (val.length < 2)2440 continue;2441 parts.push(`(${col} < ${val[0]} || ${col} > ${val[1]})`);2442 } else {2443 if (val.length < 1)2444 continue;2445 if (val.length == 1) {2446 // a single value2447 parts.push(`${col} ${op} ${val[0]}`);2448 } else {2449 // multiple values, either OR'd or AND'd together depending on the operator2450 let sub = [];2451 for (const v of val)2452 sub.push(`${col} ${op} ${v}`);2453 if (op == "=")2454 sub = sub.join(" || ");2455 else sub = sub.join(" && ");2456 parts.push("(" + sub + ")");2457 }2458 }2459 }2460 // Join the comparisons together2461 return parts.join(" && ");2462}2463// Converts the "traditional" filters into an advanced filter string and compiles it2464convertAndCompileFilters()2465{2466 const result = this.compileFilterExpression(this.convertTraditionalFilter(this.getFilters()));2467 if (result === false || result === null) {2468 window.alert("Could not compile the filter. See the console for details, then contact Opinsys support.");2469 return;2470 }2471 this.comparisons = result[0];2472 this.program = result[1];2473}2474// --------------------------------------------------------------------------------------------------2475// --------------------------------------------------------------------------------------------------2476// ADVANCED FILTERS2477setFilterString(filter)2478{2479 let box = this.$("textarea#filter");2480 if (typeof(filter) != "string")2481 box.value = "";2482 else box.value = filter;2483 this.comparisonsAdvanced = [];2484 this.programAdvanced = [];2485 const result = this.compileFilterExpression(box.value);2486 if (result === false || result === null)2487 return;2488 this.comparisonsAdvanced = result[0];2489 this.programAdvanced = result[1];2490}2491getFilterString()2492{2493 return this.$("textarea#filter").value;2494}2495// Save the advanced filter string2496onSave()2497{2498 const result = this.compileFilterExpression(this.$("textarea#filter").value);2499 if (result === false || result === null)2500 return;2501 this.comparisonsAdvanced = result[0];2502 this.programAdvanced = result[1];2503 this.changed = false;2504 this.updateUnsavedWarning();2505 this.parentClass.saveFilters();2506 this.parentClass.updateFiltering();2507}2508// Clear the advanced filter2509onClear()2510{2511 if (window.confirm(_tr('are_you_sure'))) {2512 this.$("textarea#filter").value = "";2513 this.clearMessages();2514 this.changed = true;2515 this.updateUnsavedWarning();2516 }2517}2518// Convert the traditional (mouse-driven) filter into a filter expression string2519onConvertTraditionalFilter()2520{2521 if (!window.confirm(_tr('are_you_sure')))2522 return;2523 const filters = this.getFilters();2524 if (filters === null || filters === undefined || filters.length == 0) {2525 window.alert(_tr('traditional_filter_is_empty'));2526 return;2527 }2528 const result = this.convertTraditionalFilter(filters);2529 if (result === false || result === null) {2530 window.alert("Could not compile the filter. See the console for details, then contact Opinsys support.");2531 return;2532 }2533 this.$("textarea#filter").value = result;2534 this.clearMessages();2535 this.changed = true;2536 this.updateUnsavedWarning();2537}2538// Advanced filter string has changed2539onAdvancedInput()2540{2541 this.changed = true;2542 this.updateUnsavedWarning();2543}2544// Copy the column name to the advanced filter string box2545onClickColumnName(e)2546{2547 e.preventDefault();2548 this.$("textarea#filter").value += e.target.dataset.column + " ";2549}2550updateUnsavedWarning()2551{2552 let legend = this.$("fieldset legend");2553 if (!legend)2554 return;2555 let html = _tr('tabs.filtering.expression_title');2556 if (this.changed)2557 html += ` <span class="unsaved">[${_tr('tabs.filtering.unsaved')}]</span>`;2558 legend.innerHTML = html;2559}2560generateColumnHelp()2561{2562 const COLUMN_TYPES = {2563 [ColumnType.BOOL]: _tr('tabs.filtering.column_list.type_bool'),2564 [ColumnType.NUMERIC]: _tr('tabs.filtering.column_list.type_numeric'),2565 [ColumnType.UNIXTIME]: _tr('tabs.filtering.column_list.type_unixtime'),2566 [ColumnType.STRING]: _tr('tabs.filtering.column_list.type_string'),2567 };2568 let html =2569`<table class="commonTable columnHelp"><thead><tr>2570<th>${_tr('tabs.filtering.column_list.pretty_name')}</th>2571<th>${_tr('tabs.filtering.column_list.database_name')}</th>2572<th>${_tr('tabs.filtering.column_list.type')}</th>2573<th>${_tr('tabs.filtering.column_list.operators')}</th>2574<th>${_tr('tabs.filtering.column_list.nullable')}</th>2575</tr></thead><tbody>`;2576 let columnNames = [];2577 for (const key of Object.keys(this.plainColumnDefinitions))2578 columnNames.push([key, this.columnTitles[key]]);2579 columnNames.sort((a, b) => { return a[1].localeCompare(b[1]) });2580 for (const col of columnNames) {2581 html += `<tr><td>${col[1]}</td><td>`;2582 const nullable = this.plainColumnDefinitions[col[0]].flags & ColumnFlag.F_NULLABLE;2583 let fields = Array.from(this.columnDefinitions.getAliases(col[0]));2584 fields.sort();2585 fields.unshift(col[0]);2586 html += fields.map((f) => `<a href="#" data-column="${f}">${f}</a>`).join("<br>");2587 html += "</td>";2588 const type = this.plainColumnDefinitions[col[0]].type;2589 html += `<td>${COLUMN_TYPES[type]}</td>`;2590 html += "<td>";2591 const ops = Array.from(ALLOWED_OPERATORS[type]);2592 for (let i = 0, j = ops.length; i < j; i++) {2593 if (ops[i] == "!!" && !nullable)2594 continue;2595 html += `<code>${escapeHTML(ops[i])}</code>`;2596 if (i + 1 < j)2597 html += " ";2598 }2599 html += "</td>";2600 if (nullable)2601 html += `<td>${_tr('tabs.filtering.column_list.is_nullable')}</td>`;2602 else html += "<td></td>";2603 html += "</tr>";2604 }2605 html += "</tbody></table>";2606 let cont = this.$("div#columnList");2607 // Remove old event handlers first2608 if (cont.firstChild)2609 for (let a of cont.querySelectorAll("a"))2610 a.removeEventListener("click", this.onClickColumnName);2611 cont.innerHTML = html;2612 // Then set up new event handlers2613 for (let a of cont.querySelectorAll("a"))2614 a.addEventListener("click", this.onClickColumnName);2615}2616clearMessages()2617{2618 this.$("#messages").innerHTML = `<p class="margin-0 padding-0">${_tr('tabs.filtering.no_messages')}</p>`;2619}2620// Update the advanced filter compilation messages box2621listMessages(logger)2622{2623 if (logger.empty())2624 return;2625 let html =2626`<table class="commonTable messages width-100p"><thead><tr>2627<th>${_tr('tabs.filtering.row')}</th>2628<th>${_tr('tabs.filtering.column')}</th>2629<th>${_tr('tabs.filtering.message')}</th>2630</tr></thead><tbody>`;2631 // The messages aren't necessarily in any particular order, sort them2632 const sorted = [...logger.messages].sort(function(a, b) { return a.row - b.row || a.col - b.col });2633 for (const e of sorted) {2634 let cls = [];2635 if (e.type == 'error')2636 cls.push("error");2637 html +=2638`<tr class="${cls.join(' ')}" data-pos="${e.pos}" data-len="${e.len}">2639<td class="minimize-width align-center">${e.row == -1 ? "" : e.row}</td>2640<td class="minimize-width align-center">${e.col == -1 ? "" : e.col}</td>`;2641 html += "<td>";2642 html += _tr('tabs.filtering.' + e.type) + ": ";2643 html += _tr('tabs.filtering.messages.' + e.message);2644 if (e.extra !== null)2645 html += `<br>(${e.extra})`;2646 html += "</td></tr>";2647 }2648 html += "</tbody></table>";2649 this.$("#messages").innerHTML = html;2650 // Add event listeners. I'm 99% certain this leaks memory, but I'm not sure how to fix it.2651 for (let row of this.$all(`table.messages tbody tr`))2652 row.addEventListener("click", (e) => this.highlightMessage(e));2653}2654highlightMessage(e)2655{2656 // Find the target table row. Using "pointer-events" to pass through clicks works, but2657 // it makes browsers not display the "text" cursor when hovering the table and that is2658 // just wrong.2659 let elem = e.target;2660 while (elem && elem.nodeName != "TR")2661 elem = elem.parentNode;2662 if (!elem) {2663 console.error("highlightMessage(): can't find the clicked table row");2664 return;2665 }2666 // Highlight the target2667 const pos = parseInt(elem.dataset.pos, 10),2668 len = parseInt(elem.dataset.len, 10);2669 let t = this.$("textarea#filter");2670 if (!t) {2671 console.error("highlightMessage(): can't find the textarea element");2672 return;2673 }2674 t.focus();2675 if (len == -1) {2676 // Move the cursor to the end2677 t.setSelectionRange(t.value.length, t.value.length);2678 } else {2679 t.selectionStart = pos;2680 t.selectionEnd = pos + len;2681 }2682}...

Full Screen

Full Screen

cssTokenizer.js

Source:cssTokenizer.js Github

copy

Full Screen

...247 consume();248 return new DashMatchToken();249 } else if(next() == 0x7c) {250 consume();251 return new ColumnToken();252 } else {253 return new DelimToken(code);254 }255 }256 else if(code == 0x7d) return new CloseCurlyToken();257 else if(code == 0x7e) {258 if(next() == 0x3d) {259 consume();260 return new IncludeMatchToken();261 } else {262 return new DelimToken(code);263 }264 }265 else if(digit(code)) {266 reconsume();267 return consumeANumericToken();268 }269 else if(namestartchar(code)) {270 reconsume();271 return consumeAnIdentlikeToken();272 }273 else if(eof()) return new EOFToken();274 else return new DelimToken(code);275 };276 var consumeComments = function() {277 while(next(1) == 0x2f && next(2) == 0x2a) {278 consume(2);279 while(true) {280 consume();281 if(code == 0x2a && next() == 0x2f) {282 consume();283 break;284 } else if(eof()) {285 parseerror();286 return;287 }288 }289 }290 };291 var consumeANumericToken = function() {292 var num = consumeANumber();293 if(wouldStartAnIdentifier(next(1), next(2), next(3))) {294 var token = new DimensionToken();295 token.value = num.value;296 token.repr = num.repr;297 token.type = num.type;298 token.unit = consumeAName();299 return token;300 } else if(next() == 0x25) {301 consume();302 var token = new PercentageToken();303 token.value = num.value;304 token.repr = num.repr;305 return token;306 } else {307 var token = new NumberToken();308 token.value = num.value;309 token.repr = num.repr;310 token.type = num.type;311 return token;312 }313 };314 var consumeAnIdentlikeToken = function() {315 var str = consumeAName();316 if(str.toLowerCase() == "url" && next() == 0x28) {317 consume();318 while(whitespace(next(1)) && whitespace(next(2))) consume();319 if(next() == 0x22 || next() == 0x27) {320 return new FunctionToken(str);321 } else if(whitespace(next()) && (next(2) == 0x22 || next(2) == 0x27)) {322 return new FunctionToken(str);323 } else {324 return consumeAURLToken();325 }326 } else if(next() == 0x28) {327 consume();328 return new FunctionToken(str);329 } else {330 return new IdentToken(str);331 }332 };333 var consumeAStringToken = function(endingCodePoint) {334 if(endingCodePoint === undefined) endingCodePoint = code;335 var string = "";336 while(consume()) {337 if(code == endingCodePoint || eof()) {338 return new StringToken(string);339 } else if(newline(code)) {340 parseerror();341 reconsume();342 return new BadStringToken();343 } else if(code == 0x5c) {344 if(eof(next())) {345 donothing();346 } else if(newline(next())) {347 consume();348 } else {349 string += stringFromCode(consumeEscape())350 }351 } else {352 string += stringFromCode(code);353 }354 }355 };356 var consumeAURLToken = function() {357 var token = new URLToken("");358 while(whitespace(next())) consume();359 if(eof(next())) return token;360 while(consume()) {361 if(code == 0x29 || eof()) {362 return token;363 } else if(whitespace(code)) {364 while(whitespace(next())) consume();365 if(next() == 0x29 || eof(next())) {366 consume();367 return token;368 } else {369 consumeTheRemnantsOfABadURL();370 return new BadURLToken();371 }372 } else if(code == 0x22 || code == 0x27 || code == 0x28 || nonprintable(code)) {373 parseerror();374 consumeTheRemnantsOfABadURL();375 return new BadURLToken();376 } else if(code == 0x5c) {377 if(startsWithAValidEscape()) {378 token.value += stringFromCode(consumeEscape());379 } else {380 parseerror();381 consumeTheRemnantsOfABadURL();382 return new BadURLToken();383 }384 } else {385 token.value += stringFromCode(code);386 }387 }388 };389 var consumeEscape = function() {390 // Assume the the current character is the \391 // and the next code point is not a newline.392 consume();393 if(hexdigit(code)) {394 // Consume 1-6 hex digits395 var digits = [code];396 for(var total = 0; total < 5; total++) {397 if(hexdigit(next())) {398 consume();399 digits.push(code);400 } else {401 break;402 }403 }404 if(whitespace(next())) consume();405 var value = parseInt(digits.map(function(x){return String.fromCharCode(x);}).join(''), 16);406 if( value > maximumallowedcodepoint ) value = 0xfffd;407 return value;408 } else if(eof()) {409 return 0xfffd;410 } else {411 return code;412 }413 };414 var areAValidEscape = function(c1, c2) {415 if(c1 != 0x5c) return false;416 if(newline(c2)) return false;417 return true;418 };419 var startsWithAValidEscape = function() {420 return areAValidEscape(code, next());421 };422 var wouldStartAnIdentifier = function(c1, c2, c3) {423 if(c1 == 0x2d) {424 return namestartchar(c2) || c2 == 0x2d || areAValidEscape(c2, c3);425 } else if(namestartchar(c1)) {426 return true;427 } else if(c1 == 0x5c) {428 return areAValidEscape(c1, c2);429 } else {430 return false;431 }432 };433 var startsWithAnIdentifier = function() {434 return wouldStartAnIdentifier(code, next(1), next(2));435 };436 var wouldStartANumber = function(c1, c2, c3) {437 if(c1 == 0x2b || c1 == 0x2d) {438 if(digit(c2)) return true;439 if(c2 == 0x2e && digit(c3)) return true;440 return false;441 } else if(c1 == 0x2e) {442 if(digit(c2)) return true;443 return false;444 } else if(digit(c1)) {445 return true;446 } else {447 return false;448 }449 };450 var startsWithANumber = function() {451 return wouldStartANumber(code, next(1), next(2));452 };453 var consumeAName = function() {454 var result = "";455 while(consume()) {456 if(namechar(code)) {457 result += stringFromCode(code);458 } else if(startsWithAValidEscape()) {459 result += stringFromCode(consumeEscape());460 } else {461 reconsume();462 return result;463 }464 }465 };466 var consumeANumber = function() {467 var repr = [];468 var type = "integer";469 if(next() == 0x2b || next() == 0x2d) {470 consume();471 repr += stringFromCode(code);472 }473 while(digit(next())) {474 consume();475 repr += stringFromCode(code);476 }477 if(next(1) == 0x2e && digit(next(2))) {478 consume();479 repr += stringFromCode(code);480 consume();481 repr += stringFromCode(code);482 type = "number";483 while(digit(next())) {484 consume();485 repr += stringFromCode(code);486 }487 }488 var c1 = next(1), c2 = next(2), c3 = next(3);489 if((c1 == 0x45 || c1 == 0x65) && digit(c2)) {490 consume();491 repr += stringFromCode(code);492 consume();493 repr += stringFromCode(code);494 type = "number";495 while(digit(next())) {496 consume();497 repr += stringFromCode(code);498 }499 } else if((c1 == 0x45 || c1 == 0x65) && (c2 == 0x2b || c2 == 0x2d) && digit(c3)) {500 consume();501 repr += stringFromCode(code);502 consume();503 repr += stringFromCode(code);504 consume();505 repr += stringFromCode(code);506 type = "number";507 while(digit(next())) {508 consume();509 repr += stringFromCode(code);510 }511 }512 var value = convertAStringToANumber(repr);513 return {type:type, value:value, repr:repr};514 };515 var convertAStringToANumber = function(string) {516 // CSS's number rules are identical to JS, afaik.517 return +string;518 };519 var consumeTheRemnantsOfABadURL = function() {520 while(consume()) {521 if(code == 0x29 || eof()) {522 return;523 } else if(startsWithAValidEscape()) {524 consumeEscape();525 donothing();526 } else {527 donothing();528 }529 }530 };531 var iterationCount = 0;532 while(!eof(next())) {533 tokens.push(consumeAToken());534 iterationCount++;535 if(iterationCount > str.length*2) return "I'm infinite-looping!";536 }537 return tokens;538}539function CSSParserToken() { throw "Abstract Base Class"; }540CSSParserToken.prototype.toJSON = function() {541 return {token: this.tokenType};542}543CSSParserToken.prototype.toString = function() { return this.tokenType; }544CSSParserToken.prototype.toSource = function() { return ''+this; }545function BadStringToken() { return this; }546BadStringToken.prototype = Object.create(CSSParserToken.prototype);547BadStringToken.prototype.tokenType = "BADSTRING";548function BadURLToken() { return this; }549BadURLToken.prototype = Object.create(CSSParserToken.prototype);550BadURLToken.prototype.tokenType = "BADURL";551function WhitespaceToken() { return this; }552WhitespaceToken.prototype = Object.create(CSSParserToken.prototype);553WhitespaceToken.prototype.tokenType = "WHITESPACE";554WhitespaceToken.prototype.toString = function() { return "WS"; }555WhitespaceToken.prototype.toSource = function() { return " "; }556function CDOToken() { return this; }557CDOToken.prototype = Object.create(CSSParserToken.prototype);558CDOToken.prototype.tokenType = "CDO";559CDOToken.prototype.toSource = function() { return "<!--"; }560function CDCToken() { return this; }561CDCToken.prototype = Object.create(CSSParserToken.prototype);562CDCToken.prototype.tokenType = "CDC";563CDCToken.prototype.toSource = function() { return "-->"; }564function ColonToken() { return this; }565ColonToken.prototype = Object.create(CSSParserToken.prototype);566ColonToken.prototype.tokenType = ":";567function SemicolonToken() { return this; }568SemicolonToken.prototype = Object.create(CSSParserToken.prototype);569SemicolonToken.prototype.tokenType = ";";570function CommaToken() { return this; }571CommaToken.prototype = Object.create(CSSParserToken.prototype);572CommaToken.prototype.tokenType = ",";573function GroupingToken() { throw "Abstract Base Class"; }574GroupingToken.prototype = Object.create(CSSParserToken.prototype);575function OpenCurlyToken() { this.value = "{"; this.mirror = "}"; return this; }576OpenCurlyToken.prototype = Object.create(GroupingToken.prototype);577OpenCurlyToken.prototype.tokenType = "{";578function CloseCurlyToken() { this.value = "}"; this.mirror = "{"; return this; }579CloseCurlyToken.prototype = Object.create(GroupingToken.prototype);580CloseCurlyToken.prototype.tokenType = "}";581function OpenSquareToken() { this.value = "["; this.mirror = "]"; return this; }582OpenSquareToken.prototype = Object.create(GroupingToken.prototype);583OpenSquareToken.prototype.tokenType = "[";584function CloseSquareToken() { this.value = "]"; this.mirror = "["; return this; }585CloseSquareToken.prototype = Object.create(GroupingToken.prototype);586CloseSquareToken.prototype.tokenType = "]";587function OpenParenToken() { this.value = "("; this.mirror = ")"; return this; }588OpenParenToken.prototype = Object.create(GroupingToken.prototype);589OpenParenToken.prototype.tokenType = "(";590function CloseParenToken() { this.value = ")"; this.mirror = "("; return this; }591CloseParenToken.prototype = Object.create(GroupingToken.prototype);592CloseParenToken.prototype.tokenType = ")";593function IncludeMatchToken() { return this; }594IncludeMatchToken.prototype = Object.create(CSSParserToken.prototype);595IncludeMatchToken.prototype.tokenType = "~=";596function DashMatchToken() { return this; }597DashMatchToken.prototype = Object.create(CSSParserToken.prototype);598DashMatchToken.prototype.tokenType = "|=";599function PrefixMatchToken() { return this; }600PrefixMatchToken.prototype = Object.create(CSSParserToken.prototype);601PrefixMatchToken.prototype.tokenType = "^=";602function SuffixMatchToken() { return this; }603SuffixMatchToken.prototype = Object.create(CSSParserToken.prototype);604SuffixMatchToken.prototype.tokenType = "$=";605function SubstringMatchToken() { return this; }606SubstringMatchToken.prototype = Object.create(CSSParserToken.prototype);607SubstringMatchToken.prototype.tokenType = "*=";608function ColumnToken() { return this; }609ColumnToken.prototype = Object.create(CSSParserToken.prototype);610ColumnToken.prototype.tokenType = "||";611function EOFToken() { return this; }612EOFToken.prototype = Object.create(CSSParserToken.prototype);613EOFToken.prototype.tokenType = "EOF";614EOFToken.prototype.toSource = function() { return ""; }615function DelimToken(code) {616 this.value = stringFromCode(code);617 return this;618}619DelimToken.prototype = Object.create(CSSParserToken.prototype);620DelimToken.prototype.tokenType = "DELIM";621DelimToken.prototype.toString = function() { return "DELIM("+this.value+")"; }622DelimToken.prototype.toJSON = function() {...

Full Screen

Full Screen

utils.js

Source:utils.js Github

copy

Full Screen

1const _MS_PER_DAY = 1000 * 60 * 60 * 24;2const path = require('path');3const fs = require('fs');4module.exports = function () {5 this.strToBase64 = function (str) {6 var buff = new Buffer(str);7 return buff.toString("base64");8 };9 this.base64ToStr = function (encStr) {10 var buff = new Buffer(encStr, "base64");11 return buff.toString("utf-8");12 };13 /**14 * 15 * @param {String} dirPath : 비울 디렉토리 경로 16 * @param {String} exceptionFiles : 삭제하지 않을 파일 이름 배열17 */18 this.clearDir = async (dirPath, targetExt, exceptionFiles) => {19 const dir = await fs.promises.readdir(dirPath);20 const unlinkPromises = dir.map(file => {21 if(exceptionFiles instanceof Array) {22 if(exceptionFiles.filter(name => name == file).length === 0 && path.extname(file) == targetExt)23 fs.promises.unlink(path.resolve(dirPath, file));24 25 } else if (exceptionFiles === undefined)26 if(path.extname(file) == targetExt)27 fs.promises.unlink(path.resolve(dirPath, file))28 });29 return Promise.all(unlinkPromises);30 }31 /**32 * Csv파일을 읽어 정렬한 후에 최신 아이디 이후를 가져온다33 *34 * @param {Buffer} fileBuffer : 읽을 파일 버퍼35 * @param {string} rowToken : CSV파일의 행 분리자36 * @param {string} columnToken : CSV 파일의 열 분리자37 * @param {Array} sortColumnList : 정렬 대상 컬럼들이 담긴 리스트38 * @param {string} latestId : 대상 컬럼의 최신 아이디, 이것을 초과하는 것만 반환한다39 * @param {string} compareatorGenerator : 정렬 함수를 생성하는 함수40 */41 this.processCsvAndReturnUpdatedData = function (42 fileBuffer,43 rowToken,44 columnToken,45 sortColumnList,46 latestId,47 compareatorGenerator48 ) {49 const fileStr = fileBuffer.toString();50 const rowIndex = fileStr.split(columnToken, 1)[0];51 const rowData = fileStr.substring(rowIndex.length + 1, fileStr.length);52 const indexArr = rowIndex.split(rowToken).map((str) => str.trim());53 let dataList = [];54 rowData.split(columnToken).map((row) => {55 const rowArr = row.split(rowToken);56 //잘못된 데이터는 거른다57 if (rowArr.length !== indexArr.length) {58 return;59 }60 const dataObj = new Object();61 // 인덱스를 키로 가지는 객체 생성62 for (let i = 0; i < indexArr.length; i++) {63 dataObj[indexArr[i]] = rowArr[i];64 }65 dataList.push(dataObj);66 });67 dataList = dataList.sort(compareatorGenerator(...sortColumnList));68 let lastRowIdx =69 dataList.findIndex((elemnt) => elemnt[sortColumnList[0]] == latestId) - 1;70 if(lastRowIdx+1 == -1) {71 lastRowIdx = dataList.length-1;72 }73 console.log(74 `Csv file parsing complete, total len : ${75 dataList.length76 }, updated : ${lastRowIdx + 1}`77 );78 return dataList.slice(0, lastRowIdx+1);79 };80 /**81 * 타임아웃 내에 파일의 다운로드가 끝나는지 확인한다.82 *83 * @param {string} filePath : 검사할 파일 경로84 * @param {number} timeout : 시간제한85 * @returns Promise<true>86 * @throws Error<string> when file dit not exist | download not finishid within timeout87 */88 this.checkFileDownloadedWithinTimeout = function (filePath, timeout) {89 return new Promise(function (resolve, reject) {90 const dir = path.dirname(filePath);91 const basename = path.basename(filePath);92 const watcher = fs.watch(dir, function (eventType, filename) {93 if (eventType === "rename" && filename === basename) {94 clearTimeout(timer);95 watcher.close();96 resolve(true);97 }98 });99 const timer = setTimeout(function () {100 watcher.close();101 reject(102 new Error(103 `File : ${filePath} did not exists and was not created during the timeout.`104 )105 );106 }, timeout);107 fs.access(filePath, fs.constants.R_OK, function (err) {108 if (!err) {109 clearTimeout(timer);110 watcher.close();111 resolve(true);112 }113 });114 });115 };116 this.rangeReverse = function (start, end) {117 if (start === end) return [start];118 return [start, ...rangeReverse(start - 1, end)];119 };120 this.dateDiffInDays = function (a, b) {121 // Discard the time and time-zone information.122 const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());123 const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());124 return Math.floor((utc1 - utc2) / _MS_PER_DAY);125 };126 this.wrapAsyncFn = asyncFn => {127 return (async (req, res, next) => {128 try {129 return await asyncFn(req, res, next)130 } catch (error) {131 return next(error)132 }133 }) 134 }135 // this.CovMetaData = class {136 // /**137 // *138 // * @param {*} strainOrObj : 바이러스 이름 (hCoV-19/Brazil/PE-COV0260/2020)139 // * @param {*} epi : EPI_ISL 등록번호 (EPI_ISL_502875)140 // * @param {*} date : 발병일자 (2020-06-24)141 // * @param {*} location : 발병 위치 (South America / Brazil / Pernambuco / Recife)142 // * @param {*} age : 나이 (23)143 // * @param {*} sex : 성별 (Male)144 // * @param {*} clade : GISAID 홈페이지 계통 형식 (B.1.1.28 (G))145 // * @param {*} submit_date : 제출일자 (2020-07-31)146 // */147 // constructor(strainOrObj, epi, date, location, age, sex, clade, submit_date) {148 // if(arguments.length > 1) {149 // this.strain = strainOrObj,150 // this.gisaid_epi_isl = epi,151 // this.date = date,152 // this.age = age,153 // this.sex = sex,154 // this.submit_date = submit_date;155 // let regionInfo = parseLocation(location);156 // this.region = regionInfo.region;157 // this.country = regionInfo.country;158 // this.division = regionInfo.division;159 // this.location = regionInfo.location;160 // let cladeInfo = parseClade(clade);161 // this.pangolin_lineage = cladeInfo.pangolin_lineage;162 // this.GISAID_clade = cladeInfo.GISAID_clade;163 // } else {164 // this.strain = strainOrObj.strain,165 // this.gisaid_epi_isl = strainOrObj.epi,166 // this.date = strainOrObj.date,167 // this.age = strainOrObj.age,168 // this.sex = strainOrObj.sex,169 // this.submit_date = strainOrObj.submit_date;170 // let regionInfo = parseLocation(strainOrObj.location);171 // this.region = regionInfo.region;172 // this.country = regionInfo.country;173 // this.division = regionInfo.division;174 // this.location = regionInfo.location;175 // let cladeInfo = parseClade(strainOrObj.clade);176 // this.pangolin_lineage = cladeInfo.pangolin_lineage;177 // this.GISAID_clade = cladeInfo.GISAID_clade;178 // }179 // }180 // }181 // /**182 // * GISAID 유전정보 ID인 EPI_ISL을 만든다183 // *184 // * @param {Number or String} postfix : ISL 뒤의 숫자185 // * @return {String} : EPI_ISL_${postfix}186 // */187 // this.numberToEpi = function(postfix) {188 // return "EPI_ISL_"+postfix;189 // }190 // this.epiToNumber = function(epi) {191 // let reg = RegExp('^EPI_ISL_[0-9]*$');192 // if(!reg.test(epi)) {193 // return null;194 // }195 // epi = epi.replace("EPI_ISL_","")196 // return parseInt(epi);197 // }198 // this.RegionInfo = class {199 // constructor(region, country, division, location) {200 // this.region = region,201 // this.country = country,202 // this.division = division,203 // this.location = location204 // }205 // }206 // this.CladeInfo = class {207 // constructor(lineage, clade) {208 // this.pangolin_lineage = lineage209 // this.GISAID_clade = clade210 // }211 // }212 // /**213 // * GISAID web db의 location을 metadata 형식으로 파싱한다214 // *215 // * @param {string} location : North America / USA / Texas / Houston216 // * @returns {RegionInfo} 리전 정보 객체217 // */218 // this.parseLocation = function(location) {219 // if (location === undefined || location === null) {220 // return new RegionInfo();221 // } else if (! location instanceof String) {222 // console.log(`NODE : Warn:: function parseLocation() got non-string parameter, value : ${location}`);223 // return new RegionInfo();224 // }225 // let splitList = location.split(' / ');226 // splitList.map(str => str.trim())227 // return new RegionInfo(...splitList);228 // }229 // /**230 // *231 // * @param {string} clade : B.1.2 (GH) 형식232 // * @returns {CladeInfo} 파싱된 클레이드 객체233 // */234 // this.parseClade = function(clade) {235 // let splitList = clade.split(" ");236 // splitList[1] = splitList[1].replace("(","").replace(")","");237 // splitList.map(str => str.trim())238 // return new CladeInfo(splitList[0], splitList[1])239 // }240 // /**241 // * EPI_ISL 에 inc만큼 산술 더하기를 실행한다242 // * @param {*} epi243 // * @param {*} inc244 // */245 // this.addIntToEpiIsl = function(epi, inc) {246 // return "EPI_ISL_" + (epiToNumber(epi)+inc);247 // }248 // /**249 // *250 // * @param {Date} date251 // * @param {Number} inc252 // */253 // this.addOneDayToDate = function (date) {254 // return new Date(date.getTime() + _MS_PER_DAY);255 // }...

Full Screen

Full Screen

PartialQueryStore.spec.js

Source:PartialQueryStore.spec.js Github

copy

Full Screen

...19function _updateInsertIndex(newIndex) {20 PartialQueryActions.updateInsertIndex(newIndex);21}22function _colToken(name) {23 return new ColumnToken('foo_table', {name: name || 'bar'});24}25function _colAliasToken(colName, aliasName) {26 return new AliasToken(27 _colToken(colName),28 aliasName29 );30}31function _aliasToken(subToken, aliasName) {32 return new AliasToken(33 subToken,34 aliasName35 );36}37function _keywordToken(name) {...

Full Screen

Full Screen

QueryKeyboard.js

Source:QueryKeyboard.js Github

copy

Full Screen

...200 );201 },202 _onColPressed: function(tableName, colInfo) {203 this._addToken(204 new ColumnToken(tableName, colInfo),205 );206 },207});208var styles = StyleSheet.create({209 queryText: {210 color: Colors.TEXT_BASE,211 },212 columnsContainer: {213 flexWrap: 'wrap',214 flexDirection: 'row',215 },216 columnText: {217 color: Colors.TEXT_BASE,218 fontSize: 16,...

Full Screen

Full Screen

index.js

Source:index.js Github

copy

Full Screen

1import React, { useState, useEffect } from "react";2import { Container, Row, Col } from "react-bootstrap";3import { useParams } from "react-router-dom";4import {5 firebase,6 user,7 updateCancelledTokens,8 unsubscribeBuyers,9} from "../../config/firebase";10import useWebAnimations, { shakeY } from "@wellyshen/use-web-animations";11import { connect } from "react-redux";12import "./myTokens.css";1314const MyTokens = (props) => {15 const [myTokens, setMyTokens] = useState([]);16 let userId;17 1819 const { ref: heading } = useWebAnimations({20 ...shakeY,21 timing: {22 delay: 500,23 duration: 1000 * 20,24 iterations: Infinity,25 },26 });27 const getMyToken = () => {28 firebase29 .firestore()30 .collection("buyers")31 .where("buyerId", "==", props.userInfo.userId)32 .onSnapshot((data) => {33 let arr = [];34 data.forEach((x) => {35 let temp = x.data();36 temp.id = x.id;37 arr.push(temp);38 });39 setMyTokens(arr);40 });41 };42 useEffect(() => {43 getMyToken();44 return () => {45 unsubscribeBuyers();46 };47 }, []);48 const cancel = async (docId, compId, totalToken, myToken) => {49 firebase.firestore().collection("buyers").doc(docId).delete();50 await updateCancelledTokens(docId, compId, totalToken, myToken);51 };52 return (53 <div className="custom-shape-divider-top-1600808309">54 <svg55 data-name="Layer 1"56 xmlns="http://www.w3.org/2000/svg"57 viewBox="0 0 1200 120"58 preserveAspectRatio="none"59 >60 <path61 d="M321.39,56.44c58-10.79,114.16-30.13,172-41.86,82.39-16.72,168.19-17.73,250.45-.39C823.78,31,906.67,72,985.66,92.83c70.05,18.48,146.53,26.09,214.34,3V0H0V27.35A600.21,600.21,0,0,0,321.39,56.44Z"62 className="shape-fill"63 ></path>64 </svg>65 <Container>66 <p className="text-center text-light display-3 heading" ref={heading}>67 Queue App68 </p>69 <h1 className="text-center text-light compheading">*** My Tokens***</h1>70 </Container>71 <div className="content">72 <Row>73 <Container>74 <Col md="12" key={11244}>75 <div className="columnToken">76 <div className="compName font-weight-bold">Company Name</div>77 <div className="compToken font-weight-bold">Token Number </div>78 <div className="text-danger font-weight-bold">Cancel Token</div>79 </div>80 </Col>81 {myTokens.length > 0 &&82 myTokens.map((x) => {83 const key = Math.random() * 100;84 return (85 <Col md="12" key={key}>86 <div className="columnToken">87 <div className="compName">88 <img89 className="compImageToken"90 src={x.companyImage}91 alt="compnayProfile"92 />93 {x.companyName}94 </div>95 <div className="compToken"> {x.tokenNumber}</div>96 <div97 className="btn btn-outline-danger"98 onClick={() =>99 cancel(100 x.id,101 x.companyId,102 x.totalTokens,103 x.tokenNumber104 )105 }106 >107 Cancel Token108 </div>109 </div>110 </Col>111 );112 })}113 </Container>114 </Row>115 </div>116 </div>117 );118};119const mapStateToProps = (state) => {120 return {121 userInfo: state.authReducer.user,122 };123}; ...

Full Screen

Full Screen

ColumnToken.js

Source:ColumnToken.js Github

copy

Full Screen

1"use strict";2var AbstractToken = require('../tokens/AbstractToken');3var TokenTypes = require('../constants/TokenTypes');4class ColumnToken extends AbstractToken {5 constructor(6 tableName,7 colInfo8 ) {9 super();10 this.tableName = tableName;11 this.colInfo = colInfo;12 }13 getType() {14 return TokenTypes.COLUMN;15 }16 toString() {17 return `18 COLUMN from Table "${this.tableName}" 19 info: ${JSON.stringify(this.colInfo)}`;20 }21 isColumnLike() {22 return true;23 }24 exportToQuery() {25 if (this._isBeforeColumn()) {26 return this.colInfo.name + ',';27 }28 return this.colInfo.name;29 }30}...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1const { chromium } = require('playwright');2(async () => {3 const browser = await chromium.launch();4 const context = await browser.newContext();5 const page = await context.newPage();6 const value = await page.$eval('table', (table) => {7 return table.rows[2].cells[1].innerText;8 });9 console.log(value);10 await browser.close();11})();12Your name to display (optional):

Full Screen

Using AI Code Generation

copy

Full Screen

1const { ColumnToken } = require('playwright');2const { test, expect } = require('@playwright/test');3test('test', async ({ page }) => {4 const searchInput = page.locator('#tsf > div:nth-child(2) > div > div.RNNXgb > div > div.a4bIc > input');5 await searchInput.type('Hello World');6 const searchButton = page.locator('#tsf > div:nth-child(2) > div > div.FPdoLc.VlcLAe > center > input[type="submit"]:nth-child(1)');7 await searchButton.click();

Full Screen

Using AI Code Generation

copy

Full Screen

1const { ColumnToken } = require('playwright-core/lib/server/selectors/selectorEngine');2const { Page } = require('playwright-core/lib/server/page');3const { JSHandle } = require('playwright-core/lib/server/jsHandle');4const { ElementHandle } = require('playwright-core/lib/server/dom');5const page = new Page();6const columnToken = new ColumnToken();7const handle = new JSHandle(page, 'window', null);8const elementHandle = new ElementHandle(page, handle, 'body');9const result = columnToken.queryAll(elementHandle, '1');10console.log('result: ', result);

Full Screen

Using AI Code Generation

copy

Full Screen

1const { ColumnToken } = require('playwright/lib/internal/selectorParser');2const { Selector } = require('playwright/lib/internal/selectorEngine');3const { parseSelector } = require('playwright/lib/internal/selectorParser');4const { parse } = require('playwright/lib/internal/selectorParser');5const selector = parseSelector('column=3');6console.log(selector);7const { ColumnToken } = require('playwright/lib/internal/selectorParser');8const { Selector } = require('playwright/lib/internal/selectorEngine');9const { parseSelector } = require('playwright/lib/internal/selectorParser');10const { parse } = require('playwright/lib/internal/selectorParser');11const selector = parseSelector('column=3');12console.log(selector);13const { ColumnToken } = require('playwright/lib/internal/selectorParser');14const { Selector } = require('playwright/lib/internal/selectorEngine');15const { parseSelector } = require('playwright/lib/internal/selectorParser');16const { parse } = require('playwright/lib/internal/selectorParser');17const selector = parseSelector('column=3');18console.log(selector);19const { ColumnToken } = require('playwright/lib/internal/selectorParser');20const { Selector } = require('playwright/lib/internal/selectorEngine');21const { parseSelector } = require('playwright/lib/internal/selectorParser');22const { parse } = require('playwright/lib/internal/selectorParser');23const selector = parseSelector('column=3');24console.log(selector);25const { ColumnToken } = require('playwright/lib/internal/selectorParser');26const { Selector } = require('playwright/lib/internal/selectorEngine');27const { parseSelector } = require('playwright/lib/internal/selectorParser');28const { parse } = require('playwright/lib/internal/selectorParser');29const selector = parseSelector('column=3');30console.log(selector);31const { ColumnToken } = require('playwright/lib/internal/selectorParser');32const { Selector } = require('playwright/lib/internal/selectorEngine');33const { parseSelector } = require('playwright/lib/internal/selectorParser');34const { parse } =

Full Screen

Using AI Code Generation

copy

Full Screen

1const { ColumnToken } = require("playwright/lib/server/locator");2const { test } = require("@playwright/test");3test.describe("test", () => {4 test("test", async ({ page }) => {5 await page.click(new ColumnToken("Search"));6 });7});8const { ColumnToken } = require("playwright/lib/server/locator");9const { test } = require("@playwright/test");10test.describe("test", () => {11 test("test", async ({ page }) => {12 await page.click(new ColumnToken("Search"));13 });14});15const { ColumnToken } = require("playwright/lib/server/locator");16const { test } = require("@playwright/test");17test.describe("test", () => {18 test("test", async ({ page }) => {19 await page.click(new ColumnToken("Search"));20 });21});22const { test } = require("@playwright/test");23test.describe("test", () => {24 test("test", async ({ page }) => {25 const elements = await page.$$("a");26 for (const element of elements) {27 const text = await element.innerText();28 if (text.includes("Search

Full Screen

Using AI Code Generation

copy

Full Screen

1const { chromium } = require('playwright');2(async () => {3 const browser = await chromium.launch();4 const context = await browser.newContext();5 const page = await context.newPage();6 const token = await page.columnToken({x: 100, y: 100});7 console.log(token);8 await browser.close();9})();10{ x: 100, y: 100, column: 4, token: 'playwright' }

Full Screen

Using AI Code Generation

copy

Full Screen

1const { ColumnToken } = require('playwright');2const columnToken = new ColumnToken('test', 1);3console.log(columnToken);4const { ColumnToken } = require('playwright/lib/api/stackTrace');5const columnToken = new ColumnToken('test', 1);6console.log(columnToken);7const { ColumnToken } = require('playwright/lib/api/stackTrace.js');8const columnToken = new ColumnToken('test', 1);9console.log(columnToken);10const { ColumnToken } = require('playwright/lib/api/stackTrace.ts');11const columnToken = new ColumnToken('test', 1);12console.log(columnToken);13const { ColumnToken } = require('playwright/lib/api/stackTrace.d.ts');14const columnToken = new ColumnToken('test', 1);15console.log(columnToken);16const { ColumnToken } = require('playwright/lib/api/stackTrace.mjs');17const columnToken = new ColumnToken('test', 1);18console.log(columnToken);19const { ColumnToken } = require('playwright/lib/api/stackTrace.cjs');20const columnToken = new ColumnToken('test', 1);21console.log(columnToken);22const { ColumnToken } = require('playwright/lib/api/stackTrace.js');23const columnToken = new ColumnToken('test', 1);24console.log(columnToken);25const { ColumnToken } = require('playwright/lib/api/stackTrace.ts');26const columnToken = new ColumnToken('test', 1);27console.log(columnToken);28const { ColumnToken } = require('playwright/lib/api/stackTrace.d.ts');29const columnToken = new ColumnToken('test', 1);30console.log(columnToken);31const { ColumnToken } = require('playwright/lib/api/stackTrace.mjs');

Full Screen

Using AI Code Generation

copy

Full Screen

1const { chromium } = require('playwright');2const { ColumnToken } = require('playwright/lib/internal/protocol');3(async () => {4 const browser = await chromium.launch();5 const context = await browser.newContext();6 const page = await context.newPage();7 const element = await page.$('text=Get started');8 const columnNumber = await element.evaluate(ColumnToken);9 console.log(columnNumber);10 await browser.close();11})();12const { chromium } = require('playwright');13const { LineToken } = require('playwright/lib/internal/protocol');14(async () => {15 const browser = await chromium.launch();16 const context = await browser.newContext();17 const page = await context.newPage();18 const element = await page.$('text=Get started');19 const lineNumber = await element.evaluate(LineToken);20 console.log(lineNumber);21 await browser.close();22})();23const { chromium } = require('playwright');24const { URLToken } = require('playwright/lib/internal/protocol');25(async () => {26 const browser = await chromium.launch();27 const context = await browser.newContext();28 const page = await context.newPage();29 const element = await page.$('text=Get started');30 const url = await element.evaluate(URLToken);31 console.log(url);32 await browser.close();33})();34const { chromium } = require('playwright');35const { XPathToken } = require('playwright/lib/internal/protocol');36(async () => {37 const browser = await chromium.launch();38 const context = await browser.newContext();39 const page = await context.newPage();40 const element = await page.$('text=Get started');41 const xpath = await element.evaluate(XPathToken);42 console.log(xpath);

Full Screen

Using AI Code Generation

copy

Full Screen

1import { ColumnToken } from 'playwright';2const columnToken = new ColumnToken('column');3console.log(columnToken.toString());4import { ColumnToken } from 'playwright';5const columnToken = new ColumnToken('column');6console.log(columnToken.toString());

Full Screen

Using AI Code Generation

copy

Full Screen

1const { ColumnToken } = require('@playwright/test');2const columnToken = new ColumnToken('column name', 10);3console.log(columnToken);4const { ColumnToken } = require('@playwright/test');5const columnToken = new ColumnToken('column name', 10);6console.log(columnToken);7const { ColumnToken } = require('@playwright/test');8const columnToken = new ColumnToken('column name', 10);9console.log(columnToken);10const { ColumnToken } = require('@playwright/test');11const columnToken = new ColumnToken('column name', 10);12console.log(columnToken);13const { ColumnToken } = require('@playwright/test');14const columnToken = new ColumnToken('column name', 10);15console.log(columnToken);16const { ColumnToken } = require('@playwright/test');17const columnToken = new ColumnToken('column name', 10);18console.log(columnToken);19const { ColumnToken } = require('@playwright/test');20const columnToken = new ColumnToken('column name', 10);21console.log(columnToken);22const { ColumnToken } = require('@playwright/test');23const columnToken = new ColumnToken('column name', 10);24oa ync require('playwright/lib/internal/protocol');25cons brower = awai chromium.launch);26 cons contx= wait brower.ewContext);27 const await context.newPage();28(anc () => {'playrihtdev'29 constcelemento= nst browser$a'ttxt=Gat st);ted'30 constcunll= nwait alemewt. va.$ate(URLToke');31e=censtl';lg(rl);32awitbrowe.los();33})();34 const lineNumber = await element.evaluate(LineToken);35coconsn browssr = await chromium.launch();36 cont conchxo =umwait brow er. ewContextr);37q const uire(awawait context.newPage();ight');38cot { URLToken } ='require(play'riyhttdevb'internal/protocol');39constelement= $'txt=Getstarted');40 cnst xpath = await eeet.evaluate(XPath41(aconsole.log(xpathync () => {42onst browser = await chromium.launch();

Full Screen

Using AI Code Generation

copy

Full Screen

1 const context = await browser.newContext();2imptett{ CnlumnTak() } from 'playwrght';3costToken=ewColmnTkn('clmn');4conol.log(columnToken.oSt());5import {CumnToen }frm'paywrg';6contnToke =ew ColunTokn('column');7conole.g(lunTokn.oSrg());

Full Screen

Using AI Code Generation

copy

Full Screen

1 const url = await element.evaluate(URLToken);2 await browser.close();3})();'@te'4con olh.log( olumnToktn;5const { ColumnToken } = require('@rloywrimht/tist');6monst olumnTo=er = nequire('playwri';olumncname',o10st { XPathToken } = require('playwright/lib/internal/protocol');7console.log(col nTokor);8cone ={wCoiu nTokcoe}n=wrequire('@playwrighe/)est');9c;nstoumnToen= new ClumToken('columnnam',10);10cono.log(colunTok);11 const ColumnToklnent = await p'ge.$('text=Get s'arted');12conco xolumnTokhn = new ColumnToken 'column nama't 10);13console.logecolumnTokenl;14.evaluate(XPathToken);15 {CounTok }=requir('@paywright/ts';16 to uscolumnTok nlumneTnColumnTok n('comumn nath', 10);17coosole log(colum aokyngh18const { ColumnToken } = require('playwright/lib/api/stackTrace');19csns columnToken = ne =wrequiCe(m@nToken('tt/teses)t20', 1);21console.log(columnToken);n ame, 1022);23const { Column } = require('playwright/lib/api/stackTrace.js');24const columnToken = new ColumnToken('test', 1);25console.log(columnToken);26cns =requie(@t/tes)

Full Screen

Using AI Code Generation

copy

Full Screen

1cs { ColumnToken } =reqire('@plywight/s');2conscoumnTen =ewColmnTokn('columnnam', 10);3const { ColumnToken } = require('@playwright/test');4const columnToken = new ColumnToken('column name', 10);5console.log(columnToken);6const { ColumnToken } = require('@playwright/test');7const columnToken = new ColumnToken('column name', 10);8console.log(columnToken);9const { ColumnToken } = require('@playwright/test');10const columnToken = new ColumnToken('column name', 10);11console.log(columnToken);12const { ColumnToken } = require('@playwright/test');13const columnToken = new ColumnToken('column name', 10);14console.log(columnToken);15const { ColumnToken } = require('@playwright/test');16const columnToken = new ColumnToken('column name', 10);17console.log(columnToken);18const { ColumnToken } = require('@playwright/test');19const columnToken = new ColumnToken('column name', 10);20console.log(columnToken);21const { ColumnToken } = require('@playwright/test');22const columnToken = new ColumnToken('column name', 10);23console.log(columnToken);24const { ColumnToken } = require('@playwright/test');25const columnToken = new ColumnToken('column name', 10);26const { ColumnToken } = require('playwright/lib/api/stackTrace.ts');27const columnToken = new ColumnToken('test', 1);28console.log(columnToken);29const { ColumnToken } = require('playwright/lib/api/stackTrace.d.ts');30const columnToken = new ColumnToken('test', 1);31console.log(columnToken);32const { ColumnToken } = require('playwright/lib/api/stackTrace.mjs');33const columnToken = new ColumnToken('test', 1);34console.log(columnToken);35const { ColumnToken } = require('playwright/lib/api/stackTrace.cjs');36const columnToken = new ColumnToken('test', 1);37console.log(columnToken);38const { ColumnToken } = require('playwright/lib/api/stackTrace.js');39const columnToken = new ColumnToken('test', 1);40console.log(columnToken);41const { ColumnToken } = require('playwright/lib/api/stackTrace.ts');42const columnToken = new ColumnToken('test', 1);43console.log(columnToken);44const { ColumnToken } = require('playwright/lib/api/stackTrace.d.ts');45const columnToken = new ColumnToken('test', 1);46console.log(columnToken);47const { ColumnToken } = require('playwright/lib/api/stackTrace.mjs');

Full Screen

Using AI Code Generation

copy

Full Screen

1import { ColumnToken } from 'playwright';2const columnToken = new ColumnToken('column');3console.log(columnToken.toString());4import { ColumnToken } from 'playwright';5const columnToken = new ColumnToken('column');6console.log(columnToken.toString());

Full Screen

Using AI Code Generation

copy

Full Screen

1const { ColumnToken } = require("playwright/lib/server/locator");2const { test } = require("@playwright/test");3test.describe("test", () => {4 test("test", async ({ page }) => {5 await page.click(new ColumnToken("Search"));6 });7});8const { ColumnToken } = require("playwright/lib/server/locator");9const { test } = require("@playwright/test");10test.describe("test", () => {11 test("test", async ({ page }) => {12 await page.click(new ColumnToken("Search"));13 });14});15const { ColumnToken } = require("playwright/lib/server/locator");16const { test } = require("@playwright/test");17test.describe("test", () => {18 test("test", async ({ page }) => {19 await page.click(new ColumnToken("Search"));20 });21});22const { test } = require("@playwright/test");23test.describe("test", () => {24 test("test", async ({ page }) => {25 const elements = await page.$$("a");26 for (const element of elements) {27 const text = await element.innerText();28 if (text.includes("Search

Full Screen

Using AI Code Generation

copy

Full Screen

1import { ColumnToken } from 'playwright';2const columnToken = new ColumnToken('column');3console.log(columnToken.toString());4import { ColumnToken } from 'playwright';5const columnToken = new ColumnToken('column');6console.log(columnToken.toString());

Full Screen

Playwright tutorial

LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.

Chapters:

  1. What is Playwright : Playwright is comparatively new but has gained good popularity. Get to know some history of the Playwright with some interesting facts connected with it.
  2. How To Install Playwright : Learn in detail about what basic configuration and dependencies are required for installing Playwright and run a test. Get a step-by-step direction for installing the Playwright automation framework.
  3. Playwright Futuristic Features: Launched in 2020, Playwright gained huge popularity quickly because of some obliging features such as Playwright Test Generator and Inspector, Playwright Reporter, Playwright auto-waiting mechanism and etc. Read up on those features to master Playwright testing.
  4. What is Component Testing: Component testing in Playwright is a unique feature that allows a tester to test a single component of a web application without integrating them with other elements. Learn how to perform Component testing on the Playwright automation framework.
  5. Inputs And Buttons In Playwright: Every website has Input boxes and buttons; learn about testing inputs and buttons with different scenarios and examples.
  6. Functions and Selectors in Playwright: Learn how to launch the Chromium browser with Playwright. Also, gain a better understanding of some important functions like “BrowserContext,” which allows you to run multiple browser sessions, and “newPage” which interacts with a page.
  7. Handling Alerts and Dropdowns in Playwright : Playwright interact with different types of alerts and pop-ups, such as simple, confirmation, and prompt, and different types of dropdowns, such as single selector and multi-selector get your hands-on with handling alerts and dropdown in Playright testing.
  8. Playwright vs Puppeteer: Get to know about the difference between two testing frameworks and how they are different than one another, which browsers they support, and what features they provide.
  9. Run Playwright Tests on LambdaTest: Playwright testing with LambdaTest leverages test performance to the utmost. You can run multiple Playwright tests in Parallel with the LammbdaTest test cloud. Get a step-by-step guide to run your Playwright test on the LambdaTest platform.
  10. Playwright Python Tutorial: Playwright automation framework support all major languages such as Python, JavaScript, TypeScript, .NET and etc. However, there are various advantages to Python end-to-end testing with Playwright because of its versatile utility. Get the hang of Playwright python testing with this chapter.
  11. Playwright End To End Testing Tutorial: Get your hands on with Playwright end-to-end testing and learn to use some exciting features such as TraceViewer, Debugging, Networking, Component testing, Visual testing, and many more.
  12. Playwright Video Tutorial: Watch the video tutorials on Playwright testing from experts and get a consecutive in-depth explanation of Playwright automation testing.

Run Playwright Internal automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful