Widget:Event-Timer/script.js
Zur Navigation springen
Zur Suche springen
/*<nowiki>*/ /* Guild Wars 2 Wiki: Event timer */ // Increment this every time a release is added to invalidate the existing sequence and force users to load the new map timer. var version = 'v3.7.0'; // November 2023: Added logic path to reset sequence if all non-toptime event bars have been removed using the [x]. // GLOBAL VARIABLES // User interface buttons, labels, checkboxes var uitext = { widgetlink: "Widget:Event timer", widgetlinktext: "Dokumentation", timezonehover: "Dies ist deine Zeitzone", timeshiftresume: "Liveupdate aus - Hier klicken zum Aktivieren", timeshiftnexthoverpause: "Klicken um Liveupdate zu pausieren und zwei Stunden weiter zu gehen", timeshiftprevhover: "Klicken um zu den vorherigen zwei Stunden zu gehen", timeshiftnexthover: "Klicken um zu den folgenden zwei Stunden zu gehen", checkboxhover: "Klicke auf Anwenden um die Einstellungen zu speichern.", legendname: "Einstellungen für Event-Timer", applysettings: "Einstellungen anwenden", forgetsettings: "Einstellungen zurücksetzen", deleterowhover: "Versteckt diese Zeile. Setze die Einstellungen zurück, um alle Zeilen wieder anzuzeigen.", checkboxes: { twelvehour: { name: "12-Stunden-Uhrzeit anzeigen.", hover: "Wenn ausgewählt, werden Uhrzeiten im 12-Stunden-Format mit AM/PM angezeigt.", defaultvalue: false }, toptimes: { name: "Kompakte Darstellung.", hover: "Wenn ausgewählt, wird der Timer kompakter dargestellt.", defaultvalue: true }, compact: { name: "Kompakte Überschriftendarstellung.", hover: "Wenn ausgewählt, wird der Timer kompakter dargestellt, da die Überschrften links statt über dem Timer erscheinen. Keinen Effekt wenn Überschriften versteckt wurden.", defaultvalue: true }, hidecategories: { name: "Kategorien verstecken.", hover: "Wenn ausgewählt, wird der Timer durch das Entfernen der Kategorienüberschriften kompakter dargestellt.", defaultvalue: false }, hideheadings: { name: "Überschriften verstecken.", hover: "Wenn ausgewählt, wird der Timer durch das Entfernen der Karten-Meta-Überschriften kompakter dargestellt.", defaultvalue: false }, hidechatlinks: { name: "Chatlinks verstecken.", hover: "Wenn ausgewählt, werden keine Chatlinks angezeigt.", defaultvalue: false }, even: { name: "Nur zu geraden UTC-Stunden starten.", hover: "Wenn ausgewählt, beginnt der Timer immer zur vorherigen geraden UTC-Stunde.", defaultvalue: false } } }; // Event names, schedules, colours var eventData = { // Time t: { name: "", segments: { 0: { name: "", bg: "transparent" } }, sequences: { partial: [], pattern: [{r:0,d:15}] } }, // ** Zentraltyria ** dn: { category: "Zentraltyria", name: "Tageszeit", segments: { 1: { name: "Tag", link: "Tageszeit", bg: [255,255,255] }, 2: { name: "Dämmerung", link: "Tageszeit", bg: [[255,255,255],[122,134,171]] }, 3: { name: "Nacht", link: "Tageszeit", bg: [122,134,171] }, 4: { name: "Morgengrauen", link: "Tageszeit", bg: [[122,134,171],[255,255,255]] } }, sequences: { partial: [{r:3,d:25},{r:4,d:5}], pattern: [{r:1,d:70},{r:2,d:5},{r:3,d:40},{r:4,d:5}] } }, wb: { category: "Zentraltyria", name: "Welt-Bosse", link: "Welt-Boss", segments: { 1: { name: "Admiral Taidha Covington", link: "Die Kampagne gegen Taidha Covington", chatlink: "[&BKgBAAA=]", bg: [ 66,200,215] }, 2: { name: "Klaue von Jormag", link: "Zerstörung der Klaue Jormags", chatlink: "[&BHoCAAA=]", bg: [ 66,200,215] }, 3: { name: "Feuer-Elementar", link: "Thaumanova-Reaktor-Fallout", chatlink: "[&BEcAAAA=]", bg: [138,234,244] }, 4: { name: "Inquestur-Golem Typ II", link: "Besiegt den Inquestur-Golem Typ II", chatlink: "[&BNQCAAA=]", bg: [ 66,200,215] }, 5: { name: "Großer Dschungelwurm", link: "Bezwingt den großen Dschungelwurm", chatlink: "[&BEEFAAA=]", bg: [138,234,244] }, 6: { name: "Mega-Zerstörer", link: "Die Schlacht um den Mahlstromgipfel", chatlink: "[&BM0CAAA=]", bg: [ 66,200,215] }, 7: { name: "Modniir Ulgoth", link: "Bezwingt_Ulgoth_den_Modniir_und_seine_Diener", chatlink: "[&BLAAAAA=]", bg: [ 66,200,215] }, 8: { name: "Schatten-Behemoth", link: "Geheimnisse im Sumpf", chatlink: "[&BPcAAAA=]", bg: [138,234,244] }, 9: { name: "Svanir-Schamane", link: "Der gefrorene Schlund", chatlink: "[&BMIDAAA=]", bg: [138,234,244] }, 10: { name: "Der Zerschmetterer", link: "Kralkatorriks Vermächtnis", chatlink: "[&BE4DAAA=]", bg: [ 66,200,215] } }, sequences: { partial: [], pattern: [{r:1,d:15},{r:9,d:15},{r:6,d:15},{r:3,d:15},{r:10,d:15},{r:5,d:15},{r:7,d:15},{r:8,d:15},{r:4,d:15},{r:9,d:15},{r:2,d:15},{r:3,d:15},{r:1,d:15},{r:5,d:15},{r:6,d:15},{r:8,d:15},{r:10,d:15},{r:9,d:15},{r:7,d:15},{r:3,d:15},{r:4,d:15},{r:5,d:15},{r:2,d:15},{r:8,d:15}] } }, hwb: { category: "Zentraltyria", name: "Hardcore Welt-Bosse", link: "Welt-Boss", segments: { 0: { name: "", bg: [138,234,244] }, 1: { name: "Dreifacher Ärger", link: "Dreifacher Ärger", chatlink: "[&BKoBAAA=]", bg: [ 66,200,215] }, 2: { name: "Karka-Königin", link: "Inselkontrolle", chatlink: "[&BNUGAAA=]", bg: [ 66,200,215] }, 3: { name: "Tequatl der Sonnenlose", link: "Besiegt Tequatl den Sonnenlosen", chatlink: "[&BNABAAA=]", bg: [ 66,200,215] } }, sequences: { partial: [{r:3,d:30},{r:0,d:30},{r:1,d:30},{r:0,d:30},{r:2,d:30},{r:0,d:30},{r:3,d:30},{r:0,d:30},{r:1,d:30},{r:0,d:90},{r:2,d:30},{r:0,d:30},{r:3,d:30},{r:0,d:30},{r:1,d:30},{r:0,d:120},{r:2,d:30},{r:0,d:30},{r:3,d:30},{r:0,d:30},{r:1,d:30},{r:0,d:120},{r:2,d:30},{r:0,d:30},{r:3,d:30},{r:0,d:30},{r:1,d:30},{r:0,d:30},{r:2,d:30},{r:0,d:30},{r:3,d:30},{r:0,d:30},{r:1,d:30},{r:0,d:150},{r:2,d:30},{r:0,d:30},{r:3,d:30},{r:0,d:30},{r:1,d:30}], pattern: [] } }, la: { category: "Zentraltyria", name: "Ley-Linien-Anomalie", link: "Legende Ley-Linien-Anomalie", segments: { 0: { name: "", bg: [251,132,152] }, 1: { name: "Baumgrenzen-Fälle", link: "Besiegt_die_Ley-Linien-Anomalie,_um_ihre_zerstörerische_Energie_aufzulösen,_bevor_sie_überlädt_(Baumgrenzen-Fälle)", chatlink: "[&BEwCAAA=]", bg: [215, 66, 91] }, 2: { name: "Eisenmark", link: "Besiegt_die_Ley-Linien-Anomalie,_um_ihre_zerstörerische_Energie_aufzulösen,_bevor_sie_überlädt_(Eisenmark)", chatlink: "[&BOYBAAA=]", bg: [215, 66, 91] }, 3: { name: "Gendarran-Felder", link: "Besiegt_die_Ley-Linien-Anomalie,_um_ihre_zerstörerische_Energie_aufzulösen,_bevor_sie_überlädt_(Gendarran-Felder)", chatlink: "[&BO0AAAA=]", bg: [215, 66, 91] } }, sequences: { partial: [{r:0,d:20},{r:1,d:20},{r:0,d:100},{r:2,d:20},{r:0,d:100},{r:3,d:20}], pattern: [{r:0,d:100},{r:1,d:20},{r:0,d:100},{r:2,d:20},{r:0,d:100},{r:3,d:20}] } }, pvpat: { category: "Zentraltyria", name: "PvP-Turniere", link: "Automatisierte Turniere", segments: { 0: { name: "", bg: [251,132,152] }, 1: { name: "Automatisiertes Turnier: Balthasars Rauferei", link: "Automatisierte_Turniere#Tägliches_Turnier", bg: [234, 98,121] }, 2: { name: "Automatisiertes Turnier: Grenths Gastspiel", link: "Automatisierte_Turniere#Tägliches_Turnier", bg: [234, 98,121] }, 3: { name: "Automatisiertes Turnier: Melandrus Begegnung", link: "Automatisierte_Turniere#Tägliches_Turnier", bg: [234, 98,121] }, 4: { name: "Automatisiertes Turnier: Lyssas Legionen", link: "Automatisierte_Turniere#Tägliches_Turnier", bg: [234, 98,121] } }, sequences: { partial: [], pattern: [{r:1,d:60},{r:0,d:120},{r:2,d:60},{r:0,d:120},{r:3,d:60},{r:0,d:120},{r:4,d:60},{r:0,d:120}] } }, eotn: { category: "Lebendige Welt Staffel 1", name: "Auge des Nordens", link: "Auge des Nordens", segments: { 0: { name: "", bg: [251,132,152] }, 1: { name: "Die Verdrehte Marionette (Öffentlich)", link: "Die Verdrehte Marionette", chatlink: "[&BAkMAAA=]", bg: [234, 98,121] }, 2: { name: "Tower of Nightmares (Public)", link: "The Tower of Nightmares (meta event)", chatlink: "[&BAkMAAA=]", bg: [234, 98,121] }, 3: { name: "Battle For Lion's Arch (Public)", link: "The Battle For Lion's Arch", chatlink: "[&BAkMAAA=]", bg: [234, 98,121] } }, sequences: { partial: [], pattern: [{r:1,d:20},{r:0,d:10},{r:3,d:15},{r:0,d:45},{r:2,d:15},{r:0,d:15}] } }, si: { category: "Lebendige Welt Staffel 1", name: "Scarlets Invasion", link: "Besiegt die eindringenden Diener von Scarlet Dornstrauch", segments: { 0: { name: "", bg: [251,132,152] }, 1: { name: "Besiegt Scarlets Diener", link: "Besiegt die eindringenden Diener von Scarlet Dornstrauch", chatlink: "[&BOQAAAA=]", bg: [234, 98,121] } }, sequences: { partial: [{r:0,d:60},{r:1,d:15}], pattern: [{r:0,d:105},{r:1,d:15}] } }, // ** Lebendige Welt Staffel 2 ** dt: { category: "Lebendige Welt Staffel 2", name: "Trockenkuppe", segments: { 1: { name: "Absturzstelle", bg: [251,227,132] }, 2: { name: "Sandsturm!", chatlink: "[&BIAHAAA=]", bg: [215,185, 66] } }, sequences: { partial: [], pattern: [{r:1,d:40},{r:2,d:20}] } }, // ** Heart of Thorns ** vb: { category: "Heart of Thorns", name: "Grasgrüne Schwelle", segments: { 1: { name: "Sicherung der Grasgrünen Schwelle", link: "Grasgrüne Schwelle#Tagsüber", bg: [231,251,132] }, 2: { name: "Die Nacht und der Feind", bg: [211,234, 98] }, 3: { name: "Nachtbosse", link: "Die Nacht und der Feind", chatlink: "[&BAgIAAA=]", bg: [190,215, 66] } }, sequences: { partial: [{r:2,d:10},{r:3,d:20}], pattern: [{r:1,d:75},{r:2,d:25},{r:3,d:20}] } }, ab: { category: "Heart of Thorns", name: "Güldener Talkessel", segments: { 1: { name: "Pylonen", link: "Die Verteidigung von Tarir", chatlink: "[&BN0HAAA=]", bg: [231,251,132] }, 2: { name: "Herausforderungen", link: "Feuerprobe", chatlink: "[&BGwIAAA=]", bg: [211,234, 98] }, 3: { name: "Rankenkrake", link: "Schlacht in Tarir", chatlink: "[&BAIIAAA=]", bg: [190,215, 66] }, 4: { name: "Reset", link: "Eine kurze Verschnaufpause", bg: [211,234, 98] } }, sequences: { partial: [{r:1,d:45},{r:2,d:15},{r:3,d:20},{r:4,d:10}], pattern: [{r:1,d:75},{r:2,d:15},{r:3,d:20},{r:4,d:10}] } }, td: { category: "Heart of Thorns", name: "Verschlungene Tiefen", segments: { 1: { name: "Helft den Außenposten", link: "Verschlungene_Tiefen#Meta-Events", bg: [231,251,132] }, 2: { name: "Vorbereitung", link: "König des Dschungels", bg: [211,234, 98] }, 3: { name: "Chak-Potentat", link: "König des Dschungels", chatlink: "[&BPUHAAA=]", bg: [190,215, 66] } }, sequences: { partial: [{r:1,d:25},{r:2,d:5},{r:3,d:20}], pattern: [{r:1,d:95},{r:2,d:5},{r:3,d:20}] } }, ds: { category: "Heart of Thorns", name: "Widerstand des Drachen", segments: { 1: { name: "Start des Angriffs", link: "Widerstand des Drachen (Meta-Event)", chatlink: "[&BBAIAAA=]", bg: [190,215, 66] }, 2: { name: "(fortgesetzt)", link: "Widerstand des Drachen (Meta-Event)", bg: [190,215, 66] } }, sequences: { partial: [{r:2,d:90}], pattern: [{r:1,d:120}] } }, // ** Lebendige Welt Staffel 3 ** ld: { category: "Lebendige Welt Staffel 3", name: "Doric-See", segments: { 1: { name: "Saidras Hafen", link: "Kontrolle des Weißen Mantels: Saidras Hafen", chatlink: "[&BK0JAAA=]", bg: [251,132,152] }, 2: { name: "Neulehmwald", link: "Kontrolle des Weißen Mantels: Neulehmwald", chatlink: "[&BLQJAAA=]", bg: [234, 98,121] }, 3: { name: "Norans Heimstatt", link: "Kontrolle des Weißen Mantels: Norans Heimstatt", chatlink: "[&BK8JAAA=]", bg: [215, 66, 91] } }, sequences: { partial: [{r:2,d:30}], pattern: [{r:3,d:30},{r:1,d:45},{r:2,d:45}] } }, // ** Path of Fire ** co: { category: "Path of Fire", name: "Kristalloase", segments: { 0: { name: "", bg: [251,199,132] }, 1: { name: "Runde 1 bis 3", link: "Kasino-Blitz", chatlink: "[&BLsKAAA=]", bg: [234,175, 98] }, 2: { name: "Piñata/Reset", link: "Kasino-Blitz", chatlink: "[&BLsKAAA=]", bg: [215,150, 66] } }, sequences: { partial: [{r:0,d:5},{r:1,d:16},{r:2,d:9}], pattern: [{r:0,d:95},{r:1,d:16},{r:2,d:9}] } }, dh: { category: "Path of Fire", name: "Wüsten-Hochland", segments: { 0: { name: "", bg: [251,199,132] }, 1: { name: "Vergrabene Schätze", link: "Die Suche nach vergrabenen Schätzen", chatlink: "[&BGsKAAA=]", bg: [234,175, 98] } }, sequences: { partial: [{r:0,d:60},{r:1,d:20}], pattern: [{r:0,d:100},{r:1,d:20}] } }, er: { category: "Path of Fire", name: "Elon-Flusslande", segments: { 0: { name: "", bg: [251,199,132] }, 1: { name: "Fels der Weissagung", link: "Der Pfad zum Aufstieg", chatlink: "[&BFMKAAA=]", bg: [234,175, 98] }, 2: { name: "Doppelgänger", link: "Der Pfad zum Aufstieg", chatlink: "[&BCgKAAA=]", bg: [215,150, 66] } }, sequences: { partial: [{r:2,d:15}], pattern: [{r:0,d:75},{r:1,d:25},{r:2,d:20}] } }, de: { category: "Path of Fire", name: "Das Ödland", segments: { 0: { name: "", bg: [251,199,132] }, 1: { name: "Schlünde der Qual", chatlink: "[&BKMKAAA=]", bg: [215,150, 66] }, 2: { name: "Aufstand der Junundu", chatlink: "[&BMEKAAA=]", bg: [234,175, 98] } }, sequences: { partial: [{r:0,d:30},{r:2,d:20},{r:0,d:10}], pattern: [{r:1,d:20},{r:0,d:10},{r:2,d:20},{r:0,d:40},{r:2,d:20},{r:0,d:10}] } }, dv: { category: "Path of Fire", name: "Domäne Vaabi", segments: { 0: { name: "", bg: [251,199,132] }, 1: { name: "Zorn der Schlangen", chatlink: "[&BHQKAAA=]", bg: [234,175, 98] }, 2: { name: "Im Feuer geschmiedet", chatlink: "[&BO0KAAA=]", bg: [215,150, 66] } }, sequences: { partial: [{r:2,d:30}], pattern: [{r:1,d:30},{r:2,d:30},{r:0,d:30},{r:2,d:30}] } }, // ** Lebendige Welt Staffel 4 ** ai: { category: "Lebendige Welt Staffel 4", name: "Eindringende Erweckte", segments: { 0: { name: "", bg: [187,119,207] }, 1: { name: "Eindringende Erweckte", link: "Besiegt die eindringenden Erweckten", bg: [157,65,185] }, }, sequences: { partial: [{r:0,d:30}], pattern: [{r:1,d:15},{r:0,d:45}] } }, di: { category: "Lebendige Welt Staffel 4", name: "Domäne Istan", segments: { 0: { name: "", bg: [187,119,207] }, 1: { name: "Palawadan", link: "Palawadan, Juwel von Istan (Meta-Event)", chatlink: "[&BAkLAAA=]", bg: [157,65,185] }, }, sequences: { partial: [{r:1,d:15}], pattern: [{r:0,d:90},{r:1,d:30}] } }, jb: { category: "Lebendige Welt Staffel 4", name: "Jahai-Klippen", segments: { 0: { name: "", bg: [187,119,207] }, 1: { name: "Eskorte", link: "Eskortiert die DERV zu den Gräben des Zerschmetterers", chatlink: "[&BIMLAAA=]", bg: [175, 96,199] }, 2: { name: "Zerschmetterer", link: "Vernichtet den Todesgebrandmarkten Zerschmetterer", chatlink: "[&BJMLAAA=]", bg: [157,65,185] }, }, sequences: { partial: [{r:0,d:60},{r:1,d:15},{r:2,d:15}], pattern: [{r:0,d:90},{r:1,d:15},{r:2,d:15}] } }, tp: { category: "Lebendige Welt Staffel 4", name: "Donnerkopf-Gipfel", segments: { 0: { name: "", bg: [187,119,207] }, 1: { name: "Feste Donnerkopf", link: "Feste Donnerkopf (Meta-Event)", chatlink: "[&BLsLAAA=]", bg: [157,65,185] }, 2: { name: "Öl auf dem Eis", chatlink: "[&BKYLAAA=]", bg: [157,65,185] }, }, sequences: { partial: [{r:1,d:5},{r:0,d:40},{r:2,d:15}], pattern: [{r:0,d:45},{r:1,d:20},{r:0,d:40},{r:2,d:15}] } }, // ** The Icebrood Saga ** gv: { category: "Die Eisbrut-Saga", name: "Grothmar-Tal", segments: { 0: { name: "", bg: [132,201,251] }, 1: { name: "Flammenabbild", link: "Zeremonie der Heiligen Flamme", chatlink: "[&BA4MAAA=]", bg: [ 98,177,234] }, 2: { name: "Schrein", link: "Heimsuchung des Schreins des Schicksalwissens", chatlink: "[&BA4MAAA=]", bg: [ 66,153,215] }, 3: { name: "Schleimgrube", link: "Schleimgruben-Prüfungen", chatlink: "[&BPgLAAA=]", bg: [ 98,177,234] }, 4: { name: "Konzert", link: "Ein Konzert für die Ewigkeit", chatlink: "[&BPgLAAA=]", bg: [ 66,153,215] } }, sequences: { partial: [{r:0,d:10}], pattern: [{r:1,d:15},{r:0,d:13},{r:2,d:22},{r:0,d:5},{r:3,d:20},{r:0,d:15},{r:4,d:15},{r:0,d:15}] } }, bm: { category: "Die Eisbrut-Saga", name: "Bjora-Sümpfe", segments: { 0: { name: "", bg: [132,201,251] }, 1: { name: "Drakkar und die Geister der Wildnis", link: "Champion des Eisdrachen", chatlink: "[&BDkMAAA=]", bg: [ 66,153,215] }, 2: { name: "Rabenschreine", link: "Stürme des Winters", chatlink: "[&BCcMAAA=]", bg: [ 98,177,234] }, 3: { name: "Scherben und Konstrukt", link: "Stürme des Winters", chatlink: "[&BCcMAAA=]", bg: [ 66,153,215] }, 4: { name: "Eisbrut Champions", link: "Stürme des Winters", chatlink: "[&BCcMAAA=]", bg: [ 98,177,234] } }, sequences: { partial: [{r:3,d:5},{r:4,d:15}], pattern: [{r:0,d:45},{r:1,d:35},{r:0,d:5},{r:2,d:15},{r:3,d:5},{r:4,d:15}] } }, dsp: { category: "Die Eisbrut-Saga", name: "Drachensturm", segments: { 0: { name: "", bg: [132,201,251] }, 1: { name: "Drachensturm", link: "Drachensturm", chatlink: "[&BAkMAAA=]", bg: [ 66,153,215] } }, sequences: { partial: [{r:0,d:60}], pattern: [{r:1,d:20},{r:0,d:100}] } }, // ** End of Dragons ** cdn: { category: "End of Dragons", name: "Cantha: Tageszeit", segments: { 1: { name: "Tag", link: "Tageszeit", bg: [255,255,255] }, 2: { name: "Dämmerung", link: "Tageszeit", bg: [[255,255,255],[122,134,171]] }, 3: { name: "Nacht", link: "Tageszeit", bg: [122,134,171] }, 4: { name: "Morgengrauen", link: "Tageszeit", bg: [[122,134,171],[255,255,255]] } }, sequences: { partial: [{r:3,d:35},{r:4,d:5}], pattern: [{r:1,d:55},{r:2,d:5},{r:3,d:55},{r:4,d:5}] } }, sp: { category: "End of Dragons", name: "Provinz Seitung", segments: { 0: { name: "", bg: [195,255,245] }, 1: { name: "Ätherklingen-Angriff", chatlink: "[&BGUNAAA=]", bg: [90,243,222] } }, sequences: { partial: [{r:0,d:90}], pattern: [{r:1,d:30},{r:0,d:90}] } }, nkc: { category: "End of Dragons", name: "Stadt Neu-Kaineng", segments: { 0: { name: "", bg: [195,255,245] }, 1: { name: "Kaineng-Energieausfall", chatlink: "[&BBkNAAA=]", bg: [90,243,222] } }, sequences: { partial: [], pattern: [{r:1,d:30},{r:0,d:90}] } }, tew: { category: "End of Dragons", name: "Die Echowald-Wildnis", segments: { 0: { name: "", bg: [138,234,244] }, 1: { name: "Bandenkrieg", link: "Der Bandenkrieg von Echowald", chatlink: "[&BMwMAAA=]", bg: [ 66,200,215] }, 2: { name: "Espenwald", link: "Zerstört mithilfe der der Belagerungsschildkröten die Schildgeneratoren, während Ihr Euch durch das Fort kämpft", chatlink: "[&BPkMAAA=]", bg: [ 96,220,235] } }, sequences: { partial: [], pattern: [{r:0,d:30},{r:1,d:35},{r:0,d:35},{r:2,d:20}] } }, dre: { category: "End of Dragons", name: "Drachen-Ende", segments: { 1: { name: "Vorbereitungen", chatlink: "[&BKIMAAA=]", bg: [195,255,245] }, 2: { name: "Die Schlacht ums Jademeer", chatlink: "[&BKIMAAA=]", bg: [90,243,222] } }, sequences: { partial: [], pattern: [{r:1,d:60},{r:2,d:60}] } }, // ** Secrets of the Obscure ** sa: { category: "Secrets of the Obscure", name: "Himmelswacht-Archipel", segments: { 0: { name: "", bg: [250,206,133] }, 1: { name: "Den Turm des Zauberers entriegeln", link: "Den Turm des Zauberers entriegeln", chatlink: "[&BL4NAAA=]", bg: [210,155, 73] } }, sequences: { partial: [{r:0,d:60}], pattern: [{r:1,d:25},{r:0,d:95}] } }, wt: { category: "Secrets of the Obscure", name: "Der Turm des Zauberers", segments: { 0: { name: "", bg: [250,206,133] }, 1: { name: "Himmelsschuppen-Zielübungen", link: "Himmelsschuppen-Zielübungen im Turm des Zauberers", chatlink: "[&BB8OAAA=]", bg: [226,171, 73] }, 2: { name: "Nachtflug", link: "Turm des Zauberers: Nachtflug", chatlink: "[&BB8OAAA=]", bg: [226,171, 73] }, 3: { name: "Himmelsschuppen-Zielübungen & Nachtflug", link: "Abenteuer#Secrets of the Obscure", chatlink: "[&BB8OAAA=]", bg: [200,136, 54] } }, sequences: { partial: [{r:2,d:20},{r:0,d:40}], pattern: [{r:1,d:40},{r:3,d:15},{r:2,d:25},{r:0,d:40}] } }, am: { category: "Secrets of the Obscure", name: "Amnytas", segments: { 0: { name: "", bg: [250,206,133] }, 1: { name: "Verteidigung von Amnytas", link: "Die Verteidigung von Amnytas", chatlink: "[&BDQOAAA=]", bg: [210,155, 73] } }, sequences: { partial: [], pattern: [{r:1,d:25},{r:0,d:95}] } }, con: { category: "Secrets of the Obscure", name: "Konvergenz (Instanz)", segments: { 0: { name: "", bg: [250,206,133] }, 1: { name: "Konvergenzen", link: "Konvergenz (Instanz)", chatlink: "[&BB8OAAA=]", bg: [226,171, 73] } }, sequences: { partial: [{r:0,d:90}], pattern: [{r:1,d:10},{r:0,d:170}] } }, // ** Special Events ** lc: { category: "Spezialevents", name: "Labyrinthklippen", segments: { 0: { name: "", bg: [138,234,244] }, 1: { name: "Skiff-Rennen", link: "Labyrinth-Skiffe: Bald fängt ein Rennen an", chatlink: "[&BBwHAAA=]", bg: [ 66,200,215] }, 2: { name: "Schatzjagd", link: "Sammelt vor Ablauf der Zeit so viel Beute wie möglich!", chatlink: "[&BBwHAAA=]", bg: [ 66,200,215] }, 3: { name: "Schweberochen-Rennen", link: "Schwebe-Rochen-Slalom: Erreicht die Ziellinie", chatlink: "[&BBwHAAA=]", bg: [ 66,200,215] }, 4: { name: "Angeln", link: "Anmeldung zum Angelturnier", chatlink: "[&BBwHAAA=]", bg: [ 66,200,215] }, 5: { name: "Dolyak-Rennen", link: "Fliegender Dolyak: Erreicht die Ziellinie", chatlink: "[&BBwHAAA=]", bg: [ 66,200,215] } }, sequences: { partial: [], pattern: [{r:1,d:10},{r:0,d:20},{r:2,d:30},{r:0,d:15},{r:3,d:10},{r:0,d:5},{r:4,d:10},{r:0,d:5},{r:5,d:10},{r:0,d:5}] } }, db: { category: "Spezialevents", name: "Drachen-Gepolter", segments: { 0: { name: "", bg: [138,234,244] }, 1: { name: "Wanderer-Hügel", link: "Hologramm-Ansturm des Drachen-Gepolters", chatlink: "[&BH0BAAA=]", bg: [ 66,200,215] }, 2: { name: "Schauflerschreck-Klippen", link: "Hologramm-Ansturm des Drachen-Gepolters", chatlink: "[&BGMCAAA=]", bg: [ 66,200,215] }, 3: { name: "Lornars Pass", link: "Hologramm-Ansturm des Drachen-Gepolters", chatlink: "[&BJkBAAA=]", bg: [ 66,200,215] }, 4: { name: "Schneekuhlenhöhen", link: "Hologramm-Ansturm des Drachen-Gepolters", chatlink: "[&BL4AAAA=]", bg: [ 66,200,215] } }, sequences: { partial: [], pattern: [{r:1,d:5},{r:0,d:10},{r:2,d:5},{r:0,d:10},{r:3,d:5},{r:0,d:10},{r:4,d:5},{r:0,d:10}] } }, ha: { category: "Spezialevents", name: "Halloween", segments: { 0: { name: "", bg: [242,215,162] }, 1: { name: "Der Verrückte König sagt", link: "Der Verrückte König sagt:", chatlink: "[&BBAEAAA=]", bg: [232,163,31] } }, sequences: { partial: [], pattern: [{r:1,d:10},{r:0,d:110}] } } }; // Placeholder object which will become a copy of eventData, but only with the metas specified in defaultSequence. var customEventData = {}; // Sequence in which the elements will render. var defaultSequence = Object.keys(eventData); // If there are more than 10 elements showing, it's probably a long way between the first times and the last, so add another to the end. if (defaultSequence.length > 10) { defaultSequence.push('t'); } // Calculate the user timezone offset for continued later use. Globally track start hour too. var now = new Date(), timezoneOffset = (-1 * now.getTimezoneOffset()), startHourUTC, twelveHourTimes, setIntervalHandle, otherHourOffset = 0, usedHeadings = []; // UTILITY FUNCTIONS // Utility function #1: Write CSS function writeTimerCSS() { // ** Sheet 2 - Event colour scheme ** var cssText = $.map(eventData, function (metaEventData, metaKey) { var x; return $.map(metaEventData.segments, function (v, k) { x = ''; switch (typeof v.bg) { case 'object': switch (v.bg.length) { case 2: // linear-gradients x = '.event-bar-segment.' + metaKey + k + ' { background: linear-gradient(90deg, rgb(' + v.bg[0].join(',') + '), rgb(' + v.bg[1].join(',') + ')) }'; break; case 3: x = ['.event-bar-segment.' + metaKey + k + ' { background: rgb(' + v.bg.join(',') + ') }', '.event-bar-segment.' + metaKey + k + '.future { background: rgba(' + v.bg.join(',') + ',0.3) }']; break; } break; case 'string': // transparent or other alternative text x = '.event-bar-segment.' + metaKey + k + ' { background: ' + v.bg + '}'; break; } return x; }); }).join('\n'); $('#EventTimerCSS2').text('/* Widget:Event timer - Stylesheet 1 */\n' + cssText); // ** Sheet 3 - Compact window width ** // Run once fitTimerToWindowWidth(); // And rerun when the window changes size var resizeTimer; $(window).resize(function () { clearTimeout(resizeTimer); resizeTimer = setTimeout(fitTimerToWindowWidth, 250); }); } // Utility function #2 and #3: HTML5 localStorage operator functions used to request existing preferences, and store user preferences for later visits function getEventTimerPreferences(keyname, defaultvalue) { var response = JSON.parse(localStorage.getItem('event-timer-' + keyname)); if (typeof response == 'undefined' || response == null) { response = defaultvalue; } return response; } function setEventTimerPreferences(keyname, value, defaultvalue) { switch (typeof value) { case 'string': case 'object': case 'boolean': break; default: console.log('Invalid preference ignored:', value); value = defaultvalue; break; } try { localStorage.removeItem('event-timer-' + keyname); localStorage.setItem('event-timer-' + keyname, JSON.stringify(value)); console.log('Changed preference: ', keyname); } catch (e) { console.log('localStorage not supported (HTML5 browser required)'); } } // Utility function #4: Create a legend with checkboxes for viewers to set their preferences. function eventTimerPreferences() { function addCheckbox(keyname, desc, hoverdesc, defaultvalue) { hoverdesc += ' >> ' + uitext.checkboxhover; var box = $(document.createElement("input")).attr("type", "checkbox").attr("id", keyname + "-toggle").attr("title", hoverdesc); var label = $(document.createElement("label")).attr("for", keyname + "-toggle").attr("title", hoverdesc).text(desc); box.attr('checked', getEventTimerPreferences(keyname, defaultvalue)); eventTimerSettings.append(box).append(label); } // Create fieldset container with legend var eventTimerSettings = $(document.createElement("fieldset")).attr("class", "widget").attr("id", "event-timer-legend") .append($(document.createElement("legend")).text(uitext.legendname)); $.each(uitext.checkboxes, function (k, v) { addCheckbox(k, v.name, v.hover, v.defaultvalue); }); eventTimerSettings.append($(document.createElement("input")).attr("id", "apply-button").attr("class", "mw-ui-button button").attr("type", "button").attr("value", uitext.applysettings)); eventTimerSettings.append($(document.createElement("input")).attr("id", "forget-button").attr("class", "mw-ui-button button").attr("type", "button").attr("value", uitext.forgetsettings)); eventTimerSettings.append($(document.createElement("span")) .append(wikiLink(uitext.widgetlink, uitext.widgetlinktext)) ); $('#event-wrapper').after(eventTimerSettings); // Save checkbox settings $.each(uitext.checkboxes, function (k, v) { $('#' + k + '-toggle').click(function () { setEventTimerPreferences(k, $('#' + k + '-toggle').prop('checked'), v.defaultvalue); }); }); $('#apply-button').click(function () { mainEventTimer(true); }); $('#forget-button').click(function () { try { // Remove local storage and reset checkboxes localStorage.removeItem('event-timer-version'); localStorage.removeItem('event-timer-sequence'); $.each(uitext.checkboxes, function (k, v) { localStorage.removeItem('event-timer-' + k); $('#' + k + '-toggle').prop('checked', v.defaultvalue); }); console.log('Removed stored event timer preferences.'); } catch (e) { console.log('localStorage not supported (HTML5 browser required)'); } mainEventTimer(true); }); } // Utility function #5: Convert a time given in minutes since 00:00 into a recognizable time. (1440 = one whole day, 1515 = one whole schedule day) function unwrapUTC(time) { var timeRaw, timeString; // Check combined offset in hours is not beyond 23:59 time = time % 1440; // Calculate the hours and minutes var hour = Math.floor(time / 60); var minute = time % 60; // If timezone offset is zero, use UTC time and don't bother with date objects, otherwise use local time if (timezoneOffset == 0) { if (twelveHourTimes == false) { timeRaw = pad(hour) + ':' + pad(minute); timeString = pad(hour) + ':' + pad(minute); } else { timeRaw = (((hour + 11) % 12) + 1) + ':' + pad(minute) + ' ' + (hour >= 12 ? 'PM' : 'AM'); timeString = (((hour + 11) % 12) + 1) + ':' + pad(minute) + ' ' + (hour >= 12 ? 'PM' : 'AM'); } } else { var date = new Date(); date.setUTCHours(hour, minute, 0, 0); if (twelveHourTimes == false) { timeRaw = pad(date.getHours()) + ':' + pad(date.getMinutes()); timeString = $(document.createElement("span")).attr("title", uitext.timezonehover + " (UTC" + (timezoneOffset < 0 ? timezoneOffset / 60 : "+" + timezoneOffset / 60) + ")").text(pad(date.getHours()) + ':' + pad(date.getMinutes())); } else { timeRaw = (((date.getHours() + 11) % 12) + 1) + ':' + pad(date.getMinutes()) + ' ' + (date.getHours() >= 12 ? 'PM' : 'AM'); timeString = $(document.createElement("span")).attr("title", uitext.timezonehover + " (UTC" + (timezoneOffset < 0 ? timezoneOffset / 60 : "+" + timezoneOffset / 60) + ")").text((((date.getHours() + 11) % 12) + 1) + ":" + pad(date.getMinutes()) + " " + (date.getHours() >= 12 ? "PM" : "AM")); } } return { raw: timeRaw, string: timeString }; } // Utility function #6: Zero pad numbers into strings of character length two. function pad(s) { return (s < 10 ? '0' : '') + s; } // Utility function #7: Create a one-click select element for a chatlink. function chatLinkSelect(chatLinkCode) { var span = document.createElement('span'); span.innerHTML = chatLinkCode; var input = document.createElement('input'); input.className = 'chatlink'; input.type = 'text'; input.value = chatLinkCode; input.readOnly = true; input.spellcheck = false; $(span).click(function () { this.style.visibility = 'hidden'; input.style.display = 'inline-block'; input.focus(); input.select(); }); $(input).blur(function () { this.style.display = null; span.style.visibility = null; }); var output = document.createElement('span'); output.className = 'event-chatlink'; output.appendChild(input); output.appendChild(span); return output; } // Utility function #8: Draw blocks for the given object function drawRow(metaKey, metaSingular) { // Create a bar container (this will hold the bar and the associated title) var barcontainer = $(document.createElement("div")).attr("class", "event-bar-container " + metaKey).attr("data-abbr", metaKey); // Display category if not used before if (typeof metaSingular.category != 'undefined' && usedHeadings.indexOf(metaSingular.category) == -1) { usedHeadings.push(metaSingular.category); barcontainer.append($(document.createElement("h3")).attr("class", metaKey).text(metaSingular.category)); } // Display heading with link always if (typeof metaSingular.link != 'undefined') { barcontainer.append($(document.createElement("h4")) .append($(document.createElement("span")) .append(wikiLink(metaSingular.link, metaSingular.name)) ) ); } else { barcontainer.append($(document.createElement("h4")) .append($(document.createElement("span")) .append(wikiLink(metaSingular.name)) ) ); } // Create a bar for the meta segments var bar = $(document.createElement("div")).attr("class", "event-bar"); // For each event create a "segment" $.each(metaSingular.sequences.refined, function (i, v) { var name = metaSingular.segments[v.r].name, link = metaSingular.segments[v.r].link || (name == '' ? '' : name), chatlink = metaSingular.segments[v.r].chatlink || ''; var time = unwrapUTC(v['s']); // Create a segment to represent that phase, and set the width based on the duration var segment = $(document.createElement("div")).attr("class", "event-bar-segment " + metaKey + v.r + v.cl).css("width", (100 * v["d"] / 135) + "%").attr("title", (metaSingular.name ? metaSingular.name + "\r" : "") + time.raw + (name == "" ? "" : " - ") + name); // Time var segmentTime = $(document.createElement("span")).attr("class", "event-time") .append(time.string); segment.append(segmentTime); // Segment event name and link // Use NBSP instead of leaving empty event names, as if the name is blank for a whole 2hr15 slot, then the segment height will reduce if the span element is empty if (name == "") { name = "\u00a0"; } segment.append($(document.createElement("span")).attr("class", "event-name") .append(link == "" ? document.createTextNode(name) : wikiLink(link, name))); // Chatlink if (chatlink != '') { segment.append(chatLinkSelect(chatlink)); } bar.append(segment); }); bar.append($(document.createElement("span")).attr("class", "event-bar-exit").attr("title", uitext.deleterowhover).text("[X]")); barcontainer.append(bar); $('#event-container').append(barcontainer); } // Utility function #9: Refine the schedule from 1515 to 135 minutes. Firstly apply a rough filter around the window, then truncate to ensure events are within the window. function filterEventData(metas) { function refineRow(schedule, metaKey) { // Window start, future and end times in minutes var ws = startHourUTC * 60; var wf = ws + 120; var we = ws + 135; // Filter the data down from 24 hours to roughly 2 hours. function timeWithinWindow(schedule) { return ((schedule.e > ws && schedule.s < we)); } var roughSchedule = schedule.filter(timeWithinWindow); // Refine the data to restrict lengths to visible window var refinedSchedule = []; $.each(roughSchedule, function (i, v) { // Local copies that we can adjust var r = v.r, s = v.s, e = v.e; // Check if window starts after the segment started, if so, crop it if (ws > s) { s = ws; if (metaKey == 'ds' && r == 1) { r = 2; // Special case: Dragon's Stand } } // Check end of segment is before window end, if not, crop it if (e > we) { e = we; } // Check if segment crosses the 2 hour marker, if it does, split into two if (s < wf && wf < e) { // Two objects, one beginning to the left of the future line + ending at the future line, and one starting at the future line refinedSchedule.push({ r: r, // Reference id, e.g. wb1 s: s, // Start minutes, e.g. 10 e: wf, // End minutes, e.g. 60 d: wf - s, // Duration, e.g. 50 cl: '' // Class placeholder only used for future last 15 minutes segments }); if (metaKey == 'ds' && r == 1) { r = 2; // Special case: Dragon's Stand future } refinedSchedule.push({ r: r, s: wf, e: e, d: e - wf, cl: ' future' }); } else if (wf < e) { // Just one object, with the ending after the future line + beginning on or after future line refinedSchedule.push({ r: r, s: s, e: e, d: e - s, cl: ' future' }); } else { // Just one object, with the ending on or before the future line refinedSchedule.push({ r: r, s: s, e: e, d: e - s, cl: '' }); } }); return refinedSchedule; } // Refine schedule to fit 135 minute view. $.each(metas, function (k, v) { metas[k].sequences.refined = refineRow(v.sequences.full, k); }); return metas; } // Utility function #10: Draw meta event "phases" as segments within map "bars" for each meta. function createEventBars(useEvenHourStart, metaSequence, otherHourOffset) { // All event bars and segments need to be created with the same start time var now = new Date(); startHourUTC = now.getUTCHours(); // Check if otherHour specified if (otherHourOffset) { startHourUTC += otherHourOffset; } // Use even hours if required, or any hour if not specified if (useEvenHourStart === true) { startHourUTC = Math.floor(startHourUTC / 2) * 2; } // Filter the schedule for the current 135 minute window customEventData = filterEventData(customEventData); // Reset any previously set category heading tracking usedHeadings = []; // Do the work $.each(metaSequence, function (i, metaKey) { drawRow(metaKey, customEventData[metaKey]); }); // Allow reordering of elements $('#event-container').sortable({ placeholder: 'ui-sortable-placeholder', update: function () { // Update stored values var eventBars = $('.event-bar-container'); var eventAbbrs = []; $.each(eventBars, function () { eventAbbrs.push(this.getAttribute('data-abbr')); }); setEventTimerPreferences('sequence', eventAbbrs, defaultSequence); console.log('Rearranged sequence to: ' + JSON.stringify(eventAbbrs)); // Now reload otherwise people whine about category titles. mainEventTimer(true); } }); // Allow closure of event bars $('.event-bar-exit').click(function () { var eventBar = this.closest('.event-bar-container'); var eventAbbr = eventBar.getAttribute('data-abbr'); var currentPref = getEventTimerPreferences('sequence', defaultSequence); // Adjust stored preferences to remove given element from preferences var abbrIndex = currentPref.indexOf(eventAbbr); if (abbrIndex > -1) { currentPref.splice(abbrIndex, 1); } setEventTimerPreferences('sequence', currentPref, defaultSequence); console.log('Deleted element. Remaining sequence: ' + JSON.stringify(currentPref)); // And hide chosen element whilst still on this page. Next time it won't load that element until you press reset. // $('[data-abbr="'+eventAbbr+'"]').remove(); -- not required if we redraw // Check if remaining sequence is only length 2 (i.e. only the the top and bottom times remain - everything else deleted) // If so, reset the sequence entirely. var revisedCurrentPref = getEventTimerPreferences('sequence', defaultSequence); if (revisedCurrentPref.length === 2) { setEventTimerPreferences('sequence', defaultSequence); } // Now reload otherwise people whine about category titles. mainEventTimer(true); }); // fixme - no idea why, but this line is required to make everything work. startHourUTC = now.getUTCHours(); } // Utility function #11: Generate a full day of meta pattern function eventsGenerator(eventData, metaSequence) { function fullPatternGenerator(partial, pattern) { // 23:00 plus 2 hour lookahead plus 15 mins future var fillDuration = 60 * 25 + 15; // Figure out total length of partial var partialDuration = 0; $.map(partial, function (v) { partialDuration += v.d; }); // If already sufficiently long, then we don't need to add any pattern sections var fullPattern; if (partialDuration >= fillDuration) { fullPattern = partial; } else { // Figure out total length of pattern var patternDuration = 0; $.map(pattern, function (v) { patternDuration += v.d; }); // Minimum number of pattern repetitions required var patternQty = Math.ceil((fillDuration - partialDuration) / patternDuration); // Repeat pattern - can use this when we remove IE support later: // var repeatedPattern = Array(patternQty).fill().map(function(){ return pattern; }); var repeatedPattern = 'z'.repeat(patternQty).split('').map(function () { return pattern; }); // Collapse nested arrays and concatenate with the initial partial pattern fullPattern = partial.concat($.map(repeatedPattern, function (v) { return v; })); } // Now insert start and end markers var sCumulative = 0; fullPattern = $.map(fullPattern, function (v) { // Don't bother appending if cumulative start time is outside range of interest if (sCumulative >= fillDuration) { return } // Update current object v.s = sCumulative; v.e = v.s + v.d; // Update for next sCumulative = v.s + v.d; // Return current - note if you try to return v then it caches the result and every object returned is the same as the last one return { r: v.r, d: v.d, s: v.s, e: v.e }; }); return fullPattern; } var fullMetas = {}; $.each(eventData, function (k, v) { // Don't bother calculating if the meta hasn't been requested if (metaSequence.indexOf(k) == -1) { return } fullMetas[k] = eventData[k]; fullMetas[k].sequences.full = fullPatternGenerator(v.sequences.partial, v.sequences.pattern); }); return fullMetas; } // Utility function #12: Move the pointer to a new horizontal location based on the current time. function movePointer(useEvenHourStart, metaSequence) { var now = new Date(); var hour = now.getUTCHours(); var minute = now.getUTCMinutes(); // Distance in percent of the 135 minute window (2 hour + 15 mins) var currentStartHourUTC = hour; var percentOfTwoHours = ((minute / 60) * 50) * (120 / 135); if (useEvenHourStart === true) { currentStartHourUTC = Math.floor(hour / 2) * 2; percentOfTwoHours = (((hour % 2) + (minute / 60)) * 50) * (120 / 135); } // Move the pointer $('.event-pointer').css('left', percentOfTwoHours + '%'); // Check if pointer has gone beyond the 1 or 2 hour mark, it will have slid to the left, in which case we need to redraw everything else too. if (startHourUTC != hour) { // Erase existing event bars $('#event-container').html(''); // Add new ones based on the new time createEventBars(useEvenHourStart, metaSequence); } // Update local time too var timezoneOffsetString = ''; if (timezoneOffset === 0) { if (twelveHourTimes == false) { $('.event-pointer span').text(pad(hour) + ':' + pad(minute) + ' UTC'); } else { $('.event-pointer span').text((((hour + 11) % 12) + 1) + ':' + pad(minute) + ' ' + (hour >= 12 ? 'PM' : 'AM')); } } else { // For positive timezones, add a plus sign before the hour offset. Negative timezones already have a minus sign. timezoneOffsetString = 'UTC' + (timezoneOffset < 0 ? timezoneOffset / 60 : '+' + timezoneOffset / 60); if (twelveHourTimes == false) { $('.event-pointer span').text(pad(now.getHours()) + ':' + pad(now.getMinutes()) + ' ' + timezoneOffsetString); } else { $('.event-pointer span').text((((now.getHours() + 11) % 12) + 1) + ':' + pad(now.getMinutes()) + ' ' + (now.getHours() >= 12 ? 'PM' : 'AM')); } } // Check if pointer is beyond 78% (avoid clashing between red and gray markers) if (percentOfTwoHours > 78) { $('.event-pointer-time').css('right', '0px'); } else { $('.event-pointer-time').css('right', 'inherit'); } } // Utility function #13: Allowing shuffling forwards and backwards function timeshiftOnClick() { // Allow user to shuffle forwards and backwards by clicking on the gray markers $('.event-limit-text').css('cursor', 'pointer'); $('.event-limit-text.next').prop('title', uitext.timeshiftnexthoverpause); $('.event-limit-text').click(function (e) { $('.event-pointer').css('left', '0%'); $('.event-pointer-time').text(uitext.timeshiftresume); $('.event-limit-text.prev').css('display', 'inherit'); $('.event-limit-text.prev').prop('title', uitext.timeshiftprevhover); $('.event-limit-text.next').prop('title', uitext.timeshiftnexthover); // Figure out if next or prev was clicked if (e.target.classList.contains('next')) { otherHourOffset = otherHourOffset + 2; } else { otherHourOffset = otherHourOffset + 22; } // Restrict it to +23 hours otherHourOffset = otherHourOffset % 24; // Check if its gone beyond midnight if (otherHourOffset + startHourUTC >= 24) { otherHourOffset = otherHourOffset - 24; } // Check if offset is back to zero if (otherHourOffset == 0) { mainEventTimer(true); } else { mainEventTimer(true, true); } }); // Restart live updating after clicking on the red marker $('.event-pointer-time').click(function () { mainEventTimer(true); }); } // Utility function #14: Refit compact timer to window width on resize. Only visible with the "Compact view" checkbox ticked. H4 headings 220px to left function fitTimerToWindowWidth() { var w = $('#mw-content-text')[0].offsetWidth; $('#EventTimerCSS3').text('#event-wrapper.compact { width: ' + (w - (220 + 20)) + 'px } '); }; // Utility function #15: Create wiki like links; inactive when on the same page as linked to. var pageTitlePattern = /(?:(?:\/wiki\/)(.*?)(?:\?|#|$)|(?:title=)(.*?)(?:&|#|$))/; function wikiLink(pageName, text) { text = text || pageName.replace(/_/g, " "); pageName = pageName.replace(/ /g, "_"); var match = pageTitlePattern.exec(location.href); var current = match[1] || match[2]; if (current === pageName) { return $(document.createElement("a")).attr("class", "mw-selflink selflink").text(text); } else { return $(document.createElement("a")).attr("href", "/wiki/" + pageName).attr("title", pageName.replace(/_/g, " ")).text(text); } } // MAIN FUNCTION function mainEventTimer(reloaded, paused) { // Collect parameter options if specified // var zoneParameter = '<!--{$zone|default:""|escape:"javascript"}-->'; // var excludeParameter = '<!--{$exclude|default:""|escape:"javascript"}-->'; var excludeParameter = ''; // Collect parameter options if specified var scriptNode = $('script#Widget_Event-Timer'), zoneParameter = ''; if ( scriptNode.length > 0 ) { zoneParameter = scriptNode[0].getAttribute('data-zone'); } // If the timer was reloaded via apply, or scrolled, reset event content and timers, otherwise its the first run and we need to create the preferences user interface. if (reloaded || paused) { $('#event-container').html(''); $('#event-wrapper').removeClass(); clearInterval(setIntervalHandle); } else if (zoneParameter == '') { // Display checkboxes if showing every timer (probably on the Event timers page) eventTimerPreferences(); } // Collect preferences from localStorage var useTwelveHour = getEventTimerPreferences('twelvehour', uitext.checkboxes.twelvehour.defaultvalue); var useTopTimes = getEventTimerPreferences('toptimes', uitext.checkboxes.toptimes.defaultvalue); var useCompact = getEventTimerPreferences('compact', uitext.checkboxes.compact.defaultvalue); var hideCategories = getEventTimerPreferences('hidecategories', uitext.checkboxes.hidecategories.defaultvalue); var hideHeadings = getEventTimerPreferences('hideheadings', uitext.checkboxes.hideheadings.defaultvalue); var hideChatLinks = getEventTimerPreferences('hidechatlinks', uitext.checkboxes.hidechatlinks.defaultvalue); var useEvenHourStart = getEventTimerPreferences('even', uitext.checkboxes.even.defaultvalue); // Check for sequence preferences set by a previous version of the event timer, if so, overwrite var lastVersion = getEventTimerPreferences('version', '0'); if (lastVersion != version) { setEventTimerPreferences('version', version); setEventTimerPreferences('sequence', defaultSequence); } // Respect preferences if given and the zone parameter is specified var metaSequence = getEventTimerPreferences('sequence', defaultSequence); if (zoneParameter !== '') { // Zone parameter is set // Validate zone inputs exist in the full list var whitelist = Object.keys(eventData); var zones = []; $.each(zoneParameter.replace(', ', ',').split(','), function (i, v) { if (whitelist.indexOf(v) !== -1) { zones.push(v); } }); // Check if there are no valid options remaining if (zones.length == 0) { console.log('Error - No valid options provided within the zone parameter (' + zoneParameter + ')'); return; } // Check successful, continue - overwrite metaSequence $('#event-wrapper').addClass('zone'); hideCategories = true; useCompact = false; hideHeadings = false; useTopTimes = false; hideChatLinks = false; metaSequence = []; $.each(zones, function (i, v) { metaSequence.push(v); }); } else { // Zone parameter is blank // Exclusions $.each(excludeParameter.replace(', ', ',').split(','), function (i, v) { var index = metaSequence.indexOf(v); if (index !== -1) { metaSequence.splice(index, 1); } }); // Check if there are no valid options remaining if (metaSequence.length == 0) { console.log('Error - Exclusions resulted in no valid options being provided (' + excludeParameter + ')'); return; } } // Use viewer preferences immediately where possible if (hideCategories === true) { $('#event-wrapper').addClass('hidecategories'); } if (hideHeadings === true) { $('#event-wrapper').addClass('hideheadings'); } if (useTopTimes === true) { $('#event-wrapper').addClass('toptimes'); } if (useCompact === true) { $('#event-wrapper').addClass('compact'); } if (hideChatLinks === true) { $('#event-wrapper').addClass('hidechatlinks'); } if (useTwelveHour == true) { twelveHourTimes = true; } else { twelveHourTimes = false; } // One off tasks: Draw meta event segmented-bars, enhance them, and add a static pointer. if (paused) { createEventBars(useEvenHourStart, metaSequence, otherHourOffset); } else { $('.event-limit-text.prev').css('display', 'none'); otherHourOffset = 0; customEventData = eventsGenerator(eventData, metaSequence); createEventBars(useEvenHourStart, metaSequence); movePointer(useEvenHourStart, metaSequence); // Recurring tasks: Move the pointer every 10 seconds. Every 2 hours, redraw the segmented bars setIntervalHandle = setInterval(movePointer.bind(null, useEvenHourStart, metaSequence), 10000); // bind syntax is an IE workaround } // Paranoia - recalculate compact window width if its been reloaded if (reloaded) { fitTimerToWindowWidth(); } } // DEFER LOADING SCRIPT UNTIL JQUERY IS READY. WAIT 40MS BETWEEN ATTEMPTS. function defer(method) { if (window.jQuery) { method(); } else { setTimeout(function () { defer(method) }, 40); } } // INITIALISATION defer(function () { writeTimerCSS(); // Load the event timer after loading the jquery ui module $.ajaxSetup({ cache: true }); $.getScript('/index.php?title=Widget:Event-Timer/jquery_ui_sortable_min.js&action=raw&ctype=text/javascript', function (data, textStatus, jqxhr) { // Load the main widget from above mainEventTimer(); timeshiftOnClick(); }); }); /*</nowiki>*/