Skip to content

Commit

Permalink
Add proper block inscriptions HTML (ordinals#2337)
Browse files Browse the repository at this point in the history
  • Loading branch information
veryordinally committed Aug 27, 2023
1 parent 34bb1c6 commit 898c2cd
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 21 deletions.
5 changes: 5 additions & 0 deletions src/index/block_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@ impl BlockIndex {
if block_height >= index.block_count()? || block_height < self.first_inscription_height {
return Ok(Vec::new());
}

if self.lowest_blessed_by_block.is_empty() {
return Err(anyhow!("Block index not yet initialized"));
}

let lowest_cursed = self.lowest_cursed_by_block
[usize::try_from(block_height.saturating_sub(self.first_inscription_height))?];
let lowest_blessed = self.lowest_blessed_by_block
Expand Down
68 changes: 53 additions & 15 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ use {
crate::index::block_index::BlockIndex,
crate::page_config::PageConfig,
crate::templates::{
BlockHtml, ClockSvg, HomeHtml, InputHtml, InscriptionHtml, InscriptionJson, InscriptionsHtml,
InscriptionsJson, OutputHtml, OutputJson, PageContent, PageHtml, PreviewAudioHtml,
PreviewImageHtml, PreviewModelHtml, PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml,
PreviewVideoHtml, RangeHtml, RareTxt, SatHtml, SatJson, TransactionHtml,
BlockHtml, ClockSvg, HomeHtml, InputHtml, InscriptionHtml, InscriptionJson,
InscriptionsBlockHtml, InscriptionsHtml, InscriptionsJson, OutputHtml, OutputJson, PageContent,
PageHtml, PreviewAudioHtml, PreviewImageHtml, PreviewModelHtml, PreviewPdfHtml,
PreviewTextHtml, PreviewUnknownHtml, PreviewVideoHtml, RangeHtml, RareTxt, SatHtml, SatJson,
TransactionHtml,
},
axum::{
body,
Expand Down Expand Up @@ -196,7 +197,14 @@ impl Server {
.route("/input/:block/:transaction/:input", get(Self::input))
.route("/inscription/:inscription_id", get(Self::inscription))
.route("/inscriptions", get(Self::inscriptions))
.route("/inscriptions/block/:n", get(Self::inscriptions_in_block))
.route(
"/inscriptions/block/:height",
get(Self::inscriptions_in_block),
)
.route(
"/inscriptions/block/:height/:page_index",
get(Self::inscriptions_in_block_from_page),
)
.route("/inscriptions/:from", get(Self::inscriptions_from))
.route("/inscriptions/:from/:n", get(Self::inscriptions_from_n))
.route("/install.sh", get(Self::install_script))
Expand Down Expand Up @@ -566,6 +574,7 @@ impl Server {
async fn block(
Extension(page_config): Extension<Arc<PageConfig>>,
Extension(index): Extension<Arc<Index>>,
Extension(block_index_state): Extension<Arc<BlockIndexState>>,
Path(DeserializeFromStr(query)): Path<DeserializeFromStr<BlockQuery>>,
) -> ServerResult<PageHtml<BlockHtml>> {
let (block, height) = match query {
Expand All @@ -589,9 +598,17 @@ impl Server {
}
};

let inscriptions =
index.get_inscriptions_in_block(&block_index_state.block_index.read().unwrap(), height)?;

Ok(
BlockHtml::new(block, Height(height), Self::index_height(&index)?)
.page(page_config, index.has_sat_index()?),
BlockHtml::new(
block,
Height(height),
Self::index_height(&index)?,
inscriptions,
)
.page(page_config, index.has_sat_index()?),
)
}

Expand Down Expand Up @@ -1042,19 +1059,40 @@ impl Server {
Extension(block_index_state): Extension<Arc<BlockIndexState>>,
Path(block_height): Path<u64>,
accept_json: AcceptJson,
) -> ServerResult<Response> {
Self::inscriptions_in_block_from_page(
Extension(page_config),
Extension(index),
Extension(block_index_state),
Path((block_height, 0)),
accept_json,
)
.await
}

async fn inscriptions_in_block_from_page(
Extension(page_config): Extension<Arc<PageConfig>>,
Extension(index): Extension<Arc<Index>>,
Extension(block_index_state): Extension<Arc<BlockIndexState>>,
Path((block_height, page_index)): Path<(u64, usize)>,
accept_json: AcceptJson,
) -> ServerResult<Response> {
let inscriptions = index
.get_inscriptions_in_block(&block_index_state.block_index.read().unwrap(), block_height)?;
.get_inscriptions_in_block(&block_index_state.block_index.read().unwrap(), block_height)
.map_err(|e| ServerError::NotFound(format!("Failed to get inscriptions in block: {}", e)))?;

Ok(if accept_json.0 {
Json(InscriptionsJson::new(inscriptions, None, None, None, None)).into_response()
} else {
InscriptionsHtml {
inscriptions,
prev: None,
next: None,
}
.page(page_config, index.has_sat_index()?)
.into_response()
InscriptionsBlockHtml::new(block_height, index.block_count()?, inscriptions, page_index)
.map_err(|e| {
ServerError::NotFound(format!(
"Failed to get inscriptions in inscriptions block page: {}",
e
))
})?
.page(page_config, index.has_sat_index()?)
.into_response()
})
}

Expand Down
2 changes: 2 additions & 0 deletions src/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub(crate) use {
input::InputHtml,
inscription::{InscriptionHtml, InscriptionJson},
inscriptions::{InscriptionsHtml, InscriptionsJson},
inscriptions_block::InscriptionsBlockHtml,
output::{OutputHtml, OutputJson},
page_config::PageConfig,
preview::{
Expand All @@ -27,6 +28,7 @@ mod iframe;
mod input;
pub mod inscription;
pub mod inscriptions;
mod inscriptions_block;
pub mod output;
mod preview;
mod range;
Expand Down
43 changes: 38 additions & 5 deletions src/templates/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,34 @@ pub(crate) struct BlockHtml {
best_height: Height,
block: Block,
height: Height,
num_inscriptions: usize,
txid_to_inscription_ids: BTreeMap<Txid, Vec<InscriptionId>>,
}

impl BlockHtml {
pub(crate) fn new(block: Block, height: Height, best_height: Height) -> Self {
pub(crate) fn new(
block: Block,
height: Height,
best_height: Height,
inscriptions: Vec<InscriptionId>,
) -> Self {
Self {
hash: block.header.block_hash(),
target: BlockHash::from_raw_hash(Hash::from_byte_array(block.header.target().to_be_bytes())),
block,
height,
best_height,
num_inscriptions: inscriptions.len(),
txid_to_inscription_ids: inscriptions.into_iter().fold(
BTreeMap::<Txid, Vec<InscriptionId>>::new(),
|mut acc, inscription_id| {
acc
.entry(inscription_id.txid)
.and_modify(|inscription_ids| inscription_ids.push(inscription_id))
.or_insert(vec![inscription_id]);
acc
},
),
}
}
}
Expand All @@ -34,7 +52,12 @@ mod tests {
#[test]
fn html() {
assert_regex_match!(
BlockHtml::new(Chain::Mainnet.genesis_block(), Height(0), Height(0)),
BlockHtml::new(
Chain::Mainnet.genesis_block(),
Height(0),
Height(0),
Vec::new()
),
"
<h1>Block 0</h1>
<dl>
Expand All @@ -48,7 +71,7 @@ mod tests {
prev
next
.*
<h2>1 Transaction</h2>
<h2>1 Transaction and 0 Inscriptions</h2>
<ul class=monospace>
<li><a href=/tx/[[:xdigit:]]{64}>[[:xdigit:]]{64}</a></li>
</ul>
Expand All @@ -60,15 +83,25 @@ mod tests {
#[test]
fn next_active_when_not_last() {
assert_regex_match!(
BlockHtml::new(Chain::Mainnet.genesis_block(), Height(0), Height(1)),
BlockHtml::new(
Chain::Mainnet.genesis_block(),
Height(0),
Height(1),
Vec::new()
),
r"<h1>Block 0</h1>.*prev\s*<a class=next href=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/block/1>next</a>.*"
);
}

#[test]
fn prev_active_when_not_first() {
assert_regex_match!(
BlockHtml::new(Chain::Mainnet.genesis_block(), Height(1), Height(1)),
BlockHtml::new(
Chain::Mainnet.genesis_block(),
Height(1),
Height(1),
Vec::new()
),
r"<h1>Block 1</h1>.*<a class=prev href=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/block/0>prev</a>\s*next.*",
);
}
Expand Down
113 changes: 113 additions & 0 deletions src/templates/inscriptions_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use super::*;

#[derive(Boilerplate)]
pub(crate) struct InscriptionsBlockHtml {
pub(crate) block: u64,
pub(crate) inscriptions: Vec<InscriptionId>,
pub(crate) prev_block: Option<u64>,
pub(crate) next_block: Option<u64>,
pub(crate) prev_page: Option<usize>,
pub(crate) next_page: Option<usize>,
}

impl InscriptionsBlockHtml {
pub(crate) fn new(
block: u64,
current_blockheight: u64,
inscriptions: Vec<InscriptionId>,
page_index: usize,
) -> Result<Self> {
let start = page_index * 100;
let end = start + 100;
let num_inscriptions = inscriptions.len();
let inscriptions = inscriptions[start..end].to_vec();

Ok(Self {
block,
inscriptions,
prev_block: if block == 0 { None } else { Some(block - 1) },
next_block: if block >= current_blockheight {
None
} else {
Some(block + 1)
},
prev_page: if page_index > 0 {
Some(page_index - 1)
} else {
None
},
next_page: if page_index * 100 <= num_inscriptions {
Some(page_index + 1)
} else {
None
},
})
}
}

impl PageContent for InscriptionsBlockHtml {
fn title(&self) -> String {
format!("Inscriptions in Block {0}", self.block)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn without_prev_and_next() {
assert_regex_match!(
InscriptionsBlockHtml {
block: 21,
inscriptions: vec![inscription_id(1), inscription_id(2)],
prev_block: None,
next_block: None,
prev_page: None,
next_page: None,
},
"
<h1>Inscriptions in <a href=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/block/21>Block 21</a></h1>
<div class=thumbnails>
<a href=/inscription/1{64}i1><iframe .* src=/preview/1{64}i1></iframe></a>
<a href=/inscription/2{64}i2><iframe .* src=/preview/2{64}i2></iframe></a>
</div>
.*
prev
next
.*
"
.unindent()
);
}

#[test]
fn with_prev_and_next() {
assert_regex_match!(
InscriptionsBlockHtml {
block: 21,
inscriptions: vec![inscription_id(1), inscription_id(2)],
prev_block: Some(20),
next_block: Some(22),
next_page: Some(3),
prev_page: Some(1),
},
"
<h1>Inscriptions in <a href=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/block/21>Block 21</a></h1>
<div class=thumbnails>
<a href=/inscription/1{64}i1><iframe .* src=/preview/1{64}i1></iframe></a>
<a href=/inscription/2{64}i2><iframe .* src=/preview/2{64}i2></iframe></a>
</div>
.*
<a class=prev href=/inscriptions/block/20>20</a>
&bull;
<a class=prev href=/inscriptions/block/21/1>prev</a>
<a class=next href=/inscriptions/block/21/3>next</a>
&bull;
<a class=next href=/inscriptions/block/22>22</a>
.*
"
.unindent()
);
}
}
9 changes: 8 additions & 1 deletion templates/block.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,17 @@ <h1>Block {{ self.height }}</h1>
next
%% }
</div>
<h2>{{"Transaction".tally(self.block.txdata.len())}}</h2>
<h2>{{"Transaction".tally(self.block.txdata.len())}} and {{"Inscription".tally(self.num_inscriptions)}}</h2>
<ul class=monospace>
%% for tx in &self.block.txdata {
%% let txid = tx.txid();
<li><a href=/tx/{{txid}}>{{txid}}</a></li>
%% if let Some(inscription_ids) = &self.txid_to_inscription_ids.get(&txid) {
<ul>
%% for inscription_id in *inscription_ids {
<li><a href=/inscription/{{inscription_id}}>{{inscription_id}}</a></li>
%% }
</ul>
%% }
%% }
</ul>
26 changes: 26 additions & 0 deletions templates/inscriptions-block.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<h1>Inscriptions in <a href=/block/{{ &self.block }}>Block {{ &self.block }}</a></h1>
<div class=thumbnails>
%% for id in &self.inscriptions {
{{ Iframe::thumbnail(*id) }}
%% }
</div>
<div class=center>
%% if let Some(prev_block) = &self.prev_block {
<a class=prev href=/inscriptions/block/{{ prev_block }}>{{ prev_block }}</a>
%% }
&bull;
%% if let Some(prev_page) = &self.prev_page {
<a class=prev href=/inscriptions/block/{{ &self.block }}/{{ prev_page }}>prev</a>
%% } else {
prev
%% }
%% if let Some(next_page) = &self.next_page {
<a class=next href=/inscriptions/block/{{ &self.block }}/{{ next_page }}>next</a>
%% } else {
next
%% }
&bull;
%% if let Some(next_block) = &self.next_block {
<a class=next href=/inscriptions/block/{{ next_block }}>{{ next_block }}</a>
%% }
</div>

0 comments on commit 898c2cd

Please sign in to comment.