image viewer: Reuse existing tabs (#19717)
Co-authored-by: Kirill <kirill@zed.dev> Co-authored-by: Mikayla <mikayla@zed.dev> Fixes #9896 Release Notes: - Fixed an issue where clicking on an image inside the project panel would not re-use an existing image tab Co-authored-by: Kirill <kirill@zed.dev> Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
ebc3031fd9
commit
6eb6788201
1 changed files with 66 additions and 16 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
use anyhow::Context as _;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
canvas, div, fill, img, opaque_grey, point, size, AnyElement, AppContext, Bounds, Context,
|
canvas, div, fill, img, opaque_grey, point, size, AnyElement, AppContext, Bounds, Context,
|
||||||
EventEmitter, FocusHandle, FocusableView, Img, InteractiveElement, IntoElement, Model,
|
EventEmitter, FocusHandle, FocusableView, Img, InteractiveElement, IntoElement, Model,
|
||||||
|
@ -19,6 +20,7 @@ use workspace::{
|
||||||
const IMAGE_VIEWER_KIND: &str = "ImageView";
|
const IMAGE_VIEWER_KIND: &str = "ImageView";
|
||||||
|
|
||||||
pub struct ImageItem {
|
pub struct ImageItem {
|
||||||
|
id: ProjectEntryId,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
project_path: ProjectPath,
|
project_path: ProjectPath,
|
||||||
}
|
}
|
||||||
|
@ -48,9 +50,15 @@ impl project::Item for ImageItem {
|
||||||
.read_with(&cx, |project, cx| project.absolute_path(&path, cx))?
|
.read_with(&cx, |project, cx| project.absolute_path(&path, cx))?
|
||||||
.ok_or_else(|| anyhow::anyhow!("Failed to find the absolute path"))?;
|
.ok_or_else(|| anyhow::anyhow!("Failed to find the absolute path"))?;
|
||||||
|
|
||||||
|
let id = project
|
||||||
|
.update(&mut cx, |project, cx| project.entry_for_path(&path, cx))?
|
||||||
|
.context("Entry not found")?
|
||||||
|
.id;
|
||||||
|
|
||||||
cx.new_model(|_| ImageItem {
|
cx.new_model(|_| ImageItem {
|
||||||
path: abs_path,
|
path: abs_path,
|
||||||
project_path: path,
|
project_path: path,
|
||||||
|
id,
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
|
@ -59,7 +67,7 @@ impl project::Item for ImageItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
|
fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
|
||||||
None
|
Some(self.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
|
fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
|
||||||
|
@ -68,18 +76,30 @@ impl project::Item for ImageItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ImageView {
|
pub struct ImageView {
|
||||||
path: PathBuf,
|
image: Model<ImageItem>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item for ImageView {
|
impl Item for ImageView {
|
||||||
type Event = ();
|
type Event = ();
|
||||||
|
|
||||||
fn tab_content(&self, params: TabContentParams, _cx: &WindowContext) -> AnyElement {
|
fn for_each_project_item(
|
||||||
let title = self
|
&self,
|
||||||
.path
|
cx: &AppContext,
|
||||||
|
f: &mut dyn FnMut(gpui::EntityId, &dyn project::Item),
|
||||||
|
) {
|
||||||
|
f(self.image.entity_id(), self.image.read(cx))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_singleton(&self, _cx: &AppContext) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
|
||||||
|
let path = &self.image.read(cx).path;
|
||||||
|
let title = path
|
||||||
.file_name()
|
.file_name()
|
||||||
.unwrap_or_else(|| self.path.as_os_str())
|
.unwrap_or_else(|| path.as_os_str())
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string();
|
.to_string();
|
||||||
Label::new(title)
|
Label::new(title)
|
||||||
|
@ -90,9 +110,10 @@ impl Item for ImageView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_icon(&self, cx: &WindowContext) -> Option<Icon> {
|
fn tab_icon(&self, cx: &WindowContext) -> Option<Icon> {
|
||||||
|
let path = &self.image.read(cx).path;
|
||||||
ItemSettings::get_global(cx)
|
ItemSettings::get_global(cx)
|
||||||
.file_icons
|
.file_icons
|
||||||
.then(|| FileIcons::get_icon(self.path.as_path(), cx))
|
.then(|| FileIcons::get_icon(path.as_path(), cx))
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(Icon::from_path)
|
.map(Icon::from_path)
|
||||||
}
|
}
|
||||||
|
@ -106,7 +127,7 @@ impl Item for ImageView {
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
Some(cx.new_view(|cx| Self {
|
Some(cx.new_view(|cx| Self {
|
||||||
path: self.path.clone(),
|
image: self.image.clone(),
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -118,7 +139,7 @@ impl SerializableItem for ImageView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize(
|
fn deserialize(
|
||||||
_project: Model<Project>,
|
project: Model<Project>,
|
||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
workspace_id: WorkspaceId,
|
workspace_id: WorkspaceId,
|
||||||
item_id: ItemId,
|
item_id: ItemId,
|
||||||
|
@ -129,10 +150,38 @@ impl SerializableItem for ImageView {
|
||||||
.get_image_path(item_id, workspace_id)?
|
.get_image_path(item_id, workspace_id)?
|
||||||
.ok_or_else(|| anyhow::anyhow!("No image path found"))?;
|
.ok_or_else(|| anyhow::anyhow!("No image path found"))?;
|
||||||
|
|
||||||
cx.new_view(|cx| ImageView {
|
let (worktree, relative_path) = project
|
||||||
path: image_path,
|
.update(&mut cx, |project, cx| {
|
||||||
focus_handle: cx.focus_handle(),
|
project.find_or_create_worktree(image_path.clone(), false, cx)
|
||||||
})
|
})?
|
||||||
|
.await
|
||||||
|
.context("Path not found")?;
|
||||||
|
let worktree_id = worktree.update(&mut cx, |worktree, _cx| worktree.id())?;
|
||||||
|
|
||||||
|
let project_path = ProjectPath {
|
||||||
|
worktree_id,
|
||||||
|
path: relative_path.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let id = project
|
||||||
|
.update(&mut cx, |project, cx| {
|
||||||
|
project.entry_for_path(&project_path, cx)
|
||||||
|
})?
|
||||||
|
.context("No entry found")?
|
||||||
|
.id;
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
let image = cx.new_model(|_| ImageItem {
|
||||||
|
id,
|
||||||
|
path: image_path,
|
||||||
|
project_path,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(cx.new_view(|cx| ImageView {
|
||||||
|
image,
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
}))
|
||||||
|
})?
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +203,7 @@ impl SerializableItem for ImageView {
|
||||||
let workspace_id = workspace.database_id()?;
|
let workspace_id = workspace.database_id()?;
|
||||||
|
|
||||||
Some(cx.background_executor().spawn({
|
Some(cx.background_executor().spawn({
|
||||||
let image_path = self.path.clone();
|
let image_path = self.image.read(cx).path.clone();
|
||||||
async move {
|
async move {
|
||||||
IMAGE_VIEWER
|
IMAGE_VIEWER
|
||||||
.save_image_path(item_id, workspace_id, image_path)
|
.save_image_path(item_id, workspace_id, image_path)
|
||||||
|
@ -177,6 +226,7 @@ impl FocusableView for ImageView {
|
||||||
|
|
||||||
impl Render for ImageView {
|
impl Render for ImageView {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let image_path = self.image.read(cx).path.clone();
|
||||||
let checkered_background = |bounds: Bounds<Pixels>, _, cx: &mut WindowContext| {
|
let checkered_background = |bounds: Bounds<Pixels>, _, cx: &mut WindowContext| {
|
||||||
let square_size = 32.0;
|
let square_size = 32.0;
|
||||||
|
|
||||||
|
@ -233,7 +283,7 @@ impl Render for ImageView {
|
||||||
// TODO: In browser based Tailwind & Flex this would be h-screen and we'd use w-full
|
// TODO: In browser based Tailwind & Flex this would be h-screen and we'd use w-full
|
||||||
.h_full()
|
.h_full()
|
||||||
.child(
|
.child(
|
||||||
img(self.path.clone())
|
img(image_path)
|
||||||
.object_fit(ObjectFit::ScaleDown)
|
.object_fit(ObjectFit::ScaleDown)
|
||||||
.max_w_full()
|
.max_w_full()
|
||||||
.max_h_full(),
|
.max_h_full(),
|
||||||
|
@ -254,7 +304,7 @@ impl ProjectItem for ImageView {
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
path: item.read(cx).path.clone(),
|
image: item,
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue