| @@ -0,0 +1,66 @@ | |||||
| from datetime import datetime, timedelta | |||||
| import humanize | |||||
| __all__ = ["format_isk", "format_isk_compact", "format_utctime", | |||||
| "format_utctime_compact"] | |||||
| def format_isk(value): | |||||
| """Nicely format an ISK value.""" | |||||
| if value < 10**6: | |||||
| return "{:,.2f}".format(value) | |||||
| return humanize.intword(value, "%.2f") | |||||
| def format_isk_compact(value): | |||||
| """Nicely format an ISK value compactly.""" | |||||
| # Based on humanize.intword(). | |||||
| powers = [10 ** x for x in [3, 6, 9, 12, 15]] | |||||
| letters = ["k", "m", "b", "t", "q"] | |||||
| if value < powers[0]: | |||||
| return "{:,.2f}".format(value) | |||||
| for ordinal, power in enumerate(powers[1:], 1): | |||||
| if value < power: | |||||
| chopped = value / float(powers[ordinal - 1]) | |||||
| return "{:,.2f}{}".format(chopped, letters[ordinal - 1]) | |||||
| return str(value) | |||||
| def format_utctime(value): | |||||
| """Format a UTC timestamp.""" | |||||
| return humanize.naturaltime(datetime.utcnow() - value) | |||||
| def _format_compact_delta(delta): | |||||
| """Return a snippet of formatting for a time delta.""" | |||||
| # Based on humanize.naturaldelta(). | |||||
| seconds = abs(delta.seconds) | |||||
| days = abs(delta.days) | |||||
| years = days // 365 | |||||
| days = days % 365 | |||||
| months = int(days // 30.5) | |||||
| if years == 0 and days < 1: | |||||
| if seconds < 60: | |||||
| return "{}s".format(seconds) | |||||
| if seconds < 3600: | |||||
| minutes = seconds // 60 | |||||
| return "{}m".format(minutes) | |||||
| hours = seconds // 3600 | |||||
| return "{}h".format(hours) | |||||
| if years == 0: | |||||
| if months == 0: | |||||
| return "{}d".format(days) | |||||
| return "{}mo".format(months) | |||||
| if years == 1: | |||||
| if months == 0: | |||||
| if days == 0: | |||||
| return "1y" | |||||
| return "1y {}d".format(days) | |||||
| return "1y {}mo".format(months) | |||||
| return "{}y".format(years) | |||||
| def format_utctime_compact(value): | |||||
| """Format a UTC timestamp compactly.""" | |||||
| delta = datetime.utcnow() - value | |||||
| if delta < timedelta(seconds=1): | |||||
| return "just now" | |||||
| return "{} ago".format(_format_compact_delta(delta)) | |||||
| @@ -87,6 +87,8 @@ def _update_collection_operations(cname, opnames): | |||||
| operation = campaign["operations"][opname] | operation = campaign["operations"][opname] | ||||
| show_isk = operation.get("isk", True) | show_isk = operation.get("isk", True) | ||||
| # store per-user counts; update for all users in corp who have fresh | |||||
| # API keys and leave other data stale | |||||
| ... | ... | ||||
| primary = __import__("random").randint(10, 99) | primary = __import__("random").randint(10, 99) | ||||
| secondary = __import__("random").randint(10000000, 5000000000) / 100 \ | secondary = __import__("random").randint(10000000, 5000000000) / 100 \ | ||||
| @@ -31,6 +31,11 @@ h2, h3 { | |||||
| margin: 0.5em 0; | margin: 0.5em 0; | ||||
| } | } | ||||
| abbr[title], acronym[title] { | |||||
| border-bottom: 1px dotted #555; | |||||
| text-decoration: none; | |||||
| } | |||||
| .understate { | .understate { | ||||
| font-weight: normal; | font-weight: normal; | ||||
| } | } | ||||
| @@ -393,13 +398,88 @@ h2 .disabled-info { | |||||
| font-size: 150%; | font-size: 150%; | ||||
| } | } | ||||
| .operation .secondary { | |||||
| font-size: 105%; | |||||
| } | |||||
| .operation .unit { | .operation .unit { | ||||
| font-style: italic; | font-style: italic; | ||||
| } | } | ||||
| .operation .summary .head { | |||||
| margin-top: 1em; | |||||
| font-size: 14px; | |||||
| } | |||||
| .operation .summary .contents { | |||||
| position: relative; | |||||
| margin-top: 0.5em; | |||||
| border: 1px solid #282828; | |||||
| font-size: 14px; | |||||
| } | |||||
| .operation .killboard { | |||||
| border-spacing: 0; | |||||
| border-collapse: collapse; | |||||
| text-align: left; | |||||
| } | |||||
| .operation .killboard:not(.expanded) tr:nth-child(2n) { | |||||
| background-color: #181818; | |||||
| } | |||||
| .operation .killboard:not(.expanded) tr:nth-child(2n+1) { | |||||
| background-color: #0A0A0A; | |||||
| } | |||||
| .operation .killboard td { | |||||
| padding: 0.25em 0; | |||||
| } | |||||
| .operation .killboard td:first-child { | |||||
| padding-left: 1em; | |||||
| } | |||||
| .operation .killboard td:last-child { | |||||
| padding-right: 1em; | |||||
| } | |||||
| .operation .killboard .fluid { | |||||
| padding-right: 0.5em; | |||||
| } | |||||
| .operation .killboard .icon { | |||||
| width: 46px; | |||||
| } | |||||
| .operation .killboard.expanded { | |||||
| position: absolute; | |||||
| z-index: 1; | |||||
| transition: clip-path 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); | |||||
| clip-path: inset(0 100% 0 0); | |||||
| } | |||||
| .operation .killboard:not(.expanded) .extra { | |||||
| display: none; | |||||
| } | |||||
| .operation .killboard.expanded .spacer { | |||||
| display: none; | |||||
| } | |||||
| .operation .killboard img { | |||||
| width: 42px; | |||||
| height: 42px; | |||||
| vertical-align: middle; | |||||
| } | |||||
| .operation .killboard abbr { | |||||
| border-bottom: none; | |||||
| } | |||||
| @media (min-width: 800px) { | @media (min-width: 800px) { | ||||
| #operations { | #operations { | ||||
| margin: 1em 2em; | |||||
| margin: 1em 0; | |||||
| } | } | ||||
| #operations section { | #operations section { | ||||
| @@ -429,6 +509,10 @@ h2 .disabled-info { | |||||
| margin: 0.5em 0; | margin: 0.5em 0; | ||||
| } | } | ||||
| .operation .overview { | |||||
| margin-left: 1em; | |||||
| } | |||||
| .operation .primary, .operation .primary .unit { | .operation .primary, .operation .primary .unit { | ||||
| display: inline-block; | display: inline-block; | ||||
| } | } | ||||
| @@ -436,6 +520,10 @@ h2 .disabled-info { | |||||
| .operation .unit { | .operation .unit { | ||||
| margin-left: 0.15em; | margin-left: 0.15em; | ||||
| } | } | ||||
| .operation .killboard:not(.expanded) { | |||||
| width: 100%; | |||||
| } | |||||
| } | } | ||||
| /* -------------------------------- Members -------------------------------- */ | /* -------------------------------- Members -------------------------------- */ | ||||
| @@ -64,4 +64,23 @@ $(function() { | |||||
| this.form.submit(); | this.form.submit(); | ||||
| }); | }); | ||||
| $('#campaigns-select input[type="submit"]').hide(); | $('#campaigns-select input[type="submit"]').hide(); | ||||
| //Campaigns: selectively reveal operation summary details: | |||||
| $(".operation .killboard tr").mouseenter(function() { | |||||
| var div = $("<table>", {addClass: "killboard expanded"}) | |||||
| .css($(this).position()) | |||||
| .css("background-color", $(this).css("background-color")) | |||||
| .css("position", "fixed") | |||||
| .append($("<tr>").html($(this).html())) | |||||
| .mouseleave(function() { $(this).remove(); }); | |||||
| div.find(".spacer").remove(); | |||||
| $(this).closest(".summary").find(".expanded").remove(); | |||||
| $(this).closest(".contents").prepend(div); | |||||
| div.css("width", Math.max(div.width(), $(this).width())); | |||||
| div.css("position", ""); | |||||
| div.css("clip-path", "inset(0 0% 0 0)"); | |||||
| }); | |||||
| $(".operation .summary").mouseleave(function() { | |||||
| $(this).find(".expanded").remove(); | |||||
| }); | |||||
| }); | }); | ||||
| @@ -1,4 +1,6 @@ | |||||
| <%! import humanize %> | |||||
| <%! | |||||
| from calefaction.format import format_isk | |||||
| %> | |||||
| <%inherit file="../_default.mako"/> | <%inherit file="../_default.mako"/> | ||||
| <%namespace file="renderers.mako" import="render_summary"/> | <%namespace file="renderers.mako" import="render_summary"/> | ||||
| <%block name="title"> | <%block name="title"> | ||||
| @@ -37,14 +39,16 @@ | |||||
| % if secondary is not None: | % if secondary is not None: | ||||
| <div class="secondary"> | <div class="secondary"> | ||||
| <abbr title="${"{:,.2f}".format(secondary)} ${sunit}"> | <abbr title="${"{:,.2f}".format(secondary)} ${sunit}"> | ||||
| <span class="num">${humanize.intword(secondary) | h}</span> | |||||
| <span class="num">${format_isk(secondary) | h}</span> | |||||
| <span class="unit">${sunit}</span> | <span class="unit">${sunit}</span> | ||||
| </abbr> | </abbr> | ||||
| </div> | </div> | ||||
| % endif | % endif | ||||
| </div> | </div> | ||||
| % if summary: | % if summary: | ||||
| ${render_summary(renderer, summary)} | |||||
| <div class="summary"> | |||||
| ${render_summary(renderer, summary)} | |||||
| </div> | |||||
| % endif | % endif | ||||
| </div> | </div> | ||||
| % endfor | % endfor | ||||
| @@ -1,25 +1,57 @@ | |||||
| <%! import humanize %> | |||||
| <%! | |||||
| from calefaction.format import format_isk_compact, format_utctime_compact | |||||
| %> | |||||
| <%def name="_killboard_kill(kill)"> | |||||
| <% victim = kill["victim"] %> | |||||
| <tr> | |||||
| <td class="fluid"> | |||||
| <span> | |||||
| <abbr title="${kill["date"].strftime("%Y-%m-%d %H:%M")}">${format_utctime_compact(kill["date"]) | h}</abbr><br/> | |||||
| <abbr title="${"{:,.2f}".format(kill["value"])} ISK">${format_isk_compact(kill["value"]) | h}</abbr> | |||||
| </span> | |||||
| </td> | |||||
| <td class="fluid extra"> | |||||
| <span> | |||||
| ${kill["system"]} 0.3<br/><!-- ... --> | |||||
| Region<!-- ... --> | |||||
| </span> | |||||
| </td> | |||||
| <td class="icon"> | |||||
| <a href="https://zkillboard.com/kill/${kill['id']}/"><img title="" alt="<!-- ... -->" src="${g.eve.image.inventory(victim["ship_id"], 64)}"/></a> | |||||
| </td> | |||||
| <td class="icon extra"> | |||||
| <a href="https://zkillboard.com/character/${victim['char_id']}/"><img title="" alt="<!-- ... -->" src="${g.eve.image.character(victim["char_id"], 128)}"/></a> | |||||
| </td> | |||||
| <td class="icon${' extra' if victim["alliance_id"] and victim["faction_id"] else ''}"> | |||||
| <a href="https://zkillboard.com/corporation/${victim['corp_id']}/"><img title="" alt="<!-- ... -->" src="${g.eve.image.corp(victim["corp_id"], 128)}"/></a> | |||||
| </td> | |||||
| <td class="icon${'' if victim["alliance_id"] else ' extra'}"> | |||||
| % if victim["alliance_id"]: | |||||
| <a href="https://zkillboard.com/alliance/${victim['alliance_id']}/"><img title="" alt="<!-- ... -->" src="${g.eve.image.alliance(victim["alliance_id"], 128)}"/></a> | |||||
| % endif | |||||
| </td> | |||||
| <td class="icon${'' if victim["faction_id"] else ' extra'}"> | |||||
| % if victim["faction_id"]: | |||||
| <a href="https://zkillboard.com/faction/${victim['faction_id']}/"><img title="" alt="<!-- ... -->" src="${g.eve.image.faction(victim["faction_id"], 128)}"/></a> | |||||
| % endif | |||||
| </td> | |||||
| % if not victim["alliance_id"] and not victim["faction_id"]: | |||||
| <td class="icon spacer"></td> | |||||
| % endif | |||||
| </tr> | |||||
| </%def> | |||||
| <%def name="_killboard_recent(summary)"> | <%def name="_killboard_recent(summary)"> | ||||
| <ul class="summary"> | |||||
| % for kill in summary: | |||||
| <li> | |||||
| <a href="https://zkillboard.com/kill/${kill['id']}/">${kill["id"]}</a> | |||||
| ${kill["system"]} | |||||
| <abbr title="${kill["date"].strftime("%Y-%m-%d %H:%M")}">${humanize.naturaltime(kill["date"]) | h}</abbr> | |||||
| <img src="${g.eve.image.render(kill["victim"]["ship_id"], 128)}"/> | |||||
| <img src="${g.eve.image.character(kill["victim"]["char_id"], 128)}"/> | |||||
| <img src="${g.eve.image.corp(kill["victim"]["corp_id"], 128)}"/> | |||||
| <img src="${g.eve.image.alliance(kill["victim"]["alliance_id"], 128)}"/> | |||||
| <img src="${g.eve.image.faction(kill["victim"]["faction_id"], 128)}"/> | |||||
| <abbr title="${"{:,.2f}".format(kill["value"])} ISK">${humanize.intword(kill["value"]) | h} ISK</abbr> | |||||
| </li> | |||||
| % endfor | |||||
| </ul> | |||||
| <div class="head">Most recent kills:</div> | |||||
| <div class="contents"> | |||||
| <table class="killboard"> | |||||
| % for kill in summary: | |||||
| ${_killboard_kill(kill)} | |||||
| % endfor | |||||
| </table> | |||||
| </div> | |||||
| </%def> | </%def> | ||||
| <%def name="render_summary(renderer, summary)"><% | <%def name="render_summary(renderer, summary)"><% | ||||
| if renderer == "killboard_recent": | if renderer == "killboard_recent": | ||||
| return _killboard_recent(summary) | return _killboard_recent(summary) | ||||
| else: | |||||
| raise RuntimeError("Unknown renderer: %s" % renderer) | |||||
| raise RuntimeError("Unknown renderer: %s" % renderer) | |||||
| %></%def> | %></%def> | ||||