diff --git a/kibot/out_kiri.py b/kibot/out_kiri.py new file mode 100644 index 00000000..ca4bae99 --- /dev/null +++ b/kibot/out_kiri.py @@ -0,0 +1,365 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2022-2023 Salvador E. Tropea +# Copyright (c) 2022-2023 Instituto Nacional de TecnologĂ­a Industrial +# License: GPL-3.0 +# Project: KiBot (formerly KiPlot) +""" +Dependencies: + - name: KiCad PCB/SCH Diff + version: 2.5.0 + role: mandatory + github: INTI-CMNB/KiDiff + command: kicad-diff.py + pypi: kidiff + downloader: pytool + id: KiDiff + - from: Git + role: Compare with files in the repo + - from: KiAuto + role: Compare schematics + version: 2.2.0 +""" +import datetime +import pwd +import os +from shutil import copy2 +from subprocess import CalledProcessError +from tempfile import mkdtemp, NamedTemporaryFile +from .error import KiPlotConfigurationError +from .gs import GS +from .kicad.color_theme import load_color_theme +from .kiplot import load_any_sch, run_command +from .layer import Layer +from .out_base import VariantOptions +from .macros import macros, document, output_class # noqa: F401 +from . import log + +logger = log.get_logger() +STASH_MSG = 'KiBot_Changes_Entry' +TOOLTIP_HTML = '
Commit: {hash}
Date: {dt}
Author: {author}
Description:
{desc}
' +# Icons for modified status +EMPTY_IMG = ('') +SCH_IMG = ('') +PCB_IMG = ('') +TXT_IMG = ('') +HASH_LOCAL = '_local_' + + +def get_cur_user(): + try: + name = pwd.getpwuid(os.geteuid())[4] + return name.split(',')[0] + except Exception: + return 'Local user' + + +class KiRiOptions(VariantOptions): + def __init__(self): + with document: + self.output = GS.def_global_output + """ *Filename for the output (%i=diff_pcb/diff_sch, %x=pdf) """ + self.color_theme = '_builtin_classic' + """ *Selects the color theme. Only applies to KiCad 6. + To use the KiCad 6 default colors select `_builtin_default`. + Usually user colors are stored as `user`, but you can give it another name """ + super().__init__() + self._expand_id = 'diff' + self._expand_ext = 'pdf' + + def get_targets(self, out_dir): + # TODO: Implement + return [self._parent.expand_filename(out_dir, self.output)] + + def add_to_cache(self, name, hash): + cmd = [self.command, '--no_reader', '--only_cache', '--old_file_hash', hash[:7], '--cache_dir', self.cache_dir, + '--kiri_mode', '--all_pages'] + if self.incl_file: + cmd.extend(['--layers', self.incl_file]) + if GS.debug_enabled: + cmd.insert(1, '-'+'v'*GS.debug_level) + cmd.extend([name, name]) + self.name_used_for_cache = name + run_command(cmd) + + def run_git(self, cmd, cwd=None, just_raise=False): + if cwd is None: + cwd = self.repo_dir + return run_command([self.git_command]+cmd, change_to=cwd, just_raise=just_raise) + + def git_dirty(self, file): + return self.run_git(['status', '--porcelain', '-uno', file]) + + def remove_git_worktree(self, name): + logger.debug('Removing temporal checkout at '+name) + self.run_git(['worktree', 'remove', '--force', name]) + + def create_layers_incl(self, layers): + self.incl_file = None + if not isinstance(layers, type): + layers = Layer.solve(layers) + # TODO no list (ALL) + self._solved_layers = layers + logger.debug('Including layers:') + with NamedTemporaryFile(mode='w', suffix='.lst', delete=False) as f: + self.incl_file = f.name + for la in layers: + logger.debug('- {} ({})'.format(la.layer, la.id)) + f.write(str(la.id)+'\n') + + def do_cache(self, name, tmp_wd, hash): + name_copy = self.run_git(['ls-files', '--full-name', name]) + name_copy = os.path.join(tmp_wd, name_copy) + logger.debug('- Using temporal copy: '+name_copy) + self.add_to_cache(name_copy, hash) + return name_copy + + def save_pcb_layers(self, hash=None): + subdir = os.path.join(hash[:7], '_KIRI_') if hash is not None else '' + with open(os.path.join(self.cache_dir, subdir, 'pcb_layers'), 'wt') as f: + for la in self._solved_layers: + f.write(str(la.id)+'|'+la.layer+'\n') + + def solve_layer_colors(self): + # Color theme + self._color_theme = load_color_theme(self.color_theme) + if self._color_theme is None: + raise KiPlotConfigurationError("Unable to load `{}` color theme".format(self.color_theme)) + # Assign a color if none was defined + layer_id2color = self._color_theme.layer_id2color + for la in self._solved_layers: + if la._id in layer_id2color: + la.color = layer_id2color[la._id] + else: + la.color = "#000000" + + def create_layers(self, f): + template = self.load_html_template('layers', 11) + for i, la in enumerate(self._solved_layers): + # TODO: Configure checked? + checked = 'checked="checked"' if i == 0 else '' + f.write(template.format(i=i+1, layer_id_padding='%02d' % (i+1), layer_name=la.suffix, + layer_id=la.id, layer_color=la.color, checked=checked)) + + def save_sch_sheet(self, hash, name_sch): + # Load the schematic. Really worth? + sch = load_any_sch(name_sch, GS.sch_basename) + with open(os.path.join(self.cache_dir, hash[:7], '_KIRI_', 'sch_sheets'), 'wt') as f: + base_dir = os.path.dirname(name_sch) + for s in sch.all_sheets: + fname = s.fname + no_ext = os.path.splitext(os.path.basename(fname))[0] + rel_name = os.path.relpath(fname, base_dir) + if s.sheet_path_h == '/': + instance_name = sheet_path = GS.sch_basename + else: + instance_name = os.path.basename(s.sheet_path_h) + sheet_path = s.sheet_path_h.replace('/', '-') + sheet_path = GS.sch_basename+'-'+sheet_path[1:] + f.write(f'{no_ext}|{rel_name}||{instance_name}|{sheet_path}\n') + + def create_pages(self, f): + template = self.load_html_template('pages', 11) + for i, s in enumerate(sorted(GS.sch.all_sheets, key=lambda s: s.sheet_path_h)): + fname = s.fname + checked = 'checked="checked"' if i == 0 else '' + base_name = os.path.basename(fname) + rel_name = os.path.relpath(fname, GS.sch_dir) + if s.sheet_path_h == '/': + instance_name = sheet_path = GS.sch_basename + else: + instance_name = os.path.basename(s.sheet_path_h) + sheet_path = s.sheet_path_h.replace('/', '-') + sheet_path = GS.sch_basename+'-'+sheet_path[1:] + f.write(template.format(i=i+1, page_name=instance_name, page_filename_path=rel_name, + page_filename=base_name, checked=checked)) + + def load_html_template(self, type, tabs): + """ Load a template used to generate an HTML section. + Outside of the code for easier modification/customization. """ + with open(os.path.join(GS.get_resource_path('kiri'), f'{type}_template.html'), 'rt') as f: + template = f.read() + template = template.replace('${', '{') + template = template.replace('$(printf "%02d" {i})', '{i02}') + template = template.replace('{class}', '{cls}') + template = template.replace('\t\t', '\t'*tabs) + return template + + def create_index(self, commits): + # Get the KiRi template + with open(os.path.join(GS.get_resource_path('kiri'), 'index.html'), 'rt') as f: + template = f.read() + today = datetime.datetime.today().strftime('%Y-%m-%d') + # Replacement keys + rep = {} + rep['PROJECT_TITLE'] = GS.pro_basename or GS.sch_basename or GS.pcb_basename or 'unknown' + rep['SCH_TITLE'] = GS.sch_title or 'No title' + rep['SCH_REVISION'] = GS.sch_rev or '' + rep['SCH_DATE'] = GS.sch_date or today + rep['PCB_TITLE'] = GS.pcb_title or 'No title' + rep['PCB_REVISION'] = GS.pcb_rev or '' + rep['PCB_DATE'] = GS.pcb_date or today + # Fill the template + with open(os.path.join(self.cache_dir, 'web', 'index.html'), 'wt') as f: + for ln in iter(template.splitlines()): + for k, v in rep.items(): + ln = ln.replace(f'[{k}]', v) + f.write(ln+'\n') + if ln.endswith(''): + self.create_commits(f, commits) + elif ln.endswith(''): + self.create_pages(f) + elif ln.endswith(''): + self.create_layers(f) + + def create_commits(self, f, commits): + template = self.load_html_template('commits', 8) + for i, c in enumerate(commits): + hash = c[0][:7] + dt = c[1].split()[0] + author = c[2]+' ' + desc = c[3] + tooltip = TOOLTIP_HTML.format(hash=hash, dt=dt, author=author, desc=desc) + cls = 'text-warning' if hash == HASH_LOCAL else 'text-info' + icon_pcb = PCB_IMG if c[0] in self.commits_with_changed_pcb else EMPTY_IMG + icon_sch = SCH_IMG if c[0] in self.commits_with_changed_sch else EMPTY_IMG + # TODO What's this? if we only track changes in PCB/Sch this should be empty + icon_txt = TXT_IMG + f.write(template.format(i=i+1, hash=hash, tooltip=tooltip, text=c[3], cls=cls, i02='%02d' % (i+1), + date=dt, user=author, pcb_icon=icon_pcb, sch_icon=icon_sch, txt_icon=icon_txt, hash_label=hash)) + + def get_modified_status(self, pcb_file, sch_files): + res = self.run_git(['log', '--pretty=format:%H', '--', pcb_file]) + self.commits_with_changed_pcb = set(res.split()) + res = self.run_git(['log', '--pretty=format:%H', '--'] + sch_files) + self.commits_with_changed_sch = set(res.split()) + if GS.debug_level > 1: + logger.debug(f'Commits with changes in the PCB: {self.commits_with_changed_pcb}') + logger.debug(f'Commits with changes in the Schematics: {self.commits_with_changed_sch}') + + def create_kiri_files(self): + src_dir = GS.get_resource_path('kiri') + copy2(os.path.join(src_dir, 'redirect.html'), os.path.join(self.cache_dir, 'index.html')) + copy2(os.path.join(src_dir, 'kiri-server'), os.path.join(self.cache_dir, 'kiri-server')) + web_dir = os.path.join(self.cache_dir, 'web') + os.makedirs(web_dir, exist_ok=True) + copy2(os.path.join(src_dir, 'blank.svg'), os.path.join(web_dir, 'blank.svg')) + copy2(os.path.join(src_dir, 'favicon.ico'), os.path.join(web_dir, 'favicon.ico')) + copy2(os.path.join(src_dir, 'kiri.css'), os.path.join(web_dir, 'kiri.css')) + copy2(os.path.join(src_dir, 'kiri.js'), os.path.join(web_dir, 'kiri.js')) + + def run(self, name): + self.cache_dir = self._parent.output_dir + self.command = self.ensure_tool('KiDiff') + self.git_command = self.ensure_tool('Git') + # Get a list of files for the project + GS.check_sch() + sch_files = GS.sch.get_files() + self.repo_dir = GS.sch_dir + GS.check_pcb() + # Get a list of hashes where we have changes + # TODO implement a limit -n X + res = self.run_git(['log', "--date=format:%Y-%m-%d %H:%M:%S", '--pretty=format:%H | %ad | %an | %s', '--', + GS.pcb_file] + sch_files) + hashes = [r.split(' | ') for r in res.split('\n')] + self.create_layers_incl(self.layers) + self.solve_layer_colors() + # Get more information about what is changed + self.get_modified_status(GS.pcb_file, sch_files) + # TODO ensure we have at least 2 + try: + git_tmp_wd = None + try: + for h in hashes: + hash = h[0] + git_tmp_wd = mkdtemp() + logger.debug('Checking out '+hash+' to '+git_tmp_wd) + self.run_git(['worktree', 'add', git_tmp_wd, hash]) + self.run_git(['submodule', 'update', '--init', '--recursive'], cwd=git_tmp_wd) + # Generate SVGs for the schematic + name_sch = self.do_cache(GS.sch_file, git_tmp_wd, hash) + # Generate SVGs for the PCB + self.do_cache(GS.pcb_file, git_tmp_wd, hash) + # List of layers + self.save_pcb_layers(hash) + # Schematic hierarchy + self.save_sch_sheet(hash, name_sch) + self.remove_git_worktree(git_tmp_wd) + git_tmp_wd = None + finally: + if git_tmp_wd: + self.remove_git_worktree(git_tmp_wd) + # Do we have modifications? + sch_dirty = self.git_dirty(GS.sch_file) + pcb_dirty = self.git_dirty(GS.pcb_file) + if sch_dirty or pcb_dirty: + # Include the current files + name_sch = self.do_cache(GS.sch_file, GS.sch_dir, HASH_LOCAL) + self.save_sch_sheet(HASH_LOCAL, name_sch) + self.do_cache(GS.pcb_file, GS.pcb_dir, HASH_LOCAL) + self.save_pcb_layers(HASH_LOCAL) + hashes.insert(0, (HASH_LOCAL, datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S'), get_cur_user(), + 'Local changes not committed')) + if pcb_dirty: + self.commits_with_changed_pcb.add(HASH_LOCAL) + if sch_dirty: + self.commits_with_changed_sch.add(HASH_LOCAL) + finally: + if self.incl_file: + os.remove(self.incl_file) + self.create_kiri_files() + self.create_index(hashes) + + +@output_class +class KiRi(BaseOutput): # noqa: F821 + """ KiRi + Generates a PDF with the differences between two PCBs or schematics. + Recursive git submodules aren't supported (submodules inside submodules) """ + def __init__(self): + super().__init__() + self._category = ['PCB/docs', 'Schematic/docs'] + self._both_related = True + with document: + self.options = KiRiOptions + """ *[dict] Options for the `diff` output """ + self.layers = Layer + """ *[list(dict)|list(string)|string] [all,selected,copper,technical,user] + List of PCB layers to use. When empty all available layers are used. + Note that if you want to support adding/removing layers you should specify a list here """ + + @staticmethod + def layer2dict(la): + return {'layer': la.layer, 'suffix': la.suffix, 'description': la.description} + + @staticmethod + def has_repo(git_command, file): + try: + run_command([git_command, 'ls-files', '--error-unmatch', file], change_to=os.path.dirname(file), just_raise=True) + except CalledProcessError: + logger.debug("File `{}` not inside a repo".format(file)) + return False + return True + + @staticmethod + def get_conf_examples(name, layers): + outs = [] + git_command = GS.check_tool(name, 'Git') + # TODO: Implement + if (GS.pcb_file and GS.sch_file and KiRi.has_repo(git_command, GS.pcb_file) and + KiRi.has_repo(git_command, GS.sch_file)): + gb = {} + gb['name'] = 'basic_{}'.format(name) + gb['comment'] = 'Interactive diff between commits' + gb['type'] = name + gb['dir'] = 'diff' + gb['layers'] = [KiRi.layer2dict(la) for la in layers] + outs.append(gb) + return outs + + def run(self, name): + self.options.layers = self.layers + super().run(name) diff --git a/kibot/resources/kiri/blank.svg b/kibot/resources/kiri/blank.svg new file mode 100644 index 00000000..747e878b --- /dev/null +++ b/kibot/resources/kiri/blank.svg @@ -0,0 +1,55 @@ + + + + + + + + + + image/svg+xml + + + + + + + diff --git a/kibot/resources/kiri/commits_template.html b/kibot/resources/kiri/commits_template.html new file mode 100644 index 00000000..6d347e3f --- /dev/null +++ b/kibot/resources/kiri/commits_template.html @@ -0,0 +1,21 @@ + + + diff --git a/kibot/resources/kiri/favicon.ico b/kibot/resources/kiri/favicon.ico new file mode 100644 index 00000000..0e1fef16 Binary files /dev/null and b/kibot/resources/kiri/favicon.ico differ diff --git a/kibot/resources/kiri/index.html b/kibot/resources/kiri/index.html new file mode 100644 index 00000000..a23957b1 --- /dev/null +++ b/kibot/resources/kiri/index.html @@ -0,0 +1,319 @@ + + + + + + + + + + + + + [PROJECT_TITLE] + + + + + +
+
+
+
+ +

Kicad Revision Inspector

+
+

[SCH_TITLE]

+

Rev. [SCH_REVISION] ([SCH_DATE])

+
+ +

+ + Commits +

+
+
+ +
+
+
+
+ +
+ +
+ +
+ +
+ +
+
+ + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ +
+
+ +
+
+
+
+
+
+ + + + Newer + +
+ + + ([COMMIT_1_HASH]) +
+
+
+ + + + Older + +
+ + + ([COMMIT_2_HASH]) +
+
+
+ + + Unchanged + +
+ +
+ +
+
+
+ +
+ +
+ +
+
+

+ + Pages +

+
+
+
+
+ +
+
+
+
+
+
+ + +
+
+
+ + + + + + + + + diff --git a/kibot/resources/kiri/kiri-server b/kibot/resources/kiri/kiri-server new file mode 100755 index 00000000..49ecd037 --- /dev/null +++ b/kibot/resources/kiri/kiri-server @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 + +import argparse +import os +import shutil +import re +import sys +import signal +import subprocess + +from http.server import SimpleHTTPRequestHandler + +import webbrowser +import socketserver + +from subprocess import PIPE, Popen +from typing import List, Tuple + +from urllib.parse import unquote + +www_dir_path = None +httpd = None +default_port = 8080 + + +class WebServerHandler(SimpleHTTPRequestHandler): + + def __init__(self, *args, **kwargs): + super().__init__( + *args, directory=os.path.realpath(www_dir_path, **kwargs) + ) + + def log_message(self, format, *args): + pass + + def do_GET(self): + super().do_GET() + + def do_POST(self): + try: + + self.send_response(200) + # self.send_header('Location', self.path) + self.end_headers() + + content_len = int(self.headers.get('content-length', 0)) + post_body = unquote(self.rfile.read(content_len)).split("&") + + # with open("post.txt","a") as f: + # f.write(str(post_body)) + # f.write("\n") + + commit_hash = post_body[0].split("=")[1] + kicad_pro_path = post_body[1].split("=")[1] + + cmd = "kicad_rev {}".format(kicad_pro_path) + print("Cmd:", cmd) + process = subprocess.Popen(cmd.split(" "), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + # self._set_response() + # self.wfile.write("POST request for {}".format(self.path).encode('utf-8')) + + except: + pass + +def signal_handler(sig, frame): + httpd.server_close() + sys.exit(0) + + +def parse_cli_args(): + parser = argparse.ArgumentParser(description="Kicad PCB visual diffs.") + parser.add_argument( + "-d", "--display", type=str, help="Set DISPLAY value, default :1.0", default=":1.0", + ) + parser.add_argument( + "-n", "--nested", action='store_true', help="Fix paths when it was a nested project" + ) + parser.add_argument( + "-p", "--port", type=int, help="Force the weverver on an specific port" + ) + parser.add_argument( + "-r", "--port_range", type=int, default=10, help="Port range to try" + ) + parser.add_argument( + "-S", "--server-only", action='store_true', help="Port range to try" + ) + parser.add_argument( + "-w", + "--webserver-disable", + action="store_true", + help="Does not execute webserver (just generate images)", + ) + parser.add_argument( + "-v", "--verbose", action="count", default=0, help="Increase verbosity (-vvv)" + ) + parser.add_argument( + "-i", "--ip", type=str, help="Override default IP address", default="127.0.0.1", + ) + parser.add_argument( + "www_dir", metavar="WWW_DIR", help="A folder with web/index.html inside" + ) + + args = parser.parse_args() + + if args.verbose >= 3: + print("") + print("Command Line Arguments") + print(args) + + return args + + +def launch_webserver(ip, request_handler, port, kicad_project, server_only): + + global httpd + httpd = socketserver.TCPServer(("", port), request_handler) + + with httpd: + if args.nested: + url = "{ip}:{port}/{nested_project}/web/index.html".format( + ip=ip, + port=str(port), + nested_project=kicad_project + ) + else: + url = "http://{ip}:{port}/web/index.html".format( + ip=ip, + port=str(port) + ) + + print("") + print("Starting webserver at {}".format(url)) + print("(Hit Ctrl+C to exit)") + if not server_only: + webbrowser.open(url) + httpd.serve_forever() + + +def run_cmd(path: str, cmd: List[str]) -> Tuple[str, str]: + + p = Popen( + cmd, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True, + encoding="utf-8", + cwd=path, + ) + + stdout, stderr = p.communicate() + p.wait() + + return stdout.strip("\n "), stderr + + +if __name__ == "__main__": + + signal.signal(signal.SIGINT, signal_handler) + args = parse_cli_args() + + if args.www_dir: + + www_dir_path = os.path.abspath(args.www_dir) + kicad_project = ".." + + # Assume it is running outside of the webserver the folder + index_html = os.path.realpath(os.path.join(www_dir_path, "web", "index.html")) + if not os.path.exists(index_html): + print("Could not find index.html") + exit(1) + + if args.verbose: + print("") + print("www_dir_path:", www_dir_path) + print("kicad_project:", kicad_project) + print("index_html:", index_html) + print("") + + else: + print("www directory is missing") + exit(1) + + if not args.webserver_disable: + + socketserver.TCPServer.allow_reuse_address = True + request_handler = WebServerHandler + + if args.port: + try: + launch_webserver(args.ip, request_handler, args.port, kicad_project, args.server_only) + except Exception: + print("Specified port {port} is in use".format(port=port)) + pass + else: + for i in range(args.port_range): + try: + port = default_port + i + launch_webserver(args.ip, request_handler, port, kicad_project, args.server_only) + except Exception: + # print("Specified port {port} is in use".format(port=port)) + pass + print("Specified ports are in use.") diff --git a/kibot/resources/kiri/kiri.css b/kibot/resources/kiri/kiri.css new file mode 100644 index 00000000..dda8690d --- /dev/null +++ b/kibot/resources/kiri/kiri.css @@ -0,0 +1,342 @@ + +html, body { + height : 100%; + margin : 0px; + font-size : 1em; /* Normalize view on firefox */ + line-height : 1.5em; /* Normalize view on firefox */ + caret-color: transparent; +} + +.fill { + min-height: 100%; + height: 100%; + min-width: 100%; + width: 100%; +} + +.no-gutters { + margin-right: 0px; + margin-left: 0px; + > .col, + > [class*="col-"] { + padding-right: 0px; + padding-left: 0px; + } +} + +/* ============================== + Commits List + ============================== +*/ + +.list-group-item { + user-select: none; +} + +.list-group input[type="checkbox"] { + display: none; +} + +.list-group input[type="checkbox"] + .list-group-item { + background-color: #222; + color: #dbdbdb; + font-family: monospace; + padding: 1px; + margin: 4px; + border-radius: 5px; +} + +.list-group input[type="checkbox"] + .list-group-item:before { + color: transparent; + font-weight: bold; +} + +.list-group input[type="checkbox"]:checked + .list-group-item { + background-color: #111; + color: #fff; + border-radius: 5px; +} + +.list-group input[type="checkbox"]:checked + .list-group-item:before { + color: inherit; +} + +/* ============================== + Scrollbar + ============================== +*/ + +.scrollbox { + overflow: auto; + visibility: hidden; +} + +.scrollbox-content, +.scrollbox:hover, +.scrollbox:focus { + visibility: visible; +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-thumb { + background: rgba(90, 90, 90); +} + +::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.2); +} + +/* ============================== + Toolbar buttons + ============================== +*/ + +.btn-group input[type="radio"] { + display: none; +} + +.btn-group input[type="radio"] + .btn-group-item { + font-family: monospace; +} + +.btn-group input[type="radio"] + .btn-group-item:before { +} + +/* ============================== + Layers List + ============================== +*/ + +.list-group input[type="radio"] { + display: none; +} + +.list-group input[type="radio"] + .list-group-item { + background-color: #222222; + color: #dbdbdb; + font-family: monospace; + padding: 2px; + margin: 4px; +} + +.list-group input[type="radio"] + .list-group-item:before { + color: transparent; + font-weight: bold; +} + +.list-group input[type="radio"]:checked + .list-group-item { + background-color: #111111; + color: #FFF; +} + +.list-group input[type="radio"]:checked + .list-group-item:before { + color: inherit; +} + +/* ============================== + ============================== +*/ + +#diff-container { +/* border: 1px solid #111;*/ + margin-top: 15px; + background-color: #222; + border-radius: 5px; +} + +#svg-id { +/* border: 1px solid #111;*/ + margin-top: 15px; + background-color: #222; + border-radius: 5px; +} + +/* ============================== + Layers colors + ============================== +*/ + +.F_Cu { + filter : invert(28%) sepia(50%) saturate(2065%) hue-rotate(334deg) brightness(73%) contrast(97%); +} + +.In1_Cu { + filter : invert(69%) sepia(39%) saturate(1246%) hue-rotate(17deg) brightness(97%) contrast(104%); +} + +.In2_Cu { + filter : invert(14%) sepia(79%) saturate(5231%) hue-rotate(293deg) brightness(91%) contrast(119%); +} + +.In3_Cu { + filter : invert(14%) sepia(79%) saturate(5231%) hue-rotate(293deg) brightness(91%) contrast(119%); +} + +.In4_Cu { + filter : invert(14%) sepia(79%) saturate(5231%) hue-rotate(293deg) brightness(91%) contrast(119%); +} + +.B_Cu { + filter : invert(44%) sepia(14%) saturate(2359%) hue-rotate(70deg) brightness(103%) contrast(82%); +} + +.F_Mask { + filter : invert(27%) sepia(51%) saturate(1920%) hue-rotate(269deg) brightness(89%) contrast(96%); +} + +.B_Mask { + filter : invert(22%) sepia(56%) saturate(2652%) hue-rotate(277deg) brightness(94%) contrast(87%); +} + +.F_Paste { + filter : invert(57%) sepia(60%) saturate(6%) hue-rotate(314deg) brightness(92%) contrast(99%); +} + +.B_Paste { + filter : invert(91%) sepia(47%) saturate(4033%) hue-rotate(139deg) brightness(82%) contrast(91%); +} + +.F_SilkS { + filter : invert(46%) sepia(44%) saturate(587%) hue-rotate(132deg) brightness(101%) contrast(85%); +} + +.B_SilkS { + filter : invert(14%) sepia(27%) saturate(2741%) hue-rotate(264deg) brightness(95%) contrast(102%); +} + +.Edge_Cuts { + filter : invert(79%) sepia(79%) saturate(401%) hue-rotate(6deg) brightness(88%) contrast(88%); +} + +.Margin { + filter : invert(74%) sepia(71%) saturate(5700%) hue-rotate(268deg) brightness(89%) contrast(84%); +} + +.Dwgs_User { + filter : invert(40%) sepia(68%) saturate(7431%) hue-rotate(203deg) brightness(89%) contrast(98%); +} + +.Cmts_User { + filter : invert(73%) sepia(10%) saturate(1901%) hue-rotate(171deg) brightness(95%) contrast(102%); +} + +.Eco1_User { + filter : invert(25%) sepia(98%) saturate(2882%) hue-rotate(109deg) brightness(90%) contrast(104%); +} + +.Eco2_User { + filter : invert(85%) sepia(21%) saturate(5099%) hue-rotate(12deg) brightness(91%) contrast(102%); +} + +.F_Fab { + filter : invert(71%) sepia(21%) saturate(4662%) hue-rotate(21deg) brightness(103%) contrast(100%); +} + +.B_Fab { + filter : invert(60%) sepia(0%) saturate(0%) hue-rotate(253deg) brightness(87%) contrast(90%); +} + +.F_Adhes { + filter : invert(38%) sepia(49%) saturate(1009%) hue-rotate(254deg) brightness(88%) contrast(86%); +} + +.B_Adhes { + filter : invert(24%) sepia(48%) saturate(2586%) hue-rotate(218deg) brightness(88%) contrast(92%); +} + +.F_CrtYd { + filter : invert(73%) sepia(1%) saturate(0%) hue-rotate(116deg) brightness(92%) contrast(91%); +} + +.B_CrtYd { + filter : invert(79%) sepia(92%) saturate(322%) hue-rotate(3deg) brightness(89%) contrast(92%); +} + +/* ============================== +** ============================*/ + +.ellipsis { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + display: inline-block; +} + +label#layers, label#pages { + margin-top: 0px; + margin-bottom: 4px; + white-space: nowrap; + width: 180px; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + vertical-align:top; +} + +#server_offline { + position: fixed; + display: none; + width: 100%; + height: 100%; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0,0,0,0.8); + z-index: 2; + cursor: pointer; +} + +/* ============================== +** ============================*/ + +a:link { + color: #28a745; +} + +a:visited { + color: #28a745; +} + +a:hover { + color: #28a745; +} + +a:active { + color: #28a745; +} + +#commit1_legend_hash { + color: #28a745; +} +#commit2_legend_hash { + color: #28a745; +} + +/* ============================== +** ============================*/ + +.hidden_iframe { + position:absolute; top:-1px; left:-1px; width:1px; height:1px; + display: none; +} + +.shortcut_col { + width: 150px; + text-align: right; + padding-right: 15px; +} + +.modal-content { + background: #333; +} + +hr { + background-color: white; + height: 1px; + border: 0; +} diff --git a/kibot/resources/kiri/kiri.js b/kibot/resources/kiri/kiri.js new file mode 100644 index 00000000..93d6f1c5 --- /dev/null +++ b/kibot/resources/kiri/kiri.js @@ -0,0 +1,1632 @@ + +// jshint esversion:6 + +var commit1; +var commit2; + +var old_view; +var current_view; + +current_diff_filter = "diff" // diff or normal + +var panZoom_instance = null; +var lastEventListener = null; +var lastEmbed = null; + +var current_selected_page = 0; +var previous_selected_page = -1; + +sch_current_zoom = null; +sch_old_zoom = null; +sch_current_pan = null; + +pcb_current_zoom = null; +pcb_old_zoom = null; +pcb_current_pan = null; + +// Variables updated by Kiri +var selected_view = "schematic"; + +var is_fullscreen = false; + +// ======================================= +// HANDLE SHORTCUTS +// ======================================= + +function select_next_2_commits() { + commits = $("#commits_form input:checkbox[name='commit']"); + + selected_commits = []; + next_selected_commits = []; + + for (i = 0; i < commits.length; i++) { + if ($("#commits_form input:checkbox[name='commit']")[i].checked) { + selected_commits.push(i); + next_selected_commits.push(i + 1); + } + } + + // When second commit reaches the end, moves the first commit forward (if possible) + if (next_selected_commits[1] >= commits.length) { + next_selected_commits[1] = commits.length - 1; + if (next_selected_commits[0] <= commits.length - 2) { + next_selected_commits[0] = selected_commits[0] + 1; + } + } + else { + // By default does not change the first commit + next_selected_commits[0] = selected_commits[0]; + } + + // Fix bottom boundary + if (next_selected_commits[0] >= next_selected_commits[1]) { + next_selected_commits[0] = next_selected_commits[1] - 1; + } + + // Fix bottom boundary + if (next_selected_commits[0] >= commits.length - 2) { + next_selected_commits[0] = commits.length - 2; + } + + // Update selected commits + for (i = 0; i < selected_commits.length; i++) { + commits[selected_commits[i]].checked = false; + } + for (i = 0; i < selected_commits.length; i++) { + commits[next_selected_commits[i]].checked = true; + } + + update_commits(); +} + +function select_next_commit() { + commits = $("#commits_form input:checkbox[name='commit']"); + + selected_commits = []; + next_selected_commits = []; + + for (i = 0; i < commits.length; i++) { + if ($("#commits_form input:checkbox[name='commit']")[i].checked) { + selected_commits.push(i); + next_selected_commits.push(i + 1); + } + } + + // Fix bottom boundary + if (next_selected_commits[1] >= commits.length - 1) { + next_selected_commits[1] = commits.length - 1; + } + + // Fix bottom boundary + if (next_selected_commits[0] >= commits.length - 2) { + next_selected_commits[0] = commits.length - 2; + } + + for (i = 0; i < selected_commits.length; i++) { + commits[selected_commits[i]].checked = false; + } + for (i = 0; i < selected_commits.length; i++) { + commits[next_selected_commits[i]].checked = true; + } + + update_commits(); +} + +function select_previows_2_commits() { + commits = $("#commits_form input:checkbox[name='commit']"); + + selected_commits = []; + next_selected_commits = []; + + for (i = 0; i < commits.length; i++) { + if ($("#commits_form input:checkbox[name='commit']")[i].checked) { + selected_commits.push(i); + next_selected_commits.push(i - 1); + } + } + + // By default does not change the first commit + next_selected_commits[0] = selected_commits[0]; + + // When commits are touching, move first backwards (if possible) + if (next_selected_commits[1] == next_selected_commits[0]) { + if (next_selected_commits[0] > 0) { + next_selected_commits[0] = next_selected_commits[0] -1; + } + } + + // Fix top boundary + if (next_selected_commits[0] < 0) { + next_selected_commits[0] = 0; + } + + // Fix top boundary + if (next_selected_commits[1] <= 1) { + next_selected_commits[1] = 1; + } + + // Update selected commits + for (i = 0; i < selected_commits.length; i++) { + commits[selected_commits[i]].checked = false; + } + for (i = 0; i < selected_commits.length; i++) { + commits[next_selected_commits[i]].checked = true; + } + + update_commits(); +} + +function select_previows_commit() +{ + commits = $("#commits_form input:checkbox[name='commit']"); + + selected_commits = []; + next_selected_commits = []; + + for (i = 0; i < commits.length; i++) { + if ($("#commits_form input:checkbox[name='commit']")[i].checked) { + selected_commits.push(i); + next_selected_commits.push(i - 1); + } + } + + // Fix top boundary + if (next_selected_commits[0] <= 0) { + next_selected_commits[0] = 0; + } + + // Fix top boundary + if (next_selected_commits[1] <= 1) { + next_selected_commits[1] = 1; + } + + // Update selected commits + for (i = 0; i < selected_commits.length; i++) { + commits[selected_commits[i]].checked = false; + } + for (i = 0; i < selected_commits.length; i++) { + commits[next_selected_commits[i]].checked = true; + } + + update_commits(); +} + +function reset_commits_selection() +{ + commits = $("#commits_form input:checkbox[name='commit']"); + selected_commits = []; + for (i = 0; i < commits.length; i++) { + $("#commits_form input:checkbox[name='commit']")[i].checked = false; + } + for (i = 0; i < 2; i++) { + $("#commits_form input:checkbox[name='commit']")[i].checked = true; + } + + // reset visibility of the diff images + $("#diff-xlink-1").css('visibility', 'visible') + $("#commit1_legend").css('visibility', 'visible'); + $("#commit1_legend_text").css('visibility', 'visible'); + $("#commit1_legend_fs").css('visibility', 'visible'); + $("#commit1_legend_text_fs").css('visibility', 'visible'); + $("#commit1_legend").css('color', '#00FFFF'); + $("#commit1_legend_fs").css('color', '#00FFFF'); + + $("#diff-xlink-2").css('visibility', 'visible') + $("#commit2_legend").css('visibility', 'visible'); + $("#commit2_legend_text").css('visibility', 'visible'); + $("#commit2_legend_fs").css('visibility', 'visible'); + $("#commit2_legend_text_fs").css('visibility', 'visible'); + $("#commit2_legend").css('color', '#880808'); + $("#commit2_legend_fs").css('color', '#880808'); + + $("#commit3_legend").css('visibility', 'visible'); + $("#commit3_legend_text").css('visibility', 'visible'); + $("#commit3_legend_fs").css('visibility', 'visible'); + $("#commit3_legend_text_fs").css('visibility', 'visible'); + + $("#diff-xlink-1").css('filter', 'url(#filter-1)') /// FILTER_DEFAULT + $("#diff-xlink-2").css('filter', 'url(#filter-2)') /// FILTER_DEFAULT + + update_commits(); +} + +function toggle_sch_pcb_view() { + old_view = current_view; + current_view = $('#view_mode input[name="view_mode"]:checked').val(); + if (current_view == "show_sch") { + show_pcb(); + } else { + show_sch(); + } + update_commits(); +} + +function toggle_old_commit_visibility() +{ + if ($("#diff-xlink-1").css('visibility') === "hidden") + { + current_diff_filter = "diff"; + $("#diff-xlink-1").css('visibility', 'visible') + $("#commit1_legend").css('visibility', 'visible'); + $("#commit1_legend_text").css('visibility', 'visible'); + $("#commit1_legend_fs").css('visibility', 'visible'); + $("#commit1_legend_text_fs").css('visibility', 'visible'); + + $("#commit3_legend").css('visibility', 'visible'); + $("#commit3_legend_text").css('visibility', 'visible'); + $("#commit3_legend_fs").css('visibility', 'visible'); + $("#commit3_legend_text_fs").css('visibility', 'visible'); + } + else + { + current_diff_filter = "single"; + $("#diff-xlink-1").css('visibility', 'hidden') + $("#commit1_legend").css('visibility', 'hidden'); + $("#commit1_legend_text").css('visibility', 'hidden'); + $("#commit1_legend_fs").css('visibility', 'hidden'); + $("#commit1_legend_text_fs").css('visibility', 'hidden'); + + $("#commit3_legend").css('visibility', 'hidden'); + $("#commit3_legend_text").css('visibility', 'hidden'); + $("#commit3_legend_fs").css('visibility', 'hidden'); + $("#commit3_legend_text_fs").css('visibility', 'hidden'); + } + + // enable the other image back + if ($("#diff-xlink-1").css('visibility') === "hidden") + { + $("#diff-xlink-2").css('visibility', 'visible') + $("#diff-xlink-2").css('filter', 'url(#filter-22)') /// FILTER_WHITE + $("#commit2_legend").css('visibility', 'visible'); + $("#commit2_legend_text").css('visibility', 'visible'); + $("#commit2_legend_fs").css('visibility', 'visible'); + $("#commit2_legend_text_fs").css('visibility', 'visible'); + + $("#commit2_legend").css('color', '#a7a7a7'); + $("#commit2_legend_fs").css('color', '#a7a7a7'); + } + else + { + $("#diff-xlink-1").css('filter', 'url(#filter-1)') /// FILTER_DEFAULT + $("#diff-xlink-2").css('filter', 'url(#filter-2)') /// FILTER_DEFAULT + + $("#commit1_legend").css('color', '#00FFFF'); + $("#commit1_legend_fs").css('color', '#00FFFF'); + $("#commit2_legend").css('color', '#880808'); + $("#commit2_legend_fs").css('color', '#880808'); + } +} + +function toggle_new_commit_visibility() +{ + if ($("#diff-xlink-2").css('visibility') === "hidden") + { + current_diff_filter = "diff"; + $("#diff-xlink-2").css('visibility', 'visible') + $("#commit2_legend").css('visibility', 'visible'); + $("#commit2_legend_text").css('visibility', 'visible'); + $("#commit2_legend_fs").css('visibility', 'visible'); + $("#commit2_legend_text_fs").css('visibility', 'visible'); + + $("#commit3_legend").css('visibility', 'visible'); + $("#commit3_legend_text").css('visibility', 'visible'); + $("#commit3_legend_fs").css('visibility', 'visible'); + $("#commit3_legend_text_fs").css('visibility', 'visible'); + } + else + { + current_diff_filter = "single"; + $("#diff-xlink-2").css('visibility', 'hidden') + $("#commit2_legend").css('visibility', 'hidden'); + $("#commit2_legend_text").css('visibility', 'hidden'); + $("#commit2_legend_fs").css('visibility', 'hidden'); + $("#commit2_legend_text_fs").css('visibility', 'hidden'); + + $("#commit3_legend").css('visibility', 'hidden'); + $("#commit3_legend_text").css('visibility', 'hidden'); + $("#commit3_legend_fs").css('visibility', 'hidden'); + $("#commit3_legend_text_fs").css('visibility', 'hidden'); + } + + // enable the other image back + if ($("#diff-xlink-2").css('visibility') === "hidden") + { + $("#diff-xlink-1").css('visibility', 'visible') + $("#diff-xlink-1").css('filter', 'url(#filter-12)') /// FILTER_WHITE + $("#commit1_legend").css('visibility', 'visible'); + $("#commit1_legend_text").css('visibility', 'visible'); + $("#commit1_legend_fs").css('visibility', 'visible'); + $("#commit1_legend_text_fs").css('visibility', 'visible'); + + $("#commit1_legend").css('color', '#a7a7a7'); + $("#commit1_legend_text_fs").css('color', '#a7a7a7'); + } + else + { + $("#diff-xlink-1").css('filter', 'url(#filter-1)') /// FILTER_DEFAULT + $("#diff-xlink-2").css('filter', 'url(#filter-2)') /// FILTER_DEFAULT + + $("#commit1_legend").css('color', '#00FFFF'); + $("#commit1_legend_fs").css('color', '#00FFFF'); + $("#commit2_legend").css('color', '#880808'); + $("#commit2_legend_fs").css('color', '#880808'); + } +} + +function select_next_sch_or_pcb(cycle = false) { + if (document.getElementById("show_sch").checked) { + pages = $("#pages_list input:radio[name='pages']"); + selected_page = pages.index(pages.filter(':checked')); + + new_index = selected_page + 1; + if (new_index >= pages.length) { + if (cycle) { + new_index = 0; + } + else { + new_index = pages.length - 1; + } + } + + pages[new_index].checked = true; + + update_page(); + } + else + { + layers = $("#layers_list input:radio[name='layers']"); + selected_layer = layers.index(layers.filter(':checked')); + + new_index = selected_layer + 1; + if (new_index >= layers.length) { + if (cycle) { + new_index = 0; + } + else { + new_index = layers.length - 1; + } + } + + layers[new_index].checked = true; + + update_layer(); + } +} + +function select_preview_sch_or_pcb(cycle = false) { + if (document.getElementById("show_sch").checked) { + pages = $("#pages_list input:radio[name='pages']"); + selected_page = pages.index(pages.filter(':checked')); + + new_index = selected_page - 1; + if (new_index < 0) { + if (cycle) { + new_index = pages.length - 1; + } + else { + new_index = 0; + } + } + + pages[new_index].checked = true; + + update_page(); + update_sheets_list(commit1, commit2); + + } else { + layers = $("#layers_list input:radio[name='layers']"); + selected_layer = layers.index(layers.filter(':checked')); + + new_index = selected_layer - 1; + if (new_index < 0) { + if (cycle) { + new_index = layers.length - 1; + } + else { + new_index = 0; + } + } + + layers[new_index].checked = true; + + update_layer(); + } +} + +function svg_fit_center() +{ + panZoom_instance.resetZoom(); + panZoom_instance.center(); +} + +function svg_zoom_in() +{ + panZoom_instance.zoomIn(); +} + +function svg_zoom_out() +{ + panZoom_instance.zoomOut(); +} + +function manual_pan(direction) +{ + const step = 50; + + switch(direction) { + case "up": + panZoom_instance.panBy({x: 0, y: step}); + break; + case "down": + panZoom_instance.panBy({x: 0, y: -step}); + break; + case "left": + panZoom_instance.panBy({x: step, y: 0}); + break; + case "right": + panZoom_instance.panBy({x: -step, y: 0}); + break; + } +} + +// Commits +Mousetrap.bind(['ctrl+down', 'ctrl+]','command+down', 'command+]'], function(){select_next_2_commits()}); +Mousetrap.bind(['down', ']'], function(){select_next_commit()}); + +Mousetrap.bind(['ctrl+up', 'ctrl+[', 'command+up', 'command+['], function(){select_previows_2_commits()}); +Mousetrap.bind(['up', '['], function(){select_previows_commit()}); + +Mousetrap.bind(['r', 'R'], function(){reset_commits_selection()}); + +// View +Mousetrap.bind(['s', 'S'], function(){toggle_sch_pcb_view()}); + +Mousetrap.bind(['q', 'Q'], function(){toggle_old_commit_visibility()}); +Mousetrap.bind(['w', 'W'], function(){toggle_new_commit_visibility()}); + +Mousetrap.bind(['alt+q', 'alt+Q'], function(){toggle_new_commit_visibility()}); +Mousetrap.bind(['alt+w', 'alt+W'], function(){toggle_old_commit_visibility()}); + +Mousetrap.bind(['right'], function(){select_next_sch_or_pcb()}); +Mousetrap.bind(['left'], function(){select_preview_sch_or_pcb()}); + +Mousetrap.bind(['ctrl+right', 'command+right'], function(){select_next_sch_or_pcb(true)}); +Mousetrap.bind(['ctrl+left', 'command+left'], function(){select_preview_sch_or_pcb(true)}); + +// SVG PAN +Mousetrap.bind('alt+up', function(){manual_pan("up")}); +Mousetrap.bind('alt+down', function(){manual_pan("down")}); +Mousetrap.bind('alt+left', function(){manual_pan("left")}); +Mousetrap.bind('alt+right', function(){manual_pan("right")}); + +// SVG ZOOM +Mousetrap.bind('0', function(){svg_fit_center()}); +Mousetrap.bind(['+', '='], function(){svg_zoom_in()}); +Mousetrap.bind('-', function(){svg_zoom_out()}); + +// Misc +Mousetrap.bind(['f', 'F'], function(){toggle_fullscreen()}); +Mousetrap.bind(['i', 'I'], function(){show_info_popup()}); + +// ======================================= +// ======================================= + +// For images related with each commit, it is good to have the same image cached with the same specially when serving throug the internet +// For those images, it uses the commit hash as the timestamp +function url_timestamp(timestamp_id="") { + if (timestamp_id) { + return "?t=" + timestamp_id; + } + else { + return "?t=" + new Date().getTime(); + } +} + +function if_url_exists(url, callback) { + let request = new XMLHttpRequest(); + request.open('GET', url, true); + request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); + request.setRequestHeader('Accept', '*/*'); + request.onprogress = function(event) { + let status = event.target.status; + let statusFirstNumber = (status).toString()[0]; + switch (statusFirstNumber) { + case '2': + request.abort(); + return callback(true); + default: + request.abort(); + return callback(false); + } + }; + request.send(''); +} + +function update_commits() { + + // Remove tooltips so they dont get stuck + $('[data-toggle="tooltip"]').tooltip("hide"); + + console.log("================================================================================"); + + var commits = $("#commits_form input:checkbox[name='commit']"); + var hashes = []; + + for (var i = 0; i < commits.length; i++) { + if (commits[i].checked) { + var value = commits[i].value; + hashes.push(value); + } + } + + // It needs 2 items selected to do something + if (hashes.length < 2) { + return; + } + + // Update selected commits + commit1 = hashes[0].replace(/\s+/g, ''); + commit2 = hashes[1].replace(/\s+/g, ''); + + console.log("commit1:", commit1); + console.log("commit2:", commit2); + + + // 1. Update commit_legend_links + // 2. Update commit_legend + // 3. Update current_diff_view + + + // Update commit_legend_links + + var old_commit1 = document.getElementById("commit1_hash").value; + var old_commit2 = document.getElementById("commit2_hash").value; + + var kicad_pro_path_1 = document.getElementById("commit1_kicad_pro_path").value; + var kicad_pro_path_2 = document.getElementById("commit2_kicad_pro_path").value; + + document.getElementById("commit1_kicad_pro_path").value = kicad_pro_path_1.replace(old_commit1, commit1); + document.getElementById("commit2_kicad_pro_path").value = kicad_pro_path_2.replace(old_commit2, commit2); + + // Update commit_legend + + document.getElementById("commit1_hash").value = commit1; + document.getElementById("commit2_hash").value = commit2; + + document.getElementById("commit1_legend_hash").innerHTML = commit1; + document.getElementById("commit2_legend_hash").innerHTML = commit2; + + // Update current_diff_view + + old_view = current_view; + current_view = $('#view_mode input[name="view_mode"]:checked').val(); + + if (current_view == "show_sch") { + update_page(); + } else { + update_layer(); + } +} + +function loadFile(filePath) { + + console.log("filePath:", filePath); + + var result = null; + var xmlhttp = new XMLHttpRequest(); + xmlhttp.open('GET', filePath, false); + xmlhttp.send(); + if (xmlhttp.status==200) { + result = xmlhttp.responseText; + } + return result; +} + +function update_page() +{ + console.log("-----------------------------------------"); + + // Runs only when updating commits + update_sheets_list(commit1, commit2); + + var pages = $("#pages_list input:radio[name='pages']"); + var selected_page; + var page_name; + + // if a different page was in use before, revert the selection to it + // TODO: maybe I have to use a list instead... + if (previous_selected_page > -1) { + pages[previous_selected_page].checked = true; + previous_selected_page = -1; + } + + // try to get the first page + try { + selected_page = pages.index(pages.filter(':checked')); + page_name = pages[selected_page].id; + current_selected_page = selected_page; + + // if there is no page selected, select the first one + // TODO: instead of the first item by default, a better solution would change to the next inferior index + // and keep decrementing until reaching a valid index + } catch (error) { + previous_selected_page = current_selected_page; + pages[0].checked = true; + selected_page = pages.index(pages.filter(':checked')); + page_name = pages[selected_page].id; + } + + var page_filename = pages[selected_page].value.replace(".kicad_sch", "").replace(".sch", ""); + + if (commit1 == ""){ + commit1 = document.getElementById("diff-xlink-1-sch").href.baseVal.split("/")[1]; + } + if (commit2 == ""){ + commit2 = document.getElementById("diff-xlink-2-sch").href.baseVal.split("/")[1]; + } + + var image_path_1 = "../" + commit1 + "/_KIRI_/sch/" + page_filename + ".svg"; + var image_path_2 = "../" + commit2 + "/_KIRI_/sch/" + page_filename + ".svg"; + + console.log("[SCH] page_filename =", page_filename); + console.log("[SCH] image_path_1 =", image_path_1); + console.log("[SCH] image_path_2 =", image_path_2); + + var image_path_timestamp_1 = image_path_1 + url_timestamp(commit1); + var image_path_timestamp_2 = image_path_2 + url_timestamp(commit2); + + if (current_view != old_view) + { + old_view = current_view; + removeEmbed(); + lastEmbed = createNewEmbed(image_path_timestamp_1, image_path_timestamp_2); + } + else + { + document.getElementById("diff-xlink-1").href.baseVal = image_path_timestamp_1; + document.getElementById("diff-xlink-2").href.baseVal = image_path_timestamp_2; + + document.getElementById("diff-xlink-1").setAttributeNS('http://www.w3.org/1999/xlink', 'href', image_path_timestamp_1); + document.getElementById("diff-xlink-2").setAttributeNS('http://www.w3.org/1999/xlink', 'href', image_path_timestamp_2); + + if_url_exists(image_path_timestamp_1, function(exists) { + if (exists == true) { + document.getElementById("diff-xlink-1").parentElement.style.display = 'inline' } + else { + document.getElementById("diff-xlink-1").parentElement.style.display = "none"; + } + }); + + if_url_exists(image_path_timestamp_2, function(exists) { + if (exists == true) { + document.getElementById("diff-xlink-2").parentElement.style.display = 'inline'; + } + else { + document.getElementById("diff-xlink-2").parentElement.style.display = "none"; + } + }); + } + + // keep images visibility the same as the legend + $("#diff-xlink-1").css('visibility', $("#commit1_legend").css('visibility')) + $("#diff-xlink-2").css('visibility', $("#commit2_legend").css('visibility')) + + update_fullscreen_label(); +} + +function update_sheets_list(commit1, commit2) { + + // Get current selected page name + var pages = $("#pages_list input:radio[name='pages']"); + var selected_page = pages.index(pages.filter(':checked')); + + // Save the current selected page, if any + try { + selected_sheet = pages[selected_page].id; + } + catch(err) { + selected_page = ""; + console.log("There isn't a sheet selected"); + } + + // Data format: ID|LAYER + + data1 = loadFile("../" + commit1 + "/_KIRI_/sch_sheets" + url_timestamp(commit1)).split("\n").filter((a) => a); + data2 = loadFile("../" + commit2 + "/_KIRI_/sch_sheets" + url_timestamp(commit2)).split("\n").filter((a) => a); + + var sheets = []; + + for (const d of data1) + { + sheet = d.split("|")[0]; + sheets.push(sheet); + } + + for (const d of data2) + { + sheet = d.split("|")[0]; + if (! sheets.includes(sheet)) + { + sheets.push(sheet); + } + } + + // sheets.sort(); + // sheets = Array.from(new Set(sheets.sort())); + sheets = Array.from(new Set(sheets)); + + console.log("[SCH] Sheets =", sheets.length); + console.log("sheets", sheets); + + var new_sheets_list = []; + var form_inputs_html; + + for (const sheet of sheets) + { + var input_html = ` + + + + `; + + new_sheets_list.push(sheet); + + form_inputs_html = form_inputs_html + input_html; + } + + // Get the current list of pages + pages = $("#pages_list input:radio[name='pages']"); + const current_sheets_list = Array.from(pages).map((opt) => opt.id); + + // Return if the current list is equal to the new list + console.log("current_sheets_list = ", current_sheets_list); + console.log("new_sheets_list = ", new_sheets_list); + if (current_sheets_list.toString() === new_sheets_list.toString()) { + console.log("Keep the same list of sheets"); + return; + } + + // Update list of pages + sheets_element = document.getElementById("pages_list_form"); + sheets_element.innerHTML = form_inputs_html.replace("undefined", ""); + + // rerun tooltips since they are getting ugly. + $('[data-toggle="tooltip"]').tooltip({html: true}); + $('[data-toggle="tooltip"]').tooltip('update'); + $('[data-toggle="tooltip"]').tooltip({boundary: 'body'}); + + const optionLabels = Array.from(pages).map((opt) => opt.id); + + const hasOption = optionLabels.includes(selected_sheet); + if (hasOption) { + // Keep previews selection active + $("#pages_list input:radio[name='pages'][value='" + selected_sheet + "']").prop('checked', true); + } + else { + // If old selection does not exist, maybe the list is now shorter, then select the last item... + pages[optionLabels.length-1].checked = true; + } + + // If nothing is selected still, select the first item + if (!pages.filter(':checked').length) { + pages[0].checked = true; + } +} + +function layer_color(layer_id) { + + var color; + + console.log(">>> layer_id", layer_id); + + const F_Cu = 0; + const In1_Cu = 1; + const In2_Cu = 2; + const In3_Cu = 3; + const In4_Cu = 4; + const B_Cu = 31; + const B_Adhes = 32; + const F_Adhes = 33; + const B_Paste = 34; + const F_Paste = 35; + const B_SilkS = 36; + const F_SilkS = 37; + const B_Mask = 38; + const F_Mask = 39; + const Dwgs_User = 40; + const Cmts_User = 41; + const Eco1_User = 42; + const Eco2_User = 43; + const Edge_Cuts = 44; + const Margin = 45; + const B_CrtYd = 46; + const F_CrtYd = 47; + const B_Fab = 48; + const F_Fab = 49; + + switch(layer_id) { + case B_Adhes: color="#3545A8"; break; + case B_CrtYd: color="#D3D04B"; break; + case B_Cu: color="#359632"; break; + case B_Fab: color="#858585"; break; + case B_Mask: color="#943197"; break; + case B_Paste: color="#969696"; break; + case B_SilkS: color="#481649"; break; + case Cmts_User: color="#7AC0F4"; break; + case Dwgs_User: color="#0364D3"; break; + case Eco1_User: color="#008500"; break; + case Eco2_User: color="#008500"; break; + case Edge_Cuts: color="#C9C83B"; break; + case F_Adhes: color="#A74AA8"; break; + case F_CrtYd: color="#A7A7A7"; break; + case F_Cu: color="#952927"; break; + case F_Fab: color="#C2C200"; break; + case F_Mask: color="#943197"; break; + case F_Paste: color="#3DC9C9"; break; + case F_SilkS: color="#339697"; break; + case In1_Cu: color="#C2C200"; break; + case In2_Cu: color="#C200C2"; break; + case In3_Cu: color="#C20000"; break; + case In4_Cu: color="#0000C2"; break; + case Margin: color="#D357D2"; break; + default: color="#DBDBDB"; + } + + return color; +} + +function pad(num, size) +{ + num = num.toString(); + while (num.length < size) { + num = "0" + num; + } + return num; +} + +function update_layers_list(commit1, commit2, selected_layer_idx, selected_layer_id) +{ + var used_layers_1; + var used_layers_2; + + var id; + var layer; + var dict = {}; + + var id_pad; + var layer_name; + var color; + var checked; + + var new_layers_list = []; + var form_inputs_html; + + // Get current selected page name + var layers = $("#layers_list input:radio[name='layers']"); + var selected_layer_element = layers.index(layers.filter(':checked')); + + // Save the current selected page, if any + try { + selected_layer = layers[selected_layer_element].id; + } + catch(err) { + selected_layer = ""; + console.log("There isn't a layer selected"); + } + + // File = ../[COMMIT]/_KIRI_/pcb_layers + // Format = ID|LAYER + + used_layers_1 = loadFile("../" + commit1 + "/_KIRI_/pcb_layers" + url_timestamp(commit1)).split("\n").filter((a) => a); + used_layers_2 = loadFile("../" + commit2 + "/_KIRI_/pcb_layers" + url_timestamp(commit2)).split("\n").filter((a) => a); + + for (const line of used_layers_1) + { + id = line.split("|")[0]; + layer = line.split("|")[1]; //.replace(".", "_"); + dict[id] = [layer]; + } + + for (const line of used_layers_2) + { + id = line.split("|")[0]; + layer = line.split("|")[1]; //.replace(".", "_"); + + // Add new key + if (! dict.hasOwnProperty(id)) { + dict[id] = [layer]; + } + else { + // Append if id key exists + if (dict[id] != layer) { + dict[id].push(layer); + } + } + } + + console.log("[PCB] Layers =", Object.keys(dict).length); + + for (const [layer_id, layer_names] of Object.entries(dict)) + { + id = parseInt(layer_id); + id_pad = pad(id, 2); + layer_name = layer_names[0]; + color = layer_color(id); + + var input_html = ` + + + + `; + + new_layers_list.push(layer_names.toString()); + + form_inputs_html = form_inputs_html + input_html; + } + + // Get the current list of pages + const current_layers_list = Array.from(layers).map((opt) => opt.value.replace("layer-", "")); + + // Return if the current list is equal to the new list + console.log("current_layers_list = ", current_layers_list); + console.log("new_layers_list = ", new_layers_list); + if (current_layers_list.toString() === new_layers_list.toString()) { + console.log("Keep the same list of layers"); + return; + } + + // Update layers list + layers_element = document.getElementById("layers_list_form"); + layers_element.innerHTML = form_inputs_html.replace("undefined", ""); + + // Update html tooltips + $('[data-toggle="tooltip"]').tooltip({html:true}); + $('[data-toggle="tooltip"]').tooltip('update'); + $('[data-toggle="tooltip"]').tooltip({boundary: 'body'}); + + // Enable back the selected layer + const optionLabels = Array.from(layers).map((opt) => opt.id); + + const hasOption = optionLabels.includes(selected_layer); + if (hasOption) { + // Keep previews selection active + $("#layers_list input:radio[name='layers'][value=" + selected_layer + "]").prop('checked', true); + } + else { + // If old selection does not exist, maybe the list is now shorter, then select the last item... + layers[optionLabels.length-1].checked = true; + } + + // restore previously selected index + layers = $("#layers_list input:radio[name='layers']"); + if (selected_layer_idx >= 0) { + layers[selected_layer_idx].checked = true; + } + + // If nothing is selected still, select the first item + if (! layers.filter(':checked').length) { + layers[0].checked = true; + } +} + +function update_layer() { + + console.log("-----------------------------------------"); + + var layers = $("#layers_list input:radio[name='layers']"); + var selected_layer; + var layer_id; + + if (layers) + { + selected_layer = layers.index(layers.filter(':checked')); + console.log(">>>> [selected_layer] = ", selected_layer); + if (selected_layer >= 0) { + layer_id = layers[selected_layer].id.split("-")[1]; + console.log(">>>> [label_id_IF] = ", layer_id); + } + else { + try { + layers[0].checked = true; + selected_layer = layers.index(layers.filter(':checked')); + layer_id = layers[selected_layer].id.split("-")[1]; + console.log(">>>> [label_id_ELSE] = ", layer_id); + } catch (error) { + console.log("[PCB] Images may not exist and Kicad layout may be missing."); + show_sch(); + return; + } + } + } + else { + console.log("[PCB] Images may not exist and Kicad layout may be missing."); + show_sch(); + return; + } + + if (commit1 == "") { + commit1 = document.getElementById("diff-xlink-1-pcb").href.baseVal.split("/")[1]; + } + if (commit2 == "") { + commit2 = document.getElementById("diff-xlink-2-pcb").href.baseVal.split("/")[1]; + } + + update_layers_list(commit1, commit2, selected_layer, layer_id); + + var image_path_1 = "../" + commit1 + "/_KIRI_/pcb/layer" + "-" + layer_id + ".svg"; + var image_path_2 = "../" + commit2 + "/_KIRI_/pcb/layer" + "-" + layer_id + ".svg"; + + console.log("[PCB] layer_id =", layer_id); + console.log("[PCB] image_path_1 =", image_path_1); + console.log("[PCB] image_path_2 =", image_path_2); + + var image_path_timestamp_1 = image_path_1 + url_timestamp(commit1); + var image_path_timestamp_2 = image_path_2 + url_timestamp(commit2); + + if (current_view != old_view) + { + old_view = current_view; + removeEmbed(); + lastEmbed = createNewEmbed(image_path_timestamp_1, image_path_timestamp_2); + } + else + { + document.getElementById("diff-xlink-1").href.baseVal = image_path_timestamp_1; + document.getElementById("diff-xlink-2").href.baseVal = image_path_timestamp_2; + + document.getElementById("diff-xlink-1").setAttributeNS('http://www.w3.org/1999/xlink', 'href', image_path_timestamp_1); + document.getElementById("diff-xlink-2").setAttributeNS('http://www.w3.org/1999/xlink', 'href', image_path_timestamp_2); + + if_url_exists(image_path_timestamp_1, function(exists) { + if (exists == true) { + document.getElementById("diff-xlink-1").parentElement.style.display = 'inline' } + else { + document.getElementById("diff-xlink-1").parentElement.style.display = "none"; + } + }); + + if_url_exists(image_path_timestamp_2, function(exists) { + if (exists == true) { + document.getElementById("diff-xlink-2").parentElement.style.display = 'inline'; + } + else { + document.getElementById("diff-xlink-2").parentElement.style.display = "none"; + } + }); + } + + // keep images visibility the same as the legend + $("#diff-xlink-1").css('visibility', $("#commit1_legend").css('visibility')) + $("#diff-xlink-2").css('visibility', $("#commit2_legend").css('visibility')) + + update_fullscreen_label(); +} + +// ======================================= +// SVG Controls +// ======================================= + +function select_initial_commits() +{ + var commits = $("#commits_form input:checkbox[name='commit']"); + + if (commits.length >= 2) + { + commit1 = commits[0].value; + commit2 = commits[1].value; + commits[0].checked = true; + commits[1].checked = true; + } + else if (commits.length == 1) + { + commit1 = commits[0].value; + commits[0].checked = true; + } +} + +function get_selected_commits() +{ + var commits = []; + var hashes = []; + for (var i = 0; i < commits.length; i++) { + if ($("#commits_form input:checkbox[name='commit']")[i].checked) { + var value = $("#commits_form input:checkbox[name='commit']")[i].value; + hashes.push(value); + } + } + + // It needs 2 items selected to do something + if (hashes.length < 2) { + return; + } + + var commit1 = hashes[0].replace(/\s+/g, ''); + var commit2 = hashes[1].replace(/\s+/g, ''); + + return [commit1, commit2]; +} + + +// Interpret tooltois as html +$(document).ready(function() +{ + $('[data-toggle="tooltip"]').tooltip({html:true}); + $('[data-toggle="tooltip"]').tooltip('update'); + $('[data-toggle="tooltip"]').tooltip({boundary: 'body'}); +}); + +// Limit commits list with 2 checked commits at most +$(document).ready(function() +{ + $("#commits_form input:checkbox[name='commit']").change(function() { + var max_allowed = 2; + var count = $("input[name='commit']:checked").length; + if (count > max_allowed) { + $(this).prop("checked", ""); + } + }); +}); + +function ready() +{ + check_server_status(); + select_initial_commits(); + + update_commits(); + + if (selected_view == "schematic") { + // show_sch(); + update_page(commit1, commit2); + } + else { + // show_pcb(); + update_layer(commit1, commit2); + } +} + +window.onload = function() +{ + console.log("function onload"); +}; + +window.addEventListener('DOMContentLoaded', ready); + +// ======================================= +// Toggle Schematic/Layout +// ======================================= + +function show_sch() +{ + // Show schematic stuff + document.getElementById("show_sch_lbl").classList.add('active'); + document.getElementById("show_sch").checked = true; + // document.getElementById("diff-sch").style.display = "inline"; + document.getElementById("diff-xlink-1").parentElement.style.display = "inline"; + document.getElementById("diff-xlink-2").parentElement.style.display = "inline"; + document.getElementById("pages_list").style.display = "inline"; + document.getElementById("sch_title").style.display = "inline"; + + // Hide layout stuff + document.getElementById("show_pcb_lbl").classList.remove('active'); + document.getElementById("show_pcb").checked = false; + // document.getElementById("diff-pcb").style.display = "none"; + // document.getElementById("diff-xlink-1-pcb").parentElement.style.display = "none"; + // document.getElementById("diff-xlink-2-pcb").parentElement.style.display = "none"; + document.getElementById("layers_list").style.display = "none"; + document.getElementById("pcb_title").style.display = "none"; + + update_page(commit1, commit2); +} + +function show_pcb() +{ + // Show layout stuff + document.getElementById("show_pcb_lbl").classList.add('active'); + document.getElementById("show_pcb").checked = true; + // document.getElementById("diff-pcb").style.display = "inline"; + document.getElementById("diff-xlink-1").parentElement.style.display = "inline"; + document.getElementById("diff-xlink-2").parentElement.style.display = "inline"; + document.getElementById("layers_list").style.display = "inline"; + document.getElementById("pcb_title").style.display = "inline"; + + // Hide schematic stuff + document.getElementById("show_sch_lbl").classList.remove('active'); + document.getElementById("show_sch").checked = false; + // document.getElementById("diff-sch").style.display = "none"; + // document.getElementById("diff-xlink-1-sch").parentElement.style.display = "none"; + // document.getElementById("diff-xlink-2-sch").parentElement.style.display = "none"; + document.getElementById("pages_list").style.display = "none"; + document.getElementById("sch_title").style.display = "none"; + + update_layer(commit1, commit2); +} + +// ======================================= +// Toggle Onion/Slide +// ======================================= + +function show_onion() { + // console.log("Function:", "show_onion"); +} + +function show_slide() { + // console.log("Function:", "show_slide"); +} + +// ======================================= +// ======================================= + +function update_page_onclick(obj) { + update_page(); +} + +function update_layer_onclick(obj) { + update_layer(); +} + +// Hide fields with missing images +function imgError(image) +{ + // console.log("Image Error (missing or problematic) =", image.href.baseVal); + image.onerror = null; + parent = document.getElementById(image.id).parentElement; + parent.style.display = "none"; + return true; +} + + +// #=========================== + +var server_status = 1; +var old_server_status = -1; + +function check_server_status() +{ + var img; + + img = document.getElementById("server_status_img"); + + if (! img) { + img = document.body.appendChild(document.createElement("img")); + img.setAttribute("id", "server_status_img"); + img.style.display = "none"; + } + + img.onload = function() { + server_is_online(); + }; + + img.onerror = function() { + server_is_offline(); + }; + + img.src = "favicon.ico" + url_timestamp(); + + setTimeout(check_server_status, 5000); +} + +function server_is_online() { + server_status = 1; + document.getElementById("server_offline").style.display = "none"; + if (server_status != old_server_status) { + old_server_status = server_status; + console.log("Server is Online"); + } +} + +function server_is_offline() { + server_status = 0; + document.getElementById("server_offline").style.display = "block"; + if (server_status != old_server_status) { + old_server_status = server_status; + console.log("Server is Offline"); + } +} + +// ================================================================== + +function createNewEmbed(src1, src2) +{ + console.log("createNewEmbed..."); + + var embed = document.createElement('div'); + embed.setAttribute('id', "diff-container"); + embed.setAttribute('class', "position-relative"); + embed.setAttribute('style', "padding: 0px; height: 94%;"); + + // WORKING WITH FILTERS.. + // https://fecolormatrix.com/ + + var svg_element = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + `; + + document.getElementById('diff-container').replaceWith(embed); + document.getElementById('diff-container').innerHTML = svg_element; + console.log(">>> SVG: ", embed); + + svgpanzoom_selector = "#svg-id"; + + + panZoom_instance = svgPanZoom( + svgpanzoom_selector, { + zoomEnabled: true, + controlIconsEnabled: false, + center: true, + minZoom: 1, + maxZoom: 20, + zoomScaleSensitivity: 0.22, + contain: false, + fit: false, // cannot be used, bug? (this one must be here to change the default) + viewportSelector: '.my_svg-pan-zoom_viewport', + eventsListenerElement: document.querySelector(svgpanzoom_selector), + onUpdatedCTM: function() { + if (current_view == "show_sch") { + if (sch_current_zoom != sch_old_zoom) { + console.log(">> Restoring SCH pan and zoom"); + panZoom_instance.zoom(sch_current_zoom); + panZoom_instance.pan(sch_current_pan); + sch_old_zoom = sch_current_zoom; + } + } + else { + if (pcb_current_zoom != pcb_old_zoom) { + console.log(">> Restoring PCB pan and zoom"); + panZoom_instance.zoom(pcb_current_zoom); + panZoom_instance.pan(pcb_current_pan); + pcb_old_zoom = pcb_current_zoom; + } + } + } + }); + + console.log("panZoom_instance:", panZoom_instance); + + embed.addEventListener('load', lastEventListener); + + document.getElementById('zoom-in').addEventListener('click', function(ev) { + ev.preventDefault(); + panZoom_instance.zoomIn(); + panZoom_instance.center(); + }); + + document.getElementById('zoom-out').addEventListener('click', function(ev) { + ev.preventDefault(); + panZoom_instance.zoomOut(); + panZoom_instance.center(); + }); + + document.getElementById('zoom-fit').addEventListener('click', function(ev) { + ev.preventDefault(); + panZoom_instance.resetZoom(); + panZoom_instance.center(); + }); + + if (current_diff_filter === "diff") + { + $("#diff-xlink-1").css('filter', 'url(#filter-1)') /// FILTER_DIFF + $("#diff-xlink-2").css('filter', 'url(#filter-2)') /// FILTER_DIFF + } + else + { + $("#diff-xlink-1").css('filter', 'url(#filter-12)') /// FILTER_WHITE + $("#diff-xlink-2").css('filter', 'url(#filter-22)') /// FILTER_WHITE + } + + return embed; +} + +function removeEmbed() +{ + console.log(">=============================================<"); + console.log("removeEmbed..."); + console.log(">> lastEmbed: ", lastEmbed); + console.log(">> panZoom_instance: ", panZoom_instance); + + // Destroy svgpanzoom + if (panZoom_instance) + { + if (current_view == "show_pcb") { + sch_current_zoom = panZoom_instance.getZoom(); + sch_current_pan = panZoom_instance.getPan(); + sch_old_zoom = null; + } else { + pcb_current_zoom = panZoom_instance.getZoom(); + pcb_current_pan = panZoom_instance.getPan(); + pcb_old_zoom = null; + } + + panZoom_instance.destroy(); + + // Remove event listener + lastEmbed.removeEventListener('load', lastEventListener); + + // Null last event listener + lastEventListener = null; + + // Remove embed element + // document.getElementById('diff-container').removeChild(lastEmbed); + + // Null reference to embed + lastEmbed = null; + } +} + +function update_fullscreen_label() +{ + fullscreen_label = document.getElementById("fullscreen_label"); + + commit1 = document.getElementById("commit1_hash").value; + commit2 = document.getElementById("commit2_hash").value; + + if (current_view == "show_sch") + { + pages = $("#pages_list input:radio[name='pages']"); + selected_page = pages.index(pages.filter(':checked')); + page_name = document.getElementById("label-" + pages[selected_page].id).innerHTML; + view_item = "Page " + page_name; + } + else + { + layers = $("#layers_list input:radio[name='layers']"); + selected_layer = layers.index(layers.filter(':checked')); + layer_name = document.getElementById("label-" + layers[selected_layer].id).innerHTML; + view_item = "Layer " + layer_name; + } + + if (is_fullscreen) + { + if (fullscreen_label) + { + document.getElementById("commit1_fs").innerHTML = `(${commit1})`; + document.getElementById("commit2_fs").innerHTML = `(${commit2})`; + document.getElementById("view_item_fs").innerHTML = view_item; + } + else + { + label = ` + + ` + + const element = $('#diff-container').get(0); + element.insertAdjacentHTML("afterbegin", label); + + var visibility1 = $("#diff-xlink-1").css('visibility'); + $("#commit1_legend_fs").css('visibility', visibility1) + $("#commit1_legend_text_fs").css('visibility', visibility1) + + var visibility2 = $("#diff-xlink-2").css('visibility'); + $("#commit2_legend_fs").css('visibility', visibility2) + $("#commit2_legend_text_fs").css('visibility', visibility2) + } + } +} + +function toggle_fullscreen() +{ + if (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement) + { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } + + is_fullscreen = false; + const box = document.getElementById('fullscreen_label'); + box.remove(); + + } else { + element = $('#diff-container').get(0); + if (element.requestFullscreen) { + element.requestFullscreen(); + } else if (element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if (element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else if (element.msRequestFullscreen) { + element.msRequestFullscreen(); + } + + is_fullscreen = true; + update_fullscreen_label() + } +} + +function show_info_popup() +{ + document.getElementById("info-btn").click(); +} + +// Remove focus whne info buttons is clicked with shortcut i +$('#shortcuts-modal').on('shown.bs.modal', function(e){ + $('#info-btn').one('focus', function(e){$(this).blur();}); +}); + +function change_page() +{ + update_page(); +} diff --git a/kibot/resources/kiri/layers_template.html b/kibot/resources/kiri/layers_template.html new file mode 100644 index 00000000..7aab62c7 --- /dev/null +++ b/kibot/resources/kiri/layers_template.html @@ -0,0 +1,6 @@ + + + diff --git a/kibot/resources/kiri/pages_template.html b/kibot/resources/kiri/pages_template.html new file mode 100644 index 00000000..99056165 --- /dev/null +++ b/kibot/resources/kiri/pages_template.html @@ -0,0 +1,6 @@ + + + diff --git a/kibot/resources/kiri/redirect.html b/kibot/resources/kiri/redirect.html new file mode 100644 index 00000000..722adbe5 --- /dev/null +++ b/kibot/resources/kiri/redirect.html @@ -0,0 +1,11 @@ + + + + + + + + + + +